Skip to content

Commit

Permalink
Optional node manager
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Jun 6, 2024
1 parent 0da0dce commit 722f190
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 78 deletions.
234 changes: 159 additions & 75 deletions src/components/BalanceBox.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { A, useNavigate } from "@solidjs/router";
import { Shuffle, Users } from "lucide-solid";
import { createMemo, Match, Show, Suspense, Switch } from "solid-js";
import { Plus, Shuffle, Trash, Users } from "lucide-solid";
import {
createMemo,
createResource,
createSignal,
Match,
Show,
Suspense,
Switch
} from "solid-js";

import {
AmountFiat,
Expand All @@ -11,6 +19,7 @@ import {
InfoBox,
MediumHeader,
NiceP,
SubtleButton,
VStack
} from "~/components";
import { useI18n } from "~/i18n/context";
Expand Down Expand Up @@ -47,10 +56,14 @@ const STYLE =
"px-2 py-1 rounded-xl text-sm flex gap-2 items-center font-semibold";

export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
const [state, _actions] = useMegaStore();
const [state, _actions, sw] = useMegaStore();
const navigate = useNavigate();
const i18n = useI18n();

const [nodeManagerLoading, setNodeManagerLoading] = createSignal(false);

const lightningBalance = () => state.balance?.lightning || 0n;

const totalOnchain = createMemo(
() =>
(state.balance?.confirmed || 0n) +
Expand All @@ -64,6 +77,35 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
(state.balance?.unconfirmed || 0n)
);

const [hasSelfCustody, { refetch }] = createResource(async () => {
// short circuit if we have a balance
if (totalOnchain() > 0 || state.balance?.lightning || 0n > 0n) {
return true;
}

// otherwise check if we have created a node
const nodes: string[] = await sw.list_nodes();
return nodes.length > 0;
});

const createNodeManager = async () => {
if (confirm("Pass this test:")) {
setNodeManagerLoading(true);
await sw.create_node_manager_if_needed();
await refetch();
setNodeManagerLoading(false);
}
};

const removeNodeManager = async () => {
if (confirm("Are you sure:")) {
setNodeManagerLoading(true);
await sw.remove_node_manager();
await refetch();
setNodeManagerLoading(false);
}
};

return (
<VStack>
<Switch>
Expand Down Expand Up @@ -131,81 +173,123 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
</Match>
</Switch>
<MediumHeader>{i18n.t("profile.self_custody")}</MediumHeader>
<FancyCard>
<Show when={!props.loading} fallback={<LoadingShimmer />}>
<Switch>
<Match when={state.safe_mode}>
<div class="flex flex-col gap-1">
<InfoBox accent="red">
{i18n.t("common.error_safe_mode")}
</InfoBox>
</div>
</Match>
<Match when={true}>
<div class="flex flex-col gap-1">
<div class="text-2xl">
<AmountSats
amountSats={
state.balance?.lightning || 0
}
icon="lightning"
denominationSize="lg"
/>
</div>
<div class="text-lg text-white/70">
<Suspense>
<AmountFiat
amountSats={
state.balance?.lightning || 0
}
denominationSize="sm"
/>
</Suspense>
</div>
</div>
</Match>
</Switch>
</Show>
<hr class="my-2 border-m-grey-750" />
<Show when={!props.loading} fallback={<LoadingShimmer />}>
<div class="flex justify-between">
<div class="flex flex-col gap-1">
<div class="text-2xl">
<AmountSats
amountSats={totalOnchain()}
icon="chain"
denominationSize="lg"
/>
</div>
<div class="text-lg text-white/70">
<Suspense>
<AmountFiat
amountSats={totalOnchain()}
denominationSize="sm"
/>
</Suspense>
</div>
</div>
<div class="flex flex-col items-end justify-between gap-1">
<Show when={state.balance?.unconfirmed != 0n}>
<Indicator>
{i18n.t("common.pending")}
</Indicator>
</Show>
<Show when={state.balance?.unconfirmed === 0n}>
<div />
<Suspense>
<Switch>
<Match when={hasSelfCustody()}>
<FancyCard>
<Show
when={!props.loading}
fallback={<LoadingShimmer />}
>
<Switch>
<Match when={state.safe_mode}>
<div class="flex flex-col gap-1">
<InfoBox accent="red">
{i18n.t(
"common.error_safe_mode"
)}
</InfoBox>
</div>
</Match>
<Match when={true}>
<div class="flex flex-col gap-1">
<div class="text-2xl">
<AmountSats
amountSats={lightningBalance()}
icon="lightning"
denominationSize="lg"
/>
</div>
<div class="text-lg text-white/70">
<Suspense>
<AmountFiat
amountSats={lightningBalance()}
denominationSize="sm"
/>
</Suspense>
</div>
</div>
</Match>
</Switch>
</Show>
<Show when={usableOnchain() > 0n}>
<div class="self-end justify-self-end">
<A href="/swap" class={STYLE}>
<Shuffle class="h-6 w-6" />
</A>
<hr class="my-2 border-m-grey-750" />
<Show
when={!props.loading}
fallback={<LoadingShimmer />}
>
<div class="flex justify-between">
<div class="flex flex-col gap-1">
<div class="text-2xl">
<AmountSats
amountSats={totalOnchain()}
icon="chain"
denominationSize="lg"
/>
</div>
<div class="text-lg text-white/70">
<Suspense>
<AmountFiat
amountSats={totalOnchain()}
denominationSize="sm"
/>
</Suspense>
</div>
</div>
<div class="flex flex-col items-end justify-between gap-1">
<Show
when={
state.balance?.unconfirmed != 0n
}
>
<Indicator>
{i18n.t("common.pending")}
</Indicator>
</Show>
<Show
when={
state.balance?.unconfirmed ===
0n
}
>
<div />
</Show>
<Show when={usableOnchain() > 0n}>
<div class="self-end justify-self-end">
<A href="/swap" class={STYLE}>
<Shuffle class="h-6 w-6" />
</A>
</div>
</Show>
</div>
</div>
<Show
when={
totalOnchain() === 0n &&
lightningBalance() === 0n &&
state.federations &&
state.federations.length
}
>
<SubtleButton
onClick={removeNodeManager}
loading={nodeManagerLoading()}
>
<Trash class="h-4 w-4" />
</SubtleButton>
</Show>
</Show>
</div>
</div>
</Show>
</FancyCard>
</FancyCard>
</Match>
<Match when={true}>
<SubtleButton
onClick={createNodeManager}
loading={nodeManagerLoading()}
>
<Plus class="h-4 w-4" />
</SubtleButton>
</Match>
</Switch>
</Suspense>
</VStack>
);
}
3 changes: 3 additions & 0 deletions src/components/HomePrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export function HomePrompt() {
lsps_token: params.token
};
try {
// If we're setting an LSPS config, we want a node manager
await sw.create_node_manager_if_needed();

await sw.change_lsp(
values.lsp ? values.lsp : undefined,
values.lsps_connection_string
Expand Down
3 changes: 3 additions & 0 deletions src/routes/setup/AddFederation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ import {
MutinyWalletGuard
} from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";

import { AddFederationForm } from "../settings";

export function AddFederation() {
const i18n = useI18n();
const navigate = useNavigate();
const [_state, _actions, sw] = useMegaStore();

const [confirmOpen, setConfirmOpen] = createSignal(false);

async function handleSkip() {
await sw.create_node_manager_if_needed();
navigate("/");
}

Expand Down
13 changes: 12 additions & 1 deletion src/state/megaStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export const makeMegaStoreContext = () => {
await sw.initializeWasm();

setState({ load_stage: "checking_for_existing_wallet" });
const existing = await sw.has_node_manager();
const existing = await sw.is_wallet_present();

if (!existing && !searchParams.skip_setup) {
navigate("/setup");
Expand Down Expand Up @@ -337,6 +337,17 @@ export const makeMegaStoreContext = () => {
},
60 * 1000 * state.price_sync_backoff_multiple
); // Poll every minute * backoff multiple

// handle if it is an empty wallet (we have no federations or nodes), take them to the add federation page.
// This will either force them to pick a federation or create a node manager.
const nodes: string[] = await sw.list_nodes();
const numFederations = state.federations
? state.federations.length
: 0;

if (nodes.length === 0 && numFederations === 0) {
navigate("/addfederation");
}
},
async deleteMutinyWallet(): Promise<void> {
try {
Expand Down
21 changes: 19 additions & 2 deletions src/workers/walletWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ export async function stop(): Promise<void> {
await wallet!.stop();
}

/**
* Removes the node manager from the wallet. If the node manager has any balances, this function will fail.
* @returns {Promise<void>}
*/
export async function remove_node_manager(): Promise<void> {
await wallet!.remove_node_manager();

Check failure on line 235 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / Build APK

Property 'remove_node_manager' does not exist on type 'MutinyWallet'.

Check failure on line 235 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / Build iOS

Property 'remove_node_manager' does not exist on type 'MutinyWallet'.

Check failure on line 235 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / code_quality

Property 'remove_node_manager' does not exist on type 'MutinyWallet'.
}

/**
* Clears storage and deletes all data.
*
Expand Down Expand Up @@ -546,6 +554,7 @@ export async function estimate_tx_fee(
const fee = await wallet!.estimate_tx_fee(address, amount, feeRate);
return fee;
}

/**
* Calls upon a LNURL to get the parameters for it.
* This contains what kind of LNURL it is (pay, withdrawal, auth, etc).
Expand Down Expand Up @@ -1198,6 +1207,14 @@ export async function start(): Promise<void> {
await wallet!.start();
}

/**
* Creates a node manager if we don't have one already.
* @returns {Promise<void>}
*/
export async function create_node_manager_if_needed(): Promise<void> {
await wallet!.create_node_manager_if_needed();

Check failure on line 1215 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / Build APK

Property 'create_node_manager_if_needed' does not exist on type 'MutinyWallet'.

Check failure on line 1215 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / Build iOS

Property 'create_node_manager_if_needed' does not exist on type 'MutinyWallet'.

Check failure on line 1215 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / code_quality

Property 'create_node_manager_if_needed' does not exist on type 'MutinyWallet'.
}

/**
* Authenticates with a LNURL-auth for the given profile.
* @param {string} lnurl
Expand Down Expand Up @@ -1416,8 +1433,8 @@ export async function convert_btc_to_sats(btc: number): Promise<bigint> {
* This is checked by seeing if a mnemonic seed exists in storage.
* @returns {Promise<boolean>}
*/
export async function has_node_manager(): Promise<boolean> {
return await MutinyWallet.has_node_manager();
export async function is_wallet_present(): Promise<boolean> {
return await MutinyWallet.is_wallet_present();

Check failure on line 1437 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / Build APK

Property 'is_wallet_present' does not exist on type 'typeof MutinyWallet'.

Check failure on line 1437 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / Build iOS

Property 'is_wallet_present' does not exist on type 'typeof MutinyWallet'.

Check failure on line 1437 in src/workers/walletWorker.ts

View workflow job for this annotation

GitHub Actions / code_quality

Property 'is_wallet_present' does not exist on type 'typeof MutinyWallet'.
}

/**
Expand Down

0 comments on commit 722f190

Please sign in to comment.