From 1680a6768bde21c9aaf82941d232a6ced381ed90 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Tue, 31 Dec 2019 21:31:04 -0600 Subject: [PATCH 1/7] # This is a combination of 3 commits. # This is the 1st commit message: ADD: File picker for import # This is the commit message #2: ADD: File picker for import # This is the commit message #3: ADD: Tests for DeeplinkSchemaMatch --- App.js | 252 +------ BlueComponents.js | 13 +- MainBottomTabs.js | 6 + android/app/src/main/AndroidManifest.xml | 7 +- class/abstract-hd-electrum-wallet.js | 25 +- class/app-storage.js | 1 - class/deeplinkSchemaMatch.js | 221 +++++++ class/walletImport.js | 20 +- class/watch-only-wallet.js | 3 +- ios/BlueWallet.xcodeproj/project.pbxproj | 4 +- ios/BlueWallet/BlueWalletRelease.entitlements | 18 + ios/BlueWallet/Info.plist | 73 ++- ios/Podfile.lock | 14 +- package-lock.json | 617 ++++++++++-------- package.json | 2 + screen/lnd/scanLndInvoice.js | 13 +- screen/send/details.js | 45 +- screen/send/psbtWithHardwareWallet.js | 88 ++- screen/send/scanQrAddress.js | 157 +++-- screen/wallets/import.js | 10 +- screen/wallets/list.js | 210 ++++-- screen/wallets/transactions.js | 52 +- tests/integration/deepLinkSchemaMatch.test.js | 50 ++ 23 files changed, 1217 insertions(+), 684 deletions(-) create mode 100644 class/deeplinkSchemaMatch.js create mode 100644 ios/BlueWallet/BlueWalletRelease.entitlements create mode 100644 tests/integration/deepLinkSchemaMatch.test.js diff --git a/App.js b/App.js index 4530804c..7777220b 100644 --- a/App.js +++ b/App.js @@ -1,18 +1,17 @@ import React from 'react'; import { Linking, DeviceEventEmitter, AppState, Clipboard, StyleSheet, KeyboardAvoidingView, Platform, View } from 'react-native'; -import AsyncStorage from '@react-native-community/async-storage'; import Modal from 'react-native-modal'; import { NavigationActions } from 'react-navigation'; import MainBottomTabs from './MainBottomTabs'; import NavigationService from './NavigationService'; import { BlueTextCentered, BlueButton } from './BlueComponents'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import url from 'url'; -import { AppStorage, LightningCustodianWallet } from './class'; import { Chain } from './models/bitcoinUnits'; import QuickActions from 'react-native-quick-actions'; import * as Sentry from '@sentry/react-native'; import OnAppLaunch from './class/onAppLaunch'; +import DeeplinkSchemaMatch from './class/deeplinkSchemaMatch'; +import BitcoinBIP70TransactionDecode from './bip70/bip70'; const A = require('./analytics'); if (process.env.NODE_ENV !== 'development') { @@ -21,11 +20,9 @@ if (process.env.NODE_ENV !== 'development') { }); } -const bitcoin = require('bitcoinjs-lib'); const bitcoinModalString = 'Bitcoin address'; const lightningModalString = 'Lightning Invoice'; const loc = require('./loc'); -/** @type {AppStorage} */ const BlueApp = require('./BlueApp'); export default class App extends React.Component { @@ -62,7 +59,7 @@ export default class App extends React.Component { } else { const url = await Linking.getInitialURL(); if (url) { - if (this.hasSchema(url)) { + if (DeeplinkSchemaMatch.hasSchema(url)) { this.handleOpenURL({ url }); } } else { @@ -116,12 +113,23 @@ export default class App extends React.Component { const isAddressFromStoredWallet = BlueApp.getWallets().some(wallet => wallet.chain === Chain.ONCHAIN ? wallet.weOwnAddress(clipboard) : wallet.isInvoiceGeneratedByWallet(clipboard), ); + const isBitcoinAddress = + DeeplinkSchemaMatch.isBitcoinAddress(clipboard) || BitcoinBIP70TransactionDecode.matchesPaymentURL(clipboard); + const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(clipboard); + const isLNURL = DeeplinkSchemaMatch.isLnUrl(clipboard); + const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard); if ( - (!isAddressFromStoredWallet && - this.state.clipboardContent !== clipboard && - (this.isBitcoinAddress(clipboard) || this.isLightningInvoice(clipboard) || this.isLnUrl(clipboard))) || - this.isBothBitcoinAndLightning(clipboard) + !isAddressFromStoredWallet && + this.state.clipboardContent !== clipboard && + (isBitcoinAddress || isLightningInvoice || isLNURL || isBothBitcoinAndLightning) ) { + if (isBitcoinAddress) { + this.setState({ clipboardContentModalAddressType: bitcoinModalString }); + } else if (isLightningInvoice || isLNURL) { + this.setState({ clipboardContentModalAddressType: lightningModalString }); + } else if (isBothBitcoinAndLightning) { + this.setState({ clipboardContentModalAddressType: bitcoinModalString }); + } this.setState({ isClipboardContentModalVisible: true }); } this.setState({ clipboardContent: clipboard }); @@ -130,94 +138,6 @@ export default class App extends React.Component { } }; - hasSchema(schemaString) { - if (typeof schemaString !== 'string' || schemaString.length <= 0) return false; - const lowercaseString = schemaString.trim().toLowerCase(); - return ( - lowercaseString.startsWith('bitcoin:') || - lowercaseString.startsWith('lightning:') || - lowercaseString.startsWith('blue:') || - lowercaseString.startsWith('bluewallet:') || - lowercaseString.startsWith('lapp:') - ); - } - - isBitcoinAddress(address) { - address = address - .replace('bitcoin:', '') - .replace('bitcoin=', '') - .split('?')[0]; - let isValidBitcoinAddress = false; - try { - bitcoin.address.toOutputScript(address); - isValidBitcoinAddress = true; - this.setState({ clipboardContentModalAddressType: bitcoinModalString }); - } catch (err) { - isValidBitcoinAddress = false; - } - return isValidBitcoinAddress; - } - - isLightningInvoice(invoice) { - let isValidLightningInvoice = false; - if (invoice.toLowerCase().startsWith('lightning:lnb') || invoice.toLowerCase().startsWith('lnb')) { - this.setState({ clipboardContentModalAddressType: lightningModalString }); - isValidLightningInvoice = true; - } - return isValidLightningInvoice; - } - - isLnUrl(text) { - if (text.toLowerCase().startsWith('lightning:lnurl') || text.toLowerCase().startsWith('lnurl')) { - return true; - } - return false; - } - - isBothBitcoinAndLightning(url) { - if (url.includes('lightning') && url.includes('bitcoin')) { - const txInfo = url.split(/(bitcoin:|lightning:|lightning=|bitcoin=)+/); - let bitcoin; - let lndInvoice; - for (const [index, value] of txInfo.entries()) { - try { - // Inside try-catch. We dont wan't to crash in case of an out-of-bounds error. - if (value.startsWith('bitcoin')) { - bitcoin = `bitcoin:${txInfo[index + 1]}`; - if (!this.isBitcoinAddress(bitcoin)) { - bitcoin = false; - break; - } - } else if (value.startsWith('lightning')) { - lndInvoice = `lightning:${txInfo[index + 1]}`; - if (!this.isLightningInvoice(lndInvoice)) { - lndInvoice = false; - break; - } - } - } catch (e) { - console.log(e); - } - if (bitcoin && lndInvoice) break; - } - if (bitcoin && lndInvoice) { - this.setState({ - clipboardContent: { bitcoin, lndInvoice }, - }); - return { bitcoin, lndInvoice }; - } else { - return undefined; - } - } - return undefined; - } - - isSafelloRedirect(event) { - let urlObject = url.parse(event.url, true) // eslint-disable-line - - return !!urlObject.query['safello-state-token']; - } - isBothBitcoinAndLightningWalletSelect = wallet => { const clipboardContent = this.state.clipboardContent; if (wallet.chain === Chain.ONCHAIN) { @@ -246,141 +166,7 @@ export default class App extends React.Component { }; handleOpenURL = event => { - if (event.url === null) { - return; - } - if (typeof event.url !== 'string') { - return; - } - let isBothBitcoinAndLightning; - try { - isBothBitcoinAndLightning = this.isBothBitcoinAndLightning(event.url); - } catch (e) { - console.log(e); - } - if (isBothBitcoinAndLightning) { - this.navigator && - this.navigator.dispatch( - NavigationActions.navigate({ - routeName: 'HandleOffchainAndOnChain', - params: { - onWalletSelect: this.isBothBitcoinAndLightningWalletSelect, - }, - }), - ); - } else if (this.isBitcoinAddress(event.url)) { - this.navigator && - this.navigator.dispatch( - NavigationActions.navigate({ - routeName: 'SendDetails', - params: { - uri: event.url, - }, - }), - ); - } else if (this.isLightningInvoice(event.url)) { - this.navigator && - this.navigator.dispatch( - NavigationActions.navigate({ - routeName: 'ScanLndInvoice', - params: { - uri: event.url, - }, - }), - ); - } else if (this.isLnUrl(event.url)) { - this.navigator && - this.navigator.dispatch( - NavigationActions.navigate({ - routeName: 'LNDCreateInvoice', - params: { - uri: event.url, - }, - }), - ); - } else if (this.isSafelloRedirect(event)) { - let urlObject = url.parse(event.url, true) // eslint-disable-line - - const safelloStateToken = urlObject.query['safello-state-token']; - - this.navigator && - this.navigator.dispatch( - NavigationActions.navigate({ - routeName: 'BuyBitcoin', - params: { - uri: event.url, - safelloStateToken, - }, - }), - ); - } else { - let urlObject = url.parse(event.url, true); // eslint-disable-line - console.log('parsed', urlObject); - (async () => { - if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') { - switch (urlObject.host) { - case 'openlappbrowser': - console.log('opening LAPP', urlObject.query.url); - // searching for LN wallet: - let haveLnWallet = false; - for (let w of BlueApp.getWallets()) { - if (w.type === LightningCustodianWallet.type) { - haveLnWallet = true; - } - } - - if (!haveLnWallet) { - // need to create one - let w = new LightningCustodianWallet(); - w.setLabel(this.state.label || w.typeReadable); - - try { - let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); - if (lndhub) { - w.setBaseURI(lndhub); - w.init(); - } - await w.createAccount(); - await w.authorize(); - } catch (Err) { - // giving up, not doing anything - return; - } - BlueApp.wallets.push(w); - await BlueApp.saveToDisk(); - } - - // now, opening lapp browser and navigating it to URL. - // looking for a LN wallet: - let lnWallet; - for (let w of BlueApp.getWallets()) { - if (w.type === LightningCustodianWallet.type) { - lnWallet = w; - break; - } - } - - if (!lnWallet) { - // something went wrong - return; - } - - this.navigator && - this.navigator.dispatch( - NavigationActions.navigate({ - routeName: 'LappBrowser', - params: { - fromSecret: lnWallet.getSecret(), - fromWallet: lnWallet, - url: urlObject.query.url, - }, - }), - ); - break; - } - } - })(); - } + DeeplinkSchemaMatch.navigationRouteFor(event, value => this.navigator && this.navigator.dispatch(NavigationActions.navigate(value))); }; renderClipboardContentModal = () => { diff --git a/BlueComponents.js b/BlueComponents.js index 8cb31b11..33b02e37 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -1381,7 +1381,7 @@ export class NewWalletPanel extends Component { style={{ padding: 15, borderRadius: 10, - minHeight: 164, + minHeight: Platform.OS === 'ios' ? 164 : 181, justifyContent: 'center', alignItems: 'center', }} @@ -1837,7 +1837,9 @@ export class WalletsCarousel extends Component { { if (WalletsCarousel.handleClick) { + this.onPressedOut(); WalletsCarousel.handleClick(index); + this.onPressedOut(); } }} /> @@ -1857,7 +1859,9 @@ export class WalletsCarousel extends Component { onPressOut={item.getIsFailure() ? this.onPressedOut : null} onPress={() => { if (item.getIsFailure() && WalletsCarousel.handleClick) { + this.onPressedOut(); WalletsCarousel.handleClick(index); + this.onPressedOut(); } }} > @@ -1925,7 +1929,9 @@ export class WalletsCarousel extends Component { onLongPress={WalletsCarousel.handleLongPress} onPress={() => { if (WalletsCarousel.handleClick) { + this.onPressedOut(); WalletsCarousel.handleClick(index); + this.onPressedOut(); } }} > @@ -2037,7 +2043,8 @@ export class BlueAddressInput extends Component { static propTypes = { isLoading: PropTypes.bool, onChangeText: PropTypes.func, - onBarScanned: PropTypes.func, + onBarScanned: PropTypes.func.isRequired, + launchedBy: PropTypes.string.isRequired, address: PropTypes.string, placeholder: PropTypes.string, }; @@ -2081,7 +2088,7 @@ export class BlueAddressInput extends Component { { - NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned }); + NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned, launchedBy: this.props.launchedBy }); Keyboard.dismiss(); }} style={{ diff --git a/MainBottomTabs.js b/MainBottomTabs.js index aadd42e6..4d787de0 100644 --- a/MainBottomTabs.js +++ b/MainBottomTabs.js @@ -60,6 +60,9 @@ const WalletsStackNavigator = createStackNavigator( Wallets: { screen: WalletsList, path: 'wallets', + navigationOptions: { + header: null, + }, }, WalletTransactions: { screen: WalletTransactions, @@ -157,6 +160,7 @@ const WalletsStackNavigator = createStackNavigator( const CreateTransactionStackNavigator = createStackNavigator({ SendDetails: { + routeName: 'SendDetails', screen: sendDetails, }, Confirm: { @@ -206,6 +210,7 @@ const CreateWalletStackNavigator = createStackNavigator({ }, ImportWallet: { screen: ImportWallet, + routeName: 'ImportWallet', }, PleaseBackup: { screen: PleaseBackup, @@ -290,6 +295,7 @@ const MainBottomTabs = createStackNavigator( }, // SendDetails: { + routeName: 'SendDetails', screen: CreateTransactionStackNavigator, navigationOptions: { header: null, diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 04c6060e..62a2e2cb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,7 +4,8 @@ - + + + + + + diff --git a/class/abstract-hd-electrum-wallet.js b/class/abstract-hd-electrum-wallet.js index 7e1bca83..32f1d851 100644 --- a/class/abstract-hd-electrum-wallet.js +++ b/class/abstract-hd-electrum-wallet.js @@ -8,6 +8,7 @@ const BlueElectrum = require('../BlueElectrum'); const HDNode = require('bip32'); const coinSelectAccumulative = require('coinselect/accumulative'); const coinSelectSplit = require('coinselect/split'); +const reverse = require('buffer-reverse'); const { RNRandomBytes } = NativeModules; @@ -719,7 +720,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case * @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false) { + createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { if (!changeAddress) throw new Error('No change address provided'); sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence; @@ -756,7 +757,13 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input'); } let pubkey = this._getPubkeyByAddress(input.address); - let masterFingerprint = Buffer.from([0x00, 0x00, 0x00, 0x00]); + let masterFingerprintBuffer; + if (masterFingerprint) { + const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex'); + masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); + } else { + masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); + } // this is not correct fingerprint, as we dont know real fingerprint - we got zpub with 84/0, but fingerpting // should be from root. basically, fingerprint should be provided from outside by user when importing zpub let path = this._getDerivationPathByAddress(input.address); @@ -767,7 +774,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { sequence, bip32Derivation: [ { - masterFingerprint, + masterFingerprint: masterFingerprintBuffer, path, pubkey, }, @@ -789,7 +796,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let path = this._getDerivationPathByAddress(output.address); let pubkey = this._getPubkeyByAddress(output.address); - let masterFingerprint = Buffer.from([0x00, 0x00, 0x00, 0x00]); + let masterFingerprintBuffer; + + if (masterFingerprint) { + const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex') + masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); + } else { + masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); + } + // this is not correct fingerprint, as we dont know realfingerprint - we got zpub with 84/0, but fingerpting // should be from root. basically, fingerprint should be provided from outside by user when importing zpub @@ -801,7 +816,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { if (change) { outputData['bip32Derivation'] = [ { - masterFingerprint, + masterFingerprint: masterFingerprintBuffer, path, pubkey, }, diff --git a/class/app-storage.js b/class/app-storage.js index df9f9aac..333980ee 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -305,7 +305,6 @@ export class AppStorage { if (key.prepareForSerialization) key.prepareForSerialization(); walletsToSave.push(JSON.stringify({ ...key, type: key.type })); } - let data = { wallets: walletsToSave, tx_metadata: this.tx_metadata, diff --git a/class/deeplinkSchemaMatch.js b/class/deeplinkSchemaMatch.js new file mode 100644 index 00000000..194b0c44 --- /dev/null +++ b/class/deeplinkSchemaMatch.js @@ -0,0 +1,221 @@ +import { AppStorage, LightningCustodianWallet } from './'; +import AsyncStorage from '@react-native-community/async-storage'; +import BitcoinBIP70TransactionDecode from '../bip70/bip70'; +const bitcoin = require('bitcoinjs-lib'); +const BlueApp = require('../BlueApp'); +class DeeplinkSchemaMatch { + static hasSchema(schemaString) { + if (typeof schemaString !== 'string' || schemaString.length <= 0) return false; + const lowercaseString = schemaString.trim().toLowerCase(); + return ( + lowercaseString.startsWith('bitcoin:') || + lowercaseString.startsWith('lightning:') || + lowercaseString.startsWith('blue:') || + lowercaseString.startsWith('bluewallet:') || + lowercaseString.startsWith('lapp:') + ); + } + + /** + * Examines the content of the event parameter. + * If the content is recognizable, create a dictionary with the respective + * navigation dictionary required by react-navigation + * @param {Object} event + * @param {void} completionHandler + */ + static navigationRouteFor(event, completionHandler) { + if (event.url === null) { + return; + } + if (typeof event.url !== 'string') { + return; + } + let isBothBitcoinAndLightning; + try { + isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url); + } catch (e) { + console.log(e); + } + if (isBothBitcoinAndLightning) { + completionHandler({ + routeName: 'HandleOffchainAndOnChain', + params: { + onWalletSelect: this.isBothBitcoinAndLightningWalletSelect, + }, + }); + } else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url) || BitcoinBIP70TransactionDecode.matchesPaymentURL(event.url)) { + completionHandler({ + routeName: 'SendDetails', + params: { + uri: event.url, + }, + }); + } else if (DeeplinkSchemaMatch.isLightningInvoice(event.url)) { + completionHandler({ + routeName: 'ScanLndInvoice', + params: { + uri: event.url, + }, + }); + } else if (DeeplinkSchemaMatch.isLnUrl(event.url)) { + completionHandler({ + routeName: 'LNDCreateInvoice', + params: { + uri: event.url, + }, + }); + } else if (DeeplinkSchemaMatch.isSafelloRedirect(event)) { + let urlObject = url.parse(event.url, true) // eslint-disable-line + + const safelloStateToken = urlObject.query['safello-state-token']; + + completionHandler({ + routeName: 'BuyBitcoin', + params: { + uri: event.url, + safelloStateToken, + }, + }); + } else { + let urlObject = url.parse(event.url, true); // eslint-disable-line + console.log('parsed', urlObject); + (async () => { + if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') { + switch (urlObject.host) { + case 'openlappbrowser': + console.log('opening LAPP', urlObject.query.url); + // searching for LN wallet: + let haveLnWallet = false; + for (let w of BlueApp.getWallets()) { + if (w.type === LightningCustodianWallet.type) { + haveLnWallet = true; + } + } + + if (!haveLnWallet) { + // need to create one + let w = new LightningCustodianWallet(); + w.setLabel(this.state.label || w.typeReadable); + + try { + let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); + if (lndhub) { + w.setBaseURI(lndhub); + w.init(); + } + await w.createAccount(); + await w.authorize(); + } catch (Err) { + // giving up, not doing anything + return; + } + BlueApp.wallets.push(w); + await BlueApp.saveToDisk(); + } + + // now, opening lapp browser and navigating it to URL. + // looking for a LN wallet: + let lnWallet; + for (let w of BlueApp.getWallets()) { + if (w.type === LightningCustodianWallet.type) { + lnWallet = w; + break; + } + } + + if (!lnWallet) { + // something went wrong + return; + } + + this.navigator && + this.navigator.dispatch( + completionHandler({ + routeName: 'LappBrowser', + params: { + fromSecret: lnWallet.getSecret(), + fromWallet: lnWallet, + url: urlObject.query.url, + }, + }), + ); + break; + } + } + })(); + } + } + + static isBitcoinAddress(address) { + address = address + .replace('bitcoin:', '') + .replace('bitcoin=', '') + .split('?')[0]; + let isValidBitcoinAddress = false; + try { + bitcoin.address.toOutputScript(address); + isValidBitcoinAddress = true; + } catch (err) { + isValidBitcoinAddress = false; + } + return isValidBitcoinAddress; + } + + static isLightningInvoice(invoice) { + let isValidLightningInvoice = false; + if (invoice.toLowerCase().startsWith('lightning:lnb') || invoice.toLowerCase().startsWith('lnb')) { + isValidLightningInvoice = true; + } + return isValidLightningInvoice; + } + + static isLnUrl(text) { + if (text.toLowerCase().startsWith('lightning:lnurl') || text.toLowerCase().startsWith('lnurl')) { + return true; + } + return false; + } + + static isSafelloRedirect(event) { + let urlObject = url.parse(event.url, true) // eslint-disable-line + + return !!urlObject.query['safello-state-token']; + } + + static isBothBitcoinAndLightning(url) { + if (url.includes('lightning') && url.includes('bitcoin')) { + const txInfo = url.split(/(bitcoin:|lightning:|lightning=|bitcoin=)+/); + let bitcoin; + let lndInvoice; + for (const [index, value] of txInfo.entries()) { + try { + // Inside try-catch. We dont wan't to crash in case of an out-of-bounds error. + if (value.startsWith('bitcoin')) { + bitcoin = `bitcoin:${txInfo[index + 1]}`; + if (!DeeplinkSchemaMatch.isBitcoinAddress(bitcoin)) { + bitcoin = false; + break; + } + } else if (value.startsWith('lightning')) { + lndInvoice = `lightning:${txInfo[index + 1]}`; + if (!this.isLightningInvoice(lndInvoice)) { + lndInvoice = false; + break; + } + } + } catch (e) { + console.log(e); + } + if (bitcoin && lndInvoice) break; + } + if (bitcoin && lndInvoice) { + return { bitcoin, lndInvoice }; + } else { + return undefined; + } + } + return undefined; + } +} + +export default DeeplinkSchemaMatch; diff --git a/class/walletImport.js b/class/walletImport.js index 0279404f..18f2f207 100644 --- a/class/walletImport.js +++ b/class/walletImport.js @@ -18,24 +18,34 @@ const BlueApp = require('../BlueApp'); const loc = require('../loc'); export default class WalletImport { - static async _saveWallet(w) { + static async _saveWallet(w, additionalProperties) { try { const wallet = BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret && wallet.type !== PlaceholderWallet.type); if (wallet) { alert('This wallet has been previously imported.'); WalletImport.removePlaceholderWallet(); } else { - alert(loc.wallets.import.success); ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable); w.setUserHasSavedExport(true); + if (additionalProperties) { + for (const [key, value] of Object.entries(additionalProperties)) { + w[key] = value; + } + } WalletImport.removePlaceholderWallet(); BlueApp.wallets.push(w); await BlueApp.saveToDisk(); A(A.ENUM.CREATED_WALLET); + alert(loc.wallets.import.success); } EV(EV.enum.WALLETS_COUNT_CHANGED); - } catch (_e) {} + } catch (e) { + alert(e); + console.log(e); + WalletImport.removePlaceholderWallet(); + EV(EV.enum.WALLETS_COUNT_CHANGED); + } } static removePlaceholderWallet() { @@ -58,7 +68,7 @@ export default class WalletImport { return BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type); } - static async processImportText(importText) { + static async processImportText(importText, additionalProperties) { if (WalletImport.isCurrentlyImportingWallet()) { return; } @@ -209,7 +219,7 @@ export default class WalletImport { if (watchOnly.valid()) { await watchOnly.fetchTransactions(); await watchOnly.fetchBalance(); - return WalletImport._saveWallet(watchOnly); + return WalletImport._saveWallet(watchOnly, additionalProperties); } // nope? diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index dedd613c..96c56c4e 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -11,6 +11,7 @@ export class WatchOnlyWallet extends LegacyWallet { constructor() { super(); this.use_with_hardware_wallet = false; + this.masterFingerprint = false; } allowSend() { @@ -146,7 +147,7 @@ export class WatchOnlyWallet extends LegacyWallet { */ createTransaction(utxos, targets, feeRate, changeAddress, sequence) { if (this._hdWalletInstance instanceof HDSegwitBech32Wallet) { - return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true); + return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.masterFingerprint); } else { throw new Error('Not a zpub watch-only wallet, cant create PSBT (or just not initialized)'); } diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 902a31f6..93f3854b 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -164,6 +164,7 @@ 3271B0BA236E329400DA766F /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = ""; }; 32B5A3292334450100F8D608 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = ""; }; + 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWalletRelease.entitlements; path = BlueWallet/BlueWalletRelease.entitlements; sourceTree = ""; }; 32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWalletWatch Extension.entitlements"; sourceTree = ""; }; 32F0A2502310B0910095C559 /* BlueWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWallet.entitlements; path = BlueWallet/BlueWallet.entitlements; sourceTree = ""; }; 32F0A2992311DBB20095C559 /* ComplicationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = ""; }; @@ -332,6 +333,7 @@ 13B07FAE1A68108700A75B9A /* BlueWallet */ = { isa = PBXGroup; children = ( + 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */, 32F0A2502310B0910095C559 /* BlueWallet.entitlements */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, @@ -1266,7 +1268,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements; + CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWalletRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/ios/BlueWallet/BlueWalletRelease.entitlements b/ios/BlueWallet/BlueWalletRelease.entitlements new file mode 100644 index 00000000..16a6e901 --- /dev/null +++ b/ios/BlueWallet/BlueWalletRelease.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.developer.icloud-container-identifiers + + com.apple.developer.icloud-services + + CloudDocuments + + com.apple.developer.ubiquity-container-identifiers + + com.apple.security.application-groups + + group.io.bluewallet.bluewallet + + + diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index e1375e79..d545ad13 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -2,12 +2,27 @@ - UIUserInterfaceStyle - Light CFBundleDevelopmentRegion en CFBundleDisplayName BlueWallet + CFBundleDocumentTypes + + + CFBundleTypeIconFiles + + CFBundleTypeName + PSBT + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSItemContentTypes + + io.bluewallet.psbt + + + CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -58,18 +73,18 @@ NSAppleMusicUsageDescription This alert should not show up as we do not require this data - NSFaceIDUsageDescription - In order to confirm your identity, we need your permission to use FaceID. NSBluetoothPeripheralUsageDescription This alert should not show up as we do not require this data NSCalendarsUsageDescription This alert should not show up as we do not require this data NSCameraUsageDescription In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. - NSLocationWhenInUseUsageDescription - This alert should not show up as we do not require this data + NSFaceIDUsageDescription + In order to confirm your identity, we need your permission to use FaceID. NSLocationAlwaysUsageDescription This alert should not show up as we do not require this data + NSLocationWhenInUseUsageDescription + This alert should not show up as we do not require this data NSMicrophoneUsageDescription This alert should not show up as we do not require this data NSMotionUsageDescription @@ -116,7 +131,53 @@ UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown + UIUserInterfaceStyle + Light UIViewControllerBasedStatusBarAppearance + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.data + + UTTypeDescription + PSBT + UTTypeIconFiles + + UTTypeIdentifier + io.bluewallet.psbt + UTTypeTagSpecification + + public.filename-extension + + psbt + + + + + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.data + + UTTypeDescription + PSBT + UTTypeIconFiles + + UTTypeIdentifier + io.bluewallet.psbt + UTTypeTagSpecification + + public.filename-extension + + psbt + + + + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6fd4e975..6f06cc0e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -76,6 +76,8 @@ PODS: - React - react-native-camera/RN (3.4.0): - React + - react-native-document-picker (3.2.0): + - React - react-native-haptic-feedback (1.7.1): - React - react-native-image-picker (1.1.0): @@ -142,9 +144,9 @@ PODS: - React - RNWatch (0.4.1): - React - - Sentry (4.4.2): - - Sentry/Core (= 4.4.2) - - Sentry/Core (4.4.2) + - Sentry (4.4.3): + - Sentry/Core (= 4.4.3) + - Sentry/Core (4.4.3) - swift_qrcodejs (1.1.2) - TcpSockets (3.3.2): - React @@ -169,6 +171,7 @@ DEPENDENCIES: - react-native-biometrics (from `../node_modules/react-native-biometrics`) - "react-native-blur (from `../node_modules/@react-native-community/blur`)" - react-native-camera (from `../node_modules/react-native-camera`) + - react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-haptic-feedback (from `../node_modules/react-native-haptic-feedback`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-randombytes (from `../node_modules/react-native-randombytes`) @@ -243,6 +246,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/blur" react-native-camera: :path: "../node_modules/react-native-camera" + react-native-document-picker: + :path: "../node_modules/react-native-document-picker" react-native-haptic-feedback: :path: "../node_modules/react-native-haptic-feedback" react-native-image-picker: @@ -331,6 +336,7 @@ SPEC CHECKSUMS: react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5 react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c react-native-camera: 203091b4bf99d48b788a0682ad573e8718724893 + react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154 react-native-haptic-feedback: 22c9dc85fd8059f83bf9edd9212ac4bd4ae6074d react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8 @@ -362,7 +368,7 @@ SPEC CHECKSUMS: RNSVG: 0eb087cfb5d7937be93c45b163b26352a647e681 RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4 RNWatch: a14e378448e187cc12f307f61d41fe8a65400e86 - Sentry: bba998b0fb157fdd6596aa73290a9d67ae47be79 + Sentry: 14bdd673870e8cf64932b149fad5bbbf39a9b390 swift_qrcodejs: 4d024fc98b0778b804ec6a5c810880fd092aec9d TcpSockets: 8d839b9b14f6f344d98e4642ded13ab3112b462d ToolTipMenu: bdcaa0e888bcf44778a67fe34639b094352e904e diff --git a/package-lock.json b/package-lock.json index 1fc24ea3..66ae2a25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,14 +18,14 @@ } }, "@babel/core": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.4.tgz", - "integrity": "sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.7.tgz", + "integrity": "sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==", "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", + "@babel/generator": "^7.7.7", "@babel/helpers": "^7.7.4", - "@babel/parser": "^7.7.4", + "@babel/parser": "^7.7.7", "@babel/template": "^7.7.4", "@babel/traverse": "^7.7.4", "@babel/types": "^7.7.4", @@ -39,9 +39,9 @@ } }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", + "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", "requires": { "@babel/types": "^7.7.4", "jsesc": "^2.5.1", @@ -169,9 +169,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.4.tgz", - "integrity": "sha512-ehGBu4mXrhs0FxAqN8tWkzF8GSIGAiEumu4ONZ/hD9M88uHcD+Yu2ttKfOCgwzoesJOJrtQh7trI5YPbRtMmnA==", + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz", + "integrity": "sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==", "requires": { "@babel/helper-module-imports": "^7.7.4", "@babel/helper-simple-access": "^7.7.4", @@ -274,9 +274,9 @@ } }, "@babel/parser": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz", - "integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==" + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.7.tgz", + "integrity": "sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==" }, "@babel/plugin-external-helpers": { "version": "7.7.4", @@ -342,9 +342,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz", - "integrity": "sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.7.tgz", + "integrity": "sha512-3qp9I8lelgzNedI3hrhkvhaEYree6+WHnyA/q4Dza9z7iEIs1eyhWyJnetk3jJ69RT0AT4G0UhEGwyGFJ7GUuQ==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-object-rest-spread": "^7.7.4" @@ -360,18 +360,18 @@ } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.7.4.tgz", - "integrity": "sha512-JmgaS+ygAWDR/STPe3/7y0lNlHgS+19qZ9aC06nYLwQ/XB7c0q5Xs+ksFU3EDnp9EiEsO0dnRAOKeyLHTZuW3A==", + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.7.5.tgz", + "integrity": "sha512-sOwFqT8JSchtJeDD+CjmWCaiFoLxY4Ps7NjvwHC/U7l4e9i5pTRNt8nDMIFSOUL+ncFbYSwruHM8WknYItWdXw==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-optional-chaining": "^7.7.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz", - "integrity": "sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.7.tgz", + "integrity": "sha512-80PbkKyORBUVm1fbTLrHpYdJxMThzM1UqFGh0ALEhO9TYbG86Ah9zQYAB/84axz2vcxefDLdZwWwZNlYARlu9w==", "requires": { "@babel/helper-create-regexp-features-plugin": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" @@ -540,9 +540,9 @@ } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz", - "integrity": "sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.7.tgz", + "integrity": "sha512-b4in+YlTeE/QmTgrllnb3bHA0HntYvjz8O3Mcbx75UBPJA2xhb5A8nle498VhxSXJHQefjtQxpnLPehDJ4TRlg==", "requires": { "@babel/helper-create-regexp-features-plugin": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" @@ -608,21 +608,21 @@ } }, "@babel/plugin-transform-modules-amd": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz", - "integrity": "sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ==", + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz", + "integrity": "sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==", "requires": { - "@babel/helper-module-transforms": "^7.7.4", + "@babel/helper-module-transforms": "^7.7.5", "@babel/helper-plugin-utils": "^7.0.0", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz", - "integrity": "sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA==", + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz", + "integrity": "sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==", "requires": { - "@babel/helper-module-transforms": "^7.7.4", + "@babel/helper-module-transforms": "^7.7.5", "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-simple-access": "^7.7.4", "babel-plugin-dynamic-import-node": "^2.3.0" @@ -681,9 +681,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz", - "integrity": "sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.7.tgz", + "integrity": "sha512-OhGSrf9ZBrr1fw84oFXj5hgi8Nmg+E2w5L7NhnG0lPvpDtqd7dbyilM2/vR8CKbJ907RyxPh2kj6sBCSSfI9Ew==", "requires": { "@babel/helper-call-delegate": "^7.7.4", "@babel/helper-get-function-arity": "^7.7.4", @@ -707,9 +707,9 @@ } }, "@babel/plugin-transform-react-jsx": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.7.4.tgz", - "integrity": "sha512-LixU4BS95ZTEAZdPaIuyg/k8FiiqN9laQ0dMHB4MlpydHY53uQdWCUrwjLr5o6ilS6fAgZey4Q14XBjl5tL6xw==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.7.7.tgz", + "integrity": "sha512-SlPjWPbva2+7/ZJbGcoqjl4LsQaLpKEzxW9hcxU7675s24JmdotJOSJ4cgAbV82W3FcZpHIGmRZIlUL8ayMvjw==", "requires": { "@babel/helper-builder-react-jsx": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", @@ -726,9 +726,9 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz", - "integrity": "sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw==", + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz", + "integrity": "sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==", "requires": { "regenerator-transform": "^0.14.0" } @@ -742,9 +742,9 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.4.tgz", - "integrity": "sha512-O8kSkS5fP74Ad/8pfsCMGa8sBRdLxYoSReaARRNSz3FbFQj3z/QUvoUmJ28gn9BO93YfnXc3j+Xyaqe8cKDNBQ==", + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.6.tgz", + "integrity": "sha512-tajQY+YmXR7JjTwRvwL4HePqoL3DYxpYXIHKVvrOIvJmeHe2y1w4tz5qz9ObUDC9m76rCzIMPyn4eERuwA4a4A==", "requires": { "@babel/helper-module-imports": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", @@ -871,9 +871,9 @@ } }, "@babel/register": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.7.4.tgz", - "integrity": "sha512-/fmONZqL6ZMl9KJUYajetCrID6m0xmL4odX7v+Xvoxcv0DdbP/oO0TWIeLUCHqczQ6L6njDMqmqHFy2cp3FFsA==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.7.7.tgz", + "integrity": "sha512-S2mv9a5dc2pcpg/ConlKZx/6wXaEwHeqfo7x/QbXsdCAZm+WJC1ekVvL1TVxNsedTs5y/gG63MhJTEsmwmjtiA==", "requires": { "find-cache-dir": "^2.0.0", "lodash": "^4.17.13", @@ -883,9 +883,9 @@ } }, "@babel/runtime": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz", - "integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", + "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", "requires": { "regenerator-runtime": "^0.13.2" } @@ -1395,13 +1395,13 @@ "from": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git" }, "@sentry/browser": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.9.1.tgz", - "integrity": "sha512-7AOabwp9yAH9h6Xe6TfDwlLxHbUSWs+SPWHI7bPlht2yDSAqkXYGSzRr5X0XQJX9oBQdx2cEPMqHyJrbNaP/og==", + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.10.2.tgz", + "integrity": "sha512-r3eyBu2ln7odvWtXARCZPzpuGrKsD6U9F3gKTu4xdFkA0swSLUvS7AC2FUksj/1BE23y+eB/zzPT+RYJ58tidA==", "requires": { - "@sentry/core": "5.8.0", - "@sentry/types": "5.7.1", - "@sentry/utils": "5.8.0", + "@sentry/core": "5.10.2", + "@sentry/types": "5.10.0", + "@sentry/utils": "5.10.2", "tslib": "^1.9.3" } }, @@ -1419,44 +1419,44 @@ } }, "@sentry/core": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.8.0.tgz", - "integrity": "sha512-aAh2KLidIXJVGrxmHSVq2eVKbu7tZiYn5ylW6yzJXFetS5z4MA+JYaSBaG2inVYDEEqqMIkb17TyWxxziUDieg==", - "requires": { - "@sentry/hub": "5.8.0", - "@sentry/minimal": "5.8.0", - "@sentry/types": "5.7.1", - "@sentry/utils": "5.8.0", + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.10.2.tgz", + "integrity": "sha512-sKVeFH3v8K8xw2vM5MKMnnyAAwih+JSE3pbNL0CcCCA+/SwX+3jeAo2BhgXev2SAR/TjWW+wmeC9TdIW7KyYbg==", + "requires": { + "@sentry/hub": "5.10.2", + "@sentry/minimal": "5.10.2", + "@sentry/types": "5.10.0", + "@sentry/utils": "5.10.2", "tslib": "^1.9.3" } }, "@sentry/hub": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.8.0.tgz", - "integrity": "sha512-VdApn1ZCNwH1wwQwoO6pu53PM/qgHG+DQege0hbByluImpLBhAj9w50nXnF/8KzV4UoMIVbzCb6jXzMRmqqp9A==", + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.10.2.tgz", + "integrity": "sha512-hSlZIiu3hcR/I5yEhlpN9C0nip+U7hiRzRzUQaBiHO4YG4TC58NqnOPR89D/ekiuHIXzFpjW9OQmqtAMRoSUYA==", "requires": { - "@sentry/types": "5.7.1", - "@sentry/utils": "5.8.0", + "@sentry/types": "5.10.0", + "@sentry/utils": "5.10.2", "tslib": "^1.9.3" } }, "@sentry/integrations": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.8.0.tgz", - "integrity": "sha512-Obe3GqTtq63PAJ4opYEbeZ6Bm8uw+CND+7MywJLDguqnvIVRvxpcJIZ6wxcE/VjbU3OMkNmTMnM+ra8RB7Wj6w==", + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.10.2.tgz", + "integrity": "sha512-yvIJ6aXpzWlwD1WGTxvaeCm7JmNs4flWXKLlPWm2CEwgWBfjyaWjW7PqlA0jUOnLGCeYzgFD3AdUPlpgCsFXXA==", "requires": { - "@sentry/types": "5.7.1", - "@sentry/utils": "5.8.0", + "@sentry/types": "5.10.0", + "@sentry/utils": "5.10.2", "tslib": "^1.9.3" } }, "@sentry/minimal": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.8.0.tgz", - "integrity": "sha512-MIlFOgd+JvAUrBBmq7vr9ovRH1HvckhnwzHdoUPpKRBN+rQgTyZy1o6+kA2fASCbrRqFCP+Zk7EHMACKg8DpIw==", + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.10.2.tgz", + "integrity": "sha512-GalixiM9sckYfompH5HHTp9XT2BcjawBkcl1DMEKUBEi37+kUq0bivOBmnN1G/I4/wWOUdnAI/kagDWaWpbZPg==", "requires": { - "@sentry/hub": "5.8.0", - "@sentry/types": "5.7.1", + "@sentry/hub": "5.10.2", + "@sentry/types": "5.10.0", "tslib": "^1.9.3" } }, @@ -1474,16 +1474,16 @@ } }, "@sentry/types": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.7.1.tgz", - "integrity": "sha512-tbUnTYlSliXvnou5D4C8Zr+7/wJrHLbpYX1YkLXuIJRU0NSi81bHMroAuHWILcQKWhVjaV/HZzr7Y/hhWtbXVQ==" + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.10.0.tgz", + "integrity": "sha512-TW20GzkCWsP6uAxR2JIpIkiitCKyIOfkyDsKBeLqYj4SaZjfvBPnzgNCcYR0L0UsP1/Es6oHooZfIGSkp6GGxQ==" }, "@sentry/utils": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.8.0.tgz", - "integrity": "sha512-KDxUvBSYi0/dHMdunbxAxD3389pcQioLtcO6CI6zt/nJXeVFolix66cRraeQvqupdLhvOk/el649W4fCPayTHw==", + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.10.2.tgz", + "integrity": "sha512-UcbbaFpYrGSV448lQ16Cr+W/MPuKUflQQUdrMCt5vgaf5+M7kpozlcji4GGGZUCXIA7oRP93ABoXj55s1OM9zw==", "requires": { - "@sentry/types": "5.7.1", + "@sentry/types": "5.10.0", "tslib": "^1.9.3" } }, @@ -1518,9 +1518,9 @@ } }, "@types/babel__generator": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.0.tgz", - "integrity": "sha512-c1mZUu4up5cp9KROs/QAw0gTeHrw/x7m52LcnvMxxOZ03DmLwPV0MlGmlgzV3cnSdjhJOZsj7E7FHeioai+egw==", + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -1574,9 +1574,9 @@ } }, "@types/json-schema": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", - "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", "dev": true }, "@types/node": { @@ -1590,9 +1590,9 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, "@types/yargs": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", - "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.4.tgz", + "integrity": "sha512-Ke1WmBbIkVM8bpvsNEcGgQM70XcEh/nbpxQhW7FhrsbCsXSY9BmLB1+LHtD7r9zrsOcFlLiF+a/UeJsdfw3C5A==", "requires": { "@types/yargs-parser": "*" } @@ -1880,13 +1880,14 @@ "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" }, "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" } }, "array-map": { @@ -1909,6 +1910,16 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -2932,13 +2943,13 @@ } }, "browserslist": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.0.tgz", - "integrity": "sha512-HYnxc/oLRWvJ3TsGegR0SRL/UDnknGq2s/a8dYYEO+kOQ9m9apKoS5oiathLKZdh/e9uE+/J3j92qPlGD/vTqA==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", + "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", "requires": { - "caniuse-lite": "^1.0.30001012", - "electron-to-chromium": "^1.3.317", - "node-releases": "^1.1.41" + "caniuse-lite": "^1.0.30001015", + "electron-to-chromium": "^1.3.322", + "node-releases": "^1.1.42" } }, "bs58": { @@ -3087,9 +3098,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001012", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001012.tgz", - "integrity": "sha512-7RR4Uh04t9K1uYRWzOJmzplgEOAXbfK72oVNokCdMzA67trrhPzy93ahKk1AWHiA0c58tD2P+NHqxrA8FZ+Trg==" + "version": "1.0.30001017", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001017.tgz", + "integrity": "sha512-EDnZyOJ6eYh6lHmCvCdHAFbfV4KJ9lSdfv4h/ppEhrU/Yudkl7jujwMZ1we6RX7DXqBfT04pVMQ4J+1wcTlsKA==" }, "capture-exit": { "version": "2.0.0", @@ -3536,23 +3547,23 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", - "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" }, "core-js-compat": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.4.5.tgz", - "integrity": "sha512-rYVvzvKJDKoefdAC+q6VP63vp5hMmeVONCi9pVUbU1qRrtVrmAk/nPhnRg+i+XFd775m1hpG2Yd5RY3X45ccuw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.1.tgz", + "integrity": "sha512-2Tl1EuxZo94QS2VeH28Ebf5g3xbPZG/hj/N5HDDy4XMP/ImR0JIer/nggQRiMN91Q54JVkGbytf42wO29oXVHg==", "requires": { - "browserslist": "^4.7.3", - "semver": "^6.3.0" + "browserslist": "^4.8.2", + "semver": "7.0.0" }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" } } }, @@ -3960,9 +3971,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.319", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.319.tgz", - "integrity": "sha512-t/lYNZPwS9jLJ9SBLGd6ERYtCtsYPAXzsE1VYLshrUWpQCTAswO1pERZV4iOZipW2uVsGQrJtm2iWiYVp1zTZw==" + "version": "1.3.322", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==" }, "electrum-client": { "version": "git+https://github.com/BlueWallet/rn-electrum-client.git#d194ff69195ccc86f72088ea3712179b4be9cbb4", @@ -4039,20 +4050,21 @@ } }, "es-abstract": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.2.tgz", - "integrity": "sha512-jYo/J8XU2emLXl3OLwfwtuFfuF2w6DYPs+xy9ZfVyPkDcrauu6LYrw/q2TyCtrbc/KUdCiC5e9UajRhgNkVopA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0.tgz", + "integrity": "sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==", "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" } }, "es-to-primitive": { @@ -4117,9 +4129,9 @@ } }, "eslint": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.7.2.tgz", - "integrity": "sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -4254,9 +4266,9 @@ } }, "inquirer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", - "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.1.tgz", + "integrity": "sha512-V1FFQ3TIO15det8PijPLFR9M9baSlnRs9nL7zWu1MNVA2T9YVl9ZbrHJhYs7e9X8jeMZ3lr2JH/rdHFgNCBdYw==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -4268,7 +4280,7 @@ "lodash": "^4.17.15", "mute-stream": "0.0.8", "run-async": "^2.2.0", - "rxjs": "^6.4.0", + "rxjs": "^6.5.3", "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" @@ -4422,12 +4434,12 @@ } }, "eslint-module-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", - "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz", + "integrity": "sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw==", "dev": true, "requires": { - "debug": "^2.6.8", + "debug": "^2.6.9", "pkg-dir": "^2.0.0" }, "dependencies": { @@ -4545,22 +4557,23 @@ } }, "eslint-plugin-import": { - "version": "2.18.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", - "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.19.1.tgz", + "integrity": "sha512-x68131aKoCZlCae7rDXKSAQmbT5DQuManyXo2sK6fJJ0aK5CWAkv6A6HJZGgqC8IhjQxYPgo6/IY4Oz8AFsbBw==", "dev": true, "requires": { "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.0", + "eslint-module-utils": "^2.4.1", "has": "^1.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.0", "read-pkg-up": "^2.0.0", - "resolve": "^1.11.0" + "resolve": "^1.12.0" }, "dependencies": { "debug": { @@ -5044,9 +5057,9 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { @@ -5056,11 +5069,11 @@ "dev": true }, "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", "requires": { - "bser": "^2.0.0" + "bser": "2.1.1" } }, "fbjs": { @@ -5338,13 +5351,14 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", + "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "node-pre-gyp": "*" }, "dependencies": { "abbrev": { @@ -5354,7 +5368,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5372,32 +5387,37 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5405,7 +5425,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "optional": true, "requires": { @@ -5428,11 +5448,11 @@ "optional": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -5456,7 +5476,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "optional": true, "requires": { @@ -5482,7 +5502,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "optional": true, "requires": { @@ -5499,8 +5519,9 @@ } }, "inherits": { - "version": "2.0.3", - "bundled": true + "version": "2.0.4", + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5510,6 +5531,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5522,54 +5544,58 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { - "version": "2.1.1", + "version": "2.1.2", "bundled": true, "optional": true }, "needle": { - "version": "2.3.0", + "version": "2.4.0", "bundled": true, "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "optional": true, "requires": { @@ -5582,7 +5608,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -5595,12 +5621,20 @@ } }, "npm-bundled": { - "version": "1.0.6", + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", "bundled": true, "optional": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.7", "bundled": true, "optional": true, "requires": { @@ -5621,7 +5655,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5631,6 +5666,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5660,7 +5696,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "optional": true }, @@ -5697,7 +5733,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "optional": true, "requires": { @@ -5706,7 +5742,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5719,7 +5756,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "optional": true }, @@ -5736,6 +5773,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5753,6 +5791,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5763,17 +5802,17 @@ "optional": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -5791,11 +5830,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { - "version": "3.0.3", - "bundled": true + "version": "3.1.1", + "bundled": true, + "optional": true } } }, @@ -5902,6 +5943,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, + "optional": true, "requires": { "is-glob": "^2.0.0" } @@ -5961,9 +6003,9 @@ "dev": true }, "uglify-js": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.1.tgz", - "integrity": "sha512-pnOF7jY82wdIhATVn87uUY/FHU+MDUdPLkmGFvGoclQmeu229eTkbG5gjGGBi3R7UuYYSEeYXY/TTY5j2aym2g==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.3.tgz", + "integrity": "sha512-7tINm46/3puUA4hCkKYo4Xdts+JDaVC9ZPRcG8Xw9R4nhO/gZgUM3TENq8IF4Vatk8qCig4MzP/c8G4u2BkVQg==", "dev": true, "optional": true, "requires": { @@ -6335,9 +6377,9 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" }, "is-ci": { "version": "2.0.0", @@ -6366,9 +6408,9 @@ } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-descriptor": { "version": "0.1.6", @@ -6418,7 +6460,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true + "dev": true, + "optional": true }, "is-finite": { "version": "1.0.2", @@ -6450,6 +6493,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -6505,11 +6549,11 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "requires": { - "has": "^1.0.1" + "has": "^1.0.3" } }, "is-stream": { @@ -6517,6 +6561,12 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -7780,9 +7830,9 @@ }, "dependencies": { "dayjs": { - "version": "1.8.17", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.17.tgz", - "integrity": "sha512-47VY/htqYqr9GHd7HW/h56PpQzRBSJcxIQFwqL3P20bMF/3az5c3PWdVY3LmPXFl6cQCYHL7c79b9ov+2bOBbw==" + "version": "1.8.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.18.tgz", + "integrity": "sha512-JBMJZghNK8TtuoPnKNIzW9xavVVigld/zmZNpZSyQbkb2Opp55YIfZUpE4OEqPF/iyUVQTKcn1bC2HtC8B7s3g==" } } }, @@ -9078,9 +9128,9 @@ } }, "node-releases": { - "version": "1.1.41", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.41.tgz", - "integrity": "sha512-+IctMa7wIs8Cfsa8iYzeaLTFwv5Y4r5jZud+4AnfymzeEXKBCavFX0KBgzVaPVqf0ywa6PrO8/b+bPqdwjGBSg==", + "version": "1.1.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.44.tgz", + "integrity": "sha512-NwbdvJyR7nrcGrXvKAvzc5raj/NkoJudkarh2yIpJ4t0NH4aqjUDz/486P+ynIW5eokKOfzGNRdYoLfBlomruw==", "requires": { "semver": "^6.3.0" }, @@ -9185,9 +9235,9 @@ "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", "dev": true }, "object-keys": { @@ -9215,36 +9265,36 @@ } }, "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" } }, "object.fromentries": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.1.tgz", - "integrity": "sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.15.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" } }, "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "object.omit": { @@ -9267,13 +9317,13 @@ } }, "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" } @@ -9883,9 +9933,9 @@ "dev": true }, "core-js": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.5.tgz", - "integrity": "sha512-OuvejWH6vIaUo59Ndlh89purNm4DCIy/v3QoYlcGnn+PkYI8BhNHfCuAESrWX+ZPfq9JccVJ+XXgOMy77PJexg==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz", + "integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ==", "dev": true }, "doctrine": { @@ -10061,9 +10111,9 @@ } }, "core-js": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.5.tgz", - "integrity": "sha512-OuvejWH6vIaUo59Ndlh89purNm4DCIy/v3QoYlcGnn+PkYI8BhNHfCuAESrWX+ZPfq9JccVJ+XXgOMy77PJexg==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz", + "integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ==", "dev": true }, "doctrine": { @@ -10433,9 +10483,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.5.0.tgz", - "integrity": "sha512-4vqUjKi2huMu1OJiLhi3jN6jeeKvMZdI1tYgi/njW5zV52jNLgSAZSdN16m9bJFe61/cT8ulmw4qFitV9QRsEA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", "dev": true }, "public-encrypt": { @@ -10946,6 +10996,10 @@ "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-4.0.1.tgz", "integrity": "sha512-a0Q/gwy1oQhu17CeMq9xMZ3Sl9foaj8hFwVuy9jieqkkoQwkHnPDI+R7bEW0MNMgUCPPrzzXvka+FLGOWVqacg==" }, + "react-native-document-picker": { + "version": "git+https://github.com/BlueWallet/react-native-document-picker.git#9ce83792db340d01b1361d24b19613658abef4aa", + "from": "git+https://github.com/BlueWallet/react-native-document-picker.git#9ce83792db340d01b1361d24b19613658abef4aa" + }, "react-native-elements": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-0.19.0.tgz", @@ -11131,6 +11185,13 @@ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-9.5.1.tgz", "integrity": "sha512-cRGfomzG/5LEwuJ6ct3m5yccckeI9aj8BNYwDPVxOeJ84LuJuvk5OqcjlYNeEzOWmWiH+QrFXfpLH1ag04bUeQ==" }, + "react-native-swiper": { + "version": "git+https://github.com/BlueWallet/react-native-swiper.git#e4dbde6657f6c66cba50f8abca7c472b47297c7f", + "from": "git+https://github.com/BlueWallet/react-native-swiper.git#1.5.14", + "requires": { + "prop-types": "^15.5.10" + } + }, "react-native-tab-view": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-1.4.1.tgz", @@ -11468,7 +11529,8 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "optional": true }, "string_decoder": { "version": "1.1.1", @@ -11537,12 +11599,13 @@ } }, "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", "dev": true, "requires": { - "define-properties": "^1.1.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "regexpp": { @@ -11570,9 +11633,9 @@ "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" }, "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz", + "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==", "requires": { "jsesc": "~0.5.0" }, @@ -11697,9 +11760,9 @@ "dev": true }, "resolve": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", - "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.1.tgz", + "integrity": "sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg==", "requires": { "path-parse": "^1.0.6" } @@ -11836,9 +11899,9 @@ } }, "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "requires": { "tslib": "^1.9.0" } @@ -12207,11 +12270,11 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -12480,18 +12543,18 @@ } }, "string.prototype.trimleft": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", - "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" } }, "string.prototype.trimright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", - "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -12901,15 +12964,15 @@ "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "typescript": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", - "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz", + "integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==", "dev": true }, "ua-parser-js": { - "version": "0.7.20", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", - "integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==" + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, "uglify-es": { "version": "3.3.9", @@ -13430,9 +13493,9 @@ } }, "xmldom": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" }, "xpipe": { "version": "1.0.5", diff --git a/package.json b/package.json index fc9678ab..e3971345 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "react-native-camera": "3.4.0", "react-native-default-preference": "1.4.1", "react-native-device-info": "4.0.1", + "react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#9ce83792db340d01b1361d24b19613658abef4aa", "react-native-elements": "0.19.0", "react-native-flexi-radio-button": "0.2.2", "react-native-fs": "2.13.3", @@ -116,6 +117,7 @@ "react-native-snap-carousel": "3.8.4", "react-native-sortable-list": "0.0.23", "react-native-svg": "9.5.1", + "react-native-swiper": "git+https://github.com/BlueWallet/react-native-swiper.git#1.5.14", "react-native-tcp": "git+https://github.com/aprock/react-native-tcp.git", "react-native-tooltip": "git+https://github.com/marcosrdz/react-native-tooltip.git", "react-native-vector-icons": "6.6.0", diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js index d54a5eed..c10a3952 100644 --- a/screen/lnd/scanLndInvoice.js +++ b/screen/lnd/scanLndInvoice.js @@ -19,6 +19,7 @@ import { BlueNavigationStyle, BlueAddressInput, BlueBitcoinAmount, + BlueLoading, } from '../../BlueComponents'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; @@ -45,7 +46,8 @@ export default class ScanLndInvoice extends React.Component { constructor(props) { super(props); - + this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow); + this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide); if (!BlueApp.getWallets().some(item => item.type === LightningCustodianWallet.type)) { ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); alert('Before paying a Lightning invoice, you must first add a Lightning wallet.'); @@ -78,9 +80,7 @@ export default class ScanLndInvoice extends React.Component { } } - async componentDidMount() { - this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow); - this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide); + componentDidMount() { if (this.props.navigation.state.params.uri) { this.processTextForInvoice(this.props.navigation.getParam('uri')); } @@ -265,6 +265,9 @@ export default class ScanLndInvoice extends React.Component { }; render() { + if (!this.state.fromWallet) { + return ; + } return ( @@ -300,6 +303,7 @@ export default class ScanLndInvoice extends React.Component { isLoading={this.state.isLoading} placeholder={loc.lnd.placeholder} inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID} + launchedBy={this.props.navigation.state.routeName} /> { + try { + const res = await DocumentPicker.pick(); + const file = await RNFS.readFile(res.uri, 'ascii'); + const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); + if (bufferDecoded) { + if (this.state.fromWallet.type === WatchOnlyWallet.type) { + // watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code + // so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask + // user whether he wants to broadcast it + this.props.navigation.navigate('PsbtWithHardwareWallet', { + memo: this.state.memo, + fromWallet: this.state.fromWallet, + psbt: bufferDecoded, + isFirstPSBTAlreadyBase64: true, + }); + this.setState({ isLoading: false }); + return; + } + } else { + throw new Error(); + } + } catch (err) { + if (!DocumentPicker.isCancel(err)) { + alert('The selected file does not contain a signed transaction that can be imported.'); + } + } + this.setState({ isAdvancedTransactionOptionsVisible: false }); + }; + renderAdvancedTransactionOptionsModal = () => { const isSendMaxUsed = this.state.addresses.some(element => element.amount === BitcoinUnit.MAX); return ( @@ -736,6 +770,9 @@ export default class SendDetails extends Component { onSwitch={this.onReplaceableFeeSwitchValueChanged} /> )} + {this.state.fromWallet.use_with_hardware_wallet && ( + + )} {this.state.fromWallet.allowBatchSend() && ( <> {this.state.addresses.length > 1 && ( @@ -1067,6 +1105,7 @@ SendDetails.propTypes = { getParam: PropTypes.func, setParams: PropTypes.func, state: PropTypes.shape({ + routeName: PropTypes.string, params: PropTypes.shape({ amount: PropTypes.number, address: PropTypes.string, diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 45e22dd9..280e38ad 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -1,6 +1,6 @@ /* global alert */ import React, { Component } from 'react'; -import { ActivityIndicator, TouchableOpacity, View, Dimensions, Image, TextInput, Clipboard, Linking } from 'react-native'; +import { ActivityIndicator, TouchableOpacity, ScrollView, View, Dimensions, Image, TextInput, Clipboard, Linking } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Icon, Text } from 'react-native-elements'; import { @@ -13,8 +13,11 @@ import { BlueCopyToClipboardButton, } 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'); @@ -36,7 +39,10 @@ export default class PsbtWithHardwareWallet extends Component { this.setState({ renderScanner: false }, () => { console.log(ret.data); try { - let Tx = this.state.fromWallet.combinePsbt(this.state.psbt.toBase64(), ret.data); + let Tx = this.state.fromWallet.combinePsbt( + this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), + this.state.isSecondPSBTAlreadyBase64 ? ret.data : ret.data.toBase64(), + ); this.setState({ txhex: Tx.toHex() }); } catch (Err) { alert(Err); @@ -46,18 +52,19 @@ export default class PsbtWithHardwareWallet extends Component { constructor(props) { super(props); - this.state = { isLoading: false, renderScanner: false, - qrCodeHeight: height > width ? width - 40 : width / 2, + 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, }; } - async componentDidMount() { + componentDidMount() { console.log('send/psbtWithHardwareWallet - componentDidMount'); } @@ -185,6 +192,36 @@ export default class PsbtWithHardwareWallet extends Component { ); } + exportPSBT = () => { + Share.open({ + url: `data:text/psbt;base64,${this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}`, + }) + .catch(error => console.log(error)) + .finally(() => + alert( + 'In order for your hardware wallet to read this transaction, you must rename the shared file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', + ), + ); + }; + + openSignedTransaction = async () => { + try { + const res = await DocumentPicker.pick(); + const file = await RNFS.readFile(res.uri, 'ascii'); + const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); + if (bufferDecoded) { + this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarCodeRead({ data: bufferDecoded })); + } 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 ( @@ -200,27 +237,58 @@ export default class PsbtWithHardwareWallet extends Component { return ( - + This is partially signed bitcoin transaction (PSBT). Please finish signing it with your hardware wallet. - this.setState({ renderScanner: true })} title={'Scan signed transaction'} /> + this.setState({ renderScanner: true })} + title={'Scan Signed Transaction'} + /> + + + + - + - + ); } diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index b00ab06d..fa750b5a 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -1,30 +1,69 @@ /* global alert */ -import React from 'react'; -import { Image, TouchableOpacity, Platform } from 'react-native'; -import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { Image, View, TouchableOpacity, Platform } from 'react-native'; import { RNCamera } from 'react-native-camera'; -import { SafeBlueArea } from '../../BlueComponents'; import { Icon } from 'react-native-elements'; import ImagePicker from 'react-native-image-picker'; +import PropTypes from 'prop-types'; +import { useNavigationParam, useNavigation } from 'react-navigation-hooks'; +import DocumentPicker from 'react-native-document-picker'; +import RNFS from 'react-native-fs'; const LocalQRCode = require('@remobile/react-native-qrcode-local-image'); -export default class ScanQRCode extends React.Component { - static navigationOptions = { - header: null, +const ScanQRCode = ({ + onBarScanned = useNavigationParam('onBarScanned'), + cameraPreviewIsPaused = false, + showCloseButton = true, + showFileImportButton = useNavigationParam('showFileImportButton') || false, + launchedBy = useNavigationParam('launchedBy'), +}) => { + const [isLoading, setIsLoading] = useState(false); + const { navigate } = useNavigation(); + + const onBarCodeRead = ret => { + if (!isLoading && !cameraPreviewIsPaused) { + setIsLoading(true); + try { + if (showCloseButton && launchedBy) { + navigate(launchedBy); + } + if (ret.additionalProperties) { + onBarScanned(ret.data, ret.additionalProperties); + } else { + onBarScanned(ret.data); + } + } catch (e) { + console.log(e); + } + } + setIsLoading(false); }; - cameraRef = null; + const showFilePicker = async () => { + setIsLoading(true); + try { + const res = await DocumentPicker.pick(); + const file = await RNFS.readFile(res.uri); + const fileParsed = JSON.parse(file); + if (fileParsed.keystore.xpub) { + onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint: fileParsed.keystore.ckcc_xfp } }); + } else { + throw new Error(); + } + } catch (err) { + if (!DocumentPicker.isCancel(err)) { + alert('The selected file does not contain a wallet that can be imported.'); + } + setIsLoading(false); + } + setIsLoading(false); + }; - onBarCodeRead = ret => { - if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview(); - const onBarScannedProp = this.props.navigation.getParam('onBarScanned'); - this.props.navigation.goBack(); - onBarScannedProp(ret.data); - }; // end + useEffect(() => {}, [cameraPreviewIsPaused]); - render() { - return ( - + return ( + + {!cameraPreviewIsPaused && !isLoading && ( (this.cameraRef = ref)} - style={{ flex: 1, justifyContent: 'space-between' }} - onBarCodeRead={this.onBarCodeRead} + style={{ flex: 1, justifyContent: 'space-between', backgroundColor: '#000000' }} + onBarCodeRead={onBarCodeRead} barCodeTypes={[RNCamera.Constants.BarCodeType.qr]} /> + )} + {showCloseButton && ( this.props.navigation.goBack(null)} + onPress={() => navigate(launchedBy)} > - { - if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview(); + )} + { + if (!isLoading) { + setIsLoading(true); ImagePicker.launchImageLibrary( { title: null, @@ -77,30 +119,49 @@ export default class ScanQRCode extends React.Component { const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString(); LocalQRCode.decode(uri, (error, result) => { if (!error) { - this.onBarCodeRead({ data: result }); + onBarCodeRead({ data: result }); } else { - if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview(); alert('The selected image does not contain a QR Code.'); } }); - } else { - if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview(); } + setIsLoading(false); }, ); + } + }} + > + + + {showFileImportButton && ( + - + - - ); - } -} + )} + + ); +}; +ScanQRCode.navigationOptions = { + header: null, +}; ScanQRCode.propTypes = { - navigation: PropTypes.shape({ - goBack: PropTypes.func, - dismiss: PropTypes.func, - getParam: PropTypes.func, - }), + launchedBy: PropTypes.string, + onBarScanned: PropTypes.func, + cameraPreviewIsPaused: PropTypes.bool, + showFileImportButton: PropTypes.bool, + showCloseButton: PropTypes.bool, }; +export default ScanQRCode; diff --git a/screen/wallets/import.js b/screen/wallets/import.js index 86ecbe56..f2548721 100644 --- a/screen/wallets/import.js +++ b/screen/wallets/import.js @@ -35,9 +35,9 @@ const WalletsImport = () => { importMnemonic(importText); }; - const importMnemonic = importText => { + const importMnemonic = (importText, additionalProperties) => { try { - WalletImport.processImportText(importText); + WalletImport.processImportText(importText, additionalProperties); dismiss(); } catch (error) { alert(loc.wallets.import.error); @@ -45,9 +45,9 @@ const WalletsImport = () => { } }; - const onBarScanned = value => { + const onBarScanned = (value, additionalProperties) => { setImportText(value); - importMnemonic(value); + importMnemonic(value, additionalProperties); }; return ( @@ -110,7 +110,7 @@ const WalletsImport = () => { { - navigate('ScanQrAddress', { onBarScanned }); + navigate('ScanQrAddress', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true }); }} /> diff --git a/screen/wallets/list.js b/screen/wallets/list.js index d5b96546..a52ff288 100644 --- a/screen/wallets/list.js +++ b/screen/wallets/list.js @@ -1,6 +1,17 @@ /* global alert */ import React, { Component } from 'react'; -import { View, TouchableOpacity, Text, FlatList, InteractionManager, RefreshControl, ScrollView, Alert } from 'react-native'; +import { + View, + StatusBar, + TouchableOpacity, + Text, + StyleSheet, + FlatList, + InteractionManager, + RefreshControl, + ScrollView, + Alert, +} from 'react-native'; import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents'; import { Icon } from 'react-native-elements'; import { NavigationEvents } from 'react-navigation'; @@ -8,6 +19,9 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import PropTypes from 'prop-types'; import { PlaceholderWallet } from '../../class'; import WalletImport from '../../class/walletImport'; +import Swiper from 'react-native-swiper'; +import ScanQRCode from '../send/scanQrAddress'; +import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch'; let EV = require('../../events'); let A = require('../../analytics'); /** @type {AppStorage} */ @@ -16,23 +30,8 @@ let loc = require('../../loc'); let BlueElectrum = require('../../BlueElectrum'); export default class WalletsList extends Component { - static navigationOptions = ({ navigation }) => ({ - headerStyle: { - backgroundColor: '#FFFFFF', - borderBottomWidth: 0, - elevation: 0, - }, - headerRight: ( - navigation.navigate('Settings')} - > - - - ), - }); - walletsCarousel = React.createRef(); + swiperRef = React.createRef(); constructor(props) { super(props); @@ -299,21 +298,47 @@ export default class WalletsList extends Component { } }; + onSwiperIndexChanged = index => { + StatusBar.setBarStyle(index === 1 ? 'dark-content' : 'light-content'); + this.setState({ cameraPreviewIsPaused: index === 1 }); + }; + + onBarScanned = value => { + DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => { + ReactNativeHapticFeedback.trigger('impactLight', { ignoreAndroidSystemSettings: false }); + this.props.navigation.navigate(completionValue); + }); + }; + _renderItem = data => { return ; }; + + renderNavigationHeader = () => { + return ( + + this.props.navigation.navigate('Settings')}> + + + + ); + }; + render() { if (this.state.isLoading) { return ; } return ( - + { + onDidFocus={() => { this.redrawScreen(); + this.setState({ cameraPreviewIsPaused: this.swiperRef.current.index === 1 }); }} + onWillBlur={() => this.setState({ cameraPreviewIsPaused: true })} /> this.refreshTransactions()} @@ -322,65 +347,112 @@ export default class WalletsList extends Component { /> } > - wallet.type === PlaceholderWallet.type) - ? () => this.props.navigation.navigate('AddWallet') - : null - } - /> - { - this.handleClick(index); - }} - handleLongPress={this.handleLongPress} - onSnapToItem={index => { - this.onSnapToItem(index); - }} - ref={c => (this.walletsCarousel = c)} - /> - - - + + + + + + {this.renderNavigationHeader()} + this.refreshTransactions()} refreshing={!this.state.isFlatListRefreshControlHidden} /> + } + > + wallet.type === PlaceholderWallet.type) + ? () => this.props.navigation.navigate('AddWallet') + : null + } + /> + { + this.handleClick(index); }} - > - {loc.wallets.list.empty_txs1} - - { + this.onSnapToItem(index); }} - > - {loc.wallets.list.empty_txs2} - - - } - data={this.state.dataSource} - extraData={this.state.dataSource} - keyExtractor={this._keyExtractor} - renderItem={this._renderItem} - /> - + ref={c => (this.walletsCarousel = c)} + /> + + + + {loc.wallets.list.empty_txs1} + + + {loc.wallets.list.empty_txs2} + + + } + data={this.state.dataSource} + extraData={this.state.dataSource} + keyExtractor={this._keyExtractor} + renderItem={this._renderItem} + /> + + + + + - + ); } } +const styles = StyleSheet.create({ + wrapper: { + backgroundColor: '#FFFFFF', + }, + walletsListWrapper: { + flex: 1, + backgroundColor: '#FFFFFF', + }, + scanQRWrapper: { + flex: 1, + backgroundColor: '#000000', + }, +}); + WalletsList.propTypes = { navigation: PropTypes.shape({ + state: PropTypes.shape({ + routeName: PropTypes.string, + }), navigate: PropTypes.func, }), }; diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 1f849326..289623c6 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -16,6 +16,7 @@ import { StatusBar, Linking, KeyboardAvoidingView, + Alert, } from 'react-native'; import PropTypes from 'prop-types'; import { NavigationEvents } from 'react-navigation'; @@ -29,7 +30,7 @@ import { } from '../../BlueComponents'; import WalletGradient from '../../class/walletGradient'; import { Icon } from 'react-native-elements'; -import { LightningCustodianWallet } from '../../class'; +import { LightningCustodianWallet, HDSegwitBech32Wallet } from '../../class'; import Handoff from 'react-native-handoff'; import Modal from 'react-native-modal'; import NavigationService from '../../NavigationService'; @@ -400,7 +401,7 @@ export default class WalletTransactions extends Component { } }; - async onWillBlur() { + onWillBlur() { StatusBar.setBarStyle('dark-content'); } @@ -409,6 +410,14 @@ export default class WalletTransactions extends Component { clearInterval(this.interval); } + navigateToSendScreen = () => { + this.props.navigation.navigate('SendDetails', { + fromAddress: this.state.wallet.getAddress(), + fromSecret: this.state.wallet.getSecret(), + fromWallet: this.state.wallet, + }); + }; + renderItem = item => { return ( { - if (this.state.wallet.allowSend()) { + if ( + this.state.wallet.allowSend() || + (this.state.wallet._hdWalletInstance instanceof HDSegwitBech32Wallet && this.state.wallet._hdWalletInstance.allowSend()) + ) { return ( { if (this.state.wallet.chain === Chain.OFFCHAIN) { navigate('ScanLndInvoice', { fromSecret: this.state.wallet.getSecret() }); } else { - navigate('SendDetails', { - fromAddress: this.state.wallet.getAddress(), - fromSecret: this.state.wallet.getSecret(), - fromWallet: this.state.wallet, - }); + if ( + this.state.wallet._hdWalletInstance instanceof HDSegwitBech32Wallet && + this.state.wallet._hdWalletInstance.allowSend() + ) { + if (this.state.wallet.use_with_hardware_wallet) { + this.navigateToSendScreen(); + } else { + Alert.alert( + 'Wallet', + 'This wallet is not being used in conjunction with a hardwarde wallet. Would you like to enable hardware wallet use?', + [ + { + text: loc._.ok, + onPress: async () => { + this.state.wallet.use_with_hardware_wallet = true; + await BlueApp.saveToDisk(); + this.navigateToSendScreen(); + }, + style: 'default', + }, + + { text: loc.send.details.cancel, onPress: () => {}, style: 'cancel' }, + ], + { cancelable: false }, + ); + } + } else { + this.navigateToSendScreen(); + } } }} /> diff --git a/tests/integration/deepLinkSchemaMatch.test.js b/tests/integration/deepLinkSchemaMatch.test.js new file mode 100644 index 00000000..f852b56c --- /dev/null +++ b/tests/integration/deepLinkSchemaMatch.test.js @@ -0,0 +1,50 @@ +/* global describe, it, expect */ +import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch'; +const assert = require('assert'); + +describe('unit - DeepLinkSchemaMatch', function() { + it('hasSchema', () => { + const hasSchema = DeeplinkSchemaMatch.hasSchema('bitcoin:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'); + assert.ok(hasSchema); + }); + + it('isBitcoin Address', () => { + assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG')); + }); + + it('isLighting Invoice', () => { + assert.ok( + DeeplinkSchemaMatch.isLightningInvoice( + 'lightning:lnbc10u1pwjqwkkpp5vlc3tttdzhpk9fwzkkue0sf2pumtza7qyw9vucxyyeh0yaqq66yqdq5f38z6mmwd3ujqar9wd6qcqzpgxq97zvuqrzjqvgptfurj3528snx6e3dtwepafxw5fpzdymw9pj20jj09sunnqmwqz9hx5qqtmgqqqqqqqlgqqqqqqgqjq5duu3fs9xq9vn89qk3ezwpygecu4p3n69wm3tnl28rpgn2gmk5hjaznemw0gy32wrslpn3g24khcgnpua9q04fttm2y8pnhmhhc2gncplz0zde', + ), + ); + }); + + it('isBoth Bitcoin & Invoice', () => { + assert.ok( + DeeplinkSchemaMatch.isBothBitcoinAndLightning( + 'bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=lnbc1u1pwry044pp53xlmkghmzjzm3cljl6729cwwqz5hhnhevwfajpkln850n7clft4sdqlgfy4qv33ypmj7sj0f32rzvfqw3jhxaqcqzysxq97zvuq5zy8ge6q70prnvgwtade0g2k5h2r76ws7j2926xdjj2pjaq6q3r4awsxtm6k5prqcul73p3atveljkn6wxdkrcy69t6k5edhtc6q7lgpe4m5k4', + ), + ); + }); + + it('isLnurl', () => { + assert.ok( + DeeplinkSchemaMatch.isLnUrl( + 'LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS', + ), + ); + }); + + it('navigationForRoute', () => { + const event = { uri: '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG' }; + DeeplinkSchemaMatch.navigationRouteFor(event, navValue => { + assert.strictEqual(navValue, { + routeName: 'SendDetails', + params: { + uri: event.url, + }, + }); + }); + }); +}); From a9c0a8744e865bce1d8c08a9580a6a853b61c89b Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Wed, 1 Jan 2020 02:03:15 -0600 Subject: [PATCH 2/7] Update Info.plist --- ios/BlueWallet/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index d545ad13..ec572097 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -2,6 +2,8 @@ + LSSupportsOpeningDocumentsInPlace + CFBundleDevelopmentRegion en CFBundleDisplayName From 65646397691225fd3caeb637f491c9dfbe836133 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Tue, 31 Dec 2019 23:19:31 -0600 Subject: [PATCH 3/7] ADD: Change masterfingerprint UI --- screen/wallets/details.js | 56 ++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 590c2979..db04793c 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -20,6 +20,7 @@ import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import Biometric from '../../class/biometrics'; import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class'; +import { ScrollView } from 'react-native-gesture-handler'; let EV = require('../../events'); let prompt = require('../../prompt'); /** @type {AppStorage} */ @@ -55,6 +56,7 @@ export default class WalletDetails extends Component { walletName: wallet.getLabel(), wallet, useWithHardwareWallet: !!wallet.use_with_hardware_wallet, + masterFingerprint: wallet.masterFingerprint ? String(wallet.masterFingerprint) : '', }; this.props.navigation.setParams({ isLoading, saveAction: () => this.setLabel() }); } @@ -71,6 +73,7 @@ export default class WalletDetails extends Component { this.props.navigation.setParams({ isLoading: true }); this.setState({ isLoading: true }, async () => { this.state.wallet.setLabel(this.state.walletName); + this.state.wallet.masterFingerprint = Number(this.state.masterFingerprint); BlueApp.saveToDisk(); alert('Wallet updated.'); this.props.navigation.goBack(null); @@ -122,7 +125,7 @@ export default class WalletDetails extends Component { return ( - + {(() => { if (this.state.wallet.getAddress()) { @@ -158,18 +161,20 @@ export default class WalletDetails extends Component { placeholder={loc.send.details.note_placeholder} value={this.state.walletName} onChangeText={text => { - if (text.trim().length === 0) { - text = this.state.wallet.getLabel(); - } this.setState({ walletName: text }); }} + onBlur={() => { + if (this.state.walletName.trim().length === 0) { + this.setState({ walletName: this.state.wallet.getLabel() }); + } + }} numberOfLines={1} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} editable={!this.state.isLoading} underlineColorAndroid="transparent" /> - + {loc.wallets.details.type.toLowerCase()} @@ -182,15 +187,48 @@ export default class WalletDetails extends Component { )} - {this.state.wallet.type === WatchOnlyWallet.type && this.state.wallet.getSecret().startsWith('zpub') && ( - + <> + {'advanced'} + Master Fingerprint + + + { + if (isNaN(text)) { + return; + } + this.setState({ masterFingerprint: text }); + }} + numberOfLines={1} + style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} + editable={!this.state.isLoading} + keyboardType="decimal-pad" + underlineColorAndroid="transparent" + /> + + {'Use with hardware wallet'} this.onUseWithHardwareWalletSwitch(value)} /> - + )} - + ); From 2beed9984f8251149a787131be565997693dc77c Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Wed, 1 Jan 2020 12:38:11 -0600 Subject: [PATCH 4/7] Update psbtWithHardwareWallet.js --- screen/send/psbtWithHardwareWallet.js | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 280e38ad..9d1996df 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -1,6 +1,17 @@ /* global alert */ import React, { Component } from 'react'; -import { ActivityIndicator, TouchableOpacity, ScrollView, View, Dimensions, Image, TextInput, Clipboard, Linking } from 'react-native'; +import { + ActivityIndicator, + TouchableOpacity, + ScrollView, + View, + Dimensions, + Image, + TextInput, + Clipboard, + Linking, + Platform, +} from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Icon, Text } from 'react-native-elements'; import { @@ -192,16 +203,21 @@ export default class PsbtWithHardwareWallet extends Component { ); } - exportPSBT = () => { + exportPSBT = async () => { + const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; + await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); Share.open({ - url: `data:text/psbt;base64,${this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}`, + url: Platform.OS === 'ios' ? 'file://' : 'content://' + filePath, }) .catch(error => console.log(error)) - .finally(() => - alert( - 'In order for your hardware wallet to read this transaction, you must rename the shared file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', - ), - ); + .finally(() => { + if (Platform.OS === 'android') { + alert( + 'In order for your hardware wallet to read this transaction, you must rename the exported file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', + ); + } + RNFS.unlink(filePath); + }); }; openSignedTransaction = async () => { From 84b0baf0868b7983be56345181266d274e393194 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Wed, 1 Jan 2020 12:52:16 -0600 Subject: [PATCH 5/7] Update psbtWithHardwareWallet.js --- screen/send/psbtWithHardwareWallet.js | 33 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 9d1996df..b635694a 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -204,20 +204,27 @@ export default class PsbtWithHardwareWallet extends Component { } exportPSBT = async () => { - const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; - await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); - Share.open({ - url: Platform.OS === 'ios' ? 'file://' : 'content://' + filePath, - }) - .catch(error => console.log(error)) - .finally(() => { - if (Platform.OS === 'android') { + if (Platform.OS === 'ios') { + const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; + await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); + Share.open({ + url: 'file://' + filePath, + }) + .catch(error => console.log(error)) + .finally(() => { + RNFS.unlink(filePath); + }); + } else { + Share.open({ + url: `data:text/psbt;base64,${this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}`, + }) + .catch(error => console.log(error)) + .finally(() => alert( - 'In order for your hardware wallet to read this transaction, you must rename the exported file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', - ); - } - RNFS.unlink(filePath); - }); + 'In order for your hardware wallet to read this transaction, you must rename the shared file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', + ), + ); + } }; openSignedTransaction = async () => { From 063287ec9d94cce313718761c04b286a4322638b Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Wed, 1 Jan 2020 21:05:35 -0600 Subject: [PATCH 6/7] TST --- android/app/src/main/AndroidManifest.xml | 24 +++++++++++++++---- screen/send/psbtWithHardwareWallet.js | 30 +++++++----------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 62a2e2cb..5c8bfba9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,16 +29,32 @@ - - - - + + + + + + + + + + + + diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index b635694a..f4901a24 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -204,27 +204,15 @@ export default class PsbtWithHardwareWallet extends Component { } exportPSBT = async () => { - if (Platform.OS === 'ios') { - const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; - await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); - Share.open({ - url: 'file://' + filePath, - }) - .catch(error => console.log(error)) - .finally(() => { - RNFS.unlink(filePath); - }); - } else { - Share.open({ - url: `data:text/psbt;base64,${this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}`, - }) - .catch(error => console.log(error)) - .finally(() => - alert( - 'In order for your hardware wallet to read this transaction, you must rename the shared file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', - ), - ); - } + const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; + await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); + Share.open({ + url: 'file://' + filePath, + }) + .catch(error => console.log(error)) + .finally(() => { + RNFS.unlink(filePath); + }); }; openSignedTransaction = async () => { From cce9cbeec9ff74aab8e1e0a9415ebe258963f242 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Wed, 1 Jan 2020 23:07:08 -0600 Subject: [PATCH 7/7] Update psbtWithHardwareWallet.js --- screen/send/psbtWithHardwareWallet.js | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index f4901a24..b635694a 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -204,15 +204,27 @@ export default class PsbtWithHardwareWallet extends Component { } exportPSBT = async () => { - const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; - await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); - Share.open({ - url: 'file://' + filePath, - }) - .catch(error => console.log(error)) - .finally(() => { - RNFS.unlink(filePath); - }); + if (Platform.OS === 'ios') { + const filePath = RNFS.TemporaryDirectoryPath + `/${Date.now()}.psbt`; + await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii'); + Share.open({ + url: 'file://' + filePath, + }) + .catch(error => console.log(error)) + .finally(() => { + RNFS.unlink(filePath); + }); + } else { + Share.open({ + url: `data:text/psbt;base64,${this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}`, + }) + .catch(error => console.log(error)) + .finally(() => + alert( + 'In order for your hardware wallet to read this transaction, you must rename the shared file to have a "psbt" extension. Example: From transaction.null to transaction.psbt', + ), + ); + } }; openSignedTransaction = async () => {