From 1680a6768bde21c9aaf82941d232a6ced381ed90 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Tue, 31 Dec 2019 21:31:04 -0600 Subject: [PATCH 01/68] # 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 02/68] 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 03/68] 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 04/68] 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 05/68] 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 06/68] 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 07/68] 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 () => { From f5f5e787b88090c9879b73b10424a518b7003c44 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 00:09:14 -0600 Subject: [PATCH 08/68] OPS: Package updates --- ios/Podfile.lock | 24 +++---- package-lock.json | 163 ++++++++++++++++++++++++++-------------------- package.json | 10 +-- 3 files changed, 110 insertions(+), 87 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6fd4e975..254adc46 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -82,7 +82,7 @@ PODS: - React - react-native-randombytes (3.5.3): - React - - react-native-slider (2.0.0-rc.1): + - react-native-slider (2.0.8): - React - react-native-webview (6.9.0): - React @@ -125,13 +125,13 @@ PODS: - React - RNHandoff (0.0.3): - React - - RNQuickAction (0.3.12): + - RNQuickAction (0.3.13): - React - RNRate (1.0.1): - React - RNSecureKeyStore (1.0.0): - React - - RNSentry (1.0.9): + - RNSentry (1.2.1): - React - Sentry (~> 4.4.0) - RNShare (2.0.0): @@ -140,11 +140,11 @@ PODS: - React - RNVectorIcons (6.6.0): - React - - RNWatch (0.4.1): + - RNWatch (0.4.2): - 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 @@ -334,7 +334,7 @@ SPEC CHECKSUMS: react-native-haptic-feedback: 22c9dc85fd8059f83bf9edd9212ac4bd4ae6074d react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8 - react-native-slider: 6d83f7b8076a84e965a43fbdcfcf9dac19cea42e + react-native-slider: b2f361499888302147205f17f6fffa921a7bda70 react-native-webview: f72ac4078e115dfa741cc588acb1cca25566457d React-RCTActionSheet: b0f1ea83f4bf75fb966eae9bfc47b78c8d3efd90 React-RCTAnimation: 359ba1b5690b1e87cc173558a78e82d35919333e @@ -354,15 +354,15 @@ SPEC CHECKSUMS: RNFS: c9bbde46b0d59619f8e7b735991c60e0f73d22c1 RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0 RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa - RNQuickAction: eca9a5dd04b5cdf8a0dd32d8be8844dc33aba2bd + RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 RNRate: 29be49c24b314c4e8ec09d848c3965f61cb0be47 RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8 - RNSentry: 2803ba8c8129dcf26b79e9b4d8c80168be6e4390 + RNSentry: 9b1d983b2d5d1c215ba6490348fd2a4cc23a8a9d RNShare: 8b171d4b43c1d886917fdd303bf7a4b87167b05c RNSVG: 0eb087cfb5d7937be93c45b163b26352a647e681 RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4 - RNWatch: a14e378448e187cc12f307f61d41fe8a65400e86 - Sentry: bba998b0fb157fdd6596aa73290a9d67ae47be79 + RNWatch: a36ea17fac675b98b1d8cd41604af68cf1fa9a03 + Sentry: 14bdd673870e8cf64932b149fad5bbbf39a9b390 swift_qrcodejs: 4d024fc98b0778b804ec6a5c810880fd092aec9d TcpSockets: 8d839b9b14f6f344d98e4642ded13ab3112b462d ToolTipMenu: bdcaa0e888bcf44778a67fe34639b094352e904e diff --git a/package-lock.json b/package-lock.json index 1fc24ea3..7ac832e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1211,9 +1211,9 @@ "integrity": "sha512-EJGsbrHubK1mGxPjWB74AaHAd5m9I+Gg2RRPZzMK6org7QOU9WOBnIMFqoeVto3hKOaEPlk8NV74H6G34/2pZQ==" }, "@react-native-community/blur": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-3.3.1.tgz", - "integrity": "sha512-UfH2ut/l4GpZHeq/TGx3BrmyXSCSBBwBCVx1DhPodP3k959zJ2ajgXa3PiU/qjutftTUw6KH9Frsh2U0ax9dMQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-3.4.1.tgz", + "integrity": "sha512-XhbS230J7BGuoEamjPFZ5jUWDOW16y+vD0Soyq9Iv1qL8R47esGl54bnfUSMH10WhNXrQzvPxkMzap+ONHpE2w==", "requires": { "prop-types": "^15.5.10" } @@ -1330,9 +1330,9 @@ } }, "@react-native-community/slider": { - "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-2.0.0-rc.1.tgz", - "integrity": "sha512-hdqQavGovI5M9NjCj4q4SPXyYOEHpBGXLIHBFETyL0S/B96hb09MXZAhOxsPYYs8KamYSKh2IYKwZ8yEiikNSg==" + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-2.0.8.tgz", + "integrity": "sha512-FZC3wjYzHQiD7jT7ALy3QNccyLj9zQBRiKGBFr/QvrWLkVg5orpIJ53aYFXm3eOkNvUV+wjhoI9uCkh3LCN2+A==" }, "@react-navigation/core": { "version": "3.4.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,71 +1419,71 @@ } }, "@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" } }, "@sentry/react-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-1.0.9.tgz", - "integrity": "sha512-fe1KEUJc+N4vq/k0ykqQ3el/CXxwTN7E8kBTYBCvxt9U449FdRodyFduiwz1UrkYhBsaQDC+5vQSZBZyT8EuOA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-1.2.1.tgz", + "integrity": "sha512-JE2B/pMvd7+3TFdzs03+DOdrALAHd8bAphJ8tk+nWjX7oQVJNgVn/IvnJfKxasHHBXQ2z+42Xy9n2Fqam/Gq0w==", "requires": { - "@sentry/browser": "^5.6.3", - "@sentry/core": "^5.6.2", - "@sentry/integrations": "^5.6.1", - "@sentry/types": "^5.6.1", - "@sentry/utils": "^5.6.1", - "@sentry/wizard": "^1.0.0" + "@sentry/browser": "^5.10.0", + "@sentry/core": "^5.10.0", + "@sentry/integrations": "^5.10.0", + "@sentry/types": "^5.10.0", + "@sentry/utils": "^5.10.0", + "@sentry/wizard": "^1.0.2" } }, "@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" } }, @@ -5354,7 +5354,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5372,11 +5373,13 @@ }, "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" @@ -5389,15 +5392,18 @@ }, "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", @@ -5500,7 +5506,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5510,6 +5517,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5522,17 +5530,20 @@ "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", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5549,6 +5560,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5621,7 +5633,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5631,6 +5644,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5706,7 +5720,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5736,6 +5751,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 +5769,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5791,11 +5808,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -5902,6 +5921,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" } @@ -6418,7 +6438,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 +6471,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" } @@ -11046,9 +11068,9 @@ } }, "react-native-quick-actions": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/react-native-quick-actions/-/react-native-quick-actions-0.3.12.tgz", - "integrity": "sha512-jQkzbA6L1/+FIqvHnPenHUe2IrBihh48KOMTOa0QbOaxWzsejSB8kkWpSQOjYxjGu7k+3DVgosQ8wGJTB/l4JA==" + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/react-native-quick-actions/-/react-native-quick-actions-0.3.13.tgz", + "integrity": "sha512-Vz13a0+NV0mzCh/29tNt0qDzWPh8i2srTQW8eCSzGFDArnVm1COTOhTD0FY0hWHlxRY0ahvX+BlezTDvsyAuMA==" }, "react-native-randombytes": { "version": "3.5.3", @@ -11254,9 +11276,9 @@ } }, "react-native-watch-connectivity": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-0.4.1.tgz", - "integrity": "sha512-fMx3hRXinxAoC64YUL+UkXuwlgYeYp3ECKMucxFgRtouzfCC3RGD+wewY7vxgOrcjkKXusIbCdbU4gS2Lojhqw==" + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-0.4.2.tgz", + "integrity": "sha512-es/c1yPRc5aQXNjKuNr0nCgYvuD126bGDRAhq5OaKpnccWHGQorctqxKYRKyNjloOM/NGc97C7DDNnfF1cCfJw==" }, "react-native-webview": { "version": "6.9.0", @@ -11468,7 +11490,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", diff --git a/package.json b/package.json index fc9678ab..c04ba613 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,10 @@ "dependencies": { "@babel/preset-env": "7.5.0", "@react-native-community/async-storage": "1.6.2", - "@react-native-community/blur": "3.3.1", - "@react-native-community/slider": "2.0.0-rc.1", + "@react-native-community/blur": "3.4.1", + "@react-native-community/slider": "2.0.8", "@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git", - "@sentry/react-native": "1.0.9", + "@sentry/react-native": "1.2.1", "amplitude-js": "5.6.0", "bech32": "1.1.3", "bignumber.js": "9.0.0", @@ -108,7 +108,7 @@ "react-native-privacy-snapshot": "git+https://github.com/BlueWallet/react-native-privacy-snapshot.git", "react-native-prompt-android": "git+https://github.com/marcosrdz/react-native-prompt-android.git", "react-native-qrcode-svg": "5.1.2", - "react-native-quick-actions": "0.3.12", + "react-native-quick-actions": "0.3.13", "react-native-randombytes": "3.5.3", "react-native-rate": "1.1.7", "react-native-secure-key-store": "git+https://github.com/marcosrdz/react-native-secure-key-store.git#38332f629f577cdd57c69fc8cc971b3cbad193c9", @@ -119,7 +119,7 @@ "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", - "react-native-watch-connectivity": "0.4.1", + "react-native-watch-connectivity": "0.4.2", "react-native-webview": "6.9.0", "react-navigation": "3.11.0", "react-navigation-hooks": "1.1.0", From 9e302df75d12fc3a8263171c2bfd6cf8c9ab6533 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Tue, 31 Dec 2019 21:31:04 -0600 Subject: [PATCH 09/68] ADD: Export/Import PSBTs eipiji --- App.js | 252 ++---------------- BlueComponents.js | 13 +- MainBottomTabs.js | 6 + android/app/src/main/AndroidManifest.xml | 23 +- 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 | 75 +++++- package.json | 3 + screen/lnd/scanLndInvoice.js | 13 +- screen/send/details.js | 45 +++- screen/send/psbtWithHardwareWallet.js | 124 ++++++++- screen/send/scanQrAddress.js | 157 +++++++---- screen/wallets/details.js | 56 +++- screen/wallets/import.js | 10 +- screen/wallets/list.js | 210 ++++++++++----- screen/wallets/transactions.js | 52 +++- tests/integration/deepLinkSchemaMatch.test.js | 50 ++++ 22 files changed, 969 insertions(+), 412 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..5c8bfba9 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..ec572097 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -2,12 +2,29 @@ - UIUserInterfaceStyle - Light + LSSupportsOpeningDocumentsInPlace + CFBundleDevelopmentRegion en CFBundleDisplayName BlueWallet + CFBundleDocumentTypes + + + CFBundleTypeIconFiles + + CFBundleTypeName + PSBT + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSItemContentTypes + + io.bluewallet.psbt + + + CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -58,18 +75,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 +133,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/package.json b/package.json index c04ba613..c465e9df 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,8 @@ "react-native-camera": "3.4.0", "react-native-default-preference": "1.4.1", "react-native-device-info": "4.0.1", + "react-native-directory-picker": "git+https://github.com/BlueWallet/react-native-directory-picker.git#63307e646f72444ab83b619e579c55ee38cd162a", + "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 +118,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..f7d716ae 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, 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 { @@ -13,8 +24,12 @@ 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'; +import DirectoryPickerManager from 'react-native-directory-picker'; let loc = require('../../loc'); let EV = require('../../events'); let BlueElectrum = require('../../BlueElectrum'); @@ -36,7 +51,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 +64,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 +204,60 @@ export default class PsbtWithHardwareWallet extends Component { ); } + exportPSBT = async () => { + const fileName = `${Date.now()}.psbt`; + if (Platform.OS === 'ios') { + const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`; + 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 if (Platform.OS === 'android') { + DirectoryPickerManager.showDirectoryPicker(null, async response => { + if (response.didCancel) { + console.log('User cancelled directory picker'); + } else if (response.error) { + console.log('DirectoryPickerManager Error: ', response.error); + } else { + try { + await RNFS.writeFile( + response.path + `/${fileName}`, + this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), + 'ascii', + ); + alert('Successfully exported.'); + RNFS.unlink(response.path + `/${fileName}`); + } catch (e) { + console.log(e); + alert(e); + } + } + }); + } + }; + + 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 +273,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/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)} /> - + )} - + ); 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 80c579784a78dd8745e83f9755dd3cc706ab3c70 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 09:01:55 -0600 Subject: [PATCH 10/68] # This is a combination of 4 commits. # This is the 1st commit message: ADD: Export/Import PSBTs eipiji # This is the commit message #2: # 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 # This is the commit message #3: Update Info.plist # This is the commit message #4: ADD: Change masterfingerprint UI --- android/app/src/main/AndroidManifest.xml | 4 + ios/Podfile.lock | 6 + package-lock.json | 496 ++++++++++++----------- 3 files changed, 278 insertions(+), 228 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5c8bfba9..d79cd795 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,6 +29,10 @@ + + + + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 254adc46..b5564b99 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): @@ -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 diff --git a/package-lock.json b/package-lock.json index 7ac832e4..9008fab9 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" } @@ -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": { @@ -5386,7 +5400,7 @@ } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "optional": true }, @@ -5411,7 +5425,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "optional": true, "requires": { @@ -5434,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": { @@ -5462,7 +5476,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "optional": true, "requires": { @@ -5488,7 +5502,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "optional": true, "requires": { @@ -5505,7 +5519,7 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, "optional": true }, @@ -5541,7 +5555,7 @@ "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "optional": true, "requires": { @@ -5550,11 +5564,11 @@ } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { @@ -5566,22 +5580,22 @@ } }, "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": { @@ -5594,7 +5608,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -5607,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": { @@ -5674,7 +5696,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "optional": true }, @@ -5711,7 +5733,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "optional": true, "requires": { @@ -5734,7 +5756,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "optional": true }, @@ -5780,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": { @@ -5812,7 +5834,7 @@ "optional": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, "optional": true } @@ -5981,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": { @@ -6355,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", @@ -6386,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", @@ -6527,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": { @@ -6539,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", @@ -7802,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==" } } }, @@ -9100,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" }, @@ -9207,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": { @@ -9237,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": { @@ -9289,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" } @@ -9905,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": { @@ -10083,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": { @@ -10455,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": { @@ -10968,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", @@ -11153,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", @@ -11560,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": { @@ -11593,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" }, @@ -11720,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" } @@ -11859,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" } @@ -12230,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", @@ -12503,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" @@ -12924,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", @@ -13453,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", From 82a38489d1c534c44f09371ce4e324daa3a65e38 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 09:03:03 -0600 Subject: [PATCH 11/68] ADD: Import/Export PSBT --- android/app/src/main/AndroidManifest.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d79cd795..5c8bfba9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,10 +29,6 @@ - - - - From 866dfc94ddaf3b37adbf341cb7c046afa91601df Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 10:52:59 -0600 Subject: [PATCH 12/68] Update watch-only-wallet.js --- class/watch-only-wallet.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index 96c56c4e..9c00c07b 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -44,8 +44,22 @@ export class WatchOnlyWallet extends LegacyWallet { try { bitcoin.address.toOutputScript(this.getAddress()); return true; - } catch (e) { - return false; + } catch (_e) { + try { + const parsedSecret = JSON.parse(this.secret); + if (parsedSecret.keystore.xpub) { + let masterFingerprint = false; + if (parsedSecret.keystore.ckcc_xfp) { + // It is a ColdCard Hardware Wallet + masterFingerprint = parsedSecret.keystore.ckcc_xfp; + } + this.setSecret(parsedSecret.keystore.xpub); + this.masterFingerprint = masterFingerprint; + } + return true; + } catch (_e) { + return false; + } } } From 230ab1d2bfac58aa457abc7c0e862245b741c332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Thu, 2 Jan 2020 13:43:15 -0600 Subject: [PATCH 13/68] Update import.js --- screen/wallets/import.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen/wallets/import.js b/screen/wallets/import.js index f2548721..ee598321 100644 --- a/screen/wallets/import.js +++ b/screen/wallets/import.js @@ -108,7 +108,7 @@ const WalletsImport = () => { onPress={importButtonPressed} /> { navigate('ScanQrAddress', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true }); }} From 872bbb6d9da762dc8f922e8ed1eced38a701d828 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 14:00:27 -0600 Subject: [PATCH 14/68] Update details.js --- screen/wallets/details.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/screen/wallets/details.js b/screen/wallets/details.js index db04793c..b550125d 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -73,7 +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); + this.state.wallet.masterFingerprint = String(this.state.masterFingerprint); BlueApp.saveToDisk(); alert('Wallet updated.'); this.props.navigation.goBack(null); @@ -210,15 +210,11 @@ export default class WalletDetails extends Component { placeholder="Master Fingerprint" value={this.state.masterFingerprint} onChangeText={text => { - 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" /> From d11a9456d6811ed86f52fecef7a2747ed56c7eff Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 14:54:34 -0600 Subject: [PATCH 15/68] TST --- class/abstract-hd-electrum-wallet.js | 6 +++--- class/watch-only-wallet.js | 2 +- screen/send/scanQrAddress.js | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/class/abstract-hd-electrum-wallet.js b/class/abstract-hd-electrum-wallet.js index 32f1d851..be757496 100644 --- a/class/abstract-hd-electrum-wallet.js +++ b/class/abstract-hd-electrum-wallet.js @@ -759,7 +759,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let pubkey = this._getPubkeyByAddress(input.address); let masterFingerprintBuffer; if (masterFingerprint) { - const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex'); + const hexBuffer = masterFingerprint; masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); @@ -799,9 +799,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let masterFingerprintBuffer; if (masterFingerprint) { - const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex') + const hexBuffer = Buffer.from(masterFingerprint); masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); - } else { + } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); } diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index 9c00c07b..6f3779da 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -51,7 +51,7 @@ export class WatchOnlyWallet extends LegacyWallet { let masterFingerprint = false; if (parsedSecret.keystore.ckcc_xfp) { // It is a ColdCard Hardware Wallet - masterFingerprint = parsedSecret.keystore.ckcc_xfp; + masterFingerprint = Buffer.from(Number(parsedSecret.keystore.ckcc_xfp).toString(16), 'hex').toString('hex'); } this.setSecret(parsedSecret.keystore.xpub); this.masterFingerprint = masterFingerprint; diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index fa750b5a..f7637973 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -46,7 +46,11 @@ const ScanQRCode = ({ 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 } }); + let masterFingerprint; + if (fileParsed.keystore.ckcc_xfp) { + masterFingerprint = Buffer.from(Number(fileParsed.keystore.ckcc_xfp).toString(16), 'hex').toString('hex'); + } + onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint } }); } else { throw new Error(); } From b79a974863b8cc1474dd8f58b542d5b22c05e875 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 15:29:23 -0600 Subject: [PATCH 16/68] TST --- class/abstract-hd-electrum-wallet.js | 6 ++---- class/watch-only-wallet.js | 4 +++- screen/send/scanQrAddress.js | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/class/abstract-hd-electrum-wallet.js b/class/abstract-hd-electrum-wallet.js index be757496..04371cd5 100644 --- a/class/abstract-hd-electrum-wallet.js +++ b/class/abstract-hd-electrum-wallet.js @@ -759,8 +759,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let pubkey = this._getPubkeyByAddress(input.address); let masterFingerprintBuffer; if (masterFingerprint) { - const hexBuffer = masterFingerprint; - masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); + masterFingerprintBuffer = Buffer.from(masterFingerprint, 'hex'); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); } @@ -799,8 +798,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let masterFingerprintBuffer; if (masterFingerprint) { - const hexBuffer = Buffer.from(masterFingerprint); - masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); + masterFingerprintBuffer = Buffer.from(masterFingerprint, 'hex'); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); } diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index 6f3779da..88616a38 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -51,7 +51,9 @@ export class WatchOnlyWallet extends LegacyWallet { let masterFingerprint = false; if (parsedSecret.keystore.ckcc_xfp) { // It is a ColdCard Hardware Wallet - masterFingerprint = Buffer.from(Number(parsedSecret.keystore.ckcc_xfp).toString(16), 'hex').toString('hex'); + masterFingerprint = Buffer.from(Number(parsedSecret.keystore.ckcc_xfp).toString(16), 'hex') + .reverse() + .toString('hex'); } this.setSecret(parsedSecret.keystore.xpub); this.masterFingerprint = masterFingerprint; diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index f7637973..ebd89bdb 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -48,7 +48,9 @@ const ScanQRCode = ({ if (fileParsed.keystore.xpub) { let masterFingerprint; if (fileParsed.keystore.ckcc_xfp) { - masterFingerprint = Buffer.from(Number(fileParsed.keystore.ckcc_xfp).toString(16), 'hex').toString('hex'); + masterFingerprint = Buffer.from(Number(fileParsed.keystore.ckcc_xfp).toString(16), 'hex') + .reverse() + .toString('hex'); } onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint } }); } else { From c54ef7d067eeaeee61805fd9b8b6ad4dc7416141 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 16:06:51 -0600 Subject: [PATCH 17/68] Update details.js --- screen/wallets/details.js | 368 +++++++++++++++++++------------------- 1 file changed, 187 insertions(+), 181 deletions(-) diff --git a/screen/wallets/details.js b/screen/wallets/details.js index b550125d..e0fd16e4 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -6,6 +6,7 @@ import { Text, TextInput, Alert, + KeyboardAvoidingView, TouchableOpacity, Keyboard, TouchableWithoutFeedback, @@ -21,11 +22,11 @@ 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'); +const EV = require('../../events'); +const prompt = require('../../prompt'); /** @type {AppStorage} */ -let BlueApp = require('../../BlueApp'); -let loc = require('../../loc'); +const BlueApp = require('../../BlueApp'); +const loc = require('../../loc'); export default class WalletDetails extends Component { static navigationOptions = ({ navigation }) => ({ @@ -125,201 +126,206 @@ export default class WalletDetails extends Component { return ( - - - {(() => { - if (this.state.wallet.getAddress()) { - return ( - - - {loc.wallets.details.address.toLowerCase()} - - {this.state.wallet.getAddress()} - - ); - } - })()} - - {loc.wallets.add.wallet_name.toLowerCase()} - - - - { - 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()} - - {this.state.wallet.typeReadable} - {this.state.wallet.type === LightningCustodianWallet.type && ( - - {'connected to'} - {this.state.wallet.getBaseURI()} - - )} - - - {this.state.wallet.type === WatchOnlyWallet.type && this.state.wallet.getSecret().startsWith('zpub') && ( - <> - {'advanced'} - Master Fingerprint - - - { - this.setState({ masterFingerprint: text }); - }} - numberOfLines={1} - style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} - editable={!this.state.isLoading} - underlineColorAndroid="transparent" - /> - - - - {'Use with hardware wallet'} - this.onUseWithHardwareWalletSwitch(value)} /> - - - - )} - - - this.props.navigation.navigate('WalletExport', { - address: this.state.wallet.getAddress(), - secret: this.state.wallet.getSecret(), - }) + + + + {(() => { + if (this.state.wallet.getAddress()) { + return ( + + + {loc.wallets.details.address.toLowerCase()} + + {this.state.wallet.getAddress()} + + ); } - title={loc.wallets.details.export_backup} - /> + })()} + + {loc.wallets.add.wallet_name.toLowerCase()} + + + { + 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" + /> + - - {(this.state.wallet.type === HDLegacyBreadwalletWallet.type || - this.state.wallet.type === HDLegacyP2PKHWallet.type || - this.state.wallet.type === HDSegwitBech32Wallet.type || - this.state.wallet.type === HDSegwitP2SHWallet.type) && ( + + {loc.wallets.details.type.toLowerCase()} + + {this.state.wallet.typeReadable} + {this.state.wallet.type === LightningCustodianWallet.type && ( - - this.props.navigation.navigate('WalletXpub', { - secret: this.state.wallet.getSecret(), - }) - } - title={loc.wallets.details.show_xpub} - /> - - + {'connected to'} + {this.state.wallet.getBaseURI()} )} + + + {this.state.wallet.type === WatchOnlyWallet.type && this.state.wallet.getSecret().startsWith('zpub') && ( + <> + {'advanced'} + Master Fingerprint + + + { + this.setState({ masterFingerprint: text }); + }} + numberOfLines={1} + style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} + editable={!this.state.isLoading} + underlineColorAndroid="transparent" + /> + + + + {'Use with hardware wallet'} + this.onUseWithHardwareWalletSwitch(value)} + /> + + + + )} - {this.state.wallet.type !== LightningCustodianWallet.type && ( - this.props.navigation.navigate('BuyBitcoin', { + this.props.navigation.navigate('WalletExport', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret(), }) } - title={loc.wallets.details.buy_bitcoin} + title={loc.wallets.details.export_backup} /> - )} - - { - ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false }); - Alert.alert( - loc.wallets.details.delete + ' ' + loc.wallets.details.title, - loc.wallets.details.are_you_sure, - [ - { - text: loc.wallets.details.yes_delete, - onPress: async () => { - const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); + - if (isBiometricsEnabled) { - if (!(await Biometric.unlockWithBiometrics())) { - return; + {(this.state.wallet.type === HDLegacyBreadwalletWallet.type || + this.state.wallet.type === HDLegacyP2PKHWallet.type || + this.state.wallet.type === HDSegwitBech32Wallet.type || + this.state.wallet.type === HDSegwitP2SHWallet.type) && ( + + + this.props.navigation.navigate('WalletXpub', { + secret: this.state.wallet.getSecret(), + }) + } + title={loc.wallets.details.show_xpub} + /> + + + + )} + + {this.state.wallet.type !== LightningCustodianWallet.type && ( + + this.props.navigation.navigate('BuyBitcoin', { + address: this.state.wallet.getAddress(), + secret: this.state.wallet.getSecret(), + }) + } + title={loc.wallets.details.buy_bitcoin} + /> + )} + + + { + ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false }); + Alert.alert( + loc.wallets.details.delete + ' ' + loc.wallets.details.title, + loc.wallets.details.are_you_sure, + [ + { + text: loc.wallets.details.yes_delete, + onPress: async () => { + const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled(); + + if (isBiometricsEnabled) { + if (!(await Biometric.unlockWithBiometrics())) { + return; + } + } + if (this.state.wallet.getBalance() > 0) { + this.presentWalletHasBalanceAlert(); + } else { + this.props.navigation.setParams({ isLoading: true }); + this.setState({ isLoading: true }, async () => { + BlueApp.deleteWallet(this.state.wallet); + ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); + await BlueApp.saveToDisk(); + EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); + EV(EV.enum.WALLETS_COUNT_CHANGED); + this.props.navigation.navigate('Wallets'); + }); } - } - if (this.state.wallet.getBalance() > 0) { - this.presentWalletHasBalanceAlert(); - } else { - this.props.navigation.setParams({ isLoading: true }); - this.setState({ isLoading: true }, async () => { - BlueApp.deleteWallet(this.state.wallet); - ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); - await BlueApp.saveToDisk(); - EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); - EV(EV.enum.WALLETS_COUNT_CHANGED); - this.props.navigation.navigate('Wallets'); - }); - } + }, + style: 'destructive', }, - style: 'destructive', - }, - { text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' }, - ], - { cancelable: false }, - ); - }} - > - {loc.wallets.details.delete} - - - - + { text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' }, + ], + { cancelable: false }, + ); + }} + > + {loc.wallets.details.delete} + + + + + ); From 338481a3def739a7bb500f1f62a1e1433c8c90e2 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Thu, 2 Jan 2020 22:02:41 -0600 Subject: [PATCH 18/68] TST --- class/abstract-hd-electrum-wallet.js | 6 ++- class/deeplinkSchemaMatch.js | 21 ++++++++++ class/watch-only-wallet.js | 4 +- ios/BlueWallet/BlueWalletRelease.entitlements | 2 + screen/send/details.js | 6 +-- screen/send/psbtWithHardwareWallet.js | 38 +++++++++++++------ screen/send/scanQrAddress.js | 4 +- screen/wallets/details.js | 32 +--------------- 8 files changed, 60 insertions(+), 53 deletions(-) diff --git a/class/abstract-hd-electrum-wallet.js b/class/abstract-hd-electrum-wallet.js index 04371cd5..0d8f287b 100644 --- a/class/abstract-hd-electrum-wallet.js +++ b/class/abstract-hd-electrum-wallet.js @@ -759,7 +759,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let pubkey = this._getPubkeyByAddress(input.address); let masterFingerprintBuffer; if (masterFingerprint) { - masterFingerprintBuffer = Buffer.from(masterFingerprint, 'hex'); + const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex'); + masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); } @@ -798,7 +799,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let masterFingerprintBuffer; if (masterFingerprint) { - masterFingerprintBuffer = Buffer.from(masterFingerprint, 'hex'); + const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex'); + masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); } diff --git a/class/deeplinkSchemaMatch.js b/class/deeplinkSchemaMatch.js index 194b0c44..1b452871 100644 --- a/class/deeplinkSchemaMatch.js +++ b/class/deeplinkSchemaMatch.js @@ -1,6 +1,8 @@ import { AppStorage, LightningCustodianWallet } from './'; import AsyncStorage from '@react-native-community/async-storage'; import BitcoinBIP70TransactionDecode from '../bip70/bip70'; +import RNFS from 'react-native-fs'; +import url from 'url'; const bitcoin = require('bitcoinjs-lib'); const BlueApp = require('../BlueApp'); class DeeplinkSchemaMatch { @@ -30,6 +32,21 @@ class DeeplinkSchemaMatch { if (typeof event.url !== 'string') { return; } + if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) { + RNFS.readFile(event.url) + .then(file => { + if (file) { + completionHandler({ + routeName: 'PsbtWithHardwareWallet', + params: { + deepLinkPSBT: file, + }, + }); + } + }) + .catch(e => console.warn(e)); + return; + } let isBothBitcoinAndLightning; try { isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(event.url); @@ -146,6 +163,10 @@ class DeeplinkSchemaMatch { } } + static isPossiblyPSBTFile(filePath) { + return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('-signed.psbt'); + } + static isBitcoinAddress(address) { address = address .replace('bitcoin:', '') diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index 88616a38..868d6405 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -51,9 +51,7 @@ export class WatchOnlyWallet extends LegacyWallet { let masterFingerprint = false; if (parsedSecret.keystore.ckcc_xfp) { // It is a ColdCard Hardware Wallet - masterFingerprint = Buffer.from(Number(parsedSecret.keystore.ckcc_xfp).toString(16), 'hex') - .reverse() - .toString('hex'); + masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp); } this.setSecret(parsedSecret.keystore.xpub); this.masterFingerprint = masterFingerprint; diff --git a/ios/BlueWallet/BlueWalletRelease.entitlements b/ios/BlueWallet/BlueWalletRelease.entitlements index 16a6e901..51aaf8ba 100644 --- a/ios/BlueWallet/BlueWalletRelease.entitlements +++ b/ios/BlueWallet/BlueWalletRelease.entitlements @@ -10,6 +10,8 @@ com.apple.developer.ubiquity-container-identifiers + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.application-groups group.io.bluewallet.bluewallet diff --git a/screen/send/details.js b/screen/send/details.js index 3b2e07a8..aa54fa96 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -711,10 +711,11 @@ export default class SendDetails extends Component { importTransaction = async () => { try { - const res = await DocumentPicker.pick(); + const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt'] }); const file = await RNFS.readFile(res.uri, 'ascii'); const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); if (bufferDecoded) { + this.setState({ isAdvancedTransactionOptionsVisible: false }); 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 @@ -722,7 +723,7 @@ export default class SendDetails extends Component { this.props.navigation.navigate('PsbtWithHardwareWallet', { memo: this.state.memo, fromWallet: this.state.fromWallet, - psbt: bufferDecoded, + psbt: file, isFirstPSBTAlreadyBase64: true, }); this.setState({ isLoading: false }); @@ -736,7 +737,6 @@ export default class SendDetails extends Component { alert('The selected file does not contain a signed transaction that can be imported.'); } } - this.setState({ isAdvancedTransactionOptionsVisible: false }); }; renderAdvancedTransactionOptionsModal = () => { diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 8ac8c02f..c848a142 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -51,10 +51,7 @@ export default class PsbtWithHardwareWallet extends Component { this.setState({ renderScanner: false }, () => { console.log(ret.data); try { - let Tx = this.state.fromWallet.combinePsbt( - this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), - this.state.isSecondPSBTAlreadyBase64 ? ret.data : ret.data.toBase64(), - ); + let Tx = this.state.fromWallet.combinePsbt(this.state.psbt, ret.data); this.setState({ txhex: Tx.toHex() }); } catch (Err) { alert(Err); @@ -73,9 +70,29 @@ export default class PsbtWithHardwareWallet extends Component { fromWallet: props.navigation.getParam('fromWallet'), isFirstPSBTAlreadyBase64: props.navigation.getParam('isFirstPSBTAlreadyBase64'), isSecondPSBTAlreadyBase64: false, + deepLinkPSBT: undefined, }; } + static getDerivedStateFromProps(nextProps, prevState) { + const deepLinkPSBT = nextProps.navigation.state.params.deepLinkPSBT; + if (deepLinkPSBT) { + try { + let Tx = prevState.fromWallet.combinePsbt( + prevState.isFirstPSBTAlreadyBase64 ? prevState.psbt : prevState.psbt.toBase64(), + deepLinkPSBT, + ); + return { + ...prevState, + txhex: Tx.toHex(), + }; + } catch (Err) { + alert(Err); + } + } + return prevState; + } + componentDidMount() { console.log('send/psbtWithHardwareWallet - componentDidMount'); } @@ -207,8 +224,8 @@ export default class PsbtWithHardwareWallet extends Component { exportPSBT = async () => { const fileName = `${Date.now()}.psbt`; 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'); + const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`; + await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()); Share.open({ url: 'file://' + filePath, }) @@ -242,11 +259,10 @@ export default class PsbtWithHardwareWallet extends Component { 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 })); + const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt'] }); + const file = await RNFS.readFile(res.uri); + if (file) { + this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarCodeRead({ data: file })); } else { this.setState({ isSecondPSBTAlreadyBase64: false }); throw new Error(); diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index ebd89bdb..04ed4701 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -48,9 +48,7 @@ const ScanQRCode = ({ if (fileParsed.keystore.xpub) { let masterFingerprint; if (fileParsed.keystore.ckcc_xfp) { - masterFingerprint = Buffer.from(Number(fileParsed.keystore.ckcc_xfp).toString(16), 'hex') - .reverse() - .toString('hex'); + masterFingerprint = Number(fileParsed.keystore.ckcc_xfp); } onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint } }); } else { diff --git a/screen/wallets/details.js b/screen/wallets/details.js index e0fd16e4..ce0f5e2d 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -51,13 +51,13 @@ export default class WalletDetails extends Component { super(props); const wallet = props.navigation.getParam('wallet'); + console.warn(wallet.masterFingerprint) const isLoading = true; this.state = { isLoading, walletName: wallet.getLabel(), wallet, useWithHardwareWallet: !!wallet.use_with_hardware_wallet, - masterFingerprint: wallet.masterFingerprint ? String(wallet.masterFingerprint) : '', }; this.props.navigation.setParams({ isLoading, saveAction: () => this.setLabel() }); } @@ -74,7 +74,6 @@ 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 = String(this.state.masterFingerprint); BlueApp.saveToDisk(); alert('Wallet updated.'); this.props.navigation.goBack(null); @@ -192,35 +191,6 @@ export default class WalletDetails extends Component { {this.state.wallet.type === WatchOnlyWallet.type && this.state.wallet.getSecret().startsWith('zpub') && ( <> {'advanced'} - Master Fingerprint - - - { - this.setState({ masterFingerprint: text }); - }} - numberOfLines={1} - style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} - editable={!this.state.isLoading} - underlineColorAndroid="transparent" - /> - - {'Use with hardware wallet'} Date: Fri, 3 Jan 2020 21:12:29 -0600 Subject: [PATCH 19/68] ADD: Import txn --- class/deeplinkSchemaMatch.js | 4 ++ ios/BlueWallet.xcodeproj/project.pbxproj | 6 +-- ios/BlueWallet/Info.plist | 56 ++++++++++++++++++++++-- package-lock.json | 4 ++ screen/send/details.js | 49 +++++++++++++-------- screen/send/psbtWithHardwareWallet.js | 9 +++- 6 files changed, 102 insertions(+), 26 deletions(-) diff --git a/class/deeplinkSchemaMatch.js b/class/deeplinkSchemaMatch.js index 1b452871..47a952f0 100644 --- a/class/deeplinkSchemaMatch.js +++ b/class/deeplinkSchemaMatch.js @@ -163,6 +163,10 @@ class DeeplinkSchemaMatch { } } + static isTXNFile(filePath) { + return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('.txn'); + } + static isPossiblyPSBTFile(filePath) { return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('-signed.psbt'); } diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 93f3854b..78e8ee35 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -683,7 +683,7 @@ attributes = { LastSwiftUpdateCheck = 1120; LastUpgradeCheck = 1020; - ORGANIZATIONNAME = Facebook; + ORGANIZATIONNAME = BlueWallet; TargetAttributes = { 00E356ED1AD99517003FC87E = { CreatedOnToolsVersion = 6.2; @@ -1236,7 +1236,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = A7W54YZ4WU; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = BlueWallet/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -1272,7 +1272,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = A7W54YZ4WU; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = BlueWallet/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index ec572097..125b8905 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -2,8 +2,6 @@ - LSSupportsOpeningDocumentsInPlace - CFBundleDevelopmentRegion en CFBundleDisplayName @@ -24,6 +22,20 @@ io.bluewallet.psbt + + CFBundleTypeIconFiles + + CFBundleTypeName + TXN + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSItemContentTypes + + io.bluewallet.psbt.txn + + CFBundleExecutable $(EXECUTABLE_NAME) @@ -60,6 +72,8 @@ LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + NSAppTransportSecurity NSAllowsArbitraryLoads @@ -80,7 +94,7 @@ 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. + In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. NSFaceIDUsageDescription In order to confirm your identity, we need your permission to use FaceID. NSLocationAlwaysUsageDescription @@ -158,6 +172,25 @@ + + UTTypeConformsTo + + public.data + + UTTypeDescription + PSBT TXN + UTTypeIconFiles + + UTTypeIdentifier + io.bluewallet.psbt.txn + UTTypeTagSpecification + + public.filename-extension + + txn + + + UTImportedTypeDeclarations @@ -180,6 +213,23 @@ + + UTTypeConformsTo + + public.data + + UTTypeDescription + PSBT TXN + UTTypeIdentifier + io.bluewallet.psbt.txn + UTTypeTagSpecification + + public.filename-extension + + txn + + + diff --git a/package-lock.json b/package-lock.json index 7fb5be5d..ffca03ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10996,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-directory-picker": { + "version": "git+https://github.com/BlueWallet/react-native-directory-picker.git#63307e646f72444ab83b619e579c55ee38cd162a", + "from": "git+https://github.com/BlueWallet/react-native-directory-picker.git#63307e646f72444ab83b619e579c55ee38cd162a" + }, "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" diff --git a/screen/send/details.js b/screen/send/details.js index aa54fa96..8c8844cc 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -40,6 +40,7 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { BitcoinTransaction } from '../../models/bitcoinTransactionInfo'; import DocumentPicker from 'react-native-document-picker'; import RNFS from 'react-native-fs'; +import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch'; const bitcoin = require('bitcoinjs-lib'); const bip21 = require('bip21'); let BigNumber = require('bignumber.js'); @@ -711,26 +712,36 @@ export default class SendDetails extends Component { importTransaction = async () => { try { - const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt'] }); - const file = await RNFS.readFile(res.uri, 'ascii'); - const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); - if (bufferDecoded) { - this.setState({ isAdvancedTransactionOptionsVisible: false }); - 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: file, - isFirstPSBTAlreadyBase64: true, - }); - this.setState({ isLoading: false }); - return; + const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] }); + if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) { + 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: file, + isFirstPSBTAlreadyBase64: true, + }); + this.setState({ isLoading: false }); + return; + } + } else { + throw new Error(); } - } else { - throw new Error(); + } else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) { + const file = await RNFS.readFile(res.uri, 'ascii'); + this.props.navigation.navigate('PsbtWithHardwareWallet', { + memo: this.state.memo, + fromWallet: this.state.fromWallet, + txhex: file, + }); + this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false }); + return; } } catch (err) { if (!DocumentPicker.isCancel(err)) { diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index c848a142..c9445d34 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -71,11 +71,13 @@ export default class PsbtWithHardwareWallet extends Component { isFirstPSBTAlreadyBase64: props.navigation.getParam('isFirstPSBTAlreadyBase64'), isSecondPSBTAlreadyBase64: false, deepLinkPSBT: undefined, + txhex: props.navigation.getParam('txhex') || undefined, }; } static getDerivedStateFromProps(nextProps, prevState) { const deepLinkPSBT = nextProps.navigation.state.params.deepLinkPSBT; + const txhex = nextProps.navigation.state.params.txhex; if (deepLinkPSBT) { try { let Tx = prevState.fromWallet.combinePsbt( @@ -89,6 +91,11 @@ export default class PsbtWithHardwareWallet extends Component { } catch (Err) { alert(Err); } + } else if (txhex) { + return { + ...prevState, + txhex: txhex, + }; } return prevState; } @@ -259,7 +266,7 @@ export default class PsbtWithHardwareWallet extends Component { openSignedTransaction = async () => { try { - const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt'] }); + const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] }); const file = await RNFS.readFile(res.uri); if (file) { this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarCodeRead({ data: file })); From cf1153b546e67d86408579bd102b5a2fb6e947cc Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Fri, 3 Jan 2020 23:02:30 -0600 Subject: [PATCH 20/68] ADD: Export TXN --- screen/send/create.js | 50 +++++++++++++++++-- screen/send/details.js | 3 +- screen/wallets/import.js | 2 +- tests/integration/deepLinkSchemaMatch.test.js | 2 +- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/screen/send/create.js b/screen/send/create.js index 7fb67d98..1d9d0e92 100644 --- a/screen/send/create.js +++ b/screen/send/create.js @@ -1,3 +1,4 @@ +/* global alert */ import React, { Component } from 'react'; import { TextInput, @@ -11,26 +12,36 @@ import { Keyboard, Text, View, + Platform, } from 'react-native'; import { BlueNavigationStyle, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents'; import PropTypes from 'prop-types'; import Privacy from '../../Privacy'; import { BitcoinUnit } from '../../models/bitcoinUnits'; +import { Icon } from 'react-native-elements'; +import Share from 'react-native-share'; +import RNFS from 'react-native-fs'; +import DirectoryPickerManager from 'react-native-directory-picker'; /** @type {AppStorage} */ const BlueApp = require('../../BlueApp'); const loc = require('../../loc'); const currency = require('../../currency'); export default class SendCreate extends Component { - static navigationOptions = () => ({ + static navigationOptions = ({ navigation }) => ({ ...BlueNavigationStyle, title: loc.send.create.details, + headerRight: navigation.state.params.exportTXN ? ( + + + + ) : null, }); constructor(props) { super(props); console.log('send/create constructor'); - + props.navigation.setParams({ exportTXN: this.exportTXN }); this.state = { isLoading: false, fee: props.navigation.getParam('fee'), @@ -44,11 +55,43 @@ export default class SendCreate extends Component { }; } - async componentDidMount() { + componentDidMount() { Privacy.enableBlur(); console.log('send/create - componentDidMount'); } + exportTXN = async () => { + const fileName = `${Date.now()}.txn`; + if (Platform.OS === 'ios') { + const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`; + await RNFS.writeFile(filePath, this.state.tx); + Share.open({ + url: 'file://' + filePath, + }) + .catch(error => console.log(error)) + .finally(() => { + RNFS.unlink(filePath); + }); + } else if (Platform.OS === 'android') { + DirectoryPickerManager.showDirectoryPicker(null, async response => { + if (response.didCancel) { + console.log('User cancelled directory picker'); + } else if (response.error) { + console.log('DirectoryPickerManager Error: ', response.error); + } else { + try { + await RNFS.writeFile(response.path + `/${fileName}`, this.state.tx, 'ascii'); + alert('Successfully exported.'); + RNFS.unlink(response.path + `/${fileName}`); + } catch (e) { + console.log(e); + alert(e); + } + } + }); + } + }; + componentWillUnmount() { Privacy.disableBlur(); } @@ -164,6 +207,7 @@ const styles = StyleSheet.create({ SendCreate.propTypes = { navigation: PropTypes.shape({ goBack: PropTypes.func, + setParams: PropTypes.func, getParam: PropTypes.func, navigate: PropTypes.func, dismiss: PropTypes.func, diff --git a/screen/send/details.js b/screen/send/details.js index 8c8844cc..52b68602 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -781,7 +781,8 @@ export default class SendDetails extends Component { onSwitch={this.onReplaceableFeeSwitchValueChanged} /> )} - {this.state.fromWallet.use_with_hardware_wallet && ( + {(this.state.fromWallet.type === HDSegwitBech32Wallet.type || + this.state.fromWallet._hdWalletInstance instanceof HDSegwitBech32Wallet) && ( )} {this.state.fromWallet.allowBatchSend() && ( diff --git a/screen/wallets/import.js b/screen/wallets/import.js index ee598321..6bb98e73 100644 --- a/screen/wallets/import.js +++ b/screen/wallets/import.js @@ -108,7 +108,7 @@ const WalletsImport = () => { onPress={importButtonPressed} /> { navigate('ScanQrAddress', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true }); }} diff --git a/tests/integration/deepLinkSchemaMatch.test.js b/tests/integration/deepLinkSchemaMatch.test.js index f852b56c..0c2d6194 100644 --- a/tests/integration/deepLinkSchemaMatch.test.js +++ b/tests/integration/deepLinkSchemaMatch.test.js @@ -1,4 +1,4 @@ -/* global describe, it, expect */ +/* global describe, it */ import DeeplinkSchemaMatch from '../../class/deeplinkSchemaMatch'; const assert = require('assert'); From ee5cb6da45cfc2a40e89c4a14175e65d3bbcec7c Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Fri, 3 Jan 2020 23:08:27 -0600 Subject: [PATCH 21/68] TST --- loc/en.js | 2 +- loc/fr_FR.js | 2 +- loc/vi_VN.js | 2 +- screen/transactions/RBF-create.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/loc/en.js b/loc/en.js index 897572d8..009d0c70 100644 --- a/loc/en.js +++ b/loc/en.js @@ -145,7 +145,7 @@ module.exports = { title: 'create transaction', error: 'Error creating transaction. Invalid address or send amount?', go_back: 'Go Back', - this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.', + this_is_hex: `This is your transaction's hex, signed and ready to be broadcasted to the network.`, to: 'To', amount: 'Amount', fee: 'Fee', diff --git a/loc/fr_FR.js b/loc/fr_FR.js index 9084827b..6d4c58cd 100644 --- a/loc/fr_FR.js +++ b/loc/fr_FR.js @@ -146,7 +146,7 @@ module.exports = { title: 'créer une transaction', error: 'Erreur creating transaction. Invalid address or send amount?', go_back: 'Retour', - this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.', + this_is_hex: `This is your transaction's hex, signed and ready to be broadcasted to the network.`, to: 'À', amount: 'Montant', fee: 'Frais', diff --git a/loc/vi_VN.js b/loc/vi_VN.js index a27162b1..1c50cd23 100644 --- a/loc/vi_VN.js +++ b/loc/vi_VN.js @@ -145,7 +145,7 @@ module.exports = { title: 'create transaction', error: 'Error creating transaction. Invalid address or send amount?', go_back: 'Go Back', - this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.', + this_is_hex: `This is your transaction's hex, signed and ready to be broadcasted to the network.`, to: 'To', amount: 'Amount', fee: 'Fee', diff --git a/screen/transactions/RBF-create.js b/screen/transactions/RBF-create.js index 8f23e9cc..4d3bf6a9 100644 --- a/screen/transactions/RBF-create.js +++ b/screen/transactions/RBF-create.js @@ -203,7 +203,7 @@ export default class SendCreate extends Component { - This is transaction hex, signed and ready to be broadcast to the network. Continue? + This is your transaction's hex, signed and ready to be broadcasted to the network. Continue? Date: Sat, 4 Jan 2020 00:38:32 -0600 Subject: [PATCH 22/68] Update Info.plist --- ios/BlueWallet/Info.plist | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index 125b8905..10ef2c3f 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -159,7 +159,7 @@ public.data UTTypeDescription - PSBT + Partially Signed Bitcoin Transaction UTTypeIconFiles UTTypeIdentifier @@ -178,7 +178,7 @@ public.data UTTypeDescription - PSBT TXN + Bitcoin Transaction UTTypeIconFiles UTTypeIdentifier @@ -200,7 +200,7 @@ public.data UTTypeDescription - PSBT + Partially Signed Bitcoin Transaction UTTypeIconFiles UTTypeIdentifier @@ -219,7 +219,9 @@ public.data UTTypeDescription - PSBT TXN + Bitcoin Transaction + UTTypeIconFiles + UTTypeIdentifier io.bluewallet.psbt.txn UTTypeTagSpecification From 7c120d2b20acde607760242471a1d7c1a2019307 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Sat, 4 Jan 2020 19:03:02 -0600 Subject: [PATCH 23/68] RTST --- screen/wallets/details.js | 3 ++- screen/wallets/transactions.js | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/screen/wallets/details.js b/screen/wallets/details.js index ce0f5e2d..1cc2b625 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -166,7 +166,8 @@ export default class WalletDetails extends Component { }} onBlur={() => { if (this.state.walletName.trim().length === 0) { - this.setState({ walletName: this.state.wallet.getLabel() }); + const walletLabel = this.state.wallet.getLabel(); + this.setState({ walletName: walletLabel }); } }} numberOfLines={1} diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 289623c6..c60ebd42 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -601,10 +601,13 @@ export default class WalletTransactions extends Component { [ { text: loc._.ok, - onPress: async () => { - this.state.wallet.use_with_hardware_wallet = true; - await BlueApp.saveToDisk(); - this.navigateToSendScreen(); + onPress: () => { + const wallet = this.state.wallet; + wallet.use_with_hardware_wallet = true + this.setState({ wallet }, async () => { + await BlueApp.saveToDisk(); + this.navigateToSendScreen(); + }) }, style: 'default', }, From c4aa788cdfbd91bab997686cc8e4470edf0bf1e2 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Mon, 6 Jan 2020 05:21:19 -0500 Subject: [PATCH 24/68] TTS --- screen/send/create.js | 3 +-- screen/send/psbtWithHardwareWallet.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/screen/send/create.js b/screen/send/create.js index 1d9d0e92..82c0ce29 100644 --- a/screen/send/create.js +++ b/screen/send/create.js @@ -80,9 +80,8 @@ export default class SendCreate extends Component { console.log('DirectoryPickerManager Error: ', response.error); } else { try { - await RNFS.writeFile(response.path + `/${fileName}`, this.state.tx, 'ascii'); + await RNFS.writeFile(response.decodeUri + `/${fileName}`, this.state.tx, 'ascii'); alert('Successfully exported.'); - RNFS.unlink(response.path + `/${fileName}`); } catch (e) { console.log(e); alert(e); diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index c9445d34..2f486eec 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -249,12 +249,11 @@ export default class PsbtWithHardwareWallet extends Component { } else { try { await RNFS.writeFile( - response.path + `/${fileName}`, + response.decodeUri + `/${fileName}`, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii', ); alert('Successfully exported.'); - RNFS.unlink(response.path + `/${fileName}`); } catch (e) { console.log(e); alert(e); From 2827858d041e77f9aeb6dc25332808c335b8abb4 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Mon, 6 Jan 2020 05:31:45 -0500 Subject: [PATCH 25/68] TST --- screen/send/create.js | 2 +- screen/send/psbtWithHardwareWallet.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/screen/send/create.js b/screen/send/create.js index 82c0ce29..92abea9b 100644 --- a/screen/send/create.js +++ b/screen/send/create.js @@ -80,7 +80,7 @@ export default class SendCreate extends Component { console.log('DirectoryPickerManager Error: ', response.error); } else { try { - await RNFS.writeFile(response.decodeUri + `/${fileName}`, this.state.tx, 'ascii'); + await RNFS.writeFile(response.decodedUri + `/${fileName}`, this.state.tx, 'ascii'); alert('Successfully exported.'); } catch (e) { console.log(e); diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 2f486eec..82d4a9c5 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -249,7 +249,7 @@ export default class PsbtWithHardwareWallet extends Component { } else { try { await RNFS.writeFile( - response.decodeUri + `/${fileName}`, + response.decodedUri + `/${fileName}`, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), 'ascii', ); From a35bc42b9a7740760d59df3a5927afaecc3c1521 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Tue, 7 Jan 2020 01:53:32 -0500 Subject: [PATCH 26/68] TSTS --- package-lock.json | 4 -- package.json | 1 - screen/send/create.js | 49 ++++++++++++++------- screen/send/psbtWithHardwareWallet.js | 61 ++++++++++++++++++--------- 4 files changed, 74 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index ffca03ba..7fb5be5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10996,10 +10996,6 @@ "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-directory-picker": { - "version": "git+https://github.com/BlueWallet/react-native-directory-picker.git#63307e646f72444ab83b619e579c55ee38cd162a", - "from": "git+https://github.com/BlueWallet/react-native-directory-picker.git#63307e646f72444ab83b619e579c55ee38cd162a" - }, "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" diff --git a/package.json b/package.json index c465e9df..fb5b8fa4 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "react-native-camera": "3.4.0", "react-native-default-preference": "1.4.1", "react-native-device-info": "4.0.1", - "react-native-directory-picker": "git+https://github.com/BlueWallet/react-native-directory-picker.git#63307e646f72444ab83b619e579c55ee38cd162a", "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", diff --git a/screen/send/create.js b/screen/send/create.js index 92abea9b..3a87812c 100644 --- a/screen/send/create.js +++ b/screen/send/create.js @@ -13,6 +13,7 @@ import { Text, View, Platform, + Alert, } from 'react-native'; import { BlueNavigationStyle, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents'; import PropTypes from 'prop-types'; @@ -21,7 +22,6 @@ import { BitcoinUnit } from '../../models/bitcoinUnits'; import { Icon } from 'react-native-elements'; import Share from 'react-native-share'; import RNFS from 'react-native-fs'; -import DirectoryPickerManager from 'react-native-directory-picker'; /** @type {AppStorage} */ const BlueApp = require('../../BlueApp'); const loc = require('../../loc'); @@ -73,21 +73,38 @@ export default class SendCreate extends Component { RNFS.unlink(filePath); }); } else if (Platform.OS === 'android') { - DirectoryPickerManager.showDirectoryPicker(null, async response => { - if (response.didCancel) { - console.log('User cancelled directory picker'); - } else if (response.error) { - console.log('DirectoryPickerManager Error: ', response.error); - } else { - try { - await RNFS.writeFile(response.decodedUri + `/${fileName}`, this.state.tx, 'ascii'); - alert('Successfully exported.'); - } catch (e) { - console.log(e); - alert(e); - } - } - }); + Alert.alert( + 'Export', + 'Where would you like to export this transaction?', + [ + { + text: 'External Storage', + onPress: async () => { + try { + await RNFS.writeFile('file://' + RNFS.ExternalStorageDirectoryPath + `/${fileName}`, this.state.tx, 'ascii'); + alert('Successfully exported.'); + } catch (e) { + console.log(e); + alert(e); + } + }, + }, + { + text: 'Documents Folder', + onPress: async () => { + try { + await RNFS.writeFile('file://' + RNFS.DocumentDirectoryPath + `/${fileName}`, this.state.tx, 'ascii'); + alert('Successfully exported.'); + } catch (e) { + console.log(e); + alert(e); + } + }, + }, + { text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' }, + ], + { cancelable: true }, + ); } }; diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 82d4a9c5..ad79c6b2 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -7,6 +7,7 @@ import { View, Dimensions, Image, + Alert, TextInput, Clipboard, Linking, @@ -29,7 +30,6 @@ 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'; -import DirectoryPickerManager from 'react-native-directory-picker'; let loc = require('../../loc'); let EV = require('../../events'); let BlueElectrum = require('../../BlueElectrum'); @@ -241,25 +241,46 @@ export default class PsbtWithHardwareWallet extends Component { RNFS.unlink(filePath); }); } else if (Platform.OS === 'android') { - DirectoryPickerManager.showDirectoryPicker(null, async response => { - if (response.didCancel) { - console.log('User cancelled directory picker'); - } else if (response.error) { - console.log('DirectoryPickerManager Error: ', response.error); - } else { - try { - await RNFS.writeFile( - response.decodedUri + `/${fileName}`, - this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), - 'ascii', - ); - alert('Successfully exported.'); - } catch (e) { - console.log(e); - alert(e); - } - } - }); + Alert.alert( + 'Export', + 'Where would you like to export this transaction?', + [ + { + text: 'External Storage', + onPress: async () => { + try { + await RNFS.writeFile( + 'file://' + RNFS.ExternalStorageDirectoryPath + `/${fileName}`, + this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), + 'ascii', + ); + alert('Successfully exported.'); + } catch (e) { + console.log(e); + alert(e); + } + }, + }, + { + text: 'Documents Folder', + onPress: async () => { + try { + await RNFS.writeFile( + 'file://' + RNFS.DocumentDirectoryPath + `/${fileName}`, + this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), + 'ascii', + ); + alert('Successfully exported.'); + } catch (e) { + console.log(e); + alert(e); + } + }, + }, + { text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' }, + ], + { cancelable: true }, + ); } }; From 8c2d2c517573d2c18e3c5698136bdafb20cf5ad0 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sat, 21 Dec 2019 16:44:35 -0300 Subject: [PATCH 27/68] decode invoices locally. --- class/lightning-custodian-wallet.js | 59 +++++++++++++++++------------ package.json | 1 + 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js index f4b0fad5..d93db600 100644 --- a/class/lightning-custodian-wallet.js +++ b/class/lightning-custodian-wallet.js @@ -1,5 +1,6 @@ import { LegacyWallet } from './legacy-wallet'; import Frisbee from 'frisbee'; +import bolt11 from 'bolt11'; import { BitcoinUnit, Chain } from '../models/bitcoinUnits'; export class LightningCustodianWallet extends LegacyWallet { @@ -515,7 +516,7 @@ export class LightningCustodianWallet extends LegacyWallet { * Example return: * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f', * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4', - * num_satoshisnum_satoshis: '100', + * num_satoshis: '100', * timestamp: '1535116657', * expiry: '3600', * description: 'hundredSatoshis blitzhub', @@ -527,31 +528,41 @@ export class LightningCustodianWallet extends LegacyWallet { * @param invoice BOLT invoice string * @return {Promise.} */ - async decodeInvoice(invoice) { - await this.checkLogin(); - - let response = await this._api.get('/decodeinvoice?invoice=' + invoice, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Content-Type': 'application/json', - Authorization: 'Bearer' + ' ' + this.access_token, - }, - }); - - let json = response.body; - if (typeof json === 'undefined') { - throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); - } - - if (json && json.error) { - throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); - } - - if (!json.payment_hash) { - throw new Error('API unexpected response: ' + JSON.stringify(response.body)); + decodeInvoice(invoice) { + let { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); + + var decoded = { + destination: payeeNodeKey, + num_satoshis: satoshis + ? satoshis.toString() + : millisatoshis.toString(), + timestamp: timestamp.toString(), + fallback_addr: '', + route_hints: [] + }; + + for (let i = 0; i < tags.length; i++) { + let {tagName, data} = tags[i]; + switch (tagName) { + case 'payment_hash': + decoded.payment_hash = data + break + case 'purpose_commit_hash': + decoded.description_hash = data + break + case 'min_final_cltv_expiry': + decoded.cltv_expiry = data.toString() + break + case 'expire_time': + decoded.expiry = data.toString() + break + case 'description': + decoded.description = data + break + } } - return (this.decoded_invoice_raw = json); + return (this.decoded_invoice_raw = decoded); } async fetchInfo() { diff --git a/package.json b/package.json index 76619bf9..24e15322 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "bip32": "2.0.3", "bip39": "2.5.0", "bitcoinjs-lib": "5.1.6", + "bolt11": "github:fiatjaf/bolt11#740b0516d99bb6a19f20444d1e0d52d3839f2575", "buffer": "5.2.1", "buffer-reverse": "1.0.1", "coinselect": "3.1.11", From 3c0cffc74a8b5aa9c25b7d5804de4e67af8bc4cd Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 25 Dec 2019 20:44:43 -0300 Subject: [PATCH 28/68] change dependency to bluewallet/bolt11. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 24e15322..708df789 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "bip32": "2.0.3", "bip39": "2.5.0", "bitcoinjs-lib": "5.1.6", - "bolt11": "github:fiatjaf/bolt11#740b0516d99bb6a19f20444d1e0d52d3839f2575", + "bolt11": "github:BlueWallet/bolt11#740b0516d99bb6a19f20444d1e0d52d3839f2575", "buffer": "5.2.1", "buffer-reverse": "1.0.1", "coinselect": "3.1.11", From a4d65faae1edf2121b553397e9eaa24d007d94d7 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Thu, 26 Dec 2019 16:34:35 -0300 Subject: [PATCH 29/68] non await decodeInvoice. --- screen/lnd/scanLndInvoice.js | 2 +- tests/integration/LightningCustodianWallet.test.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js index c10a3952..01889bb9 100644 --- a/screen/lnd/scanLndInvoice.js +++ b/screen/lnd/scanLndInvoice.js @@ -122,7 +122,7 @@ export default class ScanLndInvoice extends React.Component { let w = this.state.fromWallet; let decoded; try { - decoded = await w.decodeInvoice(data); + decoded = w.decodeInvoice(data); let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms if (+new Date() > expiresIn) { diff --git a/tests/integration/LightningCustodianWallet.test.js b/tests/integration/LightningCustodianWallet.test.js index f3e5aee3..7bcec36d 100644 --- a/tests/integration/LightningCustodianWallet.test.js +++ b/tests/integration/LightningCustodianWallet.test.js @@ -100,7 +100,7 @@ describe('LightningCustodianWallet', () => { let invoice = 'lnbc1u1pdcqpt3pp5ltuevvq2g69kdrzcegrs9gfqjer45rwjc0w736qjl92yvwtxhn6qdp8dp6kuerjv4j9xct5daeks6tnyp3xc6t50f582cscqp2zrkghzl535xjav52ns0rpskcn20takzdr2e02wn4xqretlgdemg596acq5qtfqhjk4jpr7jk8qfuuka2k0lfwjsk9mchwhxcgxzj3tsp09gfpy'; - let decoded = await l2.decodeInvoice(invoice); + let decoded = l2.decodeInvoice(invoice); assert.ok(decoded.payment_hash); assert.ok(decoded.description); @@ -112,7 +112,7 @@ describe('LightningCustodianWallet', () => { invoice = 'gsom'; let error = false; try { - await l2.decodeInvoice(invoice); + l2.decodeInvoice(invoice); } catch (Err) { error = true; } @@ -155,7 +155,7 @@ describe('LightningCustodianWallet', () => { await l2.fetchTransactions(); let txLen = l2.transactions_raw.length; - let decoded = await l2.decodeInvoice(invoice); + let decoded = l2.decodeInvoice(invoice); assert.ok(decoded.payment_hash); assert.ok(decoded.description); @@ -336,7 +336,7 @@ describe('LightningCustodianWallet', () => { let oldBalance = +l2.balance; let txLen = l2.transactions_raw.length; - let decoded = await l2.decodeInvoice(invoice); + let decoded = l2.decodeInvoice(invoice); assert.ok(decoded.payment_hash); assert.ok(decoded.description); assert.strictEqual(+decoded.num_satoshis, 0); @@ -443,7 +443,7 @@ describe('LightningCustodianWallet', () => { let oldBalance = +l2.balance; let txLen = l2.transactions_raw.length; - let decoded = await l2.decodeInvoice(invoice); + let decoded = l2.decodeInvoice(invoice); assert.ok(decoded.payment_hash); assert.ok(decoded.description); assert.strictEqual(+decoded.num_satoshis, 0); From b02fe5c8b1cc4827331e08bbde85c0cc7c575e0c Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Fri, 10 Jan 2020 18:15:04 -0300 Subject: [PATCH 30/68] finally update bolt11 to latest official release. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 708df789..73721ac5 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "bip32": "2.0.3", "bip39": "2.5.0", "bitcoinjs-lib": "5.1.6", - "bolt11": "github:BlueWallet/bolt11#740b0516d99bb6a19f20444d1e0d52d3839f2575", + "bolt11": "1.2.7", "buffer": "5.2.1", "buffer-reverse": "1.0.1", "coinselect": "3.1.11", From 8200fc0cf80cf5bb77070b762dddf4f9fe109b32 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Sun, 12 Jan 2020 00:00:33 +0000 Subject: [PATCH 31/68] TST: ln fix --- class/lightning-custodian-wallet.js | 74 +++++++++++++++---- .../LightningCustodianWallet.test.js | 17 +++++ 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js index d93db600..3b11d93d 100644 --- a/class/lightning-custodian-wallet.js +++ b/class/lightning-custodian-wallet.js @@ -531,34 +531,33 @@ export class LightningCustodianWallet extends LegacyWallet { decodeInvoice(invoice) { let { payeeNodeKey, tags, satoshis, millisatoshis, timestamp } = bolt11.decode(invoice); - var decoded = { + let decoded = { destination: payeeNodeKey, - num_satoshis: satoshis - ? satoshis.toString() - : millisatoshis.toString(), + num_satoshis: satoshis ? satoshis.toString() : '0', + num_millisatoshis: millisatoshis ? millisatoshis.toString() : '0', timestamp: timestamp.toString(), fallback_addr: '', - route_hints: [] + route_hints: [], }; for (let i = 0; i < tags.length; i++) { - let {tagName, data} = tags[i]; + let { tagName, data } = tags[i]; switch (tagName) { case 'payment_hash': - decoded.payment_hash = data - break + decoded.payment_hash = data; + break; case 'purpose_commit_hash': - decoded.description_hash = data - break + decoded.description_hash = data; + break; case 'min_final_cltv_expiry': - decoded.cltv_expiry = data.toString() - break + decoded.cltv_expiry = data.toString(); + break; case 'expire_time': - decoded.expiry = data.toString() - break + decoded.expiry = data.toString(); + break; case 'description': - decoded.description = data - break + decoded.description = data; + break; } } @@ -613,6 +612,49 @@ export class LightningCustodianWallet extends LegacyWallet { allowReceive() { return true; } + + /** + * Example return: + * { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f', + * payment_hash: 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4', + * num_satoshis: '100', + * timestamp: '1535116657', + * expiry: '3600', + * description: 'hundredSatoshis blitzhub', + * description_hash: '', + * fallback_addr: '', + * cltv_expiry: '10', + * route_hints: [] } + * + * @param invoice BOLT invoice string + * @return {Promise.} + */ + async decodeInvoiceRemote(invoice) { + await this.checkLogin(); + + let response = await this._api.get('/decodeinvoice?invoice=' + invoice, { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Authorization: 'Bearer' + ' ' + this.access_token, + }, + }); + + let json = response.body; + if (typeof json === 'undefined') { + throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.body)); + } + + if (json && json.error) { + throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); + } + + if (!json.payment_hash) { + throw new Error('API unexpected response: ' + JSON.stringify(response.body)); + } + + return (this.decoded_invoice_raw = json); + } } /* diff --git a/tests/integration/LightningCustodianWallet.test.js b/tests/integration/LightningCustodianWallet.test.js index 7bcec36d..a440078b 100644 --- a/tests/integration/LightningCustodianWallet.test.js +++ b/tests/integration/LightningCustodianWallet.test.js @@ -105,6 +105,7 @@ describe('LightningCustodianWallet', () => { assert.ok(decoded.payment_hash); assert.ok(decoded.description); assert.ok(decoded.num_satoshis); + assert.strictEqual(parseInt(decoded.num_satoshis) * 1000, parseInt(decoded.num_millisatoshis)); await l2.checkRouteInvoice(invoice); @@ -119,6 +120,22 @@ describe('LightningCustodianWallet', () => { assert.ok(error); }); + it('can decode invoice locally & remotely', async () => { + let l2 = new LightningCustodianWallet(); + l2.setSecret(process.env.BLITZHUB); + await l2.authorize(); + let invoice = + 'lnbc1u1pdcqpt3pp5ltuevvq2g69kdrzcegrs9gfqjer45rwjc0w736qjl92yvwtxhn6qdp8dp6kuerjv4j9xct5daeks6tnyp3xc6t50f582cscqp2zrkghzl535xjav52ns0rpskcn20takzdr2e02wn4xqretlgdemg596acq5qtfqhjk4jpr7jk8qfuuka2k0lfwjsk9mchwhxcgxzj3tsp09gfpy'; + let decodedLocally = l2.decodeInvoice(invoice); + let decodedRemotely = await l2.decodeInvoiceRemote(invoice); + assert.strictEqual(decodedLocally.destination, decodedRemotely.destination); + assert.strictEqual(decodedLocally.num_satoshis, decodedRemotely.num_satoshis); + assert.strictEqual(decodedLocally.timestamp, decodedRemotely.timestamp); + assert.strictEqual(decodedLocally.payment_hash, decodedRemotely.payment_hash); + assert.strictEqual(decodedLocally.description, decodedRemotely.description); + assert.strictEqual(decodedLocally.cltv_expiry, decodedRemotely.cltv_expiry); + }); + it('can pay invoice', async () => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; if (!process.env.BLITZHUB) { From 5e85dd30105b35239eb75c7b6c641e11a7bbee37 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Sun, 12 Jan 2020 00:02:24 +0000 Subject: [PATCH 32/68] OPS: more strict linter checks. now CI will redflag even minor codestyle issues. run 'npm run lint:fix' manually before commit --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 73721ac5..ced16b39 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "postinstall": "./node_modules/.bin/rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path --hack; npm run releasenotes2json; npm run podinstall; npx jetify", "test": "npm run unit && npm run jest && npm run lint", "jest": "node node_modules/jest/bin/jest.js tests/integration/*", - "lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ --fix", + "lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/", + "lint:fix": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ --fix", "unit": "./node_modules/.bin/mocha tests/unit/*" }, "jest": { From db1fa07f375b4ba5f40b15a735e956ee4a037f5d Mon Sep 17 00:00:00 2001 From: Overtorment Date: Sun, 12 Jan 2020 12:01:26 +0000 Subject: [PATCH 33/68] REL: ver bump --- android/app/build.gradle | 2 +- ios/BlueWallet/Info.plist | 2 +- ios/BlueWalletWatch Extension/Info.plist | 2 +- ios/BlueWalletWatch/Info.plist | 2 +- ios/TodayExtension/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 37ac1334..79db9d82 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,7 +119,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "4.9.1" + versionName "4.9.2" multiDexEnabled true missingDimensionStrategy 'react-native-camera', 'general' } diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index e1375e79..d59ec647 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.1 + 4.9.2 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/BlueWalletWatch Extension/Info.plist b/ios/BlueWalletWatch Extension/Info.plist index 34dedd1e..d61d66a2 100644 --- a/ios/BlueWalletWatch Extension/Info.plist +++ b/ios/BlueWalletWatch Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.9.1 + 4.9.2 CFBundleVersion 239 CLKComplicationPrincipalClass diff --git a/ios/BlueWalletWatch/Info.plist b/ios/BlueWalletWatch/Info.plist index 925b3b3f..3c7bec49 100644 --- a/ios/BlueWalletWatch/Info.plist +++ b/ios/BlueWalletWatch/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.1 + 4.9.2 CFBundleVersion 239 UISupportedInterfaceOrientations diff --git a/ios/TodayExtension/Info.plist b/ios/TodayExtension/Info.plist index 3fca81fb..555e9756 100644 --- a/ios/TodayExtension/Info.plist +++ b/ios/TodayExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.9.1 + 4.9.2 CFBundleVersion 1 NSExtension diff --git a/package-lock.json b/package-lock.json index 10c1c0ac..a6bf2e9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.1", + "version": "4.9.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ced16b39..0555e7d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.1", + "version": "4.9.2", "devDependencies": { "@babel/core": "^7.5.0", "@babel/runtime": "^7.5.1", From e970cc273be6b9d1a8705facd9b6539efb5d2969 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Sun, 12 Jan 2020 18:08:56 +0000 Subject: [PATCH 34/68] FIX: NaN expiry for ln invoices decoded locally --- class/lightning-custodian-wallet.js | 2 ++ tests/integration/LightningCustodianWallet.test.js | 1 + 2 files changed, 3 insertions(+) diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js index 3b11d93d..14155df8 100644 --- a/class/lightning-custodian-wallet.js +++ b/class/lightning-custodian-wallet.js @@ -561,6 +561,8 @@ export class LightningCustodianWallet extends LegacyWallet { } } + if (!decoded.expiry) decoded.expiry = '3600'; // default + return (this.decoded_invoice_raw = decoded); } diff --git a/tests/integration/LightningCustodianWallet.test.js b/tests/integration/LightningCustodianWallet.test.js index a440078b..ee574b76 100644 --- a/tests/integration/LightningCustodianWallet.test.js +++ b/tests/integration/LightningCustodianWallet.test.js @@ -131,6 +131,7 @@ describe('LightningCustodianWallet', () => { assert.strictEqual(decodedLocally.destination, decodedRemotely.destination); assert.strictEqual(decodedLocally.num_satoshis, decodedRemotely.num_satoshis); assert.strictEqual(decodedLocally.timestamp, decodedRemotely.timestamp); + assert.strictEqual(decodedLocally.expiry, decodedRemotely.expiry); assert.strictEqual(decodedLocally.payment_hash, decodedRemotely.payment_hash); assert.strictEqual(decodedLocally.description, decodedRemotely.description); assert.strictEqual(decodedLocally.cltv_expiry, decodedRemotely.cltv_expiry); From 5d5b55bcf486c9b56b9423088bef7fa26f05d6f7 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Mon, 13 Jan 2020 22:35:48 +0000 Subject: [PATCH 35/68] REL: v4.9.2 --- ios/fastlane/metadata/en-US/release_notes.txt | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/ios/fastlane/metadata/en-US/release_notes.txt b/ios/fastlane/metadata/en-US/release_notes.txt index 960466d0..af8a202c 100644 --- a/ios/fastlane/metadata/en-US/release_notes.txt +++ b/ios/fastlane/metadata/en-US/release_notes.txt @@ -1,3 +1,24 @@ +v4.9.2 +====== + +* ADD: Swipe to Scan +* ADD: Handle clipboard content with both bitcoin: and lightning: +* ADD: Ask user if they have backed up their seed phrase +* ADD: Export screen allows copying to clipboard if its a LNDHub wallet +* ADD: Show LNDHub backup when creating lnd wallet +* ADD: CLP Fiat +* FIX: TX Time visual glitch +* FIX: Show an alert when theres a fetch transactions error +* FIX: TX list uses whole canvas area +* FIX: Don't allow empty wallet labels +* FIX: Wallet type selecion clipping on advanced mode +* FIX: Receive address was not being rendered +* FIX: Don't show wallet export warning if wallet was imported +* REF: Reworked Import wallet flow +* REF: BIP49 to use electrum +* REF: Custom receive + + v4.9.0 ====== @@ -30,24 +51,4 @@ v4.8.0 * FIX: layout for small devices with flexbox * FIX: Dont allow zero invoices to enable create invoice button * FIX: Change create button on Receive LN payment should be create invoice -* FIX: Update for watch - - -v4.7.1 -====== - -* ADD: Lapp browser -* FIX: White screen on boot -* FIX: Lightning wallet was not shown on Watch app -* FIX: crash on PSBT tx broadcast (when using with hardware wallet) -* REF: mnemonic backup screen -* DEL: Auto brightenss - -v4.7.0 -====== - -* ADD: external marketplace link -* FIX: electrum connection -* FIX: Now able to use biometrics with encrypted storage (not for unlocking) -* FIX: LApp marketplace address is now editable -* FIX: single address watch-only wallet Receive button crash \ No newline at end of file +* FIX: Update for watch \ No newline at end of file From c2cea68bdad412a46c44243659d5978999ff6332 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Tue, 14 Jan 2020 23:49:57 -0500 Subject: [PATCH 36/68] TST --- android/app/src/main/AndroidManifest.xml | 1 - package-lock.json | 81 ++++++++++++++++++++++++ screen/send/create.js | 50 +++++---------- screen/send/psbtWithHardwareWallet.js | 62 ++++++------------ 4 files changed, 117 insertions(+), 77 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5c8bfba9..5a420fef 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,7 +4,6 @@ - { - try { - await RNFS.writeFile('file://' + RNFS.ExternalStorageDirectoryPath + `/${fileName}`, this.state.tx, 'ascii'); - alert('Successfully exported.'); - } catch (e) { - console.log(e); - alert(e); - } - }, - }, - { - text: 'Documents Folder', - onPress: async () => { - try { - await RNFS.writeFile('file://' + RNFS.DocumentDirectoryPath + `/${fileName}`, this.state.tx, 'ascii'); - alert('Successfully exported.'); - } catch (e) { - console.log(e); - alert(e); - } - }, - }, - { text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' }, - ], - { cancelable: true }, - ); + const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { + title: 'BlueWallet Storage Access Permission', + message: 'BlueWallet needs your permission to access your storage to save this transaction.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + }); + + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + console.log('Storage Permission: Granted'); + const filePath = RNFS.ExternalCachesDirectoryPath + `/${this.fileName}`; + await RNFS.writeFile(filePath, this.state.tx); + alert(`This transaction has been saved in ${filePath}`); + } else { + console.log('Storage Permission: Denied'); + } } }; diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index ad79c6b2..bdcb151b 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -7,11 +7,11 @@ import { View, Dimensions, Image, - Alert, TextInput, Clipboard, Linking, Platform, + PermissionsAndroid, } from 'react-native'; import QRCode from 'react-native-qrcode-svg'; import { Icon, Text } from 'react-native-elements'; @@ -73,6 +73,7 @@ export default class PsbtWithHardwareWallet extends Component { deepLinkPSBT: undefined, txhex: props.navigation.getParam('txhex') || undefined, }; + this.fileName = `${Date.now()}.psbt`; } static getDerivedStateFromProps(nextProps, prevState) { @@ -229,9 +230,8 @@ export default class PsbtWithHardwareWallet extends Component { } exportPSBT = async () => { - const fileName = `${Date.now()}.psbt`; if (Platform.OS === 'ios') { - const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`; + const filePath = RNFS.TemporaryDirectoryPath + `/${this.fileName}`; await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()); Share.open({ url: 'file://' + filePath, @@ -241,46 +241,22 @@ export default class PsbtWithHardwareWallet extends Component { RNFS.unlink(filePath); }); } else if (Platform.OS === 'android') { - Alert.alert( - 'Export', - 'Where would you like to export this transaction?', - [ - { - text: 'External Storage', - onPress: async () => { - try { - await RNFS.writeFile( - 'file://' + RNFS.ExternalStorageDirectoryPath + `/${fileName}`, - this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), - 'ascii', - ); - alert('Successfully exported.'); - } catch (e) { - console.log(e); - alert(e); - } - }, - }, - { - text: 'Documents Folder', - onPress: async () => { - try { - await RNFS.writeFile( - 'file://' + RNFS.DocumentDirectoryPath + `/${fileName}`, - this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), - 'ascii', - ); - alert('Successfully exported.'); - } catch (e) { - console.log(e); - alert(e); - } - }, - }, - { text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel' }, - ], - { cancelable: true }, - ); + const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { + title: 'BlueWallet Storage Access Permission', + message: 'BlueWallet needs your permission to access your storage to save this transaction.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + }); + + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + console.log('Storage Permission: Granted'); + const filePath = RNFS.ExternalCachesDirectoryPath + `/${this.fileName}`; + await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()); + alert(`This transaction has been saved in ${filePath}`); + } else { + console.log('Storage Permission: Denied'); + } } }; From 8a5e11834fcb2b69aef500a3dc1dc182b93d4676 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Wed, 15 Jan 2020 19:18:27 +0000 Subject: [PATCH 37/68] REL: ver bump --- android/app/build.gradle | 2 +- ios/BlueWallet/Info.plist | 2 +- ios/BlueWalletWatch Extension/Info.plist | 2 +- ios/BlueWalletWatch/Info.plist | 2 +- ios/TodayExtension/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 79db9d82..4dd02ef8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,7 +119,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "4.9.2" + versionName "4.9.3" multiDexEnabled true missingDimensionStrategy 'react-native-camera', 'general' } diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index d59ec647..79449ca7 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.2 + 4.9.3 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/BlueWalletWatch Extension/Info.plist b/ios/BlueWalletWatch Extension/Info.plist index d61d66a2..cf9e5b41 100644 --- a/ios/BlueWalletWatch Extension/Info.plist +++ b/ios/BlueWalletWatch Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.9.2 + 4.9.3 CFBundleVersion 239 CLKComplicationPrincipalClass diff --git a/ios/BlueWalletWatch/Info.plist b/ios/BlueWalletWatch/Info.plist index 3c7bec49..4659affb 100644 --- a/ios/BlueWalletWatch/Info.plist +++ b/ios/BlueWalletWatch/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.2 + 4.9.3 CFBundleVersion 239 UISupportedInterfaceOrientations diff --git a/ios/TodayExtension/Info.plist b/ios/TodayExtension/Info.plist index 555e9756..2cd3f9a6 100644 --- a/ios/TodayExtension/Info.plist +++ b/ios/TodayExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.9.2 + 4.9.3 CFBundleVersion 1 NSExtension diff --git a/package-lock.json b/package-lock.json index a6bf2e9b..de38df0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.2", + "version": "4.9.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0555e7d2..2297068f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.2", + "version": "4.9.3", "devDependencies": { "@babel/core": "^7.5.0", "@babel/runtime": "^7.5.1", From ea2fd45b11518cca23f50820669dfead37aa2ac4 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Sun, 19 Jan 2020 14:41:23 +0000 Subject: [PATCH 38/68] FIX: deeplinking (safello etc) --- class/deeplinkSchemaMatch.js | 25 +++++++++---------- tests/integration/deepLinkSchemaMatch.test.js | 5 ++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/class/deeplinkSchemaMatch.js b/class/deeplinkSchemaMatch.js index 194b0c44..488e68cf 100644 --- a/class/deeplinkSchemaMatch.js +++ b/class/deeplinkSchemaMatch.js @@ -2,7 +2,9 @@ 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'); +const BlueApp: AppStorage = require('../BlueApp'); +const url = require('url'); + class DeeplinkSchemaMatch { static hasSchema(schemaString) { if (typeof schemaString !== 'string' || schemaString.length <= 0) return false; @@ -95,7 +97,7 @@ class DeeplinkSchemaMatch { if (!haveLnWallet) { // need to create one let w = new LightningCustodianWallet(); - w.setLabel(this.state.label || w.typeReadable); + w.setLabel(w.typeReadable); try { let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); @@ -128,17 +130,14 @@ class DeeplinkSchemaMatch { return; } - this.navigator && - this.navigator.dispatch( - completionHandler({ - routeName: 'LappBrowser', - params: { - fromSecret: lnWallet.getSecret(), - fromWallet: lnWallet, - url: urlObject.query.url, - }, - }), - ); + completionHandler({ + routeName: 'LappBrowser', + params: { + fromSecret: lnWallet.getSecret(), + fromWallet: lnWallet, + url: urlObject.query.url, + }, + }); break; } } diff --git a/tests/integration/deepLinkSchemaMatch.test.js b/tests/integration/deepLinkSchemaMatch.test.js index 0c2d6194..3ba784bc 100644 --- a/tests/integration/deepLinkSchemaMatch.test.js +++ b/tests/integration/deepLinkSchemaMatch.test.js @@ -36,6 +36,11 @@ describe('unit - DeepLinkSchemaMatch', function() { ); }); + it('isSafelloRedirect', () => { + assert.ok(DeeplinkSchemaMatch.isSafelloRedirect({ url: 'bluewallet:?safello-state-token=TEST' })); + assert.ok(!DeeplinkSchemaMatch.isSafelloRedirect({ url: 'bluewallet:' })); + }); + it('navigationForRoute', () => { const event = { uri: '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG' }; DeeplinkSchemaMatch.navigationRouteFor(event, navValue => { From 2be60fa1bd34e8452a40b1e47d60cae28f4d3da4 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Sun, 19 Jan 2020 13:10:06 -0500 Subject: [PATCH 39/68] FIX: Handle both chains --- class/deeplinkSchemaMatch.js | 25 ++++++++++++++++++++++++- screen/wallets/selectWallet.js | 3 +-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/class/deeplinkSchemaMatch.js b/class/deeplinkSchemaMatch.js index 488e68cf..0a89f008 100644 --- a/class/deeplinkSchemaMatch.js +++ b/class/deeplinkSchemaMatch.js @@ -1,6 +1,7 @@ import { AppStorage, LightningCustodianWallet } from './'; import AsyncStorage from '@react-native-community/async-storage'; import BitcoinBIP70TransactionDecode from '../bip70/bip70'; +import { Chain } from '../models/bitcoinUnits'; const bitcoin = require('bitcoinjs-lib'); const BlueApp: AppStorage = require('../BlueApp'); const url = require('url'); @@ -38,11 +39,13 @@ class DeeplinkSchemaMatch { } catch (e) { console.log(e); } + console.warn(isBothBitcoinAndLightning) if (isBothBitcoinAndLightning) { completionHandler({ routeName: 'HandleOffchainAndOnChain', params: { - onWalletSelect: this.isBothBitcoinAndLightningWalletSelect, + onWalletSelect: wallet => + completionHandler(DeeplinkSchemaMatch.isBothBitcoinAndLightningOnWalletSelect(wallet, isBothBitcoinAndLightning)), }, }); } else if (DeeplinkSchemaMatch.isBitcoinAddress(event.url) || BitcoinBIP70TransactionDecode.matchesPaymentURL(event.url)) { @@ -145,6 +148,26 @@ class DeeplinkSchemaMatch { } } + static isBothBitcoinAndLightningOnWalletSelect(wallet, uri) { + if (wallet.chain === Chain.ONCHAIN) { + return { + routeName: 'SendDetails', + params: { + uri: uri.bitcoin, + fromWallet: wallet, + }, + }; + } else if (wallet.chain === Chain.OFFCHAIN) { + return { + routeName: 'ScanLndInvoice', + params: { + uri: uri.lndInvoice, + fromSecret: wallet.getSecret(), + }, + }; + } + } + static isBitcoinAddress(address) { address = address .replace('bitcoin:', '') diff --git a/screen/wallets/selectWallet.js b/screen/wallets/selectWallet.js index 3141e324..bd29bd03 100644 --- a/screen/wallets/selectWallet.js +++ b/screen/wallets/selectWallet.js @@ -7,13 +7,12 @@ import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import WalletGradient from '../../class/walletGradient'; import { useNavigationParam } from 'react-navigation-hooks'; -import { Chain } from '../../models/bitcoinUnits'; /** @type {AppStorage} */ const BlueApp = require('../../BlueApp'); const loc = require('../../loc'); const SelectWallet = () => { - const chainType = useNavigationParam('chainType') || Chain.ONCHAIN; + const chainType = useNavigationParam('chainType'); const onWalletSelect = useNavigationParam('onWalletSelect'); const [isLoading, setIsLoading] = useState(true); const data = chainType From 69702fec4d7d9d7c4e80863b3a932bd458070728 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Sun, 19 Jan 2020 20:55:10 +0000 Subject: [PATCH 40/68] FIX: LN Invoice amount renders 0 --- class/lightning-custodian-wallet.js | 4 ++++ screen/lnd/scanLndInvoice.js | 2 +- tests/integration/LightningCustodianWallet.test.js | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js index 14155df8..a2097476 100644 --- a/class/lightning-custodian-wallet.js +++ b/class/lightning-custodian-wallet.js @@ -563,6 +563,10 @@ export class LightningCustodianWallet extends LegacyWallet { if (!decoded.expiry) decoded.expiry = '3600'; // default + if (parseInt(decoded.num_satoshis) === 0 && decoded.num_millisatoshis > 0) { + decoded.num_satoshis = (decoded.num_millisatoshis / 1000).toString(); + } + return (this.decoded_invoice_raw = decoded); } diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js index 01889bb9..af7f4796 100644 --- a/screen/lnd/scanLndInvoice.js +++ b/screen/lnd/scanLndInvoice.js @@ -216,7 +216,7 @@ export default class ScanLndInvoice extends React.Component { if (typeof this.state.decoded !== 'object') { return true; } else { - if (!this.state.decoded.hasOwnProperty('num_satoshis')) { + if (!this.state.decoded.num_satoshis) { return true; } } diff --git a/tests/integration/LightningCustodianWallet.test.js b/tests/integration/LightningCustodianWallet.test.js index ee574b76..059309f6 100644 --- a/tests/integration/LightningCustodianWallet.test.js +++ b/tests/integration/LightningCustodianWallet.test.js @@ -120,7 +120,19 @@ describe('LightningCustodianWallet', () => { assert.ok(error); }); + it('decode can handle zero sats but present msats', async () => { + let l = new LightningCustodianWallet(); + let decoded = l.decodeInvoice( + 'lnbc89n1p0zptvhpp5j3h5e80vdlzn32df8y80nl2t7hssn74lzdr96ve0u4kpaupflx2sdphgfkx7cmtwd68yetpd5s9xct5v4kxc6t5v5s9gunpdeek66tnwd5k7mscqp2sp57m89zv0lrgc9zzaxy5p3d5rr2cap2pm6zm4n0ew9vyp2d5zf2mfqrzjqfxj8p6qjf5l8du7yuytkwdcjhylfd4gxgs48t65awjg04ye80mq7z990yqq9jsqqqqqqqqqqqqq05qqrc9qy9qsq9mynpa9ucxg53hwnvw323r55xdd3l6lcadzs584zvm4wdw5pv3eksdlcek425pxaqrn9u5gpw0dtpyl9jw2pynjtqexxgh50akwszjgq4ht4dh', + ); + assert.strictEqual(decoded.num_satoshis, '8.9'); + }); + it('can decode invoice locally & remotely', async () => { + if (!process.env.BLITZHUB) { + console.error('process.env.BLITZHUB not set, skipped'); + return; + } let l2 = new LightningCustodianWallet(); l2.setSecret(process.env.BLITZHUB); await l2.authorize(); From eb141c0d9912cbd4762d91ae304dc33fd2ee6776 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Sun, 19 Jan 2020 15:17:06 -0500 Subject: [PATCH 41/68] FIX: Allow wallet text input to be empty for new wallet naming --- screen/wallets/details.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 590c2979..ac58e9bd 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -70,7 +70,9 @@ export default class WalletDetails extends Component { setLabel() { this.props.navigation.setParams({ isLoading: true }); this.setState({ isLoading: true }, async () => { - this.state.wallet.setLabel(this.state.walletName); + if (this.state.walletName.trim().length > 0) { + this.state.wallet.setLabel(this.state.walletName); + } BlueApp.saveToDisk(); alert('Wallet updated.'); this.props.navigation.goBack(null); @@ -157,12 +159,7 @@ export default class WalletDetails extends Component { { - if (text.trim().length === 0) { - text = this.state.wallet.getLabel(); - } - this.setState({ walletName: text }); - }} + onChangeText={text => this.setState({ walletName: text })} numberOfLines={1} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} editable={!this.state.isLoading} From a3954ecc4b595e7e971df84794690eda21c4a716 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Sun, 19 Jan 2020 16:00:25 -0500 Subject: [PATCH 42/68] ADD: Allow walet change on invoice creation --- MainBottomTabs.js | 6 ++++ screen/lnd/lndCreateInvoice.js | 60 ++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/MainBottomTabs.js b/MainBottomTabs.js index 2626176d..69e37d03 100644 --- a/MainBottomTabs.js +++ b/MainBottomTabs.js @@ -194,6 +194,12 @@ const LNDCreateInvoiceStackNavigator = createStackNavigator({ LNDCreateInvoice: { screen: LNDCreateInvoice, }, + SelectWallet: { + screen: SelectWallet, + navigationOptions: { + headerLeft: null, + }, + }, LNDViewInvoice: { screen: LNDViewInvoice, swipeEnabled: false, diff --git a/screen/lnd/lndCreateInvoice.js b/screen/lnd/lndCreateInvoice.js index 1ce9ee5e..281738d9 100644 --- a/screen/lnd/lndCreateInvoice.js +++ b/screen/lnd/lndCreateInvoice.js @@ -20,7 +20,7 @@ import { import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; import PropTypes from 'prop-types'; import bech32 from 'bech32'; -import { BitcoinUnit } from '../../models/bitcoinUnits'; +import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import NavigationService from '../../NavigationService'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { Icon } from 'react-native-elements'; @@ -36,7 +36,8 @@ export default class LNDCreateInvoice extends Component { constructor(props) { super(props); - + this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow); + this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide); let fromWallet; if (props.navigation.state.params.fromWallet) fromWallet = props.navigation.getParam('fromWallet'); @@ -56,6 +57,7 @@ export default class LNDCreateInvoice extends Component { lnurl: '', lnurlParams: null, isLoading: true, + renderWalletSelectionButtonHidden: false, }; } @@ -85,6 +87,19 @@ export default class LNDCreateInvoice extends Component { } } + componentWillUnmount() { + this.keyboardDidShowListener.remove(); + this.keyboardDidHideListener.remove(); + } + + _keyboardDidShow = () => { + this.setState({ renderWalletSelectionButtonHidden: true }); + }; + + _keyboardDidHide = () => { + this.setState({ renderWalletSelectionButtonHidden: false }); + }; + async createInvoice() { this.setState({ isLoading: true }, async () => { try { @@ -216,6 +231,45 @@ export default class LNDCreateInvoice extends Component { ); }; + renderWalletSelectionButton = () => { + if (this.state.renderWalletSelectionButtonHidden) return; + return ( + + {!this.state.isLoading && ( + + this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN }) + } + > + {loc.wallets.select_wallet.toLowerCase()} + + + )} + + + this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN }) + } + > + {this.state.fromWallet.getLabel()} + + {loc.formatBalanceWithoutSuffix(this.state.fromWallet.getBalance(), BitcoinUnit.SATS, false)} + + + {BitcoinUnit.SATS} + + + + + ); + }; + + onWalletSelect = wallet => { + this.setState({ fromWallet: wallet }, () => this.props.navigation.pop()); + }; + render() { if (!this.state.fromWallet) { return ( @@ -283,6 +337,7 @@ export default class LNDCreateInvoice extends Component { {this.renderCreateButton()} + {this.renderWalletSelectionButton()} ); @@ -295,6 +350,7 @@ LNDCreateInvoice.propTypes = { dismiss: PropTypes.func, navigate: PropTypes.func, getParam: PropTypes.func, + pop: PropTypes.func, state: PropTypes.shape({ params: PropTypes.shape({ uri: PropTypes.string, From a7e07666124f91836b3c8ff0fa1bdea5bbc4cada Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Sun, 19 Jan 2020 17:03:16 -0500 Subject: [PATCH 43/68] FIX: Crash on scan in wallets import when closing the view --- screen/send/scanQrAddress.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index e67c4a2c..07668976 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -15,7 +15,7 @@ const ScanQRCode = ({ launchedBy = useNavigationParam('launchedBy'), }) => { const [isLoading, setIsLoading] = useState(false); - const { navigate } = useNavigation(); + const { navigate, goBack } = useNavigation(); const onBarCodeRead = ret => { if (!isLoading && !cameraPreviewIsPaused) { @@ -62,7 +62,7 @@ const ScanQRCode = ({ right: 16, top: 64, }} - onPress={() => navigate(launchedBy)} + onPress={() => launchedBy ? navigate(launchedBy) : goBack(null) } > From 301bf499ce4b0543df0f232911ec44c6998791aa Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Sun, 19 Jan 2020 17:06:15 -0500 Subject: [PATCH 44/68] FIX: There's two refresh icons on the main view, when you pull-to-refresh --- screen/wallets/list.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/screen/wallets/list.js b/screen/wallets/list.js index 75470f64..47543f60 100644 --- a/screen/wallets/list.js +++ b/screen/wallets/list.js @@ -338,16 +338,7 @@ export default class WalletsList extends Component { }} onWillBlur={() => this.setState({ cameraPreviewIsPaused: true })} /> - this.refreshTransactions()} - refreshing={!this.state.isFlatListRefreshControlHidden} - shouldRefresh={this.state.timeElpased} - /> - } - > + Date: Sun, 19 Jan 2020 22:33:17 -0500 Subject: [PATCH 45/68] FIX: Listen to lnd invoice changes --- BlueComponents.js | 2 +- class/deeplinkSchemaMatch.js | 1 - screen/lnd/scanLndInvoice.js | 67 +++++++++++++++++------------------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/BlueComponents.js b/BlueComponents.js index 169fa783..75b24843 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -2076,7 +2076,7 @@ export class BlueAddressInput extends Component { value={this.props.address} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} editable={!this.props.isLoading} - onSubmitEditing={() => Keyboard.dismiss()} + onSubmitEditing={Keyboard.dismiss} {...this.props} /> { - this.setState({ renderWalletSelectionButtonHidden: true }); - }; - - _keyboardDidHide = () => { - this.setState({ renderWalletSelectionButtonHidden: false }); - }; - - processInvoice = data => { - this.setState({ isLoading: true }, async () => { - if (!this.state.fromWallet) { - ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - alert('Before paying a Lightning invoice, you must first add a Lightning wallet.'); - return this.props.navigation.goBack(); - } - + static getDerivedStateFromProps(props, state) { + if (props.navigation.state.params.uri) { + let data = props.navigation.state.params.uri; // handling BIP21 w/BOLT11 support let ind = data.indexOf('lightning='); if (ind !== -1) { @@ -119,7 +95,7 @@ export default class ScanLndInvoice extends React.Component { /** * @type {LightningCustodianWallet} */ - let w = this.state.fromWallet; + let w = state.fromWallet; let decoded; try { decoded = w.decodeInvoice(data); @@ -131,21 +107,41 @@ export default class ScanLndInvoice extends React.Component { expiresIn = Math.round((expiresIn - +new Date()) / (60 * 1000)) + ' min'; } Keyboard.dismiss(); - this.setState({ + props.navigation.setParams({ uri: undefined }); + return { invoice: data, decoded, expiresIn, destination: data, isAmountInitiallyEmpty: decoded.num_satoshis === '0', isLoading: false, - }); + }; } catch (Err) { - Keyboard.dismiss(); - this.setState({ isLoading: false }); ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - alert(Err.message); + Keyboard.dismiss(); + props.navigation.setParams({ uri: undefined }); + setTimeout(() => alert(Err.message), 10); + return { ...state, isLoading: false }; } - }); + } + return state; + } + + componentWillUnmount() { + this.keyboardDidShowListener.remove(); + this.keyboardDidHideListener.remove(); + } + + _keyboardDidShow = () => { + this.setState({ renderWalletSelectionButtonHidden: true }); + }; + + _keyboardDidHide = () => { + this.setState({ renderWalletSelectionButtonHidden: false }); + }; + + processInvoice = data => { + this.props.navigation.setParams({ uri: data }); }; async pay() { @@ -295,7 +291,7 @@ export default class ScanLndInvoice extends React.Component { { - this.setState({ destination: text }); + text = text.trim(); this.processTextForInvoice(text); }} onBarScanned={this.processInvoice} @@ -355,6 +351,7 @@ ScanLndInvoice.propTypes = { navigate: PropTypes.func, pop: PropTypes.func, getParam: PropTypes.func, + setParams: PropTypes.func, dismiss: PropTypes.func, state: PropTypes.shape({ routeName: PropTypes.string, From bd2d2b5ed013775a1ee6f3eaf59e4c09b0be6999 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Mon, 20 Jan 2020 18:58:33 -0500 Subject: [PATCH 46/68] DEL: Remove alerts from main list --- screen/wallets/list.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/screen/wallets/list.js b/screen/wallets/list.js index 47543f60..4480043f 100644 --- a/screen/wallets/list.js +++ b/screen/wallets/list.js @@ -1,4 +1,3 @@ -/* global alert */ import React, { Component } from 'react'; import { View, @@ -68,7 +67,7 @@ export default class WalletsList extends Component { console.log('fetch all wallet txs took', (end - start) / 1000, 'sec'); } catch (error) { noErr = false; - alert(error); + console.log(error); } if (noErr) this.redrawScreen(); }); @@ -111,7 +110,6 @@ export default class WalletsList extends Component { console.log('fetch tx took', (end - start) / 1000, 'sec'); } catch (err) { noErr = false; - alert(err); console.warn(err); } if (noErr) await BlueApp.saveToDisk(); // caching @@ -262,7 +260,6 @@ export default class WalletsList extends Component { } } catch (Err) { noErr = false; - alert(Err); console.warn(Err); } From 1a0ff16037eafcc9791d06d6fa44f8382dd2349e Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Date: Wed, 22 Jan 2020 14:27:40 -0500 Subject: [PATCH 47/68] FIX: Lint --- screen/send/scanQrAddress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index 07668976..dd1cc5ab 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -62,7 +62,7 @@ const ScanQRCode = ({ right: 16, top: 64, }} - onPress={() => launchedBy ? navigate(launchedBy) : goBack(null) } + onPress={() => (launchedBy ? navigate(launchedBy) : goBack(null))} > From 64a57a1ced1e48ecdf4c99543c18f8edb69dbdc6 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Wed, 22 Jan 2020 20:22:58 +0000 Subject: [PATCH 48/68] REL: ver bump 4.9.4 --- android/app/build.gradle | 2 +- ios/BlueWallet/Info.plist | 2 +- ios/BlueWalletWatch Extension/Info.plist | 2 +- ios/BlueWalletWatch/Info.plist | 2 +- ios/TodayExtension/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 4dd02ef8..2e59c38c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,7 +119,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "4.9.3" + versionName "4.9.4" multiDexEnabled true missingDimensionStrategy 'react-native-camera', 'general' } diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index 79449ca7..5ad81631 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.3 + 4.9.4 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/BlueWalletWatch Extension/Info.plist b/ios/BlueWalletWatch Extension/Info.plist index cf9e5b41..a67854e9 100644 --- a/ios/BlueWalletWatch Extension/Info.plist +++ b/ios/BlueWalletWatch Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.9.3 + 4.9.4 CFBundleVersion 239 CLKComplicationPrincipalClass diff --git a/ios/BlueWalletWatch/Info.plist b/ios/BlueWalletWatch/Info.plist index 4659affb..6328c5e2 100644 --- a/ios/BlueWalletWatch/Info.plist +++ b/ios/BlueWalletWatch/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.3 + 4.9.4 CFBundleVersion 239 UISupportedInterfaceOrientations diff --git a/ios/TodayExtension/Info.plist b/ios/TodayExtension/Info.plist index 2cd3f9a6..143e3e05 100644 --- a/ios/TodayExtension/Info.plist +++ b/ios/TodayExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.9.3 + 4.9.4 CFBundleVersion 1 NSExtension diff --git a/package-lock.json b/package-lock.json index de38df0f..0df7a6a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.3", + "version": "4.9.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2297068f..75f50a94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.3", + "version": "4.9.4", "devDependencies": { "@babel/core": "^7.5.0", "@babel/runtime": "^7.5.1", From a7bcff1c8a636cbb5be3a789a3446da409f44e98 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Fri, 24 Jan 2020 23:32:22 +0000 Subject: [PATCH 49/68] TST --- .../LightningCustodianWallet.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/LightningCustodianWallet.test.js b/tests/integration/LightningCustodianWallet.test.js index 059309f6..ac6d6fd8 100644 --- a/tests/integration/LightningCustodianWallet.test.js +++ b/tests/integration/LightningCustodianWallet.test.js @@ -7,7 +7,7 @@ describe('LightningCustodianWallet', () => { let l1 = new LightningCustodianWallet(); it.skip('issue credentials', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; assert.ok(l1.refill_addressess.length === 0); assert.ok(l1._refresh_token_created_ts === 0); assert.ok(l1._access_token_created_ts === 0); @@ -24,7 +24,7 @@ describe('LightningCustodianWallet', () => { }); it('can create, auth and getbtc', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; assert.ok(l1.refill_addressess.length === 0); assert.ok(l1._refresh_token_created_ts === 0); assert.ok(l1._access_token_created_ts === 0); @@ -51,7 +51,7 @@ describe('LightningCustodianWallet', () => { }); it('can refresh token', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; let oldRefreshToken = l1.refresh_token; let oldAccessToken = l1.access_token; await l1.refreshAcessToken(); @@ -62,7 +62,7 @@ describe('LightningCustodianWallet', () => { }); it('can use existing login/pass', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; if (!process.env.BLITZHUB) { console.error('process.env.BLITZHUB not set, skipped'); return; @@ -150,7 +150,7 @@ describe('LightningCustodianWallet', () => { }); it('can pay invoice', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; if (!process.env.BLITZHUB) { console.error('process.env.BLITZHUB not set, skipped'); return; @@ -224,7 +224,7 @@ describe('LightningCustodianWallet', () => { }); it('can create invoice and pay other blitzhub invoice', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; if (!process.env.BLITZHUB) { console.error('process.env.BLITZHUB not set, skipped'); return; @@ -324,7 +324,7 @@ describe('LightningCustodianWallet', () => { }); it('can pay free amount (tip) invoice', async function() { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; if (!process.env.BLITZHUB) { console.error('process.env.BLITZHUB not set, skipped'); return; @@ -401,7 +401,7 @@ describe('LightningCustodianWallet', () => { it('cant create zemo amt invoices yet', async () => { let l1 = new LightningCustodianWallet(); - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; assert.ok(l1.refill_addressess.length === 0); assert.ok(l1._refresh_token_created_ts === 0); assert.ok(l1._access_token_created_ts === 0); @@ -435,7 +435,7 @@ describe('LightningCustodianWallet', () => { }); it('cant pay negative free amount', async () => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; if (!process.env.BLITZHUB) { console.error('process.env.BLITZHUB not set, skipped'); return; From baa1afe0a9a29be8439a21804c460f2d7b65a4e6 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Fri, 24 Jan 2020 23:33:02 +0000 Subject: [PATCH 50/68] REL: v4.9.4 --- ios/fastlane/metadata/en-US/release_notes.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ios/fastlane/metadata/en-US/release_notes.txt b/ios/fastlane/metadata/en-US/release_notes.txt index af8a202c..05e232b8 100644 --- a/ios/fastlane/metadata/en-US/release_notes.txt +++ b/ios/fastlane/metadata/en-US/release_notes.txt @@ -1,3 +1,18 @@ +v4.9.4 +====== + +* REL: ver bump 4.9.4 +* FIX: Lint +* FIX: Listen to lnd invoice changes +* FIX: There's two refresh icons on the main view, when you pull-to-refresh +* FIX: Crash on scan in wallets import when closing the view +* FIX: Allow walet change on invoice creation +* FIX: Allow wallet text input to be empty for new wallet naming +* FIX: LN Invoice amount renders 0 +* FIX: Handle both chains +* FIX: deeplinking (safello etc) +* DEL: Remove alerts from main list + v4.9.2 ====== From 8636919635162b3445e1d46b976ffd9b65d0bfbf Mon Sep 17 00:00:00 2001 From: Nuno Coelho Date: Thu, 30 Jan 2020 17:22:50 +0100 Subject: [PATCH 51/68] typo in spanish --- loc/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loc/es.js b/loc/es.js index 89eaf0f3..2ee32648 100644 --- a/loc/es.js +++ b/loc/es.js @@ -35,7 +35,7 @@ module.exports = { title: 'Añadir billetera', description: 'Puedes escanear la billetera de papel (en WIF - Formato de importación de billeteras) o crear una nueva billetera. Las billeteras SegWit estan compatibles por defecto.', - scan: 'Escaniar', + scan: 'Escanear', create: 'Crear', label_new_segwit: 'Nuevo SegWit', label_new_lightning: 'Nuevo Lightning', From 3c82d61c39a8fd3130aea5df5af9e78ffea00495 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Wed, 12 Feb 2020 20:40:24 +0000 Subject: [PATCH 52/68] REL: ver bump v5.0.0 --- android/app/build.gradle | 2 +- ios/BlueWallet/Info.plist | 2 +- ios/BlueWalletWatch Extension/Info.plist | 2 +- ios/BlueWalletWatch/Info.plist | 2 +- ios/TodayExtension/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2e59c38c..2c66260f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,7 +119,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "4.9.4" + versionName "5.0.0" multiDexEnabled true missingDimensionStrategy 'react-native-camera', 'general' } diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index 5ad81631..23fb4f22 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.4 + 5.0.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/BlueWalletWatch Extension/Info.plist b/ios/BlueWalletWatch Extension/Info.plist index a67854e9..78fb5e3a 100644 --- a/ios/BlueWalletWatch Extension/Info.plist +++ b/ios/BlueWalletWatch Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.9.4 + 5.0.0 CFBundleVersion 239 CLKComplicationPrincipalClass diff --git a/ios/BlueWalletWatch/Info.plist b/ios/BlueWalletWatch/Info.plist index 6328c5e2..d257572d 100644 --- a/ios/BlueWalletWatch/Info.plist +++ b/ios/BlueWalletWatch/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.9.4 + 5.0.0 CFBundleVersion 239 UISupportedInterfaceOrientations diff --git a/ios/TodayExtension/Info.plist b/ios/TodayExtension/Info.plist index 143e3e05..94d1f5b7 100644 --- a/ios/TodayExtension/Info.plist +++ b/ios/TodayExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.9.4 + 5.0.0 CFBundleVersion 1 NSExtension diff --git a/package-lock.json b/package-lock.json index 0df7a6a5..bac08b2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.4", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 75f50a94..dd1ae697 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "4.9.4", + "version": "5.0.0", "devDependencies": { "@babel/core": "^7.5.0", "@babel/runtime": "^7.5.1", From 4423b53e2988581878334ef4d6a9f2614a9a6bca Mon Sep 17 00:00:00 2001 From: Overtorment Date: Wed, 12 Feb 2020 20:47:37 +0000 Subject: [PATCH 53/68] TST: DEFAULT_TIMEOUT_INTERVAL --- tests/integration/hd-segwit-bech32-wallet.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/hd-segwit-bech32-wallet.test.js b/tests/integration/hd-segwit-bech32-wallet.test.js index 0fd8cc6a..f90cbe31 100644 --- a/tests/integration/hd-segwit-bech32-wallet.test.js +++ b/tests/integration/hd-segwit-bech32-wallet.test.js @@ -213,7 +213,7 @@ describe('Bech32 Segwit HD (BIP84)', () => { console.error('process.env.FAULTY_ZPUB not set, skipped'); return; } - jasmine.DEFAULT_TIMEOUT_INTERVAL = 90 * 1000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; let hd = new HDSegwitBech32Wallet(); hd._xpub = process.env.FAULTY_ZPUB; From 71d6d0f3d7ca490121b515eb71e49d97d6bd61f5 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 8 Feb 2020 05:57:14 +0000 Subject: [PATCH 54/68] fix: upgrade eslint-config-prettier from 6.0.0 to 6.9.0 Snyk has created this PR to upgrade eslint-config-prettier from 6.0.0 to 6.9.0. See this package in NPM: https://www.npmjs.com/package/eslint-config-prettier See this project in Snyk: https://app.snyk.io/org/bluewallet/project/4d0df22a-0152-410a-8584-6df0d0a596d4?utm_source=github&utm_medium=upgrade-pr --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd1ae697..1480f94a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "dayjs": "1.8.14", "ecurve": "1.0.6", "electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git", - "eslint-config-prettier": "6.0.0", + "eslint-config-prettier": "6.9.0", "eslint-config-standard": "12.0.0", "eslint-config-standard-react": "7.0.2", "eslint-plugin-prettier": "3.1.0", From 47c679d1d104c534d61ab942bf0ad5d0733a2957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Wed, 5 Feb 2020 19:58:56 +0000 Subject: [PATCH 55/68] FIX: watch-only delete wallet --- screen/wallets/details.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen/wallets/details.js b/screen/wallets/details.js index ac58e9bd..aa8063b7 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -256,7 +256,7 @@ export default class WalletDetails extends Component { return; } } - if (this.state.wallet.getBalance() > 0) { + if (this.state.wallet.getBalance() > 0 && this.state.wallet.allowSend()) { this.presentWalletHasBalanceAlert(); } else { this.props.navigation.setParams({ isLoading: true }); From ff522218f140cc45d5f57e956af2e65112f109e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Fri, 14 Feb 2020 15:31:50 +0000 Subject: [PATCH 56/68] TST --- screen/send/details.js | 3 ++- screen/send/psbtWithHardwareWallet.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/screen/send/details.js b/screen/send/details.js index 52b68602..efd78ce5 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -712,7 +712,8 @@ export default class SendDetails extends Component { importTransaction = async () => { try { - const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] }); + const res = await DocumentPicker.pick({ type: Platform.OS === 'ios' + ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles] }); if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) { const file = await RNFS.readFile(res.uri, 'ascii'); const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index bdcb151b..5813c8b6 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -262,7 +262,7 @@ export default class PsbtWithHardwareWallet extends Component { openSignedTransaction = async () => { try { - const res = await DocumentPicker.pick({ type: ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] }); + const res = await DocumentPicker.pick({ type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles] }); const file = await RNFS.readFile(res.uri); if (file) { this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarCodeRead({ data: file })); From efa1c52878e051afd38b01eb5811a1c6be823925 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Mon, 24 Feb 2020 15:24:33 +0000 Subject: [PATCH 57/68] FIX: allow capitalized bech32 addresses (closes #838) --- screen/send/details.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/screen/send/details.js b/screen/send/details.js index 5d69fd20..82e99e36 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -135,7 +135,10 @@ export default class SendDetails extends Component { } else { let recipients = this.state.addresses; const dataWithoutSchema = data.replace('bitcoin:', ''); - if (btcAddressRx.test(dataWithoutSchema) || (dataWithoutSchema.indexOf('bc1') === 0 && dataWithoutSchema.indexOf('?') === -1)) { + if ( + btcAddressRx.test(dataWithoutSchema) || + ((dataWithoutSchema.indexOf('bc1') === 0 || dataWithoutSchema.indexOf('BC1') === 0) && dataWithoutSchema.indexOf('?') === -1) + ) { recipients[[this.state.recipientsScrollIndex]].address = dataWithoutSchema; this.setState({ address: recipients, @@ -161,7 +164,7 @@ export default class SendDetails extends Component { this.setState({ isLoading: false }); } console.log(options); - if (btcAddressRx.test(address) || address.indexOf('bc1') === 0) { + if (btcAddressRx.test(address) || address.indexOf('bc1') === 0 || address.indexOf('BC1') === 0) { recipients[[this.state.recipientsScrollIndex]].address = address; recipients[[this.state.recipientsScrollIndex]].amount = options.amount; this.setState({ From 2138493bf13a2de55b8650567fa34d94bf629b04 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Mon, 24 Feb 2020 21:45:14 +0000 Subject: [PATCH 58/68] REF: coldcard integration --- class/abstract-hd-electrum-wallet.js | 10 ++++-- class/abstract-wallet.js | 13 +++++++ class/watch-only-wallet.js | 41 +++++++++++++---------- screen/send/details.js | 5 +-- screen/send/psbtWithHardwareWallet.js | 17 ++++++++-- screen/wallets/details.js | 29 ++++++++-------- screen/wallets/transactions.js | 4 +-- tests/integration/WatchOnlyWallet.test.js | 41 +++++++++++++++++++++++ 8 files changed, 119 insertions(+), 41 deletions(-) diff --git a/class/abstract-hd-electrum-wallet.js b/class/abstract-hd-electrum-wallet.js index 0d8f287b..0e1859b3 100644 --- a/class/abstract-hd-electrum-wallet.js +++ b/class/abstract-hd-electrum-wallet.js @@ -636,6 +636,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { async fetchUtxo() { // considering only confirmed balance + // also, fetching utxo of addresses that only have some balance let addressess = []; for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { @@ -718,6 +719,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * @param changeAddress {String} Excessive coins will go back to that address * @param sequence {Number} Used in RBF * @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case + * @param masterFingerprint {number} Decimal number of wallet's master fingerprint * @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} */ createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { @@ -759,7 +761,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let pubkey = this._getPubkeyByAddress(input.address); let masterFingerprintBuffer; if (masterFingerprint) { - const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex'); + let masterFingerprintHex = Number(masterFingerprint).toString(16); + if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte + const hexBuffer = Buffer.from(masterFingerprintHex, 'hex'); masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); @@ -799,7 +803,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { let masterFingerprintBuffer; if (masterFingerprint) { - const hexBuffer = Buffer.from(Number(masterFingerprint).toString(16), 'hex'); + let masterFingerprintHex = Number(masterFingerprint).toString(16); + if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte + const hexBuffer = Buffer.from(masterFingerprintHex, 'hex'); masterFingerprintBuffer = Buffer.from(reverse(hexBuffer)); } else { masterFingerprintBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); diff --git a/class/abstract-wallet.js b/class/abstract-wallet.js index bf0eea6e..7cbc54df 100644 --- a/class/abstract-wallet.js +++ b/class/abstract-wallet.js @@ -128,6 +128,19 @@ export class AbstractWallet { setSecret(newSecret) { this.secret = newSecret.trim(); + + try { + const parsedSecret = JSON.parse(this.secret); + if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) { + let masterFingerprint = false; + if (parsedSecret.keystore.ckcc_xfp) { + // It is a ColdCard Hardware Wallet + masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp); + } + this.secret = parsedSecret.keystore.xpub; + this.masterFingerprint = masterFingerprint; + } + } catch (_) {} return this; } diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index 868d6405..2bf0bbdb 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -44,22 +44,8 @@ export class WatchOnlyWallet extends LegacyWallet { try { bitcoin.address.toOutputScript(this.getAddress()); return true; - } catch (_e) { - try { - const parsedSecret = JSON.parse(this.secret); - if (parsedSecret.keystore.xpub) { - let masterFingerprint = false; - if (parsedSecret.keystore.ckcc_xfp) { - // It is a ColdCard Hardware Wallet - masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp); - } - this.setSecret(parsedSecret.keystore.xpub); - this.masterFingerprint = masterFingerprint; - } - return true; - } catch (_e) { - return false; - } + } catch (_) { + return false; } } @@ -161,9 +147,30 @@ 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, this.masterFingerprint); + return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.getMasterFingerprint()); } else { throw new Error('Not a zpub watch-only wallet, cant create PSBT (or just not initialized)'); } } + + getMasterFingerprint() { + return this.masterFingerprint; + } + + getMasterFingerprintHex() { + let masterFingerprintHex = Number(this.masterFingerprint).toString(16); + if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte + // poor man's little-endian conversion: + // ¯\_(ツ)_/¯ + return ( + masterFingerprintHex[6] + + masterFingerprintHex[7] + + masterFingerprintHex[4] + + masterFingerprintHex[5] + + masterFingerprintHex[2] + + masterFingerprintHex[3] + + masterFingerprintHex[0] + + masterFingerprintHex[1] + ); + } } diff --git a/screen/send/details.js b/screen/send/details.js index efd78ce5..49afd7a6 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -712,8 +712,9 @@ export default class SendDetails extends Component { importTransaction = async () => { try { - const res = await DocumentPicker.pick({ type: Platform.OS === 'ios' - ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles] }); + const res = await DocumentPicker.pick({ + type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles], + }); if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) { const file = await RNFS.readFile(res.uri, 'ascii'); const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 5813c8b6..8d7dd015 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -47,11 +47,20 @@ export default class PsbtWithHardwareWallet extends Component { cameraRef = null; onBarCodeRead = ret => { + if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) { + // this looks like NOT base64, so maybe its transaction's hex + this.setState({ txhex: ret.data }); + return; + } + if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview(); this.setState({ renderScanner: false }, () => { console.log(ret.data); try { - let Tx = this.state.fromWallet.combinePsbt(this.state.psbt, ret.data); + let Tx = this.state.fromWallet.combinePsbt( + this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), + ret.data, + ); this.setState({ txhex: Tx.toHex() }); } catch (Err) { alert(Err); @@ -262,7 +271,9 @@ export default class PsbtWithHardwareWallet extends Component { openSignedTransaction = async () => { try { - const res = await DocumentPicker.pick({ type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles] }); + const res = await DocumentPicker.pick({ + type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles], + }); const file = await RNFS.readFile(res.uri); if (file) { this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarCodeRead({ data: file })); @@ -332,7 +343,7 @@ export default class PsbtWithHardwareWallet extends Component { color: BlueApp.settings.buttonTextColor, }} onPress={this.exportPSBT} - title={'Export'} + title={'Export to file'} /> diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 1c243797..3d0762d9 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -51,7 +51,6 @@ export default class WalletDetails extends Component { super(props); const wallet = props.navigation.getParam('wallet'); - console.warn(wallet.masterFingerprint) const isLoading = true; this.state = { isLoading, @@ -271,20 +270,20 @@ export default class WalletDetails extends Component { return; } } - if (this.state.wallet.getBalance() > 0 && this.state.wallet.allowSend()) { - this.presentWalletHasBalanceAlert(); - } else { - this.props.navigation.setParams({ isLoading: true }); - this.setState({ isLoading: true }, async () => { - BlueApp.deleteWallet(this.state.wallet); - ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); - await BlueApp.saveToDisk(); - EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); - EV(EV.enum.WALLETS_COUNT_CHANGED); - this.props.navigation.navigate('Wallets'); - }); - } - } + if (this.state.wallet.getBalance() > 0 && this.state.wallet.allowSend()) { + this.presentWalletHasBalanceAlert(); + } else { + this.props.navigation.setParams({ isLoading: true }); + this.setState({ isLoading: true }, async () => { + BlueApp.deleteWallet(this.state.wallet); + ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); + await BlueApp.saveToDisk(); + EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); + EV(EV.enum.WALLETS_COUNT_CHANGED); + this.props.navigation.navigate('Wallets'); + }); + } + }, }, { text: loc.wallets.details.no_cancel, onPress: () => {}, style: 'cancel' }, ], diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index c60ebd42..0ebfeeb8 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -603,11 +603,11 @@ export default class WalletTransactions extends Component { text: loc._.ok, onPress: () => { const wallet = this.state.wallet; - wallet.use_with_hardware_wallet = true + wallet.use_with_hardware_wallet = true; this.setState({ wallet }, async () => { await BlueApp.saveToDisk(); this.navigateToSendScreen(); - }) + }); }, style: 'default', }, diff --git a/tests/integration/WatchOnlyWallet.test.js b/tests/integration/WatchOnlyWallet.test.js index ef307fa2..3beacaba 100644 --- a/tests/integration/WatchOnlyWallet.test.js +++ b/tests/integration/WatchOnlyWallet.test.js @@ -111,6 +111,47 @@ describe('Watch only wallet', () => { ); }); + it('can import coldcard/electrum compatible JSON skeleton wallet, and create a tx with master fingerprint', async () => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 120 * 1000; + const skeleton = + '{"keystore": {"ckcc_xpub": "xpub661MyMwAqRbcGmUDQVKxmhEESB5xTk8hbsdTSV3Pmhm3HE9Fj3s45R9Y8LwyaQWjXXPytZjuhTKSyCBPeNrB1VVWQq1HCvjbEZ27k44oNmg", "xpub": "zpub6rFDtF1nuXZ9PUL4XzKURh3vJBW6Kj6TUrYL4qPtFNtDXtcTVfiqjQDyrZNwjwzt5HS14qdqo3Co2282Lv3Re6Y5wFZxAVuMEpeygnnDwfx", "label": "Coldcard Import 168DD603", "ckcc_xfp": 64392470, "type": "hardware", "hw_type": "coldcard", "derivation": "m/84\'/0\'/0\'"}, "wallet_type": "standard", "use_encryption": false, "seed_version": 17}'; + let w = new WatchOnlyWallet(); + w.setSecret(skeleton); + w.init(); + assert.ok(w.valid()); + assert.strictEqual( + w.getSecret(), + 'zpub6rFDtF1nuXZ9PUL4XzKURh3vJBW6Kj6TUrYL4qPtFNtDXtcTVfiqjQDyrZNwjwzt5HS14qdqo3Co2282Lv3Re6Y5wFZxAVuMEpeygnnDwfx', + ); + assert.strictEqual(w.getMasterFingerprint(), 64392470); + assert.strictEqual(w.getMasterFingerprintHex(), '168dd603'); + + const utxos = [ + { + height: 618811, + value: 66600, + address: 'bc1qzqjwye4musmz56cg44ttnchj49zueh9yr0qsxt', + txId: '5df595dc09ee7a5c245b34ea519288137ffee731629c4ff322a6de4f72c06222', + vout: 0, + txid: '5df595dc09ee7a5c245b34ea519288137ffee731629c4ff322a6de4f72c06222', + amount: 66600, + wif: false, + confirmations: 1, + }, + ]; + + let { psbt } = await w.createTransaction( + utxos, + [{ address: 'bc1qdamevhw3zwm0ajsmyh39x8ygf0jr0syadmzepn', value: 5000 }], + 22, + 'bc1qtutssamysdkgd87df0afjct0mztx56qpze7wqe', + ); + assert.strictEqual( + psbt.toBase64(), + 'cHNidP8BAHECAAAAASJiwHJP3qYi80+cYjHn/n8TiJJR6jRbJFx67gnclfVdAAAAAAAAAACAAogTAAAAAAAAFgAUb3eWXdETtv7KGyXiUxyIS+Q3wJ1K3QAAAAAAABYAFF8XCHdkg2yGn81L+plhb9iWamgBAAAAAAABAR8oBAEAAAAAABYAFBAk4ma75DYqawitVrni8qlFzNykIgYDNK9TxoCjQ8P0+qI2Hu4hrnXnJuYAC3h2puZbgRORp+sYFo3WA1QAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgL1DWeV+AfIP5RRB5zHv5vuXsIt8+rF9rrsji3FhQlhzBgWjdYDVAAAgAAAAIAAAACAAQAAAAAAAAAA', + ); + }); + it('can combine signed PSBT and prepare it for broadcast', async () => { let w = new WatchOnlyWallet(); w.setSecret('zpub6rjLjQVqVnj7crz9E4QWj4WgczmEseJq22u2B6k2HZr6NE2PQx3ZYg8BnbjN9kCfHymSeMd2EpwpM5iiz5Nrb3TzvddxW2RMcE3VXdVaXHk'); From 844ff8f6fca3ba7ed90ac8f90f565b1b156a38bf Mon Sep 17 00:00:00 2001 From: Overtorment Date: Tue, 25 Feb 2020 12:36:49 +0000 Subject: [PATCH 59/68] TST: mock rn fs --- tests/setup.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/setup.js b/tests/setup.js index 967ee57b..d03c68c1 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -19,4 +19,50 @@ jest.mock('react-native-default-preference', () => { setName: jest.fn(), set: jest.fn(), } +}) + +jest.mock('react-native-fs', () => { + return { + mkdir: jest.fn(), + moveFile: jest.fn(), + copyFile: jest.fn(), + pathForBundle: jest.fn(), + pathForGroup: jest.fn(), + getFSInfo: jest.fn(), + getAllExternalFilesDirs: jest.fn(), + unlink: jest.fn(), + exists: jest.fn(), + stopDownload: jest.fn(), + resumeDownload: jest.fn(), + isResumable: jest.fn(), + stopUpload: jest.fn(), + completeHandlerIOS: jest.fn(), + readDir: jest.fn(), + readDirAssets: jest.fn(), + existsAssets: jest.fn(), + readdir: jest.fn(), + setReadable: jest.fn(), + stat: jest.fn(), + readFile: jest.fn(), + read: jest.fn(), + readFileAssets: jest.fn(), + hash: jest.fn(), + copyFileAssets: jest.fn(), + copyFileAssetsIOS: jest.fn(), + copyAssetsVideoIOS: jest.fn(), + writeFile: jest.fn(), + appendFile: jest.fn(), + write: jest.fn(), + downloadFile: jest.fn(), + uploadFiles: jest.fn(), + touch: jest.fn(), + MainBundlePath: jest.fn(), + CachesDirectoryPath: jest.fn(), + DocumentDirectoryPath: jest.fn(), + ExternalDirectoryPath: jest.fn(), + ExternalStorageDirectoryPath: jest.fn(), + TemporaryDirectoryPath: jest.fn(), + LibraryDirectoryPath: jest.fn(), + PicturesDirectoryPath: jest.fn(), + } }) \ No newline at end of file From faead2f5fbecdc31082b6c935250374db1692da1 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Tue, 25 Feb 2020 12:50:16 +0000 Subject: [PATCH 60/68] TST: improvements --- tests/integration/deepLinkSchemaMatch.test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/integration/deepLinkSchemaMatch.test.js b/tests/integration/deepLinkSchemaMatch.test.js index 3ba784bc..45de7c76 100644 --- a/tests/integration/deepLinkSchemaMatch.test.js +++ b/tests/integration/deepLinkSchemaMatch.test.js @@ -4,12 +4,15 @@ const assert = require('assert'); describe('unit - DeepLinkSchemaMatch', function() { it('hasSchema', () => { - const hasSchema = DeeplinkSchemaMatch.hasSchema('bitcoin:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'); - assert.ok(hasSchema); + assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG')); + assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo')); + assert.ok(DeeplinkSchemaMatch.hasSchema('bitcoin:BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7?amount=666&label=Yo')); }); it('isBitcoin Address', () => { assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG')); + assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('bc1qykcp2x3djgdtdwelxn9z4j2y956npte0a4sref')); + assert.ok(DeeplinkSchemaMatch.isBitcoinAddress('BC1QYKCP2X3DJGDTDWELXN9Z4J2Y956NPTE0A4SREF')); }); it('isLighting Invoice', () => { From fb552b1f3bece0b96e44cb3328b5dd6e943d6540 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Tue, 25 Feb 2020 14:42:11 +0000 Subject: [PATCH 61/68] REF: coldcard support --- class/walletImport.js | 13 +++++++++++++ ios/BlueWallet/Info.plist | 2 +- loc/en.js | 2 +- screen/send/details.js | 11 ++++++----- screen/wallets/import.js | 12 +++++++++++- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/class/walletImport.js b/class/walletImport.js index 18f2f207..c3bd5d7c 100644 --- a/class/walletImport.js +++ b/class/walletImport.js @@ -18,6 +18,13 @@ const BlueApp = require('../BlueApp'); const loc = require('../loc'); export default class WalletImport { + /** + * + * @param w + * @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet + * @returns {Promise} + * @private + */ static async _saveWallet(w, additionalProperties) { try { const wallet = BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret && wallet.type !== PlaceholderWallet.type); @@ -68,6 +75,12 @@ export default class WalletImport { return BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type); } + /** + * + * @param importText + * @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet + * @returns {Promise} + */ static async processImportText(importText, additionalProperties) { if (WalletImport.isCurrentlyImportingWallet()) { return; diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index 718be62f..ea94fd1c 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -96,7 +96,7 @@ NSCameraUsageDescription In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. NSFaceIDUsageDescription - In order to confirm your identity, we need your permission to use FaceID. + In order to use FaceID please confirm your permission. NSLocationAlwaysUsageDescription This alert should not show up as we do not require this data NSLocationWhenInUseUsageDescription diff --git a/loc/en.js b/loc/en.js index 009d0c70..c823bc46 100644 --- a/loc/en.js +++ b/loc/en.js @@ -80,7 +80,7 @@ module.exports = { error: 'Failed to import. Please, make sure that the provided data is valid.', success: 'Success', do_import: 'Import', - scan_qr: 'or scan QR code instead?', + scan_qr: '...scan QR or import file instead?', }, scanQrWif: { go_back: 'Go Back', diff --git a/screen/send/details.js b/screen/send/details.js index 58e0fc11..4888c05a 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -725,7 +725,8 @@ export default class SendDetails extends Component { 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 + // user whether he wants to broadcast it. + // alternatively, user can export psbt file, sign it externally and then import it this.props.navigation.navigate('PsbtWithHardwareWallet', { memo: this.state.memo, fromWallet: this.state.fromWallet, @@ -786,10 +787,10 @@ export default class SendDetails extends Component { onSwitch={this.onReplaceableFeeSwitchValueChanged} /> )} - {(this.state.fromWallet.type === HDSegwitBech32Wallet.type || - this.state.fromWallet._hdWalletInstance instanceof HDSegwitBech32Wallet) && ( - - )} + {this.state.fromWallet.type === WatchOnlyWallet.type && + this.state.fromWallet._hdWalletInstance instanceof HDSegwitBech32Wallet && ( + + )} {this.state.fromWallet.allowBatchSend() && ( <> { importMnemonic(importText); }; + /** + * + * @param importText + * @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet + */ const importMnemonic = (importText, additionalProperties) => { try { WalletImport.processImportText(importText, additionalProperties); @@ -45,6 +50,11 @@ const WalletsImport = () => { } }; + /** + * + * @param value + * @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet + */ const onBarScanned = (value, additionalProperties) => { setImportText(value); importMnemonic(value, additionalProperties); @@ -108,7 +118,7 @@ const WalletsImport = () => { onPress={importButtonPressed} /> { navigate('ScanQrAddress', { launchedBy: 'ImportWallet', onBarScanned, showFileImportButton: true }); }} From c3c76d7b394618e6b42ea20d2449a77a50d16212 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Wed, 26 Feb 2020 14:39:19 +0000 Subject: [PATCH 62/68] REF: coldcard & other --- class/watch-only-wallet.js | 23 ++++++++++++++++++++--- loc/en.js | 1 + screen/send/details.js | 3 ++- screen/send/psbtWithHardwareWallet.js | 6 +++--- screen/wallets/details.js | 19 +++++++++++++++---- screen/wallets/transactions.js | 15 +++++++++------ tests/integration/WatchOnlyWallet.test.js | 5 +++++ 7 files changed, 55 insertions(+), 17 deletions(-) diff --git a/class/watch-only-wallet.js b/class/watch-only-wallet.js index 2bf0bbdb..59445f77 100644 --- a/class/watch-only-wallet.js +++ b/class/watch-only-wallet.js @@ -15,18 +15,22 @@ export class WatchOnlyWallet extends LegacyWallet { } allowSend() { - return !!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSend(); + return ( + this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSend() + ); } allowBatchSend() { return ( - !!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowBatchSend() + this.useWithHardwareWalletEnabled() && + this._hdWalletInstance instanceof HDSegwitBech32Wallet && + this._hdWalletInstance.allowBatchSend() ); } allowSendMax() { return ( - !!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax() + this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax() ); } @@ -158,6 +162,7 @@ export class WatchOnlyWallet extends LegacyWallet { } getMasterFingerprintHex() { + if (!this.masterFingerprint) return '00000000'; let masterFingerprintHex = Number(this.masterFingerprint).toString(16); if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte // poor man's little-endian conversion: @@ -173,4 +178,16 @@ export class WatchOnlyWallet extends LegacyWallet { masterFingerprintHex[1] ); } + + isHd() { + return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub'); + } + + useWithHardwareWalletEnabled() { + return !!this.use_with_hardware_wallet; + } + + setUseWithHardwareWalletEnabled(enabled) { + this.use_with_hardware_wallet = !!enabled; + } } diff --git a/loc/en.js b/loc/en.js index c823bc46..db6ec891 100644 --- a/loc/en.js +++ b/loc/en.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Wallet', address: 'Address', + master_fingerprint: 'Master fingerprint', type: 'Type', label: 'Label', destination: 'destination', diff --git a/screen/send/details.js b/screen/send/details.js index 4888c05a..50b365f2 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -788,7 +788,8 @@ export default class SendDetails extends Component { /> )} {this.state.fromWallet.type === WatchOnlyWallet.type && - this.state.fromWallet._hdWalletInstance instanceof HDSegwitBech32Wallet && ( + this.state.fromWallet.isHd() && + this.state.fromWallet.getSecret().startsWith('zpub') && ( )} {this.state.fromWallet.allowBatchSend() && ( diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 8d7dd015..b7b05a74 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -47,15 +47,15 @@ export default class PsbtWithHardwareWallet extends Component { cameraRef = null; onBarCodeRead = ret => { + if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview(); + if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) { // this looks like NOT base64, so maybe its transaction's hex - this.setState({ txhex: ret.data }); + this.setState({ renderScanner: false, txhex: ret.data }); return; } - if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview(); this.setState({ renderScanner: false }, () => { - console.log(ret.data); try { let Tx = this.state.fromWallet.combinePsbt( this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(), diff --git a/screen/wallets/details.js b/screen/wallets/details.js index 3d0762d9..7e26ef43 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -20,7 +20,7 @@ import { HDLegacyP2PKHWallet } from '../../class/hd-legacy-p2pkh-wallet'; 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 { HDSegwitBech32Wallet, SegwitP2SHWallet, LegacyWallet, SegwitBech32Wallet, WatchOnlyWallet } from '../../class'; import { ScrollView } from 'react-native-gesture-handler'; const EV = require('../../events'); const prompt = require('../../prompt'); @@ -56,7 +56,7 @@ export default class WalletDetails extends Component { isLoading, walletName: wallet.getLabel(), wallet, - useWithHardwareWallet: !!wallet.use_with_hardware_wallet, + useWithHardwareWallet: wallet.useWithHardwareWalletEnabled(), }; this.props.navigation.setParams({ isLoading, saveAction: () => this.setLabel() }); } @@ -110,7 +110,7 @@ export default class WalletDetails extends Component { async onUseWithHardwareWalletSwitch(value) { this.setState((state, props) => { let wallet = state.wallet; - wallet.use_with_hardware_wallet = !!value; + wallet.setUseWithHardwareWalletEnabled(value); return { useWithHardwareWallet: !!value, wallet }; }); } @@ -130,7 +130,10 @@ export default class WalletDetails extends Component { {(() => { - if (this.state.wallet.getAddress()) { + if ( + [LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(this.state.wallet.type) || + (this.state.wallet.type === WatchOnlyWallet.type && !this.state.wallet.isHd()) + ) { return ( @@ -200,6 +203,14 @@ export default class WalletDetails extends Component { onValueChange={value => this.onUseWithHardwareWalletSwitch(value)} /> + + + {loc.wallets.details.master_fingerprint.toLowerCase()} + + + {this.state.wallet.getMasterFingerprintHex()} + + )} diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 0ebfeeb8..264c54dd 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -30,7 +30,7 @@ import { } from '../../BlueComponents'; import WalletGradient from '../../class/walletGradient'; import { Icon } from 'react-native-elements'; -import { LightningCustodianWallet, HDSegwitBech32Wallet } from '../../class'; +import { LightningCustodianWallet, WatchOnlyWallet } from '../../class'; import Handoff from 'react-native-handoff'; import Modal from 'react-native-modal'; import NavigationService from '../../NavigationService'; @@ -580,7 +580,9 @@ export default class WalletTransactions extends Component { {(() => { if ( this.state.wallet.allowSend() || - (this.state.wallet._hdWalletInstance instanceof HDSegwitBech32Wallet && this.state.wallet._hdWalletInstance.allowSend()) + (this.state.wallet.type === WatchOnlyWallet.type && + this.state.wallet.isHd() && + this.state.wallet.getSecret().startsWith('zpub')) ) { return ( { const wallet = this.state.wallet; - wallet.use_with_hardware_wallet = true; + wallet.setUseWithHardwareWalletEnabled(true); this.setState({ wallet }, async () => { await BlueApp.saveToDisk(); this.navigateToSendScreen(); diff --git a/tests/integration/WatchOnlyWallet.test.js b/tests/integration/WatchOnlyWallet.test.js index 3beacaba..94ff5525 100644 --- a/tests/integration/WatchOnlyWallet.test.js +++ b/tests/integration/WatchOnlyWallet.test.js @@ -56,8 +56,10 @@ describe('Watch only wallet', () => { let w = new WatchOnlyWallet(); w.setSecret('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG'); assert.ok(w.valid()); + assert.strictEqual(w.isHd(), false); w.setSecret('3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'); assert.ok(w.valid()); + assert.strictEqual(w.isHd(), false); w.setSecret('not valid'); assert.ok(!w.valid()); @@ -67,6 +69,9 @@ describe('Watch only wallet', () => { assert.ok(w.valid()); w.setSecret('zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP'); assert.ok(w.valid()); + assert.strictEqual(w.isHd(), true); + assert.strictEqual(w.getMasterFingerprint(), false); + assert.strictEqual(w.getMasterFingerprintHex(), '00000000'); }); it('can fetch balance & transactions from zpub HD', async () => { From 590b802afb7f622fc7896258b08c1735198c0785 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Wed, 26 Feb 2020 16:23:35 +0000 Subject: [PATCH 63/68] FIX: more locale strings --- loc/ZAR_Afr.js | 9 +++++++++ loc/ZAR_Xho.js | 9 +++++++++ loc/cs_CZ.js | 1 + loc/da_DK.js | 3 +++ loc/de_DE.js | 2 ++ loc/el.js | 1 + loc/es.js | 1 + loc/fi_FI.js | 1 + loc/fr_FR.js | 1 + loc/hr_HR.js | 3 +++ loc/hu_HU.js | 4 ++++ loc/id_ID.js | 1 + loc/it.js | 1 + loc/jp_JP.js | 1 + loc/nb_NO.js | 1 + loc/nl_NL.js | 1 + loc/pt_BR.js | 1 + loc/pt_PT.js | 1 + loc/ru.js | 1 + loc/sv_SE.js | 2 ++ loc/th_TH.js | 1 + loc/tr_TR.js | 1 + loc/ua.js | 1 + loc/vi_VN.js | 1 + loc/zh_cn.js | 2 ++ loc/zh_tw.js | 5 +++++ tests/integration/Loc.test.js | 18 +++++++++++------- 27 files changed, 67 insertions(+), 7 deletions(-) diff --git a/loc/ZAR_Afr.js b/loc/ZAR_Afr.js index 7b18a3b6..752a63d8 100644 --- a/loc/ZAR_Afr.js +++ b/loc/ZAR_Afr.js @@ -25,6 +25,9 @@ latest_transaction: 'laaste transaksie', empty_txs1: 'U transaksies is hier beskikbaar,', empty_txs2: 'huidiglik geen transaksies', + empty_txs1_lightning: + 'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.', + empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.', tap_here_to_buy: 'Raak hier om Bitcoin te koop', }, reorder: { @@ -50,6 +53,7 @@ details: { title: 'Beursiet', address: 'AdresAddress', + master_fingerprint: 'Master fingerprint', type: 'Tipe', label: 'Etiket', destination: 'bestemming', @@ -165,6 +169,7 @@ create: 'Skep', setAmount: 'Bedrag ontvang', }, + scan_lnurl: 'Scan to receive', }, buyBitcoin: { header: 'Koop Bitcoin', @@ -186,10 +191,14 @@ 'Om u eie LND node te konnekteer, installeer asseblief LndHub' + ' and put its URL here in settings. Leave blank om die standaard LndHub' + '(lndhub.io) te gebruik', + electrum_settings: 'Electrum Settings', + electrum_settings_explain: 'Set to blank to use default', save: 'stoor', about: 'info', language: 'Taal', currency: 'Geldeenheid', + advanced_options: 'Advanced Options', + enable_advanced_mode: 'Enable advanced mode', }, plausibledeniability: { title: 'Geloofwaardige Ontkenbaarheid', diff --git a/loc/ZAR_Xho.js b/loc/ZAR_Xho.js index b31c1deb..b4cb4dcc 100644 --- a/loc/ZAR_Xho.js +++ b/loc/ZAR_Xho.js @@ -23,6 +23,9 @@ latest_transaction: 'Utshintsho olutsha', empty_txs1: 'Intengiso yakho iya kubonakala apha,', empty_txs2: 'akuho nanye okwangoku', + empty_txs1_lightning: + 'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.', + empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.', tap_here_to_buy: 'Cofa apha ukuthenga ibitcoin', }, reorder: { @@ -48,6 +51,7 @@ details: { title: 'Ingxowa', address: 'Ikheli', + master_fingerprint: 'Master fingerprint', type: 'Uhlobo', label: 'Igama', destination: 'ukuya kuyo', @@ -163,6 +167,7 @@ create: 'Yenza', setAmount: 'Fumana ngexabiso', }, + scan_lnurl: 'Scan to receive', }, buyBitcoin: { header: 'Thenga Ibitcoin', @@ -183,10 +188,14 @@ lightning_settings_explain: 'Ukuxhuma kwi-node yakho ye-LND nceda ufake iLndHub' + ' kwaye ufake iURL apha izicwangciso. Shiya kungenanto yokusebenzisa iLndHub (Indhub.io)', + electrum_settings: 'Electrum Settings', + electrum_settings_explain: 'Set to blank to use default', save: 'ndoloza', about: 'Malunga', language: 'Ulwimi', currency: 'Lwemali', + advanced_options: 'Advanced Options', + enable_advanced_mode: 'Enable advanced mode', }, plausibledeniability: { title: 'Ukuphika', diff --git a/loc/cs_CZ.js b/loc/cs_CZ.js index bb7a41b8..c7076704 100644 --- a/loc/cs_CZ.js +++ b/loc/cs_CZ.js @@ -50,6 +50,7 @@ module.exports = { details: { title: 'Peněženka', address: 'Adresa', + master_fingerprint: 'Master fingerprint', type: 'Typ', label: 'Popisek', destination: 'cíl', diff --git a/loc/da_DK.js b/loc/da_DK.js index 1fb863e2..364446df 100644 --- a/loc/da_DK.js +++ b/loc/da_DK.js @@ -50,6 +50,7 @@ module.exports = { details: { title: 'Wallet', address: 'Adresse', + master_fingerprint: 'Master fingerprint', type: 'Type', label: 'Etiket', destination: 'destination', @@ -223,6 +224,8 @@ module.exports = { refill_lnd_balance: 'Genopfyld Lightning wallet', refill: 'Genopfyld', withdraw: 'Træk coins tilbage', + expired: 'Expired', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, pleasebackup: { title: 'Your wallet is created...', diff --git a/loc/de_DE.js b/loc/de_DE.js index a330646b..bafbe42b 100644 --- a/loc/de_DE.js +++ b/loc/de_DE.js @@ -52,6 +52,7 @@ module.exports = { details: { title: 'Wallet', address: 'Adresse', + master_fingerprint: 'Master fingerprint', type: 'Typ', label: 'Bezeichnung', destination: 'Zieladresse', @@ -225,6 +226,7 @@ module.exports = { refill_lnd_balance: 'Lade deine Lightning Wallet auf', refill: 'Aufladen', withdraw: 'Abheben', + expired: 'Expired', placeholder: 'Invoice', sameWalletAsInvoiceError: 'Du kannst nicht die Rechnung mit der Wallet begleichen, die du für die Erstellung dieser Rechnung verwendet hast.', diff --git a/loc/el.js b/loc/el.js index 1036bd76..d1f2872d 100644 --- a/loc/el.js +++ b/loc/el.js @@ -53,6 +53,7 @@ module.exports = { details: { title: 'Πορτοφόλι', address: 'Διεύθυνση', + master_fingerprint: 'Master fingerprint', type: 'Τύπος', label: 'Ετικέτα', destination: 'προορισμός', diff --git a/loc/es.js b/loc/es.js index 2ee32648..c52bd697 100644 --- a/loc/es.js +++ b/loc/es.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Detalles de la billetera', address: 'Dirección', + master_fingerprint: 'Master fingerprint', type: 'Tipo', label: 'Etiqueta', delete: 'Eliminar', diff --git a/loc/fi_FI.js b/loc/fi_FI.js index e1d4c215..09a7fec6 100644 --- a/loc/fi_FI.js +++ b/loc/fi_FI.js @@ -53,6 +53,7 @@ module.exports = { details: { title: 'Lompakko', address: 'Osoite', + master_fingerprint: 'Master fingerprint', type: 'Tyyppi', label: 'Etiketti', destination: 'määränpää', diff --git a/loc/fr_FR.js b/loc/fr_FR.js index 6d4c58cd..34f3f085 100644 --- a/loc/fr_FR.js +++ b/loc/fr_FR.js @@ -52,6 +52,7 @@ module.exports = { details: { title: 'Portefeuille', address: 'Adresse', + master_fingerprint: 'Master fingerprint', type: 'Type', label: 'Libelé', destination: 'destination', diff --git a/loc/hr_HR.js b/loc/hr_HR.js index e3aa5e47..700deb0f 100644 --- a/loc/hr_HR.js +++ b/loc/hr_HR.js @@ -10,6 +10,8 @@ module.exports = { wallets: { select_wallet: 'Odaberi volet', options: 'opcije', + createBitcoinWallet: + 'You currently do not have a Bitcoin wallet. In order to fund a Lightning wallet, a Bitcoin wallet needs to be created or imported. Would you like to continue anyway?', list: { app_name: 'BlueWallet', title: 'Voleti', @@ -48,6 +50,7 @@ module.exports = { details: { title: 'Volet', address: 'Adresa', + master_fingerprint: 'Master fingerprint', type: 'Tip', label: 'Oznaka', destination: 'odredište', diff --git a/loc/hu_HU.js b/loc/hu_HU.js index e2b66897..78924abf 100644 --- a/loc/hu_HU.js +++ b/loc/hu_HU.js @@ -51,8 +51,10 @@ module.exports = { details: { title: 'Tárca', address: 'Cím', + master_fingerprint: 'Master fingerprint', type: 'Típus', label: 'Cimke', + destination: 'destination', description: 'leírás', are_you_sure: 'Biztos vagy benne?', yes_delete: 'Igen, töröld', @@ -187,6 +189,8 @@ module.exports = { 'Saját LND-csomóponthoz való csatlakozáshoz telepítsd az LndHub-ot' + ' és írd be az URL-ét alul. Hagyd üresen, ha a BlueWallet saját LNDHub-jához (lndhub.io) szeretnél csatlakozni.' + ' A beállítások mentése után, minden újonnan létrehozott tárca a megadott LDNHubot fogja használni.', + electrum_settings: 'Electrum Settings', + electrum_settings_explain: 'Set to blank to use default', save: 'Ment', about: 'Egyéb', language: 'Nyelv', diff --git a/loc/id_ID.js b/loc/id_ID.js index 3aaa9df6..ac3b0070 100644 --- a/loc/id_ID.js +++ b/loc/id_ID.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Dompet', address: 'Alamat', + master_fingerprint: 'Master fingerprint', type: 'Tipe', label: 'Label', destination: 'tujuan', diff --git a/loc/it.js b/loc/it.js index 5f7ffc16..097003fa 100644 --- a/loc/it.js +++ b/loc/it.js @@ -53,6 +53,7 @@ module.exports = { details: { title: 'Portafoglio', address: 'Indirizzo', + master_fingerprint: 'Master fingerprint', type: 'Tipo', label: 'Etichetta', destination: 'Destinazione', diff --git a/loc/jp_JP.js b/loc/jp_JP.js index f3ceec3b..6fd7dcdb 100644 --- a/loc/jp_JP.js +++ b/loc/jp_JP.js @@ -50,6 +50,7 @@ module.exports = { details: { title: 'ウォレット', address: 'アドレス', + master_fingerprint: 'Master fingerprint', type: 'タイプ', label: 'ラベル', destination: '送り先', diff --git a/loc/nb_NO.js b/loc/nb_NO.js index 8eee0484..9e821e28 100644 --- a/loc/nb_NO.js +++ b/loc/nb_NO.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Lommebok', address: 'Adresse', + master_fingerprint: 'Master fingerprint', type: 'Type', label: 'Merkelapp', destination: 'mål', diff --git a/loc/nl_NL.js b/loc/nl_NL.js index fdf83afe..689e4a2f 100644 --- a/loc/nl_NL.js +++ b/loc/nl_NL.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Portemonnee', address: 'Adres', + master_fingerprint: 'Master fingerprint', type: 'Type', label: 'Label', destination: 'bestemming', diff --git a/loc/pt_BR.js b/loc/pt_BR.js index 428f01c6..0782ea2d 100644 --- a/loc/pt_BR.js +++ b/loc/pt_BR.js @@ -53,6 +53,7 @@ module.exports = { details: { title: 'Carteira', address: 'Endereço', + master_fingerprint: 'Master fingerprint', type: 'Tipo', destination: 'destino', description: 'descrição', diff --git a/loc/pt_PT.js b/loc/pt_PT.js index 6784539b..b0996778 100644 --- a/loc/pt_PT.js +++ b/loc/pt_PT.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'wallet', address: 'Endereço', + master_fingerprint: 'Master fingerprint', type: 'Tipo', delete: 'Eliminar', save: 'Guardar', diff --git a/loc/ru.js b/loc/ru.js index 17b0a9b0..f8d872c8 100644 --- a/loc/ru.js +++ b/loc/ru.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Информация о кошельке', address: 'Адрес', + master_fingerprint: 'Master fingerprint', type: 'Тип', label: 'Метка', delete: 'Удалить', diff --git a/loc/sv_SE.js b/loc/sv_SE.js index 35b855dd..04c92026 100644 --- a/loc/sv_SE.js +++ b/loc/sv_SE.js @@ -51,8 +51,10 @@ module.exports = { details: { title: 'Plånbok', address: 'Adress', + master_fingerprint: 'Master fingerprint', type: 'Typ', label: 'Etikett', + destination: 'destination', description: 'beskrivning', are_you_sure: 'Är du säker?', yes_delete: 'Ja, ta bort', diff --git a/loc/th_TH.js b/loc/th_TH.js index 3869a463..0972d7e1 100644 --- a/loc/th_TH.js +++ b/loc/th_TH.js @@ -50,6 +50,7 @@ module.exports = { details: { title: 'กระเป๋าสตางค์', address: 'แอดเดรส', + master_fingerprint: 'Master fingerprint', type: 'ชนิด', label: 'ป้าย', destination: 'เป้าหมาย', diff --git a/loc/tr_TR.js b/loc/tr_TR.js index b536c91a..a7ca5d71 100644 --- a/loc/tr_TR.js +++ b/loc/tr_TR.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Cüzdan', address: 'Adres', + master_fingerprint: 'Master fingerprint', type: 'Tip', label: 'Etiket', destination: 'hedef', diff --git a/loc/ua.js b/loc/ua.js index cbce9b9f..99cce004 100644 --- a/loc/ua.js +++ b/loc/ua.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Інформація про Гаманець', address: 'Адреса', + master_fingerprint: 'Master fingerprint', type: 'Тип', delete: 'Delete', save: 'Save', diff --git a/loc/vi_VN.js b/loc/vi_VN.js index 1c50cd23..4c83a71d 100644 --- a/loc/vi_VN.js +++ b/loc/vi_VN.js @@ -51,6 +51,7 @@ module.exports = { details: { title: 'Wallet', address: 'Address', + master_fingerprint: 'Master fingerprint', type: 'Type', label: 'Label', destination: 'destination', diff --git a/loc/zh_cn.js b/loc/zh_cn.js index 956754b2..dc15a687 100755 --- a/loc/zh_cn.js +++ b/loc/zh_cn.js @@ -49,6 +49,7 @@ module.exports = { details: { title: '钱包', address: '地址', + master_fingerprint: 'Master fingerprint', type: '类型', label: '标签', destination: '目的', @@ -219,6 +220,7 @@ module.exports = { withdraw: '提取', expired: '超时', sameWalletAsInvoiceError: '你不能用创建账单的钱包去支付该账单', + placeholder: 'Invoice', }, pleasebackup: { title: 'Your wallet is created...', diff --git a/loc/zh_tw.js b/loc/zh_tw.js index cc16163c..423a9ef3 100644 --- a/loc/zh_tw.js +++ b/loc/zh_tw.js @@ -49,6 +49,7 @@ module.exports = { details: { title: '錢包', address: '地址', + master_fingerprint: 'Master fingerprint', type: '類型', label: '標籤', destination: '目的', @@ -163,6 +164,7 @@ module.exports = { create: '建立', setAmount: '收款金額', }, + scan_lnurl: 'Scan to receive', }, buyBitcoin: { header: '購買比特幣', @@ -181,6 +183,8 @@ module.exports = { encrypt_storage: '加密儲存', lightning_settings: '閃電網路設定', lightning_settings_explain: '如要要連線你自己的閃電節點請安裝LndHub' + ' 並把url地址輸入到下面. 空白將使用預設的LndHub (lndhub.io)', + electrum_settings: 'Electrum Settings', + electrum_settings_explain: 'Set to blank to use default', save: '儲存', about: '關於', language: '語言', @@ -215,6 +219,7 @@ module.exports = { refill: '充值', withdraw: '提取', expired: '超時', + placeholder: 'Invoice', sameWalletAsInvoiceError: '你不能用建立賬單的錢包去支付該賬單', }, pleasebackup: { diff --git a/tests/integration/Loc.test.js b/tests/integration/Loc.test.js index 12c5bc8f..d9e0bc9c 100644 --- a/tests/integration/Loc.test.js +++ b/tests/integration/Loc.test.js @@ -1,24 +1,28 @@ /* global it, describe */ let assert = require('assert'); +const fs = require('fs'); describe('Localization', () => { it('has all keys in all locales', async () => { let en = require('../../loc/en'); - let noErrors = true; + let issues = 0; for (let key1 of Object.keys(en)) { for (let key2 of Object.keys(en[key1])) { // iterating all keys and subkeys in EN locale, which is main + let files = fs.readdirSync('./loc/'); + + for (let lang of files) { + if (lang === 'en.js') continue; // iteratin all locales except EN + if (lang === 'index.js') continue; - for (let lang of ['es', 'pt_BR', 'pt_PT', 'ru', 'ua']) { - // iteratin all locales except EN let locale = require('../../loc/' + lang); if (typeof locale[key1] === 'undefined') { console.error('Missing: ' + lang + '.' + key1); - noErrors = false; + issues++; } else if (typeof locale[key1][key2] === 'undefined') { console.error('Missing: ' + lang + '.' + key1 + '.' + key2); - noErrors = false; + issues++; } // level 1 & 2 done, doing level 3 (if it exists): @@ -27,13 +31,13 @@ describe('Localization', () => { for (let key3 of Object.keys(en[key1][key2])) { if (typeof locale[key1][key2][key3] === 'undefined') { console.error('Missing: ' + lang + '.' + key1 + '.' + key2 + '.' + key3); - noErrors = false; + issues++; } } } } } } - assert.ok(noErrors, 'Some localizations are missing keys'); + assert.ok(issues === 0, 'Some localizations are missing keys. Total issues: ' + issues); }); }); From b3baf3f2901a53faa010cb5875b54da0e9f75a14 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Thu, 27 Feb 2020 14:24:26 +0000 Subject: [PATCH 64/68] FIX: coldcard --- class/abstract-wallet.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/class/abstract-wallet.js b/class/abstract-wallet.js index 7cbc54df..8d09df9e 100644 --- a/class/abstract-wallet.js +++ b/class/abstract-wallet.js @@ -1,5 +1,6 @@ import { BitcoinUnit, Chain } from '../models/bitcoinUnits'; const createHash = require('create-hash'); + export class AbstractWallet { static type = 'abstract'; static typeReadable = 'abstract'; @@ -157,4 +158,8 @@ export class AbstractWallet { getAddressAsync() { return new Promise(resolve => resolve(this.getAddress())); } + + useWithHardwareWalletEnabled() { + return false; + } } From 84938c655e0ae2f75e889035518957fe9fa6f55d Mon Sep 17 00:00:00 2001 From: Overtorment Date: Thu, 27 Feb 2020 14:41:44 +0000 Subject: [PATCH 65/68] FIX: lnurl scan to receive is not returning the correct view (closes #828) --- screen/lnd/lndCreateInvoice.js | 2 +- screen/send/scanQrAddress.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/screen/lnd/lndCreateInvoice.js b/screen/lnd/lndCreateInvoice.js index 281738d9..d159f721 100644 --- a/screen/lnd/lndCreateInvoice.js +++ b/screen/lnd/lndCreateInvoice.js @@ -210,7 +210,7 @@ export default class LNDCreateInvoice extends Component { { - NavigationService.navigate('ScanQrAddress', { onBarScanned: this.processLnurl }); + NavigationService.navigate('ScanQrAddress', { onBarScanned: this.processLnurl, launchedBy: this.props.navigation.state.routeName }); Keyboard.dismiss(); }} style={{ diff --git a/screen/send/scanQrAddress.js b/screen/send/scanQrAddress.js index dd1cc5ab..7e50e56f 100644 --- a/screen/send/scanQrAddress.js +++ b/screen/send/scanQrAddress.js @@ -14,6 +14,7 @@ const ScanQRCode = ({ showCloseButton = true, launchedBy = useNavigationParam('launchedBy'), }) => { + if (!launchedBy || !onBarScanned) console.warn('Necessary params missing'); const [isLoading, setIsLoading] = useState(false); const { navigate, goBack } = useNavigation(); From f519df22da4e8f914d3e51c17e75ed0a5955539f Mon Sep 17 00:00:00 2001 From: Overtorment Date: Thu, 27 Feb 2020 15:38:13 +0000 Subject: [PATCH 66/68] REF: lint --- screen/lnd/lndCreateInvoice.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/screen/lnd/lndCreateInvoice.js b/screen/lnd/lndCreateInvoice.js index d159f721..e5a4673e 100644 --- a/screen/lnd/lndCreateInvoice.js +++ b/screen/lnd/lndCreateInvoice.js @@ -210,7 +210,10 @@ export default class LNDCreateInvoice extends Component { { - NavigationService.navigate('ScanQrAddress', { onBarScanned: this.processLnurl, launchedBy: this.props.navigation.state.routeName }); + NavigationService.navigate('ScanQrAddress', { + onBarScanned: this.processLnurl, + launchedBy: this.props.navigation.state.routeName, + }); Keyboard.dismiss(); }} style={{ @@ -352,6 +355,7 @@ LNDCreateInvoice.propTypes = { getParam: PropTypes.func, pop: PropTypes.func, state: PropTypes.shape({ + routeName: PropTypes.string, params: PropTypes.shape({ uri: PropTypes.string, fromWallet: PropTypes.shape({}), From fc8ba46368f30ff57eb553e6b0de75de35d72544 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Fri, 28 Feb 2020 05:58:51 +0000 Subject: [PATCH 67/68] fix: upgrade eslint-config-prettier from 6.0.0 to 6.10.0 Snyk has created this PR to upgrade eslint-config-prettier from 6.0.0 to 6.10.0. See this package in NPM: https://www.npmjs.com/package/eslint-config-prettier See this project in Snyk: https://app.snyk.io/org/bluewallet/project/4d0df22a-0152-410a-8584-6df0d0a596d4?utm_source=github&utm_medium=upgrade-pr --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f42c0ad1..ae93a9b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4447,9 +4447,9 @@ } }, "eslint-config-prettier": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.0.0.tgz", - "integrity": "sha512-vDrcCFE3+2ixNT5H83g28bO/uYAwibJxerXPj+E7op4qzBCsAV36QfvdAyVOoNxKAH2Os/e01T/2x++V0LPukA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", + "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", "requires": { "get-stdin": "^6.0.0" } diff --git a/package.json b/package.json index dbf92490..ca0f6774 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "dayjs": "1.8.14", "ecurve": "1.0.6", "electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git", - "eslint-config-prettier": "6.9.0", + "eslint-config-prettier": "6.10.0", "eslint-config-standard": "12.0.0", "eslint-config-standard-react": "7.0.2", "eslint-plugin-prettier": "3.1.0", From f7ea02e7a2a1c2c10781679eaa017b4fe325a32c Mon Sep 17 00:00:00 2001 From: Overtorment Date: Fri, 28 Feb 2020 12:34:08 +0000 Subject: [PATCH 68/68] REL: release notes --- ios/fastlane/metadata/en-US/release_notes.txt | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/ios/fastlane/metadata/en-US/release_notes.txt b/ios/fastlane/metadata/en-US/release_notes.txt index 05e232b8..27b500df 100644 --- a/ios/fastlane/metadata/en-US/release_notes.txt +++ b/ios/fastlane/metadata/en-US/release_notes.txt @@ -1,3 +1,12 @@ +v5.0.0 +====== + +* ADD: Coldcard support +* FIX: allow capitalized bech32 addresses (closes #838) +* FIX: lnurl scan to receive is not returning the correct view (closes #828) +* FIX: watch-only delete wallet doesnt have confirmation now +* FIX: typo in spanish + v4.9.4 ====== @@ -46,24 +55,4 @@ v4.9.0 * FIX: Hide Manage Funds button if wallet doesn't allow onchain refill. * FIX: LN Scan to receive is more visible * FIX: Quick actions not appearing on non-3d touch devices. -* FIX: Dont show clipboard modal when biometrics is dismissed - -v4.8.1 -====== - -* FIX: Updated biometrics -* FIX: Import QR Code from screenshot not working - -v4.8.0 -====== - -* ADD: Today Extension and Quick Actions -* ADD: Send max option on advanced menu -* ADD: Add Onchain address view for Lightning -* FIX: Allow textfield to be visible above keyboard -* FIX: lapp browser when typing URL without https scheme it doesnt work -* ADD: Value and memo to the success screen fix logic for both sent and receive -* FIX: layout for small devices with flexbox -* FIX: Dont allow zero invoices to enable create invoice button -* FIX: Change create button on Receive LN payment should be create invoice -* FIX: Update for watch \ No newline at end of file +* FIX: Dont show clipboard modal when biometrics is dismissed \ No newline at end of file