mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
REF: PSBTWithHardware screen uses Hooks
This commit is contained in:
parent
106300994f
commit
07877161fd
@ -144,42 +144,41 @@ export const BlueButtonHook = props => {
|
||||
);
|
||||
};
|
||||
|
||||
export class SecondButton extends Component {
|
||||
render() {
|
||||
let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : BlueCurrentTheme.colors.buttonBlueBackgroundColor;
|
||||
let fontColor = BlueCurrentTheme.colors.buttonTextColor;
|
||||
if (this.props.disabled === true) {
|
||||
backgroundColor = BlueCurrentTheme.colors.buttonDisabledBackgroundColor;
|
||||
fontColor = BlueCurrentTheme.colors.buttonDisabledTextColor;
|
||||
}
|
||||
// let buttonWidth = this.props.width ? this.props.width : width / 1.5;
|
||||
// if ('noMinWidth' in this.props) {
|
||||
// buttonWidth = 0;
|
||||
// }
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: backgroundColor,
|
||||
minHeight: 45,
|
||||
height: 45,
|
||||
maxHeight: 45,
|
||||
borderRadius: 25,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
{...this.props}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{this.props.icon && <Icon name={this.props.icon.name} type={this.props.icon.type} color={this.props.icon.color} />}
|
||||
{this.props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{this.props.title}</Text>}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
export const SecondButton = props => {
|
||||
const { colors } = useTheme();
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor;
|
||||
let fontColor = colors.buttonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
}
|
||||
}
|
||||
// let buttonWidth = this.props.width ? this.props.width : width / 1.5;
|
||||
// if ('noMinWidth' in this.props) {
|
||||
// buttonWidth = 0;
|
||||
// }
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: backgroundColor,
|
||||
minHeight: 45,
|
||||
height: 45,
|
||||
maxHeight: 45,
|
||||
borderRadius: 25,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{props.title}</Text>}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export const BitcoinButton = props => {
|
||||
const { colors } = useTheme();
|
||||
|
@ -146,7 +146,7 @@ const WalletsRoot = () => (
|
||||
component={LNDViewInvoice}
|
||||
options={LNDViewInvoice.navigationOptions}
|
||||
swipeEnabled={false}
|
||||
gesturesEnabled={false}
|
||||
gestureEnabled={false}
|
||||
/>
|
||||
<WalletsStack.Screen
|
||||
name="LNDViewAdditionalInvoiceInformation"
|
||||
@ -190,7 +190,14 @@ const SendDetailsRoot = () => (
|
||||
/>
|
||||
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions} />
|
||||
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions} />
|
||||
<SendDetailsStack.Screen name="Success" component={Success} options={Success.navigationOptions} />
|
||||
<SendDetailsStack.Screen
|
||||
name="Success"
|
||||
component={Success}
|
||||
options={{
|
||||
headerShown: false,
|
||||
gestureEnabled: false,
|
||||
}}
|
||||
/>
|
||||
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
|
||||
</SendDetailsStack.Navigator>
|
||||
);
|
||||
@ -205,7 +212,7 @@ const LNDCreateInvoiceRoot = () => (
|
||||
component={LNDViewInvoice}
|
||||
options={LNDViewInvoice.navigationOptions}
|
||||
swipeEnabled={false}
|
||||
gesturesEnabled={false}
|
||||
gestureEnabled={false}
|
||||
/>
|
||||
<LNDCreateInvoiceStack.Screen
|
||||
name="LNDViewAdditionalInvoiceInformation"
|
||||
|
@ -212,6 +212,7 @@
|
||||
"psbt_clipboard": "Copy to Clipboard",
|
||||
"psbt_this_is_psbt": "This is a partially signed bitcoin transaction (PSBT). Please finish signing it with your hardware wallet.",
|
||||
"psbt_tx_export": "Export to file",
|
||||
"no_tx_signing_in_progress": "There is no transaction signing in progress",
|
||||
"psbt_tx_open": "Open Signed Transaction",
|
||||
"psbt_tx_scan": "Scan Signed Transaction",
|
||||
"qr_error_no_qrcode": "The selected image does not contain a QR Code.",
|
||||
|
@ -1,11 +1,10 @@
|
||||
/* global alert */
|
||||
import React, { Component } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
View,
|
||||
Dimensions,
|
||||
TextInput,
|
||||
Linking,
|
||||
Platform,
|
||||
@ -16,7 +15,6 @@ import {
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import {
|
||||
BlueButton,
|
||||
SecondButton,
|
||||
BlueText,
|
||||
SafeBlueArea,
|
||||
@ -24,30 +22,304 @@ import {
|
||||
BlueNavigationStyle,
|
||||
BlueSpacing20,
|
||||
BlueCopyToClipboardButton,
|
||||
BlueBigCheckmark,
|
||||
DynamicQRCode,
|
||||
} from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import Share from 'react-native-share';
|
||||
import { getSystemName } from 'react-native-device-info';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import RNFS from 'react-native-fs';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import loc from '../../loc';
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
import ScanQRCode from './ScanQRCode';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
/** @type {AppStorage} */
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
const { height, width } = Dimensions.get('window');
|
||||
const isDesktop = getSystemName() === 'Mac OS X';
|
||||
|
||||
const PsbtWithHardwareWallet = () => {
|
||||
const { txMetadata, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
const { fromWallet, memo, psbt, deepLinkPSBT } = route.params;
|
||||
const routeParamsPSBT = useRef(route.params.psbt);
|
||||
const routeParamsTXHex = route.params.txhex;
|
||||
const { colors } = useTheme();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [txHex, setTxHex] = useState(route.params.txhex);
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
rootPadding: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
hexWrap: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
hexLabel: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
hexInput: {
|
||||
borderColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
hexText: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
const _combinePSBT = receivedPSBT => {
|
||||
return fromWallet.combinePsbt(psbt, receivedPSBT);
|
||||
};
|
||||
|
||||
const onBarScanned = ret => {
|
||||
if (ret && !ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
alert('BC-UR not decoded. This should never happen');
|
||||
}
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
setTxHex(ret.data);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const Tx = _combinePSBT(ret.data);
|
||||
setTxHex(Tx.toHex());
|
||||
} catch (Err) {
|
||||
alert(Err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!psbt && !route.params.txhex) {
|
||||
alert(loc.send.no_tx_signing_in_progress);
|
||||
}
|
||||
|
||||
if (deepLinkPSBT) {
|
||||
const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
|
||||
try {
|
||||
const Tx = fromWallet.combinePsbt(routeParamsPSBT.current, psbt);
|
||||
setTxHex(Tx.toHex());
|
||||
} catch (Err) {
|
||||
alert(Err);
|
||||
}
|
||||
} else if (routeParamsTXHex) {
|
||||
setTxHex(routeParamsTXHex);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [deepLinkPSBT, routeParamsTXHex]);
|
||||
|
||||
const broadcast = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const result = await fromWallet.broadcastTx(txHex);
|
||||
if (result) {
|
||||
setIsLoading(false);
|
||||
const txDecoded = bitcoin.Transaction.fromHex(txHex);
|
||||
const txid = txDecoded.getId();
|
||||
Notifications.majorTomToGroundControl([], [], [txid]);
|
||||
if (memo) {
|
||||
txMetadata[txid] = { memo };
|
||||
}
|
||||
fetchAndSaveWalletTransactions(fromWallet.getID());
|
||||
navigation.navigate('Success', { amount: undefined });
|
||||
} else {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
setIsLoading(false);
|
||||
alert(loc.errors.broadcast);
|
||||
}
|
||||
} catch (error) {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
setIsLoading(false);
|
||||
alert(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const _renderBroadcastHex = () => {
|
||||
return (
|
||||
<View style={[styles.rootPadding, stylesHook.rootPadding]}>
|
||||
<BlueCard style={[styles.hexWrap, stylesHook.hexWrap]}>
|
||||
<BlueText style={[styles.hexLabel, stylesHook.hexLabel]}>{loc.send.create_this_is_hex}</BlueText>
|
||||
<TextInput style={[styles.hexInput, stylesHook.hexInput]} height={112} multiline editable value={txHex} />
|
||||
|
||||
<TouchableOpacity style={styles.hexTouch} onPress={Clipboard.setString}>
|
||||
<Text style={[styles.hexText, stylesHook.hexText]}>{loc.send.create_copy}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.hexTouch} onPress={() => Linking.openURL('https://coinb.in/?verify=' + txHex)}>
|
||||
<Text style={[styles.hexText, stylesHook.hexText]}>{loc.send.create_verify}</Text>
|
||||
</TouchableOpacity>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton onPress={broadcast} title={loc.send.confirm_sendNow} testID="PsbtWithHardwareWalletBroadcastTransactionButton" />
|
||||
</BlueCard>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const exportPSBT = async () => {
|
||||
const fileName = `${Date.now()}.psbt`;
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`;
|
||||
await RNFS.writeFile(filePath, typeof psbt === 'string' ? psbt : psbt.toBase64());
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: loc.send.permission_storage_title,
|
||||
message: loc.send.permission_storage_message,
|
||||
buttonNeutral: loc.send.permission_storage_later,
|
||||
buttonNegative: loc._.cancel,
|
||||
buttonPositive: loc._.ok,
|
||||
});
|
||||
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`;
|
||||
await RNFS.writeFile(filePath, typeof psbt === 'string' ? psbt : psbt.toBase64());
|
||||
alert(loc.formatString(loc.send.txSaved, { filePath: fileName }));
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const openSignedTransaction = async () => {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
});
|
||||
const file = await RNFS.readFile(res.uri);
|
||||
if (file) {
|
||||
onBarScanned({ data: file });
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert(loc.send.details_no_signed_tx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const openScanner = () => {
|
||||
if (isDesktop) {
|
||||
ImagePicker.launchCamera(
|
||||
{
|
||||
title: null,
|
||||
mediaType: 'photo',
|
||||
takePhotoButtonTitle: null,
|
||||
},
|
||||
response => {
|
||||
if (response.uri) {
|
||||
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
|
||||
LocalQRCode.decode(uri, (error, result) => {
|
||||
if (!error) {
|
||||
onBarScanned(result);
|
||||
} else {
|
||||
alert(loc.send.qr_error_no_qrcode);
|
||||
}
|
||||
});
|
||||
} else if (response.error) {
|
||||
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
launchedBy: route.name,
|
||||
showFileImportButton: false,
|
||||
onBarScanned,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (txHex) return _renderBroadcastHex();
|
||||
|
||||
return isLoading ? (
|
||||
<View style={[styles.rootPadding, stylesHook.rootPadding]}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : (
|
||||
<SafeBlueArea style={[styles.root, stylesHook.root]}>
|
||||
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent} testID="PsbtWithHardwareScrollView">
|
||||
<View style={styles.container}>
|
||||
<BlueCard>
|
||||
<BlueText testID="TextHelperForPSBT">{loc.send.psbt_this_is_psbt}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<DynamicQRCode value={psbt.toHex()} capacity={200} />
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
testID="PsbtTxScanButton"
|
||||
icon={{
|
||||
name: 'qrcode',
|
||||
type: 'font-awesome',
|
||||
color: colors.buttonTextColor,
|
||||
}}
|
||||
onPress={openScanner}
|
||||
title={loc.send.psbt_tx_scan}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
icon={{
|
||||
name: 'file-import',
|
||||
type: 'material-community',
|
||||
color: colors.buttonTextColor,
|
||||
}}
|
||||
onPress={openSignedTransaction}
|
||||
title={loc.send.psbt_tx_open}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
icon={{
|
||||
name: 'share-alternative',
|
||||
type: 'entypo',
|
||||
color: colors.buttonTextColor,
|
||||
}}
|
||||
onPress={exportPSBT}
|
||||
title={loc.send.psbt_tx_export}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.copyToClipboard}>
|
||||
<BlueCopyToClipboardButton
|
||||
stringToCopy={typeof psbt === 'string' ? psbt : psbt.toBase64()}
|
||||
displayText={loc.send.psbt_clipboard}
|
||||
/>
|
||||
</View>
|
||||
</BlueCard>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default PsbtWithHardwareWallet;
|
||||
|
||||
PsbtWithHardwareWallet.navigationOptions = () => ({
|
||||
...BlueNavigationStyle(null, false),
|
||||
title: loc.send.header,
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
scrollViewContent: {
|
||||
flexGrow: 1,
|
||||
@ -66,7 +338,6 @@ const styles = StyleSheet.create({
|
||||
rootPadding: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
closeCamera: {
|
||||
width: 40,
|
||||
@ -88,18 +359,13 @@ const styles = StyleSheet.create({
|
||||
hexWrap: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
hexLabel: {
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
fontWeight: '500',
|
||||
},
|
||||
hexInput: {
|
||||
borderColor: BlueCurrentTheme.colors.formBorder,
|
||||
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
|
||||
borderRadius: 4,
|
||||
marginTop: 20,
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
paddingHorizontal: 16,
|
||||
@ -110,7 +376,6 @@ const styles = StyleSheet.create({
|
||||
marginVertical: 24,
|
||||
},
|
||||
hexText: {
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
alignSelf: 'center',
|
||||
@ -120,314 +385,3 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default class PsbtWithHardwareWallet extends Component {
|
||||
cameraRef = null;
|
||||
static contextType = BlueStorageContext;
|
||||
|
||||
_combinePSBT = receivedPSBT => {
|
||||
return this.state.fromWallet.combinePsbt(this.state.psbt, receivedPSBT);
|
||||
};
|
||||
|
||||
onBarScanned = ret => {
|
||||
if (ret && !ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
alert('BC-UR not decoded. This should never happen');
|
||||
}
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
this.setState({ txhex: ret.data });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const Tx = this._combinePSBT(ret.data);
|
||||
this.setState({ txhex: Tx.toHex() });
|
||||
} catch (Err) {
|
||||
alert(Err);
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
qrCodeHeight: height > width ? width - 40 : width / 3,
|
||||
memo: props.route.params.memo,
|
||||
psbt: props.route.params.psbt,
|
||||
fromWallet: props.route.params.fromWallet,
|
||||
isSecondPSBTAlreadyBase64: false,
|
||||
deepLinkPSBT: undefined,
|
||||
txhex: props.route.params.txhex || undefined,
|
||||
animatedQRCodeData: [],
|
||||
};
|
||||
this.fileName = `${Date.now()}.psbt`;
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (!prevState.psbt && !nextProps.route.params.txhex) {
|
||||
alert('There is no transaction signing in progress');
|
||||
return {
|
||||
...prevState,
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
const deepLinkPSBT = nextProps.route.params.deepLinkPSBT;
|
||||
const txhex = nextProps.route.params.txhex;
|
||||
if (deepLinkPSBT) {
|
||||
const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
|
||||
try {
|
||||
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, psbt);
|
||||
return {
|
||||
...prevState,
|
||||
txhex: Tx.toHex(),
|
||||
};
|
||||
} catch (Err) {
|
||||
alert(Err);
|
||||
}
|
||||
} else if (txhex) {
|
||||
return {
|
||||
...prevState,
|
||||
txhex: txhex,
|
||||
};
|
||||
}
|
||||
return prevState;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('send/psbtWithHardwareWallet - componentDidMount');
|
||||
}
|
||||
|
||||
broadcast = () => {
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
try {
|
||||
await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const result = await this.state.fromWallet.broadcastTx(this.state.txhex);
|
||||
if (result) {
|
||||
this.setState({ success: true, isLoading: false });
|
||||
const txDecoded = bitcoin.Transaction.fromHex(this.state.txhex);
|
||||
const txid = txDecoded.getId();
|
||||
Notifications.majorTomToGroundControl([], [], [txid]);
|
||||
if (this.state.memo) {
|
||||
this.context.txMetadata[txid] = { memo: this.state.memo };
|
||||
}
|
||||
} else {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
this.setState({ isLoading: false });
|
||||
alert(loc.errors.broadcast);
|
||||
}
|
||||
} catch (error) {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
this.setState({ isLoading: false });
|
||||
alert(error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_renderSuccess() {
|
||||
return (
|
||||
<SafeBlueArea style={styles.root}>
|
||||
<BlueBigCheckmark style={styles.blueBigCheckmark} />
|
||||
<BlueCard>
|
||||
<BlueButton onPress={() => this.props.navigation.dangerouslyGetParent().pop()} title={loc.send.success_done} />
|
||||
</BlueCard>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
||||
_renderBroadcastHex() {
|
||||
return (
|
||||
<View style={styles.rootPadding}>
|
||||
<BlueCard style={styles.hexWrap}>
|
||||
<BlueText style={styles.hexLabel}>{loc.send.create_this_is_hex}</BlueText>
|
||||
<TextInput style={styles.hexInput} height={112} multiline editable value={this.state.txhex} />
|
||||
|
||||
<TouchableOpacity style={styles.hexTouch} onPress={() => Clipboard.setString(this.state.txhex)}>
|
||||
<Text style={styles.hexText}>{loc.send.create_copy}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.hexTouch} onPress={() => Linking.openURL('https://coinb.in/?verify=' + this.state.txhex)}>
|
||||
<Text style={styles.hexText}>{loc.send.create_verify}</Text>
|
||||
</TouchableOpacity>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
onPress={this.broadcast}
|
||||
title={loc.send.confirm_sendNow}
|
||||
testID="PsbtWithHardwareWalletBroadcastTransactionButton"
|
||||
/>
|
||||
</BlueCard>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
exportPSBT = async () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${this.fileName}`;
|
||||
await RNFS.writeFile(filePath, typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64());
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
alert(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: loc.send.permission_storage_title,
|
||||
message: loc.send.permission_storage_message,
|
||||
buttonNeutral: loc.send.permission_storage_later,
|
||||
buttonNegative: loc._.cancel,
|
||||
buttonPositive: loc._.ok,
|
||||
});
|
||||
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.DownloadDirectoryPath + `/${this.fileName}`;
|
||||
await RNFS.writeFile(filePath, typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64());
|
||||
alert(loc.formatString(loc.send.txSaved, { filePath: this.fileName }));
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
openSignedTransaction = async () => {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
});
|
||||
const file = await RNFS.readFile(res.uri);
|
||||
if (file) {
|
||||
this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarScanned({ data: file }));
|
||||
} else {
|
||||
this.setState({ isSecondPSBTAlreadyBase64: false });
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert(loc.send.details_no_signed_tx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
openScanner = () => {
|
||||
if (isDesktop) {
|
||||
ImagePicker.launchCamera(
|
||||
{
|
||||
title: null,
|
||||
mediaType: 'photo',
|
||||
takePhotoButtonTitle: null,
|
||||
},
|
||||
response => {
|
||||
if (response.uri) {
|
||||
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
|
||||
LocalQRCode.decode(uri, (error, result) => {
|
||||
if (!error) {
|
||||
this.onBarScanned(result);
|
||||
} else {
|
||||
alert(loc.send.qr_error_no_qrcode);
|
||||
}
|
||||
});
|
||||
} else if (response.error) {
|
||||
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.props.navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
launchedBy: this.props.route.name,
|
||||
showFileImportButton: false,
|
||||
onBarScanned: this.onBarScanned,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return (
|
||||
<View style={styles.rootPadding}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.success) return this._renderSuccess();
|
||||
if (this.state.txhex) return this._renderBroadcastHex();
|
||||
|
||||
return (
|
||||
<SafeBlueArea style={styles.root}>
|
||||
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent} testID="PsbtWithHardwareScrollView">
|
||||
<View style={styles.container}>
|
||||
<BlueCard>
|
||||
<BlueText testID="TextHelperForPSBT">{loc.send.psbt_this_is_psbt}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<DynamicQRCode value={this.state.psbt.toHex()} capacity={200} />
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
testID="PsbtTxScanButton"
|
||||
icon={{
|
||||
name: 'qrcode',
|
||||
type: 'font-awesome',
|
||||
color: BlueCurrentTheme.colors.buttonTextColor,
|
||||
}}
|
||||
onPress={this.openScanner}
|
||||
title={loc.send.psbt_tx_scan}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
icon={{
|
||||
name: 'file-import',
|
||||
type: 'material-community',
|
||||
color: BlueCurrentTheme.colors.buttonTextColor,
|
||||
}}
|
||||
onPress={this.openSignedTransaction}
|
||||
title={loc.send.psbt_tx_open}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
icon={{
|
||||
name: 'share-alternative',
|
||||
type: 'entypo',
|
||||
color: BlueCurrentTheme.colors.buttonTextColor,
|
||||
}}
|
||||
onPress={this.exportPSBT}
|
||||
title={loc.send.psbt_tx_export}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.copyToClipboard}>
|
||||
<BlueCopyToClipboardButton
|
||||
stringToCopy={typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64()}
|
||||
displayText={loc.send.psbt_clipboard}
|
||||
/>
|
||||
</View>
|
||||
</BlueCard>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PsbtWithHardwareWallet.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dangerouslyGetParent: PropTypes.func,
|
||||
}),
|
||||
route: PropTypes.shape({
|
||||
params: PropTypes.object,
|
||||
name: PropTypes.string,
|
||||
}),
|
||||
};
|
||||
|
||||
PsbtWithHardwareWallet.navigationOptions = () => ({
|
||||
...BlueNavigationStyle(null, false),
|
||||
title: loc.send.header,
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { View, StyleSheet, SafeAreaView } from 'react-native';
|
||||
import { Text } from 'react-native-elements';
|
||||
import { BlueButton, SafeBlueArea, BlueCard } from '../../BlueComponents';
|
||||
import { BlueButton, BlueCard } from '../../BlueComponents';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import loc from '../../loc';
|
||||
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||
@ -11,7 +11,7 @@ import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||
const Success = () => {
|
||||
const { colors } = useTheme();
|
||||
const { dangerouslyGetParent } = useNavigation();
|
||||
const { amount, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '' } = useRoute().params;
|
||||
const { amount = 0, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '' } = useRoute().params;
|
||||
const animationRef = useRef();
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
@ -39,14 +39,16 @@ const Success = () => {
|
||||
}, [colors]);
|
||||
|
||||
return (
|
||||
<SafeBlueArea style={[styles.root, stylesHook.root]}>
|
||||
<BlueCard style={styles.amout}>
|
||||
{amount > 0 && (
|
||||
<View style={styles.view}>
|
||||
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
|
||||
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
|
||||
</View>
|
||||
)}
|
||||
<SafeAreaView style={[styles.root, stylesHook.root]}>
|
||||
<BlueCard style={styles.amount}>
|
||||
<View style={styles.view}>
|
||||
{amount > 0 && (
|
||||
<>
|
||||
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
|
||||
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
{fee > 0 && (
|
||||
<Text style={styles.feeText}>
|
||||
{loc.send.create_fee}: {fee} {BitcoinUnit.BTC}
|
||||
@ -81,28 +83,26 @@ const Success = () => {
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<BlueCard>
|
||||
<View style={styles.buttonContainer}>
|
||||
<BlueButton onPress={pop} title={loc.send.success_done} />
|
||||
</BlueCard>
|
||||
</SafeBlueArea>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
Success.navigationOptions = {
|
||||
headerShown: false,
|
||||
gesturesEnabled: false,
|
||||
};
|
||||
|
||||
export default Success;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
paddingTop: 19,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
amout: {
|
||||
buttonContainer: {
|
||||
padding: 58,
|
||||
},
|
||||
amount: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
view: {
|
||||
flexDirection: 'row',
|
||||
|
Loading…
Reference in New Issue
Block a user