ADD: import HD wallets (watch-only for now)

This commit is contained in:
Overtorment 2018-07-22 15:49:59 +01:00
parent 139010f642
commit 4a10782453
29 changed files with 782 additions and 338 deletions

View File

@ -303,4 +303,14 @@ describe('Watch only wallet', () => {
await w.fetchTransactions();
assert.equal(w.getTransactions().length, 2);
});
it('can validate address', async () => {
let w = new WatchOnlyWallet();
w.setSecret('12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
assert.ok(w.valid());
w.setSecret('3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2');
assert.ok(w.valid());
w.setSecret('not valid');
assert.ok(!w.valid());
});
});

View File

@ -7,6 +7,9 @@ import { Icon, Button, FormLabel, FormInput, Card, Text, Header, List, ListItem
import { TouchableOpacity, ActivityIndicator, View, StyleSheet, Dimensions, Image } from 'react-native';
import { WatchOnlyWallet, LegacyWallet } from './class';
import Carousel from 'react-native-snap-carousel';
import { HDLegacyP2PKHWallet } from './class/hd-legacy-p2pkh-wallet';
import { HDLegacyBreadwalletWallet } from './class/hd-legacy-breadwallet-wallet';
import { HDSegwitP2SHWallet } from './class/hd-segwit-p2sh-wallet';
let loc = require('./loc/');
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
@ -135,7 +138,7 @@ export class BlueButtonLink extends Component {
}}
buttonStyle={{
height: 25,
width: width / 2,
width: width / 1.5,
}}
backgroundColor="transparent"
color="#0c2550"
@ -221,7 +224,33 @@ export class BlueFormInput extends Component {
return (
<FormInput
{...this.props}
inputStyle={{ color: BlueApp.settings.foregroundColor }}
inputStyle={{ color: BlueApp.settings.foregroundColor, maxWidth: width - 105 }}
containerStyle={{
marginTop: 5,
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 0.5,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
}}
/>
);
}
}
export class BlueFormMultiInput extends Component {
render() {
return (
<FormInput
{...this.props}
multiline
numberOfLines={4}
inputStyle={{
width: width - 40,
color: BlueApp.settings.foregroundColor,
height: 120,
fontSize: (isIpad && 10) || ((is.iphone8() && 12) || 14),
}}
containerStyle={{
marginTop: 5,
borderColor: '#d2d2d2',
@ -825,6 +854,21 @@ export class WalletsCarousel extends Component {
gradient2 = '#15be98';
}
if (new HDLegacyP2PKHWallet().type === item.type) {
gradient1 = '#e36dfa';
gradient2 = '#bd10e0';
}
if (new HDLegacyBreadwalletWallet().type === item.type) {
gradient1 = '#fe6381';
gradient2 = '#f99c42';
}
if (new HDSegwitP2SHWallet().type === item.type) {
gradient1 = '#c65afb';
gradient2 = '#9053fe';
}
return (
<TouchableOpacity
activeOpacity={1}

View File

@ -84,7 +84,7 @@ it('can work with malformed mnemonic', () => {
// now, malformed mnemonic
mnemonic =
' honey risk juice trip orient galaxy win !situate ;; shoot ;;; anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode ';
' honey risk juice trip orient galaxy win !situate ;; shoot ;;; anchor Bounce remind\nhorse \n traffic exotic since escape mimic ramp skin judge owner topple erode ';
hd = new HDSegwitP2SHWallet();
hd.setSecret(mnemonic);
let seed2 = hd.getMnemonicToSeedHex();
@ -128,6 +128,10 @@ it('can create a Legacy HD (BIP44)', async function() {
assert.equal(hd.next_free_address_index, 1);
assert.equal(hd.next_free_change_address_index, 1);
for (let tx of hd.getTransactions()) {
assert.ok(tx.value === 1000 || tx.value === 1377 || tx.value === -1377 || tx.value === -1000);
}
// checking that internal pointer and async address getter return the same address
let freeAddress = await hd.getAddressAsync();
assert.equal(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress);
@ -159,3 +163,61 @@ it('HD breadwallet works', async function() {
let freeAddress = await hdBread.getAddressAsync();
assert.equal(hdBread._getExternalAddressByIndex(hdBread.next_free_address_index), freeAddress);
});
it('can convert blockchain.info TX to blockcypher TX format', () => {
const blockchaininfotx = {
hash: '25aa409a9ecbea6a987b35cef18ffa9c53f5ba985bdaadffaac85cdf9fdbb9e1',
ver: 1,
vin_sz: 1,
vout_sz: 1,
size: 189,
weight: 756,
fee: 1184,
relayed_by: '0.0.0.0',
lock_time: 0,
tx_index: 357712243,
double_spend: false,
result: -91300,
balance: 0,
time: 1530469581,
block_height: 530072,
inputs: [
{
prev_out: {
value: 91300,
tx_index: 357704878,
n: 1,
spent: true,
script: '76a9147580ebb44301a1165e73e25bcccd7372e1bbfe9c88ac',
type: 0,
addr: '1BiJW1jyUaxcJp2JWwbPLPzB1toPNWTFJV',
xpub: {
m: 'xpub68nLLEi3KERQY7jyznC9PQSpSjmekrEmN8324YRCXayMXaavbdEJsK4gEcX2bNf9vGzT4xRks9utZ7ot1CTHLtdyCn9udvv1NWvtY7HXroh',
path: 'M/1/117',
},
},
sequence: 4294967295,
script:
'47304402206f676bd8c87dcf6f9e5016a8d222b06cd542d824e3b22c9ae937c05e59590f7602206cfb75a516e70a79e5f33031a189ebca55f1339be8fcd94b1e1fc9149b55354201210339b7fc52be2c33a64f8f4020c9e80fb23f5ee89992a8c5dd070309b001f16a21',
witness: '',
},
],
out: [
{
value: 90116,
tx_index: 357712243,
n: 0,
spent: true,
script: 'a914e286d58e53f9247a4710e51232cce0686f16873c87',
type: 0,
addr: '3NLnALo49CFEF4tCRhCvz45ySSfz3UktZC',
},
],
};
let blockcyphertx = HDSegwitP2SHWallet.convertTx(blockchaininfotx);
assert.ok(blockcyphertx.received); // time
assert.ok(blockcyphertx.hash);
assert.ok(blockcyphertx.value);
assert.ok(blockcyphertx.confirmations);
assert.ok(blockcyphertx.outputs);
});

View File

@ -5,7 +5,6 @@ Amplitude.initialize('8b7cf19e8eea3cdcf16340f5fbf16330');
const analytics = new Analytics('UA-121673546-1');
let A = function(event) {
console.log('posting analytics event', event);
Amplitude.logEvent(event);
analytics.hit(new PageHit(event));
// .then(() => console.log('success'))

View File

@ -1,14 +1,14 @@
{
"expo": {
"sdkVersion": "23.0.0",
"version": "2.2.0",
"version": "2.3.0",
"privacy": "public",
"platforms": [
"ios"
],
"ios": {
"buildNumber": "51",
"supportsTablet": false,
"buildNumber": "53",
"supportsTablet": true,
"isRemoteJSEnabled": false,
"bundleIdentifier": "io.bluewallet.bluewallet",
"infoPlist": {

View File

@ -1,7 +1,7 @@
import { LegacyWallet } from './legacy-wallet';
import Frisbee from 'frisbee';
import { WatchOnlyWallet } from './watch-only-wallet';
const bip39 = require('bip39');
const BigNumber = require('bignumber.js');
export class AbstractHDWallet extends LegacyWallet {
constructor() {
@ -11,14 +11,63 @@ export class AbstractHDWallet extends LegacyWallet {
this.next_free_change_address_index = 0;
this.internal_addresses_cache = {}; // index => address
this.external_addresses_cache = {}; // index => address
this._xpub = ''; // cache
}
allowSend() {
return false; // TODO send from HD
}
getTransactions() {
// need to reformat txs, as we are expected to return them in blockcypher format,
// but they are from blockchain.info actually (for all hd wallets)
let txs = [];
for (let tx of this.transactions) {
txs.push(AbstractHDWallet.convertTx(tx));
}
return txs;
}
static convertTx(tx) {
// console.log('converting', tx);
var clone = Object.assign({}, tx);
clone.received = new Date(clone.time * 1000).toISOString();
clone.confirmations = (clone.block_height && 7) || 0;
clone.outputs = clone.out;
for (let o of clone.outputs) {
o.addresses = [o.addr];
}
for (let i of clone.inputs) {
if (i.prev_out && i.prev_out.addr) {
i.addresses = [i.prev_out.addr];
}
}
if (!clone.value) {
let value = 0;
for (let inp of clone.inputs) {
if (inp.prev_out && inp.prev_out.xpub) {
// our owned
value -= inp.prev_out.value;
}
}
for (let out of clone.out) {
if (out.xpub) {
// to us
value += out.value;
}
}
clone.value = value;
}
return clone;
}
setSecret(newSecret) {
this.secret = newSecret.trim();
this.secret = newSecret.trim().toLowerCase();
this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' ');
return this;
}
@ -32,7 +81,7 @@ export class AbstractHDWallet extends LegacyWallet {
}
getTypeReadable() {
return 'HD SegWit (BIP49 P2SH)';
throw new Error('Not implemented');
}
/**
@ -43,7 +92,40 @@ export class AbstractHDWallet extends LegacyWallet {
* @return {Promise.<string>}
*/
async getAddressAsync() {
throw new Error('Not implemented');
// looking for free external address
let freeAddress = '';
let c;
for (c = -1; c < 5; c++) {
if (this.next_free_address_index + c < 0) continue;
let address = this._getExternalAddressByIndex(this.next_free_address_index + c);
let WatchWallet = new WatchOnlyWallet();
WatchWallet.setSecret(address);
await WatchWallet.fetchTransactions();
if (WatchWallet.transactions.length === 0) {
// found free address
freeAddress = WatchWallet.getAddress();
this.next_free_address_index += c; // now points to _this one_
break;
}
}
if (!freeAddress) {
// could not find in cycle above, give up
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c + 1; // now points to the one _after_
}
return freeAddress;
}
/**
* Should not be used in HD wallets
*
* @deprecated
* @return {string}
*/
getAddress() {
return '';
}
_getExternalWIFByIndex(index) {
@ -66,17 +148,25 @@ export class AbstractHDWallet extends LegacyWallet {
throw new Error('Not implemented');
}
/**
* @inheritDoc
*/
async fetchBalance() {
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
try {
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
let response = await api.get('/balance?active=' + this.getXpub());
let response = await api.get('/balance?active=' + this.getXpub());
if (response && response.body) {
for (let xpub of Object.keys(response.body)) {
this.balance = response.body[xpub].final_balance / 100000000;
if (response && response.body) {
for (let xpub of Object.keys(response.body)) {
this.balance = response.body[xpub].final_balance / 100000000;
}
this._lastBalanceFetch = +new Date();
} else {
throw new Error('Could not fetch balance from API: ' + response.err);
}
} else {
throw new Error('Could not fetch balance from API');
} catch (err) {
console.warn(err);
}
}
@ -89,79 +179,88 @@ export class AbstractHDWallet extends LegacyWallet {
* @returns {Promise<void>}
*/
async fetchTransactions() {
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
this.transactions = [];
let offset = 0;
try {
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
this.transactions = [];
let offset = 0;
while (1) {
let response = await api.get('/multiaddr?active=' + this.getXpub() + '&n=100&offset=' + offset);
while (1) {
let response = await api.get('/multiaddr?active=' + this.getXpub() + '&n=100&offset=' + offset);
if (response && response.body) {
if (response.body.txs && response.body.txs.length === 0) {
break;
}
if (response && response.body) {
if (response.body.txs && response.body.txs.length === 0) {
break;
}
// processing TXs and adding to internal memory
if (response.body.txs) {
for (let tx of response.body.txs) {
let value = 0;
// processing TXs and adding to internal memory
if (response.body.txs) {
for (let tx of response.body.txs) {
let value = 0;
for (let input of tx.inputs) {
// ----- INPUTS
if (input.prev_out.xpub) {
// sent FROM US
value -= input.prev_out.value;
for (let input of tx.inputs) {
// ----- INPUTS
if (input.prev_out.xpub) {
// sent FROM US
value -= input.prev_out.value;
// setting internal caches to help ourselves in future...
let path = input.prev_out.xpub.path.split('/');
if (path[path.length - 2] === '1') {
// change address
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
// setting to point to last maximum known change address + 1
// setting internal caches to help ourselves in future...
let path = input.prev_out.xpub.path.split('/');
if (path[path.length - 2] === '1') {
// change address
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
// setting to point to last maximum known change address + 1
}
if (path[path.length - 2] === '0') {
// main (aka external) address
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
// setting to point to last maximum known main address + 1
}
// done with cache
}
if (path[path.length - 2] === '0') {
// main (aka external) address
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
// setting to point to last maximum known main address + 1
}
// done with cache
}
for (let output of tx.out) {
// ----- OUTPUTS
if (output.xpub) {
// sent TO US (change)
value += output.value;
// setting internal caches to help ourselves in future...
let path = output.xpub.path.split('/');
if (path[path.length - 2] === '1') {
// change address
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
// setting to point to last maximum known change address + 1
}
if (path[path.length - 2] === '0') {
// main (aka external) address
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
// setting to point to last maximum known main address + 1
}
// done with cache
}
}
tx.value = value; // new BigNumber(value).div(100000000).toString() * 1;
this.transactions.push(tx);
}
for (let output of tx.out) {
// ----- OUTPUTS
if (output.xpub) {
// sent TO US (change)
value += output.value;
// setting internal caches to help ourselves in future...
let path = output.xpub.path.split('/');
if (path[path.length - 2] === '1') {
// change address
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
// setting to point to last maximum known change address + 1
}
if (path[path.length - 2] === '0') {
// main (aka external) address
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
// setting to point to last maximum known main address + 1
}
// done with cache
}
if (response.body.txs.length < 100) {
// this fetch yilded less than page size, thus requesting next batch makes no sense
break;
}
tx.value = new BigNumber(value).div(100000000).toString() * 1;
this.transactions.push(tx);
} else {
break; // error ?
}
} else {
break; // error ?
throw new Error('Could not fetch transactions from API: ' + response.err); // breaks here
}
} else {
throw new Error('Could not fetch transactions from API'); // breaks here
}
offset += 100;
offset += 100;
}
} catch (err) {
console.warn(err);
}
}
}

View File

@ -1,5 +1,13 @@
import { AsyncStorage } from 'react-native';
import { WatchOnlyWallet, LegacyWallet, SegwitP2SHWallet, SegwitBech32Wallet } from './';
import {
HDLegacyBreadwalletWallet,
HDSegwitP2SHWallet,
HDLegacyP2PKHWallet,
WatchOnlyWallet,
LegacyWallet,
SegwitP2SHWallet,
SegwitBech32Wallet,
} from './';
let encryption = require('../encryption');
export class AppStorage {
@ -130,6 +138,15 @@ export class AppStorage {
case 'watchOnly':
unserializedWallet = WatchOnlyWallet.fromJson(key);
break;
case new HDLegacyP2PKHWallet().type:
unserializedWallet = HDLegacyP2PKHWallet.fromJson(key);
break;
case new HDSegwitP2SHWallet().type:
unserializedWallet = HDSegwitP2SHWallet.fromJson(key);
break;
case new HDLegacyBreadwalletWallet().type:
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
break;
case 'legacy':
default:
unserializedWallet = LegacyWallet.fromJson(key);
@ -281,7 +298,7 @@ export class AppStorage {
let c = 0;
for (let wallet of this.wallets) {
if (c++ === index) {
txs = txs.concat(wallet.transactions);
txs = txs.concat(wallet.getTransactions());
}
}
return txs;
@ -289,7 +306,7 @@ export class AppStorage {
let txs = [];
for (let wallet of this.wallets) {
txs = txs.concat(wallet.transactions);
txs = txs.concat(wallet.getTransactions());
}
return txs;
}

View File

@ -1,4 +1,3 @@
import { LegacyWallet } from './';
import { AbstractHDWallet } from './abstract-hd-wallet';
const bitcoin = require('bitcoinjs-lib');
const bip39 = require('bip39');
@ -23,17 +22,22 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
* @see https://github.com/bitcoinjs/bitcoinjs-lib/issues/997
*/
getXpub() {
if (this._xpub) {
return this._xpub; // cache hit
}
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/0'";
let child = root.derivePath(path).neutered();
return child.toBase58();
this._xpub = child.toBase58();
return this._xpub;
}
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -41,12 +45,12 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
let path = "m/0'/0/" + index;
let child = root.derivePath(path);
return child.getAddress();
return (this.external_addresses_cache[index] = child.getAddress());
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -54,7 +58,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
let path = "m/0'/1/" + index;
let child = root.derivePath(path);
return child.getAddress();
return (this.internal_addresses_cache[index] = child.getAddress());
}
_getExternalWIFByIndex(index) {
@ -76,32 +80,4 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
let child = root.derivePath(path);
return child.keyPair.toWIF();
}
/**
* @inheritDoc
*/
async getAddressAsync() {
// looking for free external address
let freeAddress = '';
let c;
for (c = -1; c < 5; c++) {
let Legacy = new LegacyWallet();
Legacy.setSecret(this._getExternalWIFByIndex(this.next_free_address_index + c));
await Legacy.fetchTransactions();
if (Legacy.transactions.length === 0) {
// found free address
freeAddress = Legacy.getAddress();
this.next_free_address_index += c; // now points to _this one_
break;
}
}
if (!freeAddress) {
// could not find in cycle above, give up
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c + 1; // now points to the one _after_
}
return freeAddress;
}
}

View File

@ -19,17 +19,22 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
}
getXpub() {
if (this._xpub) {
return this._xpub; // cache hit
}
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/44'/0'/0'";
let child = root.derivePath(path).neutered();
return child.toBase58();
this._xpub = child.toBase58();
return this._xpub;
}
_getExternalWIFByIndex(index) {
index = index * 1; // cast to int
if (index < 0) return '';
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -50,6 +55,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -58,11 +64,12 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
let w = new LegacyWallet();
w.setSecret(child.keyPair.toWIF());
return w.getAddress();
return (this.external_addresses_cache[index] = w.getAddress());
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -72,34 +79,6 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
let w = new LegacyWallet();
w.setSecret(child.keyPair.toWIF());
return w.getAddress();
}
/**
* @inheritDoc
*/
async getAddressAsync() {
// looking for free external address
let freeAddress = '';
let c;
for (c = -1; c < 5; c++) {
let Legacy = new LegacyWallet();
Legacy.setSecret(this._getExternalWIFByIndex(this.next_free_address_index + c));
await Legacy.fetchTransactions();
if (Legacy.transactions.length === 0) {
// found free address
freeAddress = Legacy.getAddress();
this.next_free_address_index += c; // now points to _this one_
break;
}
}
if (!freeAddress) {
// could not find in cycle above, give up
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c + 1; // now points to the one _after_
}
return freeAddress;
return (this.internal_addresses_cache[index] = w.getAddress());
}
}

View File

@ -1,5 +1,4 @@
import { AbstractHDWallet } from './abstract-hd-wallet';
import { SegwitP2SHWallet } from './segwit-p2sh-wallet';
import Frisbee from 'frisbee';
const bitcoin = require('bitcoinjs-lib');
const bip39 = require('bip39');
@ -39,46 +38,15 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
}
this.balance = new BigNumber(this.balance).div(100000000).toString() * 1;
this.unconfirmed_balance = new BigNumber(this.unconfirmed_balance).div(100000000).toString() * 1;
this._lastBalanceFetch = +new Date();
} else {
throw new Error('Could not fetch balance from API: ' + response.err);
}
} catch (err) {
console.warn(err);
}
}
/**
* Derives from hierarchy, returns next free address
* (the one that has no transactions). Looks for several,
* gives up if none found, and returns the used one
*
* @return {Promise.<string>}
*/
async getAddressAsync() {
// looking for free external address
let freeAddress = '';
let c;
for (c = -1; c < 5; c++) {
if (this.next_free_address_index + c < 0) continue;
let Segwit = new SegwitP2SHWallet();
Segwit.setSecret(this._getExternalWIFByIndex(this.next_free_address_index + c));
await Segwit.fetchTransactions();
if (Segwit.transactions.length === 0) {
// found free address
freeAddress = Segwit.getAddress();
console.log('found free ', freeAddress, ' with index', this.next_free_address_index + c);
this.next_free_address_index += c; // now points to _this one_
break;
}
}
if (!freeAddress) {
// could not find in cycle above, give up
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c + 1; // now points to the one _after_
}
return freeAddress;
}
_getExternalWIFByIndex(index) {
index = index * 1; // cast to int
let mnemonic = this.secret;
@ -101,6 +69,7 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -111,11 +80,12 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
return bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin);
return (this.external_addresses_cache[index] = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin));
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
@ -127,7 +97,7 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
return bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin);
return (this.internal_addresses_cache[index] = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin));
}
/**
@ -137,6 +107,9 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
* @return {String} ypub
*/
getXpub() {
if (this._xpub) {
return this._xpub; // cache hit
}
// first, getting xpub
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
@ -150,90 +123,86 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
let data = b58.decode(xpub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('049d7cb2', 'hex'), data]);
return b58.encode(data);
this._xpub = b58.encode(data);
return this._xpub;
}
/**
* @inheritDoc
*/
async fetchTransactions() {
if (this.usedAddresses.length === 0) {
// just for any case, refresh balance (it refreshes internal `this.usedAddresses`)
await this.fetchBalance();
}
let addresses = this.usedAddresses.join('|');
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
this.transactions = [];
let offset = 0;
while (1) {
let response = await api.get('/multiaddr?active=' + addresses + '&n=100&offset=' + offset);
if (response && response.body) {
if (response.body.txs && response.body.txs.length === 0) {
break;
}
// processing TXs and adding to internal memory
if (response.body.txs) {
for (let tx of response.body.txs) {
let value = 0;
for (let input of tx.inputs) {
// ----- INPUTS
if (input.prev_out.xpub) {
// sent FROM US
value -= input.prev_out.value;
// setting internal caches to help ourselves in future...
let path = input.prev_out.xpub.path.split('/');
if (path[path.length - 2] === '1') {
// change address
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
// setting to point to last maximum known change address + 1
}
if (path[path.length - 2] === '0') {
// main (aka external) address
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
// setting to point to last maximum known main address + 1
}
// done with cache
}
}
for (let output of tx.out) {
// ----- OUTPUTS
if (output.xpub) {
// sent TO US (change)
value += output.value;
// setting internal caches to help ourselves in future...
let path = output.xpub.path.split('/');
if (path[path.length - 2] === '1') {
// change address
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
// setting to point to last maximum known change address + 1
}
if (path[path.length - 2] === '0') {
// main (aka external) address
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
// setting to point to last maximum known main address + 1
}
// done with cache
}
}
tx.value = new BigNumber(value).div(100000000).toString() * 1;
this.transactions.push(tx);
}
} else {
break; // error ?
}
} else {
throw new Error('Could not fetch transactions from API'); // breaks here
try {
if (this.usedAddresses.length === 0) {
// just for any case, refresh balance (it refreshes internal `this.usedAddresses`)
await this.fetchBalance();
}
offset += 100;
let addresses = this.usedAddresses.join('|');
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
this.transactions = [];
let offset = 0;
while (1) {
let response = await api.get('/multiaddr?active=' + addresses + '&n=100&offset=' + offset);
if (response && response.body) {
if (response.body.txs && response.body.txs.length === 0) {
break;
}
// processing TXs and adding to internal memory
if (response.body.txs) {
for (let tx of response.body.txs) {
let value = 0;
for (let input of tx.inputs) {
// ----- INPUTS
if (input.prev_out && input.prev_out.addr && this.weOwnAddress(input.prev_out.addr)) {
// this is outgoing from us
value -= input.prev_out.value;
}
}
for (let output of tx.out) {
// ----- OUTPUTS
if (output.addr && this.weOwnAddress(output.addr)) {
// this is incoming to us
value += output.value;
}
}
tx.value = value; // new BigNumber(value).div(100000000).toString() * 1;
this.transactions.push(tx);
}
if (response.body.txs.length < 100) {
// this fetch yilded less than page size, thus requesting next batch makes no sense
break;
}
} else {
break; // error ?
}
} else {
throw new Error('Could not fetch transactions from API: ' + response.err); // breaks here
}
offset += 100;
}
} catch (err) {
console.warn(err);
}
}
weOwnAddress(addr) {
let hashmap = {};
for (let a of this.usedAddresses) {
hashmap[a] = 1;
}
return hashmap[addr] === 1;
}
}

View File

@ -220,7 +220,6 @@ export class LegacyWallet extends AbstractWallet {
value -= inp.output_value;
}
}
console.log('came out', value);
tx.value += value;
// end
}
@ -229,33 +228,6 @@ export class LegacyWallet extends AbstractWallet {
}
}
getShortAddress() {
let a = this.getAddress().split('');
return (
a[0] +
a[1] +
a[2] +
a[3] +
a[4] +
a[5] +
a[6] +
a[7] +
a[8] +
a[9] +
a[10] +
a[11] +
a[12] +
a[13] +
'...' +
a[a.length - 6] +
a[a.length - 5] +
a[a.length - 4] +
a[a.length - 3] +
a[a.length - 2] +
a[a.length - 1]
);
}
async broadcastTx(txhex) {
let chainso = await this._broadcastTxChainso(txhex);
console.log('chainso = ', chainso);
@ -365,13 +337,13 @@ export class LegacyWallet extends AbstractWallet {
u.amount = u.amount.div(100000000);
u.amount = u.amount.toString(10);
}
console.log('creating legacy tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
// console.log('creating legacy tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
let amountPlusFee = parseFloat(new BigNumber(amount).add(fee).toString(10));
return signer.createTransaction(utxos, toAddress, amountPlusFee, fee, this.getSecret(), this.getAddress());
}
getLatestTransactionTime() {
for (let tx of this.transactions) {
for (let tx of this.getTransactions()) {
return tx.received;
}
return 0;

View File

@ -65,7 +65,7 @@ export class SegwitP2SHWallet extends LegacyWallet {
u.amount = u.amount.div(100000000);
u.amount = u.amount.toString(10);
}
console.log('creating tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
// console.log('creating tx ', amount, ' with fee ', fee, 'secret=', this.getSecret(), 'from address', this.getAddress());
let amountPlusFee = parseFloat(new BigNumber(amount).add(fee).toString(10));
// to compensate that module substracts fee from amount
return signer.createSegwitTransaction(utxos, address, amountPlusFee, fee, this.getSecret(), this.getAddress(), sequence);

View File

@ -1,4 +1,5 @@
import { LegacyWallet } from './legacy-wallet';
const bitcoin = require('bitcoinjs-lib');
export class WatchOnlyWallet extends LegacyWallet {
constructor() {
@ -21,4 +22,13 @@ export class WatchOnlyWallet extends LegacyWallet {
createTx(utxos, amount, fee, toAddress, memo) {
throw new Error('Not supported');
}
valid() {
try {
bitcoin.address.toOutputScript(this.getAddress());
return true;
} catch (e) {
return false;
}
}
}

View File

@ -23,7 +23,7 @@ EV.enum = {
WALLETS_COUNT_CHANGED: 'WALLETS_COUNT_CHANGED',
TRANSACTIONS_COUNT_CHANGED: 'TRANSACTIONS_COUNT_CHANGED',
CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS: 'CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS',
RECEIVE_ADDRESS_CHANGED: 'RECEIVE_ADDRESS_CHANGED',
// RECEIVE_ADDRESS_CHANGED: 'RECEIVE_ADDRESS_CHANGED',
};
module.exports = EV;

View File

@ -1 +1 @@
Easy to use and secure

View File

@ -1 +1 @@
Easy to use and secure

View File

@ -1 +1 @@
Easy to use and secure

View File

@ -1 +1 @@
Easy to use and secure

View File

@ -1 +1 @@
Easy to use and secure

View File

@ -33,6 +33,7 @@ module.exports = {
wallet_type: 'wallet type',
or: 'or',
import_wallet: 'Import wallet',
imported: 'Imported',
},
details: {
title: 'wallet details',

View File

@ -1,6 +1,6 @@
{
"name": "BlueWallet",
"version": "2.2.0",
"version": "2.3.0",
"devDependencies": {
"babel-eslint": "^8.2.2",
"eslint": "^4.19.0",

4
release-notes.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
TAG=`git tag | sort | tail -n 1`
HASH=`git show-ref -s $TAG`
git log --oneline $HASH..HEAD

View File

@ -7,22 +7,15 @@ import {
SafeBlueArea,
BlueCard,
BlueHeaderDefaultSub,
BlueSpacing,
BlueSpacing40,
BlueSpacingVariable,
is,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let EV = require('../../events');
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
} else {
isIpad = true;
}
// let EV = require('../../events');
const { width } = Dimensions.get('window');
export default class ReceiveDetails extends Component {
static navigationOptions = {
@ -32,44 +25,63 @@ export default class ReceiveDetails extends Component {
constructor(props) {
super(props);
let address = props.navigation.state.params.address;
let secret = props.navigation.state.params.secret;
this.state = {
isLoading: true,
address: address,
secret: secret,
};
console.log(JSON.stringify(address));
EV(EV.enum.RECEIVE_ADDRESS_CHANGED, this.refreshFunction.bind(this));
// EV(EV.enum.RECEIVE_ADDRESS_CHANGED, this.refreshFunction.bind(this));
}
refreshFunction(newAddress) {
/* refreshFunction(newAddress) {
console.log('newAddress =', newAddress);
this.setState({
address: newAddress,
});
}
} */
async componentDidMount() {
console.log('receive/details - componentDidMount');
this.setState({
isLoading: false,
});
/** @type {AbstractWallet} */
let wallet;
let address = this.state.address;
for (let w of BlueApp.getWallets()) {
if ((address && w.getAddress() === this.state.address) || w.getSecret() === this.state.secret) {
// found our wallet
wallet = w;
}
}
if (wallet && wallet.getAddressAsync) {
setTimeout(async () => {
address = await wallet.getAddressAsync();
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
this.setState({
address: address,
isLoading: false,
});
}, 1);
} else {
this.setState({
isLoading: false,
address,
});
}
}
render() {
console.log('render() receive/details, address=', this.state.address);
console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret);
if (this.state.isLoading) {
return <BlueLoading />;
}
return (
<SafeBlueArea style={{ flex: 1 }}>
{(() => {
if (isIpad) {
return <BlueSpacing40 />;
} else {
return <BlueSpacing />;
}
})()}
<BlueSpacingVariable />
<BlueHeaderDefaultSub leftText={loc.receive.list.header} onClose={() => this.props.navigation.goBack()} />
<BlueCard
@ -85,12 +97,12 @@ export default class ReceiveDetails extends Component {
<View
style={{
left: (width - ((isIpad && 250) || 312)) / 2,
left: (width - ((is.ipad() && 250) || 312)) / 2,
}}
>
<QRCode
value={this.state.address}
size={(isIpad && 250) || 312}
size={(is.ipad() && 250) || 312}
bgColor={BlueApp.settings.foregroundColor}
fgColor={BlueApp.settings.brandingColor}
/>
@ -106,6 +118,7 @@ ReceiveDetails.propTypes = {
state: PropTypes.shape({
params: PropTypes.shape({
address: PropTypes.string,
secret: PropTypes.string,
}),
}),
}),

View File

@ -2,6 +2,7 @@ import { StackNavigator } from 'react-navigation';
import WalletsList from './wallets/list';
import AddWallet from './wallets/add';
import ImportWallet from './wallets/import';
import WalletDetails from './wallets/details';
import WalletExport from './wallets/export';
import scanQrWif from './wallets/scanQrWif';
@ -24,6 +25,9 @@ const WalletsNavigator = StackNavigator(
AddWallet: {
screen: AddWallet,
},
ImportWallet: {
screen: ImportWallet,
},
ScanQrWif: {
screen: scanQrWif,
},

View File

@ -144,7 +144,7 @@ export default class WalletsAdd extends Component {
<BlueButtonLink
title={loc.wallets.add.import_wallet}
onPress={() => {
this.props.navigation.navigate('ScanQrWif');
this.props.navigation.navigate('ImportWallet');
}}
/>
</View>

View File

@ -27,12 +27,13 @@ export default class WalletDetails extends Component {
super(props);
let address = props.navigation.state.params.address;
let secret = props.navigation.state.params.secret;
/** @type {AbstractWallet} */
let wallet;
for (let w of BlueApp.getWallets()) {
if (w.getAddress() === address) {
if ((address && w.getAddress() === address) || w.getSecret() === secret) {
// found our wallet
wallet = w;
}
@ -82,8 +83,16 @@ export default class WalletDetails extends Component {
<BlueHeaderDefaultSub leftText={loc.wallets.details.title} onClose={() => this.props.navigation.goBack()} />
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
<BlueFormLabel>{loc.wallets.details.address}:</BlueFormLabel>
<BlueFormInputAddress value={this.state.wallet.getAddress()} editable />
{(() => {
if (this.state.wallet.getAddress()) {
return (
<View>
<BlueFormLabel>{loc.wallets.details.address}:</BlueFormLabel>
<BlueFormInputAddress value={this.state.wallet.getAddress()} editable />
</View>
);
}
})()}
<BlueFormLabel>{loc.wallets.details.type}:</BlueFormLabel>
<BlueFormInput value={this.state.wallet.getTypeReadable()} editable={false} />
@ -155,6 +164,7 @@ export default class WalletDetails extends Component {
onPress={() =>
this.props.navigation.navigate('WalletExport', {
address: this.state.wallet.getAddress(),
secret: this.state.wallet.getSecret(),
})
}
title={loc.wallets.details.export_backup}
@ -176,6 +186,7 @@ WalletDetails.propTypes = {
state: PropTypes.shape({
params: PropTypes.shape({
address: PropTypes.string,
secret: PropTypes.string,
}),
}),
navigate: PropTypes.func,

View File

@ -24,10 +24,11 @@ export default class WalletExport extends Component {
super(props);
let address = props.navigation.state.params.address;
let secret = props.navigation.state.params.secret;
let wallet;
for (let w of BlueApp.getWallets()) {
if (w.getAddress() === address) {
if ((address && w.getAddress() === address) || w.getSecret() === secret) {
// found our wallet
wallet = w;
}
@ -76,7 +77,15 @@ export default class WalletExport extends Component {
<BlueHeaderDefaultSub leftText={loc.wallets.export.title} onClose={() => this.props.navigation.goBack()} />
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
<BlueText>{this.state.wallet.getAddress()}</BlueText>
{(() => {
if (this.state.wallet.getAddress()) {
return (
<View>
<BlueText>{this.state.wallet.getAddress()}</BlueText>
</View>
);
}
})()}
<QRCode
value={this.state.wallet.getSecret()}
size={312}
@ -95,6 +104,7 @@ WalletExport.propTypes = {
state: PropTypes.shape({
params: PropTypes.shape({
address: PropTypes.string,
secret: PropTypes.string,
}),
}),
navigate: PropTypes.func,

254
screen/wallets/import.js Normal file
View File

@ -0,0 +1,254 @@
/* global alert */
import {
SegwitP2SHWallet,
LegacyWallet,
WatchOnlyWallet,
HDLegacyBreadwalletWallet,
HDSegwitP2SHWallet,
HDLegacyP2PKHWallet,
} from '../../class';
import React, { Component } from 'react';
import { KeyboardAvoidingView, ActivityIndicator, Dimensions, View } from 'react-native';
import {
BlueFormMultiInput,
BlueButtonLink,
BlueFormLabel,
BlueSpacingVariable,
BlueButton,
SafeBlueArea,
BlueHeaderDefaultSub,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
let EV = require('../../events');
let A = require('../../analytics');
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
const { width } = Dimensions.get('window');
export default class WalletsImport extends Component {
static navigationOptions = {
tabBarVisible: false,
};
constructor(props) {
super(props);
this.state = {
isLoading: true,
};
}
async componentDidMount() {
this.setState({
isLoading: false,
label: '',
});
}
async _saveWallet(w) {
alert('Success');
// await w.fetchTransactions();
w.setLabel(loc.wallets.add.imported + ' ' + w.getTypeReadable());
BlueApp.wallets.push(w);
await BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET);
setTimeout(() => {
this.props.navigation.popToTop();
}, 500);
}
async importMnemonic(text) {
try {
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);
}
}
// 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);
}
}
// is it even valid? if yes we will import as:
if (hd2.validateMnemonic()) {
return this._saveWallet(hd2);
}
// 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('Error');
// TODO:
// 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)
// 7. check if its private key (legacy address)
// this.props.navigation.goBack();
/* let w = new SegwitP2SHWallet();
w.setLabel(this.state.label || loc.wallets.add.label_new_segwit);
w.generate();
BlueApp.wallets.push(w);
BlueApp.saveToDisk();
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET); */
}
setLabel(text) {
this.setState({
label: text,
}); /* also, a hack to make screen update new typed text */
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
<KeyboardAvoidingView behavior="position" enabled>
<BlueSpacingVariable />
<BlueHeaderDefaultSub leftText={'Import'} onClose={() => this.props.navigation.goBack()} />
<BlueFormLabel>
Write here you mnemonic, private key, WIF, or anything you've got. BlueWallet will do it's best to guess the correct format and
import your wallet
</BlueFormLabel>
<BlueFormMultiInput
value={this.state.label}
placeholder={''}
onChangeText={text => {
this.setLabel(text);
}}
/>
<View
style={{
alignItems: 'center',
}}
>
<BlueButton
title={'Import'}
buttonStyle={{
width: width / 1.5,
}}
onPress={async () => {
if (!this.state.label) {
return;
}
this.setState({ isLoading: true });
await this.importMnemonic(this.state.label.trim());
this.setState({ isLoading: false });
}}
/>
<BlueButtonLink
title={'or scan QR code instead?'}
onPress={() => {
this.props.navigation.navigate('ScanQrWif');
}}
/>
</View>
</KeyboardAvoidingView>
</SafeBlueArea>
);
}
}
WalletsImport.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
popToTop: PropTypes.func,
goBack: PropTypes.func,
}),
};

View File

@ -16,6 +16,7 @@ import {
is,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
const BigNumber = require('bignumber.js');
let EV = require('../../events');
let A = require('../../analytics');
/** @type {AppStorage} */
@ -56,8 +57,11 @@ export default class WalletsList extends Component {
// more responsive
let noErr = true;
try {
await BlueApp.fetchWalletTransactions(that.lastSnappedTo || 0);
await BlueApp.fetchWalletBalances(that.lastSnappedTo || 0);
let start = +new Date();
await BlueApp.fetchWalletTransactions(that.lastSnappedTo || 0);
let end = +new Date();
console.log('tx took', (end - start) / 1000, 'sec');
} catch (err) {
noErr = false;
console.warn(err);
@ -111,7 +115,8 @@ export default class WalletsList extends Component {
let wallet = BlueApp.wallets[index];
if (wallet) {
this.props.navigation.navigate('WalletDetails', {
address: wallet.getAddress(),
address: wallet.getAddress(), // either one of them will work
secret: wallet.getSecret(),
});
} else {
// if its out of index - this must be last card with incentive to create wallet
@ -333,7 +338,7 @@ export default class WalletsList extends Component {
}}
chevron={false}
chevronColor="transparent"
rightTitle={rowData.value / 100000000 + ''}
rightTitle={new BigNumber(rowData.value).div(100000000).toString()}
rightTitleStyle={{
position: 'relative',
right: -30,
@ -358,17 +363,22 @@ export default class WalletsList extends Component {
return (
<BlueReceiveButtonIcon
onPress={() => {
let start = +new Date();
let walletIndex = this.lastSnappedTo || 0;
console.log('receiving on #', walletIndex);
let c = 0;
for (let w of BlueApp.getWallets()) {
if (c++ === walletIndex) {
console.log('found receiving address ', w.getAddress());
navigate('ReceiveDetails', { address: w.getAddress() });
EV(EV.enum.RECEIVE_ADDRESS_CHANGED, w.getAddress());
console.log('found receiving address, secret=', w.getAddress(), ',', w.getSecret());
navigate('ReceiveDetails', { address: w.getAddress(), secret: w.getSecret() });
if (w.getAddress()) {
// EV(EV.enum.RECEIVE_ADDRESS_CHANGED, w.getAddress());
}
}
}
let end = +new Date();
console.log('took', (end - start) / 1000, 'sec');
}}
/>
);