UNPKG

@ducatus/ducatus-wallet-service-rev

Version:
361 lines 15 kB
"use strict"; 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