diff --git a/lib/server.js b/lib/server.js index e6343af..c774e7f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1683,14 +1683,30 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) async.series([ function(next) { - // feePerKb is required unless inputs & fee are specified - if (!_.isNumber(opts.feePerKb) && !(opts.inputs && _.isNumber(opts.fee))) - return next(new ClientError('Required argument missing')); + var feeArgs = !!opts.feeLevel + _.isNumber(opts.feePerKb) + _.isNumber(opts.fee); + if (feeArgs > 1) + return next(new ClientError('Only one of feeLevel/feePerKb/fee can be specified')); + + if (feeArgs == 0) { + log.debug('No fee provided, using "normal" fee level'); + opts.feeLevel = 'normal'; + } + + if (opts.feeLevel) { + if (!_.any(Defaults.FEE_LEVELS, { + name: opts.feeLevel + })) + return next(new ClientError('Invalid fee level. Valid values are ' + _.pluck(Defaults.FEE_LEVELS, 'name').join(', '))); + } if (_.isNumber(opts.feePerKb)) { if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB) return next(new ClientError('Invalid fee per KB')); } + + if (_.isNumber(opts.fee) && !opts.inputs) + return next(new ClientError('fee can only be set when inputs are specified')); + next(); }, function(next) { @@ -1705,7 +1721,7 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) if (_.isNumber(opts.outputs[0].amount)) return next(new ClientError('Amount is not allowed when sendMax is specified')); if (_.isNumber(opts.fee)) - return next(new ClientError('Fee is not allowed when sendMax is specified (use feePerKb instead)')); + return next(new ClientError('Fee is not allowed when sendMax is specified (use feeLevel/feePerKb instead)')); self.getSendMaxInfo({ feePerKb: opts.feePerKb || Defaults.DEFAULT_FEE_PER_KB, @@ -1738,7 +1754,8 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) * @param {number} opts.outputs[].amount - Amount to transfer in satoshi. * @param {string} opts.outputs[].message - A message to attach to this output. * @param {string} opts.message - A message to attach to this transaction. - * @param {number} opts.feePerKb - Use an alternative fee per KB for this TX. + * @param {number} opts.feeLevel[='normal'] - Optional. Specify the fee level for this TX ('priority', 'normal', 'economy', 'superEconomy') as defined in Defaults.FEE_LEVELS. + * @param {number} opts.feePerKb - Optional. Specify the fee per KB for this TX (in satoshi). * @param {string} opts.changeAddress - Optional. Use this address as the change address for the tx. The address should belong to the wallet. In the case of singleAddress wallets, the first main address will be used. * @param {Boolean} opts.sendMax - Optional. Send maximum amount of funds that make sense under the specified fee/feePerKb conditions. (defaults to false). * @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX @@ -1774,13 +1791,33 @@ WalletService.prototype.createTx = function(opts, cb) { } }; + + function getFeePerKb(wallet, cb) { + if (opts.inputs && _.isNumber(opts.fee)) return null; + if (_.isNumber(opts.feePerKb)) return opts.feePerKb; + self.getFeeLevels({ + network: wallet.network + }, function(err, levels) { + if (err) return cb(err); + var level = _.find(levels, { + level: opts.feeLevel + }); + if (!level) { + var msg = 'Could not compute fee for "' + opts.feeLevel + '" level'; + log.error(msg); + return cb(new ClientError(msg)); + } + return cb(null, level.feePerKb); + }); + }; + function checkTxpAlreadyExists(txProposalId, cb) { if (!txProposalId) return cb(); self.storage.fetchTx(self.walletId, txProposalId, cb); }; self._runLocked(cb, function(cb) { - var txp, changeAddress; + var txp, changeAddress, feePerKb; self.getWallet({}, function(err, wallet) { if (err) return cb(err); if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE); @@ -1809,6 +1846,12 @@ WalletService.prototype.createTx = function(opts, cb) { next(); }); }, + function(next) { + getFeePerKb(wallet, function(err, fee) { + feePerKb = fee; + next(); + }); + }, function(next) { var txOpts = { id: opts.txProposalId, @@ -1817,7 +1860,7 @@ WalletService.prototype.createTx = function(opts, cb) { outputs: opts.outputs, message: opts.message, changeAddress: changeAddress, - feePerKb: opts.feePerKb, + feePerKb: feePerKb, payProUrl: opts.payProUrl, walletM: wallet.m, walletN: wallet.n, @@ -1853,7 +1896,6 @@ WalletService.prototype.createTx = function(opts, cb) { }); }); }; - WalletService.prototype._verifyRequestPubKey = function(requestPubKey, signature, xPubKey) { var pub = (new Bitcore.HDPublicKey(xPubKey)).derive(Constants.PATHS.REQUEST_KEY_AUTH).publicKey; return Utils.verifyMessage(requestPubKey, signature, pub.toString());