|
|
@ -6,6 +6,7 @@ var log = require('npmlog'); |
|
|
|
log.debug = log.verbose; |
|
|
|
var inherits = require('inherits'); |
|
|
|
var events = require('events'); |
|
|
|
var nodeutil = require('util'); |
|
|
|
|
|
|
|
var Bitcore = require('bitcore'); |
|
|
|
var PublicKey = Bitcore.PublicKey; |
|
|
@ -22,6 +23,7 @@ var Wallet = require('./model/wallet'); |
|
|
|
var Copayer = require('./model/copayer'); |
|
|
|
var Address = require('./model/address'); |
|
|
|
var TxProposal = require('./model/txproposal'); |
|
|
|
var Notification = require('./model/Notification'); |
|
|
|
|
|
|
|
|
|
|
|
var initialized = false; |
|
|
@ -34,8 +36,11 @@ var storage; |
|
|
|
function CopayServer() { |
|
|
|
if (!initialized) throw new Error('Server not initialized'); |
|
|
|
this.storage = storage; |
|
|
|
this.notifyTicker = 0; |
|
|
|
}; |
|
|
|
|
|
|
|
nodeutil.inherits(CopayServer, events.EventEmitter); |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Initializes global settings for all instances. |
|
|
@ -61,7 +66,8 @@ CopayServer.getInstance = function() { |
|
|
|
*/ |
|
|
|
CopayServer.getInstanceWithAuth = function(opts, cb) { |
|
|
|
|
|
|
|
if (!Utils.checkRequired(opts, ['copayerId', 'message', 'signature'])) return cb(new ClientError('Required argument missing')); |
|
|
|
if (!Utils.checkRequired(opts, ['copayerId', 'message', 'signature'])) |
|
|
|
return cb(new ClientError('Required argument missing')); |
|
|
|
|
|
|
|
var server = new CopayServer(); |
|
|
|
server.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) { |
|
|
@ -148,6 +154,29 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { |
|
|
|
return SignUtils.verify(text, signature, pubKey); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* _notify |
|
|
|
* |
|
|
|
* @param type |
|
|
|
* @param data |
|
|
|
*/ |
|
|
|
CopayServer.prototype._notify = function(type, data) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var walletId = self.walletId || data.walletId; |
|
|
|
$.checkState(walletId); |
|
|
|
|
|
|
|
var n = new Notification({ |
|
|
|
type: type, |
|
|
|
data: data, |
|
|
|
ticker: this.notifyTicker++, |
|
|
|
}); |
|
|
|
this.storage.storeNotification(walletId, n, function() { |
|
|
|
self.emit(n); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Joins a wallet in creation. |
|
|
|
* @param {Object} opts |
|
|
@ -162,7 +191,8 @@ CopayServer.prototype.joinWallet = function(opts, cb) { |
|
|
|
if (!Utils.checkRequired(opts, ['walletId', 'name', 'xPubKey', 'xPubKeySignature'])) |
|
|
|
return cb(new ClientError('Required argument missing')); |
|
|
|
|
|
|
|
if (_.isEmpty(opts.name)) return cb(new ClientError('Invalid copayer name')); |
|
|
|
if (_.isEmpty(opts.name)) |
|
|
|
return cb(new ClientError('Invalid copayer name')); |
|
|
|
|
|
|
|
Utils.runLocked(opts.walletId, cb, function(cb) { |
|
|
|
self.storage.fetchWallet(opts.walletId, function(err, wallet) { |
|
|
@ -176,7 +206,8 @@ CopayServer.prototype.joinWallet = function(opts, cb) { |
|
|
|
if (_.find(wallet.copayers, { |
|
|
|
xPubKey: opts.xPubKey |
|
|
|
})) return cb(new ClientError('CINWALLET', 'Copayer already in wallet')); |
|
|
|
if (wallet.copayers.length == wallet.n) return cb(new ClientError('WFULL', 'Wallet full')); |
|
|
|
if (wallet.copayers.length == wallet.n) |
|
|
|
return cb(new ClientError('WFULL', 'Wallet full')); |
|
|
|
|
|
|
|
var copayer = new Copayer({ |
|
|
|
name: opts.name, |
|
|
@ -187,6 +218,10 @@ CopayServer.prototype.joinWallet = function(opts, cb) { |
|
|
|
|
|
|
|
wallet.addCopayer(copayer); |
|
|
|
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { |
|
|
|
self._notify('NewCopayer', { |
|
|
|
walletId: opts.walletId, |
|
|
|
copayerId: copayer.id, |
|
|
|
}); |
|
|
|
return cb(err, copayer.id); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -204,13 +239,15 @@ CopayServer.prototype.createAddress = function(opts, cb) { |
|
|
|
Utils.runLocked(self.walletId, cb, function(cb) { |
|
|
|
self.getWallet({}, function(err, wallet) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); |
|
|
|
if (!wallet.isComplete()) |
|
|
|
return cb(new ClientError('Wallet is not complete')); |
|
|
|
|
|
|
|
var address = wallet.createAddress(false); |
|
|
|
|
|
|
|
self.storage.storeAddressAndWallet(wallet, address, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('NewAddress'); |
|
|
|
return cb(null, address); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -403,11 +440,13 @@ CopayServer.prototype.createTx = function(opts, cb) { |
|
|
|
self.getWallet({}, function(err, wallet) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); |
|
|
|
if (wallet.isShared() && !Utils.checkRequired(opts, 'proposalSignature')) return cb(new ClientError('Proposal signature is required for shared wallets')); |
|
|
|
if (wallet.isShared() && !Utils.checkRequired(opts, 'proposalSignature')) |
|
|
|
return cb(new ClientError('Proposal signature is required for shared wallets')); |
|
|
|
|
|
|
|
var copayer = wallet.getCopayer(self.copayerId); |
|
|
|
var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; |
|
|
|
if (!self._verifySignature(msg, opts.proposalSignature, copayer.signingPubKey)) return cb(new ClientError('Invalid proposal signature')); |
|
|
|
if (!self._verifySignature(msg, opts.proposalSignature, copayer.signingPubKey)) |
|
|
|
return cb(new ClientError('Invalid proposal signature')); |
|
|
|
|
|
|
|
var toAddress; |
|
|
|
try { |
|
|
@ -415,7 +454,8 @@ CopayServer.prototype.createTx = function(opts, cb) { |
|
|
|
} catch (ex) { |
|
|
|
return cb(new ClientError('INVALIDADDRESS', 'Invalid address')); |
|
|
|
} |
|
|
|
if (toAddress.network != wallet.getNetworkName()) return cb(new ClientError('INVALIDADDRESS', 'Incorrect address network')); |
|
|
|
if (toAddress.network != wallet.getNetworkName()) |
|
|
|
return cb(new ClientError('INVALIDADDRESS', 'Incorrect address network')); |
|
|
|
|
|
|
|
self._getUtxos(function(err, utxos) { |
|
|
|
if (err) return cb(err); |
|
|
@ -449,6 +489,9 @@ CopayServer.prototype.createTx = function(opts, cb) { |
|
|
|
self.storage.storeTx(wallet.id, txp, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('NewTxProposal', { |
|
|
|
amount: opts.amount |
|
|
|
}); |
|
|
|
return cb(null, txp); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -521,6 +564,7 @@ CopayServer.prototype.removePendingTx = function(opts, cb) { |
|
|
|
if (actors.length > 1 || (actors.length == 1 && actors[0] !== self.copayerId)) |
|
|
|
return cb(new ClientError('Cannot remove a proposal signed/rejected by other copayers')); |
|
|
|
|
|
|
|
self._notify('transactionProposalRemoved'); |
|
|
|
self.storage.removeTx(self.walletId, opts.id, cb); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -573,7 +617,17 @@ CopayServer.prototype.signTx = function(opts, cb) { |
|
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('TxProposalAcceptedBy', { |
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
copayerId: self.copayerId, |
|
|
|
}); |
|
|
|
|
|
|
|
if (txp.status == 'accepted') { |
|
|
|
|
|
|
|
self._notify('TxProposalFinallyAccepted', { |
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
}); |
|
|
|
|
|
|
|
self._broadcastTx(txp, function(err, txid) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
@ -581,6 +635,11 @@ CopayServer.prototype.signTx = function(opts, cb) { |
|
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('NewOutgoingTx', { |
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
txid: txid |
|
|
|
}); |
|
|
|
|
|
|
|
return cb(null, txp); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -612,14 +671,29 @@ CopayServer.prototype.rejectTx = function(opts, cb) { |
|
|
|
var action = _.find(txp.actions, { |
|
|
|
copayerId: self.copayerId |
|
|
|
}); |
|
|
|
if (action) return cb(new ClientError('CVOTED', 'Copayer already voted on this transaction proposal')); |
|
|
|
if (txp.status != 'pending') return cb(new ClientError('TXNOTPENDING', 'The transaction proposal is not pending')); |
|
|
|
if (action) |
|
|
|
return cb(new ClientError('CVOTED', 'Copayer already voted on this transaction proposal')); |
|
|
|
|
|
|
|
if (txp.status != 'pending') |
|
|
|
return cb(new ClientError('TXNOTPENDING', 'The transaction proposal is not pending')); |
|
|
|
|
|
|
|
txp.reject(self.copayerId); |
|
|
|
|
|
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
self._notify('TxProposalRejectedBy', { |
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
copayerId: self.copayerId, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (txp.status == 'rejected') { |
|
|
|
self._notify('TxProposalFinallyRejected', { |
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
return cb(); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -642,10 +716,12 @@ CopayServer.prototype.getPendingTxs = function(opts, cb) { |
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieves pending transaction proposals in the range (maxTs-minTs) |
|
|
|
* Times are in UNIX EPOCH |
|
|
|
* |
|
|
|
* @param {Object} opts.minTs (defaults to 0) |
|
|
|
* @param {Object} opts.maxTs (defaults to now) |
|
|
|
* @param {Object} opts.limit |
|
|
|
* @returns {TxProposal[]} Transaction proposal. |
|
|
|
* @returns {TxProposal[]} Transaction proposals, first newer |
|
|
|
*/ |
|
|
|
CopayServer.prototype.getTxs = function(opts, cb) { |
|
|
|
var self = this; |
|
|
@ -656,6 +732,27 @@ CopayServer.prototype.getTxs = function(opts, cb) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieves notifications in the range (maxTs-minTs). |
|
|
|
* Times are in UNIX EPOCH. Order is assured even for events with the same time |
|
|
|
* |
|
|
|
* @param {Object} opts.minTs (defaults to 0) |
|
|
|
* @param {Object} opts.maxTs (defaults to now) |
|
|
|
* @param {Object} opts.limit |
|
|
|
* @param {Object} opts.reverse (default false) |
|
|
|
* @returns {Notification[]} Notifications |
|
|
|
*/ |
|
|
|
CopayServer.prototype.getNotifications = function(opts, cb) { |
|
|
|
var self = this; |
|
|
|
self.storage.fetchNotifications(self.walletId, opts, function(err, notifications) { |
|
|
|
if (err) return cb(err); |
|
|
|
return cb(null, notifications); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = CopayServer; |
|
|
|
module.exports.ClientError = ClientError; |
|
|
|