2018-07-08 20:01:52 +01:00
|
|
|
import { LegacyWallet } from './legacy-wallet';
|
2019-05-09 09:35:57 +01:00
|
|
|
import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet';
|
|
|
|
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
|
|
|
|
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
2018-07-22 15:49:59 +01:00
|
|
|
const bitcoin = require('bitcoinjs-lib');
|
2020-07-29 15:52:31 +01:00
|
|
|
const HDNode = require('bip32');
|
2018-07-08 20:01:52 +01:00
|
|
|
|
|
|
|
export class WatchOnlyWallet extends LegacyWallet {
|
2018-12-28 16:52:06 +01:00
|
|
|
static type = 'watchOnly';
|
|
|
|
static typeReadable = 'Watch-only';
|
2018-07-08 20:01:52 +01:00
|
|
|
|
2019-09-27 15:49:56 +01:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.use_with_hardware_wallet = false;
|
2019-12-31 21:31:04 -06:00
|
|
|
this.masterFingerprint = false;
|
2019-09-27 15:49:56 +01:00
|
|
|
}
|
|
|
|
|
2018-07-08 20:01:52 +01:00
|
|
|
allowSend() {
|
2020-02-26 14:39:19 +00:00
|
|
|
return (
|
|
|
|
this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSend()
|
|
|
|
);
|
2019-09-27 15:49:56 +01:00
|
|
|
}
|
|
|
|
|
2021-03-23 15:16:32 +03:00
|
|
|
allowSignVerifyMessage() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-08 20:01:52 +01:00
|
|
|
getAddress() {
|
2020-04-17 15:41:35 +01:00
|
|
|
if (this.isAddressValid(this.secret)) return this.secret; // handling case when there is an XPUB there
|
|
|
|
if (this._hdWalletInstance) throw new Error('Should not be used in watch-only HD wallets');
|
|
|
|
throw new Error('Not initialized');
|
2018-07-08 20:01:52 +01:00
|
|
|
}
|
|
|
|
|
2018-07-22 15:49:59 +01:00
|
|
|
valid() {
|
2020-07-29 15:52:31 +01:00
|
|
|
if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) return this.isXpubValid();
|
2019-05-09 09:35:57 +01:00
|
|
|
|
2018-07-22 15:49:59 +01:00
|
|
|
try {
|
|
|
|
bitcoin.address.toOutputScript(this.getAddress());
|
|
|
|
return true;
|
2020-02-24 21:45:14 +00:00
|
|
|
} catch (_) {
|
|
|
|
return false;
|
2018-07-22 15:49:59 +01:00
|
|
|
}
|
|
|
|
}
|
2019-05-09 09:35:57 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* this method creates appropriate HD wallet class, depending on whether we have xpub, ypub or zpub
|
|
|
|
* as a property of `this`, and in case such property exists - it recreates it and copies data from old one.
|
|
|
|
* this is needed after serialization/save/load/deserialization procedure.
|
2021-03-20 13:00:01 +00:00
|
|
|
*
|
|
|
|
* @return {WatchOnlyWallet} this
|
2019-05-09 09:35:57 +01:00
|
|
|
*/
|
|
|
|
init() {
|
|
|
|
let hdWalletInstance;
|
|
|
|
if (this.secret.startsWith('xpub')) hdWalletInstance = new HDLegacyP2PKHWallet();
|
2019-05-15 00:19:35 +01:00
|
|
|
else if (this.secret.startsWith('ypub')) hdWalletInstance = new HDSegwitP2SHWallet();
|
|
|
|
else if (this.secret.startsWith('zpub')) hdWalletInstance = new HDSegwitBech32Wallet();
|
2021-03-20 13:00:01 +00:00
|
|
|
else return this;
|
2019-05-09 09:35:57 +01:00
|
|
|
hdWalletInstance._xpub = this.secret;
|
|
|
|
if (this._hdWalletInstance) {
|
|
|
|
// now, porting all properties from old object to new one
|
2020-06-01 15:54:23 +03:00
|
|
|
for (const k of Object.keys(this._hdWalletInstance)) {
|
2019-05-09 09:35:57 +01:00
|
|
|
hdWalletInstance[k] = this._hdWalletInstance[k];
|
|
|
|
}
|
2019-09-27 15:49:56 +01:00
|
|
|
|
|
|
|
// deleting properties that cant survive serialization/deserialization:
|
|
|
|
delete hdWalletInstance._node1;
|
|
|
|
delete hdWalletInstance._node0;
|
2019-05-09 09:35:57 +01:00
|
|
|
}
|
|
|
|
this._hdWalletInstance = hdWalletInstance;
|
2021-03-20 13:00:01 +00:00
|
|
|
|
|
|
|
return this;
|
2019-05-09 09:35:57 +01:00
|
|
|
}
|
|
|
|
|
2020-07-02 14:43:35 +01:00
|
|
|
prepareForSerialization() {
|
|
|
|
if (this._hdWalletInstance) {
|
|
|
|
delete this._hdWalletInstance._node0;
|
|
|
|
delete this._hdWalletInstance._node1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-09 09:35:57 +01:00
|
|
|
getBalance() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getBalance();
|
|
|
|
return super.getBalance();
|
|
|
|
}
|
|
|
|
|
|
|
|
getTransactions() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getTransactions();
|
|
|
|
return super.getTransactions();
|
|
|
|
}
|
|
|
|
|
|
|
|
async fetchBalance() {
|
|
|
|
if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) {
|
|
|
|
if (!this._hdWalletInstance) this.init();
|
|
|
|
return this._hdWalletInstance.fetchBalance();
|
|
|
|
} else {
|
|
|
|
// return LegacyWallet.prototype.fetchBalance.call(this);
|
|
|
|
return super.fetchBalance();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fetchTransactions() {
|
|
|
|
if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) {
|
|
|
|
if (!this._hdWalletInstance) this.init();
|
|
|
|
return this._hdWalletInstance.fetchTransactions();
|
|
|
|
} else {
|
|
|
|
// return LegacyWallet.prototype.fetchBalance.call(this);
|
|
|
|
return super.fetchTransactions();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async getAddressAsync() {
|
2019-10-16 17:49:23 +01:00
|
|
|
if (this.isAddressValid(this.secret)) return new Promise(resolve => resolve(this.secret));
|
2019-05-09 09:35:57 +01:00
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getAddressAsync();
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
2019-09-27 15:49:56 +01:00
|
|
|
|
2020-09-19 23:53:08 +01:00
|
|
|
_getExternalAddressByIndex(index) {
|
2019-09-27 15:49:56 +01:00
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index);
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
2020-10-05 18:53:47 +01:00
|
|
|
_getInternalAddressByIndex(index) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index);
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
2020-04-17 15:41:35 +01:00
|
|
|
getNextFreeAddressIndex() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index;
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
2020-10-05 18:53:47 +01:00
|
|
|
getNextFreeChangeAddressIndex() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_change_address_index;
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
2019-09-27 15:49:56 +01:00
|
|
|
async getChangeAddressAsync() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync();
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
|
|
|
async fetchUtxo() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo();
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
2020-10-23 13:49:17 +03:00
|
|
|
getUtxo(...args) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args);
|
2019-09-27 15:49:56 +01:00
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
|
|
|
combinePsbt(base64one, base64two) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(base64one, base64two);
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
|
|
|
broadcastTx(hex) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(hex);
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* signature of this method is the same ad BIP84 createTransaction, BUT this method should be used to create
|
|
|
|
* unsinged PSBT to be used with HW wallet (or other external signer)
|
|
|
|
* @see HDSegwitBech32Wallet.createTransaction
|
|
|
|
*/
|
|
|
|
createTransaction(utxos, targets, feeRate, changeAddress, sequence) {
|
|
|
|
if (this._hdWalletInstance instanceof HDSegwitBech32Wallet) {
|
2020-02-24 21:45:14 +00:00
|
|
|
return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true, this.getMasterFingerprint());
|
2019-09-27 15:49:56 +01:00
|
|
|
} else {
|
|
|
|
throw new Error('Not a zpub watch-only wallet, cant create PSBT (or just not initialized)');
|
|
|
|
}
|
|
|
|
}
|
2020-02-24 21:45:14 +00:00
|
|
|
|
|
|
|
getMasterFingerprint() {
|
|
|
|
return this.masterFingerprint;
|
|
|
|
}
|
|
|
|
|
|
|
|
getMasterFingerprintHex() {
|
2020-02-26 14:39:19 +00:00
|
|
|
if (!this.masterFingerprint) return '00000000';
|
2020-02-24 21:45:14 +00:00
|
|
|
let masterFingerprintHex = Number(this.masterFingerprint).toString(16);
|
|
|
|
if (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte
|
|
|
|
// poor man's little-endian conversion:
|
|
|
|
// ¯\_(ツ)_/¯
|
|
|
|
return (
|
|
|
|
masterFingerprintHex[6] +
|
|
|
|
masterFingerprintHex[7] +
|
|
|
|
masterFingerprintHex[4] +
|
|
|
|
masterFingerprintHex[5] +
|
|
|
|
masterFingerprintHex[2] +
|
|
|
|
masterFingerprintHex[3] +
|
|
|
|
masterFingerprintHex[0] +
|
|
|
|
masterFingerprintHex[1]
|
|
|
|
);
|
|
|
|
}
|
2020-02-26 14:39:19 +00:00
|
|
|
|
|
|
|
isHd() {
|
|
|
|
return this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub');
|
|
|
|
}
|
|
|
|
|
2020-04-17 15:41:35 +01:00
|
|
|
weOwnAddress(address) {
|
|
|
|
if (this.isHd()) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.weOwnAddress(address);
|
|
|
|
throw new Error('Not initialized');
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.getAddress() === address;
|
|
|
|
}
|
|
|
|
|
2020-04-13 17:45:42 +01:00
|
|
|
allowHodlHodlTrading() {
|
|
|
|
return this.isHd();
|
|
|
|
}
|
|
|
|
|
2021-03-23 18:58:13 +03:00
|
|
|
allowMasterFingerprint() {
|
|
|
|
return this.getSecret().startsWith('zpub');
|
|
|
|
}
|
|
|
|
|
2020-02-26 14:39:19 +00:00
|
|
|
useWithHardwareWalletEnabled() {
|
|
|
|
return !!this.use_with_hardware_wallet;
|
|
|
|
}
|
|
|
|
|
|
|
|
setUseWithHardwareWalletEnabled(enabled) {
|
|
|
|
this.use_with_hardware_wallet = !!enabled;
|
|
|
|
}
|
2020-07-29 15:52:31 +01:00
|
|
|
|
2020-07-29 21:00:00 +01:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
getAllExternalAddresses() {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getAllExternalAddresses();
|
|
|
|
return super.getAllExternalAddresses();
|
|
|
|
}
|
|
|
|
|
2020-07-29 15:52:31 +01:00
|
|
|
isXpubValid() {
|
|
|
|
let xpub;
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (this.secret.startsWith('zpub')) {
|
|
|
|
xpub = this.constructor._zpubToXpub(this.secret);
|
|
|
|
} else if (this.secret.startsWith('ypub')) {
|
|
|
|
xpub = this.constructor._ypubToXpub(this.secret);
|
|
|
|
} else {
|
|
|
|
xpub = this.secret;
|
|
|
|
}
|
|
|
|
|
|
|
|
const hdNode = HDNode.fromBase58(xpub);
|
|
|
|
hdNode.derive(0);
|
|
|
|
return true;
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-29 21:42:40 +03:00
|
|
|
|
|
|
|
addressIsChange(...args) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.addressIsChange(...args);
|
2020-10-30 10:44:44 +03:00
|
|
|
return super.addressIsChange(...args);
|
2020-10-29 21:42:40 +03:00
|
|
|
}
|
2020-11-04 19:24:39 +03:00
|
|
|
|
|
|
|
getUTXOMetadata(...args) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.getUTXOMetadata(...args);
|
|
|
|
return super.getUTXOMetadata(...args);
|
|
|
|
}
|
|
|
|
|
|
|
|
setUTXOMetadata(...args) {
|
|
|
|
if (this._hdWalletInstance) return this._hdWalletInstance.setUTXOMetadata(...args);
|
|
|
|
return super.setUTXOMetadata(...args);
|
|
|
|
}
|
2018-07-08 20:01:52 +01:00
|
|
|
}
|