|
@ -6,6 +6,7 @@ var log = require('npmlog'); |
|
|
log.debug = log.verbose; |
|
|
log.debug = log.verbose; |
|
|
var inherits = require('inherits'); |
|
|
var inherits = require('inherits'); |
|
|
var events = require('events'); |
|
|
var events = require('events'); |
|
|
|
|
|
var nodeutil = require('util'); |
|
|
|
|
|
|
|
|
var Bitcore = require('bitcore'); |
|
|
var Bitcore = require('bitcore'); |
|
|
var PublicKey = Bitcore.PublicKey; |
|
|
var PublicKey = Bitcore.PublicKey; |
|
@ -22,6 +23,7 @@ var Wallet = require('./model/wallet'); |
|
|
var Copayer = require('./model/copayer'); |
|
|
var Copayer = require('./model/copayer'); |
|
|
var Address = require('./model/address'); |
|
|
var Address = require('./model/address'); |
|
|
var TxProposal = require('./model/txproposal'); |
|
|
var TxProposal = require('./model/txproposal'); |
|
|
|
|
|
var Notification = require('./model/Notification'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var initialized = false; |
|
|
var initialized = false; |
|
@ -36,7 +38,7 @@ function CopayServer() { |
|
|
this.storage = storage; |
|
|
this.storage = storage; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
util.inherits(CopayServer, events.EventEmitter); |
|
|
nodeutil.inherits(CopayServer, events.EventEmitter); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
@ -147,19 +149,22 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* notify |
|
|
* _notify |
|
|
* |
|
|
* |
|
|
* @param type |
|
|
* @param type |
|
|
* @param data |
|
|
* @param data |
|
|
*/ |
|
|
*/ |
|
|
CopayServer.prototype.notify = function(type, data) { |
|
|
CopayServer.prototype._notify = function(type, data) { |
|
|
var self = this; |
|
|
var self = this; |
|
|
|
|
|
|
|
|
|
|
|
var walletId = self.walletId || data.walletId; |
|
|
|
|
|
$.checkState(walletId); |
|
|
|
|
|
|
|
|
var n = new Notification({ |
|
|
var n = new Notification({ |
|
|
type: type, |
|
|
type: type, |
|
|
data: data, |
|
|
data: data, |
|
|
}); |
|
|
}); |
|
|
this.storage.storeNotification(this.walletId, n, function () { |
|
|
this.storage.storeNotification(walletId, n, function() { |
|
|
self.emit(n); |
|
|
self.emit(n); |
|
|
}); |
|
|
}); |
|
|
}; |
|
|
}; |
|
@ -202,7 +207,9 @@ CopayServer.prototype.joinWallet = function(opts, cb) { |
|
|
|
|
|
|
|
|
wallet.addCopayer(copayer); |
|
|
wallet.addCopayer(copayer); |
|
|
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { |
|
|
self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { |
|
|
self.notify('newCopayer'); |
|
|
self._notify('NewCopayer', { |
|
|
|
|
|
walletId: opts.walletId, |
|
|
|
|
|
}); |
|
|
return cb(err, copayer.id); |
|
|
return cb(err, copayer.id); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
@ -227,6 +234,7 @@ CopayServer.prototype.createAddress = function(opts, cb) { |
|
|
self.storage.storeAddressAndWallet(wallet, address, function(err) { |
|
|
self.storage.storeAddressAndWallet(wallet, address, function(err) { |
|
|
if (err) return cb(err); |
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
|
|
|
|
self._notify('NewAddress'); |
|
|
return cb(null, address); |
|
|
return cb(null, address); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
@ -411,17 +419,20 @@ CopayServer.prototype._selectUtxos = function(txp, utxos) { |
|
|
CopayServer.prototype.createTx = function(opts, cb) { |
|
|
CopayServer.prototype.createTx = function(opts, cb) { |
|
|
var self = this; |
|
|
var self = this; |
|
|
|
|
|
|
|
|
if (!Utils.checkRequired(opts, ['toAddress', 'amount'])) return cb(new ClientError('Required argument missing')); |
|
|
if (!Utils.checkRequired(opts, ['toAddress', 'amount'])) |
|
|
|
|
|
return cb(new ClientError('Required argument missing')); |
|
|
|
|
|
|
|
|
Utils.runLocked(self.walletId, cb, function(cb) { |
|
|
Utils.runLocked(self.walletId, cb, function(cb) { |
|
|
self.getWallet({}, function(err, wallet) { |
|
|
self.getWallet({}, function(err, wallet) { |
|
|
if (err) return cb(err); |
|
|
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')); |
|
|
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 copayer = wallet.getCopayer(self.copayerId); |
|
|
var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; |
|
|
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; |
|
|
var toAddress; |
|
|
try { |
|
|
try { |
|
@ -429,7 +440,8 @@ CopayServer.prototype.createTx = function(opts, cb) { |
|
|
} catch (ex) { |
|
|
} catch (ex) { |
|
|
return cb(new ClientError('INVALIDADDRESS', 'Invalid address')); |
|
|
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) { |
|
|
self._getUtxos(function(err, utxos) { |
|
|
if (err) return cb(err); |
|
|
if (err) return cb(err); |
|
@ -463,6 +475,9 @@ CopayServer.prototype.createTx = function(opts, cb) { |
|
|
self.storage.storeTx(wallet.id, txp, function(err) { |
|
|
self.storage.storeTx(wallet.id, txp, function(err) { |
|
|
if (err) return cb(err); |
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
|
|
|
|
self._notify('NewTxProposal', { |
|
|
|
|
|
amount: opts.amount |
|
|
|
|
|
}); |
|
|
return cb(null, txp); |
|
|
return cb(null, txp); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
@ -538,6 +553,7 @@ CopayServer.prototype.removePendingTx = function(opts, cb) { |
|
|
if (actors.length == 1 && actors[0] !== self.copayerId) |
|
|
if (actors.length == 1 && actors[0] !== self.copayerId) |
|
|
return cb(new ClientError('Not allowed to erase this TX')); |
|
|
return cb(new ClientError('Not allowed to erase this TX')); |
|
|
|
|
|
|
|
|
|
|
|
self._notify('transactionProposalRemoved'); |
|
|
self.storage.removeTx(self.walletId, opts.id, cb); |
|
|
self.storage.removeTx(self.walletId, opts.id, cb); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
@ -590,7 +606,17 @@ CopayServer.prototype.signTx = function(opts, cb) { |
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
if (err) return cb(err); |
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
|
|
|
|
self._notify('TxProposalAcceptedBy', { |
|
|
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
|
|
copayerId: self.copayerId, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
if (txp.status == 'accepted') { |
|
|
if (txp.status == 'accepted') { |
|
|
|
|
|
|
|
|
|
|
|
self._notify('TxProposalFinallyAccepted', { |
|
|
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
self._broadcastTx(txp, function(err, txid) { |
|
|
self._broadcastTx(txp, function(err, txid) { |
|
|
if (err) return cb(err); |
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
@ -598,6 +624,11 @@ CopayServer.prototype.signTx = function(opts, cb) { |
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
if (err) return cb(err); |
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
|
|
|
|
self._notify('newOutgoingTx', { |
|
|
|
|
|
txProposalId: opts.txProposalId, |
|
|
|
|
|
txid: txid |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
return cb(null, txp); |
|
|
return cb(null, txp); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
@ -628,14 +659,29 @@ CopayServer.prototype.rejectTx = function(opts, cb) { |
|
|
var action = _.find(txp.actions, { |
|
|
var action = _.find(txp.actions, { |
|
|
copayerId: self.copayerId |
|
|
copayerId: self.copayerId |
|
|
}); |
|
|
}); |
|
|
if (action) return cb(new ClientError('CVOTED', 'Copayer already voted on this transaction proposal')); |
|
|
if (action) |
|
|
if (txp.status != 'pending') return cb(new ClientError('TXNOTPENDING', 'The transaction proposal is not pending')); |
|
|
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); |
|
|
txp.reject(self.copayerId); |
|
|
|
|
|
|
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
self.storage.storeTx(self.walletId, txp, function(err) { |
|
|
if (err) return cb(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(); |
|
|
return cb(); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|