|
|
@ -62,7 +62,8 @@ function WalletService() { |
|
|
|
function checkRequired(obj, args, cb) { |
|
|
|
var missing = Utils.getMissingFields(obj, args); |
|
|
|
if (_.isEmpty(missing)) return true; |
|
|
|
cb(new ClientError('Required argument ' + _.first(missing) + ' missing.')); |
|
|
|
if (_.isFunction(cb)) |
|
|
|
cb(new ClientError('Required argument ' + _.first(missing) + ' missing.')); |
|
|
|
return false; |
|
|
|
}; |
|
|
|
|
|
|
@ -215,8 +216,9 @@ WalletService.prototype._runLocked = function(cb, task) { |
|
|
|
* @param {number} opts.m - Required copayers. |
|
|
|
* @param {number} opts.n - Total copayers. |
|
|
|
* @param {string} opts.pubKey - Public key to verify copayers joining have access to the wallet secret. |
|
|
|
* @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet. |
|
|
|
* @param {string} [opts.supportBIP44AndP2PKH = true] - Client supports BIP44 & P2PKH for new wallets. |
|
|
|
* @param {string} opts.singleAddress[=false] - The wallet will only ever have one address. |
|
|
|
* @param {string} opts.network[='livenet'] - The Bitcoin network for this wallet. |
|
|
|
* @param {string} opts.supportBIP44AndP2PKH[=true] - Client supports BIP44 & P2PKH for new wallets. |
|
|
|
*/ |
|
|
|
WalletService.prototype.createWallet = function(opts, cb) { |
|
|
|
var self = this, |
|
|
@ -263,6 +265,7 @@ WalletService.prototype.createWallet = function(opts, cb) { |
|
|
|
n: opts.n, |
|
|
|
network: opts.network, |
|
|
|
pubKey: pubKey.toString(), |
|
|
|
singleAddress: !!opts.singleAddress, |
|
|
|
derivationStrategy: derivationStrategy, |
|
|
|
addressType: addressType, |
|
|
|
}); |
|
|
@ -784,28 +787,41 @@ WalletService.prototype.createAddress = function(opts, cb) { |
|
|
|
|
|
|
|
opts = opts || {}; |
|
|
|
|
|
|
|
self._runLocked(cb, function(cb) { |
|
|
|
self.getWallet({}, function(err, wallet) { |
|
|
|
function createNewAddress(wallet, cb) { |
|
|
|
self._canCreateAddress(opts.ignoreMaxGap, function(err, canCreate) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE); |
|
|
|
if (!canCreate) return cb(Errors.MAIN_ADDRESS_GAP_REACHED); |
|
|
|
|
|
|
|
self._canCreateAddress(opts.ignoreMaxGap, function(err, canCreate) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!canCreate) return cb(Errors.MAIN_ADDRESS_GAP_REACHED); |
|
|
|
|
|
|
|
var address = wallet.createAddress(false); |
|
|
|
var address = wallet.createAddress(false); |
|
|
|
|
|
|
|
self.storage.storeAddressAndWallet(wallet, address, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
self.storage.storeAddressAndWallet(wallet, address, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('NewAddress', { |
|
|
|
address: address.address, |
|
|
|
}, function() { |
|
|
|
return cb(null, address); |
|
|
|
}); |
|
|
|
self._notify('NewAddress', { |
|
|
|
address: address.address, |
|
|
|
}, function() { |
|
|
|
return cb(null, address); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
function getFirstAddress(wallet, cb) { |
|
|
|
self.storage.fetchAddresses(self.walletId, function(err, addresses) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!_.isEmpty(addresses)) return cb(null, _.first(addresses)) |
|
|
|
return createNewAddress(wallet, cb); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
self._runLocked(cb, function(cb) { |
|
|
|
self.getWallet({}, function(err, wallet) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE); |
|
|
|
|
|
|
|
var createFn = wallet.singleAddress ? getFirstAddress : createNewAddress; |
|
|
|
return createFn(wallet, cb); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
@ -1669,7 +1685,9 @@ WalletService.prototype._validateOutputs = function(opts, wallet, cb) { |
|
|
|
var output = opts.outputs[i]; |
|
|
|
output.valid = false; |
|
|
|
|
|
|
|
if (!checkRequired(output, ['toAddress', 'amount'], cb)) return; |
|
|
|
if (!checkRequired(output, ['toAddress', 'amount'])) { |
|
|
|
return new ClientError('Argument missing in output #' + (i + 1) + '.'); |
|
|
|
} |
|
|
|
|
|
|
|
var toAddress = {}; |
|
|
|
try { |
|
|
@ -1744,6 +1762,8 @@ WalletService.prototype.createTxLegacy = function(opts, cb) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE); |
|
|
|
|
|
|
|
if (wallet.singleAddress) return cb(new ClientError('Not compatible with single-address wallets')); |
|
|
|
|
|
|
|
if (opts.payProUrl) { |
|
|
|
if (wallet.addressType == Constants.SCRIPT_TYPES.P2PKH && !self._clientSupportsPayProRefund()) { |
|
|
|
return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To sign this spend proposal you need to upgrade your client app.')); |
|
|
@ -1858,6 +1878,10 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) |
|
|
|
} |
|
|
|
next(); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
if (wallet.singleAddress && opts.changeAddress) return next(new ClientError('Cannot specify change address on single-address wallet')); |
|
|
|
next(); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
if (!opts.sendMax) return next(); |
|
|
|
if (!_.isArray(opts.outputs) || opts.outputs.length > 1) { |
|
|
@ -1899,7 +1923,7 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) |
|
|
|
* @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 {string} opts.changeAddress - Optional. Use this address as the change address for the tx. The address should belong to the wallet. |
|
|
|
* @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 |
|
|
|
* @param {Boolean} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs |
|
|
@ -1913,6 +1937,25 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) |
|
|
|
WalletService.prototype.createTx = function(opts, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
function getChangeAddress(wallet, cb) { |
|
|
|
if (wallet.singleAddress) { |
|
|
|
self.storage.fetchAddresses(self.walletId, function(err, addresses) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (_.isEmpty(addresses)) return cb(new ClientError('The wallet has no addresses')); |
|
|
|
return cb(null, _.first(addresses)); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
if (opts.changeAddress) { |
|
|
|
self.storage.fetchAddress(opts.changeAddress, function(err, address) { |
|
|
|
if (err) return cb(Errors.INVALID_CHANGE_ADDRESS); |
|
|
|
return cb(null, address); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
return cb(null, wallet.createAddress(true)); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
self._runLocked(cb, function(cb) { |
|
|
|
var wallet, txp, changeAddress; |
|
|
|
async.series([ |
|
|
@ -1937,16 +1980,11 @@ WalletService.prototype.createTx = function(opts, cb) { |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
if (opts.sendMax) return next(); |
|
|
|
if (opts.changeAddress) { |
|
|
|
self.storage.fetchAddress(opts.changeAddress, function(err, address) { |
|
|
|
if (err) return next(Errors.INVALID_CHANGE_ADDRESS); |
|
|
|
changeAddress = address; |
|
|
|
return next(); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
changeAddress = wallet.createAddress(true); |
|
|
|
return next(); |
|
|
|
} |
|
|
|
getChangeAddress(wallet, function(err, address) { |
|
|
|
if (err) return next(err); |
|
|
|
changeAddress = address; |
|
|
|
next(); |
|
|
|
}); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
var txOpts = { |
|
|
|