mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
ADD: import HD wallets (watch-only for now)
This commit is contained in:
parent
139010f642
commit
4a10782453
10
App.test.js
10
App.test.js
@ -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());
|
||||
});
|
||||
});
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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'))
|
||||
|
6
app.json
6
app.json
@ -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": {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -1 +1 @@
|
||||
|
||||
Easy to use and secure
|
||||
|
@ -1 +1 @@
|
||||
|
||||
Easy to use and secure
|
||||
|
@ -1 +1 @@
|
||||
|
||||
Easy to use and secure
|
||||
|
@ -1 +1 @@
|
||||
|
||||
Easy to use and secure
|
||||
|
@ -1 +1 @@
|
||||
|
||||
Easy to use and secure
|
||||
|
@ -33,6 +33,7 @@ module.exports = {
|
||||
wallet_type: 'wallet type',
|
||||
or: 'or',
|
||||
import_wallet: 'Import wallet',
|
||||
imported: 'Imported',
|
||||
},
|
||||
details: {
|
||||
title: 'wallet details',
|
||||
|
@ -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
4
release-notes.sh
Executable 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
|
@ -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,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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
254
screen/wallets/import.js
Normal 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,
|
||||
}),
|
||||
};
|
@ -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');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user