diff --git a/config.js b/config.js index ff0a27f19..a15369429 100644 --- a/config.js +++ b/config.js @@ -1,8 +1,12 @@ 'use strict'; var defaultConfig = { - // livenet or testnet + // DEFAULT network (livenet or testnet) networkName: 'testnet', + // DEFAULT unit: Bit + unitName: 'bits', + unitToSatoshi: 100, + // wallet limits limits: { totalCopayers: 12, @@ -21,7 +25,7 @@ var defaultConfig = { */ // Use this to connect to bitpay's PeerJS server - key: 'satoshirocks', + key: 'satoshirocks', host: '162.242.219.26', port: 10000, path: '/', @@ -107,7 +111,7 @@ var defaultConfig = { // local encryption/security config passphrase: { iterations: 100, - storageSalt: 'mjuBtGybi/4=', + storageSalt: 'mjuBtGybi/4=', }, // theme list diff --git a/css/main.css b/css/main.css index f2ff8044c..2ac4d575c 100644 --- a/css/main.css +++ b/css/main.css @@ -219,6 +219,21 @@ small.has-error { font-weight: bold; } + +.totalAmount { + line-height: 120%; + margin-top:2px; +} + + +/* Turn Off Number Input Spinners */ +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + + .small { font-size: 60%; line-height: inherit; diff --git a/index.html b/index.html index aeb16d94b..caf3c0eb1 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,7 @@
- + {{$root.wallet.getName()}}
- Balance: + Balance
- {{totalBalance || 0}} - + {{totalBalance || 0 |number}} {{$root.unitName}}
- Available to Spend: + Available to Spend
- {{availableBalance || 0}} - + {{availableBalance || 0|number}} {{$root.unitName}}
@@ -383,13 +381,11 @@ - {{$root.balanceByAddr[addr.address] || 0}} - + {{$root.balanceByAddr[addr.address] || 0|number}} {{$root.unitName}} - {{addr.balance || 0}} - + {{addr.balance || 0|number}} {{$root.unitName}} @@ -407,14 +403,12 @@ - {{balanceByAddr[selectedAddr.address] || 0}} - + {{balanceByAddr[selectedAddr.address] || 0 | number}} {{selectedAddr.address}}
- {{selectedAddr.balance || 0}} - + {{selectedAddr.balance || 0|number}} {{$root.unitName}}

@@ -445,7 +439,7 @@
-
{{out.value}}
+
{{out.value | number}} {{$root.unitName}}
{{out.address}}
@@ -527,7 +521,7 @@

{{tx.missingSignatures}} signatures missing

- Fee: {{tx.fee}} + Fee: {{tx.fee|number}} {{$root.unitName}} Proposal ID: {{tx.ntxid}}
@@ -571,7 +565,7 @@
- {{vin.value}} + {{vin.value| number}} {{$root.unitName}}

{{vin.addr}}

@@ -580,7 +574,7 @@
- {{vout.value}} + {{vout.value| number}} {{$root.unitName}}

{{vout.addr}}

@@ -588,9 +582,9 @@
-
Fees: {{btx.fees}}
+
Fees: {{btx.fees | number}} {{$root.unitName}}
Confirmations: {{btx.confirmations || 0}}
-
Total: {{btx.valueOut}}
+
Total: {{btx.valueOut| number}} {{$root.unitName}}
@@ -652,22 +646,38 @@
+ min="1" max="10000000000" enough-amount required + autocomplete="off" + >
- BTC + {{$root.unitName}}
+
+ + Total amount for this transaction: + +
+ {{amount + defaultFee |number}} {{$root.unitName}} + + {{ ((amount + defaultFee) * unitToBtc) |number}} BTC + +
+ + Including fee of {{defaultFee|number}} {{$root.unitName}} + +
@@ -680,7 +690,7 @@
+ name="comment" placeholder="Leave a private message to your copayers" ng-model="commentText" ng-maxlength="100">
@@ -740,6 +750,17 @@ +
+ Wallet Unit + + +
+
+ Videoconferencing + + +
Insight API server @@ -758,13 +779,6 @@
-
- Videoconferencing - - -
- -
diff --git a/js/controllers/header.js b/js/controllers/header.js index bbbbce675..708ac02f7 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -32,6 +32,7 @@ angular.module('copayApp.controllers').controller('HeaderController', } }); + $rootScope.unitName = config.unitName; // Initialize alert notification (not show when init wallet) $rootScope.txAlertCount = 0; diff --git a/js/controllers/import.js b/js/controllers/import.js index eb4c9b8f6..639f6a4a1 100644 --- a/js/controllers/import.js +++ b/js/controllers/import.js @@ -20,15 +20,7 @@ angular.module('copayApp.controllers').controller('ImportController', } $rootScope.wallet = w; - controllerUtils.startNetwork($rootScope.wallet); - $rootScope.wallet.on('connectionError', function() { - var message = "Looks like you are already connected to this wallet, please logout from it and try importing it again."; - $rootScope.$flashMessage = { message: message, type: 'error'}; - }); - $rootScope.wallet.on('serverError', function() { - $rootScope.$flashMessage = { message: 'The PeerJS server is not responding, please try again', type: 'error'}; - controllerUtils.onErrorDigest(); - }); + controllerUtils.startNetwork($rootScope.wallet, $scope); }); }; diff --git a/js/controllers/send.js b/js/controllers/send.js index 0e72e3e5e..8673d2107 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -1,29 +1,34 @@ 'use strict'; +var bitcore = require('bitcore'); angular.module('copayApp.controllers').controller('SendController', function($scope, $rootScope, $window, $location, $timeout) { $scope.title = 'Send'; $scope.loading = false; + var satToUnit = 1 / config.unitToSatoshi; + $scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit; + $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN; + // TODO this shouldnt be on a particular controller. // Detect mobile devices var isMobile = { Android: function() { - return navigator.userAgent.match(/Android/i); + return navigator.userAgent.match(/Android/i); }, BlackBerry: function() { - return navigator.userAgent.match(/BlackBerry/i); + return navigator.userAgent.match(/BlackBerry/i); }, iOS: function() { - return navigator.userAgent.match(/iPhone|iPad|iPod/i); + return navigator.userAgent.match(/iPhone|iPad|iPod/i); }, Opera: function() { - return navigator.userAgent.match(/Opera Mini/i); + return navigator.userAgent.match(/Opera Mini/i); }, Windows: function() { - return navigator.userAgent.match(/IEMobile/i); + return navigator.userAgent.match(/IEMobile/i); }, any: function() { - return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows()); + return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows()); } }; @@ -32,27 +37,31 @@ angular.module('copayApp.controllers').controller('SendController', navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; - $scope.isMobile = isMobile.any(); - $scope.unitIds = ['BTC','mBTC']; - $scope.selectedUnit = $scope.unitIds[0]; $scope.submitForm = function(form) { if (form.$invalid) { - $rootScope.$flashMessage = { message: 'You can not send a proposal transaction. Please, try again', type: 'error'}; + $rootScope.$flashMessage = { + message: 'You can not send a proposal transaction. Please, try again', + type: 'error' + }; return; } $scope.loading = true; var address = form.address.$modelValue; - var amount = (form.amount.$modelValue * 100000000).toFixed(); // satoshi to string - var comment = form.comment.$modelValue; + var amount = (form.amount.$modelValue * config.unitToSatoshi) | 0; + var commentText = form.comment.$modelValue; var w = $rootScope.wallet; - w.createTx(address, amount, comment, function() { + + w.createTx(address, amount, commentText, function() { $scope.loading = false; - $rootScope.$flashMessage = { message: 'The transaction proposal has been created', type: 'success'}; + $rootScope.$flashMessage = { + message: 'The transaction proposal has been created', + type: 'success' + }; $rootScope.$digest(); }); @@ -81,7 +90,11 @@ angular.module('copayApp.controllers').controller('SendController', reader.onload = (function(theFile) { return function(e) { var mpImg = new MegaPixImage(file); - mpImg.render(canvas, { maxWidth: 200, maxHeight: 200, orientation: 6 }); + mpImg.render(canvas, { + maxWidth: 200, + maxHeight: 200, + orientation: 6 + }); $timeout(function() { qrcode.width = canvas.width; @@ -107,7 +120,7 @@ angular.module('copayApp.controllers').controller('SendController', try { qrcode.decode(); - } catch(e) { + } catch (e) { //qrcodeError(e); } } @@ -168,7 +181,9 @@ angular.module('copayApp.controllers').controller('SendController', canvas.height = 225; context.clearRect(0, 0, 300, 225); - navigator.getUserMedia({video: true}, _successCallback, _videoError); + navigator.getUserMedia({ + video: true + }, _successCallback, _videoError); } }, 500); }; diff --git a/js/controllers/settings.js b/js/controllers/settings.js index 9c9f21824..78a5f5762 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -11,10 +11,35 @@ angular.module('copayApp.controllers').controller('SettingsController', $scope.networkHost = config.network.host; $scope.networkPort = config.network.port; $scope.networkSecure = config.network.secure || false; - $scope.disableVideo = config.disableVideo || true; + $scope.disableVideo = config.disableVideo || true; + + $scope.unitOpts = [{ + name: 'Satoshis (100,000,000 bits = 1BTC)', + shortName: 'SAT', + value: 1 + }, { + name: 'bits (1,000,000 bits = 1BTC)', + shortName: 'bits', + value: 100 + }, { + name: 'mBTC (1,000 mBTC = 1BTC)', + shortName: 'mBTC', + value: 100000 + }, { + name: 'BTC', + shortName: 'BTC', + value: 100000000 + }]; + + for (var ii in $scope.unitOpts) { + if (config.unitName === $scope.unitOpts[ii].shortName) { + $scope.selectedUnit = $scope.unitOpts[ii]; + break; + } + } $scope.$watch('networkName', function(net) { - $scope.insightHost = net === 'testnet' ? 'test.insight.is' : 'live.insight.is'; + $scope.insightHost = net === 'testnet' ? 'test.insight.is' : 'live.insight.is'; }); $scope.save = function() { @@ -25,20 +50,21 @@ angular.module('copayApp.controllers').controller('SettingsController', network.secure = $scope.networkSecure; localStorage.setItem('config', JSON.stringify({ - networkName: $scope.networkName, - blockchain: { - host: $scope.insightHost, - port: $scope.insightPort - }, - socket: { - host: $scope.insightHost, - port: $scope.insightPort - }, - network: network, - disableVideo: $scope.disableVideo, - }) - ); + networkName: $scope.networkName, + blockchain: { + host: $scope.insightHost, + port: $scope.insightPort + }, + socket: { + host: $scope.insightHost, + port: $scope.insightPort + }, + network: network, + disableVideo: $scope.disableVideo, + unitName: $scope.selectedUnit.shortName, + unitToSatoshi: $scope.selectedUnit.value, + })); - $window.location.href= $window.location.origin + $window.location.pathname; + $window.location.href = $window.location.origin + $window.location.pathname; }; }); diff --git a/js/controllers/setup.js b/js/controllers/setup.js index 4f49dcb96..bdc9d8a49 100644 --- a/js/controllers/setup.js +++ b/js/controllers/setup.js @@ -84,7 +84,7 @@ angular.module('copayApp.controllers').controller('SetupController', passphrase: passphrase, }; var w = walletFactory.create(opts); - controllerUtils.startNetwork(w); + controllerUtils.startNetwork(w, $scope); }); }; diff --git a/js/controllers/signin.js b/js/controllers/signin.js index 86468b1f1..2c522efba 100644 --- a/js/controllers/signin.js +++ b/js/controllers/signin.js @@ -34,8 +34,7 @@ angular.module('copayApp.controllers').controller('SigninController', $rootScope.$digest(); return; } - installStartupHandlers(w); - controllerUtils.startNetwork(w); + controllerUtils.startNetwork(w, $scope); }); }; @@ -65,25 +64,9 @@ angular.module('copayApp.controllers').controller('SigninController', $rootScope.$flashMessage = { message: 'Unknown error', type: 'error'}; controllerUtils.onErrorDigest(); } else { - controllerUtils.startNetwork(w); - installStartupHandlers(w); + controllerUtils.startNetwork(w, $scope); } }); }); - }; - - function installStartupHandlers(wallet) { - wallet.on('serverError', function(msg) { - $rootScope.$flashMessage = { - message: 'There was an error connecting to the PeerJS server.' - +(msg||'Check you settings and Internet connection.'), - type: 'error', - }; - controllerUtils.onErrorDigest($scope); - }); - wallet.on('ready', function() { - $scope.loading = false; - }); } - }); diff --git a/js/controllers/transactions.js b/js/controllers/transactions.js index 744e64aaf..1b8154d54 100644 --- a/js/controllers/transactions.js +++ b/js/controllers/transactions.js @@ -1,4 +1,5 @@ 'use strict'; +var bitcore = require('bitcore'); angular.module('copayApp.controllers').controller('TransactionsController', function($scope, $rootScope, $timeout, controllerUtils) { @@ -10,13 +11,13 @@ angular.module('copayApp.controllers').controller('TransactionsController', $scope.txpCurrentPage = 1; $scope.txpItemsPerPage = 4; - - var COIN = 100000000; $scope.blockchain_txs = []; - $scope.update = function () { + var satToUnit = 1 / config.unitToSatoshi; + + $scope.update = function() { $scope.loading = false; - var from = ($scope.txpCurrentPage-1) * $scope.txpItemsPerPage; + var from = ($scope.txpCurrentPage - 1) * $scope.txpItemsPerPage; var opts = { onlyPending: $scope.onlyPending, skip: !$scope.onlyPending ? [from, from + $scope.txpItemsPerPage] : null @@ -25,10 +26,10 @@ angular.module('copayApp.controllers').controller('TransactionsController', $rootScope.$digest(); }; - $scope.show = function (onlyPending) { - $scope.loading=true; + $scope.show = function(onlyPending) { + $scope.loading = true; $scope.onlyPending = onlyPending; - setTimeout(function(){ + setTimeout(function() { $scope.update(); }, 10); }; @@ -42,19 +43,19 @@ angular.module('copayApp.controllers').controller('TransactionsController', var tmp = {}; var u = 0; - for(var i=0; i < l; i++) { + for (var i = 0; i < l; i++) { var notAddr = false; // non standard input if (items[i].scriptSig && !items[i].addr) { - items[i].addr = 'Unparsed address [' + u++ + ']'; + items[i].addr = 'Unparsed address [' + u+++']'; items[i].notAddr = true; notAddr = true; } // non standard output if (items[i].scriptPubKey && !items[i].scriptPubKey.addresses) { - items[i].scriptPubKey.addresses = ['Unparsed address [' + u++ + ']']; + items[i].scriptPubKey.addresses = ['Unparsed address [' + u+++']']; items[i].notAddr = true; notAddr = true; } @@ -77,62 +78,64 @@ angular.module('copayApp.controllers').controller('TransactionsController', } tmp[addr].isSpent = items[i].spentTxId; - tmp[addr].doubleSpentTxID = tmp[addr].doubleSpentTxID || items[i].doubleSpentTxID; + tmp[addr].doubleSpentTxID = tmp[addr].doubleSpentTxID || items[i].doubleSpentTxID; tmp[addr].doubleSpentIndex = tmp[addr].doubleSpentIndex || items[i].doubleSpentIndex; tmp[addr].unconfirmedInput += items[i].unconfirmedInput; tmp[addr].dbError = tmp[addr].dbError || items[i].dbError; - tmp[addr].valueSat += Math.round(items[i].value * COIN); + tmp[addr].valueSat += parseInt((items[i].value * bitcore.util.COIN).toFixed(0)); tmp[addr].items.push(items[i]); tmp[addr].notAddr = notAddr; tmp[addr].count++; } angular.forEach(tmp, function(v) { - v.value = v.value || parseInt(v.valueSat) / COIN; + v.value = (parseInt(v.valueSat || 0).toFixed(0)) * satToUnit; ret.push(v); }); return ret; }; - $scope.toogleLast = function () { + $scope.toogleLast = function() { $scope.lastShowed = !$scope.lastShowed; if ($scope.lastShowed) { $scope.getTransactions(); } }; - $scope.send = function (ntxid,cb) { + $scope.send = function(ntxid, cb) { $scope.loading = true; $rootScope.txAlertCount = 0; var w = $rootScope.wallet; w.sendTx(ntxid, function(txid) { - $rootScope.$flashMessage = txid - ? {type:'success', message: 'Transaction broadcasted. txid: ' + txid} - : {type:'error', message: 'There was an error sending the Transaction'} - ; - if (cb) return cb(); - else $scope.update(); + $rootScope.$flashMessage = txid ? { + type: 'success', + message: 'Transaction broadcasted. txid: ' + txid + } : { + type: 'error', + message: 'There was an error sending the Transaction' + }; + if (cb) return cb(); + else $scope.update(); }); }; - $scope.sign = function (ntxid) { + $scope.sign = function(ntxid) { $scope.loading = true; var w = $rootScope.wallet; - w.sign(ntxid, function(ret){ + w.sign(ntxid, function(ret) { if (!ret) { $rootScope.$flashMessage = { - type:'error', + type: 'error', message: 'There was an error signing the Transaction', }; - $scope.update(); + $scope.update(); } else { var p = w.txProposals.getTxProposal(ntxid); if (p.builder.isFullySigned()) { $scope.send(ntxid, function() { $scope.update(); }); - } - else + } else $scope.update(); } }); @@ -145,18 +148,19 @@ angular.module('copayApp.controllers').controller('TransactionsController', var addresses = w.getAddressesStr(); if (addresses.length > 0) { $scope.blockchain_txs = []; - w.blockchain.getTransactions(addresses, function(txs) { + w.blockchain.getTransactions(addresses, function(txs) { $timeout(function() { - for (var i=0; i 0) { if (availableBalanceNum < vNum) { ctrl.$setValidity('enoughAmount', false); @@ -72,7 +72,7 @@ angular.module('copayApp.directives') require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { - ctrl.$setValidity('walletSecret', Boolean(walletFactory.decodeSecret(value))); + ctrl.$setValidity('walletSecret', Boolean(walletFactory.decodeSecret(value))); return value; }; @@ -88,7 +88,7 @@ angular.module('copayApp.directives') var a = element.html(); var text = attr.loading; element.on('click', function() { - element.html(' ' + text + '...'); + element.html(' ' + text + '...'); }); $scope.$watch('loading', function(val) { if (!val) { @@ -126,9 +126,11 @@ angular.module('copayApp.directives') return { restrict: 'A', link: function(scope, element, attrs) { - scope.$watch(attrs.highlightOnChange, function (newValue, oldValue) { + scope.$watch(attrs.highlightOnChange, function(newValue, oldValue) { element.addClass('highlight'); - setTimeout(function() { element.removeClass('highlight'); }, 500); + setTimeout(function() { + element.removeClass('highlight'); + }, 500); }); } } @@ -142,7 +144,7 @@ angular.module('copayApp.directives') var strength = { messages: ['very weak', 'weak', 'weak', 'medium', 'strong'], colors: ['#c0392b', '#e74c3c', '#d35400', '#f39c12', '#27ae60'], - mesureStrength: function (p) { + mesureStrength: function(p) { var force = 0; var regex = /[$-/:-?{-~!"^_`\[\]]/g; var lowerLetters = /[a-z]+/.test(p); @@ -150,37 +152,51 @@ angular.module('copayApp.directives') var numbers = /[0-9]+/.test(p); var symbols = regex.test(p); var flags = [lowerLetters, upperLetters, numbers, symbols]; - var passedMatches = flags.filter(function (el) { return !!el; }).length; - + var passedMatches = flags.filter(function(el) { + return !!el; + }).length; + force = 2 * p.length + (p.length >= 10 ? 1 : 0); force += passedMatches * 10; - + // penality (short password) force = (p.length <= 6) ? Math.min(force, 10) : force; - + // penality (poor variety of characters) force = (passedMatches == 1) ? Math.min(force, 10) : force; force = (passedMatches == 2) ? Math.min(force, 20) : force; force = (passedMatches == 3) ? Math.min(force, 40) : force; return force; }, - getColor: function (s) { + getColor: function(s) { var idx = 0; - - if (s <= 10) { idx = 0; } - else if (s <= 20) { idx = 1; } - else if (s <= 30) { idx = 2; } - else if (s <= 40) { idx = 3; } - else { idx = 4; } - - return { idx: idx + 1, col: this.colors[idx], message: this.messages[idx] }; + + if (s <= 10) { + idx = 0; + } else if (s <= 20) { + idx = 1; + } else if (s <= 30) { + idx = 2; + } else if (s <= 40) { + idx = 3; + } else { + idx = 4; + } + + return { + idx: idx + 1, + col: this.colors[idx], + message: this.messages[idx] + }; } }; - scope.$watch(attrs.ngModel, function (newValue, oldValue) { + scope.$watch(attrs.ngModel, function(newValue, oldValue) { if (newValue && newValue !== '') { var c = strength.getColor(strength.mesureStrength(newValue)); - element.css({ 'border-color': c.col }); + element.css({ + 'border-color': c.col + }); scope[attrs.checkStrength] = c.message; } }); diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 92f31bc47..897dd22c8 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -183,34 +183,35 @@ Insight.prototype._request = function(options, callback) { request.timeout = 5000; request.ontimeout = function() { setTimeout(function() { - return self._request(options,callback); + return self._request(options, callback); }, self.retryDelay); return callback(new Error('Insight request timeout')); }; request.onreadystatechange = function() { - if (request.readyState === 4) { - if (request.status === 200 || request.status === 304) { - try { - var ret = JSON.parse(request.responseText); - return callback(null, ret); - } catch (e) { - return callback(new Error('CRITICAL: Wrong response from insight')); - } - } - // User error - else if (request.status >= 400 && request.status < 499) { - return callback(new Error('CRITICAL: Bad request to insight. Probably wrong transaction to broadcast?.')); - } - else { - var err= 'Error code: ' + request.status + ' - Status: ' + request.statusText - + ' - Description: ' + request.responseText; - setTimeout(function() { - return self._request(options,callback); - }, self.retryDelay); - return callback(new Error(err)); + if (request.readyState !== 4) return; + var ret, errTxt, e; + + if (request.status === 200 || request.status === 304) { + try { + ret = JSON.parse(request.responseText); + } catch (e2) { + errTxt = 'CRITICAL: Wrong response from insight' + e2; } + } else if (request.status >= 400 && request.status < 499) { + errTxt = 'CRITICAL: Bad request to insight. Probably wrong transaction to broadcast?.'; + } else { + errTxt = 'Error code: ' + request.status + ' - Status: ' + request.statusText + ' - Description: ' + request.responseText; + setTimeout(function() { + console.log('### Retrying Insight Request....'); + return self._request(options, callback); + }, self.retryDelay); } + if (errTxt) { + console.log("INSIGHT ERROR:", e); + e = new Error(errTxt); + } + return callback(e, ret); }; if (options.method === 'POST') { @@ -218,9 +219,7 @@ Insight.prototype._request = function(options, callback) { } request.send(options.data || null); - } - - else { + } else { var http = require('http'); var req = http.request(options, function(response) { var ret; diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index f25b2b9a7..4bc4f2fa9 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -1,22 +1,21 @@ - 'use strict'; -var imports = require('soop').imports(); -var bitcore = require('bitcore'); -var util = bitcore.util; +var imports = require('soop').imports(); +var bitcore = require('bitcore'); +var util = bitcore.util; var Transaction = bitcore.Transaction; -var Builder = bitcore.TransactionBuilder; -var Script = bitcore.Script; +var Builder = bitcore.TransactionBuilder; +var Script = bitcore.Script; var buffertools = bitcore.buffertools; function TxProposal(opts) { - this.creator = opts.creator; - this.createdTs = opts.createdTs; - this.seenBy = opts.seenBy || {}; + this.creator = opts.creator; + this.createdTs = opts.createdTs; + this.seenBy = opts.seenBy || {}; this.signedBy = opts.signedBy || {}; this.rejectedBy = opts.rejectedBy || {}; - this.builder = opts.builder; + this.builder = opts.builder; this.sentTs = opts.sentTs || null; this.sentTxid = opts.sentTxid || null; this.inputChainPaths = opts.inputChainPaths || []; @@ -53,8 +52,8 @@ module.exports = require('soop')(TxProposal); function TxProposals(opts) { opts = opts || {}; this.walletId = opts.walletId; - this.network = opts.networkName === 'livenet' ? - bitcore.networks.livenet : bitcore.networks.testnet; + this.network = opts.networkName === 'livenet' ? + bitcore.networks.livenet : bitcore.networks.testnet; this.txps = {}; } @@ -77,7 +76,7 @@ TxProposals.prototype.getNtxids = function() { TxProposals.prototype.toObj = function(onlyThisNtxid) { var ret = []; - for(var id in this.txps){ + for (var id in this.txps) { if (onlyThisNtxid && id != onlyThisNtxid) continue; @@ -86,31 +85,33 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) { if (!t.sent) ret.push(t.toObj()); } - return { - txps: ret, + return { + txps: ret, walletId: this.walletId, networkName: this.network.name, }; }; TxProposals.prototype._startMerge = function(myTxps, theirTxps) { - var fromUs=0, fromTheirs=0, merged =0; - var toMerge = {}, ready={}; + var fromUs = 0, + fromTheirs = 0, + merged = 0; + var toMerge = {}, + ready = {}; - for(var hash in theirTxps){ + for (var hash in theirTxps) { if (!myTxps[hash]) { - ready[hash]=theirTxps[hash]; // only in theirs; + ready[hash] = theirTxps[hash]; // only in theirs; fromTheirs++; - } - else { - toMerge[hash]=theirTxps[hash]; // need Merging + } else { + toMerge[hash] = theirTxps[hash]; // need Merging merged++; } } - for(var hash in myTxps){ - if(!toMerge[hash]) { - ready[hash]=myTxps[hash]; // only in myTxps; + for (var hash in myTxps) { + if (!toMerge[hash]) { + ready[hash] = myTxps[hash]; // only in myTxps; fromUs++; } } @@ -130,7 +131,7 @@ TxProposals.prototype._startMerge = function(myTxps, theirTxps) { TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) { var toMerge = mergeInfo.toMerge; - var hasChanged =0; + var hasChanged = 0; Object.keys(toMerge).forEach(function(hash) { var v0 = myTxps[hash]; @@ -158,7 +159,7 @@ TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) { }); if (!v0.sentTxid && v1.sentTxid) { - v0.sentTs = v1.sentTs; + v0.sentTs = v1.sentTs; v0.sentTxid = v1.sentTxid; hasChanged++; } @@ -170,9 +171,9 @@ TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) { TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) { var toMerge = mergeInfo.toMerge; - var hasChanged=0; + var hasChanged = 0; - for(var hash in toMerge){ + for (var hash in toMerge) { var v0 = myTxps[hash].builder; var v1 = toMerge[hash].builder; @@ -180,7 +181,7 @@ TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) { var before = JSON.stringify(v0.toObj()); v0.merge(v1); var after = JSON.stringify(v0.toObj()); - if (after !== before) hasChanged ++; + if (after !== before) hasChanged++; } }; @@ -191,7 +192,7 @@ TxProposals.prototype.add = function(data) { }; -TxProposals.prototype.setSent = function(ntxid,txid) { +TxProposals.prototype.setSent = function(ntxid, txid) { //sent TxProposals are local an not broadcasted. this.txps[ntxid].setSent(txid); }; @@ -205,26 +206,28 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) { i.peerActions = {}; if (copayers) { - for(var j=0; j < copayers.length; j++) { + for (var j = 0; j < copayers.length; j++) { var p = copayers[j]; i.peerActions[p] = {}; } } - for(var p in txp.seenBy){ - i.peerActions[p]={seen: txp.seenBy[p]}; + for (var p in txp.seenBy) { + i.peerActions[p] = { + seen: txp.seenBy[p] + }; } - for(var p in txp.signedBy){ - i.peerActions[p]= i.peerActions[p] || {}; + for (var p in txp.signedBy) { + i.peerActions[p] = i.peerActions[p] || {}; i.peerActions[p].sign = txp.signedBy[p]; } - var r=0; - for(var p in txp.rejectedBy){ - i.peerActions[p]= i.peerActions[p] || {}; + var r = 0; + for (var p in txp.rejectedBy) { + i.peerActions[p] = i.peerActions[p] || {}; i.peerActions[p].rejected = txp.rejectedBy[p]; r++; } - i.rejectCount=r; + i.rejectCount = r; var c = txp.creator; i.peerActions[c] = i.peerActions[c] || {}; @@ -235,30 +238,30 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) { //returns the unspent txid-vout used in PENDING Txs TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { var ret = {}; - for(var i in this.txps) { + for (var i in this.txps) { var u = this.txps[i].builder.getSelectedUnspent(); var p = this.getTxProposal(i); - if (p.rejectCount>maxRejectCount || p.sentTxid) + if (p.rejectCount > maxRejectCount || p.sentTxid) continue; for (var j in u) { - ret[u[j].txid + ',' + u[j].vout]=1; + ret[u[j].txid + ',' + u[j].vout] = 1; } } return ret; }; TxProposals.prototype.merge = function(t) { - if (this.network.name !== t.network.name) + if (this.network.name !== t.network.name) throw new Error('network mismatch in:', t); var res = []; var hasChanged = 0; - var myTxps = this.txps; - var theirTxps = t.txps; + var myTxps = this.txps; + var theirTxps = t.txps; - var mergeInfo = this._startMerge(myTxps, theirTxps); + var mergeInfo = this._startMerge(myTxps, theirTxps); hasChanged += this._mergeMetadata(myTxps, theirTxps, mergeInfo); hasChanged += this._mergeBuilder(myTxps, theirTxps, mergeInfo); @@ -268,7 +271,7 @@ TxProposals.prototype.merge = function(t) { mergeInfo.stats.hasChanged = hasChanged; - this.txps=mergeInfo.ready; + this.txps = mergeInfo.ready; return mergeInfo.stats; }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 6c77a9620..4dfd7708d 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -89,9 +89,9 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { var wasIncomplete = !this.publicKeyRing.isComplete(); var hasChanged; - try{ + try { hasChanged = this.publicKeyRing.merge(inPKR, true); - } catch (e){ + } catch (e) { this.log('## WALLET ERROR', e); //TODO this.emit('connectionError', e.message); return; @@ -140,7 +140,7 @@ Wallet.prototype._handleTxProposals = function(senderId, data, isInbound) { Wallet.prototype._handleData = function(senderId, data, isInbound) { // TODO check message signature - + if (data.type !== 'walletId' && this.id !== data.walletId) { this.emit('badMessage', senderId); this.log('badMessage FROM:', senderId); //TODO @@ -281,7 +281,7 @@ Wallet.prototype.scheduleConnect = function() { var self = this; if (self.network.isOnline()) { self.connectToAll(); - self.currentDelay = self.currentDelay*2 || self.reconnectDelay; + self.currentDelay = self.currentDelay * 2 || self.reconnectDelay; setTimeout(self.scheduleConnect.bind(self), self.currentDelay); } } @@ -329,7 +329,7 @@ Wallet.prototype.toObj = function() { opts: optsObj, publicKeyRing: this.publicKeyRing.toObj(), txProposals: this.txProposals.toObj(), - privateKey: this.privateKey?this.privateKey.toObj():undefined + privateKey: this.privateKey ? this.privateKey.toObj() : undefined }; return walletObj; @@ -454,7 +454,7 @@ Wallet.prototype.reject = function(ntxid) { var myId = this.getMyCopayerId(); var txp = this.txProposals.txps[ntxid]; if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { - throw new Error('Invalid transaction to reject: '+ntxid); + throw new Error('Invalid transaction to reject: ' + ntxid); } txp.rejectedBy[myId] = Date.now(); @@ -562,11 +562,12 @@ Wallet.prototype.addressIsOwn = function(addrStr, opts) { return ret; }; +//retunrs values in SATOSHIs Wallet.prototype.getBalance = function(cb) { var balance = 0; var safeBalance = 0; var balanceByAddr = {}; - var COIN = bitcore.util.COIN; + var COIN = coinUtil.COIN; this.getUnspent(function(err, safeUnspent, unspent) { if (err) { @@ -580,11 +581,12 @@ Wallet.prototype.getBalance = function(cb) { balanceByAddr[u.address] = (balanceByAddr[u.address] || 0) + amt; } - // we multiply and divide by COIN to avoid rounding errors when adding + // we multiply and divide by BIT to avoid rounding errors when adding for (var a in balanceByAddr) { - balanceByAddr[a] = balanceByAddr[a].toFixed(0) / COIN; + balanceByAddr[a] = parseInt(balanceByAddr[a].toFixed(0)); } - balance = balance / COIN; + + balance = parseInt(balance.toFixed(0)); for (var i = 0; i < safeUnspent.length; i++) { var u = safeUnspent[i]; @@ -592,7 +594,7 @@ Wallet.prototype.getBalance = function(cb) { safeBalance += amt; } - safeBalance = safeBalance.toFixed(0) / COIN; + safeBalance = parseInt(safeBalance.toFixed(0)); return cb(null, balance, balanceByAddr, safeBalance); }); }; @@ -610,8 +612,8 @@ Wallet.prototype.getUnspent = function(cb) { var uu = self.txProposals.getUsedUnspent(maxRejectCount); for (var i in unspentList) { - var u=unspentList[i]; - if (! uu[u.txid +','+u.vout]) + var u = unspentList[i]; + if (!uu[u.txid + ',' + u.vout]) safeUnspendList.push(u); } diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index d0ce6bfbf..a512d5fd6 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -1,9 +1,9 @@ 'use strict'; +var bitcore = require('bitcore'); angular.module('copayApp.services') - .factory('controllerUtils', function($rootScope, $sce, $location, $notification, Socket, video) { + .factory('controllerUtils', function($rootScope, $sce, $location, $notification, $timeout, Socket, video) { var root = {}; - var bitcore = require('bitcore'); root.getVideoMutedStatus = function(copayer) { var vi = $rootScope.videoInfo[copayer] @@ -36,12 +36,45 @@ angular.module('copayApp.services') root.onError(scope); if (msg) $rootScope.$flashMessage = { type: 'error', - message: msg + message: msg }; $rootScope.$digest(); - } + }; + + root.installStartupHandlers = function(wallet, $scope) { + wallet.on('serverError', function(msg) { + $rootScope.$flashMessage = { + message: 'There was an error connecting to the PeerJS server.' + (msg || 'Check you settings and Internet connection.'), + type: 'error', + }; + root.onErrorDigest($scope); + $location.path('addresses'); + }); + wallet.on('connectionError', function() { + var message = "Looks like you are already connected to this wallet, please logout from it and try importing it again."; + $rootScope.$flashMessage = { + message: message, + type: 'error' + }; + root.onErrorDigest($scope); + }); + wallet.on('serverError', function() { + $rootScope.$flashMessage = { + message: 'The PeerJS server is not responding, please try again', + type: 'error' + }; + root.onErrorDigest($scope); + }); + wallet.on('ready', function() { + $scope.loading = false; + }); + }; + + + root.startNetwork = function(w, $scope) { + + root.installStartupHandlers(w, $scope); - root.startNetwork = function(w) { var handlePeerVideo = function(err, peerID, url) { if (err) { delete $rootScope.videoInfo[peerID]; @@ -77,12 +110,17 @@ angular.module('copayApp.services') } }); w.on('txProposalsUpdated', function(dontDigest) { - root.updateTxs({onlyPending:true}); - root.updateBalance(function(){ - if (!dontDigest) { - $rootScope.$digest(); - } + root.updateTxs({ + onlyPending: true }); + // give sometime to the tx to propagate. + $timeout(function() { + root.updateBalance(function() { + if (!dontDigest) { + $rootScope.$digest(); + } + }); + }, 3000); }); w.on('connectionError', function(msg) { root.onErrorDigest(null, msg); @@ -109,25 +147,33 @@ angular.module('copayApp.services') var w = $rootScope.wallet; if (!w) return root.onErrorDigest(); - $rootScope.balanceByAddr = {}; $rootScope.updatingBalance = true; - w.getBalance(function(err, balance, balanceByAddr, safeBalance) { + + w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) { if (err) { console.error('Error: ' + err.message); //TODO root._setCommError(); return null; - } - else { + } else { root._clearCommError(); } - - $rootScope.totalBalance = balance; + + var satToUnit = 1 / config.unitToSatoshi; + var COIN = bitcore.util.COIN; + + $rootScope.totalBalance = balanceSat * satToUnit; + $rootScope.totalBalanceBTC = (balanceSat / COIN).toFixed(4); + $rootScope.availableBalance = safeBalanceSat * satToUnit; + $rootScope.availableBalanceBTC = (safeBalanceSat / COIN).toFixed(4); + var balanceByAddr = {}; + for (var ii in balanceByAddrSat) { + balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit; + } $rootScope.balanceByAddr = balanceByAddr; - $rootScope.availableBalance = safeBalance; root.updateAddressList(); $rootScope.updatingBalance = false; - return cb?cb():null; + return cb ? cb() : null; }); }; @@ -135,13 +181,16 @@ angular.module('copayApp.services') var w = $rootScope.wallet; if (!w) return; opts = opts || {}; - + + var satToUnit = 1 / config.unitToSatoshi; var myCopayerId = w.getMyCopayerId(); var pendingForUs = 0; - var inT = w.getTxProposals().sort(function(t1, t2) { return t2.createdTs - t1.createdTs }); - var txs = []; + var inT = w.getTxProposals().sort(function(t1, t2) { + return t2.createdTs - t1.createdTs + }); + var txs = []; - inT.forEach(function(i, index){ + inT.forEach(function(i, index) { if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) { return txs.push(null); } @@ -150,47 +199,49 @@ angular.module('copayApp.services') pendingForUs++; } if (!i.finallyRejected && !i.sentTs) { - i.isPending=1; + i.isPending = 1; } if (!opts.onlyPending || i.isPending) { - var tx = i.builder.build(); + var tx = i.builder.build(); var outs = []; tx.outs.forEach(function(o) { var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString(); - if (!w.addressIsOwn(addr, {excludeMain:true})) { + if (!w.addressIsOwn(addr, { + excludeMain: true + })) { outs.push({ - address: addr, - value: bitcore.util.valueToBigInt(o.getValue())/bitcore.util.COIN, + address: addr, + value: bitcore.util.valueToBigInt(o.getValue()) * satToUnit, }); } }); // extra fields i.outs = outs; - i.fee = i.builder.feeSat/bitcore.util.COIN; + i.fee = i.builder.feeSat * satToUnit; i.missingSignatures = tx.countInputMissingSignatures(0); txs.push(i); } }); - + $rootScope.txs = txs; //.some(function(i) {return i.isPending; } ); if ($rootScope.pendingTxCount < pendingForUs) { $rootScope.txAlertCount = pendingForUs; } $rootScope.pendingTxCount = pendingForUs; - }; + }; root._setCommError = function(e) { - if ($rootScope.insightError<0) - $rootScope.insightError=0; + if ($rootScope.insightError < 0) + $rootScope.insightError = 0; $rootScope.insightError++; }; root._clearCommError = function(e) { - if ($rootScope.insightError>0) - $rootScope.insightError=-1; + if ($rootScope.insightError > 0) + $rootScope.insightError = -1; else - $rootScope.insightError=0; + $rootScope.insightError = 0; }; root.setSocketHandlers = function() { @@ -200,16 +251,16 @@ angular.module('copayApp.services') Socket.sysOn('reconnect_failed', root._setCommError); Socket.sysOn('connect', root._clearCommError); Socket.sysOn('reconnect', root._clearCommError); - Socket.sysEventsSet=true; + Socket.sysEventsSet = true; } if (!$rootScope.wallet) return; - var currentAddrs= Socket.getListeners(); + var currentAddrs = Socket.getListeners(); var addrs = $rootScope.wallet.getAddressesStr(); - - var newAddrs=[]; - for(var i in addrs){ - var a=addrs[i]; + + var newAddrs = []; + for (var i in addrs) { + var a = addrs[i]; if (!currentAddrs[a]) newAddrs.push(a); } @@ -219,7 +270,7 @@ angular.module('copayApp.services') newAddrs.forEach(function(addr) { Socket.on(addr, function(txid) { $rootScope.receivedFund = [txid, addr]; - root.updateBalance(function(){ + root.updateBalance(function() { $rootScope.$digest(); }); }); diff --git a/karma.conf.js b/karma.conf.js index 89e521f10..077882857 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -43,11 +43,11 @@ module.exports = function(config) { 'lib/chai/chai.js', 'test/lib/chai-should.js', 'test/lib/chai-expect.js', + 'test/mocks/FakeWallet.js', //Mocha stuff 'test/mocha.conf.js', - //App-specific Code 'js/app.js', 'js/routes.js', diff --git a/test/index.html b/test/index.html index 0e3170129..52dab4944 100644 --- a/test/index.html +++ b/test/index.html @@ -18,7 +18,6 @@ var copay = require('copay'); - @@ -26,6 +25,7 @@ +