|
|
@ -1147,9 +1147,9 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) { |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
WalletService.prototype._canCreateTx = function(copayerId, cb) { |
|
|
|
WalletService.prototype._canCreateTx = function(cb) { |
|
|
|
var self = this; |
|
|
|
self.storage.fetchLastTxs(self.walletId, copayerId, 5 + Defaults.BACKOFF_OFFSET, function(err, txs) { |
|
|
|
self.storage.fetchLastTxs(self.walletId, self.copayerId, 5 + Defaults.BACKOFF_OFFSET, function(err, txs) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
if (!txs.length) |
|
|
@ -1277,7 +1277,7 @@ WalletService.prototype.createTx = function(opts, cb) { |
|
|
|
if (!signingKey) |
|
|
|
return cb(new ClientError('Invalid proposal signature')); |
|
|
|
|
|
|
|
self._canCreateTx(self.copayerId, function(err, canCreate) { |
|
|
|
self._canCreateTx(function(err, canCreate) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!canCreate) return cb(Errors.TX_CANNOT_CREATE); |
|
|
|
|
|
|
@ -1345,6 +1345,95 @@ WalletService.prototype.createTx = function(opts, cb) { |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Creates a new transaction proposal. |
|
|
|
* @param {Object} opts |
|
|
|
* @param {string} opts.type - Proposal type. |
|
|
|
* @param {Array} opts.outputs - List of outputs. |
|
|
|
* @param {string} opts.outputs[].toAddress - Destination address. |
|
|
|
* @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 {Array} opts.inputs - Optional. Inputs for this TX |
|
|
|
* @param {string} opts.feePerKb - Optional. Use an alternative fee per KB for this TX |
|
|
|
* @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX |
|
|
|
* @param {string} opts.excludeUnconfirmedUtxos - Optional. Do not use UTXOs of unconfirmed transactions as inputs (defaults to false) |
|
|
|
* @returns {TxProposal} Transaction proposal. |
|
|
|
*/ |
|
|
|
WalletService.prototype.createTx2 = function(opts, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
if (!Utils.checkRequired(opts, ['outputs'])) |
|
|
|
return cb(new ClientError('Required argument missing')); |
|
|
|
|
|
|
|
var type = opts.type || Model.TxProposal.Types.STANDARD; |
|
|
|
if (!Model.TxProposal.isTypeSupported(type)) |
|
|
|
return cb(new ClientError('Invalid proposal type')); |
|
|
|
|
|
|
|
var feePerKb = opts.feePerKb || Defaults.DEFAULT_FEE_PER_KB; |
|
|
|
if (feePerKb < Defaults.MIN_FEE_PER_KB || feePerKb > Defaults.MAX_FEE_PER_KB) |
|
|
|
return cb(new ClientError('Invalid fee per KB value')); |
|
|
|
|
|
|
|
self._runLocked(cb, function(cb) { |
|
|
|
self.getWallet({}, function(err, wallet) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE); |
|
|
|
|
|
|
|
self._canCreateTx(function(err, canCreate) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!canCreate) return cb(Errors.TX_CANNOT_CREATE); |
|
|
|
|
|
|
|
if (type != Model.TxProposal.Types.EXTERNAL) { |
|
|
|
var validationError = self._validateOutputs(opts, wallet); |
|
|
|
if (validationError) { |
|
|
|
return cb(validationError); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var txOpts = { |
|
|
|
version: 3, |
|
|
|
type: type, |
|
|
|
walletId: self.walletId, |
|
|
|
creatorId: self.copayerId, |
|
|
|
outputs: opts.outputs, |
|
|
|
inputs: opts.inputs, |
|
|
|
message: opts.message, |
|
|
|
changeAddress: wallet.createAddress(true), |
|
|
|
feePerKb: feePerKb, |
|
|
|
payProUrl: opts.payProUrl, |
|
|
|
requiredSignatures: wallet.m, |
|
|
|
requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), |
|
|
|
walletN: wallet.n, |
|
|
|
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos, |
|
|
|
addressType: wallet.addressType, |
|
|
|
customData: opts.customData, |
|
|
|
}; |
|
|
|
|
|
|
|
var txp = Model.TxProposal.create(txOpts); |
|
|
|
|
|
|
|
self._selectTxInputs(txp, opts.utxosToExclude, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self.storage.storeAddressAndWallet(wallet, txp.changeAddress, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self.storage.storeTx(wallet.id, txp, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('NewTxProposal', { |
|
|
|
amount: txp.getTotalAmount() |
|
|
|
}, function() { |
|
|
|
return cb(null, txp); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieves a tx from storage. |
|
|
|
* @param {Object} opts |
|
|
|