mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-03 03:59:10 +01:00
ADD: HD bech32 wallets (BIP84)
This commit is contained in:
parent
2a7c0975a9
commit
3afeefaed9
8 changed files with 141 additions and 12 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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…
Add table
Reference in a new issue