Skip to content

Commit 712c446

Browse files
authored
Merge pull request #2563 from synonymdev/ldk-set-confirmed-debug
feat(lightning): debug function to manual set tx as confirmed
2 parents 217b504 + e761c7f commit 712c446

File tree

1 file changed

+292
-2
lines changed

1 file changed

+292
-2
lines changed

src/screens/Settings/DevSettings/LdkDebug.tsx

Lines changed: 292 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import Clipboard from '@react-native-clipboard/clipboard';
2-
import lm from '@synonymdev/react-native-ldk';
2+
import lm, { ldk } from '@synonymdev/react-native-ldk';
33
import React, { ReactElement, memo, useState } from 'react';
44
import { useTranslation } from 'react-i18next';
5-
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
5+
import {
6+
Alert,
7+
ScrollView,
8+
StyleSheet,
9+
TouchableOpacity,
10+
View,
11+
} from 'react-native';
612
import RNFS from 'react-native-fs';
713
import Share from 'react-native-share';
814

@@ -46,11 +52,15 @@ const LdkDebug = (): ReactElement => {
4652
const dispatch = useAppDispatch();
4753
const sheetRef = useSheetRef('forceTransfer');
4854
const [peer, setPeer] = useState('');
55+
const [txid, setTxid] = useState('');
4956
const [payingInvoice, setPayingInvoice] = useState(false);
5057
const [refreshingLdk, setRefreshingLdk] = useState(false);
5158
const [restartingLdk, setRestartingLdk] = useState(false);
5259
const [rebroadcastingLdk, setRebroadcastingLdk] = useState(false);
5360
const [spendingStuckOutputs, setSpendingStuckOutputs] = useState(false);
61+
const [settingConfirmedTx, setSettingConfirmedTx] = useState(false);
62+
const [runningComprehensiveDebug, setRunningComprehensiveDebug] =
63+
useState(false);
5464

5565
const { localBalance, remoteBalance } = useLightningBalance();
5666
const selectedWallet = useAppSelector(selectedWalletSelector);
@@ -324,6 +334,253 @@ const LdkDebug = (): ReactElement => {
324334
setPayingInvoice(false);
325335
};
326336

337+
const onSetConfirmedTx = async (): Promise<void> => {
338+
if (!txid) {
339+
// Attempt to grab and set txid string from clipboard.
340+
const clipboardStr = await Clipboard.getString();
341+
setTxid(clipboardStr);
342+
return;
343+
}
344+
345+
setSettingConfirmedTx(true);
346+
try {
347+
// Get network endpoint
348+
const baseUrl = 'https://mempool.space/api';
349+
350+
// Fetch transaction details
351+
const txResponse = await fetch(`${baseUrl}/tx/${txid.trim()}`);
352+
if (!txResponse.ok) {
353+
showToast({
354+
type: 'error',
355+
title: 'Transaction Not Found',
356+
description: 'Unable to find transaction on mempool.space',
357+
});
358+
setSettingConfirmedTx(false);
359+
return;
360+
}
361+
362+
const tx = await txResponse.json();
363+
364+
// Check if transaction is confirmed
365+
if (!tx.status?.confirmed || !tx.status?.block_height) {
366+
showToast({
367+
type: 'error',
368+
title: 'Transaction Not Confirmed',
369+
description: 'Transaction is not yet confirmed on the blockchain',
370+
});
371+
setSettingConfirmedTx(false);
372+
return;
373+
}
374+
375+
// Fetch transaction hex data
376+
const txHexResponse = await fetch(`${baseUrl}/tx/${txid.trim()}/hex`);
377+
if (!txHexResponse.ok) {
378+
showToast({
379+
type: 'error',
380+
title: 'Transaction Hex Error',
381+
description: 'Unable to fetch transaction hex data',
382+
});
383+
setSettingConfirmedTx(false);
384+
return;
385+
}
386+
387+
const txHex = await txHexResponse.text();
388+
389+
// Fetch block header
390+
const blockHash = tx.status.block_hash;
391+
const blockResponse = await fetch(`${baseUrl}/block/${blockHash}/header`);
392+
if (!blockResponse.ok) {
393+
showToast({
394+
type: 'error',
395+
title: 'Block Header Error',
396+
description: 'Unable to fetch block header',
397+
});
398+
setSettingConfirmedTx(false);
399+
return;
400+
}
401+
402+
const blockHeader = await blockResponse.text();
403+
404+
// Validate all required parameters
405+
if (!blockHeader) {
406+
showToast({
407+
type: 'error',
408+
title: 'Missing Block Header',
409+
description: 'Block header is empty or invalid',
410+
});
411+
setSettingConfirmedTx(false);
412+
return;
413+
}
414+
415+
if (!txHex) {
416+
showToast({
417+
type: 'error',
418+
title: 'Missing Transaction Data',
419+
description: 'Transaction hex data is missing',
420+
});
421+
setSettingConfirmedTx(false);
422+
return;
423+
}
424+
425+
if (!tx.status.block_height) {
426+
showToast({
427+
type: 'error',
428+
title: 'Missing Block Height',
429+
description: 'Block height is missing or invalid',
430+
});
431+
setSettingConfirmedTx(false);
432+
return;
433+
}
434+
435+
// Call ldk.setTxConfirmed
436+
const setTxConfirmedRes = await ldk.setTxConfirmed({
437+
header: blockHeader,
438+
txData: [
439+
{
440+
transaction: txHex,
441+
pos: tx.status.block_time || 0, // Using block_time as position fallback
442+
},
443+
],
444+
height: tx.status.block_height,
445+
});
446+
447+
if (setTxConfirmedRes.isErr()) {
448+
showToast({
449+
type: 'error',
450+
title: 'Set Confirmed Failed',
451+
description: setTxConfirmedRes.error.message,
452+
});
453+
} else {
454+
showToast({
455+
type: 'success',
456+
title: 'Transaction Confirmed',
457+
description: `Transaction ${txid.slice(0, 8)}... set as confirmed at height ${
458+
tx.status.block_height
459+
}`,
460+
});
461+
}
462+
} catch (error) {
463+
showToast({
464+
type: 'error',
465+
title: 'Error',
466+
description:
467+
error instanceof Error ? error.message : 'Unknown error occurred',
468+
});
469+
} finally {
470+
setSettingConfirmedTx(false);
471+
}
472+
};
473+
474+
const sleep = (ms: number) =>
475+
new Promise((resolve) => setTimeout(resolve, ms));
476+
477+
const onComprehensiveDebug = async (): Promise<void> => {
478+
Alert.alert(
479+
'Hard Refresh & Recovery',
480+
'This will perform a hard refresh of LDK and attempt to recover any stuck funds. This can take up to 2 minutes.\n\nPlease keep the app open on this screen until complete.',
481+
[
482+
{
483+
text: 'Cancel',
484+
style: 'cancel',
485+
},
486+
{
487+
text: 'Continue',
488+
onPress: async () => {
489+
setRunningComprehensiveDebug(true);
490+
let currentStep = '';
491+
492+
try {
493+
// Step 1: Refresh LDK
494+
currentStep = 'Refreshing LDK';
495+
showToast({
496+
type: 'info',
497+
title: 'Step 1/5',
498+
description: currentStep,
499+
});
500+
await refreshLdk({ selectedWallet, selectedNetwork });
501+
502+
await sleep(2000);
503+
504+
// Step 2: Rebroadcast LDK Txs
505+
currentStep = 'Rebroadcasting LDK Transactions';
506+
showToast({
507+
type: 'info',
508+
title: 'Step 2/5',
509+
description: currentStep,
510+
});
511+
await rebroadcastAllKnownTransactions();
512+
513+
await sleep(2000);
514+
515+
// Step 3: Spend Stuck Outputs
516+
currentStep = 'Spending Stuck Outputs';
517+
showToast({
518+
type: 'info',
519+
title: 'Step 3/5',
520+
description: currentStep,
521+
});
522+
const stuckOutputsRes = await recoverOutputs();
523+
if (stuckOutputsRes.isOk()) {
524+
showToast({
525+
type: 'info',
526+
title: 'Stuck Outputs',
527+
description: stuckOutputsRes.value,
528+
});
529+
}
530+
531+
await sleep(2000);
532+
533+
// Step 4: Spend Outputs from Force Close
534+
currentStep = 'Spending Outputs from Force Close';
535+
showToast({
536+
type: 'info',
537+
title: 'Step 4/5',
538+
description: currentStep,
539+
});
540+
const forceCloseRes = await recoverOutputsFromForceClose();
541+
if (forceCloseRes.isOk()) {
542+
showToast({
543+
type: 'info',
544+
title: 'Force Close Outputs',
545+
description: forceCloseRes.value,
546+
});
547+
}
548+
549+
await sleep(2000);
550+
551+
// Step 5: Final Refresh LDK
552+
currentStep = 'Final LDK Refresh';
553+
showToast({
554+
type: 'info',
555+
title: 'Step 5/5',
556+
description: currentStep,
557+
});
558+
await refreshLdk({ selectedWallet, selectedNetwork });
559+
560+
// Success
561+
showToast({
562+
type: 'success',
563+
title: 'Hard Refresh & Recovery Complete',
564+
description: 'All operations completed successfully',
565+
});
566+
} catch (error) {
567+
showToast({
568+
type: 'error',
569+
title: `Failed at: ${currentStep}`,
570+
description:
571+
error instanceof Error
572+
? error.message
573+
: 'Unknown error occurred',
574+
});
575+
} finally {
576+
setRunningComprehensiveDebug(false);
577+
}
578+
},
579+
},
580+
],
581+
);
582+
};
583+
327584
return (
328585
<ThemedView style={styles.root}>
329586
<SafeAreaInset type="top" />
@@ -356,9 +613,42 @@ const LdkDebug = (): ReactElement => {
356613
onPress={onAddPeer}
357614
/>
358615

616+
<Caption13Up style={styles.sectionTitle} color="secondary">
617+
Set Confirmed Transaction
618+
</Caption13Up>
619+
<TextInput
620+
style={styles.textInput}
621+
autoCapitalize="none"
622+
autoComplete="off"
623+
autoCorrect={false}
624+
autoFocus={false}
625+
value={txid}
626+
placeholder="Transaction ID"
627+
blurOnSubmit
628+
returnKeyType="done"
629+
testID="TxidInput"
630+
onChangeText={setTxid}
631+
/>
632+
<Button
633+
style={styles.button}
634+
text={
635+
txid ? 'Set Confirmed Tx' : 'Paste Transaction ID From Clipboard'
636+
}
637+
loading={settingConfirmedTx}
638+
testID="SetConfirmedTxButton"
639+
onPress={onSetConfirmedTx}
640+
/>
641+
359642
<Caption13Up style={styles.sectionTitle} color="secondary">
360643
Debug
361644
</Caption13Up>
645+
<Button
646+
style={styles.button}
647+
text="Hard Refresh & Recovery"
648+
loading={runningComprehensiveDebug}
649+
testID="HardRefreshRecovery"
650+
onPress={onComprehensiveDebug}
651+
/>
362652
<Button
363653
style={styles.button}
364654
text="Get Node ID"

0 commit comments

Comments
 (0)