feat(CLNRest): fallback to mempool.space for raw tx broadcast as it lacks publishTransaction
This commit is contained in:
@@ -35,11 +35,11 @@ import {
|
||||
posStore,
|
||||
settingsStore,
|
||||
swapStore,
|
||||
sweepStore,
|
||||
syncStore,
|
||||
transactionsStore,
|
||||
unitsStore,
|
||||
utxosStore
|
||||
utxosStore,
|
||||
sweepStore
|
||||
} from './stores/Stores';
|
||||
import NavigationService from './NavigationService';
|
||||
import PushNotificationManager from './PushNotificationManager';
|
||||
@@ -259,6 +259,7 @@ import Watchtowers from './views/Tools/Watchtowers/WatchtowerList';
|
||||
import AddWatchtower from './views/Tools/Watchtowers/AddWatchtower';
|
||||
import WatchtowerDetails from './views/Tools/Watchtowers/WatchtowerDetails';
|
||||
import ShareIntentProcessing from './views/ShareIntentProcessing';
|
||||
import WIFSweeper from './views/Tools/WIFSweeper';
|
||||
|
||||
import { isLightTheme, themeColor } from './utils/ThemeUtils';
|
||||
import LinkingUtils from './utils/LinkingUtils';
|
||||
@@ -266,7 +267,6 @@ import CreateWithdrawalRequest from './views/Tools/CreateWithdrawalRequest';
|
||||
import WithdrawalRequestView from './views/WithdrawalRequest';
|
||||
import WithdrawalRequestInfo from './views/WithdrawalRequestInfo';
|
||||
import RedeemWithdrawalRequest from './views/RedeemWithdrawalRequest';
|
||||
import WIFSweeper from './views/WIFSweeper';
|
||||
|
||||
export default class App extends React.PureComponent {
|
||||
private backPressListenerSubscription: NativeEventSubscription;
|
||||
|
||||
+13
-9
@@ -843,15 +843,16 @@
|
||||
"views.Wif.createTransaction": "Create Transaction",
|
||||
"views.Wif.invalidBase58Chars": "Invalid base58 characters",
|
||||
"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.Sweep.failedToFetchTxDetails": "Failed to fetch transaction details for {{txid}}",
|
||||
"views.Sweep.outputNotFound": "Output {{vout}} not found in transaction {{txid}}",
|
||||
"views.Sweep.outputIndexNotFound": "Output index {{vout}} not found in transaction {{txid}}",
|
||||
"views.Sweep.failedToDerivePubkey": "Failed to derive public key",
|
||||
"views.Sweep.noUtxosFound": "No UTXOs found for the given private key",
|
||||
"views.Sweep.addressTypeNotSupported": "This address type is not yet supported for sweeping",
|
||||
"views.Sweep.insufficientFundsAfterFees": "Insufficient funds after fees",
|
||||
"views.Sweep.noTaprootSupport": "Taproot (P2TR) sweep is not supported yet",
|
||||
"views.Wif.failedToFetchTxHex": "Failed to fetch transaction hex for {{txid}}",
|
||||
"views.Wif.failedToFetchTxDetails": "Failed to fetch transaction details for {{txid}}",
|
||||
"views.Wif.outputNotFound": "Output {{vout}} not found in transaction {{txid}}",
|
||||
"views.Wif.outputIndexNotFound": "Output index {{vout}} not found in transaction {{txid}}",
|
||||
"views.Wif.failedToDerivePubkey": "Failed to derive public key",
|
||||
"views.Wif.noUtxosFound": "No UTXOs found for the given private key",
|
||||
"views.Wif.addressTypeNotSupported": "This address type is not yet supported for sweeping",
|
||||
"views.Wif.insufficientFundsAfterFees": "Insufficient funds after fees",
|
||||
"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.enabled": "Enabled",
|
||||
"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.broadcast": "Broadcast TX",
|
||||
"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",
|
||||
"time.seconds": "Seconds",
|
||||
"time.minutes": "Minutes",
|
||||
|
||||
+1
-1
@@ -21,11 +21,11 @@ import PaymentsStore from './PaymentsStore';
|
||||
import PosStore from './PosStore';
|
||||
import SettingsStore from './SettingsStore';
|
||||
import SwapStore from './SwapStore';
|
||||
import SweepStore from './SweepStore';
|
||||
import SyncStore from './SyncStore';
|
||||
import TransactionsStore from './TransactionsStore';
|
||||
import UnitsStore from './UnitsStore';
|
||||
import UTXOsStore from './UTXOsStore';
|
||||
import SweepStore from './SweepStore';
|
||||
|
||||
export const settingsStore = new SettingsStore();
|
||||
export const modalStore = new ModalStore();
|
||||
|
||||
+24
-30
@@ -12,10 +12,8 @@ import { localeString } from '../utils/LocaleUtils';
|
||||
import ecc from '../zeus_modules/noble_ecc';
|
||||
|
||||
export default class SweepStore {
|
||||
@observable loading: boolean = false;
|
||||
@observable sweepErrorMsg: string | null = null;
|
||||
@observable sweepError: boolean = false;
|
||||
@observable sweepTxHex: string | null = null;
|
||||
@observable onChainBalance: number;
|
||||
@observable destination: string;
|
||||
@observable txHex: string | null = null;
|
||||
@@ -37,11 +35,6 @@ export default class SweepStore {
|
||||
this.nodeInfoStore = nodeInfoStore;
|
||||
}
|
||||
|
||||
@action
|
||||
setLoading(loading: boolean) {
|
||||
this.loading = loading;
|
||||
}
|
||||
|
||||
@action
|
||||
resetSweepError() {
|
||||
this.sweepError = false;
|
||||
@@ -53,7 +46,7 @@ export default class SweepStore {
|
||||
`${wifUtils.baseUrl(network)}/address/${address}/utxo`
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(localeString(`Error fetching UTXOs`));
|
||||
throw new Error(localeString('views.Wif.errorFetchingUtxos'));
|
||||
}
|
||||
|
||||
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
|
||||
@@ -141,7 +134,7 @@ export default class SweepStore {
|
||||
if (this.addressType === 'p2tr') {
|
||||
this.sweepError = true;
|
||||
this.sweepErrorMsg = localeString(
|
||||
'views.Sweep.addressTypeNotSupported'
|
||||
'views.Wif.addressTypeNotSupported'
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -153,11 +146,12 @@ export default class SweepStore {
|
||||
const res = await fetch(
|
||||
`${wifUtils.baseUrl(networkStr)}/tx/${txid}/hex`
|
||||
);
|
||||
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
localeString(
|
||||
`views.Sweep.failedToFetchTxHex:${txid}`
|
||||
)
|
||||
localeString('views.Wif.failedToFetchTxHex', {
|
||||
txid
|
||||
})
|
||||
);
|
||||
|
||||
const rawTxHex = await res.text();
|
||||
@@ -177,9 +171,9 @@ export default class SweepStore {
|
||||
);
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
localeString(
|
||||
`views.Sweep.failedToFetchTxDetails:${txid}`
|
||||
)
|
||||
localeString('views.Wif.failedToFetchTxDetails', {
|
||||
txid
|
||||
})
|
||||
);
|
||||
|
||||
const tx = await res.json();
|
||||
@@ -187,9 +181,10 @@ export default class SweepStore {
|
||||
|
||||
if (!output)
|
||||
throw new Error(
|
||||
localeString(
|
||||
`views.Sweep.outputIndexNotFound:${vout}:${txid}`
|
||||
)
|
||||
localeString('views.Sweep.outputIndexNotFound', {
|
||||
vout,
|
||||
txid
|
||||
})
|
||||
);
|
||||
|
||||
const value = Math.round(output.value);
|
||||
@@ -218,9 +213,9 @@ export default class SweepStore {
|
||||
);
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
localeString(
|
||||
`views.Sweep.failedToFetchTxDetails:${txid}`
|
||||
)
|
||||
localeString('views.Wif.failedToFetchTxDetails', {
|
||||
txid
|
||||
})
|
||||
);
|
||||
|
||||
const tx = await res.json();
|
||||
@@ -228,9 +223,10 @@ export default class SweepStore {
|
||||
|
||||
if (!output)
|
||||
throw new Error(
|
||||
localeString(
|
||||
`views.Sweep.outputNotFound:${vout}:${txid}`
|
||||
)
|
||||
localeString('views.Wif.outputNotFound', {
|
||||
vout,
|
||||
txid
|
||||
})
|
||||
);
|
||||
const scriptPubKeyHex = output.scriptpubkey;
|
||||
const script = Buffer.from(scriptPubKeyHex, 'hex');
|
||||
@@ -240,7 +236,7 @@ export default class SweepStore {
|
||||
|
||||
if (!fullPub) {
|
||||
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);
|
||||
if (!fullPub)
|
||||
throw new Error(
|
||||
localeString('views.Sweep.failedToDerivePubkey')
|
||||
);
|
||||
throw new Error(localeString('views.Wif.failedToDerivePubkey'));
|
||||
|
||||
const signer: bitcoin.Signer = {
|
||||
publicKey: Buffer.from(fullPub),
|
||||
@@ -310,7 +304,7 @@ export default class SweepStore {
|
||||
|
||||
if (this.valueToSend <= 0)
|
||||
throw new Error(
|
||||
localeString('views.Sweep.insufficientFundsAfterFees')
|
||||
localeString('views.Wif.insufficientFundsAfterFees')
|
||||
);
|
||||
|
||||
this.psbt.addOutput({
|
||||
|
||||
@@ -129,7 +129,7 @@ export default class TransactionsStore {
|
||||
};
|
||||
|
||||
@action
|
||||
public broadcast = (raw_final_tx: string) => {
|
||||
public broadcast = async (raw_final_tx: string) => {
|
||||
this.loading = true;
|
||||
|
||||
const tx_hex = raw_final_tx.includes('=')
|
||||
@@ -144,6 +144,48 @@ export default class TransactionsStore {
|
||||
txid = tx.getId();
|
||||
} 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({
|
||||
tx_hex
|
||||
})
|
||||
|
||||
@@ -5,34 +5,34 @@ import { Route } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { TouchableOpacity } from 'react-native-gesture-handler';
|
||||
|
||||
import InvoicesStore from '../stores/InvoicesStore';
|
||||
import SweepStore from '../stores/SweepStore';
|
||||
import InvoicesStore from '../../stores/InvoicesStore';
|
||||
import SweepStore from '../../stores/SweepStore';
|
||||
|
||||
import Button from '../components/Button';
|
||||
import { ErrorMessage } from '../components/SuccessErrorMessage';
|
||||
import Header from '../components/Header';
|
||||
import Screen from '../components/Screen';
|
||||
import TextInput from '../components/TextInput';
|
||||
import LoadingIndicator from '../components/LoadingIndicator';
|
||||
import OnchainFeeInput from '../components/OnchainFeeInput';
|
||||
import ShowHideToggle from '../components/ShowHideToggle';
|
||||
import Button from '../../components/Button';
|
||||
import { ErrorMessage } from '../../components/SuccessErrorMessage';
|
||||
import Header from '../../components/Header';
|
||||
import Screen from '../../components/Screen';
|
||||
import TextInput from '../../components/TextInput';
|
||||
import LoadingIndicator from '../../components/LoadingIndicator';
|
||||
import OnchainFeeInput from '../../components/OnchainFeeInput';
|
||||
import ShowHideToggle from '../../components/ShowHideToggle';
|
||||
|
||||
import { localeString } from '../utils/LocaleUtils';
|
||||
import { themeColor } from '../utils/ThemeUtils';
|
||||
import wifUtils from '../utils/WIFUtils';
|
||||
import AddressUtils from '../utils/AddressUtils';
|
||||
import { localeString } from '../../utils/LocaleUtils';
|
||||
import { themeColor } from '../../utils/ThemeUtils';
|
||||
import wifUtils from '../../utils/WIFUtils';
|
||||
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;
|
||||
navigation: StackNavigationProp<any, any>;
|
||||
InvoicesStore: InvoicesStore;
|
||||
SweepStore: SweepStore;
|
||||
route: Route<'Sweep', { wif: string }>;
|
||||
route: Route<'WIFSweeper', { wif: string }>;
|
||||
}
|
||||
|
||||
interface SweepState {
|
||||
interface WIFSweepState {
|
||||
privateKey: string;
|
||||
hidden: boolean;
|
||||
isValid: boolean;
|
||||
@@ -41,13 +41,14 @@ interface SweepState {
|
||||
onChainAddressloading: boolean;
|
||||
feeRate: string;
|
||||
feeLoadingError: boolean;
|
||||
isWifValid: boolean;
|
||||
}
|
||||
|
||||
@inject('SweepStore', 'InvoicesStore')
|
||||
@observer
|
||||
export default class WIFSweeper extends React.Component<
|
||||
SweepProps,
|
||||
SweepState
|
||||
WIFSweepProps,
|
||||
WIFSweepState
|
||||
> {
|
||||
state = {
|
||||
privateKey: '',
|
||||
@@ -57,7 +58,8 @@ export default class WIFSweeper extends React.Component<
|
||||
loading: false,
|
||||
onChainAddressloading: false,
|
||||
feeRate: '2',
|
||||
feeLoadingError: false
|
||||
feeLoadingError: false,
|
||||
isWifValid: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -74,7 +76,7 @@ export default class WIFSweeper extends React.Component<
|
||||
this.initFromProps(this.props);
|
||||
}
|
||||
|
||||
initFromProps(props: SweepProps) {
|
||||
initFromProps(props: WIFSweepProps) {
|
||||
const { route } = props;
|
||||
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) {
|
||||
this.initFromProps(this.props);
|
||||
}
|
||||
@@ -160,11 +162,25 @@ export default class WIFSweeper extends React.Component<
|
||||
'views.Wif.enterPrivateKeyPlaceholder'
|
||||
)}
|
||||
value={privateKey}
|
||||
onChangeText={(text: string) =>
|
||||
this.setState({ privateKey: text })
|
||||
}
|
||||
onChangeText={(text: string) => {
|
||||
const isWifValid =
|
||||
text.length === 0 ||
|
||||
wifUtils.validateWIF(text).isValid;
|
||||
this.setState({
|
||||
privateKey: text,
|
||||
isWifValid
|
||||
});
|
||||
}}
|
||||
secureTextEntry={hidden}
|
||||
style={{ flex: 1, marginRight: 15 }}
|
||||
style={{
|
||||
flex: 1,
|
||||
marginRight: 15
|
||||
}}
|
||||
textColor={
|
||||
!this.state.isWifValid
|
||||
? themeColor('delete')
|
||||
: themeColor('text')
|
||||
}
|
||||
locked={loading}
|
||||
/>
|
||||
<ShowHideToggle
|
||||
@@ -207,6 +223,13 @@ export default class WIFSweeper extends React.Component<
|
||||
marginHorizontal: 20
|
||||
}}
|
||||
value={SweepStore.destination}
|
||||
textColor={
|
||||
SweepStore.destination &&
|
||||
!this.state.isValid
|
||||
? themeColor('delete')
|
||||
: themeColor('text')
|
||||
}
|
||||
locked={loading}
|
||||
/>
|
||||
{onChainAddressloading && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
@@ -228,7 +251,7 @@ export default class WIFSweeper extends React.Component<
|
||||
onChainAddressloading: true
|
||||
});
|
||||
this.props.SweepStore.resetSweepError();
|
||||
const { isValid } =
|
||||
const { isValid, error } =
|
||||
wifUtils.validateWIF(privateKey);
|
||||
if (isValid) {
|
||||
await this.props.SweepStore.prepareSweepInputs(
|
||||
@@ -236,16 +259,17 @@ export default class WIFSweeper extends React.Component<
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
localeString(
|
||||
'views.Wif.invalidWif'
|
||||
)
|
||||
error ||
|
||||
localeString(
|
||||
'views.Wif.invalidWif'
|
||||
)
|
||||
);
|
||||
}
|
||||
if (SweepStore.sweepError) {
|
||||
throw new Error(
|
||||
SweepStore.sweepErrorMsg ||
|
||||
localeString(
|
||||
'views.Sweep.unknownError'
|
||||
'views.Wif.invalidWif'
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -295,7 +319,9 @@ export default class WIFSweeper extends React.Component<
|
||||
)}
|
||||
secondary
|
||||
disabled={
|
||||
!privateKey || !!SweepStore.destination
|
||||
!privateKey ||
|
||||
!this.state.isWifValid ||
|
||||
!!SweepStore.destination
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
@@ -332,12 +358,30 @@ export default class WIFSweeper extends React.Component<
|
||||
this.state.privateKey
|
||||
);
|
||||
if (isValid) {
|
||||
await this.props.SweepStore.finalizeSweepTransaction(
|
||||
feeRate
|
||||
);
|
||||
navigation.navigate('TxHex', {
|
||||
txHex: SweepStore.txHex
|
||||
});
|
||||
try {
|
||||
await this.props.SweepStore.finalizeSweepTransaction(
|
||||
feeRate
|
||||
);
|
||||
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 {
|
||||
this.props.SweepStore.sweepError = true;
|
||||
this.props.SweepStore.sweepErrorMsg =
|
||||
@@ -349,6 +393,7 @@ export default class WIFSweeper extends React.Component<
|
||||
!privateKey ||
|
||||
loading ||
|
||||
!SweepStore.destination ||
|
||||
!this.state.isValid ||
|
||||
sweepError ||
|
||||
feeLoadingError ||
|
||||
(!!error && error.length > 0)
|
||||
+68
-24
@@ -4,7 +4,8 @@ import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
View,
|
||||
Alert
|
||||
} from 'react-native';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { ButtonGroup } from 'react-native-elements';
|
||||
@@ -12,6 +13,7 @@ import { UR, UREncoder } from '@ngraveio/bc-ur';
|
||||
import clone from 'lodash/clone';
|
||||
import { Route } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { runInAction } from 'mobx';
|
||||
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
|
||||
@@ -34,6 +36,7 @@ import UrlUtils from '../utils/UrlUtils';
|
||||
import ChannelsStore from '../stores/ChannelsStore';
|
||||
import NodeInfoStore from '../stores/NodeInfoStore';
|
||||
import TransactionsStore from '../stores/TransactionsStore';
|
||||
import SettingsStore from '../stores/SettingsStore';
|
||||
import {
|
||||
getQRAnimationInterval,
|
||||
QRAnimationSpeed
|
||||
@@ -41,6 +44,7 @@ import {
|
||||
|
||||
interface TxHexProps {
|
||||
navigation: StackNavigationProp<any, any>;
|
||||
SettingsStore: SettingsStore;
|
||||
ChannelsStore: ChannelsStore;
|
||||
NodeInfoStore: NodeInfoStore;
|
||||
TransactionsStore: TransactionsStore;
|
||||
@@ -59,7 +63,7 @@ interface TxHexState {
|
||||
qrAnimationSpeed: QRAnimationSpeed;
|
||||
}
|
||||
|
||||
@inject('ChannelsStore', 'NodeInfoStore', 'TransactionsStore')
|
||||
@inject('ChannelsStore', 'NodeInfoStore', 'TransactionsStore', 'SettingsStore')
|
||||
@observer
|
||||
export default class TxHex extends React.Component<TxHexProps, TxHexState> {
|
||||
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() {
|
||||
const { ChannelsStore, NodeInfoStore, TransactionsStore, navigation } =
|
||||
this.props;
|
||||
@@ -415,28 +480,7 @@ export default class TxHex extends React.Component<TxHexProps, TxHexState> {
|
||||
? 'views.TxHex.finalizeFlowAndBroadcast'
|
||||
: 'views.TxHex.broadcast'
|
||||
)}
|
||||
onPress={() => {
|
||||
if (
|
||||
pending_chan_ids.length > 0
|
||||
) {
|
||||
TransactionsStore.finalizeTxHexAndBroadcastChannel(
|
||||
txHex,
|
||||
pending_chan_ids
|
||||
).then(() => {
|
||||
navigation.navigate(
|
||||
'SendingOnChain'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
TransactionsStore.broadcast(
|
||||
txHex
|
||||
).then(() => {
|
||||
navigation.navigate(
|
||||
'SendingOnChain'
|
||||
);
|
||||
});
|
||||
}
|
||||
}}
|
||||
onPress={this.handleBroadcast}
|
||||
containerStyle={{ width: '100%' }}
|
||||
buttonStyle={{ height: 40 }}
|
||||
tertiary
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
Reference in New Issue
Block a user