mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-03 20:07:11 +01:00
REF: Reworked Import wallet flow
This commit is contained in:
parent
ec5bc4a3c6
commit
c975347b6f
11 changed files with 581 additions and 750 deletions
|
@ -23,7 +23,7 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import { LightningCustodianWallet } from './class';
|
import { LightningCustodianWallet, PlaceholderWallet } from './class';
|
||||||
import Carousel from 'react-native-snap-carousel';
|
import Carousel from 'react-native-snap-carousel';
|
||||||
import { BitcoinUnit } from './models/bitcoinUnits';
|
import { BitcoinUnit } from './models/bitcoinUnits';
|
||||||
import NavigationService from './NavigationService';
|
import NavigationService from './NavigationService';
|
||||||
|
@ -748,6 +748,7 @@ export class BlueHeaderDefaultMain extends Component {
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
rightComponent={
|
rightComponent={
|
||||||
|
this.props.onNewWalletPress && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={this.props.onNewWalletPress}
|
onPress={this.props.onNewWalletPress}
|
||||||
style={{
|
style={{
|
||||||
|
@ -757,6 +758,7 @@ export class BlueHeaderDefaultMain extends Component {
|
||||||
>
|
>
|
||||||
<BluePlusIcon />
|
<BluePlusIcon />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
@ -957,7 +959,7 @@ export class BlueLoading extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SafeBlueArea>
|
<SafeBlueArea>
|
||||||
<View style={{ flex: 1, paddingTop: 200 }}>
|
<View style={{ flex: 1, paddingTop: 200 }} {...this.props}>
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
</View>
|
</View>
|
||||||
</SafeBlueArea>
|
</SafeBlueArea>
|
||||||
|
@ -1844,6 +1846,74 @@ export class WalletsCarousel extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.type === PlaceholderWallet.type) {
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
||||||
|
shadowOpacity={40 / 100}
|
||||||
|
shadowOffset={{ width: 0, height: 0 }}
|
||||||
|
shadowRadius={5}
|
||||||
|
>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
onPressIn={item.getIsFailure() ? this.onPressedIn : null}
|
||||||
|
onPressOut={item.getIsFailure() ? this.onPressedOut : null}
|
||||||
|
onPress={() => {
|
||||||
|
if (item.getIsFailure() && WalletsCarousel.handleClick) {
|
||||||
|
WalletsCarousel.handleClick(index);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LinearGradient
|
||||||
|
shadowColor={BlueApp.settings.shadowColor}
|
||||||
|
colors={WalletGradient.gradientsFor(item.type)}
|
||||||
|
style={{
|
||||||
|
padding: 15,
|
||||||
|
borderRadius: 10,
|
||||||
|
minHeight: 164,
|
||||||
|
elevation: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
source={require('./img/btc-shape.png')}
|
||||||
|
style={{
|
||||||
|
width: 99,
|
||||||
|
height: 94,
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={{ backgroundColor: 'transparent' }} />
|
||||||
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
fontSize: 19,
|
||||||
|
color: BlueApp.settings.inverseForegroundColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.getLabel()}
|
||||||
|
</Text>
|
||||||
|
{item.getIsFailure() ? (
|
||||||
|
<Text
|
||||||
|
numberOfLines={0}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
fontSize: 19,
|
||||||
|
marginTop: 40,
|
||||||
|
color: BlueApp.settings.inverseForegroundColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
An error was encountered when attempting to import this wallet.
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<ActivityIndicator style={{ marginTop: 40 }} />
|
||||||
|
)}
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
||||||
|
@ -1936,6 +2006,7 @@ export class WalletsCarousel extends Component {
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
snapToItem = item => {
|
snapToItem = item => {
|
||||||
this.walletsCarousel.current.snapToItem(item);
|
this.walletsCarousel.current.snapToItem(item);
|
||||||
|
|
|
@ -23,7 +23,6 @@ import WalletExport from './screen/wallets/export';
|
||||||
import WalletXpub from './screen/wallets/xpub';
|
import WalletXpub from './screen/wallets/xpub';
|
||||||
import BuyBitcoin from './screen/wallets/buyBitcoin';
|
import BuyBitcoin from './screen/wallets/buyBitcoin';
|
||||||
import Marketplace from './screen/wallets/marketplace';
|
import Marketplace from './screen/wallets/marketplace';
|
||||||
import scanQrWif from './screen/wallets/scanQrWif';
|
|
||||||
import ReorderWallets from './screen/wallets/reorderWallets';
|
import ReorderWallets from './screen/wallets/reorderWallets';
|
||||||
import SelectWallet from './screen/wallets/selectWallet';
|
import SelectWallet from './screen/wallets/selectWallet';
|
||||||
|
|
||||||
|
@ -277,9 +276,6 @@ const MainBottomTabs = createStackNavigator(
|
||||||
header: null,
|
header: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ScanQrWif: {
|
|
||||||
screen: scanQrWif,
|
|
||||||
},
|
|
||||||
WalletExport: {
|
WalletExport: {
|
||||||
screen: WalletExport,
|
screen: WalletExport,
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,8 +9,9 @@ import {
|
||||||
SegwitP2SHWallet,
|
SegwitP2SHWallet,
|
||||||
SegwitBech32Wallet,
|
SegwitBech32Wallet,
|
||||||
HDSegwitBech32Wallet,
|
HDSegwitBech32Wallet,
|
||||||
|
PlaceholderWallet,
|
||||||
|
LightningCustodianWallet,
|
||||||
} from './';
|
} from './';
|
||||||
import { LightningCustodianWallet } from './lightning-custodian-wallet';
|
|
||||||
import WatchConnectivity from '../WatchConnectivity';
|
import WatchConnectivity from '../WatchConnectivity';
|
||||||
import DeviceQuickActions from './quickActions';
|
import DeviceQuickActions from './quickActions';
|
||||||
const encryption = require('../encryption');
|
const encryption = require('../encryption');
|
||||||
|
@ -192,6 +193,8 @@ export class AppStorage {
|
||||||
let tempObj = JSON.parse(key);
|
let tempObj = JSON.parse(key);
|
||||||
let unserializedWallet;
|
let unserializedWallet;
|
||||||
switch (tempObj.type) {
|
switch (tempObj.type) {
|
||||||
|
case PlaceholderWallet.type:
|
||||||
|
continue;
|
||||||
case SegwitBech32Wallet.type:
|
case SegwitBech32Wallet.type:
|
||||||
unserializedWallet = SegwitBech32Wallet.fromJson(key);
|
unserializedWallet = SegwitBech32Wallet.fromJson(key);
|
||||||
break;
|
break;
|
||||||
|
@ -298,7 +301,7 @@ export class AppStorage {
|
||||||
async saveToDisk() {
|
async saveToDisk() {
|
||||||
let walletsToSave = [];
|
let walletsToSave = [];
|
||||||
for (let key of this.wallets) {
|
for (let key of this.wallets) {
|
||||||
if (typeof key === 'boolean') continue;
|
if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue;
|
||||||
if (key.prepareForSerialization) key.prepareForSerialization();
|
if (key.prepareForSerialization) key.prepareForSerialization();
|
||||||
walletsToSave.push(JSON.stringify({ ...key, type: key.type }));
|
walletsToSave.push(JSON.stringify({ ...key, type: key.type }));
|
||||||
}
|
}
|
||||||
|
@ -349,13 +352,13 @@ export class AppStorage {
|
||||||
console.log('fetchWalletBalances for wallet#', index);
|
console.log('fetchWalletBalances for wallet#', index);
|
||||||
if (index || index === 0) {
|
if (index || index === 0) {
|
||||||
let c = 0;
|
let c = 0;
|
||||||
for (let wallet of this.wallets) {
|
for (let wallet of this.wallets.filter(wallet => wallet.type !== PlaceholderWallet.type)) {
|
||||||
if (c++ === index) {
|
if (c++ === index) {
|
||||||
await wallet.fetchBalance();
|
await wallet.fetchBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let wallet of this.wallets) {
|
for (let wallet of this.wallets.filter(wallet => wallet.type !== PlaceholderWallet.type)) {
|
||||||
await wallet.fetchBalance();
|
await wallet.fetchBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +378,7 @@ export class AppStorage {
|
||||||
console.log('fetchWalletTransactions for wallet#', index);
|
console.log('fetchWalletTransactions for wallet#', index);
|
||||||
if (index || index === 0) {
|
if (index || index === 0) {
|
||||||
let c = 0;
|
let c = 0;
|
||||||
for (let wallet of this.wallets) {
|
for (let wallet of this.wallets.filter(wallet => wallet.type !== PlaceholderWallet.type)) {
|
||||||
if (c++ === index) {
|
if (c++ === index) {
|
||||||
await wallet.fetchTransactions();
|
await wallet.fetchTransactions();
|
||||||
if (wallet.fetchPendingTransactions) {
|
if (wallet.fetchPendingTransactions) {
|
||||||
|
|
|
@ -12,3 +12,4 @@ export * from './lightning-custodian-wallet';
|
||||||
export * from './abstract-hd-wallet';
|
export * from './abstract-hd-wallet';
|
||||||
export * from './hd-segwit-bech32-wallet';
|
export * from './hd-segwit-bech32-wallet';
|
||||||
export * from './hd-segwit-bech32-transaction';
|
export * from './hd-segwit-bech32-transaction';
|
||||||
|
export * from './placeholder-wallet';
|
||||||
|
|
31
class/placeholder-wallet.js
Normal file
31
class/placeholder-wallet.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { AbstractWallet } from './abstract-wallet';
|
||||||
|
|
||||||
|
export class PlaceholderWallet extends AbstractWallet {
|
||||||
|
static type = 'placeholder';
|
||||||
|
static typeReadable = 'Placeholder';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._isFailure = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
allowSend() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLabel() {
|
||||||
|
return this.getIsFailure() ? 'Wallet Import' : 'Importing Wallet...';
|
||||||
|
}
|
||||||
|
|
||||||
|
allowReceive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsFailure() {
|
||||||
|
return this._isFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsFailure(value) {
|
||||||
|
this._isFailure = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet';
|
||||||
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
|
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
|
||||||
import { WatchOnlyWallet } from './watch-only-wallet';
|
import { WatchOnlyWallet } from './watch-only-wallet';
|
||||||
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
||||||
|
import { PlaceholderWallet } from './placeholder-wallet';
|
||||||
|
|
||||||
export default class WalletGradient {
|
export default class WalletGradient {
|
||||||
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
|
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
|
||||||
|
@ -41,6 +42,9 @@ export default class WalletGradient {
|
||||||
case LightningCustodianWallet.type:
|
case LightningCustodianWallet.type:
|
||||||
gradient = WalletGradient.lightningCustodianWallet;
|
gradient = WalletGradient.lightningCustodianWallet;
|
||||||
break;
|
break;
|
||||||
|
case PlaceholderWallet.type:
|
||||||
|
gradient = WalletGradient.watchOnlyWallet;
|
||||||
|
break;
|
||||||
case 'CreateWallet':
|
case 'CreateWallet':
|
||||||
gradient = WalletGradient.createWallet;
|
gradient = WalletGradient.createWallet;
|
||||||
break;
|
break;
|
||||||
|
|
228
class/walletImport.js
Normal file
228
class/walletImport.js
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
/* global alert */
|
||||||
|
import {
|
||||||
|
SegwitP2SHWallet,
|
||||||
|
LegacyWallet,
|
||||||
|
WatchOnlyWallet,
|
||||||
|
HDLegacyBreadwalletWallet,
|
||||||
|
HDSegwitP2SHWallet,
|
||||||
|
HDLegacyP2PKHWallet,
|
||||||
|
HDSegwitBech32Wallet,
|
||||||
|
LightningCustodianWallet,
|
||||||
|
PlaceholderWallet,
|
||||||
|
} from '../class';
|
||||||
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
|
const EV = require('../events');
|
||||||
|
const A = require('../analytics');
|
||||||
|
/** @type {AppStorage} */
|
||||||
|
const BlueApp = require('../BlueApp');
|
||||||
|
const loc = require('../loc');
|
||||||
|
|
||||||
|
export default class WalletImport {
|
||||||
|
static async _saveWallet(w) {
|
||||||
|
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);
|
||||||
|
WalletImport.removePlaceholderWallet();
|
||||||
|
BlueApp.wallets.push(w);
|
||||||
|
await BlueApp.saveToDisk();
|
||||||
|
A(A.ENUM.CREATED_WALLET);
|
||||||
|
}
|
||||||
|
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||||
|
} catch (_e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static removePlaceholderWallet() {
|
||||||
|
const placeholderWalletIndex = BlueApp.wallets.findIndex(wallet => wallet.type === PlaceholderWallet.type);
|
||||||
|
if (placeholderWalletIndex > -1) {
|
||||||
|
BlueApp.wallets.splice(placeholderWalletIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static addPlaceholderWallet(importText, isFailure = false) {
|
||||||
|
const wallet = new PlaceholderWallet();
|
||||||
|
wallet.setSecret(importText);
|
||||||
|
wallet.setIsFailure(isFailure);
|
||||||
|
BlueApp.wallets.push(wallet);
|
||||||
|
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isCurrentlyImportingWallet() {
|
||||||
|
return BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async processImportText(importText) {
|
||||||
|
if (WalletImport.isCurrentlyImportingWallet()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const placeholderWallet = WalletImport.addPlaceholderWallet(importText);
|
||||||
|
// Plan:
|
||||||
|
// 0. check if its HDSegwitBech32Wallet (BIP84)
|
||||||
|
// 1. check if its HDSegwitP2SHWallet (BIP49)
|
||||||
|
// 2. check if its HDLegacyP2PKHWallet (BIP44)
|
||||||
|
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
|
||||||
|
// 4. check if its Segwit WIF (P2SH)
|
||||||
|
// 5. check if its Legacy WIF
|
||||||
|
// 6. check if its address (watch-only wallet)
|
||||||
|
// 7. check if its private key (segwit address P2SH) TODO
|
||||||
|
// 7. check if its private key (legacy address) TODO
|
||||||
|
|
||||||
|
try {
|
||||||
|
// is it lightning custodian?
|
||||||
|
if (importText.indexOf('blitzhub://') !== -1 || importText.indexOf('lndhub://') !== -1) {
|
||||||
|
let lnd = new LightningCustodianWallet();
|
||||||
|
if (importText.includes('@')) {
|
||||||
|
const split = importText.split('@');
|
||||||
|
lnd.setBaseURI(split[1]);
|
||||||
|
lnd.setSecret(split[0]);
|
||||||
|
} else {
|
||||||
|
lnd.setBaseURI(LightningCustodianWallet.defaultBaseUri);
|
||||||
|
lnd.setSecret(importText);
|
||||||
|
}
|
||||||
|
lnd.init();
|
||||||
|
await lnd.authorize();
|
||||||
|
await lnd.fetchTransactions();
|
||||||
|
await lnd.fetchUserInvoices();
|
||||||
|
await lnd.fetchPendingTransactions();
|
||||||
|
await lnd.fetchBalance();
|
||||||
|
return WalletImport._saveWallet(lnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trying other wallet types
|
||||||
|
|
||||||
|
let hd4 = new HDSegwitBech32Wallet();
|
||||||
|
hd4.setSecret(importText);
|
||||||
|
if (hd4.validateMnemonic()) {
|
||||||
|
await hd4.fetchBalance();
|
||||||
|
if (hd4.getBalance() > 0) {
|
||||||
|
await hd4.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(hd4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let segwitWallet = new SegwitP2SHWallet();
|
||||||
|
segwitWallet.setSecret(importText);
|
||||||
|
if (segwitWallet.getAddress()) {
|
||||||
|
// ok its a valid WIF
|
||||||
|
|
||||||
|
let legacyWallet = new LegacyWallet();
|
||||||
|
legacyWallet.setSecret(importText);
|
||||||
|
|
||||||
|
await legacyWallet.fetchBalance();
|
||||||
|
if (legacyWallet.getBalance() > 0) {
|
||||||
|
// yep, its legacy we're importing
|
||||||
|
await legacyWallet.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(legacyWallet);
|
||||||
|
} else {
|
||||||
|
// by default, we import wif as Segwit P2SH
|
||||||
|
await segwitWallet.fetchBalance();
|
||||||
|
await segwitWallet.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(segwitWallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// case - WIF is valid, just has uncompressed pubkey
|
||||||
|
|
||||||
|
let legacyWallet = new LegacyWallet();
|
||||||
|
legacyWallet.setSecret(importText);
|
||||||
|
if (legacyWallet.getAddress()) {
|
||||||
|
await legacyWallet.fetchBalance();
|
||||||
|
await legacyWallet.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(legacyWallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're here - nope, its not a valid WIF
|
||||||
|
|
||||||
|
let hd1 = new HDLegacyBreadwalletWallet();
|
||||||
|
hd1.setSecret(importText);
|
||||||
|
if (hd1.validateMnemonic()) {
|
||||||
|
await hd1.fetchBalance();
|
||||||
|
if (hd1.getBalance() > 0) {
|
||||||
|
await hd1.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(hd1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hd2 = new HDSegwitP2SHWallet();
|
||||||
|
hd2.setSecret(importText);
|
||||||
|
if (hd2.validateMnemonic()) {
|
||||||
|
await hd2.fetchBalance();
|
||||||
|
if (hd2.getBalance() > 0) {
|
||||||
|
await hd2.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(hd2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hd3 = new HDLegacyP2PKHWallet();
|
||||||
|
hd3.setSecret(importText);
|
||||||
|
if (hd3.validateMnemonic()) {
|
||||||
|
await hd3.fetchBalance();
|
||||||
|
if (hd3.getBalance() > 0) {
|
||||||
|
await hd3.fetchTransactions();
|
||||||
|
return WalletImport._saveWallet(hd3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no balances? how about transactions count?
|
||||||
|
|
||||||
|
if (hd1.validateMnemonic()) {
|
||||||
|
await hd1.fetchTransactions();
|
||||||
|
if (hd1.getTransactions().length !== 0) {
|
||||||
|
return WalletImport._saveWallet(hd1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hd2.validateMnemonic()) {
|
||||||
|
await hd2.fetchTransactions();
|
||||||
|
if (hd2.getTransactions().length !== 0) {
|
||||||
|
return WalletImport._saveWallet(hd2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hd3.validateMnemonic()) {
|
||||||
|
await hd3.fetchTransactions();
|
||||||
|
if (hd3.getTransactions().length !== 0) {
|
||||||
|
return WalletImport._saveWallet(hd3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hd4.validateMnemonic()) {
|
||||||
|
await hd4.fetchTransactions();
|
||||||
|
if (hd4.getTransactions().length !== 0) {
|
||||||
|
return WalletImport._saveWallet(hd4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is it even valid? if yes we will import as:
|
||||||
|
if (hd4.validateMnemonic()) {
|
||||||
|
return WalletImport._saveWallet(hd4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not valid? maybe its a watch-only address?
|
||||||
|
|
||||||
|
let watchOnly = new WatchOnlyWallet();
|
||||||
|
watchOnly.setSecret(importText);
|
||||||
|
if (watchOnly.valid()) {
|
||||||
|
await watchOnly.fetchTransactions();
|
||||||
|
await watchOnly.fetchBalance();
|
||||||
|
return WalletImport._saveWallet(watchOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// nope?
|
||||||
|
|
||||||
|
// TODO: try a raw private key
|
||||||
|
} catch (Err) {
|
||||||
|
WalletImport.removePlaceholderWallet(placeholderWallet);
|
||||||
|
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||||
|
console.warn(Err);
|
||||||
|
}
|
||||||
|
WalletImport.removePlaceholderWallet();
|
||||||
|
WalletImport.addPlaceholderWallet(importText, true);
|
||||||
|
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||||
|
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||||
|
alert(loc.wallets.import.error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,253 +1,54 @@
|
||||||
/* global alert */
|
/* global alert */
|
||||||
import {
|
import React, { useEffect, useState } from 'react';
|
||||||
SegwitP2SHWallet,
|
|
||||||
LegacyWallet,
|
|
||||||
WatchOnlyWallet,
|
|
||||||
HDLegacyBreadwalletWallet,
|
|
||||||
HDSegwitP2SHWallet,
|
|
||||||
HDLegacyP2PKHWallet,
|
|
||||||
HDSegwitBech32Wallet,
|
|
||||||
} from '../../class';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { KeyboardAvoidingView, Platform, Dimensions, View, TouchableWithoutFeedback, Keyboard } from 'react-native';
|
import { KeyboardAvoidingView, Platform, Dimensions, View, TouchableWithoutFeedback, Keyboard } from 'react-native';
|
||||||
import {
|
import {
|
||||||
BlueFormMultiInput,
|
BlueFormMultiInput,
|
||||||
BlueButtonLink,
|
BlueButtonLink,
|
||||||
BlueFormLabel,
|
BlueFormLabel,
|
||||||
BlueLoading,
|
|
||||||
BlueDoneAndDismissKeyboardInputAccessory,
|
BlueDoneAndDismissKeyboardInputAccessory,
|
||||||
BlueButton,
|
BlueButton,
|
||||||
SafeBlueArea,
|
SafeBlueArea,
|
||||||
BlueSpacing20,
|
BlueSpacing20,
|
||||||
BlueNavigationStyle,
|
BlueNavigationStyle,
|
||||||
} from '../../BlueComponents';
|
} from '../../BlueComponents';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
|
||||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
import Privacy from '../../Privacy';
|
import Privacy from '../../Privacy';
|
||||||
let EV = require('../../events');
|
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
|
||||||
let A = require('../../analytics');
|
import WalletImport from '../../class/walletImport';
|
||||||
/** @type {AppStorage} */
|
|
||||||
let BlueApp = require('../../BlueApp');
|
|
||||||
let loc = require('../../loc');
|
let loc = require('../../loc');
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
|
|
||||||
export default class WalletsImport extends Component {
|
const WalletsImport = () => {
|
||||||
static navigationOptions = {
|
const [isToolbarVisibleForAndroid, setIsToolbarVisibleForAndroid] = useState(false);
|
||||||
...BlueNavigationStyle(),
|
const [importText, setImportText] = useState(useNavigationParam('label') || '');
|
||||||
title: loc.wallets.import.title,
|
const { navigate, dismiss } = useNavigation();
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
useEffect(() => {
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
isLoading: true,
|
|
||||||
isToolbarVisibleForAndroid: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setState({
|
|
||||||
isLoading: false,
|
|
||||||
label: '',
|
|
||||||
});
|
|
||||||
Privacy.enableBlur();
|
Privacy.enableBlur();
|
||||||
}
|
return () => Privacy.disableBlur();
|
||||||
|
});
|
||||||
|
|
||||||
componentWillUnmount() {
|
const importButtonPressed = () => {
|
||||||
Privacy.disableBlur();
|
if (importText.trim().length === 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
importMnemonic(importText);
|
||||||
|
};
|
||||||
|
|
||||||
async _saveWallet(w) {
|
const importMnemonic = importText => {
|
||||||
if (BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret)) {
|
|
||||||
alert('This wallet has been previously imported.');
|
|
||||||
} else {
|
|
||||||
alert(loc.wallets.import.success);
|
|
||||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
|
||||||
w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable);
|
|
||||||
w.setUserHasSavedExport(true);
|
|
||||||
BlueApp.wallets.push(w);
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
|
||||||
A(A.ENUM.CREATED_WALLET);
|
|
||||||
this.props.navigation.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async importMnemonic(text) {
|
|
||||||
try {
|
try {
|
||||||
// is it lightning custodian?
|
WalletImport.processImportText(importText);
|
||||||
if (text.indexOf('blitzhub://') !== -1 || text.indexOf('lndhub://') !== -1) {
|
dismiss();
|
||||||
let lnd = new LightningCustodianWallet();
|
} catch (error) {
|
||||||
if (text.includes('@')) {
|
|
||||||
const split = text.split('@');
|
|
||||||
lnd.setBaseURI(split[1]);
|
|
||||||
lnd.setSecret(split[0]);
|
|
||||||
} else {
|
|
||||||
lnd.setBaseURI(LightningCustodianWallet.defaultBaseUri);
|
|
||||||
lnd.setSecret(text);
|
|
||||||
}
|
|
||||||
lnd.init();
|
|
||||||
await lnd.authorize();
|
|
||||||
await lnd.fetchTransactions();
|
|
||||||
await lnd.fetchUserInvoices();
|
|
||||||
await lnd.fetchPendingTransactions();
|
|
||||||
await lnd.fetchBalance();
|
|
||||||
return this._saveWallet(lnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// trying other wallet types
|
|
||||||
|
|
||||||
let hd4 = new HDSegwitBech32Wallet();
|
|
||||||
hd4.setSecret(text);
|
|
||||||
if (hd4.validateMnemonic()) {
|
|
||||||
await hd4.fetchBalance();
|
|
||||||
if (hd4.getBalance() > 0) {
|
|
||||||
await hd4.fetchTransactions();
|
|
||||||
return this._saveWallet(hd4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let segwitWallet = new SegwitP2SHWallet();
|
|
||||||
segwitWallet.setSecret(text);
|
|
||||||
if (segwitWallet.getAddress()) {
|
|
||||||
// ok its a valid WIF
|
|
||||||
|
|
||||||
let legacyWallet = new LegacyWallet();
|
|
||||||
legacyWallet.setSecret(text);
|
|
||||||
|
|
||||||
await legacyWallet.fetchBalance();
|
|
||||||
if (legacyWallet.getBalance() > 0) {
|
|
||||||
// yep, its legacy we're importing
|
|
||||||
await legacyWallet.fetchTransactions();
|
|
||||||
return this._saveWallet(legacyWallet);
|
|
||||||
} else {
|
|
||||||
// by default, we import wif as Segwit P2SH
|
|
||||||
await segwitWallet.fetchBalance();
|
|
||||||
await segwitWallet.fetchTransactions();
|
|
||||||
return this._saveWallet(segwitWallet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// case - WIF is valid, just has uncompressed pubkey
|
|
||||||
|
|
||||||
let legacyWallet = new LegacyWallet();
|
|
||||||
legacyWallet.setSecret(text);
|
|
||||||
if (legacyWallet.getAddress()) {
|
|
||||||
await legacyWallet.fetchBalance();
|
|
||||||
await legacyWallet.fetchTransactions();
|
|
||||||
return this._saveWallet(legacyWallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we're here - nope, its not a valid WIF
|
|
||||||
|
|
||||||
let hd1 = new HDLegacyBreadwalletWallet();
|
|
||||||
hd1.setSecret(text);
|
|
||||||
if (hd1.validateMnemonic()) {
|
|
||||||
await hd1.fetchBalance();
|
|
||||||
if (hd1.getBalance() > 0) {
|
|
||||||
await hd1.fetchTransactions();
|
|
||||||
return this._saveWallet(hd1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hd2 = new HDSegwitP2SHWallet();
|
|
||||||
hd2.setSecret(text);
|
|
||||||
if (hd2.validateMnemonic()) {
|
|
||||||
await hd2.fetchBalance();
|
|
||||||
if (hd2.getBalance() > 0) {
|
|
||||||
await hd2.fetchTransactions();
|
|
||||||
return this._saveWallet(hd2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hd3 = new HDLegacyP2PKHWallet();
|
|
||||||
hd3.setSecret(text);
|
|
||||||
if (hd3.validateMnemonic()) {
|
|
||||||
await hd3.fetchBalance();
|
|
||||||
if (hd3.getBalance() > 0) {
|
|
||||||
await hd3.fetchTransactions();
|
|
||||||
return this._saveWallet(hd3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no balances? how about transactions count?
|
|
||||||
|
|
||||||
if (hd1.validateMnemonic()) {
|
|
||||||
await hd1.fetchTransactions();
|
|
||||||
if (hd1.getTransactions().length !== 0) {
|
|
||||||
return this._saveWallet(hd1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hd2.validateMnemonic()) {
|
|
||||||
await hd2.fetchTransactions();
|
|
||||||
if (hd2.getTransactions().length !== 0) {
|
|
||||||
return this._saveWallet(hd2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hd3.validateMnemonic()) {
|
|
||||||
await hd3.fetchTransactions();
|
|
||||||
if (hd3.getTransactions().length !== 0) {
|
|
||||||
return this._saveWallet(hd3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hd4.validateMnemonic()) {
|
|
||||||
await hd4.fetchTransactions();
|
|
||||||
if (hd4.getTransactions().length !== 0) {
|
|
||||||
return this._saveWallet(hd4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// is it even valid? if yes we will import as:
|
|
||||||
if (hd4.validateMnemonic()) {
|
|
||||||
return this._saveWallet(hd4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// not valid? maybe its a watch-only address?
|
|
||||||
|
|
||||||
let watchOnly = new WatchOnlyWallet();
|
|
||||||
watchOnly.setSecret(text);
|
|
||||||
if (watchOnly.valid()) {
|
|
||||||
await watchOnly.fetchTransactions();
|
|
||||||
await watchOnly.fetchBalance();
|
|
||||||
return this._saveWallet(watchOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
// nope?
|
|
||||||
|
|
||||||
// TODO: try a raw private key
|
|
||||||
} catch (Err) {
|
|
||||||
console.warn(Err);
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(loc.wallets.import.error);
|
alert(loc.wallets.import.error);
|
||||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||||
// Plan:
|
|
||||||
// 0. check if its HDSegwitBech32Wallet (BIP84)
|
|
||||||
// 1. check if its HDSegwitP2SHWallet (BIP49)
|
|
||||||
// 2. check if its HDLegacyP2PKHWallet (BIP44)
|
|
||||||
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
|
|
||||||
// 4. check if its Segwit WIF (P2SH)
|
|
||||||
// 5. check if its Legacy WIF
|
|
||||||
// 6. check if its address (watch-only wallet)
|
|
||||||
// 7. check if its private key (segwit address P2SH) TODO
|
|
||||||
// 7. check if its private key (legacy address) TODO
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setLabel(text) {
|
const onBarScanned = value => {
|
||||||
this.setState({
|
setImportText(value);
|
||||||
label: text,
|
importMnemonic(value);
|
||||||
}); /* also, a hack to make screen update new typed text */
|
};
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.isLoading) {
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1, paddingTop: 20 }}>
|
|
||||||
<BlueLoading />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
||||||
|
@ -256,27 +57,36 @@ export default class WalletsImport extends Component {
|
||||||
<BlueFormLabel>{loc.wallets.import.explanation}</BlueFormLabel>
|
<BlueFormLabel>{loc.wallets.import.explanation}</BlueFormLabel>
|
||||||
<BlueSpacing20 />
|
<BlueSpacing20 />
|
||||||
<BlueFormMultiInput
|
<BlueFormMultiInput
|
||||||
value={this.state.label}
|
value={importText}
|
||||||
placeholder=""
|
|
||||||
contextMenuHidden
|
contextMenuHidden
|
||||||
onChangeText={text => {
|
onChangeText={setImportText}
|
||||||
this.setLabel(text);
|
|
||||||
}}
|
|
||||||
inputAccessoryViewID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}
|
inputAccessoryViewID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||||
onFocus={() => this.setState({ isToolbarVisibleForAndroid: true })}
|
onFocus={() => setIsToolbarVisibleForAndroid(true)}
|
||||||
onBlur={() => this.setState({ isToolbarVisibleForAndroid: false })}
|
onBlur={() => setIsToolbarVisibleForAndroid(false)}
|
||||||
/>
|
/>
|
||||||
{Platform.select({
|
{Platform.select({
|
||||||
ios: (
|
ios: (
|
||||||
<BlueDoneAndDismissKeyboardInputAccessory
|
<BlueDoneAndDismissKeyboardInputAccessory
|
||||||
onClearTapped={() => this.setState({ label: '' }, () => Keyboard.dismiss())}
|
onClearTapped={() => {
|
||||||
onPasteTapped={text => this.setState({ label: text }, () => Keyboard.dismiss())}
|
setImportText('');
|
||||||
|
Keyboard.dismiss();
|
||||||
|
}}
|
||||||
|
onPasteTapped={text => {
|
||||||
|
setImportText(text);
|
||||||
|
Keyboard.dismiss();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
android: this.state.isToolbarVisibleForAndroid && (
|
android: isToolbarVisibleForAndroid && (
|
||||||
<BlueDoneAndDismissKeyboardInputAccessory
|
<BlueDoneAndDismissKeyboardInputAccessory
|
||||||
onClearTapped={() => this.setState({ label: '' }, () => Keyboard.dismiss())}
|
onClearTapped={() => {
|
||||||
onPasteTapped={text => this.setState({ label: text }, () => Keyboard.dismiss())}
|
setImportText('');
|
||||||
|
Keyboard.dismiss();
|
||||||
|
}}
|
||||||
|
onPasteTapped={text => {
|
||||||
|
setImportText(text);
|
||||||
|
Keyboard.dismiss();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
|
@ -290,37 +100,26 @@ export default class WalletsImport extends Component {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BlueButton
|
<BlueButton
|
||||||
disabled={!this.state.label}
|
disabled={importText.trim().length === 0}
|
||||||
title={loc.wallets.import.do_import}
|
title={loc.wallets.import.do_import}
|
||||||
buttonStyle={{
|
buttonStyle={{
|
||||||
width: width / 1.5,
|
width: width / 1.5,
|
||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={importButtonPressed}
|
||||||
if (!this.state.label) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({ isLoading: true }, async () => {
|
|
||||||
await this.importMnemonic(this.state.label.trim());
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<BlueButtonLink
|
<BlueButtonLink
|
||||||
title={loc.wallets.import.scan_qr}
|
title={loc.wallets.import.scan_qr}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
this.props.navigation.navigate('ScanQrWif');
|
navigate('ScanQrAddress', { onBarScanned });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</SafeBlueArea>
|
</SafeBlueArea>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletsImport.propTypes = {
|
|
||||||
navigation: PropTypes.shape({
|
|
||||||
navigate: PropTypes.func,
|
|
||||||
goBack: PropTypes.func,
|
|
||||||
dismiss: PropTypes.func,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WalletsImport.navigationOptions = {
|
||||||
|
...BlueNavigationStyle(),
|
||||||
|
title: loc.wallets.import.title,
|
||||||
|
};
|
||||||
|
export default WalletsImport;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
/* global alert */
|
/* global alert */
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View, TouchableOpacity, Text, FlatList, InteractionManager, RefreshControl, ScrollView } from 'react-native';
|
import { View, TouchableOpacity, Text, FlatList, InteractionManager, RefreshControl, ScrollView, Alert } from 'react-native';
|
||||||
import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents';
|
import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents';
|
||||||
import { Icon } from 'react-native-elements';
|
import { Icon } from 'react-native-elements';
|
||||||
import { NavigationEvents } from 'react-navigation';
|
import { NavigationEvents } from 'react-navigation';
|
||||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { PlaceholderWallet } from '../../class';
|
||||||
|
import WalletImport from '../../class/walletImport';
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
let A = require('../../analytics');
|
let A = require('../../analytics');
|
||||||
/** @type {AppStorage} */
|
/** @type {AppStorage} */
|
||||||
|
@ -135,7 +137,7 @@ export default class WalletsList extends Component {
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if (scrollToEnd) {
|
if (scrollToEnd) {
|
||||||
this.walletsCarousel.snapToItem(this.state.wallets.length - 1);
|
this.walletsCarousel.snapToItem(this.state.wallets.length - 2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -152,15 +154,44 @@ export default class WalletsList extends Component {
|
||||||
console.log('click', index);
|
console.log('click', index);
|
||||||
let wallet = BlueApp.wallets[index];
|
let wallet = BlueApp.wallets[index];
|
||||||
if (wallet) {
|
if (wallet) {
|
||||||
|
if (wallet.type === PlaceholderWallet.type) {
|
||||||
|
Alert.alert(
|
||||||
|
loc.wallets.add.details,
|
||||||
|
'There was a problem importing this wallet.',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: loc.wallets.details.delete,
|
||||||
|
onPress: () => {
|
||||||
|
WalletImport.removePlaceholderWallet();
|
||||||
|
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||||
|
},
|
||||||
|
style: 'destructive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Try Again',
|
||||||
|
onPress: () => {
|
||||||
|
this.props.navigation.navigate('ImportWallet', { label: wallet.getSecret() });
|
||||||
|
WalletImport.removePlaceholderWallet();
|
||||||
|
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||||
|
},
|
||||||
|
style: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ cancelable: false },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.props.navigation.navigate('WalletTransactions', {
|
this.props.navigation.navigate('WalletTransactions', {
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
key: `WalletTransactions-${wallet.getID()}`,
|
key: `WalletTransactions-${wallet.getID()}`,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// if its out of index - this must be last card with incentive to create wallet
|
// if its out of index - this must be last card with incentive to create wallet
|
||||||
|
if (!BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)) {
|
||||||
this.props.navigation.navigate('AddWallet');
|
this.props.navigation.navigate('AddWallet');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onSnapToItem(index) {
|
onSnapToItem(index) {
|
||||||
console.log('onSnapToItem', index);
|
console.log('onSnapToItem', index);
|
||||||
|
@ -171,6 +202,10 @@ export default class WalletsList extends Component {
|
||||||
// not the last
|
// not the last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.wallets[index].type === PlaceholderWallet.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// now, lets try to fetch balance and txs for this wallet in case it has changed
|
// now, lets try to fetch balance and txs for this wallet in case it has changed
|
||||||
this.lazyRefreshWallet(index);
|
this.lazyRefreshWallet(index);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +228,7 @@ export default class WalletsList extends Component {
|
||||||
let didRefresh = false;
|
let didRefresh = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (wallets && wallets[index] && wallets[index].timeToRefreshBalance()) {
|
if (wallets && wallets[index] && wallets[index].type !== PlaceholderWallet.type && wallets[index].timeToRefreshBalance()) {
|
||||||
console.log('snapped to, and now its time to refresh wallet #', index);
|
console.log('snapped to, and now its time to refresh wallet #', index);
|
||||||
await wallets[index].fetchBalance();
|
await wallets[index].fetchBalance();
|
||||||
if (oldBalance !== wallets[index].getBalance() || wallets[index].getUnconfirmedBalance() !== 0) {
|
if (oldBalance !== wallets[index].getBalance() || wallets[index].getUnconfirmedBalance() !== 0) {
|
||||||
|
@ -250,7 +285,7 @@ export default class WalletsList extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleLongPress = () => {
|
handleLongPress = () => {
|
||||||
if (BlueApp.getWallets().length > 1) {
|
if (BlueApp.getWallets().length > 1 && !BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)) {
|
||||||
this.props.navigation.navigate('ReorderWallets');
|
this.props.navigation.navigate('ReorderWallets');
|
||||||
} else {
|
} else {
|
||||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||||
|
@ -276,7 +311,14 @@ export default class WalletsList extends Component {
|
||||||
<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={!this.state.isFlatListRefreshControlHidden} />
|
<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={!this.state.isFlatListRefreshControlHidden} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<BlueHeaderDefaultMain leftText={loc.wallets.list.title} onNewWalletPress={() => this.props.navigation.navigate('AddWallet')} />
|
<BlueHeaderDefaultMain
|
||||||
|
leftText={loc.wallets.list.title}
|
||||||
|
onNewWalletPress={
|
||||||
|
!BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)
|
||||||
|
? () => this.props.navigation.navigate('AddWallet')
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
<WalletsCarousel
|
<WalletsCarousel
|
||||||
removeClippedSubviews={false}
|
removeClippedSubviews={false}
|
||||||
data={this.state.wallets}
|
data={this.state.wallets}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { SafeBlueArea, BlueNavigationStyle } from '../../BlueComponents';
|
||||||
import SortableList from 'react-native-sortable-list';
|
import SortableList from 'react-native-sortable-list';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
import { PlaceholderWallet, LightningCustodianWallet } from '../../class';
|
||||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
import WalletGradient from '../../class/walletGradient';
|
import WalletGradient from '../../class/walletGradient';
|
||||||
let EV = require('../../events');
|
let EV = require('../../events');
|
||||||
|
@ -51,7 +51,7 @@ export default class ReorderWallets extends Component {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const wallets = BlueApp.getWallets();
|
const wallets = BlueApp.getWallets().filter(wallet => wallet.type !== PlaceholderWallet.type);
|
||||||
this.setState({
|
this.setState({
|
||||||
data: wallets,
|
data: wallets,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|
|
@ -1,344 +0,0 @@
|
||||||
/* global alert */
|
|
||||||
import React from 'react';
|
|
||||||
import { ActivityIndicator, Image, View, TouchableOpacity } from 'react-native';
|
|
||||||
import { BlueText, SafeBlueArea, BlueButton } from '../../BlueComponents';
|
|
||||||
import { RNCamera } from 'react-native-camera';
|
|
||||||
import { SegwitP2SHWallet, LegacyWallet, WatchOnlyWallet, HDLegacyP2PKHWallet, HDSegwitBech32Wallet } from '../../class';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
|
|
||||||
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
|
||||||
import bip21 from 'bip21';
|
|
||||||
/** @type {AppStorage} */
|
|
||||||
let BlueApp = require('../../BlueApp');
|
|
||||||
let EV = require('../../events');
|
|
||||||
let bip38 = require('../../bip38');
|
|
||||||
let wif = require('wif');
|
|
||||||
let prompt = require('../../prompt');
|
|
||||||
let loc = require('../../loc');
|
|
||||||
|
|
||||||
export default class ScanQrWif extends React.Component {
|
|
||||||
static navigationOptions = {
|
|
||||||
header: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = { isLoading: false };
|
|
||||||
|
|
||||||
onBarCodeScanned = async ret => {
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.pausePreview();
|
|
||||||
if (+new Date() - this.lastTimeIveBeenHere < 6000) {
|
|
||||||
this.lastTimeIveBeenHere = +new Date();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lastTimeIveBeenHere = +new Date();
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
if (ret.data[0] === '6') {
|
|
||||||
// password-encrypted, need to ask for password and decrypt
|
|
||||||
console.log('trying to decrypt...');
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
message: loc.wallets.scanQrWif.decoding,
|
|
||||||
});
|
|
||||||
shold_stop_bip38 = undefined; // eslint-disable-line
|
|
||||||
let password = await prompt(loc.wallets.scanQrWif.input_password, loc.wallets.scanQrWif.password_explain);
|
|
||||||
if (!password) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let that = this;
|
|
||||||
try {
|
|
||||||
let decryptedKey = await bip38.decrypt(ret.data, password, function(status) {
|
|
||||||
that.setState({
|
|
||||||
message: loc.wallets.scanQrWif.decoding + '... ' + status.percent.toString().substr(0, 4) + ' %',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ret.data = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.message);
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
this.setState({ message: false, isLoading: false });
|
|
||||||
return alert(loc.wallets.scanQrWif.bad_password);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ message: false, isLoading: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let w of BlueApp.wallets) {
|
|
||||||
if (w.getSecret() === ret.data) {
|
|
||||||
// lookig for duplicates
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// is it HD BIP49 mnemonic?
|
|
||||||
let hd = new HDSegwitP2SHWallet();
|
|
||||||
hd.setSecret(ret.data);
|
|
||||||
if (hd.validateMnemonic()) {
|
|
||||||
for (let w of BlueApp.wallets) {
|
|
||||||
if (w.getSecret() === hd.getSecret()) {
|
|
||||||
// lookig for duplicates
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
hd.setLabel(loc.wallets.import.imported + ' ' + hd.typeReadable);
|
|
||||||
await hd.fetchBalance();
|
|
||||||
if (hd.getBalance() !== 0) {
|
|
||||||
await hd.fetchTransactions();
|
|
||||||
BlueApp.wallets.push(hd);
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
alert(loc.wallets.import.success);
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// nope
|
|
||||||
|
|
||||||
// is it HD legacy (BIP44) mnemonic?
|
|
||||||
hd = new HDLegacyP2PKHWallet();
|
|
||||||
hd.setSecret(ret.data);
|
|
||||||
if (hd.validateMnemonic()) {
|
|
||||||
for (let w of BlueApp.wallets) {
|
|
||||||
if (w.getSecret() === hd.getSecret()) {
|
|
||||||
// lookig for duplicates
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await hd.fetchTransactions();
|
|
||||||
if (hd.getTransactions().length !== 0) {
|
|
||||||
await hd.fetchBalance();
|
|
||||||
hd.setLabel(loc.wallets.import.imported + ' ' + hd.typeReadable);
|
|
||||||
BlueApp.wallets.push(hd);
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
alert(loc.wallets.import.success);
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// nope
|
|
||||||
|
|
||||||
// is it HD BIP49 mnemonic?
|
|
||||||
hd = new HDSegwitBech32Wallet();
|
|
||||||
hd.setSecret(ret.data);
|
|
||||||
if (hd.validateMnemonic()) {
|
|
||||||
for (let w of BlueApp.wallets) {
|
|
||||||
if (w.getSecret() === hd.getSecret()) {
|
|
||||||
// lookig for duplicates
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
hd.setLabel(loc.wallets.import.imported + ' ' + hd.typeReadable);
|
|
||||||
BlueApp.wallets.push(hd);
|
|
||||||
await hd.fetchBalance();
|
|
||||||
await hd.fetchTransactions();
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
alert(loc.wallets.import.success);
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// nope
|
|
||||||
|
|
||||||
// is it lndhub?
|
|
||||||
if (ret.data.indexOf('blitzhub://') !== -1 || ret.data.indexOf('lndhub://') !== -1) {
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
let lnd = new LightningCustodianWallet();
|
|
||||||
lnd.setSecret(ret.data);
|
|
||||||
if (ret.data.includes('@')) {
|
|
||||||
const split = ret.data.split('@');
|
|
||||||
lnd.setBaseURI(split[1]);
|
|
||||||
lnd.init();
|
|
||||||
lnd.setSecret(split[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await lnd.authorize();
|
|
||||||
await lnd.fetchTransactions();
|
|
||||||
await lnd.fetchUserInvoices();
|
|
||||||
await lnd.fetchPendingTransactions();
|
|
||||||
await lnd.fetchBalance();
|
|
||||||
} catch (Err) {
|
|
||||||
console.log(Err);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
alert(Err.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlueApp.wallets.push(lnd);
|
|
||||||
lnd.setLabel(loc.wallets.import.imported + ' ' + lnd.typeReadable);
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
alert(loc.wallets.import.success);
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// nope
|
|
||||||
|
|
||||||
// is it just address..?
|
|
||||||
let watchOnly = new WatchOnlyWallet();
|
|
||||||
let watchAddr = ret.data;
|
|
||||||
|
|
||||||
// Is it BIP21 format?
|
|
||||||
if (ret.data.indexOf('bitcoin:') === 0 || ret.data.indexOf('BITCOIN:') === 0) {
|
|
||||||
try {
|
|
||||||
watchAddr = bip21.decode(ret.data).address;
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (watchOnly.setSecret(watchAddr) && watchOnly.valid()) {
|
|
||||||
watchOnly.setLabel(loc.wallets.scanQrWif.imported_watchonly);
|
|
||||||
BlueApp.wallets.push(watchOnly);
|
|
||||||
alert(loc.wallets.scanQrWif.imported_watchonly + loc.wallets.scanQrWif.with_address + watchOnly.getAddress());
|
|
||||||
await watchOnly.fetchBalance();
|
|
||||||
await watchOnly.fetchTransactions();
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// nope
|
|
||||||
|
|
||||||
let newWallet = new SegwitP2SHWallet();
|
|
||||||
newWallet.setSecret(ret.data);
|
|
||||||
let newLegacyWallet = new LegacyWallet();
|
|
||||||
newLegacyWallet.setSecret(ret.data);
|
|
||||||
|
|
||||||
if (newWallet.getAddress() === false && newLegacyWallet.getAddress() === false) {
|
|
||||||
alert(loc.wallets.scanQrWif.bad_wif);
|
|
||||||
if (RNCamera.Constants.CameraStatus === RNCamera.Constants.CameraStatus.READY) this.cameraRef.resumePreview();
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newWallet.getAddress() === false && newLegacyWallet.getAddress() !== false) {
|
|
||||||
// case - WIF is valid, just has uncompressed pubkey
|
|
||||||
newLegacyWallet.setLabel(loc.wallets.scanQrWif.imported_legacy);
|
|
||||||
BlueApp.wallets.push(newLegacyWallet);
|
|
||||||
alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newLegacyWallet.getAddress());
|
|
||||||
await newLegacyWallet.fetchBalance();
|
|
||||||
await newLegacyWallet.fetchTransactions();
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
await newLegacyWallet.fetchBalance();
|
|
||||||
console.log('newLegacyWallet == ', newLegacyWallet.getBalance());
|
|
||||||
|
|
||||||
if (newLegacyWallet.getBalance()) {
|
|
||||||
newLegacyWallet.setLabel(loc.wallets.scanQrWif.imported_legacy);
|
|
||||||
BlueApp.wallets.push(newLegacyWallet);
|
|
||||||
alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newLegacyWallet.getAddress());
|
|
||||||
await newLegacyWallet.fetchTransactions();
|
|
||||||
} else {
|
|
||||||
await newWallet.fetchBalance();
|
|
||||||
await newWallet.fetchTransactions();
|
|
||||||
newWallet.setLabel(loc.wallets.scanQrWif.imported_segwit);
|
|
||||||
BlueApp.wallets.push(newWallet);
|
|
||||||
alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newWallet.getAddress());
|
|
||||||
}
|
|
||||||
await BlueApp.saveToDisk();
|
|
||||||
this.props.navigation.popToTop();
|
|
||||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
|
||||||
}; // end
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.isLoading) {
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1, paddingTop: 20, justifyContent: 'center', alignContent: 'center' }}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
{(() => {
|
|
||||||
if (this.state.message) {
|
|
||||||
return (
|
|
||||||
<SafeBlueArea>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BlueText>{this.state.message}</BlueText>
|
|
||||||
<BlueButton
|
|
||||||
icon={{ name: 'ban', type: 'font-awesome' }}
|
|
||||||
onPress={async () => {
|
|
||||||
this.setState({ message: false });
|
|
||||||
shold_stop_bip38 = true; // eslint-disable-line
|
|
||||||
}}
|
|
||||||
title={loc.wallets.scanQrWif.cancel}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</SafeBlueArea>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<SafeBlueArea style={{ flex: 1 }}>
|
|
||||||
<RNCamera
|
|
||||||
captureAudio={false}
|
|
||||||
androidCameraPermissionOptions={{
|
|
||||||
title: 'Permission to use camera',
|
|
||||||
message: 'We need your permission to use your camera',
|
|
||||||
buttonPositive: 'OK',
|
|
||||||
buttonNegative: 'Cancel',
|
|
||||||
}}
|
|
||||||
style={{ flex: 1, justifyContent: 'space-between' }}
|
|
||||||
onBarCodeRead={this.onBarCodeScanned}
|
|
||||||
ref={ref => (this.cameraRef = ref)}
|
|
||||||
barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
|
|
||||||
/>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
marginLeft: 24,
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: 20,
|
|
||||||
position: 'absolute',
|
|
||||||
top: 64,
|
|
||||||
}}
|
|
||||||
onPress={() => this.props.navigation.goBack(null)}
|
|
||||||
>
|
|
||||||
<Image style={{ alignSelf: 'center' }} source={require('../../img/close.png')} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</SafeBlueArea>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScanQrWif.propTypes = {
|
|
||||||
navigation: PropTypes.shape({
|
|
||||||
goBack: PropTypes.func,
|
|
||||||
popToTop: PropTypes.func,
|
|
||||||
navigate: PropTypes.func,
|
|
||||||
}),
|
|
||||||
};
|
|
Loading…
Add table
Reference in a new issue