diff --git a/src/screens/Recovery/Lightning.tsx b/src/screens/Recovery/Lightning.tsx
new file mode 100644
index 000000000..140a10d4c
--- /dev/null
+++ b/src/screens/Recovery/Lightning.tsx
@@ -0,0 +1,297 @@
+import React, { ReactElement, useEffect, useState } from 'react';
+import { ActivityIndicator, StyleSheet, View } from 'react-native';
+import { useTranslation } from 'react-i18next';
+
+import lm, { ldk, TLdkData } from '@synonymdev/react-native-ldk';
+import { View as ThemedView } from '../../styles/components';
+import List, { EItemType, IListData, ItemData } from '../../components/List';
+
+import NavigationHeader from '../../components/NavigationHeader';
+import SafeAreaInset from '../../components/SafeAreaInset';
+import Button from '../../components/Button';
+import { RecoveryStackScreenProps } from '../../navigation/types';
+import { useSelectedSlashtag } from '../../hooks/slashtags';
+import { SlashtagsProvider } from '../../components/SlashtagsProvider';
+import {
+ EBackupCategories,
+ fetchBackup,
+ listBackups,
+} from '../../utils/backup/backpack';
+import { EAvailableNetworks } from '../../utils/networks';
+import Dialog from '../../components/Dialog';
+import { startWalletServices } from '../../utils/startup';
+import { showToast } from '../../utils/notifications';
+import RNExitApp from 'react-native-exit-app';
+import { selectedNetworkSelector } from '../../store/reselect/wallet';
+import { useSelector } from 'react-redux';
+import { bytesToString } from '../../utils/converters';
+import { setLdkStoragePath } from '../../utils/lightning';
+import { TAccountBackup } from '../../store/types/backup';
+const Lightning = (
+ props: RecoveryStackScreenProps<'Lightning'>,
+): ReactElement => {
+ return (
+
+
+
+ );
+};
+
+const LightningWithSlashtags = ({
+ navigation,
+}: RecoveryStackScreenProps<'Lightning'>): ReactElement => {
+ const { t } = useTranslation('security');
+ const slashtag = useSelectedSlashtag();
+ const [history, setHistory] = useState({
+ title: 'Loading backups...',
+ data: [],
+ });
+ const [showConfirmRecoveryDialog, setShowConfirmRecoveryDialog] =
+ useState(false);
+ const [isFetchingBackup, setIsFetchingBackup] = useState(false);
+ const [backup, setBackup] = useState | null>(null);
+ const [isRecoveringChannels, setIsRecoveringChannels] = useState(false);
+ const [recoveredSats, setRecoveredSats] = useState(0);
+ const [showLdkRecoverySuccessDialog, setShowLdkRecoverySuccessDialog] =
+ useState(false);
+ const selectedNetwork = useSelector(selectedNetworkSelector);
+
+ //On mount
+ useEffect(() => {
+ if (!slashtag || history.data.length > 0) {
+ return;
+ }
+
+ const listLdkBackups = async (): Promise => {
+ const res = await listBackups(
+ slashtag.slashtag,
+ EBackupCategories.ldkComplete,
+ EAvailableNetworks.bitcoin,
+ );
+
+ if (res.isErr()) {
+ console.error(res.error);
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: res.error.message,
+ });
+ return;
+ }
+
+ const data: ItemData[] = res.value.map(({ timestamp }) => {
+ return {
+ title: `${new Date(timestamp).toLocaleString()}`,
+ enabled: true,
+ type: EItemType.button,
+ onPress: async (): Promise =>
+ confirmRestoreFromBackup(timestamp),
+ };
+ });
+
+ setHistory({
+ data: data,
+ });
+ };
+
+ listLdkBackups().catch((e) => console.log(e));
+ });
+
+ const onBack = (): void => {
+ console.warn(JSON.stringify(navigation));
+ navigation.goBack();
+ };
+
+ const confirmRestoreFromBackup = async (timestamp: number): Promise => {
+ if (isFetchingBackup) {
+ return;
+ }
+
+ setIsFetchingBackup(true);
+ const res = await fetchBackup(
+ slashtag.slashtag,
+ timestamp,
+ EBackupCategories.ldkComplete,
+ selectedNetwork,
+ );
+
+ if (res.isErr()) {
+ console.log(res.error);
+ setIsFetchingBackup(false);
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: res.error.message,
+ });
+ return;
+ }
+
+ const bytesToStringRes = bytesToString(res.value.content);
+ if (bytesToStringRes.isErr()) {
+ console.log(bytesToStringRes.error);
+ setIsFetchingBackup(false);
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: bytesToStringRes.error.message,
+ });
+ return;
+ }
+
+ setBackup(JSON.parse(bytesToStringRes.value));
+ setIsFetchingBackup(false);
+ setShowConfirmRecoveryDialog(true);
+ };
+
+ const onShowLdkRecoveryConfirmed = async (): Promise => {
+ if (!backup) {
+ return;
+ }
+
+ if (Object.keys(backup.data.channel_monitors).length === 0) {
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: t('lightning_recovery_no_channels'),
+ });
+ return;
+ }
+
+ setShowConfirmRecoveryDialog(false);
+ setIsRecoveringChannels(true);
+
+ await ldk.stop();
+
+ const storageRes = await setLdkStoragePath();
+ if (storageRes.isErr()) {
+ console.error(storageRes.error);
+ setIsRecoveringChannels(false);
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: storageRes.error.message,
+ });
+ return;
+ }
+
+ const importRes = await lm.importAccount({
+ backup,
+ });
+ if (importRes.isErr()) {
+ console.error(importRes.error);
+ setIsRecoveringChannels(false);
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: importRes.error.message,
+ });
+ return;
+ }
+
+ const setupRes = await startWalletServices({
+ onchain: false,
+ lightning: true,
+ restore: false,
+ staleBackupRecoveryMode: true,
+ });
+
+ if (setupRes.isErr()) {
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: setupRes.error.message,
+ });
+ setIsRecoveringChannels(false);
+ return;
+ }
+
+ const balances = await ldk.claimableBalances(false);
+ if (balances.isErr()) {
+ showToast({
+ type: 'error',
+ title: t('lightning_recovery_error'),
+ description: balances.error.message,
+ });
+ setIsRecoveringChannels(false);
+ return;
+ }
+
+ await ldk.stop();
+
+ let sats = 0;
+ balances.value.forEach((balance) => {
+ sats += balance.claimable_amount_satoshis;
+ });
+
+ setRecoveredSats(sats);
+ setShowLdkRecoverySuccessDialog(true);
+ setIsRecoveringChannels(false);
+ };
+
+ const onCloseApp = (): void => {
+ RNExitApp.exitApp();
+ };
+
+ return (
+
+
+
+
+ {isRecoveringChannels ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ root: {
+ flex: 1,
+ },
+ content: {
+ flex: 1,
+ paddingHorizontal: 16,
+ },
+ buttonContainer: {
+ marginTop: 'auto',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
+ button: {
+ flex: 1,
+ },
+});
+
+export default Lightning;
diff --git a/src/screens/Recovery/Recovery.tsx b/src/screens/Recovery/Recovery.tsx
index be588a275..b2b4e80cc 100644
--- a/src/screens/Recovery/Recovery.tsx
+++ b/src/screens/Recovery/Recovery.tsx
@@ -4,7 +4,6 @@ import { useSelector } from 'react-redux';
import Share from 'react-native-share';
import { useTranslation } from 'react-i18next';
import RNExitApp from 'react-native-exit-app';
-import { ldk } from '@synonymdev/react-native-ldk';
import { wipeApp } from '../../store/actions/settings';
import { openURL } from '../../utils/helpers';
@@ -20,7 +19,6 @@ import Dialog from '../../components/Dialog';
import { RecoveryStackScreenProps } from '../../navigation/types';
import { walletExistsSelector } from '../../store/reselect/wallet';
import { pinSelector } from '../../store/reselect/settings';
-import { startWalletServices } from '../../utils/startup';
const Recovery = ({
navigation,
@@ -30,11 +28,6 @@ const Recovery = ({
const walletExists = useSelector(walletExistsSelector);
const [locked, setLocked] = useState(true);
const [showWipeDialog, setShowWipeDialog] = useState(false);
- const [showLdkRecoveryDialog, setShowLdkRecoveryDialog] = useState(false);
- const [showLdkRecoverySuccessDialog, setShowLdkRecoverySuccessDialog] =
- useState(false);
- const [recoveredSats, setRecoveredSats] = useState(0);
- const [isRecoveringChannels, setIsRecoveringChannels] = useState(false);
useEffect(() => {
// avoid accidentally pressing a button
@@ -80,12 +73,12 @@ const Recovery = ({
onSuccess: () => {
// hack needed for Android
setTimeout(() => {
- setShowLdkRecoveryDialog(true);
+ navigation.navigate('Lightning');
}, 100);
},
});
} else {
- setShowLdkRecoveryDialog(true);
+ navigation.navigate('Lightning');
}
};
@@ -114,50 +107,6 @@ const Recovery = ({
setShowWipeDialog(false);
};
- const onShowLdkRecoveryConfirmed = async (): Promise => {
- setShowLdkRecoveryDialog(false);
- setIsRecoveringChannels(true);
-
- const setupRes = await startWalletServices({
- onchain: false,
- lightning: true,
- restore: false,
- staleBackupRecoveryMode: true,
- });
- if (setupRes.isErr()) {
- showToast({
- type: 'error',
- title: t('lightning_recovery_error'),
- description: setupRes.error.message,
- });
- setIsRecoveringChannels(false);
- return;
- }
-
- const balances = await ldk.claimableBalances(false);
- if (balances.isErr()) {
- showToast({
- type: 'error',
- title: t('lightning_recovery_error'),
- description: balances.error.message,
- });
- setIsRecoveringChannels(false);
- return;
- }
-
- await ldk.stop();
-
- let sats = 0;
- balances.value.forEach((balance) => {
- sats += balance.claimable_amount_satoshis;
- });
- setRecoveredSats(sats);
- setShowLdkRecoverySuccessDialog(true);
- setIsRecoveringChannels(false);
-
- return;
- };
-
const onCloseApp = (): void => {
RNExitApp.exitApp();
};
@@ -189,7 +138,6 @@ const Recovery = ({
- setShowLdkRecoveryDialog(false)}
- onConfirm={onShowLdkRecoveryConfirmed}
- />
-
-
-
);
diff --git a/src/screens/Recovery/RecoveryNavigator.tsx b/src/screens/Recovery/RecoveryNavigator.tsx
index 3e2eec79e..00ebbe205 100644
--- a/src/screens/Recovery/RecoveryNavigator.tsx
+++ b/src/screens/Recovery/RecoveryNavigator.tsx
@@ -8,12 +8,14 @@ import { NavigationContainer } from '../../styles/components';
import AuthCheck from '../../components/AuthCheck';
import Recovery from '../../screens/Recovery/Recovery';
import Mnemonic from '../../screens/Recovery/Mnemonic';
+import Lightning from './Lightning';
import { __E2E__ } from '../../constants/env';
export type RecoveryStackParamList = {
AuthCheck: { onSuccess: () => void };
Recovery: undefined;
Mnemonic: undefined;
+ Lightning: undefined;
};
const Stack = createStackNavigator();
@@ -32,6 +34,7 @@ const RecoveryNavigator = (): ReactElement => {
+
);
diff --git a/src/store/actions/backup.ts b/src/store/actions/backup.ts
index 62faf2c8a..87016a087 100644
--- a/src/store/actions/backup.ts
+++ b/src/store/actions/backup.ts
@@ -575,7 +575,7 @@ export const setRemoteBackupsEnabled = (
});
};
-export const checkProfileAndContanctsBackup = async (
+export const checkProfileAndContactsBackup = async (
slashtag: Slashtag,
): Promise => {
dispatch({ type: actions.BACKUP_SEEDER_CHECK_START });
diff --git a/src/utils/backup/backups-subscriber.tsx b/src/utils/backup/backups-subscriber.tsx
index 84689e0f0..f7e9a9577 100644
--- a/src/utils/backup/backups-subscriber.tsx
+++ b/src/utils/backup/backups-subscriber.tsx
@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import {
- checkProfileAndContanctsBackup,
+ checkProfileAndContactsBackup,
performRemoteBackup,
performRemoteLdkBackup,
} from '../../store/actions/backup';
@@ -236,7 +236,7 @@ const EnabledSlashtag = (): ReactElement => {
}
const timer = setInterval(() => {
- checkProfileAndContanctsBackup(slashtag);
+ checkProfileAndContactsBackup(slashtag);
}, BACKUP_CHECK_INTERVAL);
return (): void => {
diff --git a/src/utils/i18n/locales/en/security.json b/src/utils/i18n/locales/en/security.json
index 7b222a972..8200d4088 100644
--- a/src/utils/i18n/locales/en/security.json
+++ b/src/utils/i18n/locales/en/security.json
@@ -74,10 +74,11 @@
"recovery": "Recovery",
"recovery_text": "You've entered Bitkit's recovery mode. Here are some actions to perform when running into issues that prevent the app from fully functioning. Restart the app for a normal startup.",
"lightning_recovery_title": "Lightning Recovery",
- "lightning_recovery_desc": "Warning: This will force close all your channels and recover any funds to your on-chain wallet. Only use this option if you are unable to access your lightning wallet due to a restore error.",
+ "lightning_recovery_desc": "Warning: This will force close all of your channels ({channelCount}) and recover any funds to your on-chain wallet. Only use this option if you are unable to access your lightning wallet due to a restore error.",
"lightning_recovery_success": "Lightning Recovery Success",
"lightning_recovery_success_message": "Pending balance {sats} sats recovered. Please reopen the app to see your recovered balance.",
"lightning_recovery_error": "Lightning Recovery Error",
+ "lightning_recovery_no_channels": "No lightning channels found in this backup to recover.",
"display_seed": "Show Seed Phrase",
"contact_support": "Contact Support",
"wipe_app": "Wipe App",