diff --git a/e2e/lightning.e2e.js b/e2e/lightning.e2e.js
index 6d18bbbd0..8e6fdf854 100644
--- a/e2e/lightning.e2e.js
+++ b/e2e/lightning.e2e.js
@@ -92,9 +92,11 @@ d('Lightning', () => {
).getAttributes();
await element(by.id('NavigationBack')).atIndex(0).tap();
await sleep(100);
+ await element(by.id('NavigationBack')).atIndex(0).tap();
+ await element(by.id('DevSettings')).tap();
+ await element(by.id('LDKDebug')).tap();
// connect to LND
- await element(by.id('Channels')).tap();
await element(by.id('AddPeerInput')).replaceText(
`${lndNodeID}@127.0.0.1:9735`,
);
@@ -145,6 +147,8 @@ d('Lightning', () => {
await sleep(500);
await element(by.id('NavigationBack')).atIndex(0).tap();
await sleep(100);
+ await element(by.id('NavigationBack')).atIndex(0).tap();
+ await element(by.id('AdvancedSettings')).atIndex(0).tap();
await element(by.id('Channels')).tap();
await element(by.id('Channel')).atIndex(0).tap();
await expect(
diff --git a/e2e/settings.e2e.js b/e2e/settings.e2e.js
index f1b42e6f8..0a0536e56 100644
--- a/e2e/settings.e2e.js
+++ b/e2e/settings.e2e.js
@@ -407,8 +407,8 @@ d('Settings', () => {
if (!__DEV__) {
await element(by.id('DevOptions')).multiTap(5); // enable dev mode
}
- await element(by.id('AdvancedSettings')).tap();
- await element(by.id('Channels')).tap();
+ await element(by.id('DevSettings')).tap();
+ await element(by.id('LDKDebug')).tap();
await element(by.id('CopyNodeId')).tap();
await element(by.id('RefreshLDK')).tap();
await element(by.id('RestartLDK')).tap();
@@ -417,7 +417,8 @@ d('Settings', () => {
.toBeVisible()
.withTimeout(5000);
await element(by.id('NavigationBack')).atIndex(0).tap();
-
+ await element(by.id('NavigationBack')).atIndex(0).tap();
+ await element(by.id('AdvancedSettings')).tap();
await element(by.id('LightningNodeInfo')).tap();
// TODO: this fails too often on CI
// await waitFor(element(by.id('LDKNodeID')))
diff --git a/src/navigation/settings/SettingsNavigator.tsx b/src/navigation/settings/SettingsNavigator.tsx
index b42e01b74..0656f0810 100644
--- a/src/navigation/settings/SettingsNavigator.tsx
+++ b/src/navigation/settings/SettingsNavigator.tsx
@@ -15,6 +15,7 @@ import CoinSelectPreference from '../../screens/Settings/CoinSelectPreference';
import PaymentPreference from '../../screens/Settings/PaymentPreference';
import AddressTypePreference from '../../screens/Settings/AddressTypePreference';
import DevSettings from '../../screens/Settings/DevSettings';
+import LdkDebug from '../../screens/Settings/DevSettings/LdkDebug';
import AddressViewer from '../../screens/Settings/AddressViewer';
import LightningNodeInfo from '../../screens/Settings/Lightning/LightningNodeInfo';
import UnitSettings from '../../screens/Settings/Unit';
@@ -91,6 +92,7 @@ export type SettingsStackParamList = {
PaymentPreference: undefined;
AddressTypePreference: undefined;
DevSettings: undefined;
+ LdkDebug: undefined;
ExportToPhone: undefined;
ResetAndRestore: undefined;
BitcoinNetworkSelection: undefined;
@@ -160,6 +162,7 @@ const SettingsNavigator = (): ReactElement => {
component={AddressTypePreference}
/>
+
diff --git a/src/screens/Settings/DevSettings/LdkDebug.tsx b/src/screens/Settings/DevSettings/LdkDebug.tsx
new file mode 100644
index 000000000..dcbf2652e
--- /dev/null
+++ b/src/screens/Settings/DevSettings/LdkDebug.tsx
@@ -0,0 +1,433 @@
+import React, { ReactElement, memo, useState } from 'react';
+import { StyleSheet, ScrollView } from 'react-native';
+import Share from 'react-native-share';
+import { useTranslation } from 'react-i18next';
+import Clipboard from '@react-native-clipboard/clipboard';
+import lm from '@synonymdev/react-native-ldk';
+
+import { Caption13Up } from '../../../styles/text';
+import { View as ThemedView, TextInput } from '../../../styles/components';
+import SafeAreaInset from '../../../components/SafeAreaInset';
+import Button from '../../../components/buttons/Button';
+import NavigationHeader from '../../../components/NavigationHeader';
+import { useLightningBalance } from '../../../hooks/lightning';
+import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
+import { zipLogs } from '../../../utils/lightning/logs';
+import { showToast } from '../../../utils/notifications';
+import {
+ addPeer,
+ getNodeId,
+ payLightningInvoice,
+ rebroadcastAllKnownTransactions,
+ recoverOutputs,
+ recoverOutputsFromForceClose,
+ refreshLdk,
+ setupLdk,
+ removeUnusedPeers,
+} from '../../../utils/lightning';
+import { openChannelsSelector } from '../../../store/reselect/lightning';
+import { showBottomSheet } from '../../../store/utils/ui';
+import { removeLightningPeer } from '../../../store/slices/lightning';
+import {
+ createLightningInvoice,
+ savePeer,
+} from '../../../store/utils/lightning';
+import {
+ selectedNetworkSelector,
+ selectedWalletSelector,
+} from '../../../store/reselect/wallet';
+
+const LdkDebug = (): ReactElement => {
+ const { t } = useTranslation('lightning');
+ const dispatch = useAppDispatch();
+ const [peer, setPeer] = useState('');
+ const [payingInvoice, setPayingInvoice] = useState(false);
+ const [refreshingLdk, setRefreshingLdk] = useState(false);
+ const [restartingLdk, setRestartingLdk] = useState(false);
+ const [rebroadcastingLdk, setRebroadcastingLdk] = useState(false);
+ const [spendingStuckOutputs, setSpendingStuckOutputs] = useState(false);
+
+ const { localBalance, remoteBalance } = useLightningBalance();
+ const selectedWallet = useAppSelector(selectedWalletSelector);
+ const selectedNetwork = useAppSelector(selectedNetworkSelector);
+ const openChannels = useAppSelector(openChannelsSelector);
+
+ const onNodeId = async (): Promise => {
+ const nodeId = await getNodeId();
+ if (nodeId.isErr()) {
+ console.log(nodeId.error.message);
+ return;
+ }
+ console.log(`Node ID: ${nodeId.value}`);
+ Clipboard.setString(nodeId.value);
+ showToast({
+ type: 'success',
+ title: 'Copied Node ID to Clipboard',
+ description: nodeId.value,
+ });
+ };
+
+ const onRefreshLdk = async (): Promise => {
+ setRefreshingLdk(true);
+ await refreshLdk({ selectedWallet, selectedNetwork });
+ setRefreshingLdk(false);
+ };
+
+ const onRestartLdk = async (): Promise => {
+ setRestartingLdk(true);
+ await setupLdk({ selectedWallet, selectedNetwork });
+ setRestartingLdk(false);
+ };
+
+ const onAddPeer = async (): Promise => {
+ if (!peer) {
+ // Attempt to grab and set peer string from clipboard.
+ const clipboardStr = await Clipboard.getString();
+ setPeer(clipboardStr);
+ return;
+ }
+ const addPeerRes = await addPeer({ peer, timeout: 5000 });
+ if (addPeerRes.isErr()) {
+ showToast({
+ type: 'warning',
+ title: t('error_add_title'),
+ description: addPeerRes.error.message,
+ });
+ return;
+ }
+ const savePeerRes = savePeer({ selectedWallet, selectedNetwork, peer });
+ if (savePeerRes.isErr()) {
+ showToast({
+ type: 'warning',
+ title: t('error_save_title'),
+ description: savePeerRes.error.message,
+ });
+ return;
+ }
+ showToast({
+ type: 'success',
+ title: savePeerRes.value,
+ description: t('peer_saved'),
+ });
+ };
+
+ const onListPeers = async (): Promise => {
+ const peers = await lm.getPeers();
+ console.log({ peers });
+ };
+
+ const onDisconnectPeers = async (): Promise => {
+ const peers = await lm.getPeers();
+
+ const promises = peers.map(({ pubKey, address, port }) => {
+ const peerStr = `${pubKey}@${address}:${port}`;
+ // Remove peer from local storage
+ dispatch(
+ removeLightningPeer({
+ peer: peerStr,
+ selectedWallet,
+ selectedNetwork,
+ }),
+ );
+ // Instruct LDK to disconnect from peer
+ return lm.removePeer({ pubKey, address, port, timeout: 5000 });
+ });
+
+ const results = await Promise.all(promises);
+ for (const result of results) {
+ if (result.isOk()) {
+ console.log('Disconnected from peer.');
+ } else {
+ console.error(`Failed to disconnect: ${result.error.message}`);
+ }
+ }
+ };
+
+ const onRemoveUnusedPeers = async (): Promise => {
+ const res = await removeUnusedPeers({ selectedWallet, selectedNetwork });
+ if (res.isErr()) {
+ showToast({
+ type: 'warning',
+ title: 'No unused peers removed',
+ description: res.error.message,
+ });
+ } else {
+ showToast({
+ type: 'info',
+ title: 'Removed unused peers',
+ description: res.value,
+ });
+ }
+ };
+
+ const onExportLogs = async (): Promise => {
+ const result = await zipLogs();
+ if (result.isErr()) {
+ showToast({
+ type: 'warning',
+ title: t('error_logs'),
+ description: t('error_logs_description'),
+ });
+ return;
+ }
+
+ // Share the zip file
+ await Share.open({
+ type: 'application/zip',
+ url: `file://${result.value}`,
+ title: t('export_logs'),
+ });
+ };
+
+ const onCreateInvoice = async (amountSats = 100): Promise => {
+ const createPaymentRequest = await createLightningInvoice({
+ amountSats,
+ description: '',
+ expiryDeltaSeconds: 99999,
+ selectedNetwork,
+ selectedWallet,
+ });
+ if (createPaymentRequest.isErr()) {
+ showToast({
+ type: 'warning',
+ title: t('error_invoice'),
+ description: createPaymentRequest.error.message,
+ });
+ return;
+ }
+ const { to_str } = createPaymentRequest.value;
+ console.log(to_str);
+ Clipboard.setString(to_str);
+ showToast({
+ type: 'success',
+ title: t('invoice_copied'),
+ description: to_str,
+ });
+ };
+
+ const onRebroadcastLdkTxs = async (): Promise => {
+ setRebroadcastingLdk(true);
+ await rebroadcastAllKnownTransactions();
+ setRebroadcastingLdk(false);
+ };
+
+ const onSpendStuckOutputs = async (): Promise => {
+ setSpendingStuckOutputs(true);
+ const res = await recoverOutputs();
+ if (res.isOk()) {
+ showToast({
+ type: 'info',
+ title: 'Stuck outputs recovered',
+ description: res.value,
+ });
+ } else {
+ showToast({
+ type: 'warning',
+ title: 'No stuck outputs recovered',
+ description: res.error.message,
+ });
+ }
+ setSpendingStuckOutputs(false);
+ };
+
+ const onForceCloseChannels = (): void => {
+ showBottomSheet('forceTransfer');
+ };
+
+ const onSpendOutputsFromForceClose = async (): Promise => {
+ setSpendingStuckOutputs(true);
+ const res = await recoverOutputsFromForceClose();
+ if (res.isOk()) {
+ showToast({
+ type: 'info',
+ title: 'Completed',
+ description: res.value,
+ });
+ } else {
+ showToast({
+ type: 'warning',
+ title: 'No stuck outputs recovered',
+ description: res.error.message,
+ });
+ }
+ setSpendingStuckOutputs(false);
+ };
+
+ const onPayInvoiceFromClipboard = async (): Promise => {
+ setPayingInvoice(true);
+ const invoice = await Clipboard.getString();
+ if (!invoice) {
+ showToast({
+ type: 'warning',
+ title: 'No Invoice Detected',
+ description: 'Unable to retrieve anything from the clipboard.',
+ });
+ setPayingInvoice(false);
+ return;
+ }
+ const response = await payLightningInvoice({ invoice });
+ if (response.isErr()) {
+ showToast({
+ type: 'warning',
+ title: 'Invoice Payment Failed',
+ description: response.error.message,
+ });
+ setPayingInvoice(false);
+ return;
+ }
+ await refreshLdk({ selectedWallet, selectedNetwork });
+ setPayingInvoice(false);
+ };
+
+ return (
+
+
+
+
+ Add Peer
+
+
+
+
+ Debug
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {openChannels.length > 0 && (
+ <>
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ root: {
+ flex: 1,
+ },
+ content: {
+ flexGrow: 1,
+ paddingHorizontal: 16,
+ },
+ sectionTitle: {
+ marginTop: 32,
+ },
+ textInput: {
+ width: '100%',
+ minHeight: 50,
+ borderRadius: 10,
+ padding: 10,
+ textAlign: 'left',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontWeight: 'bold',
+ fontSize: 16,
+ marginTop: 8,
+ },
+ button: {
+ marginTop: 8,
+ },
+});
+
+export default memo(LdkDebug);
diff --git a/src/screens/Settings/DevSettings/index.tsx b/src/screens/Settings/DevSettings/index.tsx
index 5a07160f2..6f8501a94 100644
--- a/src/screens/Settings/DevSettings/index.tsx
+++ b/src/screens/Settings/DevSettings/index.tsx
@@ -149,7 +149,16 @@ const DevSettings = ({
},
{
title: 'Debug',
- data: [],
+ data: [
+ {
+ title: 'LDK',
+ type: EItemType.button,
+ testID: 'LDKDebug',
+ onPress: (): void => {
+ navigation.navigate('LdkDebug');
+ },
+ },
+ ],
},
{
title: 'Wallet Checks',
diff --git a/src/screens/Settings/Lightning/Channels.tsx b/src/screens/Settings/Lightning/Channels.tsx
index be640518c..d8c91a50b 100644
--- a/src/screens/Settings/Lightning/Channels.tsx
+++ b/src/screens/Settings/Lightning/Channels.tsx
@@ -9,15 +9,10 @@ import {
import Share from 'react-native-share';
import { FadeIn, FadeOut } from 'react-native-reanimated';
import { useTranslation } from 'react-i18next';
-import Clipboard from '@react-native-clipboard/clipboard';
import { IBtOrder } from '@synonymdev/blocktank-lsp-http-client';
import { BtOrderState2 } from '@synonymdev/blocktank-lsp-http-client/dist/shared/BtOrderState2';
-import {
- AnimatedView,
- View as ThemedView,
- TextInput,
-} from '../../../styles/components';
+import { AnimatedView, View as ThemedView } from '../../../styles/components';
import { Caption13Up, BodyMSB } from '../../../styles/text';
import {
ChevronRight,
@@ -36,27 +31,13 @@ import Money from '../../../components/Money';
import useColors from '../../../hooks/colors';
import { useAppSelector } from '../../../hooks/redux';
import { refreshOrdersList } from '../../../store/utils/blocktank';
-import {
- addPeer,
- getNodeId,
- payLightningInvoice,
- rebroadcastAllKnownTransactions,
- recoverOutputs,
- recoverOutputsFromForceClose,
- refreshLdk,
- setupLdk,
- removeUnusedPeers,
-} from '../../../utils/lightning';
+import { refreshLdk } from '../../../utils/lightning';
import { showToast } from '../../../utils/notifications';
import {
useLightningChannelName,
useLightningBalance,
useLightningChannelBalance,
} from '../../../hooks/lightning';
-import {
- createLightningInvoice,
- savePeer,
-} from '../../../store/utils/lightning';
import {
selectedNetworkSelector,
selectedWalletSelector,
@@ -66,7 +47,6 @@ import {
openChannelsSelector,
pendingChannelsSelector,
} from '../../../store/reselect/lightning';
-import { enableDevOptionsSelector } from '../../../store/reselect/settings';
import { zipLogs } from '../../../utils/lightning/logs';
import { SettingsScreenProps } from '../../../navigation/types';
import {
@@ -75,7 +55,6 @@ import {
} from '../../../store/reselect/blocktank';
import { TPaidBlocktankOrders } from '../../../store/types/blocktank';
import { EUnit } from '../../../store/types/wallet';
-import { showBottomSheet } from '../../../store/utils/ui';
import { EChannelStatus, TChannel } from '../../../store/types/lightning';
/**
@@ -220,23 +199,16 @@ const Channels = ({
route,
}: SettingsScreenProps<'Channels'>): ReactElement => {
const { t } = useTranslation('lightning');
- const [peer, setPeer] = useState('');
+ const [refreshingLdk, setRefreshingLdk] = useState(false);
const [showClosed, setShowClosed] = useState(
route.params?.showClosed ?? false,
);
- const [payingInvoice, setPayingInvoice] = useState(false);
- const [refreshingLdk, setRefreshingLdk] = useState(false);
- const [restartingLdk, setRestartingLdk] = useState(false);
- const [rebroadcastingLdk, setRebroadcastingLdk] = useState(false);
- const [spendingStuckOutputs, setSpendingStuckOutputs] = useState(false);
const colors = useColors();
const br = useBreakpoints();
const { localBalance, remoteBalance } = useLightningBalance();
const selectedWallet = useAppSelector(selectedWalletSelector);
const selectedNetwork = useAppSelector(selectedNetworkSelector);
- const enableDevOptions = useAppSelector(enableDevOptionsSelector);
-
const blocktankOrders = useAppSelector(blocktankOrdersSelector);
const paidOrders = useAppSelector(blocktankPaidOrdersSelector);
const openChannels = useAppSelector(openChannelsSelector);
@@ -279,32 +251,6 @@ const Channels = ({
[navigation],
);
- const createInvoice = async (amountSats = 100): Promise => {
- const createPaymentRequest = await createLightningInvoice({
- amountSats,
- description: '',
- expiryDeltaSeconds: 99999,
- selectedNetwork,
- selectedWallet,
- });
- if (createPaymentRequest.isErr()) {
- showToast({
- type: 'warning',
- title: t('error_invoice'),
- description: createPaymentRequest.error.message,
- });
- return;
- }
- const { to_str } = createPaymentRequest.value;
- console.log(to_str);
- Clipboard.setString(to_str);
- showToast({
- type: 'success',
- title: t('invoice_copied'),
- description: to_str,
- });
- };
-
const onRefreshLdk = useCallback(async (): Promise => {
setRefreshingLdk(true);
await refreshLdk({ selectedWallet, selectedNetwork });
@@ -312,58 +258,6 @@ const Channels = ({
setRefreshingLdk(false);
}, [selectedNetwork, selectedWallet]);
- const onAddPeer = useCallback(async () => {
- if (!peer) {
- // Attempt to grab and set peer string from clipboard.
- const clipboardStr = await Clipboard.getString();
- setPeer(clipboardStr);
- return;
- }
- const addPeerRes = await addPeer({
- peer,
- timeout: 5000,
- });
- if (addPeerRes.isErr()) {
- showToast({
- type: 'warning',
- title: t('error_add_title'),
- description: addPeerRes.error.message,
- });
- return;
- }
- const savePeerRes = savePeer({ selectedWallet, selectedNetwork, peer });
- if (savePeerRes.isErr()) {
- showToast({
- type: 'warning',
- title: t('error_save_title'),
- description: savePeerRes.error.message,
- });
- return;
- }
- showToast({
- type: 'success',
- title: savePeerRes.value,
- description: t('peer_saved'),
- });
- }, [peer, selectedNetwork, selectedWallet, t]);
-
- const onRemoveUnusedPeers = useCallback(async () => {
- const res = await removeUnusedPeers({ selectedWallet, selectedNetwork });
- if (res.isErr()) {
- showToast({
- type: 'warning',
- title: 'No unused peers removed',
- description: res.error.message,
- });
- } else {
- showToast({
- type: 'info',
- title: 'Removed unused peers',
- description: res.value,
- });
- }
- }, [selectedNetwork, selectedWallet]);
-
return (
@@ -471,202 +365,6 @@ const Channels = ({
/>
)}
- {enableDevOptions && (
-
-
- Dev Options
-
- {
- setPeer(txt);
- }}
- blurOnSubmit
- returnKeyType="done"
- testID="AddPeerInput"
- />
-
-
-
- => {
- setRestartingLdk(true);
- await setupLdk({ selectedWallet, selectedNetwork });
- setRestartingLdk(false);
- }}
- testID="RestartLDK"
- />
- => {
- setRebroadcastingLdk(true);
- await rebroadcastAllKnownTransactions();
- setRebroadcastingLdk(false);
- }}
- testID="RebroadcastLDKTXS"
- />
- => {
- setSpendingStuckOutputs(true);
- const res = await recoverOutputs();
- if (res.isOk()) {
- showToast({
- type: 'info',
- title: 'Stuck outputs recovered',
- description: res.value,
- });
- } else {
- showToast({
- type: 'warning',
- title: 'No stuck outputs recovered',
- description: res.error.message,
- });
- }
- setSpendingStuckOutputs(false);
- }}
- />
- {
- showBottomSheet('forceTransfer');
- }}
- />
- => {
- setSpendingStuckOutputs(true);
- const res = await recoverOutputsFromForceClose();
- if (res.isOk()) {
- showToast({
- type: 'info',
- title: 'Completed',
- description: res.value,
- });
- } else {
- showToast({
- type: 'warning',
- title: 'No stuck outputs recovered',
- description: res.error.message,
- });
- }
- setSpendingStuckOutputs(false);
- }}
- />
- => {
- const nodeId = await getNodeId();
- if (nodeId.isErr()) {
- console.log(nodeId.error.message);
- return;
- }
- console.log(nodeId.value);
- Clipboard.setString(nodeId.value);
- showToast({
- type: 'success',
- title: 'Copied Node ID to Clipboard',
- description: nodeId.value,
- });
- }}
- testID="CopyNodeId"
- />
-
- {openChannels.length > 0 && (
- <>
- {remoteBalance > 100 && (
- => {
- createInvoice(100).then();
- }}
- />
- )}
- {remoteBalance > 5000 && (
- => {
- createInvoice(5000).then();
- }}
- />
- )}
- {localBalance > 0 && (
- <>
- => {
- setPayingInvoice(true);
- const invoice = await Clipboard.getString();
- if (!invoice) {
- showToast({
- type: 'warning',
- title: 'No Invoice Detected',
- description:
- 'Unable to retrieve anything from the clipboard.',
- });
- }
- const response = await payLightningInvoice({ invoice });
- if (response.isErr()) {
- showToast({
- type: 'warning',
- title: 'Invoice Payment Failed',
- description: response.error.message,
- });
- setPayingInvoice(false);
- return;
- }
- await refreshLdk({ selectedWallet, selectedNetwork });
- setPayingInvoice(false);
- }}
- />
- >
- )}
- >
- )}
-
- )}
-