@ducatus/ducatus-wallet-service-rev
Version:
A service for Mutisig HD Bitcoin Wallets
361 lines • 15 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var ducatus_crypto_wallet_core_rev_1 = require("@ducatus/ducatus-crypto-wallet-core-rev");
var lodash_1 = __importDefault(require("lodash"));
var clienterror_1 = require("../../errors/clienterror");
var model_1 = require("../../model");
var $ = require('preconditions').singleton();
var Common = require('../../common');
var Constants = Common.Constants;
var Utils = Common.Utils;
var Defaults = Common.Defaults;
var Errors = require('../../errors/errordefinitions');
var BtcChain = (function () {
function BtcChain(bitcoreLib) {
if (bitcoreLib === void 0) { bitcoreLib = ducatus_crypto_wallet_core_rev_1.BitcoreLib; }
this.bitcoreLib = bitcoreLib;
}
BtcChain.prototype.getWalletBalance = function (server, wallet, opts, cb) {
server._getUtxosForCurrentWallet({
coin: opts.coin,
addresses: opts.addresses
}, function (err, utxos) {
if (err)
return cb(err);
var balance = __assign(__assign({}, server._totalizeUtxos(utxos)), { byAddress: [] });
var byAddress = {};
lodash_1.default.each(lodash_1.default.keyBy(lodash_1.default.sortBy(utxos, 'address'), 'address'), function (value, key) {
byAddress[key] = {
address: key,
path: value.path,
amount: 0
};
});
lodash_1.default.each(utxos, function (utxo) {
byAddress[utxo.address].amount += utxo.satoshis;
});
balance.byAddress = lodash_1.default.values(byAddress);
return cb(null, balance);
});
};
BtcChain.prototype.getWalletSendMaxInfo = function (server, wallet, opts, cb) {
server._getUtxosForCurrentWallet({}, function (err, utxos) {
if (err)
return cb(err);
var info = {
size: 0,
amount: 0,
fee: 0,
feePerKb: 0,
inputs: [],
utxosBelowFee: 0,
amountBelowFee: 0,
utxosAboveMaxSize: 0,
amountAboveMaxSize: 0
};
var inputs = lodash_1.default.reject(utxos, 'locked');
if (!!opts.excludeUnconfirmedUtxos) {
inputs = lodash_1.default.filter(inputs, 'confirmations');
}
inputs = lodash_1.default.sortBy(inputs, function (input) {
return -input.satoshis;
});
if (lodash_1.default.isEmpty(inputs))
return cb(null, info);
server._getFeePerKb(wallet, opts, function (err, feePerKb) {
if (err)
return cb(err);
info.feePerKb = feePerKb;
var txp = model_1.TxProposal.create({
walletId: server.walletId,
coin: wallet.coin,
network: wallet.network,
walletM: wallet.m,
walletN: wallet.n,
feePerKb: feePerKb
});
var baseTxpSize = txp.getEstimatedSize();
var sizePerInput = txp.getEstimatedSizeForSingleInput();
var feePerInput = (sizePerInput * txp.feePerKb) / 1000;
var partitionedByAmount = lodash_1.default.partition(inputs, function (input) {
return input.satoshis > feePerInput;
});
info.utxosBelowFee = partitionedByAmount[1].length;
info.amountBelowFee = lodash_1.default.sumBy(partitionedByAmount[1], 'satoshis');
inputs = partitionedByAmount[0];
lodash_1.default.each(inputs, function (input, i) {
var sizeInKb = (baseTxpSize + (i + 1) * sizePerInput) / 1000;
if (sizeInKb > Defaults.MAX_TX_SIZE_IN_KB[wallet.coin]) {
info.utxosAboveMaxSize = inputs.length - i;
info.amountAboveMaxSize = lodash_1.default.sumBy(lodash_1.default.slice(inputs, i), 'satoshis');
return false;
}
txp.inputs.push(input);
});
if (lodash_1.default.isEmpty(txp.inputs))
return cb(null, info);
var fee = txp.getEstimatedFee();
var amount = lodash_1.default.sumBy(txp.inputs, 'satoshis') - fee;
if (amount < Defaults.MIN_OUTPUT_AMOUNT)
return cb(null, info);
info.size = txp.getEstimatedSize();
info.fee = fee;
info.amount = amount;
if (opts.returnInputs) {
info.inputs = lodash_1.default.shuffle(txp.inputs);
}
return cb(null, info);
});
});
};
BtcChain.prototype.getDustAmountValue = function () {
return this.bitcoreLib.Transaction.DUST_AMOUNT;
};
BtcChain.prototype.getTransactionCount = function () {
return null;
};
BtcChain.prototype.getChangeAddress = function (server, wallet, opts) {
var _this = this;
return new Promise(function (resolve, reject) {
var getChangeAddress = function (wallet, cb) {
if (wallet.singleAddress) {
server.storage.fetchAddresses(server.walletId, function (err, addresses) {
if (err)
return cb(err);
if (lodash_1.default.isEmpty(addresses))
return cb(new clienterror_1.ClientError('The wallet has no addresses'));
return cb(null, lodash_1.default.head(addresses));
});
}
else {
if (opts.changeAddress) {
try {
_this.validateAddress(wallet, opts.changeAddress, opts);
}
catch (addrErr) {
return cb(addrErr);
}
server.storage.fetchAddressByWalletId(wallet.id, opts.changeAddress, function (err, address) {
if (err || !address)
return cb(Errors.INVALID_CHANGE_ADDRESS);
return cb(null, address);
});
}
else {
return cb(null, wallet.createAddress(true), true);
}
}
};
getChangeAddress(wallet, function (err, address, isNew) {
if (err)
return reject(err);
return resolve(address);
});
});
};
BtcChain.prototype.checkDust = function (output) {
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, this.bitcoreLib.Transaction.DUST_AMOUNT);
if (output.amount < dustThreshold) {
return Errors.DUST_AMOUNT;
}
};
BtcChain.prototype.getFee = function (server, wallet, opts) {
return new Promise(function (resolve) {
server._getFeePerKb(wallet, opts, function (err, feePerKb) {
return resolve({ feePerKb: feePerKb });
});
});
};
BtcChain.prototype.buildTx = function (txp) {
var _this = this;
var t = new this.bitcoreLib.Transaction();
if (txp.version <= 3) {
t.setVersion(1);
}
else {
t.setVersion(2);
}
switch (txp.addressType) {
case Constants.SCRIPT_TYPES.P2WSH:
case Constants.SCRIPT_TYPES.P2SH:
lodash_1.default.each(txp.inputs, function (i) {
$.checkState(i.publicKeys, 'Inputs should include public keys');
t.from(i, i.publicKeys, txp.requiredSignatures);
});
break;
case Constants.SCRIPT_TYPES.P2WPKH:
case Constants.SCRIPT_TYPES.P2PKH:
t.from(txp.inputs);
break;
}
lodash_1.default.each(txp.outputs, function (o) {
$.checkState(o.script || o.toAddress, 'Output should have either toAddress or script specified');
if (o.script) {
t.addOutput(new _this.bitcoreLib.Transaction.Output({
script: o.script,
satoshis: o.amount
}));
}
else {
t.to(o.toAddress, o.amount);
}
});
t.fee(txp.fee);
if (txp.changeAddress) {
t.change(txp.changeAddress.address);
}
if (t.outputs.length > 1) {
var outputOrder_1 = lodash_1.default.reject(txp.outputOrder, function (order) {
return order >= t.outputs.length;
});
$.checkState(t.outputs.length == outputOrder_1.length);
t.sortOutputs(function (outputs) {
return lodash_1.default.map(outputOrder_1, function (i) {
return outputs[i];
});
});
}
var totalInputs = lodash_1.default.sumBy(t.inputs, 'output.satoshis');
var totalOutputs = lodash_1.default.sumBy(t.outputs, 'satoshis');
$.checkState(totalInputs > 0 && totalOutputs > 0 && totalInputs >= totalOutputs, 'not-enought-inputs');
$.checkState(totalInputs - totalOutputs <= Defaults.MAX_TX_FEE[txp.coin], 'fee-too-high');
return t;
};
BtcChain.prototype.convertFeePerKb = function (p, feePerKb) {
return [p, Utils.strip(feePerKb * 1e8)];
};
BtcChain.prototype.checkTx = function (server, txp) {
var bitcoreError;
var serializationOpts = {
disableIsFullySigned: true,
disableSmallFees: true,
disableLargeFees: true
};
if (lodash_1.default.isEmpty(txp.inputPaths))
return Errors.NO_INPUT_PATHS;
try {
var bitcoreTx = txp.getBitcoreTx();
bitcoreError = bitcoreTx.getSerializationError(serializationOpts);
if (!bitcoreError) {
txp.fee = bitcoreTx.getFee();
}
}
catch (ex) {
server.logw('Error building Bitcore transaction', ex);
return ex;
}
if (bitcoreError instanceof this.bitcoreLib.errors.Transaction.FeeError)
return Errors.INSUFFICIENT_FUNDS_FOR_FEE;
if (bitcoreError instanceof this.bitcoreLib.errors.Transaction.DustOutputs)
return Errors.DUST_AMOUNT;
return bitcoreError;
};
BtcChain.prototype.checkTxUTXOs = function (server, txp, opts, cb) {
server.logd('Rechecking UTXOs availability for publishTx');
var utxoKey = function (utxo) {
return utxo.txid + '|' + utxo.vout;
};
server._getUtxosForCurrentWallet({
addresses: txp.inputs
}, function (err, utxos) {
if (err)
return cb(err);
var txpInputs = lodash_1.default.map(txp.inputs, utxoKey);
var utxosIndex = lodash_1.default.keyBy(utxos, utxoKey);
var unavailable = lodash_1.default.some(txpInputs, function (i) {
var utxo = utxosIndex[i];
return !utxo || utxo.locked;
});
if (unavailable)
return cb(Errors.UNAVAILABLE_UTXOS);
return cb();
});
};
BtcChain.prototype.selectTxInputs = function (server, txp, wallet, opts, cb, next) {
return server._selectTxInputs(txp, opts.utxosToExclude, next);
};
BtcChain.prototype.checkUtxos = function (opts) {
if (lodash_1.default.isNumber(opts.fee) && lodash_1.default.isEmpty(opts.inputs))
return true;
};
BtcChain.prototype.checkValidTxAmount = function (output) {
if (!lodash_1.default.isNumber(output.amount) || lodash_1.default.isNaN(output.amount) || output.amount <= 0) {
return false;
}
return true;
};
BtcChain.prototype.setInputs = function (info) {
return info.inputs;
};
BtcChain.prototype.supportsMultisig = function () {
return true;
};
BtcChain.prototype.notifyConfirmations = function (network) {
if (network != 'livenet')
return false;
return true;
};
BtcChain.prototype.isUTXOCoin = function () {
return true;
};
BtcChain.prototype.isSingleAddress = function () {
return false;
};
BtcChain.prototype.addressFromStorageTransform = function (network, address) { };
BtcChain.prototype.addressToStorageTransform = function (network, address) { };
BtcChain.prototype.addSignaturesToBitcoreTx = function (tx, inputs, inputPaths, signatures, xpub) {
var _this = this;
if (signatures.length != inputs.length)
throw new Error('Number of signatures does not match number of inputs');
var i = 0;
var x = new this.bitcoreLib.HDPublicKey(xpub);
lodash_1.default.each(signatures, function (signatureHex) {
try {
var signature = _this.bitcoreLib.crypto.Signature.fromString(signatureHex);
var pub = x.deriveChild(inputPaths[i]).publicKey;
var s = {
inputIndex: i,
signature: signature,
sigtype: _this.bitcoreLib.crypto.Signature.SIGHASH_ALL | _this.bitcoreLib.crypto.Signature.SIGHASH_FORKID,
publicKey: pub
};
tx.inputs[i].addSignature(tx, s);
i++;
}
catch (e) { }
});
if (i != tx.inputs.length)
throw new Error('Wrong signatures');
};
BtcChain.prototype.validateAddress = function (wallet, inaddr, opts) {
var A = this.bitcoreLib.Address;
var addr = {};
try {
addr = new A(inaddr);
}
catch (ex) {
throw Errors.INVALID_ADDRESS;
}
if (addr.network.toString() != wallet.network) {
throw Errors.INCORRECT_ADDRESS_NETWORK;
}
return;
};
return BtcChain;
}());
exports.BtcChain = BtcChain;
//# sourceMappingURL=index.js.map