/* global alert */ import React, { Component } from 'react'; import { ActivityIndicator, TouchableOpacity, ScrollView, View, Dimensions, Image, TextInput, Clipboard, Linking, Platform, PermissionsAndroid, } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Text } from 'react-native-elements'; import { BlueButton, BlueText, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueSpacing20, BlueCopyToClipboardButton, BlueBigCheckmark, } from '../../BlueComponents'; import PropTypes from 'prop-types'; import Share from 'react-native-share'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { RNCamera } from 'react-native-camera'; import RNFS from 'react-native-fs'; import DocumentPicker from 'react-native-document-picker'; let loc = require('../../loc'); let EV = require('../../events'); let BlueElectrum = require('../../BlueElectrum'); /** @type {AppStorage} */ const BlueApp = require('../../BlueApp'); const bitcoin = require('bitcoinjs-lib'); const { height, width } = Dimensions.get('window'); export default class PsbtWithHardwareWallet extends Component { static navigationOptions = () => ({ ...BlueNavigationStyle(null, false), title: loc.send.header, }); cameraRef = null; onBarCodeRead = ret => { if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview(); 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({ renderScanner: false, txhex: ret.data }); return; } this.setState({ renderScanner: false }, () => { try { let Tx = this.state.fromWallet.combinePsbt( this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), ret.data, ); this.setState({ txhex: Tx.toHex() }); } catch (Err) { alert(Err); } }); }; constructor(props) { super(props); this.state = { isLoading: false, renderScanner: false, qrCodeHeight: height > width ? width - 40 : width / 3, memo: props.navigation.getParam('memo'), psbt: props.navigation.getParam('psbt'), fromWallet: props.navigation.getParam('fromWallet'), isFirstPSBTAlreadyBase64: props.navigation.getParam('isFirstPSBTAlreadyBase64'), isSecondPSBTAlreadyBase64: false, deepLinkPSBT: undefined, txhex: props.navigation.getParam('txhex') || undefined, }; this.fileName = `${Date.now()}.psbt`; } static getDerivedStateFromProps(nextProps, prevState) { const deepLinkPSBT = nextProps.navigation.state.params.deepLinkPSBT; const txhex = nextProps.navigation.state.params.txhex; if (deepLinkPSBT) { try { let Tx = prevState.fromWallet.combinePsbt( prevState.isFirstPSBTAlreadyBase64 ? prevState.psbt : prevState.psbt.toBase64(), deepLinkPSBT, ); 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(); let result = await this.state.fromWallet.broadcastTx(this.state.txhex); if (result) { EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs this.setState({ success: true, isLoading: false }); if (this.state.memo) { let txDecoded = bitcoin.Transaction.fromHex(this.state.txhex); const txid = txDecoded.getId(); BlueApp.tx_metadata[txid] = { memo: this.state.memo }; } } else { ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); this.setState({ isLoading: false }); alert('Broadcast failed'); } } catch (error) { ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); this.setState({ isLoading: false }); alert(error.message); } }); }; _renderScanner() { return ( (this.cameraRef = ref)} style={{ flex: 1, justifyContent: 'space-between' }} onBarCodeRead={this.onBarCodeRead} barCodeTypes={[RNCamera.Constants.BarCodeType.qr]} /> this.setState({ renderScanner: false })} > ); } _renderSuccess() { return ( ); } _renderBroadcastHex() { return ( {loc.send.create.this_is_hex} Clipboard.setString(this.state.txhex)}> Copy and broadcast later Linking.openURL('https://coinb.in/?verify=' + this.state.txhex)}> Verify on coinb.in ); } exportPSBT = async () => { if (Platform.OS === 'ios') { const filePath = RNFS.TemporaryDirectoryPath + `/${this.fileName}`; await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()); Share.open({ url: 'file://' + filePath, }) .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: 'BlueWallet Storage Access Permission', message: 'BlueWallet needs your permission to access your storage to save this transaction.', buttonNeutral: 'Ask Me Later', buttonNegative: 'Cancel', buttonPositive: 'OK', }); if (granted === PermissionsAndroid.RESULTS.GRANTED) { console.log('Storage Permission: Granted'); const filePath = RNFS.ExternalCachesDirectoryPath + `/${this.fileName}`; await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()); alert(`This transaction has been saved in ${filePath}`); } 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.onBarCodeRead({ data: file })); } else { this.setState({ isSecondPSBTAlreadyBase64: false }); throw new Error(); } } catch (err) { if (!DocumentPicker.isCancel(err)) { alert('The selected file does not contain a signed transaction that can be imported.'); } } }; render() { if (this.state.isLoading) { return ( ); } if (this.state.success) return this._renderSuccess(); if (this.state.renderScanner) return this._renderScanner(); if (this.state.txhex) return this._renderBroadcastHex(); return ( This is partially signed bitcoin transaction (PSBT). Please finish signing it with your hardware wallet. this.setState({ renderScanner: true })} title={'Scan Signed Transaction'} /> ); } } PsbtWithHardwareWallet.propTypes = { navigation: PropTypes.shape({ goBack: PropTypes.func, getParam: PropTypes.func, navigate: PropTypes.func, dismiss: PropTypes.func, state: PropTypes.shape({ params: PropTypes.shape({ memo: PropTypes.string, fromWallet: PropTypes.shape({ fromAddress: PropTypes.string, fromSecret: PropTypes.string, }), }), }), }), };