@ -31,12 +31,12 @@ var TxProposal = require('./model/txproposal');
* /
function CopayServer ( opts ) {
opts = opts || { } ;
this . storage = opts . storage || new Storage ( ) ;
this . storage = opts . storage || new Storage ( ) ;
} ;
inherits ( CopayServer , events . EventEmitter ) ;
CopayServer . _ emit = function ( event ) {
CopayServer . _ emit = function ( event ) {
var args = Array . prototype . slice . call ( arguments ) ;
log . debug ( 'Emitting: ' , args ) ;
this . emit . apply ( this , arguments ) ;
@ -52,8 +52,9 @@ CopayServer._emit = function (event) {
* @ 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 .
* /
CopayServer . prototype . createWallet = function ( opts , cb ) {
var self = this , pubKey ;
CopayServer . prototype . createWallet = function ( opts , cb ) {
var self = this ,
pubKey ;
Utils . checkRequired ( opts , [ 'id' , 'name' , 'm' , 'n' , 'pubKey' ] ) ;
if ( ! Wallet . verifyCopayerLimits ( opts . m , opts . n ) ) return cb ( 'Incorrect m or n value' ) ;
@ -66,7 +67,7 @@ CopayServer.prototype.createWallet = function (opts, cb) {
return cb ( e . toString ( ) ) ;
} ;
self . storage . fetchWallet ( opts . id , function ( err , wallet ) {
self . storage . fetchWallet ( opts . id , function ( err , wallet ) {
if ( err ) return cb ( err ) ;
if ( wallet ) return cb ( 'Wallet already exists' ) ;
@ -89,10 +90,10 @@ CopayServer.prototype.createWallet = function (opts, cb) {
* @ param { string } opts . id - The wallet id .
* @ returns { Object } wallet
* /
CopayServer . prototype . getWallet = function ( opts , cb ) {
CopayServer . prototype . getWallet = function ( opts , cb ) {
var self = this ;
self . storage . fetchWallet ( opts . id , function ( err , wallet ) {
self . storage . fetchWallet ( opts . id , function ( err , wallet ) {
if ( err ) return cb ( err ) ;
if ( ! wallet ) return cb ( 'Wallet not found' ) ;
return cb ( null , wallet ) ;
@ -119,13 +120,15 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) {
* @ param { number } opts . xPubKey - Extended Public Key for this copayer .
* @ param { number } opts . xPubKeySignature - Signature of xPubKey using the wallet pubKey .
* /
CopayServer . prototype . joinWallet = function ( opts , cb ) {
CopayServer . prototype . joinWallet = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , [ 'walletId' , 'id' , 'name' , 'xPubKey' , 'xPubKeySignature' ] ) ;
Utils . runLocked ( opts . walletId , cb , function ( cb ) {
self . getWallet ( { id : opts . walletId } , function ( err , wallet ) {
Utils . runLocked ( opts . walletId , cb , function ( cb ) {
self . getWallet ( {
id : opts . walletId
} , function ( err , wallet ) {
if ( err ) return cb ( err ) ;
if ( ! self . _ verifySignature ( opts . xPubKey , opts . xPubKeySignature , wallet . pubKey ) ) {
@ -144,10 +147,10 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) {
xPubKeySignature : opts . xPubKeySignature ,
copayerIndex : wallet . copayers . length ,
} ) ;
wallet . addCopayer ( copayer ) ;
self . storage . storeWallet ( wallet , function ( err ) {
self . storage . storeWallet ( wallet , function ( err ) {
return cb ( err ) ;
} ) ;
} ) ;
@ -164,16 +167,18 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) {
* @ param { truthy } opts . isChange - Indicates whether this is a regular address or a change address .
* @ returns { Address } address
* /
CopayServer . prototype . createAddress = function ( opts , cb ) {
CopayServer . prototype . createAddress = function ( opts , cb ) {
var self = this ;
var isChange = opts . isChange || false ;
Utils . checkRequired ( opts , [ 'walletId' , 'isChange' ] ) ;
Utils . runLocked ( opts . walletId , cb , function ( cb ) {
self . getWallet ( { id : opts . walletId } , function ( err , wallet ) {
Utils . runLocked ( opts . walletId , cb , function ( cb ) {
self . getWallet ( {
id : opts . walletId
} , function ( err , wallet ) {
if ( err ) return cb ( err ) ;
var copayer = wallet . copayers [ 0 ] ; // TODO: Assign copayer from authentication.
var path = copayer . getNewAddressPath ( isChange ) ;
@ -202,12 +207,14 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) {
* @ param { string } opts . signature - The signature of message to verify .
* @ returns { truthy } The result of the verification .
* /
CopayServer . prototype . verifyMessageSignature = function ( opts , cb ) {
CopayServer . prototype . verifyMessageSignature = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , [ 'walletId' , 'copayerId' , 'message' , 'signature' ] ) ;
self . getWallet ( { id : opts . walletId } , function ( err , wallet ) {
self . getWallet ( {
id : opts . walletId
} , function ( err , wallet ) {
if ( err ) return cb ( err ) ;
var copayer = wallet . getCopayer ( opts . copayerId ) ;
@ -219,7 +226,7 @@ CopayServer.prototype.verifyMessageSignature = function (opts, cb) {
} ;
CopayServer . prototype . _ getBlockExplorer = function ( provider , network ) {
CopayServer . prototype . _ getBlockExplorer = function ( provider , network ) {
var url ;
switch ( provider ) {
@ -239,33 +246,37 @@ CopayServer.prototype._getBlockExplorer = function (provider, network) {
}
} ;
CopayServer . prototype . _ getUtxos = function ( opts , cb ) {
CopayServer . prototype . _ getUtxos = function ( opts , cb ) {
var self = this ;
// Get addresses for this wallet
self . storage . fetchAddresses ( opts . walletId , function ( err , addresses ) {
self . storage . fetchAddresses ( opts . walletId , function ( err , addresses ) {
if ( err ) return cb ( err ) ;
if ( addresses . length == 0 ) return cb ( 'The wallet has no addresses' ) ;
var addresses = _ . pluck ( addresses , 'address' ) ;
var bc = self . _ getBlockExplorer ( 'insight' , opts . network ) ;
bc . getUnspentUtxos ( addresses , function ( err , utxos ) {
bc . getUnspentUtxos ( addresses , function ( err , utxos ) {
if ( err ) return cb ( err ) ;
self . getPendingTxs ( { walletId : opts . walletId } , function ( err , txps ) {
self . getPendingTxs ( {
walletId : opts . walletId
} , function ( err , txps ) {
if ( err ) return cb ( err ) ;
var inputs = _ . chain ( txps )
. pluck ( 'inputs' )
. flatten ( )
. map ( function ( utxo ) { return utxo . txid + '|' + utxo . vout } ) ;
. map ( function ( utxo ) {
return utxo . txid + '|' + utxo . vout
} ) ;
var dictionary = _ . groupBy ( utxos , function ( utxo ) {
var dictionary = _ . groupBy ( utxos , function ( utxo ) {
return utxo . txid + '|' + utxo . vout ;
} ) ;
_ . each ( inputs , function ( input ) {
_ . each ( inputs , function ( input ) {
if ( dictionary [ input ] ) {
dictionary [ input ] . locked = true ;
}
@ -283,17 +294,25 @@ CopayServer.prototype._getUtxos = function (opts, cb) {
* @ param { string } opts . walletId - The wallet id .
* @ returns { Object } balance - Total amount & locked amount .
* /
CopayServer . prototype . getBalance = function ( opts , cb ) {
CopayServer . prototype . getBalance = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , 'walletId' ) ;
self . _ getUtxos ( { walletId : opts . walletId } , function ( err , utxos ) {
self . _ getUtxos ( {
walletId : opts . walletId
} , function ( err , utxos ) {
if ( err ) return cb ( err ) ;
var balance = { } ;
balance . totalAmount = _ . reduce ( utxos , function ( sum , utxo ) { return sum + utxo . amount ; } , 0 ) ;
balance . lockedAmount = _ . reduce ( _ . without ( utxos , { locked : true } ) , function ( sum , utxo ) { return sum + utxo . amount ; } , 0 ) ;
balance . totalAmount = _ . reduce ( utxos , function ( sum , utxo ) {
return sum + utxo . amount ;
} , 0 ) ;
balance . lockedAmount = _ . reduce ( _ . without ( utxos , {
locked : true
} ) , function ( sum , utxo ) {
return sum + utxo . amount ;
} , 0 ) ;
return cb ( null , balance ) ;
} ) ;
@ -336,18 +355,24 @@ CopayServer.prototype._selectUtxos = function(txp, utxos) {
* @ param { string } opts . message - A message to attach to this transaction .
* @ returns { TxProposal } Transaction proposal .
* /
CopayServer . prototype . createTx = function ( opts , cb ) {
CopayServer . prototype . createTx = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , [ 'walletId' , 'copayerId' , 'toAddress' , 'amount' , 'message' ] ) ;
self . getWallet ( { id : opts . walletId } , function ( err , wallet ) {
self . getWallet ( {
id : opts . walletId
} , function ( err , wallet ) {
if ( err ) return cb ( err ) ;
self . _ getUtxos ( { walletId : wallet . id } , function ( err , utxos ) {
self . _ getUtxos ( {
walletId : wallet . id
} , function ( err , utxos ) {
if ( err ) return cb ( err ) ;
utxos = _ . without ( utxos , { locked : true } ) ;
utxos = _ . without ( utxos , {
locked : true
} ) ;
var txp = new TxProposal ( {
creatorId : opts . copayerId ,
@ -361,18 +386,18 @@ CopayServer.prototype.createTx = function (opts, cb) {
txp . rawTx = self . _ createRawTx ( txp ) ;
self . storage . storeTx ( wallet . id , txp , function ( err ) {
self . storage . storeTx ( wallet . id , txp , function ( err ) {
if ( err ) return cb ( err ) ;
return cb ( null , txp ) ;
} ) ;
} ) ;
} ) ;
} ;
} ;
CopayServer . prototype . _ broadcastTx = function ( rawTx , cb ) {
CopayServer . prototype . _ broadcastTx = function ( rawTx , cb ) {
// TODO: this should attempt to broadcast _all_ accepted and not-yet broadcasted (status=='accepted') txps?
cb = cb || function ( ) { } ;
cb = cb || function ( ) { } ;
throw 'not implemented' ;
} ;
@ -385,29 +410,31 @@ CopayServer.prototype._broadcastTx = function (rawTx, cb) {
* @ param { string } opts . txProposalId - The identifier of the transaction .
* @ param { string } opts . signature - The signature of the tx for this copayer .
* /
CopayServer . prototype . signTx = function ( opts , cb ) {
CopayServer . prototype . signTx = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , [ 'walletId' , 'copayerId' , 'txProposalId' , 'signature' ] ) ;
self . fetchTx ( opts . walletId , opts . txProposalId , function ( err , txp ) {
self . fetchTx ( opts . walletId , opts . txProposalId , function ( err , txp ) {
if ( err ) return cb ( err ) ;
if ( ! txp ) return cb ( 'Transaction proposal not found' ) ;
var action = _ . find ( txp . actions , { copayerId : opts . copayerId } ) ;
var action = _ . find ( txp . actions , {
copayerId : opts . copayerId
} ) ;
if ( action ) return cb ( 'Copayer already voted on this transaction proposal' ) ;
if ( txp . status != 'pending' ) return cb ( 'The transaction proposal is not pending' ) ;
txp . sign ( opts . copayerId , opts . signature ) ;
self . storage . storeTx ( opts . walletId , txp , function ( err ) {
self . storage . storeTx ( opts . walletId , txp , function ( err ) {
if ( err ) return cb ( err ) ;
if ( txp . status == 'accepted' ) ;
self . _ broadcastTx ( txp . rawTx , function ( err , txid ) {
self . _ broadcastTx ( txp . rawTx , function ( err , txid ) {
if ( err ) return cb ( err ) ;
tx . setBroadcasted ( txid ) ;
self . storage . storeTx ( opts . walletId , txp , function ( err ) {
self . storage . storeTx ( opts . walletId , txp , function ( err ) {
if ( err ) return cb ( err ) ;
return cb ( ) ;
@ -425,21 +452,23 @@ CopayServer.prototype.signTx = function (opts, cb) {
* @ param { string } opts . txProposalId - The identifier of the transaction .
* @ param { string } [ opts . reason ] - A message to other copayers explaining the rejection .
* /
CopayServer . prototype . rejectTx = function ( opts , cb ) {
CopayServer . prototype . rejectTx = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , [ 'walletId' , 'copayerId' , 'txProposalId' ] ) ;
self . fetchTx ( opts . walletId , opts . txProposalId , function ( err , txp ) {
self . fetchTx ( opts . walletId , opts . txProposalId , function ( err , txp ) {
if ( err ) return cb ( err ) ;
if ( ! txp ) return cb ( 'Transaction proposal not found' ) ;
var action = _ . find ( txp . actions , { copayerId : opts . copayerId } ) ;
var action = _ . find ( txp . actions , {
copayerId : opts . copayerId
} ) ;
if ( action ) return cb ( 'Copayer already voted on this transaction proposal' ) ;
if ( txp . status != 'pending' ) return cb ( 'The transaction proposal is not pending' ) ;
txp . reject ( opts . copayerId ) ;
self . storage . storeTx ( opts . walletId , txp , function ( err ) {
self . storage . storeTx ( opts . walletId , txp , function ( err ) {
if ( err ) return cb ( err ) ;
return cb ( ) ;
@ -453,15 +482,17 @@ CopayServer.prototype.rejectTx = function (opts, cb) {
* @ param { string } opts . walletId - The wallet id .
* @ returns { TxProposal [ ] } Transaction proposal .
* /
CopayServer . prototype . getPendingTxs = function ( opts , cb ) {
CopayServer . prototype . getPendingTxs = function ( opts , cb ) {
var self = this ;
Utils . checkRequired ( opts , 'walletId' ) ;
self . storage . fetchTxs ( opts . walletId , function ( err , txps ) {
self . storage . fetchTxs ( opts . walletId , function ( err , txps ) {
if ( err ) return cb ( err ) ;
var pending = _ . filter ( txps , { status : 'pending' } ) ;
var pending = _ . filter ( txps , {
status : 'pending'
} ) ;
return cb ( null , pending ) ;
} ) ;
} ;