/* global alert */ import React, { Component } from 'react'; import { ActivityIndicator, View, TextInput, TouchableOpacity, Linking, Clipboard } from 'react-native'; import { BlueSpacing20, BlueReplaceFeeSuggestions, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueSpacing, BlueNavigationStyle, } from '../../BlueComponents'; import PropTypes from 'prop-types'; import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class'; import { Icon, Text } from 'react-native-elements'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; /** @type {AppStorage} */ let EV = require('../../events'); let BlueElectrum = require('../../BlueElectrum'); let loc = require('../../loc'); /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); export default class CPFP extends Component { static navigationOptions = () => ({ ...BlueNavigationStyle(null, false), title: 'Bump fee (CPFP)', }); constructor(props) { super(props); let txid; let wallet; if (props.navigation.state.params) txid = props.navigation.state.params.txid; if (props.navigation.state.params) wallet = props.navigation.state.params.wallet; this.state = { isLoading: true, stage: 1, txid, wallet, }; } broadcast() { this.setState({ isLoading: true }, async () => { try { await BlueElectrum.ping(); await BlueElectrum.waitTillConnected(); let result = await this.state.wallet.broadcastTx(this.state.txhex); if (result) { console.log('broadcast result = ', result); EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs this.setState({ stage: 3, isLoading: false }); this.onSuccessBroadcast(); } 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); } }); } onSuccessBroadcast() { BlueApp.tx_metadata[this.state.newTxid] = { memo: 'Child pays for parent (CPFP)' }; } async componentDidMount() { console.log('transactions/CPFP - componentDidMount'); this.setState({ isLoading: true, newFeeRate: '', nonReplaceable: false, }); await this.checkPossibilityOfCPFP(); } async checkPossibilityOfCPFP() { if (this.state.wallet.type !== HDSegwitBech32Wallet.type) { return this.setState({ nonReplaceable: true, isLoading: false }); } let tx = new HDSegwitBech32Transaction(null, this.state.txid, this.state.wallet); if ((await tx.isToUsTransaction()) && (await tx.getRemoteConfirmationsNum()) === 0) { let info = await tx.getInfo(); return this.setState({ nonReplaceable: false, feeRate: info.feeRate + 1, isLoading: false, tx }); // 1 sat makes a lot of difference, since sometimes because of rounding created tx's fee might be insufficient } else { return this.setState({ nonReplaceable: true, isLoading: false }); } } async createTransaction() { const newFeeRate = parseInt(this.state.newFeeRate); if (newFeeRate > this.state.feeRate) { /** @type {HDSegwitBech32Transaction} */ const tx = this.state.tx; this.setState({ isLoading: true }); try { let { tx: newTx } = await tx.createCPFPbumpFee(newFeeRate); this.setState({ stage: 2, txhex: newTx.toHex(), newTxid: newTx.getId() }); this.setState({ isLoading: false }); } catch (_) { this.setState({ isLoading: false }); alert('Failed: ' + _.message); } } } render() { if (this.state.isLoading) { return ( ); } if (this.state.stage === 3) { return this.renderStage3(); } if (this.state.stage === 2) { return this.renderStage2(); } if (this.state.nonReplaceable) { return ( This transaction is not bumpable ); } return this.renderStage1( 'We will create another transaction that spends your unconfirmed transaction. Total fee will be higher than original transaction\n' + 'fee, so it should be mined faster. This is called CPFP - Child Pays For Parent.', ); } renderStage2() { 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 this.broadcast()} title={loc.send.confirm.sendNow} /> ); } renderStage3() { return ( { this.props.navigation.popToTop(); }} title={loc.send.success.done} /> ); } renderStage1(text) { return ( {text} this.setState({ newFeeRate: fee })} transactionMinimum={this.state.feeRate} /> this.createTransaction()} title="Create" /> ); } } CPFP.propTypes = { navigation: PropTypes.shape({ popToTop: PropTypes.func, navigate: PropTypes.func, state: PropTypes.shape({ params: PropTypes.shape({ txid: PropTypes.string, wallet: PropTypes.object, }), }), }), };