mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-03 03:59:10 +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,
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { LightningCustodianWallet } from './class';
|
||||
import { LightningCustodianWallet, PlaceholderWallet } from './class';
|
||||
import Carousel from 'react-native-snap-carousel';
|
||||
import { BitcoinUnit } from './models/bitcoinUnits';
|
||||
import NavigationService from './NavigationService';
|
||||
|
@ -748,15 +748,17 @@ export class BlueHeaderDefaultMain extends Component {
|
|||
</Text>
|
||||
}
|
||||
rightComponent={
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onNewWalletPress}
|
||||
style={{
|
||||
height: 48,
|
||||
alignSelf: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<BluePlusIcon />
|
||||
</TouchableOpacity>
|
||||
this.props.onNewWalletPress && (
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onNewWalletPress}
|
||||
style={{
|
||||
height: 48,
|
||||
alignSelf: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<BluePlusIcon />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
|
@ -957,7 +959,7 @@ export class BlueLoading extends Component {
|
|||
render() {
|
||||
return (
|
||||
<SafeBlueArea>
|
||||
<View style={{ flex: 1, paddingTop: 200 }}>
|
||||
<View style={{ flex: 1, paddingTop: 200 }} {...this.props}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
|
@ -1844,97 +1846,166 @@ export class WalletsCarousel extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
||||
shadowOpacity={40 / 100}
|
||||
shadowOffset={{ width: 0, height: 0 }}
|
||||
shadowRadius={5}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
onPressIn={this.onPressedIn}
|
||||
onPressOut={this.onPressedOut}
|
||||
onLongPress={WalletsCarousel.handleLongPress}
|
||||
onPress={() => {
|
||||
if (WalletsCarousel.handleClick) {
|
||||
WalletsCarousel.handleClick(index);
|
||||
}
|
||||
}}
|
||||
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}
|
||||
>
|
||||
<LinearGradient
|
||||
shadowColor={BlueApp.settings.shadowColor}
|
||||
colors={WalletGradient.gradientsFor(item.type)}
|
||||
style={{
|
||||
padding: 15,
|
||||
borderRadius: 10,
|
||||
minHeight: 164,
|
||||
elevation: 5,
|
||||
<TouchableWithoutFeedback
|
||||
onPressIn={item.getIsFailure() ? this.onPressedIn : null}
|
||||
onPressOut={item.getIsFailure() ? this.onPressedOut : null}
|
||||
onPress={() => {
|
||||
if (item.getIsFailure() && WalletsCarousel.handleClick) {
|
||||
WalletsCarousel.handleClick(index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={(LightningCustodianWallet.type === item.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')}
|
||||
<LinearGradient
|
||||
shadowColor={BlueApp.settings.shadowColor}
|
||||
colors={WalletGradient.gradientsFor(item.type)}
|
||||
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,
|
||||
padding: 15,
|
||||
borderRadius: 10,
|
||||
minHeight: 164,
|
||||
elevation: 5,
|
||||
}}
|
||||
>
|
||||
{item.getLabel()}
|
||||
</Text>
|
||||
{item.hideBalance ? (
|
||||
<BluePrivateBalance />
|
||||
) : (
|
||||
<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}
|
||||
adjustsFontSizeToFit
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
fontSize: 19,
|
||||
color: BlueApp.settings.inverseForegroundColor,
|
||||
}}
|
||||
>
|
||||
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
|
||||
{item.getLabel()}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={{ backgroundColor: 'transparent' }} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
{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 (
|
||||
<Animated.View
|
||||
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
||||
shadowOpacity={40 / 100}
|
||||
shadowOffset={{ width: 0, height: 0 }}
|
||||
shadowRadius={5}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
onPressIn={this.onPressedIn}
|
||||
onPressOut={this.onPressedOut}
|
||||
onLongPress={WalletsCarousel.handleLongPress}
|
||||
onPress={() => {
|
||||
if (WalletsCarousel.handleClick) {
|
||||
WalletsCarousel.handleClick(index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LinearGradient
|
||||
shadowColor={BlueApp.settings.shadowColor}
|
||||
colors={WalletGradient.gradientsFor(item.type)}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 13,
|
||||
color: BlueApp.settings.inverseForegroundColor,
|
||||
padding: 15,
|
||||
borderRadius: 10,
|
||||
minHeight: 164,
|
||||
elevation: 5,
|
||||
}}
|
||||
>
|
||||
{loc.wallets.list.latest_transaction}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
color: BlueApp.settings.inverseForegroundColor,
|
||||
}}
|
||||
>
|
||||
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</TouchableWithoutFeedback>
|
||||
</Animated.View>
|
||||
);
|
||||
<Image
|
||||
source={(LightningCustodianWallet.type === item.type && require('./img/lnd-shape.png')) || 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.hideBalance ? (
|
||||
<BluePrivateBalance />
|
||||
) : (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
color: BlueApp.settings.inverseForegroundColor,
|
||||
}}
|
||||
>
|
||||
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={{ backgroundColor: 'transparent' }} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 13,
|
||||
color: BlueApp.settings.inverseForegroundColor,
|
||||
}}
|
||||
>
|
||||
{loc.wallets.list.latest_transaction}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
color: BlueApp.settings.inverseForegroundColor,
|
||||
}}
|
||||
>
|
||||
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</TouchableWithoutFeedback>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
snapToItem = item => {
|
||||
|
|
|
@ -23,7 +23,6 @@ import WalletExport from './screen/wallets/export';
|
|||
import WalletXpub from './screen/wallets/xpub';
|
||||
import BuyBitcoin from './screen/wallets/buyBitcoin';
|
||||
import Marketplace from './screen/wallets/marketplace';
|
||||
import scanQrWif from './screen/wallets/scanQrWif';
|
||||
import ReorderWallets from './screen/wallets/reorderWallets';
|
||||
import SelectWallet from './screen/wallets/selectWallet';
|
||||
|
||||
|
@ -277,9 +276,6 @@ const MainBottomTabs = createStackNavigator(
|
|||
header: null,
|
||||
},
|
||||
},
|
||||
ScanQrWif: {
|
||||
screen: scanQrWif,
|
||||
},
|
||||
WalletExport: {
|
||||
screen: WalletExport,
|
||||
},
|
||||
|
|
|
@ -9,8 +9,9 @@ import {
|
|||
SegwitP2SHWallet,
|
||||
SegwitBech32Wallet,
|
||||
HDSegwitBech32Wallet,
|
||||
PlaceholderWallet,
|
||||
LightningCustodianWallet,
|
||||
} from './';
|
||||
import { LightningCustodianWallet } from './lightning-custodian-wallet';
|
||||
import WatchConnectivity from '../WatchConnectivity';
|
||||
import DeviceQuickActions from './quickActions';
|
||||
const encryption = require('../encryption');
|
||||
|
@ -192,6 +193,8 @@ export class AppStorage {
|
|||
let tempObj = JSON.parse(key);
|
||||
let unserializedWallet;
|
||||
switch (tempObj.type) {
|
||||
case PlaceholderWallet.type:
|
||||
continue;
|
||||
case SegwitBech32Wallet.type:
|
||||
unserializedWallet = SegwitBech32Wallet.fromJson(key);
|
||||
break;
|
||||
|
@ -298,7 +301,7 @@ export class AppStorage {
|
|||
async saveToDisk() {
|
||||
let walletsToSave = [];
|
||||
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();
|
||||
walletsToSave.push(JSON.stringify({ ...key, type: key.type }));
|
||||
}
|
||||
|
@ -349,13 +352,13 @@ export class AppStorage {
|
|||
console.log('fetchWalletBalances for wallet#', index);
|
||||
if (index || index === 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) {
|
||||
await wallet.fetchBalance();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let wallet of this.wallets) {
|
||||
for (let wallet of this.wallets.filter(wallet => wallet.type !== PlaceholderWallet.type)) {
|
||||
await wallet.fetchBalance();
|
||||
}
|
||||
}
|
||||
|
@ -375,7 +378,7 @@ export class AppStorage {
|
|||
console.log('fetchWalletTransactions for wallet#', index);
|
||||
if (index || index === 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) {
|
||||
await wallet.fetchTransactions();
|
||||
if (wallet.fetchPendingTransactions) {
|
||||
|
|
|
@ -12,3 +12,4 @@ export * from './lightning-custodian-wallet';
|
|||
export * from './abstract-hd-wallet';
|
||||
export * from './hd-segwit-bech32-wallet';
|
||||
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 { WatchOnlyWallet } from './watch-only-wallet';
|
||||
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
||||
import { PlaceholderWallet } from './placeholder-wallet';
|
||||
|
||||
export default class WalletGradient {
|
||||
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
|
||||
|
@ -41,6 +42,9 @@ export default class WalletGradient {
|
|||
case LightningCustodianWallet.type:
|
||||
gradient = WalletGradient.lightningCustodianWallet;
|
||||
break;
|
||||
case PlaceholderWallet.type:
|
||||
gradient = WalletGradient.watchOnlyWallet;
|
||||
break;
|
||||
case 'CreateWallet':
|
||||
gradient = WalletGradient.createWallet;
|
||||
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,326 +1,125 @@
|
|||
/* global alert */
|
||||
import {
|
||||
SegwitP2SHWallet,
|
||||
LegacyWallet,
|
||||
WatchOnlyWallet,
|
||||
HDLegacyBreadwalletWallet,
|
||||
HDSegwitP2SHWallet,
|
||||
HDLegacyP2PKHWallet,
|
||||
HDSegwitBech32Wallet,
|
||||
} from '../../class';
|
||||
import React, { Component } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { KeyboardAvoidingView, Platform, Dimensions, View, TouchableWithoutFeedback, Keyboard } from 'react-native';
|
||||
import {
|
||||
BlueFormMultiInput,
|
||||
BlueButtonLink,
|
||||
BlueFormLabel,
|
||||
BlueLoading,
|
||||
BlueDoneAndDismissKeyboardInputAccessory,
|
||||
BlueButton,
|
||||
SafeBlueArea,
|
||||
BlueSpacing20,
|
||||
BlueNavigationStyle,
|
||||
} from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import Privacy from '../../Privacy';
|
||||
let EV = require('../../events');
|
||||
let A = require('../../analytics');
|
||||
/** @type {AppStorage} */
|
||||
let BlueApp = require('../../BlueApp');
|
||||
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
|
||||
import WalletImport from '../../class/walletImport';
|
||||
let loc = require('../../loc');
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
export default class WalletsImport extends Component {
|
||||
static navigationOptions = {
|
||||
...BlueNavigationStyle(),
|
||||
title: loc.wallets.import.title,
|
||||
const WalletsImport = () => {
|
||||
const [isToolbarVisibleForAndroid, setIsToolbarVisibleForAndroid] = useState(false);
|
||||
const [importText, setImportText] = useState(useNavigationParam('label') || '');
|
||||
const { navigate, dismiss } = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
Privacy.enableBlur();
|
||||
return () => Privacy.disableBlur();
|
||||
});
|
||||
|
||||
const importButtonPressed = () => {
|
||||
if (importText.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
importMnemonic(importText);
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
isToolbarVisibleForAndroid: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
label: '',
|
||||
});
|
||||
Privacy.enableBlur();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Privacy.disableBlur();
|
||||
}
|
||||
|
||||
async _saveWallet(w) {
|
||||
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) {
|
||||
const importMnemonic = importText => {
|
||||
try {
|
||||
// is it lightning custodian?
|
||||
if (text.indexOf('blitzhub://') !== -1 || text.indexOf('lndhub://') !== -1) {
|
||||
let lnd = new LightningCustodianWallet();
|
||||
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);
|
||||
WalletImport.processImportText(importText);
|
||||
dismiss();
|
||||
} catch (error) {
|
||||
alert(loc.wallets.import.error);
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
}
|
||||
};
|
||||
|
||||
alert(loc.wallets.import.error);
|
||||
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
|
||||
}
|
||||
const onBarScanned = value => {
|
||||
setImportText(value);
|
||||
importMnemonic(value);
|
||||
};
|
||||
|
||||
setLabel(text) {
|
||||
this.setState({
|
||||
label: text,
|
||||
}); /* also, a hack to make screen update new typed text */
|
||||
}
|
||||
return (
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||
<KeyboardAvoidingView behavior="position" enabled>
|
||||
<BlueFormLabel>{loc.wallets.import.explanation}</BlueFormLabel>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput
|
||||
value={importText}
|
||||
contextMenuHidden
|
||||
onChangeText={setImportText}
|
||||
inputAccessoryViewID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||
onFocus={() => setIsToolbarVisibleForAndroid(true)}
|
||||
onBlur={() => setIsToolbarVisibleForAndroid(false)}
|
||||
/>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<BlueDoneAndDismissKeyboardInputAccessory
|
||||
onClearTapped={() => {
|
||||
setImportText('');
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
onPasteTapped={text => {
|
||||
setImportText(text);
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
android: isToolbarVisibleForAndroid && (
|
||||
<BlueDoneAndDismissKeyboardInputAccessory
|
||||
onClearTapped={() => {
|
||||
setImportText('');
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
onPasteTapped={text => {
|
||||
setImportText(text);
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
</KeyboardAvoidingView>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20 }}>
|
||||
<BlueLoading />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||
<KeyboardAvoidingView behavior="position" enabled>
|
||||
<BlueFormLabel>{loc.wallets.import.explanation}</BlueFormLabel>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput
|
||||
value={this.state.label}
|
||||
placeholder=""
|
||||
contextMenuHidden
|
||||
onChangeText={text => {
|
||||
this.setLabel(text);
|
||||
}}
|
||||
inputAccessoryViewID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||
onFocus={() => this.setState({ isToolbarVisibleForAndroid: true })}
|
||||
onBlur={() => this.setState({ isToolbarVisibleForAndroid: false })}
|
||||
/>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<BlueDoneAndDismissKeyboardInputAccessory
|
||||
onClearTapped={() => this.setState({ label: '' }, () => Keyboard.dismiss())}
|
||||
onPasteTapped={text => this.setState({ label: text }, () => Keyboard.dismiss())}
|
||||
/>
|
||||
),
|
||||
android: this.state.isToolbarVisibleForAndroid && (
|
||||
<BlueDoneAndDismissKeyboardInputAccessory
|
||||
onClearTapped={() => this.setState({ label: '' }, () => Keyboard.dismiss())}
|
||||
onPasteTapped={text => this.setState({ label: text }, () => Keyboard.dismiss())}
|
||||
/>
|
||||
),
|
||||
})}
|
||||
</KeyboardAvoidingView>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
<BlueSpacing20 />
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<BlueButton
|
||||
disabled={importText.trim().length === 0}
|
||||
title={loc.wallets.import.do_import}
|
||||
buttonStyle={{
|
||||
width: width / 1.5,
|
||||
}}
|
||||
>
|
||||
<BlueButton
|
||||
disabled={!this.state.label}
|
||||
title={loc.wallets.import.do_import}
|
||||
buttonStyle={{
|
||||
width: width / 1.5,
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!this.state.label) {
|
||||
return;
|
||||
}
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
await this.importMnemonic(this.state.label.trim());
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<BlueButtonLink
|
||||
title={loc.wallets.import.scan_qr}
|
||||
onPress={() => {
|
||||
this.props.navigation.navigate('ScanQrWif');
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WalletsImport.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
navigate: PropTypes.func,
|
||||
goBack: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
}),
|
||||
onPress={importButtonPressed}
|
||||
/>
|
||||
<BlueButtonLink
|
||||
title={loc.wallets.import.scan_qr}
|
||||
onPress={() => {
|
||||
navigate('ScanQrAddress', { onBarScanned });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
};
|
||||
|
||||
WalletsImport.navigationOptions = {
|
||||
...BlueNavigationStyle(),
|
||||
title: loc.wallets.import.title,
|
||||
};
|
||||
export default WalletsImport;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
/* global alert */
|
||||
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 { Icon } from 'react-native-elements';
|
||||
import { NavigationEvents } from 'react-navigation';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PlaceholderWallet } from '../../class';
|
||||
import WalletImport from '../../class/walletImport';
|
||||
let EV = require('../../events');
|
||||
let A = require('../../analytics');
|
||||
/** @type {AppStorage} */
|
||||
|
@ -135,7 +137,7 @@ export default class WalletsList extends Component {
|
|||
},
|
||||
() => {
|
||||
if (scrollToEnd) {
|
||||
this.walletsCarousel.snapToItem(this.state.wallets.length - 1);
|
||||
this.walletsCarousel.snapToItem(this.state.wallets.length - 2);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -152,13 +154,42 @@ export default class WalletsList extends Component {
|
|||
console.log('click', index);
|
||||
let wallet = BlueApp.wallets[index];
|
||||
if (wallet) {
|
||||
this.props.navigation.navigate('WalletTransactions', {
|
||||
wallet: wallet,
|
||||
key: `WalletTransactions-${wallet.getID()}`,
|
||||
});
|
||||
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', {
|
||||
wallet: wallet,
|
||||
key: `WalletTransactions-${wallet.getID()}`,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// if its out of index - this must be last card with incentive to create wallet
|
||||
this.props.navigation.navigate('AddWallet');
|
||||
if (!BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)) {
|
||||
this.props.navigation.navigate('AddWallet');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +202,10 @@ export default class WalletsList extends Component {
|
|||
// 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
|
||||
this.lazyRefreshWallet(index);
|
||||
}
|
||||
|
@ -193,7 +228,7 @@ export default class WalletsList extends Component {
|
|||
let didRefresh = false;
|
||||
|
||||
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);
|
||||
await wallets[index].fetchBalance();
|
||||
if (oldBalance !== wallets[index].getBalance() || wallets[index].getUnconfirmedBalance() !== 0) {
|
||||
|
@ -250,7 +285,7 @@ export default class WalletsList extends Component {
|
|||
};
|
||||
|
||||
handleLongPress = () => {
|
||||
if (BlueApp.getWallets().length > 1) {
|
||||
if (BlueApp.getWallets().length > 1 && !BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)) {
|
||||
this.props.navigation.navigate('ReorderWallets');
|
||||
} else {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
|
@ -276,7 +311,14 @@ export default class WalletsList extends Component {
|
|||
<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
|
||||
removeClippedSubviews={false}
|
||||
data={this.state.wallets}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { SafeBlueArea, BlueNavigationStyle } from '../../BlueComponents';
|
|||
import SortableList from 'react-native-sortable-list';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
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 WalletGradient from '../../class/walletGradient';
|
||||
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({
|
||||
data: wallets,
|
||||
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