BlueWallet/class/wallets/abstract-wallet.js

260 lines
6.5 KiB
JavaScript
Raw Normal View History

import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import b58 from 'bs58check';
2019-10-03 05:06:47 +02:00
const createHash = require('create-hash');
2020-02-27 15:24:26 +01:00
2018-03-20 21:41:07 +01:00
export class AbstractWallet {
static type = 'abstract';
static typeReadable = 'abstract';
static fromJson(obj) {
const obj2 = JSON.parse(obj);
const temp = new this();
for (const key2 of Object.keys(obj2)) {
temp[key2] = obj2[key2];
}
return temp;
}
2018-03-20 21:41:07 +01:00
constructor() {
2018-12-28 18:02:39 +01:00
this.type = this.constructor.type;
this.typeReadable = this.constructor.typeReadable;
2018-03-20 21:41:07 +01:00
this.label = '';
this.secret = ''; // private key or recovery phrase
this.balance = 0;
this.unconfirmed_balance = 0;
2018-03-20 21:41:07 +01:00
this.transactions = [];
this._address = false; // cache
this.utxo = [];
this._lastTxFetch = 0;
this._lastBalanceFetch = 0;
2018-12-14 05:31:13 +01:00
this.preferredBalanceUnit = BitcoinUnit.BTC;
this.chain = Chain.ONCHAIN;
2019-08-03 23:29:15 +02:00
this.hideBalance = false;
this.userHasSavedExport = false;
this._hideTransactionsInWalletsList = false;
2018-03-20 21:41:07 +01:00
}
2019-10-03 05:06:47 +02:00
getID() {
return createHash('sha256').update(this.getSecret()).digest().toString('hex');
2019-10-03 05:06:47 +02:00
}
getTransactions() {
return this.transactions;
2018-03-20 21:41:07 +01:00
}
getUserHasSavedExport() {
return this.userHasSavedExport;
}
setUserHasSavedExport(value) {
this.userHasSavedExport = value;
}
getHideTransactionsInWalletsList() {
return this._hideTransactionsInWalletsList;
}
setHideTransactionsInWalletsList(value) {
this._hideTransactionsInWalletsList = value;
}
2018-03-20 21:41:07 +01:00
/**
*
* @returns {string}
*/
getLabel() {
if (this.label.trim().length === 0) {
return 'Wallet';
}
2018-03-20 21:41:07 +01:00
return this.label;
}
getXpub() {
return this._address;
}
/**
*
* @returns {number} Available to spend amount, int, in sats
*/
2018-03-20 21:41:07 +01:00
getBalance() {
return this.balance + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0);
2018-03-20 21:41:07 +01:00
}
2018-12-14 05:31:13 +01:00
getPreferredBalanceUnit() {
for (const value of Object.values(BitcoinUnit)) {
2018-12-23 19:18:27 +01:00
if (value === this.preferredBalanceUnit) {
return this.preferredBalanceUnit;
}
}
return BitcoinUnit.BTC;
2018-12-14 05:31:13 +01:00
}
2018-07-07 13:30:50 +02:00
allowReceive() {
return true;
}
allowSend() {
2018-10-06 02:45:24 +02:00
return true;
2018-07-07 13:30:50 +02:00
}
2019-08-04 21:33:15 +02:00
allowSendMax(): boolean {
return false;
}
2018-10-31 21:14:28 +01:00
allowRBF() {
return false;
}
2019-06-08 01:06:21 +02:00
allowBatchSend() {
return false;
}
allowHodlHodlTrading() {
return false;
}
2019-09-12 03:59:11 +02:00
weOwnAddress(address) {
throw Error('not implemented');
2019-09-12 03:59:11 +02:00
}
/**
* Returns delta of unconfirmed balance. For example, if theres no
* unconfirmed balance its 0
*
* @return {number} Satoshis
*/
getUnconfirmedBalance() {
return this.unconfirmed_balance;
}
2018-03-20 21:41:07 +01:00
setLabel(newLabel) {
this.label = newLabel;
return this;
}
getSecret() {
return this.secret;
}
setSecret(newSecret) {
this.secret = newSecret.trim().replace('bitcoin:', '').replace('BITCOIN:', '');
if (this.secret.startsWith('BC1')) this.secret = this.secret.toLowerCase();
2020-02-24 22:45:14 +01:00
// [fingerprint/derivation]zpub
2020-07-05 10:22:08 +02:00
const re = /\[([^\]]+)\](.*)/;
const m = this.secret.match(re);
if (m && m.length === 3) {
let hexFingerprint = m[1].split('/')[0];
if (hexFingerprint.length === 8) {
hexFingerprint = Buffer.from(hexFingerprint, 'hex').reverse().toString('hex');
this.masterFingerprint = parseInt(hexFingerprint, 16);
}
this.secret = m[2];
}
2020-02-24 22:45:14 +01:00
try {
const parsedSecret = JSON.parse(this.secret);
if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) {
let masterFingerprint = false;
if (parsedSecret.keystore.ckcc_xfp) {
// It is a ColdCard Hardware Wallet
masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp);
}
this.secret = parsedSecret.keystore.xpub;
this.masterFingerprint = masterFingerprint;
}
// It is a Cobo Vault Hardware Wallet
if (parsedSecret && parsedSecret.ExtPubKey && parsedSecret.MasterFingerprint) {
this.secret = parsedSecret.ExtPubKey;
const mfp = Buffer.from(parsedSecret.MasterFingerprint, 'hex').reverse().toString('hex');
this.masterFingerprint = parseInt(mfp, 16);
this.setLabel('Cobo Vault ' + parsedSecret.MasterFingerprint);
}
2020-02-24 22:45:14 +01:00
} catch (_) {}
2018-03-20 21:41:07 +01:00
return this;
}
2018-06-25 00:22:46 +02:00
getLatestTransactionTime() {
return 0;
}
/**
* @deprecated
*/
createTx() {
throw Error('not implemented');
}
/**
*
* @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String}>} List of spendable utxos
* @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate)
* @param feeRate {Number} satoshi per byte
* @param changeAddress {String} Excessive coins will go back to that address
* @param sequence {Number} Used in RBF
* @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case
* @param masterFingerprint {number} Decimal number of wallet's master fingerprint
* @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}}
*/
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) {
throw Error('not implemented');
}
2019-10-16 23:38:23 +02:00
getAddress() {
throw Error('not implemented');
}
getAddressAsync() {
return new Promise(resolve => resolve(this.getAddress()));
}
2020-02-27 15:24:26 +01:00
useWithHardwareWalletEnabled() {
return false;
}
async wasEverUsed() {
throw new Error('Not implemented');
}
/**
* Returns _all_ external addresses in hierarchy (for HD wallets) or just address for single-address wallets
* _Not_ internal ones, as this method is supposed to be used for subscription of external notifications.
*
* @returns string[] Addresses
*/
getAllExternalAddresses() {
return [];
}
/*
* Converts zpub to xpub
*
* @param {String} zpub
* @returns {String} xpub
*/
static _zpubToXpub(zpub) {
let data = b58.decode(zpub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
/**
* Converts ypub to xpub
* @param {String} ypub - wallet ypub
* @returns {*}
*/
static _ypubToXpub(ypub) {
let data = b58.decode(ypub);
if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!');
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
2018-03-20 21:41:07 +01:00
}