feat(CLNRest): fallback to mempool.space for raw tx broadcast as it lacks publishTransaction

This commit is contained in:
Khushal
2025-10-23 12:45:02 +05:30
parent cc77c2ee8a
commit 4e0346f9c0
8 changed files with 236 additions and 140 deletions
+3 -3
View File
@@ -35,11 +35,11 @@ import {
posStore, posStore,
settingsStore, settingsStore,
swapStore, swapStore,
sweepStore,
syncStore, syncStore,
transactionsStore, transactionsStore,
unitsStore, unitsStore,
utxosStore utxosStore,
sweepStore
} from './stores/Stores'; } from './stores/Stores';
import NavigationService from './NavigationService'; import NavigationService from './NavigationService';
import PushNotificationManager from './PushNotificationManager'; import PushNotificationManager from './PushNotificationManager';
@@ -259,6 +259,7 @@ import Watchtowers from './views/Tools/Watchtowers/WatchtowerList';
import AddWatchtower from './views/Tools/Watchtowers/AddWatchtower'; import AddWatchtower from './views/Tools/Watchtowers/AddWatchtower';
import WatchtowerDetails from './views/Tools/Watchtowers/WatchtowerDetails'; import WatchtowerDetails from './views/Tools/Watchtowers/WatchtowerDetails';
import ShareIntentProcessing from './views/ShareIntentProcessing'; import ShareIntentProcessing from './views/ShareIntentProcessing';
import WIFSweeper from './views/Tools/WIFSweeper';
import { isLightTheme, themeColor } from './utils/ThemeUtils'; import { isLightTheme, themeColor } from './utils/ThemeUtils';
import LinkingUtils from './utils/LinkingUtils'; import LinkingUtils from './utils/LinkingUtils';
@@ -266,7 +267,6 @@ import CreateWithdrawalRequest from './views/Tools/CreateWithdrawalRequest';
import WithdrawalRequestView from './views/WithdrawalRequest'; import WithdrawalRequestView from './views/WithdrawalRequest';
import WithdrawalRequestInfo from './views/WithdrawalRequestInfo'; import WithdrawalRequestInfo from './views/WithdrawalRequestInfo';
import RedeemWithdrawalRequest from './views/RedeemWithdrawalRequest'; import RedeemWithdrawalRequest from './views/RedeemWithdrawalRequest';
import WIFSweeper from './views/WIFSweeper';
export default class App extends React.PureComponent { export default class App extends React.PureComponent {
private backPressListenerSubscription: NativeEventSubscription; private backPressListenerSubscription: NativeEventSubscription;
+13 -9
View File
@@ -843,15 +843,16 @@
"views.Wif.createTransaction": "Create Transaction", "views.Wif.createTransaction": "Create Transaction",
"views.Wif.invalidBase58Chars": "Invalid base58 characters", "views.Wif.invalidBase58Chars": "Invalid base58 characters",
"views.Wif.noUtxos": "The unspent outputs from this private key contain insufficient funds to spend(0 sats).", "views.Wif.noUtxos": "The unspent outputs from this private key contain insufficient funds to spend(0 sats).",
"views.Sweep.failedToFetchTxHex": "Failed to fetch transaction hex for {{txid}}", "views.Wif.failedToFetchTxHex": "Failed to fetch transaction hex for {{txid}}",
"views.Sweep.failedToFetchTxDetails": "Failed to fetch transaction details for {{txid}}", "views.Wif.failedToFetchTxDetails": "Failed to fetch transaction details for {{txid}}",
"views.Sweep.outputNotFound": "Output {{vout}} not found in transaction {{txid}}", "views.Wif.outputNotFound": "Output {{vout}} not found in transaction {{txid}}",
"views.Sweep.outputIndexNotFound": "Output index {{vout}} not found in transaction {{txid}}", "views.Wif.outputIndexNotFound": "Output index {{vout}} not found in transaction {{txid}}",
"views.Sweep.failedToDerivePubkey": "Failed to derive public key", "views.Wif.failedToDerivePubkey": "Failed to derive public key",
"views.Sweep.noUtxosFound": "No UTXOs found for the given private key", "views.Wif.noUtxosFound": "No UTXOs found for the given private key",
"views.Sweep.addressTypeNotSupported": "This address type is not yet supported for sweeping", "views.Wif.addressTypeNotSupported": "This address type is not yet supported for sweeping",
"views.Sweep.insufficientFundsAfterFees": "Insufficient funds after fees", "views.Wif.insufficientFundsAfterFees": "Insufficient funds after fees",
"views.Sweep.noTaprootSupport": "Taproot (P2TR) sweep is not supported yet", "views.Wif.failedToCreateTransaction": "Failed to create transaction. Please check your inputs and try again.",
"views.Wif.errorFetchingUtxos": "Error fetching UTXOs",
"views.Settings.title": "Settings", "views.Settings.title": "Settings",
"views.Settings.enabled": "Enabled", "views.Settings.enabled": "Enabled",
"views.Settings.disabled": "Disabled", "views.Settings.disabled": "Disabled",
@@ -1539,6 +1540,9 @@
"views.TxHex.channelWarning": "No channel loaded in the channel funding flow engine. DO NOT PUBLISH if this transaction is for a channel open.", "views.TxHex.channelWarning": "No channel loaded in the channel funding flow engine. DO NOT PUBLISH if this transaction is for a channel open.",
"views.TxHex.broadcast": "Broadcast TX", "views.TxHex.broadcast": "Broadcast TX",
"views.TxHex.finalizeFlowAndBroadcast": "Finalize funding flow + Broadcast", "views.TxHex.finalizeFlowAndBroadcast": "Finalize funding flow + Broadcast",
"views.TxHex.broadcastingViaMempool": "Broadcasting via Mempool.space",
"views.TxHex.thirdPartyBroadcastWarning": "This implementation requires broadcasting via a third-party service (Mempool.space).",
"views.TxHex.transactionBroadcastCancelled": "Transaction broadcast cancelled",
"pos.customItem": "Custom item", "pos.customItem": "Custom item",
"time.seconds": "Seconds", "time.seconds": "Seconds",
"time.minutes": "Minutes", "time.minutes": "Minutes",
+1 -1
View File
@@ -21,11 +21,11 @@ import PaymentsStore from './PaymentsStore';
import PosStore from './PosStore'; import PosStore from './PosStore';
import SettingsStore from './SettingsStore'; import SettingsStore from './SettingsStore';
import SwapStore from './SwapStore'; import SwapStore from './SwapStore';
import SweepStore from './SweepStore';
import SyncStore from './SyncStore'; import SyncStore from './SyncStore';
import TransactionsStore from './TransactionsStore'; import TransactionsStore from './TransactionsStore';
import UnitsStore from './UnitsStore'; import UnitsStore from './UnitsStore';
import UTXOsStore from './UTXOsStore'; import UTXOsStore from './UTXOsStore';
import SweepStore from './SweepStore';
export const settingsStore = new SettingsStore(); export const settingsStore = new SettingsStore();
export const modalStore = new ModalStore(); export const modalStore = new ModalStore();
+24 -30
View File
@@ -12,10 +12,8 @@ import { localeString } from '../utils/LocaleUtils';
import ecc from '../zeus_modules/noble_ecc'; import ecc from '../zeus_modules/noble_ecc';
export default class SweepStore { export default class SweepStore {
@observable loading: boolean = false;
@observable sweepErrorMsg: string | null = null; @observable sweepErrorMsg: string | null = null;
@observable sweepError: boolean = false; @observable sweepError: boolean = false;
@observable sweepTxHex: string | null = null;
@observable onChainBalance: number; @observable onChainBalance: number;
@observable destination: string; @observable destination: string;
@observable txHex: string | null = null; @observable txHex: string | null = null;
@@ -37,11 +35,6 @@ export default class SweepStore {
this.nodeInfoStore = nodeInfoStore; this.nodeInfoStore = nodeInfoStore;
} }
@action
setLoading(loading: boolean) {
this.loading = loading;
}
@action @action
resetSweepError() { resetSweepError() {
this.sweepError = false; this.sweepError = false;
@@ -53,7 +46,7 @@ export default class SweepStore {
`${wifUtils.baseUrl(network)}/address/${address}/utxo` `${wifUtils.baseUrl(network)}/address/${address}/utxo`
); );
if (!res.ok) { if (!res.ok) {
throw new Error(localeString(`Error fetching UTXOs`)); throw new Error(localeString('views.Wif.errorFetchingUtxos'));
} }
const utxos = await res.json(); const utxos = await res.json();
@@ -104,7 +97,7 @@ export default class SweepStore {
} }
} }
throw new Error(localeString('views.Sweep.addressTypeNotSupported')); throw new Error(localeString('views.Wif.noUtxosFound'));
} }
@action @action
@@ -141,7 +134,7 @@ export default class SweepStore {
if (this.addressType === 'p2tr') { if (this.addressType === 'p2tr') {
this.sweepError = true; this.sweepError = true;
this.sweepErrorMsg = localeString( this.sweepErrorMsg = localeString(
'views.Sweep.addressTypeNotSupported' 'views.Wif.addressTypeNotSupported'
); );
return; return;
} }
@@ -153,11 +146,12 @@ export default class SweepStore {
const res = await fetch( const res = await fetch(
`${wifUtils.baseUrl(networkStr)}/tx/${txid}/hex` `${wifUtils.baseUrl(networkStr)}/tx/${txid}/hex`
); );
if (!res.ok) if (!res.ok)
throw new Error( throw new Error(
localeString( localeString('views.Wif.failedToFetchTxHex', {
`views.Sweep.failedToFetchTxHex:${txid}` txid
) })
); );
const rawTxHex = await res.text(); const rawTxHex = await res.text();
@@ -177,9 +171,9 @@ export default class SweepStore {
); );
if (!res.ok) if (!res.ok)
throw new Error( throw new Error(
localeString( localeString('views.Wif.failedToFetchTxDetails', {
`views.Sweep.failedToFetchTxDetails:${txid}` txid
) })
); );
const tx = await res.json(); const tx = await res.json();
@@ -187,9 +181,10 @@ export default class SweepStore {
if (!output) if (!output)
throw new Error( throw new Error(
localeString( localeString('views.Sweep.outputIndexNotFound', {
`views.Sweep.outputIndexNotFound:${vout}:${txid}` vout,
) txid
})
); );
const value = Math.round(output.value); const value = Math.round(output.value);
@@ -218,9 +213,9 @@ export default class SweepStore {
); );
if (!res.ok) if (!res.ok)
throw new Error( throw new Error(
localeString( localeString('views.Wif.failedToFetchTxDetails', {
`views.Sweep.failedToFetchTxDetails:${txid}` txid
) })
); );
const tx = await res.json(); const tx = await res.json();
@@ -228,9 +223,10 @@ export default class SweepStore {
if (!output) if (!output)
throw new Error( throw new Error(
localeString( localeString('views.Wif.outputNotFound', {
`views.Sweep.outputNotFound:${vout}:${txid}` vout,
) txid
})
); );
const scriptPubKeyHex = output.scriptpubkey; const scriptPubKeyHex = output.scriptpubkey;
const script = Buffer.from(scriptPubKeyHex, 'hex'); const script = Buffer.from(scriptPubKeyHex, 'hex');
@@ -240,7 +236,7 @@ export default class SweepStore {
if (!fullPub) { if (!fullPub) {
throw new Error( throw new Error(
localeString('views.Sweep.failedToDerivePubkey') localeString('views.Wif.failedToDerivePubkey')
); );
} }
@@ -276,9 +272,7 @@ export default class SweepStore {
const fullPub = ecc.pointFromScalar(privateKey, true); const fullPub = ecc.pointFromScalar(privateKey, true);
if (!fullPub) if (!fullPub)
throw new Error( throw new Error(localeString('views.Wif.failedToDerivePubkey'));
localeString('views.Sweep.failedToDerivePubkey')
);
const signer: bitcoin.Signer = { const signer: bitcoin.Signer = {
publicKey: Buffer.from(fullPub), publicKey: Buffer.from(fullPub),
@@ -310,7 +304,7 @@ export default class SweepStore {
if (this.valueToSend <= 0) if (this.valueToSend <= 0)
throw new Error( throw new Error(
localeString('views.Sweep.insufficientFundsAfterFees') localeString('views.Wif.insufficientFundsAfterFees')
); );
this.psbt.addOutput({ this.psbt.addOutput({
+43 -1
View File
@@ -129,7 +129,7 @@ export default class TransactionsStore {
}; };
@action @action
public broadcast = (raw_final_tx: string) => { public broadcast = async (raw_final_tx: string) => {
this.loading = true; this.loading = true;
const tx_hex = raw_final_tx.includes('=') const tx_hex = raw_final_tx.includes('=')
@@ -144,6 +144,48 @@ export default class TransactionsStore {
txid = tx.getId(); txid = tx.getId();
} catch (e) {} } catch (e) {}
// CLN REST does not support publishTransaction; broadcast via mempool.space
if (this.settingsStore.implementation === 'cln-rest') {
const headers = {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/plain'
} as any;
const url = `https://mempool.space/${
this.nodeInfoStore.nodeInfo.isTestNet ? 'testnet/' : ''
}api/tx`;
return ReactNativeBlobUtil.fetch('POST', url, headers, tx_hex)
.then((response: any) => {
const status = response.info().status;
const data = response.data;
if (status == 200) {
runInAction(() => {
this.txid = data || txid;
this.publishSuccess = true;
this.loading = false;
this.channelsStore.resetOpenChannel();
});
return data;
} else {
runInAction(() => {
this.error_msg = errorToUserFriendly(data);
this.error = true;
this.loading = false;
});
}
})
.catch((err: any) => {
runInAction(() => {
this.error_msg = errorToUserFriendly(
err?.error || err?.message || err?.toString()
);
this.error = true;
this.loading = false;
});
});
}
return BackendUtils.publishTransaction({ return BackendUtils.publishTransaction({
tx_hex tx_hex
}) })
@@ -5,34 +5,34 @@ import { Route } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { TouchableOpacity } from 'react-native-gesture-handler'; import { TouchableOpacity } from 'react-native-gesture-handler';
import InvoicesStore from '../stores/InvoicesStore'; import InvoicesStore from '../../stores/InvoicesStore';
import SweepStore from '../stores/SweepStore'; import SweepStore from '../../stores/SweepStore';
import Button from '../components/Button'; import Button from '../../components/Button';
import { ErrorMessage } from '../components/SuccessErrorMessage'; import { ErrorMessage } from '../../components/SuccessErrorMessage';
import Header from '../components/Header'; import Header from '../../components/Header';
import Screen from '../components/Screen'; import Screen from '../../components/Screen';
import TextInput from '../components/TextInput'; import TextInput from '../../components/TextInput';
import LoadingIndicator from '../components/LoadingIndicator'; import LoadingIndicator from '../../components/LoadingIndicator';
import OnchainFeeInput from '../components/OnchainFeeInput'; import OnchainFeeInput from '../../components/OnchainFeeInput';
import ShowHideToggle from '../components/ShowHideToggle'; import ShowHideToggle from '../../components/ShowHideToggle';
import { localeString } from '../utils/LocaleUtils'; import { localeString } from '../../utils/LocaleUtils';
import { themeColor } from '../utils/ThemeUtils'; import { themeColor } from '../../utils/ThemeUtils';
import wifUtils from '../utils/WIFUtils'; import wifUtils from '../../utils/WIFUtils';
import AddressUtils from '../utils/AddressUtils'; import AddressUtils from '../../utils/AddressUtils';
import Scan from '../assets/images/SVG/Scan.svg'; import Scan from '../../assets/images/SVG/Scan.svg';
interface SweepProps { interface WIFSweepProps {
exitSetup: any; exitSetup: any;
navigation: StackNavigationProp<any, any>; navigation: StackNavigationProp<any, any>;
InvoicesStore: InvoicesStore; InvoicesStore: InvoicesStore;
SweepStore: SweepStore; SweepStore: SweepStore;
route: Route<'Sweep', { wif: string }>; route: Route<'WIFSweeper', { wif: string }>;
} }
interface SweepState { interface WIFSweepState {
privateKey: string; privateKey: string;
hidden: boolean; hidden: boolean;
isValid: boolean; isValid: boolean;
@@ -41,13 +41,14 @@ interface SweepState {
onChainAddressloading: boolean; onChainAddressloading: boolean;
feeRate: string; feeRate: string;
feeLoadingError: boolean; feeLoadingError: boolean;
isWifValid: boolean;
} }
@inject('SweepStore', 'InvoicesStore') @inject('SweepStore', 'InvoicesStore')
@observer @observer
export default class WIFSweeper extends React.Component< export default class WIFSweeper extends React.Component<
SweepProps, WIFSweepProps,
SweepState WIFSweepState
> { > {
state = { state = {
privateKey: '', privateKey: '',
@@ -57,7 +58,8 @@ export default class WIFSweeper extends React.Component<
loading: false, loading: false,
onChainAddressloading: false, onChainAddressloading: false,
feeRate: '2', feeRate: '2',
feeLoadingError: false feeLoadingError: false,
isWifValid: true
}; };
componentDidMount() { componentDidMount() {
@@ -74,7 +76,7 @@ export default class WIFSweeper extends React.Component<
this.initFromProps(this.props); this.initFromProps(this.props);
} }
initFromProps(props: SweepProps) { initFromProps(props: WIFSweepProps) {
const { route } = props; const { route } = props;
const scannedKey = route.params?.wif; const scannedKey = route.params?.wif;
@@ -85,7 +87,7 @@ export default class WIFSweeper extends React.Component<
} }
} }
componentDidUpdate(prevProps: SweepProps) { componentDidUpdate(prevProps: WIFSweepProps) {
if (prevProps.route.params?.wif !== this.props.route.params?.wif) { if (prevProps.route.params?.wif !== this.props.route.params?.wif) {
this.initFromProps(this.props); this.initFromProps(this.props);
} }
@@ -160,11 +162,25 @@ export default class WIFSweeper extends React.Component<
'views.Wif.enterPrivateKeyPlaceholder' 'views.Wif.enterPrivateKeyPlaceholder'
)} )}
value={privateKey} value={privateKey}
onChangeText={(text: string) => onChangeText={(text: string) => {
this.setState({ privateKey: text }) const isWifValid =
} text.length === 0 ||
wifUtils.validateWIF(text).isValid;
this.setState({
privateKey: text,
isWifValid
});
}}
secureTextEntry={hidden} secureTextEntry={hidden}
style={{ flex: 1, marginRight: 15 }} style={{
flex: 1,
marginRight: 15
}}
textColor={
!this.state.isWifValid
? themeColor('delete')
: themeColor('text')
}
locked={loading} locked={loading}
/> />
<ShowHideToggle <ShowHideToggle
@@ -207,6 +223,13 @@ export default class WIFSweeper extends React.Component<
marginHorizontal: 20 marginHorizontal: 20
}} }}
value={SweepStore.destination} value={SweepStore.destination}
textColor={
SweepStore.destination &&
!this.state.isValid
? themeColor('delete')
: themeColor('text')
}
locked={loading}
/> />
{onChainAddressloading && ( {onChainAddressloading && (
<View style={styles.loadingOverlay}> <View style={styles.loadingOverlay}>
@@ -228,7 +251,7 @@ export default class WIFSweeper extends React.Component<
onChainAddressloading: true onChainAddressloading: true
}); });
this.props.SweepStore.resetSweepError(); this.props.SweepStore.resetSweepError();
const { isValid } = const { isValid, error } =
wifUtils.validateWIF(privateKey); wifUtils.validateWIF(privateKey);
if (isValid) { if (isValid) {
await this.props.SweepStore.prepareSweepInputs( await this.props.SweepStore.prepareSweepInputs(
@@ -236,16 +259,17 @@ export default class WIFSweeper extends React.Component<
); );
} else { } else {
throw new Error( throw new Error(
localeString( error ||
'views.Wif.invalidWif' localeString(
) 'views.Wif.invalidWif'
)
); );
} }
if (SweepStore.sweepError) { if (SweepStore.sweepError) {
throw new Error( throw new Error(
SweepStore.sweepErrorMsg || SweepStore.sweepErrorMsg ||
localeString( localeString(
'views.Sweep.unknownError' 'views.Wif.invalidWif'
) )
); );
} }
@@ -295,7 +319,9 @@ export default class WIFSweeper extends React.Component<
)} )}
secondary secondary
disabled={ disabled={
!privateKey || !!SweepStore.destination !privateKey ||
!this.state.isWifValid ||
!!SweepStore.destination
} }
/> />
</View> </View>
@@ -332,12 +358,30 @@ export default class WIFSweeper extends React.Component<
this.state.privateKey this.state.privateKey
); );
if (isValid) { if (isValid) {
await this.props.SweepStore.finalizeSweepTransaction( try {
feeRate await this.props.SweepStore.finalizeSweepTransaction(
); feeRate
navigation.navigate('TxHex', { );
txHex: SweepStore.txHex if (SweepStore.txHex) {
}); navigation.navigate('TxHex', {
txHex: SweepStore.txHex
});
} else {
throw new Error(
localeString(
'views.Wif.failedToCreateTransaction'
)
);
}
} catch (e: any) {
this.props.SweepStore.sweepError = true;
this.props.SweepStore.sweepErrorMsg =
e.message ||
localeString(
'views.Wif.failedToCreateTransaction'
);
this.setState({ error: e.message });
}
} else { } else {
this.props.SweepStore.sweepError = true; this.props.SweepStore.sweepError = true;
this.props.SweepStore.sweepErrorMsg = this.props.SweepStore.sweepErrorMsg =
@@ -349,6 +393,7 @@ export default class WIFSweeper extends React.Component<
!privateKey || !privateKey ||
loading || loading ||
!SweepStore.destination || !SweepStore.destination ||
!this.state.isValid ||
sweepError || sweepError ||
feeLoadingError || feeLoadingError ||
(!!error && error.length > 0) (!!error && error.length > 0)
+68 -24
View File
@@ -4,7 +4,8 @@ import {
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
View View,
Alert
} from 'react-native'; } from 'react-native';
import { inject, observer } from 'mobx-react'; import { inject, observer } from 'mobx-react';
import { ButtonGroup } from 'react-native-elements'; import { ButtonGroup } from 'react-native-elements';
@@ -12,6 +13,7 @@ import { UR, UREncoder } from '@ngraveio/bc-ur';
import clone from 'lodash/clone'; import clone from 'lodash/clone';
import { Route } from '@react-navigation/native'; import { Route } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { runInAction } from 'mobx';
const bitcoin = require('bitcoinjs-lib'); const bitcoin = require('bitcoinjs-lib');
@@ -34,6 +36,7 @@ import UrlUtils from '../utils/UrlUtils';
import ChannelsStore from '../stores/ChannelsStore'; import ChannelsStore from '../stores/ChannelsStore';
import NodeInfoStore from '../stores/NodeInfoStore'; import NodeInfoStore from '../stores/NodeInfoStore';
import TransactionsStore from '../stores/TransactionsStore'; import TransactionsStore from '../stores/TransactionsStore';
import SettingsStore from '../stores/SettingsStore';
import { import {
getQRAnimationInterval, getQRAnimationInterval,
QRAnimationSpeed QRAnimationSpeed
@@ -41,6 +44,7 @@ import {
interface TxHexProps { interface TxHexProps {
navigation: StackNavigationProp<any, any>; navigation: StackNavigationProp<any, any>;
SettingsStore: SettingsStore;
ChannelsStore: ChannelsStore; ChannelsStore: ChannelsStore;
NodeInfoStore: NodeInfoStore; NodeInfoStore: NodeInfoStore;
TransactionsStore: TransactionsStore; TransactionsStore: TransactionsStore;
@@ -59,7 +63,7 @@ interface TxHexState {
qrAnimationSpeed: QRAnimationSpeed; qrAnimationSpeed: QRAnimationSpeed;
} }
@inject('ChannelsStore', 'NodeInfoStore', 'TransactionsStore') @inject('ChannelsStore', 'NodeInfoStore', 'TransactionsStore', 'SettingsStore')
@observer @observer
export default class TxHex extends React.Component<TxHexProps, TxHexState> { export default class TxHex extends React.Component<TxHexProps, TxHexState> {
private qrAnimationInterval?: any; private qrAnimationInterval?: any;
@@ -147,6 +151,67 @@ export default class TxHex extends React.Component<TxHexProps, TxHexState> {
} }
} }
handleBroadcast = async () => {
const { txHex } = this.state;
const { navigation, SettingsStore, TransactionsStore, ChannelsStore } =
this.props;
const { pending_chan_ids } = ChannelsStore;
try {
if (pending_chan_ids.length > 0) {
await TransactionsStore.finalizeTxHexAndBroadcastChannel(
txHex,
pending_chan_ids
);
navigation.navigate('SendingOnChain');
return;
}
if (SettingsStore.implementation === 'cln-rest') {
const userConfirmed = await new Promise<boolean>((resolve) => {
Alert.alert(
localeString('views.TxHex.broadcastingViaMempool'),
localeString('views.TxHex.thirdPartyBroadcastWarning'),
[
{
text: localeString('general.cancel'),
style: 'cancel',
onPress: () => resolve(false)
},
{ text: 'OK', onPress: () => resolve(true) }
]
);
});
if (!userConfirmed) {
runInAction(() => {
TransactionsStore.error = true;
TransactionsStore.error_msg = localeString(
'views.TxHex.transactionBroadcastCancelled'
);
TransactionsStore.loading = false;
});
return;
}
}
await TransactionsStore.broadcast(txHex);
if (!TransactionsStore.error) {
navigation.navigate('SendingOnChain');
}
} catch (error: any) {
console.error('Broadcast failed:', error);
runInAction(() => {
TransactionsStore.error = true;
TransactionsStore.error_msg =
error?.message || 'Broadcast failed';
TransactionsStore.loading = false;
});
}
};
render() { render() {
const { ChannelsStore, NodeInfoStore, TransactionsStore, navigation } = const { ChannelsStore, NodeInfoStore, TransactionsStore, navigation } =
this.props; this.props;
@@ -415,28 +480,7 @@ export default class TxHex extends React.Component<TxHexProps, TxHexState> {
? 'views.TxHex.finalizeFlowAndBroadcast' ? 'views.TxHex.finalizeFlowAndBroadcast'
: 'views.TxHex.broadcast' : 'views.TxHex.broadcast'
)} )}
onPress={() => { onPress={this.handleBroadcast}
if (
pending_chan_ids.length > 0
) {
TransactionsStore.finalizeTxHexAndBroadcastChannel(
txHex,
pending_chan_ids
).then(() => {
navigation.navigate(
'SendingOnChain'
);
});
} else {
TransactionsStore.broadcast(
txHex
).then(() => {
navigation.navigate(
'SendingOnChain'
);
});
}
}}
containerStyle={{ width: '100%' }} containerStyle={{ width: '100%' }}
buttonStyle={{ height: 40 }} buttonStyle={{ height: 40 }}
tertiary tertiary
-33
View File
@@ -1,33 +0,0 @@
pick 8f75f07d # fix: disable create transaction button on fee loading error
fixup b5f94d6d # fix: errors in the sweep view
# Rebase 5b024b2a..b5f94d6d onto 5b024b2a (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
# to this position in the new commits. The <ref> is
# updated at the end of the rebase
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#