Browse Source

ADD: HD bech32 wallets (BIP84)

fixqramount
Overtorment 5 years ago
parent
commit
3afeefaed9
  1. 6
      HDBech32Wallet.test.js
  2. 4
      class/app-storage.js
  3. 14
      class/hd-segwit-bech32-wallet.js
  4. 8
      class/walletGradient.js
  5. 54
      screen/send/details.js
  6. 14
      screen/wallets/add.js
  7. 22
      screen/wallets/import.js
  8. 31
      screen/wallets/scanQrWif.js

6
HDBech32Wallet.test.js

@ -101,8 +101,14 @@ describe('Bech32 Segwit HD (BIP84)', () => {
hd.setSecret(process.env.HD_MNEMONIC);
assert.ok(hd.validateMnemonic());
assert.strictEqual(hd.timeToRefreshBalance(), true);
assert.ok(hd._lastTxFetch === 0);
assert.ok(hd._lastBalanceFetch === 0);
await hd.fetchBalance();
await hd.fetchTransactions();
assert.ok(hd._lastTxFetch > 0);
assert.ok(hd._lastBalanceFetch > 0);
assert.strictEqual(hd.timeToRefreshBalance(), false);
assert.strictEqual(hd.getTransactions().length, 4);
for (let tx of hd.getTransactions()) {

4
class/app-storage.js

@ -7,6 +7,7 @@ import {
LegacyWallet,
SegwitP2SHWallet,
SegwitBech32Wallet,
HDSegwitBech32Wallet,
} from './';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import WatchConnectivity from '../WatchConnectivity';
@ -170,6 +171,9 @@ export class AppStorage {
case HDSegwitP2SHWallet.type:
unserializedWallet = HDSegwitP2SHWallet.fromJson(key);
break;
case HDSegwitBech32Wallet.type:
unserializedWallet = HDSegwitBech32Wallet.fromJson(key);
break;
case HDLegacyBreadwalletWallet.type:
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
break;

14
class/hd-segwit-bech32-wallet.js

@ -68,7 +68,17 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
for (let bal of Object.values(this._balances_by_internal_index)) {
ret += bal.c;
}
return ret;
return ret + this.getUnconfirmedBalance();
}
/**
* @inheritDoc
*/
timeToRefreshTransaction() {
for (let tx of this.getTransactions()) {
if (tx.confirmations < 7) return true;
}
return false;
}
getUnconfirmedBalance() {
@ -240,6 +250,8 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
this._txs_by_internal_index[c] = await BlueElectrum.getTransactionsFullByAddress(this._getInternalAddressByIndex(c));
}
}
this._lastTxFetch = +new Date();
}
getTransactions() {

8
class/walletGradient.js

@ -4,9 +4,11 @@ import { LightningCustodianWallet } from './lightning-custodian-wallet';
import { HDLegacyBreadwalletWallet } from './hd-legacy-breadwallet-wallet';
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
import { WatchOnlyWallet } from './watch-only-wallet';
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
export default class WalletGradient {
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
static hdSegwitBech32Wallet = ['#68bbe1', '#3b73d4'];
static watchOnlyWallet = ['#7d7d7d', '#4a4a4a'];
static legacyWallet = ['#40fad1', '#15be98'];
static hdLegacyP2PKHWallet = ['#e36dfa', '#bd10e0'];
@ -33,6 +35,9 @@ export default class WalletGradient {
case HDSegwitP2SHWallet.type:
gradient = WalletGradient.hdSegwitP2SHWallet;
break;
case HDSegwitBech32Wallet.type:
gradient = WalletGradient.hdSegwitBech32Wallet;
break;
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
@ -64,6 +69,9 @@ export default class WalletGradient {
case HDSegwitP2SHWallet.type:
gradient = WalletGradient.hdSegwitP2SHWallet;
break;
case HDSegwitBech32Wallet.type:
gradient = WalletGradient.hdSegwitBech32Wallet;
break;
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;

54
screen/send/details.js

@ -29,7 +29,7 @@ import Modal from 'react-native-modal';
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
import BitcoinBIP70TransactionDecode from '../../bip70/bip70';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { HDLegacyP2PKHWallet, HDSegwitP2SHWallet, LightningCustodianWallet } from '../../class';
import { HDLegacyP2PKHWallet, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const bip21 = require('bip21');
let BigNumber = require('bignumber.js');
@ -50,7 +50,6 @@ export default class SendDetails extends Component {
constructor(props) {
super(props);
console.log('props.navigation.state.params=', props.navigation.state.params);
let address;
let memo;
if (props.navigation.state.params) address = props.navigation.state.params.address;
@ -223,6 +222,7 @@ export default class SendDetails extends Component {
let availableBalance;
try {
availableBalance = new BigNumber(balance);
availableBalance = availableBalance.div(100000000); // sat2btc
availableBalance = availableBalance.minus(amount);
availableBalance = availableBalance.minus(fee);
availableBalance = availableBalance.toString(10);
@ -307,8 +307,6 @@ export default class SendDetails extends Component {
let error = false;
let requestedSatPerByte = this.state.fee.toString().replace(/\D/g, '');
console.log({ requestedSatPerByte });
if (!this.state.amount || this.state.amount === '0' || parseFloat(this.state.amount) === 0) {
error = loc.send.details.amount_field_is_not_valid;
console.log('validation error');
@ -351,6 +349,20 @@ export default class SendDetails extends Component {
return;
}
if (this.state.fromWallet.type === HDSegwitBech32Wallet.type) {
try {
await this.createHDBech32Transaction();
} catch (Err) {
this.setState({ isLoading: false }, () => {
alert(Err.message);
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
});
}
return;
}
// legacy send below
this.setState({ isLoading: true }, async () => {
let utxo;
let actualSatoshiPerByte;
@ -436,6 +448,40 @@ export default class SendDetails extends Component {
});
}
async createHDBech32Transaction() {
/** @type {HDSegwitBech32Wallet} */
const wallet = this.state.fromWallet;
await wallet.fetchUtxo();
const changeAddress = await wallet.getChangeAddressAsync();
let satoshis = new BigNumber(this.state.amount).multipliedBy(100000000).toNumber();
const requestedSatPerByte = +this.state.fee.toString().replace(/\D/g, '');
console.log({ satoshis, requestedSatPerByte, utxo: wallet.getUtxo() });
let targets = [];
targets.push({ address: this.state.address, value: satoshis });
let { tx, fee } = wallet.createTransaction(wallet.getUtxo(), targets, requestedSatPerByte, changeAddress);
BlueApp.tx_metadata = BlueApp.tx_metadata || {};
BlueApp.tx_metadata[tx.getId()] = {
txhex: tx.toHex(),
memo: this.state.memo,
};
await BlueApp.saveToDisk();
this.setState({ isLoading: false }, () =>
this.props.navigation.navigate('Confirm', {
amount: this.state.amount,
fee: new BigNumber(fee).dividedBy(100000000).toNumber(),
address: this.state.address,
memo: this.state.memo,
fromWallet: wallet,
tx: tx.toHex(),
satoshiPerByte: requestedSatPerByte,
}),
);
}
onWalletSelect = wallet => {
this.setState({ fromAddress: wallet.getAddress(), fromSecret: wallet.getSecret(), fromWallet: wallet }, () => {
this.props.navigation.pop();

14
screen/wallets/add.js

@ -20,7 +20,7 @@ import { RadioGroup, RadioButton } from 'react-native-flexi-radio-button';
import PropTypes from 'prop-types';
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
import { AppStorage, SegwitP2SHWallet } from '../../class';
import { AppStorage, HDSegwitBech32Wallet, SegwitP2SHWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
let EV = require('../../events');
let A = require('../../analytics');
@ -166,7 +166,7 @@ export default class WalletsAdd extends Component {
return (
<View
style={{
height: 100,
height: 140,
}}
>
<BlueSpacing20 />
@ -178,6 +178,9 @@ export default class WalletsAdd extends Component {
<RadioButton value={SegwitP2SHWallet.type}>
<BlueText>{SegwitP2SHWallet.typeReadable} - Single address</BlueText>
</RadioButton>
<RadioButton value={HDSegwitBech32Wallet.type}>
<BlueText>{HDSegwitBech32Wallet.typeReadable} - Multiple addresses</BlueText>
</RadioButton>
</RadioGroup>
</View>
);
@ -285,6 +288,11 @@ export default class WalletsAdd extends Component {
} else {
this.createLightningWallet();
}
} else if (this.state.selectedIndex === 2) {
// btc was selected
// index 2 radio - hd bip84
w = new HDSegwitBech32Wallet();
w.setLabel(this.state.label || loc.wallets.details.title);
} else if (this.state.selectedIndex === 1) {
// btc was selected
// index 1 radio - segwit single address
@ -302,7 +310,7 @@ export default class WalletsAdd extends Component {
EV(EV.enum.WALLETS_COUNT_CHANGED);
A(A.ENUM.CREATED_WALLET);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
if (w.type === HDSegwitP2SHWallet.type) {
if (w.type === HDSegwitP2SHWallet.type || w.type === HDSegwitBech32Wallet.type) {
this.props.navigation.navigate('PleaseBackup', {
secret: w.getSecret(),
});

22
screen/wallets/import.js

@ -6,6 +6,7 @@ import {
HDLegacyBreadwalletWallet,
HDSegwitP2SHWallet,
HDLegacyP2PKHWallet,
HDSegwitBech32Wallet,
} from '../../class';
import React, { Component } from 'react';
import { KeyboardAvoidingView, Dimensions, View, TouchableWithoutFeedback, Keyboard } from 'react-native';
@ -117,6 +118,16 @@ export default class WalletsImport extends Component {
// if we're here - nope, its not a valid WIF
let hd4 = new HDSegwitBech32Wallet();
hd4.setSecret(text);
if (hd4.validateMnemonic()) {
await hd4.fetchBalance();
if (hd4.getBalance() > 0) {
await hd4.fetchTransactions();
return this._saveWallet(hd4);
}
}
let hd1 = new HDLegacyBreadwalletWallet();
hd1.setSecret(text);
if (hd1.validateMnemonic()) {
@ -167,10 +178,16 @@ export default class WalletsImport extends Component {
return this._saveWallet(hd3);
}
}
if (hd4.validateMnemonic()) {
await hd4.fetchTransactions();
if (hd4.getTransactions().length !== 0) {
return this._saveWallet(hd4);
}
}
// is it even valid? if yes we will import as:
if (hd2.validateMnemonic()) {
return this._saveWallet(hd2);
if (hd4.validateMnemonic()) {
return this._saveWallet(hd4);
}
// not valid? maybe its a watch-only address?
@ -193,6 +210,7 @@ export default class WalletsImport extends Component {
alert(loc.wallets.import.error);
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
// Plan:
// 0. check if its HDSegwitBech32Wallet (BIP84)
// 1. check if its HDSegwitP2SHWallet (BIP49)
// 2. check if its HDLegacyP2PKHWallet (BIP44)
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")

31
screen/wallets/scanQrWif.js

@ -3,7 +3,7 @@ import React from 'react';
import { ActivityIndicator, Image, View, TouchableOpacity } from 'react-native';
import { BlueText, SafeBlueArea, BlueButton } from '../../BlueComponents';
import { RNCamera } from 'react-native-camera';
import { SegwitP2SHWallet, LegacyWallet, WatchOnlyWallet, HDLegacyP2PKHWallet } from '../../class';
import { SegwitP2SHWallet, LegacyWallet, WatchOnlyWallet, HDLegacyP2PKHWallet, HDSegwitBech32Wallet } from '../../class';
import PropTypes from 'prop-types';
import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet';
import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet';
@ -71,8 +71,35 @@ export default class ScanQrWif extends React.Component {
}
}
// is it HD BIP84 mnemonic?
let hd = new HDSegwitBech32Wallet();
hd.setSecret(ret.data);
if (hd.validateMnemonic()) {
for (let w of BlueApp.wallets) {
if (w.getSecret() === hd.getSecret()) {
// lookig for duplicates
this.setState({ isLoading: false });
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
this.setState({ isLoading: true });
hd.setLabel(loc.wallets.import.imported + ' ' + hd.typeReadable);
await hd.fetchBalance();
if (hd.getBalance() !== 0) {
await hd.fetchTransactions();
BlueApp.wallets.push(hd);
await BlueApp.saveToDisk();
alert(loc.wallets.import.success);
this.props.navigation.popToTop();
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
this.setState({ isLoading: false });
return;
}
}
// nope
// is it HD legacy (BIP44) mnemonic?
let hd = new HDLegacyP2PKHWallet();
hd = new HDLegacyP2PKHWallet();
hd.setSecret(ret.data);
if (hd.validateMnemonic()) {
for (let w of BlueApp.wallets) {

Loading…
Cancel
Save