diff --git a/App.js b/App.js index 71bd56928..3ab34acdd 100644 --- a/App.js +++ b/App.js @@ -14,7 +14,6 @@ import QuickActions from 'react-native-quick-actions'; import * as Sentry from '@sentry/react-native'; import OnAppLaunch from './class/on-app-launch'; import DeeplinkSchemaMatch from './class/deeplink-schema-match'; -import BitcoinBIP70TransactionDecode from './bip70/bip70'; const A = require('./analytics'); if (process.env.NODE_ENV !== 'development') { @@ -116,8 +115,7 @@ export default class App extends React.Component { return wallet.isInvoiceGeneratedByWallet(clipboard) || wallet.weOwnAddress(clipboard); } }); - const isBitcoinAddress = - DeeplinkSchemaMatch.isBitcoinAddress(clipboard) || BitcoinBIP70TransactionDecode.matchesPaymentURL(clipboard); + const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(clipboard); const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(clipboard); const isLNURL = DeeplinkSchemaMatch.isLnUrl(clipboard); const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard); diff --git a/bip70/bip70.js b/bip70/bip70.js deleted file mode 100644 index 7a2d770ed..000000000 --- a/bip70/bip70.js +++ /dev/null @@ -1,85 +0,0 @@ -import Frisbee from 'frisbee'; - -export class BitcoinBIP70Transaction { - constructor(amount, address, memo, fee, expires) { - this.amount = amount; - this.address = address; - this.memo = memo; - this.fee = fee; - this.expires = expires; - } -} - -export class BitcoinBIP70TransactionError { - constructor(errorMessage) { - this.errorMessage = errorMessage; - } -} - -export default class BitcoinBIP70TransactionDecode { - static decode(data) { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - try { - let url; - if (data.match(/bitcoin:\?r=https?:\/\/\S+/gi)) { - url = data.toString().split('bitcoin:?r=')[1]; - } else if (data.startsWith('https://bitpay.com/i/') || data.startsWith('https://www.bitpay.com/i/')) { - url = data.toString(); - } - const api = new Frisbee({ - baseURI: url, - headers: { - Accept: 'application/payment-request', - }, - }); - const response = await api.get(); - if (response && response.body) { - const parsedJSON = JSON.parse(response.body); - - // Check that the invoice has not expired - const expires = new Date(parsedJSON.expires).getTime(); - const now = new Date().getTime(); - if (now > expires) { - throw new BitcoinBIP70TransactionError('This invoice has expired.'); - } - // - - const decodedTransaction = new BitcoinBIP70Transaction( - parsedJSON.outputs[0].amount, - parsedJSON.outputs[0].address, - parsedJSON.memo, - parsedJSON.requiredFeeRate.toFixed(0), - parsedJSON.expires, - ); - console.log(decodedTransaction); - resolve(decodedTransaction); - } else { - console.log('Could not fetch transaction details: ' + response.err); - throw new BitcoinBIP70TransactionError('Unable to fetch transaction details. Please, make sure the provided link is valid.'); - } - } catch (err) { - console.warn(err); - reject(err); - } - }); - } - - static isExpired(transactionExpires) { - if (transactionExpires === null) { - return false; - } - const expires = new Date(transactionExpires).getTime(); - const now = new Date().getTime(); - return now > expires; - } - - static matchesPaymentURL(data) { - return ( - data !== null && - (data.match(/bitcoin:\?r=https?:\/\/\S+/gi) !== null || - data.startsWith('https://bitpay.com/i/') || - data.startsWith('https://www.bitpay.com/i/')) - ); - } -} diff --git a/class/deeplink-schema-match.js b/class/deeplink-schema-match.js index 381ba6de7..cf8c8e909 100644 --- a/class/deeplink-schema-match.js +++ b/class/deeplink-schema-match.js @@ -1,6 +1,5 @@ import { AppStorage, LightningCustodianWallet } from './'; import AsyncStorage from '@react-native-community/async-storage'; -import BitcoinBIP70TransactionDecode from '../bip70/bip70'; import RNFS from 'react-native-fs'; import url from 'url'; import { Chain } from '../models/bitcoinUnits'; @@ -76,7 +75,7 @@ class DeeplinkSchemaMatch { }, }, ]); - } else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url) || BitcoinBIP70TransactionDecode.matchesPaymentURL(event.url)) { + } else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url)) { completionHandler([ 'SendDetailsRoot', { diff --git a/package.json b/package.json index 6b9fa8c74..b475f48f5 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "e2e:release": "detox build -c android.emu.release; npm run e2e:release-no-build", "e2e:release-no-build": "detox test -c android.emu.release --record-videos all --take-screenshots all --headless", "e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || detox build -c android.emu.debug; detox test -c android.emu.debug", - "lint": "eslint *.js screen/**/*.js bip70/ blue_modules/crypto.js class/**/*.js models/ loc/ tests/**/*.js", + "lint": "eslint *.js screen/**/*.js blue_modules/crypto.js class/**/*.js models/ loc/ tests/**/*.js", "lint:fix": "npm run lint -- --fix", "lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep '\\.js' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0", "unit": "jest tests/unit/*" diff --git a/screen/send/details.js b/screen/send/details.js index d89c662ec..69ffc8d81 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -33,7 +33,6 @@ import Slider from '@react-native-community/slider'; import PropTypes from 'prop-types'; import Modal from 'react-native-modal'; import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees'; -import BitcoinBIP70TransactionDecode from '../../bip70/bip70'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { AppStorage, HDSegwitBech32Wallet, LightningCustodianWallet, WatchOnlyWallet } from '../../class'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; @@ -263,7 +262,6 @@ export default class SendDetails extends Component { fee: 1, feeSliderValue: 1, amountUnit: fromWallet.preferredBalanceUnit, // default for whole screen - bip70TransactionExpiration: null, renderWalletSelectionButtonHidden: false, }; } @@ -286,66 +284,51 @@ export default class SendDetails extends Component { */ processAddressData = data => { this.setState({ isLoading: true }, async () => { - if (BitcoinBIP70TransactionDecode.matchesPaymentURL(data)) { - const bip70 = await this.processBIP70Invoice(data); + const recipients = this.state.addresses; + const dataWithoutSchema = data.replace('bitcoin:', '').replace('BITCOIN:', ''); + if (this.state.fromWallet.isAddressValid(dataWithoutSchema)) { + recipients[[this.state.recipientsScrollIndex]].address = dataWithoutSchema; + const units = this.state.units; + units[this.state.recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC this.setState({ - addresses: [bip70.recipient], - memo: bip70.memo, - feeSliderValue: bip70.feeSliderValue, - fee: bip70.fee, + address: recipients, isLoading: false, amountUnit: BitcoinUnit.BTC, - bip70TransactionExpiration: bip70.bip70TransactionExpiration, + units, }); } else { - const recipients = this.state.addresses; - const dataWithoutSchema = data.replace('bitcoin:', '').replace('BITCOIN:', ''); - if (this.state.fromWallet.isAddressValid(dataWithoutSchema)) { - recipients[[this.state.recipientsScrollIndex]].address = dataWithoutSchema; + let address = ''; + let options; + try { + if (!data.toLowerCase().startsWith('bitcoin:')) { + data = `bitcoin:${data}`; + } + const decoded = DeeplinkSchemaMatch.bip21decode(data); + address = decoded.address; + options = decoded.options; + } catch (error) { + data = data.replace(/(amount)=([^&]+)/g, '').replace(/(amount)=([^&]+)&/g, ''); + const decoded = DeeplinkSchemaMatch.bip21decode(data); + decoded.options.amount = 0; + address = decoded.address; + options = decoded.options; + this.setState({ isLoading: false }); + } + console.log(options); + if (btcAddressRx.test(address) || address.indexOf('bc1') === 0 || address.indexOf('BC1') === 0) { const units = this.state.units; units[this.state.recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC + recipients[[this.state.recipientsScrollIndex]].address = address; + recipients[[this.state.recipientsScrollIndex]].amount = options.amount; this.setState({ - address: recipients, - bip70TransactionExpiration: null, + addresses: recipients, + memo: options.label || options.message, isLoading: false, amountUnit: BitcoinUnit.BTC, units, }); } else { - let address = ''; - let options; - try { - if (!data.toLowerCase().startsWith('bitcoin:')) { - data = `bitcoin:${data}`; - } - const decoded = DeeplinkSchemaMatch.bip21decode(data); - address = decoded.address; - options = decoded.options; - } catch (error) { - data = data.replace(/(amount)=([^&]+)/g, '').replace(/(amount)=([^&]+)&/g, ''); - const decoded = DeeplinkSchemaMatch.bip21decode(data); - decoded.options.amount = 0; - address = decoded.address; - options = decoded.options; - this.setState({ isLoading: false }); - } - console.log(options); - if (btcAddressRx.test(address) || address.indexOf('bc1') === 0 || address.indexOf('BC1') === 0) { - const units = this.state.units; - units[this.state.recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC - recipients[[this.state.recipientsScrollIndex]].address = address; - recipients[[this.state.recipientsScrollIndex]].amount = options.amount; - this.setState({ - addresses: recipients, - memo: options.label || options.message, - bip70TransactionExpiration: null, - isLoading: false, - amountUnit: BitcoinUnit.BTC, - units, - }); - } else { - this.setState({ isLoading: false }); - } + this.setState({ isLoading: false }); } } }); @@ -359,21 +342,14 @@ export default class SendDetails extends Component { let initialMemo = ''; if (this.props.route.params.uri) { const uri = this.props.route.params.uri; - if (BitcoinBIP70TransactionDecode.matchesPaymentURL(uri)) { - const { recipient, memo, fee, feeSliderValue } = await this.processBIP70Invoice(uri); - addresses.push(recipient); + try { + const { address, amount, memo } = this.decodeBitcoinUri(uri); + addresses.push(new BitcoinTransaction(address, amount, currency.btcToSatoshi(amount))); initialMemo = memo; - this.setState({ addresses, memo: initialMemo, fee, feeSliderValue, isLoading: false, amountUnit: BitcoinUnit.BTC }); - } else { - try { - const { address, amount, memo } = this.decodeBitcoinUri(uri); - addresses.push(new BitcoinTransaction(address, amount, currency.btcToSatoshi(amount))); - initialMemo = memo; - this.setState({ addresses, memo: initialMemo, isLoading: false, amountUnit: BitcoinUnit.BTC }); - } catch (error) { - console.log(error); - alert('Error: Unable to decode Bitcoin address'); - } + this.setState({ addresses, memo: initialMemo, isLoading: false, amountUnit: BitcoinUnit.BTC }); + } catch (error) { + console.log(error); + alert('Error: Unable to decode Bitcoin address'); } } else if (this.props.route.params.address) { addresses.push(new BitcoinTransaction(this.props.route.params.address)); @@ -406,17 +382,13 @@ export default class SendDetails extends Component { }); if (this.props.route.params.uri) { - if (BitcoinBIP70TransactionDecode.matchesPaymentURL(this.props.route.params.uri)) { - this.processBIP70Invoice(this.props.route.params.uri); - } else { - try { - const { address, amount, memo } = this.decodeBitcoinUri(this.props.route.params.uri); - this.setState({ address, amount, memo, isLoading: false }); - } catch (error) { - console.log(error); - this.setState({ isLoading: false }); - alert('Error: Unable to decode Bitcoin address'); - } + try { + const { address, amount, memo } = this.decodeBitcoinUri(this.props.route.params.uri); + this.setState({ address, amount, memo, isLoading: false }); + } catch (error) { + console.log(error); + this.setState({ isLoading: false }); + alert('Error: Unable to decode Bitcoin address'); } } } else { @@ -459,32 +431,6 @@ export default class SendDetails extends Component { return { address, amount, memo }; } - async processBIP70Invoice(text) { - try { - if (BitcoinBIP70TransactionDecode.matchesPaymentURL(text)) { - Keyboard.dismiss(); - return BitcoinBIP70TransactionDecode.decode(text) - .then(response => { - const recipient = new BitcoinTransaction(response.address, currency.satoshiToBTC(response.amount), response.amount); - return { - recipient, - memo: response.memo, - fee: response.fee, - feeSliderValue: response.fee, - bip70TransactionExpiration: response.expires, - }; - }) - .catch(error => { - alert(error.errorMessage); - throw error; - }); - } - } catch (error) { - return false; - } - throw new Error('BIP70: Unable to process.'); - } - async createTransaction() { Keyboard.dismiss(); this.setState({ isLoading: true }); @@ -504,9 +450,6 @@ export default class SendDetails extends Component { // first sanity check is that sending amount is not bigger than available balance error = loc.send.details.total_exceeds_balance; console.log('validation error'); - } else if (BitcoinBIP70TransactionDecode.isExpired(this.state.bip70TransactionExpiration)) { - error = 'Transaction has expired.'; - console.log('validation error'); } else if (transaction.address) { const address = transaction.address.trim().toLowerCase(); if (address.startsWith('lnb') || address.startsWith('lightning:lnb')) { @@ -986,23 +929,15 @@ export default class SendDetails extends Component { onChangeText={async text => { text = text.trim(); const transactions = this.state.addresses; - try { - const { recipient, memo, fee, feeSliderValue } = await this.processBIP70Invoice(text); - transactions[index].address = recipient.address; - transactions[index].amount = recipient.amount; - this.setState({ addresses: transactions, memo: memo, fee, feeSliderValue, isLoading: false }); - } catch (_e) { - const { address, amount, memo } = this.decodeBitcoinUri(text); - item.address = address || text; - item.amount = amount || item.amount; - transactions[index] = item; - this.setState({ - addresses: transactions, - memo: memo || this.state.memo, - isLoading: false, - bip70TransactionExpiration: null, - }); - } + const { address, amount, memo } = this.decodeBitcoinUri(text); + item.address = address || text; + item.amount = amount || item.amount; + transactions[index] = item; + this.setState({ + addresses: transactions, + memo: memo || this.state.memo, + isLoading: false, + }); }} onBarScanned={this.processAddressData} address={item.address}