diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index e0914424..c528216a 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -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, @@ -11,6 +19,7 @@ import { InfoBox, MediumHeader, NiceP, + SubtleButton, VStack } from "~/components"; import { useI18n } from "~/i18n/context"; @@ -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) + @@ -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 ( @@ -131,81 +173,123 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) { {i18n.t("profile.self_custody")} - - }> - - -
- - {i18n.t("common.error_safe_mode")} - -
-
- -
-
- -
-
- - - -
-
-
-
-
-
- }> -
-
-
- -
-
- - - -
-
-
- - - {i18n.t("common.pending")} - - - -
+ + + + + } + > + + +
+ + {i18n.t( + "common.error_safe_mode" + )} + +
+
+ +
+
+ +
+
+ + + +
+
+
+
- 0n}> -
- - - +
+ } + > +
+
+
+ +
+
+ + + +
+
+
+ + + {i18n.t("common.pending")} + + + +
+ + 0n}> +
+ + + +
+
+
+ + + + + -
-
-
-
+ +
+ + + + + +
+
); } diff --git a/src/components/HomePrompt.tsx b/src/components/HomePrompt.tsx index b118ae85..8506f2e1 100644 --- a/src/components/HomePrompt.tsx +++ b/src/components/HomePrompt.tsx @@ -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 diff --git a/src/routes/SwapLightning.tsx b/src/routes/SwapLightning.tsx index ef0df62a..58c8511d 100644 --- a/src/routes/SwapLightning.tsx +++ b/src/routes/SwapLightning.tsx @@ -126,6 +126,8 @@ export function SwapLightning() { setLoading(true); setFeeEstimateWarning(undefined); + await sw.create_node_manager_if_needed(); + const mutinyInvoice = await sw.create_sweep_federation_invoice( isMax() ? undefined : amountSats() ); diff --git a/src/routes/setup/AddFederation.tsx b/src/routes/setup/AddFederation.tsx index 5841a4ae..4cd0b5da 100644 --- a/src/routes/setup/AddFederation.tsx +++ b/src/routes/setup/AddFederation.tsx @@ -6,20 +6,33 @@ import { ConfirmDialog, DefaultMain, ExternalLink, - MutinyWalletGuard + MutinyWalletGuard, + showToast } from "~/components"; import { useI18n } from "~/i18n/context"; +import { useMegaStore } from "~/state/megaStore"; +import { eify } from "~/utils"; import { AddFederationForm } from "../settings"; export function AddFederation() { const i18n = useI18n(); const navigate = useNavigate(); + const [_state, _actions, sw] = useMegaStore(); const [confirmOpen, setConfirmOpen] = createSignal(false); + const [confirmLoading, setConfirmLoading] = createSignal(false); async function handleSkip() { - navigate("/"); + setConfirmLoading(true); + try { + await sw.create_node_manager_if_needed(); + navigate("/"); + } catch (e) { + console.error(e); + setConfirmLoading(false); + showToast(eify(e)); + } } return ( @@ -41,7 +54,7 @@ export function AddFederation() { setConfirmOpen(false)} > diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index 3974da4a..2de2f09a 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -155,7 +155,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"); @@ -379,6 +379,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 { try { diff --git a/src/workers/walletWorker.ts b/src/workers/walletWorker.ts index 06cddc05..7f3eebff 100644 --- a/src/workers/walletWorker.ts +++ b/src/workers/walletWorker.ts @@ -282,6 +282,14 @@ export async function stop(): Promise { await wallet!.stop(); } +/** + * Removes the node manager from the wallet. If the node manager has any balances, this function will fail. + * @returns {Promise} + */ +export async function remove_node_manager(): Promise { + await wallet!.remove_node_manager(); +} + /** * Clears storage and deletes all data. * @@ -601,6 +609,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). @@ -1253,6 +1262,14 @@ export async function start(): Promise { await wallet!.start(); } +/** + * Creates a node manager if we don't have one already. + * @returns {Promise} + */ +export async function create_node_manager_if_needed(): Promise { + await wallet!.create_node_manager_if_needed(); +} + /** * Authenticates with a LNURL-auth for the given profile. * @param {string} lnurl @@ -1471,8 +1488,8 @@ export async function convert_btc_to_sats(btc: number): Promise { * This is checked by seeing if a mnemonic seed exists in storage. * @returns {Promise} */ -export async function has_node_manager(): Promise { - return await MutinyWallet.has_node_manager(); +export async function is_wallet_present(): Promise { + return await MutinyWallet.is_wallet_present(); } /**