Files
app.zeusln.zeus/views/Tools/WIFSweeper.tsx
T

424 lines
18 KiB
TypeScript

import * as React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { inject, observer } from 'mobx-react';
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 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 Scan from '../../assets/images/SVG/Scan.svg';
interface WIFSweepProps {
exitSetup: any;
navigation: StackNavigationProp<any, any>;
InvoicesStore: InvoicesStore;
SweepStore: SweepStore;
route: Route<'WIFSweeper', { wif: string }>;
}
interface WIFSweepState {
privateKey: string;
hidden: boolean;
isValid: boolean;
error: string;
loading: boolean;
onChainAddressloading: boolean;
feeRate: string;
feeLoadingError: boolean;
isWifValid: boolean;
}
@inject('SweepStore', 'InvoicesStore')
@observer
export default class WIFSweeper extends React.Component<
WIFSweepProps,
WIFSweepState
> {
state = {
privateKey: '',
hidden: true,
isValid: false,
error: '',
loading: false,
onChainAddressloading: false,
feeRate: '2',
feeLoadingError: false,
isWifValid: true
};
componentDidMount() {
this.props.SweepStore.destination = '';
this.setState({
error: '',
isValid: false,
onChainAddressloading: false
});
this.props.InvoicesStore.reset();
this.props.SweepStore.resetSweepError();
this.initFromProps(this.props);
}
initFromProps(props: WIFSweepProps) {
const { route } = props;
const scannedKey = route.params?.wif;
if (scannedKey) {
this.setState({
privateKey: scannedKey
});
}
}
componentDidUpdate(prevProps: WIFSweepProps) {
if (prevProps.route.params?.wif !== this.props.route.params?.wif) {
this.initFromProps(this.props);
}
}
render() {
const { navigation, SweepStore, InvoicesStore } = this.props;
const {
privateKey,
hidden,
error,
loading,
onChainAddressloading,
feeRate,
feeLoadingError
} = this.state;
const { sweepError, sweepErrorMsg } = SweepStore;
const ScanButton = () => (
<TouchableOpacity
onPress={() =>
navigation.navigate('HandleAnythingQRScanner', {
wif: true
})
}
>
<Scan fill={themeColor('text')} width={30} height={30} />
</TouchableOpacity>
);
return (
<Screen>
<View style={{ flex: 1 }}>
<Header
leftComponent="Back"
centerComponent={{
text: localeString('views.Wif.title'),
style: {
color: themeColor('text'),
fontFamily: 'PPNeueMontreal-Book'
}
}}
navigation={navigation}
rightComponent={<ScanButton />}
/>
{(sweepError || error) && (
<View>
<ErrorMessage
message={
error ||
sweepErrorMsg ||
localeString('views.Wif.invalidWif')
}
/>
</View>
)}
<View style={{ padding: 20, flex: 1 }}>
<Text style={{ color: themeColor('secondaryText') }}>
{localeString('views.Wif.enterPrivateKey')}
</Text>
<View
style={{
flexDirection: 'row',
alignItems: 'center'
}}
>
<TextInput
placeholder={localeString(
'views.Wif.enterPrivateKeyPlaceholder'
)}
value={privateKey}
onChangeText={(text: string) => {
const isWifValid =
text.length === 0 ||
wifUtils.validateWIF(text).isValid;
this.setState({
privateKey: text,
isWifValid
});
}}
secureTextEntry={hidden}
style={{
flex: 1,
marginRight: 15
}}
textColor={
!this.state.isWifValid
? themeColor('delete')
: themeColor('text')
}
locked={loading}
/>
<ShowHideToggle
onPress={() =>
this.setState({
hidden: !hidden
})
}
/>
</View>
</View>
<View>
<View
style={{ position: 'relative', marginBottom: 10 }}
>
<TextInput
onChangeText={async (text: string) => {
let isValid;
isValid = text
? AddressUtils.isValidBitcoinAddress(
text,
true
)
: false;
this.props.SweepStore.destination = text;
this.setState({
isValid,
error: ''
});
}}
placeholder={
onChainAddressloading
? ''
: localeString(
'views.Wif.destinationOnChainAddress'
)
}
style={{
marginHorizontal: 20
}}
value={SweepStore.destination}
textColor={
SweepStore.destination &&
!this.state.isValid
? themeColor('delete')
: themeColor('text')
}
locked={loading}
/>
{onChainAddressloading && (
<View style={styles.loadingOverlay}>
<LoadingIndicator />
</View>
)}
</View>
<View
style={{
paddingBottom: 30
}}
>
<Button
onPress={async () => {
try {
this.props.SweepStore.destination = '';
this.setState({
onChainAddressloading: true
});
this.props.SweepStore.resetSweepError();
const { isValid, error } =
wifUtils.validateWIF(privateKey);
if (isValid) {
await this.props.SweepStore.prepareSweepInputs(
privateKey
);
} else {
throw new Error(
error ||
localeString(
'views.Wif.invalidWif'
)
);
}
if (SweepStore.sweepError) {
throw new Error(
SweepStore.sweepErrorMsg ||
localeString(
'views.Wif.invalidWif'
)
);
}
if (!SweepStore.onChainBalance) {
throw new Error(
localeString(
'views.Wif.noUtxos'
)
);
}
await InvoicesStore.createUnifiedInvoice(
{
memo: '',
value:
SweepStore.onChainBalance.toString() ??
'10',
expirySeconds: '3600'
}
);
if (InvoicesStore.onChainAddress) {
this.props.SweepStore.destination =
InvoicesStore.onChainAddress;
this.setState({
error: '',
isValid: true,
onChainAddressloading: false
});
} else {
throw new Error(
localeString(
'views.Swaps.generateOnchainAddressFailed'
)
);
}
} catch (e: any) {
this.props.SweepStore.sweepError = true;
this.props.SweepStore.sweepErrorMsg =
e.message;
this.setState({
error: e.message,
onChainAddressloading: false
});
}
}}
title={localeString(
'views.Swaps.generateOnchainAddress'
)}
secondary
disabled={
!privateKey ||
!this.state.isWifValid ||
!!SweepStore.destination
}
/>
</View>
<View
style={{ marginHorizontal: 20, paddingBottom: 10 }}
>
<Text
style={{
...styles.text,
color: themeColor('secondaryText')
}}
>
{localeString('views.Send.feeSatsVbyte')}
</Text>
<OnchainFeeInput
fee={feeRate}
onChangeFee={(text: string) =>
this.setState({
feeRate: text,
feeLoadingError: false
})
}
onFeeError={(error) =>
this.setState({ feeLoadingError: error })
}
navigation={navigation}
/>
</View>
<Button
title={localeString('views.Wif.createTransaction')}
onPress={async () => {
const { isValid, error } = wifUtils.validateWIF(
this.state.privateKey
);
if (isValid) {
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 =
error ||
localeString('views.Wif.invalidWif');
}
}}
disabled={
!privateKey ||
loading ||
!SweepStore.destination ||
!this.state.isValid ||
sweepError ||
feeLoadingError ||
(!!error && error.length > 0)
}
containerStyle={{ paddingBottom: 30 }}
/>
</View>
</View>
</Screen>
);
}
}
const styles = StyleSheet.create({
loadingOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center'
},
text: {
fontFamily: 'PPNeueMontreal-Book'
}
});