diff --git a/copay.js b/copay.js index f7d6dab53..a55c4cd9b 100644 --- a/copay.js +++ b/copay.js @@ -5,8 +5,14 @@ module.exports.TxProposals = require('./js/models/core/TxProposals'); module.exports.PrivateKey = require('./js/models/core/PrivateKey'); // components -module.exports.Network = require('./js/models/network/WebRTC'); -module.exports.Storage = require('./js/models/storage/Plain'); +var WebRTC = require('./js/models/network/WebRTC'); +var Plain = require('./js/models/storage/Plain'); +var Insight = require('./js/models/blockchain/Insight'); + +module.exports.Wallet = require('soop').load('./js/models/core/Wallet',{ + Network: WebRTC, + Storage: Plain, + Blockchain: Insight, +}); + -// test -module.exports.FakeStorage = require('./test/FakeStorage'); diff --git a/js/config.js b/js/config.js index 6faa87857..b7619cf05 100644 --- a/js/config.js +++ b/js/config.js @@ -7,4 +7,8 @@ var config = { maxPeers: 5, requiredCopayers: 2, totalCopayers: 3 + insight: { + host: 'localhost', + port: 3001 + }, }; diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js new file mode 100644 index 000000000..015971503 --- /dev/null +++ b/js/models/blockchain/Insight.js @@ -0,0 +1,115 @@ +'use strict'; + +var imports = require('soop').imports(); +var bitcore = require('bitcore'); +var http = require('http'); + +function Insight(opts) { + opts = opts || {}; + this.host = 'localhost'; + this.port = '3001'; +} + +function _asyncForEach(array, fn, callback) { + array = array.slice(0); + function processOne() { + var item = array.pop(); + fn(item, function(result) { + if(array.length > 0) { + setTimeout(processOne, 0); // schedule immediately + } else { + callback(); // Done! + } + }); + } + if(array.length > 0) { + setTimeout(processOne, 0); // schedule immediately + } else { + callback(); // Done! + } +}; + +Insight.prototype.getBalance = function(unspent) { + var balance = 0; + for(var i=0;i 0) { + all = all.concat(res); + } + callback(); + }); + }, function() { + return cb(all); + }); +}; + +Insight.prototype.sendRawTransaction = function(rawtx, cb) { + if (!rawtx) return callback(); + + var options = { + host: this.host, + port: this.port, + method: 'POST', + path: '/api/tx/send', + data: 'rawtx='+rawtx, + headers: { 'content-type' : 'application/x-www-form-urlencoded' } + }; + + this._request(options, function(err,res) { + if (err) return cb(); + return cb(res.txid); + }); +}; + +Insight.prototype._request = function(options, callback) { + var req = http.request(options, function(response) { + var ret; + if (response.statusCode == 200) { + response.on('data', function(chunk) { + try { + ret = JSON.parse(chunk); + } catch (e) { + callback({message: "Wrong response from insight"}); + return; + } + }); + response.on('end', function () { + callback(undefined, ret); + return; + }); + } + else { + callback({message: 'Error ' + response.statusCode}); + return; + } + }); + if (options.data) { + req.write(options.data); + } + req.end(); +} + + +module.exports = require('soop')(Insight); + diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 559d87b5f..80dd9d5b0 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1,114 +1,78 @@ 'use strict'; var imports = require('soop').imports(); + var bitcore = require('bitcore'); var http = require('http'); -function Wallet(opts) { +var Storage = imports.Storage || require('FakeStorage'); +var Network = imports.Network || require('FakeNetwork'); +var Blockchain = imports.Blockchain || require('FakeBlockchain'); + +var copay = copay || require('../../../copay'); + + +function Wallet(opts, config) { opts = opts || {}; - this.host = 'localhost'; - this.port = '3001'; -} -function asyncForEach(array, fn, callback) { - array = array.slice(0); - function processOne() { - var item = array.pop(); - fn(item, function(result) { - if(array.length > 0) { - setTimeout(processOne, 0); // schedule immediately - } else { - callback(); // Done! - } - }); - } - if(array.length > 0) { - setTimeout(processOne, 0); // schedule immediately - } else { - callback(); // Done! - } -}; + console.log('### CREATING WALLET.' + + (opts.walletId ? ' USING ID: ' +opts.walletId : ' NEW ID') ); -Wallet.prototype.getBalance = function(unspent) { - var balance = 0; - for(var i=0;i 0) { - all = all.concat(res); - } - callback(); - }); - }, function() { - return cb(all); + + this.publicKeyRing = opts.publicKeyRing || new copay.PublicKeyRing({ + id: opts.walletId, + requiredCopayers: opts.requiredCopayers || config.wallet.requiredCopayers, + totalCopayers: opts.totalCopayers || config.wallet.totalCopayers, + networkName: config.networkName, }); -}; -Wallet.prototype.sendRawTransaction = function(rawtx, cb) { - if (!rawtx) return callback(); + this.publicKeyRing.addCopayer(this.privateKey.getBIP32().extendedPublicKeyString()); + console.log('\t### PublicKeyRing Initialized WalletID: ' + this.publicKeyRing.id); - var options = { - host: this.host, - port: this.port, - method: 'POST', - path: '/api/tx/send', - data: 'rawtx='+rawtx, - headers: { 'content-type' : 'application/x-www-form-urlencoded' } - }; - - this.request(options, function(err,res) { - if (err) return cb(); - return cb(res.txid); + this.txProposals = new copay.TxProposals({ + walletId: this.publicKeyRing.id, + publicKeyRing: this.publicKeyRing, + networkName: config.networkName, }); -}; - -Wallet.prototype.request = function(options, callback) { - var req = http.request(options, function(response) { - var ret; - if (response.statusCode == 200) { - response.on('data', function(chunk) { - try { - ret = JSON.parse(chunk); - } catch (e) { - callback({message: "Wrong response from insight"}); - return; - } - }); - response.on('end', function () { - callback(undefined, ret); - return; - }); - } - else { - callback({message: 'Error ' + response.statusCode}); - return; - } - }); - if (options.data) { - req.write(options.data); - } - req.end(); + console.log('\t### TxProposals Initialized'); } +// +// var Read = function(walletId) { +// this.storage.read(walletId); +// $rootScope.w = new copay.PublicKeyRing.fromObj(pkr); +// $rootScope.txProposals = new copay.TxProposals.fromObj(txp); +// $rootScope.PrivateKey = new copay.PrivateKey.fromObj(priv); //TODO secure +// +// + + // HERE or in Storage? + // $rootScope.walletId = walletId; + // $rootScope.w = new copay.PublicKeyRing.fromObj(pkr); + // $rootScope.txProposals = new copay.TxProposals.fromObj(txp); + // $rootScope.PrivateKey = new copay.PrivateKey.fromObj(priv); //TODO secure + + // // JIC: Add our key + // try { + // $rootScope.publicKeyRing.addCopayer( + // $rootScope.PrivateKey.getBIP32().extendedPublicKeyString() + // ); + // } catch (e) { + // console.log('NOT NECCESARY AN ERROR:', e); //TODO + // }; + // ret = true; + // } + // return ret; + // }; +// }; module.exports = require('soop')(Wallet); diff --git a/js/services/network.js b/js/services/network.js index 3814110f3..8b5b7cb40 100644 --- a/js/services/network.js +++ b/js/services/network.js @@ -44,91 +44,39 @@ angular.module('copay.network') var storeOpenWallet = function() { var id = $rootScope.walletId; Storage.addWalletId(id); - Storage.set(id, 'publicKeyRing',$rootScope.publicKeyRing.toObj()); - Storage.set(id, 'privateKey', $rootScope.privateKey.toObj()); - Storage.set(id, 'txProposals', $rootScope.txProposals.toObj()); - console.log('\t### Wallet Stored'); + Storage.set(id, 'wallet',$rootScope.wallet.toObj()); + console.log('\t### Wallet %s Stored', id); }; // TODO -> probably not in network.js var createWallet = function(walletId) { - console.log('### CREATING WALLET. ID:' + walletId); - - var priv = new copay.PrivateKey({networkName: config.networkName}); - console.log('\t### PrivateKey Initialized'); -//TODO -console.log('[PRIVATE]',priv.toObj()); //TODO - - //TODO create a wallet and WalletId, not only pkr - var pkr = new copay.PublicKeyRing({ - networkName: config.networkName, - id: walletId, - requiredCopayers: config.requiredCopayers || 3, // TODO set per wallet - totalCopayers: config.totalCopayers || 5, - }); - -console.log('[network.js.70] WALLET ID IS:', pkr.id); //TODO - - // Add self to the ring. - pkr.addCopayer(priv.getBIP32().extendedPublicKeyString()); - - console.log('\t### PublicKeyRing Initialized'); - - var txp = new copay.TxProposals({ - networkName: config.networkName, - publicKeyRing: pkr, - walletId: pkr.id, - }); - console.log('\t### TxProposals Initialized'); + opts.walletId = walletId; + var w = new copay.Wallet(opts, config); // Store it on rootScope - $rootScope.walletId = pkr.id; - $rootScope.privateKey = priv; // TODO secure this. - $rootScope.publicKeyRing = pkr; - $rootScope.txProposals = txp; + $rootScope.walletId = pkr.id; + $rootScope.wallet = w; - storeOpenWallet(); +//TODO +// w.store(); }; var openWallet = function (walletId) { -console.log('[network.js.90:openWallet:]',walletId); //TODO - - var ret = false; - var pkr = Storage.get(walletId, 'publicKeyRing'); - var priv = Storage.get(walletId, 'privateKey'); - var txp = Storage.get(walletId, 'txProposals'); - -console.log('[network.js.96:pkr:]',pkr); //TODO -console.log('[network.js.97:priv:]',priv); //TODO - if (pkr && pkr.copayersExtPubKeys.length && priv) { - console.log('### WALLET OPENED:', walletId, pkr); - $rootScope.walletId = walletId; - $rootScope.publicKeyRing = new copay.PublicKeyRing.fromObj(pkr); - $rootScope.txProposals = new copay.TxProposals.fromObj(txp); - $rootScope.PrivateKey = new copay.PrivateKey.fromObj(priv); //TODO secure + var w = Wallet.read(walletId); - // JIC: Add our key - try { - $rootScope.publicKeyRing.addCopayer( - $rootScope.PrivateKey.getBIP32().extendedPublicKeyString() - ); - } catch (e) { - console.log('NOT NECCESARY AN ERROR:', e); //TODO - }; - ret = true; + if (w && w.publicKeyRing.copayersExtPubKeys.length && w.privateKey) { + console.log('### WALLET OPENED:', w.walletId); + $rootScope.walletId = walletId; + $rootScope.wallet = w; } - return ret; }; - var closeWallet = function() { console.log('### CLOSING WALLET'); - $rootScope.walletId = null; - $rootScope.publicKeyRing = null; - $rootScope.privateKey = null; - $rootScope.txProposals = null; + $rootScope.delete('walletId'); + $rootScope.delete('w'); }; var _checkWallet = function(walletId, allowChange) { diff --git a/test/test.insight.js b/test/test.insight.js new file mode 100644 index 000000000..c5fd13ff6 --- /dev/null +++ b/test/test.insight.js @@ -0,0 +1,78 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var bitcore = bitcore || require('bitcore'); +var copay = copay || require('../copay'); +var Insight = copay.Insight || require('../js/models/blockchain/Insight'); + +var ID = '933bf321393459b7'; +var copayers = [ + 'tpubD6NzVbkrYhZ4WeSS3M5axcR1EMYPeerA8GozBmYVLKSjriMXhse1C4kiLJMvaaDKRBaP7iSJJo5wMBh3JSYcMz1vrwXKKnAgtt4V4pfSEcq', + 'tpubD6NzVbkrYhZ4XPjvz7c2544jPBY2WKCJVCETEE68ykBLMcE7J3GVDGvmPEdzvTWWXxQsE25rm7f4J1ZNxzWhuR7iEhX1m4dS9HrYbg1ezUP', + 'tpubD6NzVbkrYhZ4YTRVfKf1tHgydyvoEWdsBRVCG6odCZdpY7nPZWxA26sLPtyHkquzHmgdAH8HpftobnJJUvcbi7MyHVqXmPLJCW9KCS6rkw8', + 'tpubD6NzVbkrYhZ4XDY86vJmcCUuUvbqujhM633a5ih8b6ngm1AsskGz3orGkjvbzcJNQUJSK9jqggRwSohq3LAigwWZ8uzGNrGZqCwaE95foAj', + 'tpubD6NzVbkrYhZ4XGHkbBTx4kU5w7RDb9hWXyK9tuEaYrY9SJUWBCUxrcMFkqBa6qAv11FNdVJ4MFxKdnKnjoBWDY6SwBtmP83gjFHTV5zz4RW' +]; +var addresses = [ + '2NATQJnaQe2CUKLyhL1zdNkttJM1dUH9HaM', + '2NE9hTCffeugo5gQtfB4owq98gyTeWC56yb', // 41btc + '2N9D5bcCQ2bPWUDByQ6Qb5bMgMtgsk1rw3x', // 50btc + '2NBEAi14f3xhwmGs9omEgKUwsW84BkzLp7S', + '2N3RhiBW4ssXJnEbPjBCYThJHhEHQWAapf6', + '2Mvn2Duvw8cdHs5AB8ZLXfoef1a71UrDr4W', + '2NFjCBFZSsxiwWAD7CKQ3hzWFtf9DcqTucY', + '2N9EdxU3co5XKTyj3yhFBeU3qw3EM1rrgzE' +]; + +var unspent = [ + { + address: "2NE9hTCffeugo5gQtfB4owq98gyTeWC56yb", + txid: "d5597c6cf7f72507af63a4d5a2f9f84edb45fb42452cc8c514435b7a93158915", + vout: 0, + ts: 1397050347, + scriptPubKey: "a914e54f125244a0bf91f9c5d861dc28343ccf19883d87", + amount: 41, + confirmations: 7007 + }, + { + address: "2N9D5bcCQ2bPWUDByQ6Qb5bMgMtgsk1rw3x", + txid: "90d0e1f993fc41596e7b0a7a3be8ef65d606164e13ce538bd3f48136b60eff5a", + vout: 0, + ts: 1397070106, + scriptPubKey: "a914af1a2d1a9c0fa172ed70bc1c50ea6b66994e9abf87", + amount: 50, + confirmations: 6728 + } +]; + +var rawtx = '01000000010c2a03ed71ee18148e8c99c5ff66d5ffb75e5def46cdea2acc6f30103f33bfb5010000006a47304402207f960aeefdfad270dd77d1acca7af17d3a2e47e2059034ff5d6305cf63635e1d02202f061ee196cc4459cdecae6559beac696a9ecde9a17520849f319fa2a627e64f012103870465f9b4efb90b5d186a7a5eacd7081e601020dacd68d942e5918a56ed0bfcffffffff02a086010000000000ad532102a9495c64323cd8c3354dbf0b3400d830ee680da493acbccc3c2c356d1b20fabf21028233cf8bc6112ae2c36468bd447732c5586b52e1ba3284a2319cadfac6367f99210279fd856e5ed13ab6807e85ed7c0cd6f80613be042240fd731c43f5aba3dcae9821021380858a67a4f99eda52ce2d72c300911f9d3eb9d7a45102a2133f14f7b2dc14210215739b613ce42106a11ce433342c13c610bf68a1bc934f607ad7aeb4178e04cf55ae2044d200000000001976a9146917322f0010aaf7ec136a34b476dfc5eb7a331288ac00000000'; + +describe('Insight model', function() { + + it('should create an instance', function () { + var w = new Insight(); + should.exist(w); + }); + it.skip('should return array of unspent output', function(done) { + var w = new Insight(); + w.listUnspent(addresses, function(a) { + should.exist(a); + done(); + }); + }); + it('should return balance', function () { + var w = new Insight(); + var b = w.getBalance(unspent); + should.exist(b); + b.should.equal(91); + }); + it.skip('should return txid', function (done) { + var w = new Insight(); + w.sendRawTransaction(rawtx, function(a) { + should.exist(a); + done(); + }); + }); +}); + diff --git a/test/test.storage-plain.js b/test/test.storage-plain.js new file mode 100644 index 000000000..e6e0d38b8 --- /dev/null +++ b/test/test.storage-plain.js @@ -0,0 +1,16 @@ +'use strict'; + +if (!process.version) { + var chai = chai || require('chai'); + var should = chai.should(); + var copay = copay || require('../copay'); + var Plain = copay.Storage; + + describe('Storage model', function() { + + it('should create an instance', function () { + var s = new Storage(); + should.exist(s); + }); + }); +} diff --git a/test/test.storage.js b/test/test.storage.js deleted file mode 100644 index d108b6e5b..000000000 --- a/test/test.storage.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var chai = chai || require('chai'); -var should = chai.should(); -var copay = copay || require('../copay'); -var Storage = copay.Storage || require('../js/models/Storage'); - -describe('Storage model', function() { - - it('should create an instance', function () { - var s = new Storage(); - should.exist(s); - }); -}); - diff --git a/test/test.wallet.js b/test/test.wallet.js index f1b56d98b..4c359e892 100644 --- a/test/test.wallet.js +++ b/test/test.wallet.js @@ -4,75 +4,22 @@ var chai = chai || require('chai'); var should = chai.should(); var bitcore = bitcore || require('bitcore'); var copay = copay || require('../copay'); -var Wallet = copay.Wallet || require('soop').load('../js/models/core/Wallet'); +var Wallet = copay.Wallet || require('./js/models/core/Wallet'); -var ID = '933bf321393459b7'; -var copayers = [ - 'tpubD6NzVbkrYhZ4WeSS3M5axcR1EMYPeerA8GozBmYVLKSjriMXhse1C4kiLJMvaaDKRBaP7iSJJo5wMBh3JSYcMz1vrwXKKnAgtt4V4pfSEcq', - 'tpubD6NzVbkrYhZ4XPjvz7c2544jPBY2WKCJVCETEE68ykBLMcE7J3GVDGvmPEdzvTWWXxQsE25rm7f4J1ZNxzWhuR7iEhX1m4dS9HrYbg1ezUP', - 'tpubD6NzVbkrYhZ4YTRVfKf1tHgydyvoEWdsBRVCG6odCZdpY7nPZWxA26sLPtyHkquzHmgdAH8HpftobnJJUvcbi7MyHVqXmPLJCW9KCS6rkw8', - 'tpubD6NzVbkrYhZ4XDY86vJmcCUuUvbqujhM633a5ih8b6ngm1AsskGz3orGkjvbzcJNQUJSK9jqggRwSohq3LAigwWZ8uzGNrGZqCwaE95foAj', - 'tpubD6NzVbkrYhZ4XGHkbBTx4kU5w7RDb9hWXyK9tuEaYrY9SJUWBCUxrcMFkqBa6qAv11FNdVJ4MFxKdnKnjoBWDY6SwBtmP83gjFHTV5zz4RW' -]; -var addresses = [ - '2NATQJnaQe2CUKLyhL1zdNkttJM1dUH9HaM', - '2NE9hTCffeugo5gQtfB4owq98gyTeWC56yb', // 41btc - '2N9D5bcCQ2bPWUDByQ6Qb5bMgMtgsk1rw3x', // 50btc - '2NBEAi14f3xhwmGs9omEgKUwsW84BkzLp7S', - '2N3RhiBW4ssXJnEbPjBCYThJHhEHQWAapf6', - '2Mvn2Duvw8cdHs5AB8ZLXfoef1a71UrDr4W', - '2NFjCBFZSsxiwWAD7CKQ3hzWFtf9DcqTucY', - '2N9EdxU3co5XKTyj3yhFBeU3qw3EM1rrgzE' -]; -var unspent = [ - { - address: "2NE9hTCffeugo5gQtfB4owq98gyTeWC56yb", - txid: "d5597c6cf7f72507af63a4d5a2f9f84edb45fb42452cc8c514435b7a93158915", - vout: 0, - ts: 1397050347, - scriptPubKey: "a914e54f125244a0bf91f9c5d861dc28343ccf19883d87", - amount: 41, - confirmations: 7007 - }, - { - address: "2N9D5bcCQ2bPWUDByQ6Qb5bMgMtgsk1rw3x", - txid: "90d0e1f993fc41596e7b0a7a3be8ef65d606164e13ce538bd3f48136b60eff5a", - vout: 0, - ts: 1397070106, - scriptPubKey: "a914af1a2d1a9c0fa172ed70bc1c50ea6b66994e9abf87", - amount: 50, - confirmations: 6728 +var config = { + wallet: { + requiredCopayers: 3, + totalCopayers: 5, } -]; - -var rawtx = '01000000010c2a03ed71ee18148e8c99c5ff66d5ffb75e5def46cdea2acc6f30103f33bfb5010000006a47304402207f960aeefdfad270dd77d1acca7af17d3a2e47e2059034ff5d6305cf63635e1d02202f061ee196cc4459cdecae6559beac696a9ecde9a17520849f319fa2a627e64f012103870465f9b4efb90b5d186a7a5eacd7081e601020dacd68d942e5918a56ed0bfcffffffff02a086010000000000ad532102a9495c64323cd8c3354dbf0b3400d830ee680da493acbccc3c2c356d1b20fabf21028233cf8bc6112ae2c36468bd447732c5586b52e1ba3284a2319cadfac6367f99210279fd856e5ed13ab6807e85ed7c0cd6f80613be042240fd731c43f5aba3dcae9821021380858a67a4f99eda52ce2d72c300911f9d3eb9d7a45102a2133f14f7b2dc14210215739b613ce42106a11ce433342c13c610bf68a1bc934f607ad7aeb4178e04cf55ae2044d200000000001976a9146917322f0010aaf7ec136a34b476dfc5eb7a331288ac00000000'; +}; describe('Wallet model', function() { it('should create an instance', function () { - var w = new Wallet(); + var opts = {}; + var w = new Wallet(opts, config); should.exist(w); }); - it.skip('should return array of unspent output', function(done) { - var w = new Wallet(); - w.listUnspent(addresses, function(a) { - should.exist(a); - done(); - }); - }); - it('should return balance', function () { - var w = new Wallet(); - var b = w.getBalance(unspent); - should.exist(b); - b.should.equal(91); - }); - it.skip('should return txid', function (done) { - var w = new Wallet(); - w.sendRawTransaction(rawtx, function(a) { - should.exist(a); - done(); - }); - }); });