From 8032bae4314453c0f80b67f7fe4a8fe26fcf6b5e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 28 Nov 2014 13:10:15 -0300 Subject: [PATCH 01/30] debugging --- js/models/Async.js | 2 ++ js/models/Identity.js | 3 +++ js/models/Insight.js | 7 +++++- js/models/PublicKeyRing.js | 43 ++++++++++++++++++++++++++++------ js/models/Wallet.js | 8 ++++--- js/services/controllerUtils.js | 20 +++++++++++++--- js/services/identityService.js | 2 ++ views/home.html | 1 + 8 files changed, 72 insertions(+), 14 deletions(-) diff --git a/js/models/Async.js b/js/models/Async.js index b7b9bca33..ed0212332 100644 --- a/js/models/Async.js +++ b/js/models/Async.js @@ -288,6 +288,8 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) { self.socket.emit('subscribe', pubkey); }); + log.debug('Async subs done'); + if (typeof cb === 'function') cb(); }); diff --git a/js/models/Identity.js b/js/models/Identity.js index fbb5c8783..5b4fa72d3 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -133,9 +133,12 @@ Identity.createFromPartialJson = function(jsonString, opts, callback) { async.map(exported.walletIds, function(walletId, callback) { identity.retrieveWalletFromStorage(walletId, {}, function(error, wallet) { if (!error) { + +console.log('[Identity.js.136] GOT:', wallet.getName()); //TODO identity.wallets[wallet.getId()] = wallet; identity.bindWallet(wallet); wallet.netStart(); +console.log('[Identity.js.136] STARTED:', wallet.getName()); //TODO } callback(error, wallet); }); diff --git a/js/models/Insight.js b/js/models/Insight.js index 7d187cb2d..0dd6182a2 100644 --- a/js/models/Insight.js +++ b/js/models/Insight.js @@ -200,6 +200,8 @@ Insight.prototype.subscribe = function(addresses) { addresses = Array.isArray(addresses) ? addresses : [addresses]; var self = this; +console.log('[Insight.js.202] subscribe STARTED'); //TODO + function handlerFor(self, address) { return function(txid) { // verify the address is still subscribed @@ -220,12 +222,15 @@ Insight.prototype.subscribe = function(addresses) { if (!self.subscribed[address]) { var handler = handlerFor(self, address); self.subscribed[address] = handler; - log.debug('Subcribe to: ', address); + log.debug('Subscribe to: ', address); s.emit('subscribe', address); s.on(address, handler); } }); + + +console.log('[Insight.js.202] subscribe ENDED'); //TODO }; Insight.prototype.getSubscriptions = function(addresses) { diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js index 10fa59e4e..0b8e628f8 100644 --- a/js/models/PublicKeyRing.js +++ b/js/models/PublicKeyRing.js @@ -85,22 +85,30 @@ PublicKeyRing.trim = function(data) { * @param {object} data - a serialized version of PublicKeyRing {@see PublicKeyRing#trim} * @return {PublicKeyRing} - the deserialized object */ -PublicKeyRing.fromObj = function(data) { - preconditions.checkArgument(!(data instanceof PublicKeyRing), 'bad data format: Did you use .toObj()?'); - var opts = PublicKeyRing.trim(data); +PublicKeyRing.fromObj = function(opts) { + preconditions.checkArgument(!(opts instanceof PublicKeyRing), 'bad opts format: Did you use .toObj()?'); // Support old indexes schema if (!Array.isArray(opts.indexes)) { opts.indexes = HDParams.update(opts.indexes, opts.totalCopayers); } - var ret = new PublicKeyRing(opts); + var pkr = new PublicKeyRing(opts); for (var k in opts.copayersExtPubKeys) { - ret.addCopayer(opts.copayersExtPubKeys[k]); + pkr.addCopayer(opts.copayersExtPubKeys[k]); } - return ret; + if (opts._cache){ + log.debug('PublicKeyRing: Using address cache'); + pkr._cacheAddressMap = opts._cache; + } + + return pkr; +}; + +PublicKeyRing.fromUntrustedObj = function(opts) { + return PublicKeyRing.fromObj(PublicKeyRing.trim(opts)); }; /** @@ -120,10 +128,17 @@ PublicKeyRing.prototype.toObj = function() { copayersExtPubKeys: this.copayersHK.map(function(b) { return b.extendedPublicKeyString(); }), - nicknameFor: this.nicknameFor + nicknameFor: this.nicknameFor, + _cache: this._cacheAddressMap }; }; + +PublicKeyRing.prototype.toTrimmedObj = function() { + return PublicKeyRing.trim(this.toObj()); +} + + /** * @desc * Retrieve a copayer's public key as a hexadecimal encoded string @@ -335,6 +350,8 @@ PublicKeyRing.prototype.getRedeemScript = function(index, isChange, copayerIndex PublicKeyRing.prototype.getAddress = function(index, isChange, id) { var copayerIndex = this.getCosigner(id); if (!this._cachedAddress(index, isChange, id)) { + +console.log('[PublicKeyRing.js.338] CACHE MISS'); //TODO var script = this.getRedeemScript(index, isChange, copayerIndex); var address = Address.fromScript(script, this.network.name); this.addressToPath[address.toString()] = HDPath.FullBranch(index, isChange, copayerIndex); @@ -472,12 +489,16 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { */ PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) { +console.log('[PublicKeyRing.js.474] STARTED'); //TODO var ret = []; var self = this; var copayerIndex = pubkey && this.getCosigner(pubkey); +console.log('[PublicKeyRing.js.478:copayerIndex:]',copayerIndex); //TODO this.indexes.forEach(function(index) { +console.log('[PublicKeyRing.js.479:index:]',index); //TODO ret = ret.concat(self.getAddressesInfoForIndex(index, opts, copayerIndex)); }); +console.log('[PublicKeyRing.js.474] END'); //TODO return ret; }; @@ -511,15 +532,23 @@ PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayer isChange: isChange, owned: isOwned }); + +console.log('[PublicKeyRing.js.518] Appending address'); //TODO }; + console.log('[PublicKeyRing.js.519] getAddressesInfoForIndex'); //TODO for (var i = 0; !opts.excludeChange && i < index.changeIndex; i++) { appendAddressInfo(this.getAddress(i, true, index.copayerIndex), true); } + +console.log('[PublicKeyRing.js.526]'); //TODO for (var i = 0; !opts.excludeMain && i < index.receiveIndex; i++) { appendAddressInfo(this.getAddress(i, false, index.copayerIndex), false); } + +console.log('[PublicKeyRing.js.534] CACHE IS' , this._cacheAddressMap); //TODO + return ret; }; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 43ea774a4..5cc85bdee 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -314,7 +314,7 @@ Wallet.prototype.changeSettings = function(settings) { Wallet.prototype._onPublicKeyRing = function(senderId, data) { log.debug('Wallet:' + this.id + ' RECV PUBLICKEYRING:', data); - var inPKR = PublicKeyRing.fromObj(data.publicKeyRing); + var inPKR = PublicKeyRing.fromUntrustedObj(data.publicKeyRing); var wasIncomplete = !this.publicKeyRing.isComplete(); var hasChanged; @@ -886,6 +886,8 @@ Wallet.prototype._lockIncomming = function() { }; Wallet.prototype._setBlockchainListeners = function() { + +console.log('[Wallet.js.889] address'); //TODO var self = this; self.blockchain.removeAllListeners(); self.subscribeToAddresses(); @@ -1053,7 +1055,6 @@ Wallet.prototype.toObj = function() { settings: this.settings, networkNonce: this.network.getHexNonce(), //yours networkNonces: this.network.getHexNonces(), //copayers - publicKeyRing: this.publicKeyRing.toObj(), txProposals: this.txProposals.toObj(), privateKey: this.privateKey ? this.privateKey.toObj() : undefined, addressBook: this.addressBook, @@ -1307,7 +1308,7 @@ Wallet.prototype.sendWalletId = function(recipients) { * @param {string[]} [recipients] - the pubkeys of the recipients */ Wallet.prototype.sendPublicKeyRing = function(recipients) { - var publicKeyRingObj = this.publicKeyRing.toObj(); + var publicKeyRingObj = this.publicKeyRing.toTrimmedObj(); this._sendToPeers(recipients, { type: 'publicKeyRing', @@ -2001,6 +2002,7 @@ Wallet.prototype.getAddressesStr = function(opts) { Wallet.prototype.subscribeToAddresses = function() { if (!this.publicKeyRing.isComplete()) return; +console.log('[Wallet.js.2002:subscribeToAddresses:]'); //TODO var addrInfo = this.publicKeyRing.getAddressesInfo(); this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr')); diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 935bc1ee1..07d157720 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -217,22 +217,32 @@ angular.module('copayApp.services') $rootScope.wallet = w; w.updateFocusedTimestamp(Date.now()); root.redirIfLogged(); - root.updateTxs(); - root.updateBalance(w, function() { + $timeout(function(){ $rootScope.$digest(); - }) + },1) + // root.updateTxs(); + // root.updateBalance(w, function() { + // $rootScope.$digest(); + // }) }; root.bindProfile = function($scope, iden, w) { + +console.log('[controllerUtils.js.230] bindProfile Globals'); //TODO root.setupGlobalVariables(iden); +console.log('[controllerUtils.js.230] bindProfile Wallets'); //TODO root.rebindWallets($scope, iden); if (w) { +console.log('[controllerUtils.js.230] bindProfile set Focus'); //TODO root.setFocusedWallet(w); } else { $location.path('/create'); } $timeout(function() { + +console.log('[controllerUtils.js.242] DIGEST'); //TODO $rootScope.$digest() +console.log('[controllerUtils.js.242] DIGEST DONE'); //TODO }, 1); }; @@ -310,6 +320,10 @@ angular.module('copayApp.services') root.updateBalance = function(w, cb, refreshAll) { + return + cb?cb(): null; + + w = w || $rootScope.wallet; if (!w) return root.onErrorDigest(); if (!w.isReady()) return; diff --git a/js/services/identityService.js b/js/services/identityService.js index 6c54b189d..492bf0f4b 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -92,6 +92,8 @@ angular.module('copayApp.services') $rootScope.$digest() }, 1); } else { + +console.log('[identityService.js.95] LISTO OPEN!!'); //TODO var firstWallet = iden.getLastFocusedWallet(); controllerUtils.bindProfile(scope, iden, firstWallet); } diff --git a/views/home.html b/views/home.html index b6edc54d3..03aa994e0 100644 --- a/views/home.html +++ b/views/home.html @@ -9,6 +9,7 @@
 
Accessing your profile... +13 From a8f0401e8e017d7e6d85fffd98e1eac6206df286 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 28 Nov 2014 18:43:22 -0300 Subject: [PATCH 02/30] simplified and cached address derivation --- js/controllers/receive.js | 13 +- js/models/PublicKeyRing.js | 265 ++++++++++++++++--------------------- js/models/Wallet.js | 96 +++++++------- test/PublicKeyRing.js | 59 +++++---- test/Wallet.js | 184 +++++++++++++------------ test/performance.js | 75 ----------- 6 files changed, 304 insertions(+), 388 deletions(-) delete mode 100644 test/performance.js diff --git a/js/controllers/receive.js b/js/controllers/receive.js index a701c0392..a3a34e49c 100644 --- a/js/controllers/receive.js +++ b/js/controllers/receive.js @@ -13,13 +13,12 @@ angular.module('copayApp.controllers').controller('ReceiveController', var w = $rootScope.wallet; $scope.loading = true; $scope.isNewAddr = false; - w.generateAddress(null, function() { - $timeout(function() { - controllerUtils.updateAddressList(); - $scope.loading = false; - $scope.isNewAddr = true; - }, 1); - }); + w.generateAddress(null); + $timeout(function() { + controllerUtils.updateAddressList(); + $scope.loading = false; + $scope.isNewAddr = true; + }, 1); }; $scope.openAddressModal = function(address) { diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js index 0b8e628f8..c12220ff4 100644 --- a/js/models/PublicKeyRing.js +++ b/js/models/PublicKeyRing.js @@ -42,10 +42,21 @@ function PublicKeyRing(opts) { this.publicKeysCache = {}; this.nicknameFor = opts.nicknameFor || {}; this.copayerIds = []; - this.addressToPath = {}; + this.resetCache(); }; +PublicKeyRing.prototype.resetCache = function() { + this.cache = {}; + this.cache.addressToPath = {}; + this.cache.receiveAddresses = []; + this.cache.changeAddresses = []; + + // Non persistent cache + this._isChange = {}; +}; + + /** * @desc Returns an object with only the keys needed to rebuild a PublicKeyRing * @@ -99,9 +110,9 @@ PublicKeyRing.fromObj = function(opts) { pkr.addCopayer(opts.copayersExtPubKeys[k]); } - if (opts._cache){ + if (opts.cache) { log.debug('PublicKeyRing: Using address cache'); - pkr._cacheAddressMap = opts._cache; + pkr.cache = opts.cache; } return pkr; @@ -129,7 +140,7 @@ PublicKeyRing.prototype.toObj = function() { return b.extendedPublicKeyString(); }), nicknameFor: this.nicknameFor, - _cache: this._cacheAddressMap + cache: this.cache, }; }; @@ -300,20 +311,12 @@ PublicKeyRing.prototype.addCopayer = function(newHexaExtendedPublicKey, nickname PublicKeyRing.prototype.getPubKeys = function(index, isChange, copayerIndex) { this._checkKeys(); + log.warn('Slow pubkey derivation...'); var path = HDPath.Branch(index, isChange, copayerIndex); - var pubKeys = this.publicKeysCache[path]; - if (!pubKeys) { - pubKeys = _.map(this.copayersHK, function(hdKey) { + var pubKeys = _.map(this.copayersHK, function(hdKey) { return hdKey.derive(path).eckey.public; - }); - this.publicKeysCache[path] = pubKeys.map(function(pk) { - return pk.toString('hex'); - }); - } else { - pubKeys = pubKeys.map(function(s) { - return new Buffer(s, 'hex'); - }); - } + }); + return pubKeys; }; @@ -347,31 +350,24 @@ PublicKeyRing.prototype.getRedeemScript = function(index, isChange, copayerIndex * @param {number} copayerIndex - the index of the copayer that requested the derivation * @returns {bitcore.Address} */ -PublicKeyRing.prototype.getAddress = function(index, isChange, id) { +PublicKeyRing.prototype._getAddress = function(index, isChange, id) { var copayerIndex = this.getCosigner(id); - if (!this._cachedAddress(index, isChange, id)) { + var path = HDPath.FullBranch(index, isChange, copayerIndex); -console.log('[PublicKeyRing.js.338] CACHE MISS'); //TODO - var script = this.getRedeemScript(index, isChange, copayerIndex); - var address = Address.fromScript(script, this.network.name); - this.addressToPath[address.toString()] = HDPath.FullBranch(index, isChange, copayerIndex); - this._cacheAddress(index, isChange, copayerIndex, address); - } - return this._cachedAddress(index, isChange, copayerIndex); + log.info('Generating Address:', index, isChange, copayerIndex); + var script = this.getRedeemScript(index, isChange, copayerIndex); + var address = Address.fromScript(script, this.network.name).toString(); + + this._cacheAddress(address, path, isChange); + return address; }; -PublicKeyRing.prototype._cacheAddress = function(index, isChange, copayerIndex, address) { - var changeIndex = isChange ? 1 : 0; - if (!this._cacheAddressMap) this._cacheAddressMap = {}; - if (!this._cacheAddressMap[index]) this._cacheAddressMap[index] = {}; - if (!this._cacheAddressMap[index][changeIndex]) this._cacheAddressMap[index][changeIndex] = {}; - this._cacheAddressMap[index][changeIndex][copayerIndex] = address; -}; -PublicKeyRing.prototype._cachedAddress = function(index, isChange, copayerIndex) { - var changeIndex = isChange ? 1 : 0; - if (!this._cacheAddressMap) return undefined; - if (!this._cacheAddressMap[index]) return undefined; - if (!this._cacheAddressMap[index][changeIndex]) return undefined; - return this._cacheAddressMap[index][changeIndex][copayerIndex]; + +PublicKeyRing.prototype._cacheAddress = function(address, path, isChange) { + this.cache.addressToPath[address] = path; + if (isChange) + this.cache.changeAddresses.push(address); + else + this.cache.receiveAddresses.push(address); }; /** @@ -401,26 +397,12 @@ PublicKeyRing.prototype.getHDParams = function(id) { * @return {HDPath} */ PublicKeyRing.prototype.pathForAddress = function(address) { - var path = this.addressToPath[address]; + this._checkAndRebuildCache(); + var path = this.cache.addressToPath[address]; if (!path) throw new Error('Couldn\'t find path for address ' + address); return path; }; -/** - * @desc - * Get the hexadecimal representation of a P2SH script - * - * @param {number} index - index to use when generating the address - * @param {boolean} isChange - generate a change address or a receive addres - * @param {number|string} pubkey - index of the copayer, or his public key - * @returns {string} hexadecimal encoded P2SH hash - */ -PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) { - var copayerIndex = this.getCosigner(pubkey); - var addr = this.getAddress(index, isChange, copayerIndex); - return Script.createP2SH(addr.payload()).getBuffer().toString('hex'); -}; - /** * @desc * Generates a new address and updates the last index used @@ -433,26 +415,44 @@ PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) { */ PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) { isChange = !!isChange; - var HDParams = this.getHDParams(pubkey); - var index = isChange ? HDParams.getChangeIndex() : HDParams.getReceiveIndex(); - var ret = this.getAddress(index, isChange, HDParams.copayerIndex); - HDParams.increment(isChange); + var hdParams = this.getHDParams(pubkey); + var index = isChange ? hdParams.getChangeIndex() : hdParams.getReceiveIndex(); + var ret = this._getAddress(index, isChange, hdParams.copayerIndex); + hdParams.increment(isChange); return ret; }; /** - * @desc - * Retrieve the addresses from a getAddressInfo return object + * @desc Is an address is from this wallet? * - * {@see PublicKeyRing#getAddressInfo} - * @returns {string[]} the result of retrieving the addresses from calling + * @param {string} address + * @return {boolean} */ -PublicKeyRing.prototype.getAddresses = function(opts) { - return this.getAddressesInfo(opts).map(function(info) { - return info.address; - }); +PublicKeyRing.prototype.addressIsOwn = function(address) { + return !!this.cache.addressToPath[address]; }; +/** + * @desc Is an address is a change address? + * + * @param {string} address + * @return {boolean} + */ +PublicKeyRing.prototype.addressIsChange = function(address) { + this._checkAndRebuildCache(); + + if (!this.cache.addressToPath[address]) + return null; + + //Memoization Only, never stored. + if (_.isUndefined(this._isChange[address])) { + this._isChange[address] = _.indexOf(this.cache.changeAddresses, address) >= 0; + } + return !!this._isChange[address]; +}; + + + /** * @desc * Maps a copayer's public key to his index in the keyring @@ -478,79 +478,46 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { return index; }; + + +PublicKeyRing.prototype.buildAddressCache = function() { + var ret = []; + var self = this; + + log.info('Rebuilding Address Cache...this will take a while'); + _.each(this.indexes, function(index) { + for (var i = 0; i < index.receiveIndex; i++) { + self._getAddress(i, false, index.copayerIndex); + } + for (var i = 0; i < index.changeIndex; i++) { + self._getAddress(i, true, index.copayerIndex); + } + }); + log.info('...done!'); +}; + + +PublicKeyRing.prototype._checkAndRebuildCache = function(opts) { + // If cache exists, it has to be updated + if (_.isEmpty(this.cache.addressToPath)) { + this.buildAddressCache(); + } +}; + + /** * @desc * Gets information about addresses for a copayer * - * @see PublicKeyRing#getAddressesInfoForIndex * @param {Object} opts - * @param {string|number} pubkey - the pubkey or index of a copayer in the ring * @returns {AddressInfo[]} */ -PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) { - -console.log('[PublicKeyRing.js.474] STARTED'); //TODO - var ret = []; - var self = this; - var copayerIndex = pubkey && this.getCosigner(pubkey); -console.log('[PublicKeyRing.js.478:copayerIndex:]',copayerIndex); //TODO - this.indexes.forEach(function(index) { -console.log('[PublicKeyRing.js.479:index:]',index); //TODO - ret = ret.concat(self.getAddressesInfoForIndex(index, opts, copayerIndex)); - }); -console.log('[PublicKeyRing.js.474] END'); //TODO +PublicKeyRing.prototype.getAddresses = function() { + this._checkAndRebuildCache(); + var ret = this.cache.receiveAddresses.concat(this.cache.changeAddresses); return ret; }; -/** - * @typedef AddressInfo - * @property {bitcore.Address} address - the address generated - * @property {string} addressStr - the base58 encoded address - * @property {boolean} isChange - true if it's a change address - * @property {boolean} owned - true if it's an address generated by a copayer - */ -/** - * @desc - * Retrieves info about addresses generated by a copayer - * - * @param {HDParams} index - HDParams of the copayer - * @param {Object} opts - * @param {boolean} opts.excludeChange - don't append information about change addresses - * @param {boolean} opts.excludeMain - don't append information about receive addresses - * @param {string|number|undefined} copayerIndex - copayer index, pubkey, or undefined to fetch info - * about shared addresses - * @return {AddressInfo[]} a list of AddressInfo - */ -PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) { - opts = opts || {}; - var isOwned = index.copayerIndex === HDPath.SHARED_INDEX || index.copayerIndex === copayerIndex; - var ret = []; - var appendAddressInfo = function(address, isChange) { - ret.push({ - address: address, - addressStr: address.toString(), - isChange: isChange, - owned: isOwned - }); - -console.log('[PublicKeyRing.js.518] Appending address'); //TODO - }; - - console.log('[PublicKeyRing.js.519] getAddressesInfoForIndex'); //TODO - for (var i = 0; !opts.excludeChange && i < index.changeIndex; i++) { - appendAddressInfo(this.getAddress(i, true, index.copayerIndex), true); - } - -console.log('[PublicKeyRing.js.526]'); //TODO - for (var i = 0; !opts.excludeMain && i < index.receiveIndex; i++) { - appendAddressInfo(this.getAddress(i, false, index.copayerIndex), false); - } - - -console.log('[PublicKeyRing.js.534] CACHE IS' , this._cacheAddressMap); //TODO - - return ret; -}; /** * @desc @@ -722,25 +689,6 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) { return hasChanged; }; -/** - * @desc - * Merges this public key ring with another one, optionally ignoring the - * wallet id - * - * @param {PublicKeyRing} inPkr - * @param {boolean} ignoreId - * @return {boolean} true if the internal state has changed - */ -PublicKeyRing.prototype.merge = function(inPKR, ignoreId) { - this._checkInPKR(inPKR, ignoreId); - - var hasChanged = false; - hasChanged |= this.mergeIndexes(inPKR.indexes); - hasChanged |= this._mergePubkeys(inPKR); - - return !!hasChanged; -}; - /** * @desc @@ -763,4 +711,25 @@ PublicKeyRing.prototype.mergeIndexes = function(indexes) { } +/** + * @desc + * Merges this public key ring with another one, optionally ignoring the + * wallet id + * + * @param {PublicKeyRing} inPkr + * @param {boolean} ignoreId + * @return {boolean} true if the internal state has changed + */ +PublicKeyRing.prototype.merge = function(inPKR, ignoreId) { + this._checkInPKR(inPKR, ignoreId); + + var hasChanged = false; + hasChanged |= this.mergeIndexes(inPKR.indexes); + hasChanged |= this._mergePubkeys(inPKR); + + return !!hasChanged; +}; + + + module.exports = PublicKeyRing; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 5cc85bdee..f9def93a7 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -887,7 +887,6 @@ Wallet.prototype._lockIncomming = function() { Wallet.prototype._setBlockchainListeners = function() { -console.log('[Wallet.js.889] address'); //TODO var self = this; self.blockchain.removeAllListeners(); self.subscribeToAddresses(); @@ -902,14 +901,12 @@ console.log('[Wallet.js.889] address'); //TODO log.debug('Wallet:' + self.id + 'blockchain disconnect event'); self.emitAndKeepAlive('insightError'); }); + self.blockchain.on('tx', function(tx) { log.debug('Wallet:' + self.id + ' blockchain tx event'); - var addresses = self.getAddressesInfo(); - var addr = _.findWhere(addresses, { - addressStr: tx.address - }); - if (addr) { - self.emitAndKeepAlive('tx', tx.address, addr.isChange); + var addresses = self.getAddresses(); + if (_.indexOf(addresses,tx.address)>=0) { + self.emitAndKeepAlive('tx', tx.address, self.addressIsChange(tx.address)); } }); @@ -1055,6 +1052,7 @@ Wallet.prototype.toObj = function() { settings: this.settings, networkNonce: this.network.getHexNonce(), //yours networkNonces: this.network.getHexNonces(), //copayers + publicKeyRing: this.publicKeyRing.toObj(), txProposals: this.txProposals.toObj(), privateKey: this.privateKey ? this.privateKey.toObj() : undefined, addressBook: this.addressBook, @@ -1376,21 +1374,15 @@ Wallet.prototype._doGenerateAddress = function(isChange) { return this.publicKeyRing.generateAddress(isChange, this.publicKey); }; -/** - * @callback addressCallback - * @param {string} addr - all the addresses of the wallet - */ /** * @desc Generate a new address * @param {boolean} isChange - whether to generate a change address or a receive address - * @param {addressCallback} cb * @return {string[]} a list of all the addresses generated so far for the wallet */ -Wallet.prototype.generateAddress = function(isChange, cb) { +Wallet.prototype.generateAddress = function(isChange) { var addr = this._doGenerateAddress(isChange); this.sendIndexes(); this._newAddresses(); - if (cb) return cb(addr); return addr; }; @@ -1844,12 +1836,21 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) { */ Wallet.prototype._getPayProRefundOutputs = function(txp) { var pkr = this.publicKeyRing; - var index = pkr.getHDParams(this.publicKey); var amount = +txp.merchant.total.toString(10); var output = new PayPro.Output(); - var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey); - output.set('script', new Buffer(script, 'hex')); + var opts = JSON.parse(txp.builder.vanilla.opts); + if (!opts.remainderOut) { + log.warn('no remainder set. Not setting refund in PayPro'); + return; + } +console.log('[Wallet.js.1842:builder:]',txp.builder.vanilla.opts); //TODO + var addrStr = opts.remainderOut.address; + var addr = new bitcore.Address(addrStr); + var script = bitcore.Script.createP2SH(addr.payload()).getBuffer(); + log.debug('PayPro refund address set to:' + addrStr); + + output.set('script', script); output.set('amount', amount); return [output]; }; @@ -1867,7 +1868,6 @@ Wallet.prototype.createPayProPayment = function(txp) { var tx = txp.builder.build(); var txBuf = tx.serialize(); - var refund_outputs = this._getPayProRefundOutputs(txp); // We send this to the serve after receiving a PaymentRequest var pay = new PayPro(); @@ -1878,9 +1878,11 @@ Wallet.prototype.createPayProPayment = function(txp) { merchant_data = new Buffer(merchant_data, 'hex'); pay.set('merchant_data', merchant_data); } - pay.set('transactions', [txBuf]); - pay.set('refund_to', refund_outputs); + + var refund_outputs = this._getPayProRefundOutputs(txp); + if (refund_outputs) + pay.set('refund_to', refund_outputs); // Unused for now // options.memo = ''; @@ -2002,28 +2004,32 @@ Wallet.prototype.getAddressesStr = function(opts) { Wallet.prototype.subscribeToAddresses = function() { if (!this.publicKeyRing.isComplete()) return; -console.log('[Wallet.js.2002:subscribeToAddresses:]'); //TODO - var addrInfo = this.publicKeyRing.getAddressesInfo(); - this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr')); - log.debug('Subscribed to ' + addrInfo.length + ' addresses'); + var addresses = this.publicKeyRing.getAddresses(); + this.blockchain.subscribe(addresses); + log.debug('Subscribed to ' + addresses.length + ' addresses'); }; -/** - * @desc Alias for {@link PublicKeyRing#getAddressesInfo} - */ -Wallet.prototype.getAddressesInfo = function(opts) { - return this.publicKeyRing.getAddressesInfo(opts, this.publicKey); -}; /** * @desc Returns true if a given address was generated by deriving our master public key * @return {boolean} */ Wallet.prototype.addressIsOwn = function(addrStr) { - return !!this.publicKeyRing.addressToPath[addrStr]; + return this.publicKeyRing.addressIsOwn(addrStr); }; +/** + * @desc Returns true if a given address is a change address (remainder) + * @param addrStr + * @return {boolean} + */ +Wallet.prototype.addressIsChange = function(addrStr) { + return this.publicKeyRing.addressIsChange(addrStr); +}; + + + /** * Estimate a tx fee in satoshis given its input count * (only used when spending all wallet funds) @@ -2110,7 +2116,7 @@ Wallet.prototype.maxRejectCount = function() { // TODO: Can we add cache to getUnspent? Wallet.prototype.getUnspent = function(cb) { var self = this; - this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { + this.blockchain.getUnspent(this.getAddresses(), function(err, unspentList) { if (err) { return cb(err); @@ -2372,7 +2378,8 @@ Wallet.prototype.deriveAddresses = function(index, amount, isChange, copayerInde var ret = new Array(amount); for (var i = 0; i < amount; i++) { - ret[i] = this.publicKeyRing.getAddress(index + i, isChange, copayerIndex).toString(); + // TODO + ret[i] = this.publicKeyRing._getAddress(index + i, isChange, copayerIndex).toString(); } return ret; }; @@ -2534,7 +2541,7 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { } opts = opts || {}; - var addresses = self.getAddressesInfo(); + var addresses = self.getAddresses(); var proposals = self.txProposals.txps; var satToUnit = 1 / self.settings.unitToSatoshi; @@ -2544,33 +2551,26 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { function extractInsOuts(tx) { // Inputs var inputs = _.map(tx.vin, function(item) { - var addr = _.findWhere(addresses, { - addressStr: item.addr - }); return { type: 'in', - address: addr ? addr.addressStr : item.addr, - isMine: !_.isUndefined(addr), - isChange: addr ? !!addr.isChange : false, + address: item.addr, + isMine: self.addressIsOwn(item.addr), + isChange: self.addressIsChange(item.addr), amountSat: item.valueSat, } }); var outputs = _.map(tx.vout, function(item) { - var addr; var itemAddr; // If classic multisig, ignore if (item.scriptPubKey && item.scriptPubKey.addresses.length == 1) { itemAddr = item.scriptPubKey.addresses[0]; - addr = _.findWhere(addresses, { - addressStr: itemAddr, - }); } return { type: 'out', - address: addr ? addr.addressStr : itemAddr, - isMine: !_.isUndefined(addr), - isChange: addr ? !!addr.isChange : false, + address: itemAddr, + isMine: self.addressIsOwn(itemAddr), + isChange: self.addressIsChange(itemAddr), label: self.addressBook[itemAddr] ? self.addressBook[itemAddr].label : undefined, amountSat: parseInt((item.value * bitcore.util.COIN).toFixed(0)), } @@ -2608,6 +2608,8 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { var fees = parseInt((tx.fees * bitcore.util.COIN).toFixed(0)); var amount; + + if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? fees : 0))) { tx.action = 'moved'; amount = amountOut; diff --git a/test/PublicKeyRing.js b/test/PublicKeyRing.js index 321b81ffe..ca0a8d648 100644 --- a/test/PublicKeyRing.js +++ b/test/PublicKeyRing.js @@ -136,7 +136,8 @@ describe('PublicKeyRing model', function() { [true, false].forEach(function(isChange) { for (var i = 0; i < 2; i++) { - var a = w.generateAddress(isChange, k.pub); + var aStr = w.generateAddress(isChange, k.pub); + var a= new bitcore.Address(aStr); a.isValid().should.equal(true); a.isScript().should.equal(true); a.network().name.should.equal('livenet'); @@ -152,27 +153,32 @@ describe('PublicKeyRing model', function() { var setup = getCachedW(); var pubkeyring = setup.w; - var address = pubkeyring.getAddress(3, false, 4); + var address = pubkeyring._getAddress(3, false, 4); - (pubkeyring._cacheAddressMap[3][0][4]).should.equal(address); + pubkeyring.cache.addressToPath[address].should.equal("m/45'/4/0/3"); + _.indexOf(pubkeyring.cache.receiveAddresses,address).should.be.above(0); + _.indexOf(pubkeyring.cache.changeAddresses,address).should.be.equal(-1); }); - it('getAddress cache hit doesn\'t alter state', function() { - var setup = getCachedW(); - var pubkeyring = setup.w; - var spySave; - - pubkeyring.getAddress(3, false, 4); - - spySave = sinon.stub(pubkeyring, '_cacheAddress'); - spySave.onFirstCall().throws(new Error()); - - pubkeyring.getAddress(3, false, 4); - - spySave.restore(); + it('should generate one address by default', function() { + var k = createW(); + var w = k.w; + var a = w.getAddresses(); + a.length.should.equal(1); }); - it('should return PublicKeyRing addresses', function() { + it('should generate one address by default', function() { + var k = createW(); + var w = k.w; + + var a = w.getAddresses(); + a.length.should.equal(1); + a = w.getAddresses(); + a.length.should.equal(1); + }); + + + it('should generate 4+1 addresses', function() { var k = createW(); var w = k.w; @@ -184,15 +190,16 @@ describe('PublicKeyRing model', function() { w.generateAddress(isChange, k.pub); } }); + }); - var as = w.getAddressesInfo(); - as.length.should.equal(5); // include pre-generated shared one - for (var j in as) { - var a = as[j]; - a.address.isValid().should.equal(true); - a.addressStr.should.equal(a.address.toString()); - a.isChange.should.equal([false, true, true, false, false][j]); - } + it('should check isChange 4+1 addresses', function() { + var k = createW(); + var w = k.w; + var a = w.getAddresses(); + _.each(a, function(a, j) { + var addr = new bitcore.Address(a); + w.addressIsChange(a).should.equal([false, true, true, false, false][j]); + }); }); @@ -405,7 +412,7 @@ describe('PublicKeyRing model', function() { (function() { PublicKeyRing.fromObj(pkr); - }).should.throw('bad data format: Did you use .toObj()?'); + }).should.throw('format'); }); diff --git a/test/Wallet.js b/test/Wallet.js index bbef0644d..db1c437b6 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -8,6 +8,7 @@ var Transaction = bitcore.Transaction; var Address = bitcore.Address; var PayPro = bitcore.PayPro; var Buffer = bitcore.Buffer; +var Script = bitcore.Script; function assertObjectEqual(a, b) { @@ -163,17 +164,13 @@ describe('Wallet model', function() { should.exist(w.addressBook); }); - it('should provide some basic features', function(done) { + it('should provide some basic features', function() { var opts = {}; var w = cachedCreateW(); addCopayers(w); w.publicKeyRing.generateAddress(false, w.publicKey); w.publicKeyRing.isComplete().should.equal(true); - w.generateAddress(true).isValid().should.equal(true); - w.generateAddress(true, function(addr) { - addr.isValid().should.equal(true); - done(); - }); + (new bitcore.Address(w.generateAddress(true))).isValid().should.equal(true); }); var unspentTest = [{ @@ -224,14 +221,18 @@ describe('Wallet model', function() { return w; }; + var unSpentTestFromWallet = function(w, addrStr) { + + unspentTest[0].address = addrStr; + var a = new bitcore.Address(addrStr); + unspentTest[0].scriptPubKey = Script.createP2SH(a.payload()).getBuffer().toString('hex'); + }; + it('#create, fail for network', function() { var w = cachedCreateW2(); - - unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true); - + unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true)); var f = function() { w._createTxProposal( '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', @@ -240,15 +241,14 @@ describe('Wallet model', function() { unspentTest ); }; - f.should.throw(Error); + f.should.throw('networkname'); }); it('#create, check builder opts', function() { var w = cachedCreateW2(); - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true)); var txp = w._createTxProposal( 'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79', '123456789', @@ -264,9 +264,7 @@ describe('Wallet model', function() { it('#create, 1 sign', function() { var w = cachedCreateW2(); - - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true)); var txp = w._createTxProposal( 'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79', @@ -288,9 +286,7 @@ describe('Wallet model', function() { var w = cachedCreateW2(); var comment = 'This is a comment'; - - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true)); var txp = w._createTxProposal( 'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79', @@ -308,9 +304,7 @@ describe('Wallet model', function() { var w = cachedCreateW2(); var comment = 'Lorem ipsum dolor sit amet, suas euismod vis te, velit deleniti vix an. Pri ex suscipit similique, inermis per'; - - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true)); (function() { w._createTxProposal( @@ -340,8 +334,7 @@ describe('Wallet model', function() { var ts = Date.now(); for (var isChange = false; !isChange; isChange = true) { for (var index = 0; index < 3; index++) { - unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange, w.publicKey); + unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true)); var txp = w._createTxProposal( 'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79', '123456789', @@ -910,7 +903,7 @@ describe('Wallet model', function() { w.issueTx(ntxid, function(err, txid, status) { should.not.exist(err); - txp.getSent().should.be.above(now-1); + txp.getSent().should.be.above(now - 1); txp.sentTxid.should.be.equal(txid); txid.should.equal(1234); status.should.equal(Wallet.TX_BROADCASTED); @@ -1170,16 +1163,14 @@ describe('Wallet model', function() { describe('#subscribeToAddresses', function() { it('should subscribe successfully', function() { var w = cachedCreateW2(); - var addr1 = w.getAddresses()[0].toString(); var addr2 = w.generateAddress().toString(); var addr3 = w.generateAddress(true).toString(); - chai.expect(w.getAddresses().length).to.equal(3); w.blockchain.subscribe = sinon.spy(); w.subscribeToAddresses(); w.blockchain.subscribe.calledOnce.should.equal(true); var arg = w.blockchain.subscribe.getCall(0).args[0]; - chai.expect(_.difference(arg, [addr1, addr2, addr3]).length).to.equal(0); + _.intersection(arg, [addr2, addr3]).length.should.be.equal(2); }); }); @@ -1967,27 +1958,40 @@ describe('Wallet model', function() { - it('should emit notification when tx received', function(done) { + it('should emit notification when tx received', function() { var w = cachedCreateW2(); + + var addr1 = w.generateAddress(false); + sinon.stub(w,'subscribeToAddresses'); + w.blockchain.removeAllListeners = sinon.stub(); - var spy = sinon.spy(w, 'emit'); + w.blockchain.on = sinon.stub(); - w.generateAddress(false, function(addr1) { - w.generateAddress(true, function(addr2) { - w.blockchain.on = sinon.stub().withArgs('tx').yields({ - address: addr1.toString(), - }); - w._setBlockchainListeners(); - spy.calledWith('tx', addr1.toString(), false).should.be.true; - - w.blockchain.on = sinon.stub().withArgs('tx').yields({ - address: addr2.toString(), - }); - w._setBlockchainListeners(); - spy.calledWith('tx', addr2.toString(), true).should.be.true; - done(); - }); + w.blockchain.on.withArgs('tx').yields({ + address: addr1, }); + + var spy = sinon.spy(w, 'emit'); + w._setBlockchainListeners(); + spy.calledWith('tx', addr1, false).should.equal(true); + }); + + it('should emit notification when tx received (change addr)', function() { + var w = cachedCreateW2(); + + var addr1 = w.generateAddress(true); + sinon.stub(w,'subscribeToAddresses'); + + w.blockchain.removeAllListeners = sinon.stub(); + w.blockchain.on = sinon.stub(); + + w.blockchain.on.withArgs('tx').yields({ + address: addr1, + }); + + var spy = sinon.spy(w, 'emit'); + w._setBlockchainListeners(); + spy.calledWith('tx', addr1, true).should.equal(true); }); describe('#fromObj / #toObj', function() { @@ -2041,13 +2045,28 @@ describe('Wallet model', function() { should.exist(w.txProposals.toObj); should.exist(w.privateKey.toObj); - assertObjectEqual(w.toObj(), JSON.parse(o2)); + var obj = w.toObj(); + + // remove data from new versions + delete obj.publicKeyRing['cache']; + + assertObjectEqual(obj, JSON.parse(o2)); }); }); describe('#getTransactionHistory', function() { + var w; + beforeEach(function() { + w = cachedCreateW2(); + }); + afterEach(function() { + if (w.publicKeyRing.addressIsOwn.restore) + w.publicKeyRing.addressIsOwn.restore(); + if (w.publicKeyRing.addressIsChange.restore) + w.publicKeyRing.addressIsChange.restore(); + }); + it('should return list of txs', function(done) { - var w = cachedCreateW2(); var txs = [{ vin: [{ addr: 'addr_in_1', @@ -2092,11 +2111,17 @@ describe('Wallet model', function() { items: txs, totalItems: txs.length, }); - w.getAddressesInfo = sinon.stub().returns([{ - addressStr: 'addr_in_1' - }, { - addressStr: 'addr_out_2' - }]); + + sinon.stub(w,'getAddresses').returns([ 'addr_in_1', 'addr_out_2' ]); + var s = sinon.stub(w.publicKeyRing,'addressIsOwn'); + s.withArgs('addr_in_1').returns(true); + s.withArgs('addr_in_2').returns(false); + s.withArgs('addr_out_2').returns(true); + + + var s2 = sinon.stub(w.publicKeyRing,'addressIsChange'); + s2.withArgs('addr_out_1').returns(false); + s2.withArgs('addr_out_2').returns(false); w.getTransactionHistory(function(err, res) { res.should.exist; @@ -2113,7 +2138,6 @@ describe('Wallet model', function() { }); }); it('should return paginated list of txs', function(done) { - var w = cachedCreateW2(); var txs = [{ txid: 'id1', vin: [{ @@ -2161,11 +2185,6 @@ describe('Wallet model', function() { items: txs.slice(2, 3), totalItems: txs.length, }); - w.getAddressesInfo = sinon.stub().returns([{ - addressStr: 'addr_in_1' - }, { - addressStr: 'addr_out_2' - }]); w.getTransactionHistory({ currentPage: 2, @@ -2182,17 +2201,11 @@ describe('Wallet model', function() { }); }); it('should paginate empty list', function(done) { - var w = cachedCreateW2(); var txs = []; w.blockchain.getTransactions = sinon.stub().yields(null, { items: txs, totalItems: txs.length, }); - w.getAddressesInfo = sinon.stub().returns([{ - addressStr: 'addr_in_1' - }, { - addressStr: 'addr_out_2' - }]); w.getTransactionHistory({ currentPage: 2, @@ -2207,7 +2220,6 @@ describe('Wallet model', function() { }); }); it('should compute sent amount correctly', function(done) { - var w = cachedCreateW2(); var txs = [{ vin: [{ addr: 'addr_in_1', @@ -2234,14 +2246,17 @@ describe('Wallet model', function() { items: txs, totalItems: txs.length, }); - w.getAddressesInfo = sinon.stub().returns([{ - addressStr: 'addr_in_1' - }, { - addressStr: 'addr_in_2' - }, { - addressStr: 'change', - isChange: true, - }]); + + + sinon.stub(w,'getAddresses').returns([ 'addr_in_1', 'addr_in_2', 'change']); + var s = sinon.stub(w.publicKeyRing,'addressIsOwn'); + s.withArgs('addr_in_1').returns(true); + s.withArgs('addr_in_2').returns(true); + s.withArgs('change').returns(true); + + var s2 = sinon.stub(w.publicKeyRing,'addressIsChange'); + s2.withArgs('addr_out_2').returns(false); + s2.withArgs('change').returns(true); w.getTransactionHistory(function(err, res) { res.should.exist; @@ -2253,7 +2268,6 @@ describe('Wallet model', function() { }); }); it('should compute moved amount correctly', function(done) { - var w = cachedCreateW2(); var txs = [{ vin: [{ addr: 'addr_1', @@ -2280,14 +2294,16 @@ describe('Wallet model', function() { items: txs, totalItems: txs.length, }); - w.getAddressesInfo = sinon.stub().returns([{ - addressStr: 'addr_1' - }, { - addressStr: 'addr_2' - }, { - addressStr: 'change', - isChange: true, - }]); + + sinon.stub(w,'getAddresses').returns([ 'addr_in_1', 'addr_in_2', 'change']); + var s = sinon.stub(w.publicKeyRing,'addressIsOwn'); + s.withArgs('addr_1').returns(true); + s.withArgs('addr_2').returns(true); + s.withArgs('change').returns(true); + + var s2 = sinon.stub(w.publicKeyRing,'addressIsChange'); + s2.withArgs('addr_1').returns(false); + s2.withArgs('change').returns(true); w.getTransactionHistory(function(err, res) { res.should.exist; @@ -2299,7 +2315,6 @@ describe('Wallet model', function() { }); }); it('should assign label when address in address book', function(done) { - var w = cachedCreateW2(); var txs = [{ vin: [{ addr: 'addr_in_1', @@ -2349,7 +2364,6 @@ describe('Wallet model', function() { }); }); it('should assign comment from tx proposal if found', function(done) { - var w = cachedCreateW2(); var txs = [{ txid: 'id1', vin: [{ @@ -2517,7 +2531,7 @@ describe('Wallet model', function() { // DATA - var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}'; + var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{ "cache": { "addressToPath": {}, "changeAddresses": [], "receiveAddresses": [] }, "walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}'; }); diff --git a/test/performance.js b/test/performance.js deleted file mode 100644 index 8f218e24a..000000000 --- a/test/performance.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -var PrivateKey = copay.PrivateKey; -var PublicKeyRing = copay.PublicKeyRing; - -var getNewEpk = function() { - return new PrivateKey({ - networkName: 'livenet', - }) - .deriveBIP45Branch() - .extendedPublicKeyString(); -} - - -describe('Performance tests', function() { - describe('PrivateKey', function() { - it('should optimize BIP32 private key gen time with cache', function() { - var k1 = new PrivateKey(); - var generateN = 25; - var generated = []; - var start1 = new Date().getTime(); - for (var i = 0; i < generateN; i++) { - var k = JSON.stringify(k1.get(i, false).storeObj()); - generated.push(k); - } - var delta1 = new Date().getTime() - start1; - var start2 = new Date().getTime(); - for (var i = 0; i < generateN; i++) { - var k = JSON.stringify(k1.get(i, false).storeObj()); - generated[i].should.equal(k); - } - var delta2 = new Date().getTime() - start2; - delta2.should.be.below(delta1); - }); - }); - describe('PublicKeyRing', function() { - var maxN = 7; - for (var n = 1; n < maxN; n++) { - for (var m = 1; m <= n; m++) { - if ((m === 3 && n === 5) || - (m === 2 && n === 3)) { - var M = m; - var N = n; - (function(M, N) { - it('should optimize BIP32 publickey gen time with cache for ' + M + '-of-' + N, function() { - var pkr1 = new PublicKeyRing({ - totalCopayers: N, - requiredCopayers: M - }); - for (var i = 0; i < N; i++) { - pkr1.addCopayer(getNewEpk()); // add new random ext public key - } - var generateN = 5; - var generated = []; - var start1 = new Date().getTime(); - for (var i = 0; i < generateN; i++) { - var pubKeys = JSON.stringify(pkr1.getPubKeys(i, false)); - generated.push(pubKeys); - } - var delta1 = new Date().getTime() - start1; - var start2 = new Date().getTime(); - for (var i = 0; i < generateN; i++) { - var pubKeys = JSON.stringify(pkr1.getPubKeys(i, false)); - generated[i].should.equal(pubKeys); - } - var delta2 = new Date().getTime() - start2; - delta2.should.be.below(delta1); - }); - })(M, N); - } - } - } - - }); -}); From 57299d675e43200c3db60d9adc8f35aae72cbf65 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 29 Nov 2014 18:35:48 -0300 Subject: [PATCH 03/30] balance Service --- js/controllers/copayers.js | 5 +- js/controllers/create.js | 7 +- js/controllers/createProfile.js | 7 +- js/controllers/head.js | 14 +- js/controllers/history.js | 8 +- js/controllers/home.js | 5 +- js/controllers/homeWallet.js | 10 +- js/controllers/import.js | 36 ++- js/controllers/importProfile.js | 6 +- js/controllers/join.js | 25 +- js/controllers/more.js | 14 +- js/controllers/paymentIntent.js | 27 +-- js/controllers/profile.js | 23 +- js/controllers/receive.js | 29 ++- js/controllers/send.js | 35 ++- js/controllers/settings.js | 4 +- js/controllers/sidebar.js | 54 ++--- js/controllers/warning.js | 11 +- js/models/Identity.js | 8 +- js/models/Insight.js | 3 +- js/models/PublicKeyRing.js | 52 +++-- js/models/Wallet.js | 27 ++- js/routes.js | 9 + js/services/balanceService.js | 84 +++++++ js/services/controllerUtils.js | 396 -------------------------------- js/services/identityService.js | 276 +++++++++++++++++++++- test/PublicKeyRing.js | 39 +++- test/Wallet.js | 2 +- views/homeWallet.html | 4 +- views/includes/sidebar.html | 6 +- views/send.html | 6 +- 31 files changed, 586 insertions(+), 646 deletions(-) create mode 100644 js/services/balanceService.js delete mode 100644 js/services/controllerUtils.js diff --git a/js/controllers/copayers.js b/js/controllers/copayers.js index 551a1cf60..f94cab1b2 100644 --- a/js/controllers/copayers.js +++ b/js/controllers/copayers.js @@ -1,7 +1,8 @@ 'use strict'; angular.module('copayApp.controllers').controller('CopayersController', - function($scope, $rootScope, $location, controllerUtils) { + function($scope, $rootScope, $location) { + if (!$rootScope.wallet.isReady()) { $rootScope.title = 'Waiting copayers for ' + $rootScope.wallet.getName(); } @@ -9,9 +10,7 @@ angular.module('copayApp.controllers').controller('CopayersController', $scope.secret = $rootScope.wallet.getSecret(); $scope.goToWallet = function() { - controllerUtils.updateAddressList(); $location.path('/homeWallet'); - }; $scope.copayersList = function() { diff --git a/js/controllers/create.js b/js/controllers/create.js index 64ee88bcf..72d356fb3 100644 --- a/js/controllers/create.js +++ b/js/controllers/create.js @@ -1,10 +1,9 @@ 'use strict'; angular.module('copayApp.controllers').controller('CreateController', - function($scope, $rootScope, $location, $timeout, controllerUtils, backupService, notification, defaults) { + function($scope, $rootScope, $location, $timeout, identityService, backupService, notification, defaults) { $rootScope.fromSetup = true; - $rootScope.starting = false; $scope.loading = false; $scope.walletPassword = $rootScope.walletPassword; $scope.isMobile = !!window.cordova; @@ -53,10 +52,8 @@ angular.module('copayApp.controllers').controller('CreateController', privateKeyHex: $scope.private, networkName: $scope.networkName, }; - $rootScope.iden.createWallet(opts, function(err, w) { + identityService.createWallet(opts, function(){ $scope.loading = false; - controllerUtils.installWalletHandlers($scope, w); - controllerUtils.setFocusedWallet(w); }); }; }); diff --git a/js/controllers/createProfile.js b/js/controllers/createProfile.js index 824c9a0ce..1f2938493 100644 --- a/js/controllers/createProfile.js +++ b/js/controllers/createProfile.js @@ -1,14 +1,15 @@ 'use strict'; -angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) { - controllerUtils.redirIfLogged(); +angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, pluginManager, identityService) { + + identityService.goWalletHome(); + $scope.loading = false; $scope.createProfile = function(form) { if (form && form.$invalid) { $scope.error('Error', 'Please enter the required fields'); return; } - $rootScope.starting = true; identityService.create($scope, form); } diff --git a/js/controllers/head.js b/js/controllers/head.js index 33c296d79..ce1da9149 100644 --- a/js/controllers/head.js +++ b/js/controllers/head.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('HeadController', function($scope, $rootScope, $filter, $timeout, notification, controllerUtils) { +angular.module('copayApp.controllers').controller('HeadController', function($scope, $rootScope, $filter, $timeout, notification, identityService, balanceService) { $scope.username = $rootScope.iden.getName(); $scope.hoverMenu = false; @@ -14,7 +14,7 @@ angular.module('copayApp.controllers').controller('HeadController', function($sc $scope.signout = function() { $rootScope.signingOut = true; - controllerUtils.logout(); + identityService.logout(); }; $scope.refresh = function() { @@ -23,12 +23,10 @@ angular.module('copayApp.controllers').controller('HeadController', function($sc if (w.isReady()) { w.sendWalletReady(); - if ($rootScope.addrInfos.length > 0) { - controllerUtils.clearBalanceCache(w); - controllerUtils.updateBalance(w, function() { - $rootScope.$digest(); - }); - } + balanceService.clearBalanceCache(w); + balanceService.update(w, function() { + $rootScope.$digest(); + }, true); } }; diff --git a/js/controllers/history.js b/js/controllers/history.js index 2328a1de9..8a592f370 100644 --- a/js/controllers/history.js +++ b/js/controllers/history.js @@ -2,9 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.controllers').controller('HistoryController', - function($scope, $rootScope, $timeout, controllerUtils, notification, rateService) { - controllerUtils.redirIfNotComplete(); - + function($scope, $rootScope) { var w = $rootScope.wallet; $rootScope.title = 'History'; @@ -19,15 +17,11 @@ angular.module('copayApp.controllers').controller('HistoryController', $scope.blockchain_txs = []; $scope.alternativeCurrency = []; - - $scope.selectPage = function(page) { $scope.currentPage = page; $scope.update(); }; - - $scope.downloadHistory = function() { var w = $rootScope.wallet; if (!w) return; diff --git a/js/controllers/home.js b/js/controllers/home.js index 2e529109f..e39da6827 100644 --- a/js/controllers/home.js +++ b/js/controllers/home.js @@ -1,8 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService, Compatibility) { - controllerUtils.redirIfLogged(); - +angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, identityService, Compatibility) { // This is only for backwards compat, insight api should link to #!/confirmed directly if (getParam('confirmed')) { var hashIndex = window.location.href.indexOf('/?'); @@ -24,7 +22,6 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc $scope.error = 'Please enter the required fields'; return; } - $rootScope.starting = true; identityService.open($scope, form); } diff --git a/js/controllers/homeWallet.js b/js/controllers/homeWallet.js index ad25cd973..146fc2fae 100644 --- a/js/controllers/homeWallet.js +++ b/js/controllers/homeWallet.js @@ -1,11 +1,13 @@ 'use strict'; angular.module('copayApp.controllers').controller('HomeWalletController', - function($scope, $rootScope, $timeout, $modal, controllerUtils) { - - controllerUtils.redirIfNotComplete(); - $rootScope.starting = false; + function($scope, $rootScope) { $rootScope.title = 'Home'; + $scope.addr = _.last($rootScope.wallet.getReceiveAddresses()); + // This is necesarry, since wallet can change in homeWallet, without running init() again. + $rootScope.$watch('wallet', function() { + $scope.addr = _.last($rootScope.wallet.getReceiveAddresses()); + }); } ); diff --git a/js/controllers/import.js b/js/controllers/import.js index b7ecf09bc..895bc5790 100644 --- a/js/controllers/import.js +++ b/js/controllers/import.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('ImportController', - function($scope, $rootScope, $location, controllerUtils, notification, isMobile, Compatibility) { + function($scope, $rootScope, $location, identityService, notification, isMobile, Compatibility) { $rootScope.title = 'Import wallet'; $scope.importStatus = 'Importing wallet - Reading backup...'; @@ -18,28 +18,18 @@ angular.module('copayApp.controllers').controller('ImportController', $scope.$digest(); } - $scope._doImport = function(encryptedObj, password) { - updateStatus('Importing wallet - Procesing backup...'); - - copay.Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj, - $scope.password, $scope.importOpts, function(err, wallet) { - if (err) { - $scope.loading = false; - $scope.error = 'Could not read wallet. Please check your password'; - } else { - controllerUtils.installWalletHandlers($scope, wallet); - controllerUtils.setFocusedWallet(wallet); - } - } - ); - }; - $scope.getFile = function() { // If we use onloadend, we need to check the readyState. reader.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { // DONE == 2 var encryptedObj = evt.target.result; - $scope._doImport(encryptedObj, $scope.password); + updateStatus('Importing wallet - Procesing backup...'); + identityService.importWallet(encryptedObj, $scope.password, {}, function(err){ + if (err) { + $scope.loading = false; + $scope.error = 'Could not read wallet. Please check your password'; + } + }); } }; }; @@ -85,8 +75,14 @@ angular.module('copayApp.controllers').controller('ImportController', if (backupFile) { reader.readAsBinaryString(backupFile); } else { - $scope._doImport(backupText, $scope.password); - copay.Compatibility.deleteOldWallet(backupOldWallet); + updateStatus('Importing wallet - Procesing backup...'); + identityService.importWallet(encryptedObj, $scope.password, $scope.importOpts, function(err){ + if (err) { + $scope.loading = false; + $scope.error = 'Could not read wallet. Please check your password'; + } + copay.Compatibility.deleteOldWallet(backupOldWallet); + }); } }; }); diff --git a/js/controllers/importProfile.js b/js/controllers/importProfile.js index 016308052..b9f07e7cb 100644 --- a/js/controllers/importProfile.js +++ b/js/controllers/importProfile.js @@ -1,9 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('ImportProfileController', - function($scope, $rootScope, $location, controllerUtils, notification, isMobile, pluginManager, identityService) { - controllerUtils.redirIfLogged(); - + function($scope, $rootScope, $location, notification, isMobile, pluginManager, identityService) { $scope.title = 'Import a backup'; $scope.importStatus = 'Importing wallet - Reading backup...'; $scope.hideAdv = true; @@ -41,7 +39,7 @@ angular.module('copayApp.controllers').controller('ImportProfileController', } else { var firstWallet = iden.getLastFocusedWallet(); - controllerUtils.bindProfile($scope, iden, firstWallet); + root.bind($scope, iden, firstWallet); } }); }; diff --git a/js/controllers/join.js b/js/controllers/join.js index 89d33da99..ce3f989c5 100644 --- a/js/controllers/join.js +++ b/js/controllers/join.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('JoinController', - function($scope, $rootScope, $timeout, isMobile, controllerUtils, notification) { + function($scope, $rootScope, $timeout, isMobile, notification) { $rootScope.fromSetup = false; $scope.loading = false; $scope.isMobile = isMobile.any(); @@ -119,31 +119,12 @@ angular.module('copayApp.controllers').controller('JoinController', } $scope.loading = true; - - $rootScope.iden.joinWallet({ + identityService.joinWallet({ secret: $scope.connectionId, nickname: $scope.nickname, privateHex: $scope.private, - }, function(err, w) { - + }, function(err) { $scope.loading = false; - if (err || !w) { - if (err === 'joinError') - notification.error('Fatal error connecting to Insight server'); - else if (err === 'walletFull') - notification.error('The wallet is full'); - else if (err === 'badNetwork') - notification.error('Network Error', 'Wallet network configuration missmatch'); - else if (err === 'badSecret') - notification.error('Bad secret', 'The secret string you entered is invalid'); - else { - notification.error('Error', err.message || err); - } - controllerUtils.onErrorDigest(); - } else { - controllerUtils.installWalletHandlers($scope, w); - controllerUtils.setFocusedWallet(w); - } }); } }); diff --git a/js/controllers/more.js b/js/controllers/more.js index 7d57c7a89..f3068922a 100644 --- a/js/controllers/more.js +++ b/js/controllers/more.js @@ -1,8 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('MoreController', - function($scope, $rootScope, $location, $filter, controllerUtils, notification, rateService) { - controllerUtils.redirIfNotComplete(); + function($scope, $rootScope, $location, $filter, balanceService, notification, rateService) { var w = $rootScope.wallet; $scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; @@ -76,7 +75,7 @@ angular.module('copayApp.controllers').controller('MoreController', alternativeIsoCode: $scope.selectedAlternative.isoCode, }); notification.success('Success', $filter('translate')('settings successfully updated')); - controllerUtils.updateBalance(w, function() { + balanceService.update(w, function() { $rootScope.$digest(); }); }; @@ -84,9 +83,9 @@ angular.module('copayApp.controllers').controller('MoreController', $scope.purge = function(deleteAll) { var removed = w.purgeTxProposals(deleteAll); if (removed) { - controllerUtils.updateBalance(w, function() { + balanceService.update(w, function() { $rootScope.$digest(); - }); + }, true); } notification.info('Transactions Proposals Purged', removed + ' ' + $filter('translate')('transaction proposal purged')); }; @@ -99,12 +98,11 @@ angular.module('copayApp.controllers').controller('MoreController', if (err) { notification.error('Error', $filter('translate')('Error updating indexes: ') + err); } - controllerUtils.updateAddressList(); - controllerUtils.updateBalance(w, function() { + balanceService.update(w, function() { notification.info('Finished', 'The balance is updated using the derived addresses'); w.sendIndexes(); $rootScope.$digest(); - }); + }, true); }); }; }); diff --git a/js/controllers/paymentIntent.js b/js/controllers/paymentIntent.js index 93a40f5ce..a575383f6 100644 --- a/js/controllers/paymentIntent.js +++ b/js/controllers/paymentIntent.js @@ -1,26 +1,17 @@ 'use strict'; -var bitcore = require('bitcore'); - -angular.module('copayApp.controllers').controller('PaymentIntentController', function($rootScope, $scope, $modal, $location, controllerUtils) { +angular.module('copayApp.controllers').controller('PaymentIntentController', function($rootScope, $scope, $modal, $location, balanceService) { $scope.wallets = []; $rootScope.title = 'Payment intent'; - $rootScope.starting = true; + $scope.wallets = rootScope.iden.listWallets(); - var wids = _.pluck($rootScope.iden.listWallets(), 'id'); - _.each(wids, function(wid) { - var w = $rootScope.iden.getWalletById(wid); - if (w && w.isReady()) { - - $scope.wallets.push(w); - $rootScope.starting = false; - controllerUtils.clearBalanceCache(w); - controllerUtils.updateBalance(w, function() { + var l = $scope.wallet.length; + _.each($scope.wallets, function(w, i) { + balanceService.update(w, function(){ + if (i === l-1) $rootScope.$digest(); - }, true); - - } + }); }); $scope.open = function() { @@ -39,10 +30,10 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', fun // Please note that $modalInstance represents a modal window (instance) dependency. // It is not the same as the $modal service used above. - var ModalInstanceCtrl = function($scope, $modalInstance, items, controllerUtils) { + var ModalInstanceCtrl = function($scope, $modalInstance, items, identityService) { $scope.wallets = items; $scope.ok = function(selectedItem) { - controllerUtils.setPaymentWallet(selectedItem); + identityService.setPaymentWallet(selectedItem); $modalInstance.close(); }; diff --git a/js/controllers/profile.js b/js/controllers/profile.js index 1d5280968..ef8a471a7 100644 --- a/js/controllers/profile.js +++ b/js/controllers/profile.js @@ -1,5 +1,5 @@ 'use strict'; -angular.module('copayApp.controllers').controller('ProfileController', function($scope, $rootScope, $location, $modal, controllerUtils, backupService) { +angular.module('copayApp.controllers').controller('ProfileController', function($scope, $rootScope, $location, $modal, backupService) { $scope.username = $rootScope.iden.getName(); $scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; @@ -14,28 +14,11 @@ angular.module('copayApp.controllers').controller('ProfileController', function( $scope.hideViewProfileBackup = true; }; - $scope.getWallets = function() { - if (!$rootScope.iden) return; - $scope.wallets = []; - var wids = _.pluck($rootScope.iden.listWallets(), 'id'); - _.each(wids, function(wid) { - var w = $rootScope.iden.getWalletById(wid); - $scope.wallets.push(w); - controllerUtils.updateBalance(w, function() { - $rootScope.$digest(); - }, true); - }); - }; - $scope.deleteWallet = function(w) { if (!w) return; $scope.loading = w.id; - controllerUtils.deleteWallet($scope, w, function() { - if ($rootScope.wallet.id === w.id) { - $rootScope.wallet = null; - var lastFocused = $rootScope.iden.getLastFocusedWallet(); - controllerUtils.bindProfile($scope, $rootScope.iden, lastFocused); - } + + identityService.deleteWallet(w.id,function() { $scope.loading = false; $scope.getWallets(); }); diff --git a/js/controllers/receive.js b/js/controllers/receive.js index a3a34e49c..9401aa1e2 100644 --- a/js/controllers/receive.js +++ b/js/controllers/receive.js @@ -1,9 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('ReceiveController', - function($scope, $rootScope, $timeout, $modal, controllerUtils) { - controllerUtils.redirIfNotComplete(); - + function($scope, $rootScope, $timeout, $modal) { $rootScope.title = 'Receive'; $scope.loading = false; $scope.showAll = false; @@ -15,7 +13,6 @@ angular.module('copayApp.controllers').controller('ReceiveController', $scope.isNewAddr = false; w.generateAddress(null); $timeout(function() { - controllerUtils.updateAddressList(); $scope.loading = false; $scope.isNewAddr = true; }, 1); @@ -74,20 +71,22 @@ angular.module('copayApp.controllers').controller('ReceiveController', $scope.addressList = function() { $scope.addresses = []; + var w = $rootScope.wallet; + var balance = $rootScope.balanceByAddr; - if ($rootScope.addrInfos) { - var addrInfos = $rootScope.addrInfos; - $scope.addrLength = addrInfos.length; - for (var i = 0; i < addrInfos.length; i++) { - var addrinfo = addrInfos[i]; + var addresses = w.getAddresses(); + if (addresses) { + $scope.addrLength = addresses.length; + _.each(addresses, function(address, index){ $scope.addresses.push({ - 'index': i, - 'address': addrinfo.addressStr, - 'balance': $rootScope.balanceByAddr ? $rootScope.balanceByAddr[addrinfo.addressStr] : 0, - 'isChange': addrinfo.isChange, - 'owned': addrinfo.owned + 'index': index, + 'address': address, + 'balance': balance ? balance[address] : 0, + 'isChange': w.addressIsChange(address), + // TODO + 'owned': w.addressIsOwn(address), }); - } + }); $scope.addresses = $scope.limitAddress($scope.addresses, $scope.isNewAddr); } }; diff --git a/js/controllers/send.js b/js/controllers/send.js index d6b1aecb2..4b98044e7 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -3,10 +3,7 @@ var bitcore = require('bitcore'); var preconditions = require('preconditions').singleton(); angular.module('copayApp.controllers').controller('SendController', - function($scope, $rootScope, $window, $timeout, $modal, isMobile, notification, controllerUtils, rateService) { - - controllerUtils.redirIfNotComplete(); - + function($scope, $rootScope, $window, $timeout, $modal, isMobile, notification, rateService) { var w = $rootScope.wallet; preconditions.checkState(w); preconditions.checkState(w.settings.unitToSatoshi); @@ -33,6 +30,34 @@ angular.module('copayApp.controllers').controller('SendController', $scope.$digest(); }); + $scope.setAlternativeAmount = function(w, tx, cb) { + rateService.whenAvailable(function() { + _.each(tx.outs, function(out) { + var valueSat = out.value * w.settings.unitToSatoshi; + out.alternativeAmount = rateService.toFiat(valueSat, w.settings.alternativeIsoCode); + out.alternativeIsoCode = w.settings.alternativeIsoCode; + }); + if (cb) return cb(tx); + }); + }; + + $scope.updateTxs = function() { + var w = $rootScope.wallet; + if (!w) return; + + var res = w.getPendingTxProposals(); + _.each(res.txs, function(tx) { + $scope.setAlternativeAmount(w, tx); + if (tx.merchant) { + var url = tx.merchant.request_url; + var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1]; + tx.merchant.domain = domain; + } + }); + $scope.txps = res.txs; +// TODO +// $rootScope.pendingTxCount = res.pendingForUs; + }; /** * Setting the two related amounts as properties prevents an infinite @@ -76,7 +101,7 @@ angular.module('copayApp.controllers').controller('SendController', $scope.loadTxs = function() { - controllerUtils.updateTxs(); + $scope.updateTxs(); setTimeout(function() { $scope.loading = false; $rootScope.$digest(); diff --git a/js/controllers/settings.js b/js/controllers/settings.js index 2756f55d3..3c24d3604 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -1,8 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $route, $location, $anchorScroll, controllerUtils, notification) { - controllerUtils.redirIfLogged(); - +angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $route, $location, $anchorScroll, notification) { $scope.title = 'Settings'; $scope.defaultLanguage = config.defaultLanguage || 'en'; $scope.insightLivenet = config.network.livenet.url; diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js index e64de73db..760c15739 100644 --- a/js/controllers/sidebar.js +++ b/js/controllers/sidebar.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, controllerUtils) { +angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, $timeout, identityService) { $scope.menu = [{ 'title': 'Home', @@ -24,21 +24,6 @@ angular.module('copayApp.controllers').controller('SidebarController', function( 'link': 'more' }]; - $scope.refresh = function() { - var w = $rootScope.wallet; - if (!w) return; - - if (w.isReady()) { - w.sendWalletReady(); - if ($rootScope.addrInfos.length > 0) { - controllerUtils.clearBalanceCache(w); - controllerUtils.updateBalance(w, function() { - $rootScope.$digest(); - }); - } - } - }; - $scope.signout = function() { $scope.$emit('signout'); }; @@ -47,35 +32,32 @@ angular.module('copayApp.controllers').controller('SidebarController', function( return item.link && item.link == $location.path().split('/')[1]; }; - if ($rootScope.wallet) { - $rootScope.$watch('wallet.id', function() { - $scope.walletSelection = false; - $scope.getWallets(); - }); - } - $scope.switchWallet = function(wid) { - controllerUtils.setFocusedWallet(wid); + identityService.setFocusedWallet(wid); }; $scope.toggleWalletSelection = function() { $scope.walletSelection = !$scope.walletSelection; if (!$scope.walletSelection) return; - - $scope.getWallets(); + $scope.setWallets(); }; - $scope.getWallets = function() { + + $scope.init = function() { + if ($rootScope.wallet) { + $rootScope.$watch('wallet', function() { + $scope.walletSelection = false; + $scope.setWallets(); + }); + } + }; + + + $scope.setWallets = function() { if (!$rootScope.iden) return; - $scope.wallets = []; - var wids = _.pluck($rootScope.iden.listWallets(), 'id'); - _.each(wids, function(wid) { - if (controllerUtils.isFocusedWallet(wid)) return; - var w = $rootScope.iden.getWalletById(wid); - $scope.wallets.push(w); - controllerUtils.updateBalance(w, function() { - $rootScope.$digest(); - }) + var ret = _.filter($rootScope.iden.listWallets(), function(w) { + return !identityService.isFocused(w.getId()); }); + $scope.wallets = ret; }; }); diff --git a/js/controllers/warning.js b/js/controllers/warning.js index 75f19ff56..175c0f6c9 100644 --- a/js/controllers/warning.js +++ b/js/controllers/warning.js @@ -1,16 +1,14 @@ 'use strict'; -angular.module('copayApp.controllers').controller('WarningController', function($scope, $rootScope, $location, controllerUtils) { - +angular.module('copayApp.controllers').controller('WarningController', function($scope, $rootScope, $location, identityService) { $scope.checkLock = function() { if (!$rootScope.tmp || !$rootScope.tmp.getLock()) { - controllerUtils.redirIfLogged(); + console.log('[warning.js.7] TODO LOCK'); //TODO } }; - $scope.signout = function() { - controllerUtils.logout(); + identityService.logout(); }; $scope.ignoreLock = function() { @@ -22,7 +20,8 @@ angular.module('copayApp.controllers').controller('WarningController', function( } else { w.ignoreLock = 1; $scope.loading = true; - controllerUtils.startNetwork(w, $scope); + //controllerUtils.startNetwork(w, $scope); + // TODO } }; }); diff --git a/js/models/Identity.js b/js/models/Identity.js index 5b4fa72d3..8b93af888 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -172,11 +172,7 @@ Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, callback blockchainOpts: self.blockchainOpts, skipFields: [] }; - - return callback(null, importFunction(walletData, readOpts)); - } catch (e) { - log.debug("ERROR: ", e.message); if (e && e.message && e.message.indexOf('MISSOPTS') !== -1) { return callback(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message)); @@ -184,6 +180,7 @@ Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, callback return callback(e); } } + return callback(null, importFunction(walletData, readOpts)); }); }; @@ -564,6 +561,9 @@ Identity.prototype.listWallets = function() { Identity.prototype.deleteWallet = function(walletId, cb) { var self = this; + var w = this.getWalletById(walletId); + w.close(); + delete this.wallets[walletId]; this.storage.removeItem(Wallet.getStorageKey(walletId), function(err) { if (err) { diff --git a/js/models/Insight.js b/js/models/Insight.js index 0dd6182a2..d0de500cd 100644 --- a/js/models/Insight.js +++ b/js/models/Insight.js @@ -200,8 +200,6 @@ Insight.prototype.subscribe = function(addresses) { addresses = Array.isArray(addresses) ? addresses : [addresses]; var self = this; -console.log('[Insight.js.202] subscribe STARTED'); //TODO - function handlerFor(self, address) { return function(txid) { // verify the address is still subscribed @@ -293,6 +291,7 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) { }; Insight.prototype.getUnspent = function(addresses, cb) { +console.log('[Insight.js.296:addresses:]',addresses); //TODO preconditions.shouldBeArray(addresses); preconditions.shouldBeFunction(cb); diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js index c12220ff4..083708bac 100644 --- a/js/models/PublicKeyRing.js +++ b/js/models/PublicKeyRing.js @@ -518,6 +518,20 @@ PublicKeyRing.prototype.getAddresses = function() { return ret; }; +/** + * @desc + * Gets information about addresses for a copayer + * + * @param {Object} opts + * @returns {AddressInfo[]} + */ +PublicKeyRing.prototype.getReceiveAddresses = function() { + this._checkAndRebuildCache(); + var ret = this.cache.receiveAddresses; + return ret; +}; + + /** * @desc @@ -526,43 +540,43 @@ PublicKeyRing.prototype.getAddresses = function() { * @param {string} path - the BIP32 path * @return {Buffer[]} the public keys, in buffer format */ -PublicKeyRing.prototype.getForPath = function(path) { +PublicKeyRing.prototype._getForPath = function(path) { var p = HDPath.indexesForPath(path); return this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex); }; -/** - * @desc - * Retrieve the public keys for all cosigners for multiple paths - * @see PublicKeyRing#getForPath - * - * @param {string[]} paths - the BIP32 paths - * @return {Array[]} the public keys, in buffer format (matrix of Buffer, Buffer[][]) - */ -PublicKeyRing.prototype.getForPaths = function(paths) { - preconditions.checkArgument(!_.isUndefined(paths)); - preconditions.checkArgument(_.isArray(paths)); - preconditions.checkArgument(_.all(paths, _.isString)); - - return paths.map(this.getForPath.bind(this)); -}; /** * @desc * Retrieve the public keys for derived addresses and the public keys for copayers * - * @TODO: Should this exist? A user should just call getForPath(paths) + * @TODO: Should this exist? A user should just call _getForPath(paths) * * @param {string[]} paths - the paths to be derived * @return {Object} with keys pubKeys and copayerIds */ PublicKeyRing.prototype.forPaths = function(paths) { return { - pubKeys: paths.map(this.getForPath.bind(this)), + pubKeys: paths.map(this._getForPath.bind(this)), copayerIds: this.copayerIds, } }; +/** + * @desc + * Retrieve the public keys for all cosigners for multiple paths + * + * @param {string[]} paths - the BIP32 paths + * @return {Array[]} the public keys, in buffer format (matrix of Buffer, Buffer[][]) + */ +PublicKeyRing.prototype._getForPaths = function(paths) { + preconditions.checkArgument(!_.isUndefined(paths)); + preconditions.checkArgument(_.isArray(paths)); + preconditions.checkArgument(_.all(paths, _.isString)); + + return paths.map(this._getForPath.bind(this)); +}; + /** * @desc * Returns a map from a pubkey of an address to the id that generated it @@ -580,7 +594,7 @@ PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { inKeyMap[pubkeys[i]] = 1; }; - var keys = this.getForPaths(paths); + var keys = this._getForPaths(paths); for (var i in keys) { for (var copayerIndex in keys[i]) { var kHex = keys[i][copayerIndex].toString('hex'); diff --git a/js/models/Wallet.js b/js/models/Wallet.js index f9def93a7..a97e96888 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -915,6 +915,8 @@ Wallet.prototype._setBlockchainListeners = function() { } } + + /** * @desc Sets up the networking with other peers. * @@ -1983,29 +1985,25 @@ Wallet.prototype.addSeenToTxProposals = function() { /** * @desc Alias for {@link PublicKeyRing#getAddresses} - * @TODO: remove this method and use getAddressesInfo everywhere * @return {Buffer[]} */ -Wallet.prototype.getAddresses = function(opts) { - return this.publicKeyRing.getAddresses(opts); +Wallet.prototype.getAddresses = function() { + return this.publicKeyRing.getAddresses(); }; /** - * @desc Retrieves all addresses as strings. - * - * @param {Object} opts - Same options as {@link PublicKeyRing#getAddresses} - * @return {string[]} + * @desc Alias for {@link PublicKeyRing#getAddresses} + * @return {Buffer[]} */ -Wallet.prototype.getAddressesStr = function(opts) { - return this.getAddresses(opts).map(function(a) { - return a.toString(); - }); +Wallet.prototype.getReceiveAddresses = function() { + return this.publicKeyRing.getReceiveAddresses(); }; + Wallet.prototype.subscribeToAddresses = function() { if (!this.publicKeyRing.isComplete()) return; - var addresses = this.publicKeyRing.getAddresses(); + var addresses = this.getAddresses(); this.blockchain.subscribe(addresses); log.debug('Subscribed to ' + addresses.length + ' addresses'); }; @@ -2442,7 +2440,9 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) * @desc Closes the wallet and disconnects all services */ Wallet.prototype.close = function(cb) { + this.network.removeAllListeners(); this.network.cleanUp(); + this.blockchain.removeAllListeners(); this.blockchain.destroy(); log.debug('## CLOSING Wallet: ' + this.id); @@ -2664,10 +2664,9 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { }; if (addresses.length > 0) { - var addressesStr = _.pluck(addresses, 'addressStr'); var from = (opts.currentPage - 1) * opts.itemsPerPage; var to = opts.currentPage * opts.itemsPerPage; - self.blockchain.getTransactions(addressesStr, from, to, function(err, res) { + self.blockchain.getTransactions(addresses, from, to, function(err, res) { if (err) return cb(err); _.each(res.items, function(tx) { diff --git a/js/routes.js b/js/routes.js index a9e0a59e7..35f4f1ddd 100644 --- a/js/routes.js +++ b/js/routes.js @@ -47,26 +47,32 @@ angular }) .when('/homeWallet', { templateUrl: 'views/homeWallet.html', + walletShouldBeReady: true, logged: true }) .when('/receive', { templateUrl: 'views/receive.html', + walletShouldBeReady: true, logged: true }) .when('/history', { templateUrl: 'views/history.html', + walletShouldBeReady: true, logged: true }) .when('/send', { templateUrl: 'views/send.html', + walletShouldBeReady: true, logged: true }) .when('/more', { templateUrl: 'views/more.html', + walletShouldBeReady: true, logged: true }) .when('/settings', { templateUrl: 'views/settings.html', + walletShouldBeReady: true, logged: false }) .when('/warning', { @@ -119,6 +125,9 @@ angular $idle.unwatch(); $location.path('/'); } + if ($rootScope.wallet && !$rootScope.wallet.isReady() && next.walletShouldBeReady) { + $location.path('/copayers'); + } } }); }) diff --git a/js/services/balanceService.js b/js/services/balanceService.js new file mode 100644 index 000000000..49a008f3f --- /dev/null +++ b/js/services/balanceService.js @@ -0,0 +1,84 @@ +'use strict'; +var bitcore = require('bitcore'); + +angular.module('copayApp.services') + .factory('balanceService', function($rootScope, $sce, $location, $filter, notification, $timeout, rateService) { + var root = {}; + var _balanceCache = {}; + root.clearBalanceCache = function(w) { + delete _balanceCache[w.getId()]; + }; + + root._fetchBalance = function(w, cb) { + cb = cb || function() {}; + var satToUnit = 1 / w.settings.unitToSatoshi; + var COIN = bitcore.util.COIN; + + console.log('[balanceS.js.257] FETCH BALANCE: ', w.getName()); //TODO + w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat, safeUnspentCount) { + if (err) return cb(err); + + var r = {}; + r.totalBalance = balanceSat * satToUnit; + r.totalBalanceBTC = (balanceSat / COIN); + r.availableBalance = safeBalanceSat * satToUnit; + r.availableBalanceBTC = (safeBalanceSat / COIN); + r.safeUnspentCount = safeUnspentCount; + + r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit; + r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN; + + + if (r.safeUnspentCount) { + var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount); + r.topAmount = (((r.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi); + } + + var balanceByAddr = {}; + for (var ii in balanceByAddrSat) { + balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit; + } + r.balanceByAddr = balanceByAddr; + + if (rateService.isAvailable()) { + r.totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode); + r.alternativeIsoCode = w.settings.alternativeIsoCode; + r.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode); + r.alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode); + r.alternativeBalanceAvailable = true; + }; + + r.updatingBalance = false; + + return cb(null, r) + }); + }; + + root.update = function(w, cb, isFocused) { + console.log(' UPDATE BALANCE!!!!', w ? w.getName() : 'current'); //TODO + + w = w || $rootScope.wallet; + if (!w || !w.isReady()) return; + + console.log('DO UPDATE BALANCE!!!!', w.getName()); //TODO + var wid = w.getId(); + + if (_balanceCache[wid]) { + w.balanceInfo = _balanceCache[wid]; + } else { + $rootScope.updatingBalance = true; + } + + root._fetchBalance(w, function(err, res) { + if (err) throw err; + w.balanceInfo=_balanceCache[wid] = res; + $rootScope.updatingBalance = false; + if (isFocused) { + _.extend($rootScope, w.balanceInfo); + } + if (cb) cb(); + }); + }; + + return root; + }); diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js deleted file mode 100644 index 07d157720..000000000 --- a/js/services/controllerUtils.js +++ /dev/null @@ -1,396 +0,0 @@ -'use strict'; -var bitcore = require('bitcore'); - -angular.module('copayApp.services') - .factory('controllerUtils', function($rootScope, $sce, $location, $filter, notification, $timeout, rateService) { - var root = {}; - - - root.redirIfNotComplete = function() { - var w = $rootScope.wallet; - if (w) { - if (!w.isReady()) { - $location.path('/copayers'); - } - } else { - $location.path('/'); - } - }; - - - root.redirIfLogged = function() { - var w = $rootScope.wallet; - if (w) { - if (!w.isReady()) { - $location.path('/copayers'); - } else { - $location.path('homeWallet'); - } - } - }; - - root.logout = function() { - - if ($rootScope.iden) { - $rootScope.iden.store(null, function() { - $rootScope.iden.close(); - - delete $rootScope['wallet']; - delete $rootScope['iden']; - - // Go home reloading the application - var hashIndex = window.location.href.indexOf('#!/'); - window.location = window.location.href.substr(0, hashIndex); - }); - } - }; - - root.onError = function(scope) { - if (scope) { - scope.loading = false; - } - } - - root.onErrorDigest = function(scope, msg) { - root.onError(scope); - if (msg) { - notification.error('Error', msg); - } - }; - - - root.isFocusedWallet = function(wid) { - return $rootScope.wallet && wid === $rootScope.wallet.getId(); - }; - - - - root.installWalletHandlers = function($scope, w) { - - var wid = w.getId(); - w.on('connectionError', function() { - if (root.isFocusedWallet(wid)) { - var message = "Could not connect to the Insight server. Check your settings and network configuration"; - notification.error('Networking Error', message); - root.onErrorDigest($scope); - } - }); - - w.on('corrupt', function(peerId) { - if (root.isFocusedWallet(wid)) { - notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId); - } - }); - w.on('ready', function(myPeerID) { - $scope.loading = false; - if ($rootScope.initialConnection) { - $rootScope.initialConnection = false; - if ($rootScope.pendingPayment) { - $location.path('paymentIntent'); - } else { - root.redirIfLogged(); - } - } - }); - - w.on('tx', function(address, isChange) { - if (!isChange) { - notification.funds('Funds received on ' + w.getName(), address); - } - root.updateBalance(w, function() { - $rootScope.$digest(); - }); - }); - - w.on('balanceUpdated', function() { - root.updateBalance(w, function() { - $rootScope.$digest(); - }); - }); - - w.on('insightReconnected', function() { - $rootScope.reconnecting = false; - root.updateAddressList(w.getId()); - root.updateBalance(w, function() { - $rootScope.$digest(); - }); - }); - - w.on('insightError', function() { - if (root.isFocusedWallet(wid)) { - $rootScope.reconnecting = true; - $rootScope.$digest(); - } - }); - w.on('newAddresses', function() { - root.updateBalance(w); - }); - - w.on('txProposalsUpdated', function() { - if (root.isFocusedWallet(wid)) { - root.updateTxs(); - } - }); - - w.on('paymentACK', function(memo) { - notification.success('Payment Acknowledged', memo); - }); - - w.on('txProposalEvent', function(e) { - - if (root.isFocusedWallet(wid)) { - root.updateTxs(); - } - - // TODO: add wallet name notification - var user = w.publicKeyRing.nicknameForCopayer(e.cId); - var name = w.getName(); - switch (e.type) { - case 'new': - notification.info('[' + name + '] New Transaction', - $filter('translate')('You received a transaction proposal from') + ' ' + user); - break; - case 'signed': - notification.info('[' + name + '] Transaction Signed', - $filter('translate')('A transaction was signed by') + ' ' + user); - break; - case 'signedAndBroadcasted': - notification.info('[' + name + '] Transaction Approved', - $filter('translate')('A transaction was signed and broadcasted by') + ' ' + user); - break; - case 'rejected': - notification.info('[' + name + '] Transaction Rejected', - $filter('translate')('A transaction was rejected by') + ' ' + user); - break; - case 'corrupt': - notification.error('[' + name + '] Transaction Error', - $filter('translate')('Received corrupt transaction from') + ' ' + user); - break; - } - $rootScope.$digest(); - }); - w.on('addressBookUpdated', function(dontDigest) { - if (root.isFocusedWallet(wid)) { - if (!dontDigest) { - $rootScope.$digest(); - } - } - }); - w.on('connect', function(peerID) { - $rootScope.$digest(); - }); - w.on('close', root.onErrorDigest); - w.on('locked', root.onErrorDigest.bind(this)); - - }; - - root.setupGlobalVariables = function(iden) { - notification.enableHtml5Mode(); // for chrome: if support, enable it - $rootScope.unitName = config.unitName; - $rootScope.pendingTxCount = 0; - $rootScope.initialConnection = true; - $rootScope.reconnecting = false; - $rootScope.isCollapsed = true; - - $rootScope.iden = iden; - }; - - - root.rebindWallets = function($scope, iden) { - _.each(iden.listWallets(), function(wallet) { - preconditions.checkState(wallet); - root.installWalletHandlers($scope, wallet); - }); - }; - - root.setPaymentWallet = function(w) { - root.setFocusedWallet(w); - $location.path('/send'); - }; - - root.setFocusedWallet = function(w) { - if (!_.isObject(w)) - w = $rootScope.iden.getWalletById(w); - - preconditions.checkState(w && _.isObject(w)); - - $rootScope.wallet = w; - w.updateFocusedTimestamp(Date.now()); - root.redirIfLogged(); - $timeout(function(){ - $rootScope.$digest(); - },1) - // root.updateTxs(); - // root.updateBalance(w, function() { - // $rootScope.$digest(); - // }) - }; - - root.bindProfile = function($scope, iden, w) { - -console.log('[controllerUtils.js.230] bindProfile Globals'); //TODO - root.setupGlobalVariables(iden); -console.log('[controllerUtils.js.230] bindProfile Wallets'); //TODO - root.rebindWallets($scope, iden); - if (w) { -console.log('[controllerUtils.js.230] bindProfile set Focus'); //TODO - root.setFocusedWallet(w); - } else { - $location.path('/create'); - } - $timeout(function() { - -console.log('[controllerUtils.js.242] DIGEST'); //TODO - $rootScope.$digest() -console.log('[controllerUtils.js.242] DIGEST DONE'); //TODO - }, 1); - }; - - // On the focused wallet - root.updateAddressList = function(wid) { - - if (!wid || root.isFocusedWallet(wid)) { - var w = $rootScope.wallet; - - if (w && w.isReady()) { - $rootScope.addrInfos = w.getAddressesInfo(); - } - } - }; - - var _balanceCache = {}; - root.clearBalanceCache = function(w) { - delete _balanceCache[w.getId()]; - }; - - - root._fetchBalance = function(w, cb) { - cb = cb || function() {}; - var satToUnit = 1 / w.settings.unitToSatoshi; - var COIN = bitcore.util.COIN; - - w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat, safeUnspentCount) { - if (err) return cb(err); - - var r = {}; - r.totalBalance = balanceSat * satToUnit; - r.totalBalanceBTC = (balanceSat / COIN); - r.availableBalance = safeBalanceSat * satToUnit; - r.availableBalanceBTC = (safeBalanceSat / COIN); - r.safeUnspentCount = safeUnspentCount; - - r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit; - r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN; - - - if (r.safeUnspentCount) { - var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount); - r.topAmount = (((r.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi); - } - - var balanceByAddr = {}; - for (var ii in balanceByAddrSat) { - balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit; - } - r.balanceByAddr = balanceByAddr; - root.updateAddressList(); - - - - if (rateService.isAvailable()) { - r.totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode); - r.alternativeIsoCode = w.settings.alternativeIsoCode; - r.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode); - r.alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode); - r.alternativeBalanceAvailable = true; - }; - - r.updatingBalance = false; - - return cb(null, r) - }); - }; - - root._updateScope = function(w, data, scope, cb) { - _.each(data, function(v, k) { - scope[k] = data[k]; - }) - if (cb) return cb(); - }; - - root.updateBalance = function(w, cb, refreshAll) { - - return - cb?cb(): null; - - - w = w || $rootScope.wallet; - if (!w) return root.onErrorDigest(); - if (!w.isReady()) return; - - w.balanceInfo = {}; - var scope = root.isFocusedWallet(w.id) && !refreshAll ? $rootScope : w.balanceInfo; - - var wid = w.getId(); - - if (_balanceCache[wid]) { - root._updateScope(w, _balanceCache[wid], scope, function() { - if (root.isFocusedWallet(w.id) && !refreshAll) { - setTimeout(function() { - $rootScope.$digest(); - }, 1); - } - }); - } else { - scope.updatingBalance = true; - } - - root._fetchBalance(w, function(err, res) { - if (err) throw err; - _balanceCache[wid] = res; - root._updateScope(w, _balanceCache[wid], scope, function() { - scope.updatingBalance = false; - if (cb) cb(); - }); - }); - }; - - root.setAlternativeAmount = function(w, tx, cb) { - rateService.whenAvailable(function() { - _.each(tx.outs, function(out) { - var valueSat = out.value * w.settings.unitToSatoshi; - out.alternativeAmount = rateService.toFiat(valueSat, w.settings.alternativeIsoCode); - out.alternativeIsoCode = w.settings.alternativeIsoCode; - }); - if (cb) return cb(tx); - }); - }; - - root.updateTxs = function() { - var w = $rootScope.wallet; - if (!w) return; - - var res = w.getPendingTxProposals(); - _.each(res.txs, function(tx) { - root.setAlternativeAmount(w, tx); - if (tx.merchant) { - var url = tx.merchant.request_url; - var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1]; - tx.merchant.domain = domain; - } - }); - $rootScope.txps = res.txs; - $rootScope.pendingTxCount = res.pendingForUs; - }; - - root.deleteWallet = function($scope, w, cb) { - if (!w) return root.onErrorDigest(); - var name = w.getName(); - $rootScope.iden.deleteWallet(w.id, function() { - notification.info(name + ' deleted', $filter('translate')('This wallet was deleted')); - return cb(); - }); - }; - - return root; - }); diff --git a/js/services/identityService.js b/js/services/identityService.js index 492bf0f4b..734722552 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.services') - .factory('identityService', function($rootScope, $location, $timeout, pluginManager, controllerUtils) { + .factory('identityService', function($rootScope, $location, $timeout, $filter, pluginManager, notification, pendingTxsService, balanceService) { var root = {}; root.check = function(scope) { @@ -22,7 +22,23 @@ angular.module('copayApp.services') }); }; + root.goWalletHome = function() { + var w = $rootScope.wallet; + if (w) { + if (!w.isReady()) { + $location.path('/copayers'); + } else { + if ($rootScope.pendingPayment) { + $location.path('paymentIntent'); + } else { + $location.path('homeWallet'); + } + } + } + }; + root.create = function(scope, form) { + $rootScope.starting = true; copay.Identity.create({ email: form.email.$modelValue, password: form.password.$modelValue, @@ -43,7 +59,7 @@ angular.module('copayApp.services') $rootScope.starting = false; $timeout(function() { $rootScope.$digest() - }, 1); + }, 1); return; } var walletOptions = { @@ -64,7 +80,7 @@ angular.module('copayApp.services') }, 1); return; } - controllerUtils.bindProfile(scope, iden, wallet.id); + root.bind(scope, iden, wallet.id); }); }); @@ -72,6 +88,7 @@ angular.module('copayApp.services') root.open = function(scope, form) { + $rootScope.starting = true; copay.Identity.open({ email: form.email.$modelValue, password: form.password.$modelValue, @@ -81,24 +98,269 @@ angular.module('copayApp.services') walletDefaults: config.wallet, passphraseConfig: config.passphraseConfig, }, function(err, iden) { + $rootScope.starting = false; if (err && !iden) { if ((err.toString() || '').match('PNOTFOUND')) { scope.error = 'Invalid email or password'; } else { scope.error = 'Unknown error'; } - $rootScope.starting = false; $timeout(function() { $rootScope.$digest() - }, 1); + }, 1); } else { -console.log('[identityService.js.95] LISTO OPEN!!'); //TODO + console.log('[identityService.js.95] LISTO OPEN!!'); //TODO var firstWallet = iden.getLastFocusedWallet(); - controllerUtils.bindProfile(scope, iden, firstWallet); + root.bind(scope, iden, firstWallet); } }); }; + root.deleteWallet = function($scope, iden, w) { + $rootScope.iden.deleteWallet(w.id, function() { + notification.info(name + ' deleted', $filter('translate')('This wallet was deleted')); + if ($rootScope.wallet.id === w.id) { + $rootScope.wallet = null; + var lastFocused = $rootScope.iden.getLastFocusedWallet(); + root.bind($scope, $rootScope.iden, lastFocused); + } + }); + }; + + root.isFocused = function(wid) { + return $rootScope.wallet && wid === $rootScope.wallet.getId(); + }; + + root.setupGlobalVariables = function(iden) { + notification.enableHtml5Mode(); // for chrome: if support, enable it + $rootScope.unitName = config.unitName; + $rootScope.pendingTxCount = 0; + $rootScope.initialConnection = true; + $rootScope.reconnecting = false; + $rootScope.isCollapsed = true; + + $rootScope.iden = iden; + }; + + root.setPaymentWallet = function(w) { + root.setFocusedWallet(w); + $location.path('/send'); + }; + + root.setFocusedWallet = function(w) { + if (!_.isObject(w)) + w = $rootScope.iden.getWalletById(w); + + preconditions.checkState(w && _.isObject(w)); + $rootScope.wallet = w; + w.updateFocusedTimestamp(Date.now()); + root.goWalletHome(); + pendingTxsService.update(); + + console.log('[controllerUtils.js.221] SET FOCUS'); //TODO + balanceService.update(w, function() { + $rootScope.$digest(); + }, true) + }; + + root.installWalletHandlers = function($scope, w) { + var wid = w.getId(); + w.on('connectionError', function() { + console.log('err', w.getName()); //TODO + if (root.isFocused(wid)) { + var message = "Could not connect to the Insight server. Check your settings and network configuration"; + notification.error('Networking Error', message); + } + }); + + w.on('corrupt', function(peerId) { + console.log('corr', w.getName()); //TODO + if (root.isFocused(wid)) { + notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId); + } + }); + w.on('ready', function() { + console.log('read', w.getName()); //TODO + $scope.loading = false; + if ($rootScope.initialConnection) { + $rootScope.initialConnection = false; + root.goWalletHome(); + } + }); + + w.on('tx', function(address, isChange) { + console.log('tx', w.getName()); //TODO + if (!isChange) { + notification.funds('Funds received on ' + w.getName(), address); + } + balanceService.update(w, function() { + $rootScope.$digest(); + }, root.isFocused(wid)); + }); + + w.on('balanceUpdated', function() { + console.log('b', w.getName()); //TODO + balanceService.update(w, function() { + $rootScope.$digest(); + }, root.isFocused(wid)); + }); + + w.on('insightReconnected', function() { + console.log('i', w.getName()); //TODO + $rootScope.reconnecting = false; + balanceService.update(w, function() { + $rootScope.$digest(); + }, root.isFocused(wid)); + }); + + w.on('insightError', function() { + console.log('i', w.getName()); //TODO + if (root.isFocused(wid)) { + $rootScope.reconnecting = true; + $rootScope.$digest(); + } + }); + w.on('newAddresses', function() { + console.log('newAddress', w.getName()); //TODO + }); + + w.on('txProposalsUpdated', function() { + if (root.isFocused(wid)) { + pendingTxsService.update(); + } + }); + + w.on('paymentACK', function(memo) { + notification.success('Payment Acknowledged', memo); + }); + + w.on('txProposalEvent', function(e) { + if (root.isFocused(wid)) { + pendingTxsService.update(); + } + + // TODO: add wallet name notification + var user = w.publicKeyRing.nicknameForCopayer(e.cId); + var name = w.getName(); + switch (e.type) { + case 'new': + notification.info('[' + name + '] New Transaction', + $filter('translate')('You received a transaction proposal from') + ' ' + user); + break; + case 'signed': + notification.info('[' + name + '] Transaction Signed', + $filter('translate')('A transaction was signed by') + ' ' + user); + break; + case 'signedAndBroadcasted': + notification.info('[' + name + '] Transaction Approved', + $filter('translate')('A transaction was signed and broadcasted by') + ' ' + user); + break; + case 'rejected': + notification.info('[' + name + '] Transaction Rejected', + $filter('translate')('A transaction was rejected by') + ' ' + user); + break; + case 'corrupt': + notification.error('[' + name + '] Transaction Error', + $filter('translate')('Received corrupt transaction from') + ' ' + user); + break; + } + $rootScope.$digest(); + }); + w.on('addressBookUpdated', function(dontDigest) { + if (root.isFocused(wid)) { + if (!dontDigest) { + $rootScope.$digest(); + } + } + }); + w.on('connect', function(peerID) { + $rootScope.$digest(); + }); + // TODO? + // w.on('close', ); + // w.on('locked',); + }; + + root.rebindWallets = function($scope, iden) { + _.each(iden.listWallets(), function(wallet) { + preconditions.checkState(wallet); + root.installWalletHandlers($scope, wallet); + }); + }; + + root.bind = function($scope, iden, w) { + console.log('ident bind Globals'); //TODO + root.setupGlobalVariables(iden); + root.rebindWallets($scope, iden); + if (w) { + root.setFocusedWallet(w); + } else { + $location.path('/create'); + } + $timeout(function() { + console.log('[controllerUtils.js.242] DIGEST'); //TODO + $rootScope.$digest() + console.log('[controllerUtils.js.242] DIGEST DONE'); //TODO + }, 1); + }; + + root.logout = function() { + if ($rootScope.iden) { + $rootScope.iden.store(null, function() { + $rootScope.iden.close(); + + delete $rootScope['wallet']; + delete $rootScope['iden']; + + // Go home reloading the application + var hashIndex = window.location.href.indexOf('#!/'); + window.location = window.location.href.substr(0, hashIndex); + }); + } + }; + + root.createWallet = function(opts, cb) { + $rootScope.iden.createWallet(opts, function(err, w) { + root.installWalletHandlers($scope, w); + root.setFocusedWallet(w); + return cb(); + }); + }; + + root.importWallet = function(encryptedObj, pass, opts, cb) { + copay.Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj, + pass, opts, function(err, wallet) { + if (err) return cb(err); + root.installWalletHandlers($scope, wallet); + root.setFocusedWallet(wallet); + return cb(); + }); + }; + + root.joinWallet = function(opts, cb) { + $rootScope.iden.joinWallet(opts, function(err, w) { + $scope.loading = false; + if (err || !w) { + if (err === 'joinError') + notification.error('Fatal error connecting to Insight server'); + else if (err === 'walletFull') + notification.error('The wallet is full'); + else if (err === 'badNetwork') + notification.error('Network Error', 'Wallet network configuration missmatch'); + else if (err === 'badSecret') + notification.error('Bad secret', 'The secret string you entered is invalid'); + else { + notification.error('Error', err.message || err); + } + } else { + root.installWalletHandlers($scope, w); + root.setFocusedWallet(w); + } + return cb(err); + }); + }; + + return root; }); diff --git a/test/PublicKeyRing.js b/test/PublicKeyRing.js index ca0a8d648..39f7d0491 100644 --- a/test/PublicKeyRing.js +++ b/test/PublicKeyRing.js @@ -439,6 +439,33 @@ describe('PublicKeyRing model', function() { }); + it('#fromObj old backup ', function() { + var pkr = PublicKeyRing.fromObj(JSON.parse(obj)); + should.exist(pkr); + pkr.isComplete().should.equal(true); + pkr.requiredCopayers.should.equal(2); + pkr.totalCopayers.should.equal(2); + }); + + it('#fromObj #toObj rountrip', function() { + var obj2 = PublicKeyRing.fromObj(JSON.parse(obj)).toObj(); + var pkr = PublicKeyRing.fromObj(obj2); + pkr.isComplete().should.equal(true); + pkr.requiredCopayers.should.equal(2); + pkr.totalCopayers.should.equal(2); + }); + + it('#fromUntrustedObj #toObj rountrip', function() { + var obj2 = PublicKeyRing.fromUntrustedObj(JSON.parse(obj)).toObj(); + var pkr = PublicKeyRing.fromUntrustedObj(obj2); + pkr.isComplete().should.equal(true); + pkr.requiredCopayers.should.equal(2); + pkr.totalCopayers.should.equal(2); + }); + + + + it('#getHDParams should return the right one', function() { var config = { networkName: 'livenet', @@ -482,15 +509,15 @@ describe('PublicKeyRing model', function() { }); }); - it('#getForPath should return 5 pubkeys', function() { + it('#_getForPath should return 5 pubkeys', function() { var w = getCachedW().w; - var pubkeys = w.getForPath('m/45\'/2147483647/1/0'); + var pubkeys = w._getForPath('m/45\'/2147483647/1/0'); pubkeys.length.should.equal(5); }); - it('#getForPaths should return 2 arrays of 5 pubkey ', function() { + it('#_getForPaths should return 2 arrays of 5 pubkey ', function() { var w = getCachedW().w; - var pubkeys = w.getForPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']); + var pubkeys = w._getForPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']); pubkeys.length.should.equal(2); pubkeys[0].length.should.equal(5); pubkeys[1].length.should.equal(5); @@ -505,4 +532,8 @@ describe('PublicKeyRing model', function() { ret.pubKeys[1].length.should.equal(5); }); + + }); + +var obj = '{"walletId":"0a903a2eb33793d1","networkName":"testnet","requiredCopayers":2,"totalCopayers":2,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1},{"copayerIndex":0,"changeIndex":39,"receiveIndex":0},{"copayerIndex":1,"changeIndex":102,"receiveIndex":39}],"copayersExtPubKeys":["tpubD9peJo88ArhgmJNqRkQmhHt4zAGTYVowsHrDj385xyXyMy4RhWZpV5Qx2mMDUVzpbAD5V9jci5D7cZaHhjLYP8gEkngmTKtSF4Y7V3qkAsy","tpubD8udwzKWwNUgoE2WG7LYsXKf5m1eRtJ1Etp43vnoxViFmrmZ1ND2CkdqGyQtuidcN1CiqdBUvbKegbdsMQaj5VLY2hbA4LEnLDrqkgSzikz"],"nicknameFor":{"03338b105850c7126f1f5b0439b357765b17ead8eed15bcdfdbd28d0e3915b696f":"5@queparece","0286b376d65cc4af0de5932fb8299cbef2ca9ed37ec9fdb0edfd4e9cb74eac45da":"4@queparece"}}'; diff --git a/test/Wallet.js b/test/Wallet.js index db1c437b6..f99c5121d 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -318,7 +318,7 @@ describe('Wallet model', function() { it('#addressIsOwn', function() { var wallet = cachedCreateW2(); - var allAddresses = wallet.getAddressesStr(); + var allAddresses = wallet.getAddresses(); for (var i = 0; i < allAddresses.length; i++) { wallet.addressIsOwn(allAddresses[i]).should.equal(true); } diff --git a/views/homeWallet.html b/views/homeWallet.html index 1680c8b93..fa7346bbe 100644 --- a/views/homeWallet.html +++ b/views/homeWallet.html @@ -41,10 +41,10 @@

Quick receive

- +
-

{{$root.addrInfos[0].addressStr}}

+

{{addr}}

diff --git a/views/includes/sidebar.html b/views/includes/sidebar.html index da182e7b8..54cf5a8ce 100644 --- a/views/includes/sidebar.html +++ b/views/includes/sidebar.html @@ -1,9 +1,9 @@ -
+
{{$root.wallet.getName() | limitTo: 1}}
-
+
[ {{$root.wallet.requiredCopayers}} of {{$root.wallet.totalCopayers}} ]
@@ -22,7 +22,7 @@
-
+
diff --git a/views/send.html b/views/send.html index 9d9c1acfc..543e8c1eb 100644 --- a/views/send.html +++ b/views/send.html @@ -1,18 +1,18 @@
-
+

Pending Transactions Proposals

-
+

{{$root.title}}

From 3ae6378678e72fe1c3e903ccb2020d63464b8451 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 00:31:17 -0300 Subject: [PATCH 04/30] identity now emits! --- index.html | 4 +- js/controllers/copayers.js | 2 +- js/controllers/create.js | 11 +- js/controllers/createProfile.js | 7 +- js/controllers/head.js | 4 +- js/controllers/home.js | 14 +- js/controllers/join.js | 13 ++ js/controllers/profile.js | 1 - js/controllers/settings.js | 9 +- js/controllers/warning.js | 2 +- js/models/Async.js | 17 +- js/models/Identity.js | 205 ++++++++++++-------- js/models/Insight.js | 4 - js/models/Wallet.js | 94 ++++------ js/plugins/InsightStorage.js | 1 - js/routes.js | 14 +- js/services/applicationService.js | 17 ++ js/services/balanceService.js | 20 +- js/services/identityService.js | 226 +++++++++-------------- js/services/pendingTxsService.js | 11 ++ test/Wallet.js | 8 +- test/unit/controllers/controllersSpec.js | 2 +- test/unit/directives/directivesSpec.js | 2 +- test/unit/filters/filtersSpec.js | 2 +- test/unit/services/servicesSpec.js | 2 +- views/copayers.html | 6 +- views/history.html | 2 - views/homeWallet.html | 3 - views/includes/sidebar-mobile.html | 10 +- views/includes/sidebar.html | 12 +- views/profile.html | 8 +- views/receive.html | 2 - views/send.html | 3 - 33 files changed, 384 insertions(+), 354 deletions(-) create mode 100644 js/services/applicationService.js create mode 100644 js/services/pendingTxsService.js diff --git a/index.html b/index.html index 9c4235557..50c68e3d0 100644 --- a/index.html +++ b/index.html @@ -50,7 +50,7 @@ Network Error.
Attempting to reconnect..
-
+
{{'Locked'|translate}}   - - {{lockedBalance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}} - {{lockedBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}} + + {{$root.wallet.balanceInfo.lockedBalance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}} - {{$root.wallet.balanceInfo.lockedBalanceAlternative |noFractionNumber:2}} {{$root.wallet.balanceInfo.alternativeIsoCode}} Date: Sun, 30 Nov 2014 04:03:23 -0300 Subject: [PATCH 10/30] fix profile --- js/controllers/home.js | 4 ++-- js/controllers/profile.js | 6 ++++++ views/profile.html | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/js/controllers/home.js b/js/controllers/home.js index 16ee866d1..58c70e93e 100644 --- a/js/controllers/home.js +++ b/js/controllers/home.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, identityService, Compatibility) { +angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, $timeout, notification, identityService, Compatibility) { // This is only for backwards compat, insight api should link to #!/confirmed directly if (getParam('confirmed')) { var hashIndex = window.location.href.indexOf('/?'); @@ -24,6 +24,7 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc } $rootScope.starting = true; identityService.open(form.email.$modelValue, form.password.$modelValue, function(err, iden) { + $rootScope.starting = false; if (err) { copay.logger.warn(err); if ((err.toString() || '').match('PNOTFOUND')) { @@ -31,7 +32,6 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc } else { $scope.error = 'Unknown error'; } - $rootScope.$digest() } }); } diff --git a/js/controllers/profile.js b/js/controllers/profile.js index f1a3e90ef..2b9bb0cd4 100644 --- a/js/controllers/profile.js +++ b/js/controllers/profile.js @@ -23,6 +23,12 @@ angular.module('copayApp.controllers').controller('ProfileController', function( }); }; + $scope.setWallets = function() { + if (!$rootScope.iden) return; + $scope.wallets=$rootScope.iden.listWallets(); + }; + + $scope.downloadWalletBackup = function(w) { if (!w) return; backupService.walletDownload(w); diff --git a/views/profile.html b/views/profile.html index 82df6f426..e1b1ebea2 100644 --- a/views/profile.html +++ b/views/profile.html @@ -1,4 +1,4 @@ -
+

{{$root.title}}

From 49205a76a073681b01b9da8fff0c21f87282ed04 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 04:42:39 -0300 Subject: [PATCH 11/30] fix import / logs --- js/controllers/importProfile.js | 22 +++----------- js/models/Identity.js | 54 +++++++++++---------------------- js/models/Insight.js | 3 +- js/models/PublicKeyRing.js | 3 -- js/models/Wallet.js | 2 +- js/services/identityService.js | 4 +-- 6 files changed, 27 insertions(+), 61 deletions(-) diff --git a/js/controllers/importProfile.js b/js/controllers/importProfile.js index 1c36c934a..c83013104 100644 --- a/js/controllers/importProfile.js +++ b/js/controllers/importProfile.js @@ -18,18 +18,10 @@ angular.module('copayApp.controllers').controller('ImportProfileController', var password = $scope.password; updateStatus('Importing profile - Setting things up...'); - identityService.importProfile(str,password, function(err){ - }) - copay.Identity.importFromEncryptedFullJson(str, password, { - pluginManager: pluginManager, - network: config.network, - networkName: config.networkName, - walletDefaults: config.wallet, - passphraseConfig: config.passphraseConfig, - }, function(err, iden) { + identityService.importProfile(str,password, function(err, iden) { + $scope.loading = false; if (err) { - $scope.loading = false; - + copay.logger.warn(err); if ((err.toString() || '').match('BADSTR')) { $scope.error = 'Bad password or corrupt profile file'; } else if ((err.toString() || '').match('EEXISTS')) { @@ -37,12 +29,8 @@ angular.module('copayApp.controllers').controller('ImportProfileController', } else { $scope.error = 'Unknown error'; } - $scope.$digest(); - - } else { - var firstWallet = iden.getLastFocusedWallet(); - root.bind($scope, iden, firstWallet); - } + $rootScope.$digest(); + } }); }; diff --git a/js/models/Identity.js b/js/models/Identity.js index 1b8ff7632..23329ca77 100644 --- a/js/models/Identity.js +++ b/js/models/Identity.js @@ -108,7 +108,7 @@ Identity.create = function(opts, cb) { * * After opening a profile, and setting its wallet event handlers, * the client must run .netStart on each - * wallet (or call Identity.netStart()) + * (probably on iden's newWallet handler * * @param {Object} opts * @param {Object} opts.storage @@ -322,7 +322,6 @@ Identity.prototype.close = function() { }; -// TODO: Add feedback function Identity.prototype.importWalletFromObj = function(obj, opts, cb) { var self = this; preconditions.checkArgument(cb); @@ -336,28 +335,30 @@ Identity.prototype.importWalletFromObj = function(obj, opts, cb) { var w = importFunction(obj, readOpts); if (!w) return cb(new Error('Could not decrypt')); - log.debug('Wallet decryped:' + w.getName()); + log.debug('Wallet decrypted:' + w.getName()); self._checkVersion(w.version); log.debug('Updating Indexes for wallet:' + w.getName()); w.updateIndexes(function(err) { log.debug('Adding wallet to profile:' + w.getName()); self.bindWallet(w); - self.updateFocusedTimestamp(w.getId()); - - var writeOpts = _.extend({ - noWallets: true - }, opts); - - self.storeWallet(w, function(err) { - if (err) return cb(err); - self.store(writeOpts, function(err) { - return cb(err, w); - }); - }); + self.storeWallet(w, cb); }); }; + +Identity.prototype.importMultipleWalletsFromObj = function(objs, opts) { + var self = this; + opts = opts || {}; + + async.eachSeries(objs, function(walletData, cb) { + if (!walletData) + return cb(); + self.importWalletFromObj(walletData, opts, cb); + }); +}; + + /** * @param {Wallet} wallet * @param {Function} cb @@ -414,30 +415,11 @@ Identity.importFromFullJson = function(str, password, opts, cb) { iden.store(opts, function(err) { if (err) return cb(err); //profile already exists - opts.failIfExists = false; - async.map(json.wallets, function(walletData, cb) { - - if (!walletData) - return cb(); - - iden.importWalletFromObj(walletData, opts, function(err, w) { - if (err) return cb(err); - log.debug('Wallet ' + w.getId() + ' imported'); - cb(); - }); - }, function(err, results) { - if (err) return cb(err); - - iden.store(opts, function(err) { - return cb(err, iden); - }); - }); - + return cb(null, iden, json.wallets); }); - - }; + /** * @desc binds a wallet's events and emits 'newWallet' * @param {string} walletId Wallet id to be binded diff --git a/js/models/Insight.js b/js/models/Insight.js index c62e7777e..e1cde179a 100644 --- a/js/models/Insight.js +++ b/js/models/Insight.js @@ -220,8 +220,7 @@ Insight.prototype.subscribe = function(addresses) { if (!self.subscribed[address]) { var handler = handlerFor(self, address); self.subscribed[address] = handler; - log.debug('Subscribe to: ', address); - +// log.debug('Subscribe to: ', address); s.emit('subscribe', address); s.on(address, handler); } diff --git a/js/models/PublicKeyRing.js b/js/models/PublicKeyRing.js index b4ee1529f..6963e8bab 100644 --- a/js/models/PublicKeyRing.js +++ b/js/models/PublicKeyRing.js @@ -497,8 +497,6 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { PublicKeyRing.prototype.buildAddressCache = function() { var ret = []; var self = this; - - log.info('Rebuilding Address Cache...this will take a while'); _.each(this.indexes, function(index) { for (var i = 0; i < index.receiveIndex; i++) { self._getAddress(i, false, index.copayerIndex); @@ -507,7 +505,6 @@ PublicKeyRing.prototype.buildAddressCache = function() { self._getAddress(i, true, index.copayerIndex); } }); - log.info('...done!'); }; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 395f00639..e2382ef10 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -1986,7 +1986,7 @@ Wallet.prototype.subscribeToAddresses = function() { var addresses = this.getAddresses(); this.blockchain.subscribe(addresses); - log.debug('Subscribed to ' + addresses.length + ' addresses'); + log.debug('Wallet:' + this.getName() + ' Subscribed to:' + addresses.length + ' addresses'); }; /** diff --git a/js/services/identityService.js b/js/services/identityService.js index 4a0096e7f..f5cc5c84c 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -310,10 +310,10 @@ angular.module('copayApp.services') networkName: config.networkName, walletDefaults: config.wallet, passphraseConfig: config.passphraseConfig, - }, function(err, iden) { + }, function(err, iden, walletObjs) { if (err) return cb(err); root.bind(iden); - iden.openWallets(); + iden.importMultipleWalletsFromObj(walletObjs); return cb(); }); }; From e52d45c5d8817febe1a08d3a7527a9be0830b7db Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 05:06:26 -0300 Subject: [PATCH 12/30] fix changing wallets on incomplete wallets --- index.html | 5 ++--- views/copayers.html | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 50c68e3d0..abd3b8dbe 100644 --- a/index.html +++ b/index.html @@ -49,12 +49,11 @@ Network Error.
Attempting to reconnect..
-
-
+
- 1 BTC = {{alternativeConversionRate|noFractionNumber:2}} {{alternativeIsoCode}} + 1 BTC = {{$root.wallet.balanceInfo.alternativeConversionRate}} {{alternativeIsoCode}}
From 3ea948e6ba909faffeb862722c12c939a1ee6bfe Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 18:35:30 -0300 Subject: [PATCH 20/30] update tx proposals on #send --- js/controllers/send.js | 24 ++++++++++++++---------- js/services/identityService.js | 8 +------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 01437ee40..6a624588f 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -41,7 +41,9 @@ angular.module('copayApp.controllers').controller('SendController', }); }; - $scope.updateTxs = function() { + + $scope.updateTxs = _.throttle(function() { +console.log('[send.js.44:updateTxs:]'); //TODO var w = $rootScope.wallet; if (!w) return; @@ -60,11 +62,12 @@ angular.module('copayApp.controllers').controller('SendController', } }); $scope.txps = res.txs; - }; + }, 1000); /** * Setting the two related amounts as properties prevents an infinite * recursion for watches while preserving the original angular updates + * */ Object.defineProperty($scope, "alternative", { @@ -106,11 +109,14 @@ angular.module('copayApp.controllers').controller('SendController', $scope.init = function() { $rootScope.pendingTxCount = 0; $scope.updateTxs(); - setTimeout(function() { - $scope.loading = false; - $rootScope.$digest(); - }, 1); - } + var w = $rootScope.wallet; + w.on('txProposalEvent', $scope.updateTxs); + }; + + $scope.$on("$destroy", function(){ + var w = $rootScope.wallet; + w.removeListener('txProposalEvent', $scope.updateTxs ); + }); $scope.showAddressBook = function() { return w && _.keys(w.addressBook).length > 0; @@ -419,16 +425,14 @@ angular.module('copayApp.controllers').controller('SendController', $scope.sign = function(ntxid) { $scope.loading = true; $scope.error = $scope.success = null; - w.signAndSend(ntxid, function(err, id, status) { + $scope.loading = false; $scope.notifyStatus(status); $scope.updateTxs(); }); }; $scope.reject = function(ntxid) { - $scope.loading = true; - $rootScope.txAlertCount = 0; w.reject(ntxid); notification.warning('Transaction rejected', 'You rejected the transaction successfully'); $scope.updateTxs(); diff --git a/js/services/identityService.js b/js/services/identityService.js index f5cc5c84c..ee7f924b0 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -126,7 +126,6 @@ angular.module('copayApp.services') root.installWalletHandlers = function(w) { var wid = w.getId(); w.on('connectionError', function() { - console.log('err', w.getName()); //TODO if (root.isFocused(wid)) { var message = "Could not connect to the Insight server. Check your settings and network configuration"; notification.error('Networking Error', message); @@ -134,7 +133,6 @@ angular.module('copayApp.services') }); w.on('corrupt', function(peerId) { - console.log('corr', w.getName()); //TODO if (root.isFocused(wid)) { notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId); } @@ -149,7 +147,6 @@ angular.module('copayApp.services') }); w.on('tx', function(address, isChange) { - console.log('tx', w.getName()); //TODO if (!isChange) { notification.funds('Funds received on ' + w.getName(), address); } @@ -159,14 +156,12 @@ angular.module('copayApp.services') }); w.on('balanceUpdated', function() { - console.log('b', w.getName()); //TODO balanceService.update(w, function() { $rootScope.$digest(); }, root.isFocused(wid)); }); w.on('insightReconnected', function() { - console.log('i', w.getName()); //TODO $rootScope.reconnecting = false; balanceService.update(w, function() { $rootScope.$digest(); @@ -174,14 +169,13 @@ angular.module('copayApp.services') }); w.on('insightError', function() { - console.log('i', w.getName()); //TODO if (root.isFocused(wid)) { $rootScope.reconnecting = true; $rootScope.$digest(); } }); w.on('newAddresses', function() { - console.log('newAddress', w.getName()); //TODO + // Nothing yet }); w.on('txProposalsUpdated', function() { From 0691bb5f7ea36d6c20be85b67cb65978f3da8049 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 18:40:03 -0300 Subject: [PATCH 21/30] fix disabled button --- js/controllers/send.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/controllers/send.js b/js/controllers/send.js index 6a624588f..8c3879bfc 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -190,6 +190,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO comment: commentText, url: (payInfo && payInfo.merchant) ? payInfo.merchant : null, }, function(err, txid, status) { + $scope.loading = false; // reset fields $scope.address = $scope.amount = $scope.commentText = null; form.address.$pristine = form.amount.$pristine = true; From c2163f8c7a46b93b935997cc80dbc8b4dd50bd27 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 18:55:15 -0300 Subject: [PATCH 22/30] fix mocha --- test/PublicKeyRing.js | 20 +++++++++++++++++--- test/Wallet.js | 6 +++--- test/mocks/FakeBlockchain.js | 3 +++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/test/PublicKeyRing.js b/test/PublicKeyRing.js index 39f7d0491..e575e3e46 100644 --- a/test/PublicKeyRing.js +++ b/test/PublicKeyRing.js @@ -154,10 +154,24 @@ describe('PublicKeyRing model', function() { var pubkeyring = setup.w; var address = pubkeyring._getAddress(3, false, 4); - pubkeyring.cache.addressToPath[address].should.equal("m/45'/4/0/3"); - _.indexOf(pubkeyring.cache.receiveAddresses,address).should.be.above(0); - _.indexOf(pubkeyring.cache.changeAddresses,address).should.be.equal(-1); + }); + + it('cach4es calls to getAddress (2)', function() { + var setup = getCachedW(); + var pubkeyring = setup.w; + var address = pubkeyring.generateAddress(false, setup.pub); + _.indexOf(pubkeyring.getReceiveAddresses(),address).should.be.above(-1); + _.indexOf(pubkeyring.getAddresses(),address).should.be.above(-1); + }); + + + it('cach4es calls to getAddress (3)', function() { + var setup = getCachedW(); + var pubkeyring = setup.w; + var address = pubkeyring.generateAddress(true, setup.pub); + _.indexOf(pubkeyring.getReceiveAddresses(),address).should.be.equal(-1); + _.indexOf(pubkeyring.getAddresses(),address).should.be.above(-1); }); it('should generate one address by default', function() { diff --git a/test/Wallet.js b/test/Wallet.js index 90a243a36..45f843093 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -1972,7 +1972,7 @@ describe('Wallet model', function() { }); var spy = sinon.spy(w, 'emit'); - w._setBlockchainListeners(); + w._setupBlockchainHandlers(); spy.calledWith('tx', addr1, false).should.equal(true); }); @@ -1990,7 +1990,7 @@ describe('Wallet model', function() { }); var spy = sinon.spy(w, 'emit'); - w._setBlockchainListeners(); + w._setupBlockchainHandlers(); spy.calledWith('tx', addr1, true).should.equal(true); }); @@ -2531,7 +2531,7 @@ describe('Wallet model', function() { // DATA - var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{ "cache": { "addressToPath": {}, "changeAddresses": [], "receiveAddresses": [] }, "walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}'; + var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{ "cache": { "addressToPath": {}}, "walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}'; }); diff --git a/test/mocks/FakeBlockchain.js b/test/mocks/FakeBlockchain.js index 6af0cc8e3..be2483dcf 100644 --- a/test/mocks/FakeBlockchain.js +++ b/test/mocks/FakeBlockchain.js @@ -17,6 +17,9 @@ FakeBlockchain.prototype.getTransactions = function(addresses, from, to, cb) { FakeBlockchain.prototype.subscribe = function() { }; +FakeBlockchain.prototype.on = function() { +}; + FakeBlockchain.prototype.fixUnspent = function(u) { this.u = u; }; From 022de7d2b59cd1eb640532c1a0570275cc379f7f Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Sun, 30 Nov 2014 19:14:22 -0300 Subject: [PATCH 23/30] Fix payment intent --- js/controllers/paymentIntent.js | 4 ++-- views/paymentIntent.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/controllers/paymentIntent.js b/js/controllers/paymentIntent.js index a575383f6..c34a867c6 100644 --- a/js/controllers/paymentIntent.js +++ b/js/controllers/paymentIntent.js @@ -4,7 +4,7 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', fun $scope.wallets = []; $rootScope.title = 'Payment intent'; - $scope.wallets = rootScope.iden.listWallets(); + $scope.wallets = $rootScope.iden.listAllWallets(); var l = $scope.wallet.length; _.each($scope.wallets, function(w, i) { @@ -39,8 +39,8 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', fun $scope.cancel = function() { $rootScope.pendingPayment = null; - $location.path('/'); $modalInstance.close(); + $location.path('/homeWallet'); }; }; diff --git a/views/paymentIntent.html b/views/paymentIntent.html index 2246aac4b..cd267745a 100644 --- a/views/paymentIntent.html +++ b/views/paymentIntent.html @@ -17,8 +17,8 @@
- {{item.balanceInfo.totalBalance || 0 |noFractionNumber}} {{item.settings.unitName}} - {{item.balanceInfo.totalBalanceAlternative |noFractionNumber:2}} {{item.balanceInfo.alternativeIsoCode}} + {{item.balanceInfo.totalBalance || 0}} {{item.settings.unitName}} + {{item.balanceInfo.totalBalanceAlternative}} {{item.balanceInfo.alternativeIsoCode}}
From e3a0ff926d25df441e48fcbf331b0161181d6186 Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Sun, 30 Nov 2014 19:16:37 -0300 Subject: [PATCH 24/30] . --- js/controllers/paymentIntent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/controllers/paymentIntent.js b/js/controllers/paymentIntent.js index c34a867c6..2fe6f71c1 100644 --- a/js/controllers/paymentIntent.js +++ b/js/controllers/paymentIntent.js @@ -4,7 +4,7 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', fun $scope.wallets = []; $rootScope.title = 'Payment intent'; - $scope.wallets = $rootScope.iden.listAllWallets(); + $scope.wallets = $rootScope.iden.listWallets(); var l = $scope.wallet.length; _.each($scope.wallets, function(w, i) { From f490f4a661ed0affc10df88d580a885245a333f0 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 23:08:16 -0300 Subject: [PATCH 25/30] updates karma tests --- js/controllers/history.js | 3 +- js/controllers/profile.js | 9 ++--- js/services/balanceService.js | 3 +- js/services/identityService.js | 4 +-- test/unit/controllers/controllersSpec.js | 15 ++++---- test/unit/services/servicesSpec.js | 45 ++++++++---------------- 6 files changed, 35 insertions(+), 44 deletions(-) diff --git a/js/controllers/history.js b/js/controllers/history.js index 81dd8bcef..2779705f9 100644 --- a/js/controllers/history.js +++ b/js/controllers/history.js @@ -147,7 +147,8 @@ angular.module('copayApp.controllers').controller('HistoryController', _.each(res, function(r) { var tx = index[r.ts]; tx.alternativeAmount = $filter('noFractionNumber')( - (r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null); + (r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null)) + ; }); setTimeout(function() { $scope.$digest(); diff --git a/js/controllers/profile.js b/js/controllers/profile.js index 2b9bb0cd4..92238910c 100644 --- a/js/controllers/profile.js +++ b/js/controllers/profile.js @@ -1,5 +1,5 @@ 'use strict'; -angular.module('copayApp.controllers').controller('ProfileController', function($scope, $rootScope, $location, $modal, backupService) { +angular.module('copayApp.controllers').controller('ProfileController', function($scope, $rootScope, $location, $modal, backupService, identityService) { $scope.username = $rootScope.iden.getName(); $scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; @@ -16,10 +16,11 @@ angular.module('copayApp.controllers').controller('ProfileController', function( $scope.deleteWallet = function(w) { if (!w) return; - $scope.loading = w.id; - - identityService.deleteWallet(w.id,function() { + identityService.deleteWallet(w, function(err) { $scope.loading = false; + if (err) { + log.warn(err); + } }); }; diff --git a/js/services/balanceService.js b/js/services/balanceService.js index 05a23ac3d..c2be78dcb 100644 --- a/js/services/balanceService.js +++ b/js/services/balanceService.js @@ -19,6 +19,7 @@ angular.module('copayApp.services') var r = {}; r.totalBalance = $filter('noFractionNumber')(balanceSat * satToUnit); r.totalBalanceBTC = (balanceSat / COIN); + var availableBalanceNr = safeBalanceSat * satToUnit; r.availableBalance = $filter('noFractionNumber')(safeBalanceSat * satToUnit); r.availableBalanceBTC = (safeBalanceSat / COIN); r.safeUnspentCount = safeUnspentCount; @@ -29,7 +30,7 @@ angular.module('copayApp.services') if (r.safeUnspentCount) { var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount); - r.topAmount = (((r.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi); + r.topAmount = (((availableBalanceNr * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi); } var balanceByAddr = {}; diff --git a/js/services/identityService.js b/js/services/identityService.js index ee7f924b0..6fb1cd0aa 100644 --- a/js/services/identityService.js +++ b/js/services/identityService.js @@ -87,8 +87,8 @@ angular.module('copayApp.services') }); }; - root.deleteWallet = function($scope, iden, w) { - $rootScope.iden.deleteWallet(w.id); + root.deleteWallet = function(w, cb) { + $rootScope.iden.deleteWallet(w.id, cb); }; root.isFocused = function(wid) { diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index f3b80e1ff..d770e3544 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -15,6 +15,8 @@ saveAs = function(blob, filename) { describe("Unit: Controllers", function() { config.plugins.LocalStorage = true; config.plugins.GoogleDrive = null; + config.plugins.InsightStorage = null; + config.plugins.EncryptedInsightStorage= null; var anAddr = 'mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'; var anAmount = 1000; @@ -254,13 +256,13 @@ describe("Unit: Controllers", function() { sendForm.amount.$setViewValue(anAmount); sendForm.comment.$setViewValue(aComment); - scope.loadTxs = sinon.spy(); + scope.updateTxs = sinon.spy(); var w = scope.wallet; scope.submitForm(sendForm); sinon.assert.callCount(w.spend, 1); sinon.assert.callCount(w.broadcastTx, 0); - sinon.assert.callCount(scope.loadTxs, 1); + sinon.assert.callCount(scope.updateTxs, 1); var spendArgs = w.spend.getCall(0).args[0]; spendArgs.toAddress.should.equal(anAddr); spendArgs.amountSat.should.equal(anAmount * scope.wallet.settings.unitToSatoshi); @@ -275,7 +277,7 @@ describe("Unit: Controllers", function() { sendForm.amount.$setViewValue(100); sendForm.address.$setViewValue(anAddr); - scope.loadTxs = sinon.spy(); + scope.updateTxs = sinon.spy(); scope.submitForm(sendForm); var w = scope.wallet; w.spend.getCall(0).args[0].amountSat.should.equal(100 * scope.wallet.settings.unitToSatoshi); @@ -602,9 +604,10 @@ describe("Unit: Controllers", function() { it('Delete a wallet', function() { var w = scope.wallet; - scope.deleteWallet(w); - scope.$digest(); - expect(scope.wallet).equal(null); + scope.deleteWallet(w, function() { + scope.$digest(); + expect(scope.wallet).equal(null); + }); }); }); diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index 41ad2b35b..3abe1efaf 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -65,49 +65,34 @@ describe("Angular services", function() { - describe("Unit: controllerUtils", function() { + describe("Unit: balanceService", function() { - it('should updateBalance in bits', inject(function(controllerUtils, $rootScope) { + it('should updateBalance in bits', inject(function(balanceService, $rootScope) { var w = $rootScope.wallet; - - expect(controllerUtils.updateBalance).not.to.equal(null); + expect(balanceService.update).not.to.equal(null); var Waddr = Object.keys($rootScope.wallet.balanceByAddr)[0]; var a = {}; a[Waddr] = 200; w.getBalance = sinon.stub().yields(null, 100000001, a, 90000002, 5); - var orig =controllerUtils.isFocusedWallet; - controllerUtils.isFocusedWallet = sinon.stub().returns(true); - //retuns values in DEFAULT UNIT(bits) - controllerUtils.updateBalance(null, function() { + balanceService.update(w, function() { + var b = w.balanceInfo; + expect(b.totalBalanceBTC).to.be.equal(1.00000001); + expect(b.availableBalanceBTC).to.be.equal(0.90000002); + expect(b.lockedBalanceBTC).to.be.equal(0.09999999); + expect(b.totalBalance).to.be.equal('1,000,000.01'); + expect(b.availableBalance).to.be.equal('900,000.02'); + expect(b.lockedBalance).to.be.equal(99999.99); - expect($rootScope.totalBalanceBTC).to.be.equal(1.00000001); - expect($rootScope.availableBalanceBTC).to.be.equal(0.90000002); - expect($rootScope.lockedBalanceBTC).to.be.equal(0.09999999); - - expect($rootScope.totalBalance).to.be.equal(1000000.01); - expect($rootScope.availableBalance).to.be.equal(900000.02); - expect($rootScope.lockedBalance).to.be.equal(99999.99); - - expect($rootScope.balanceByAddr[Waddr]).to.equal(2); - expect($rootScope.safeUnspentCount).to.equal(5); - expect($rootScope.topAmount).to.equal(899800.02); - }); - - controllerUtils.isFocusedWallet = orig; + expect(b.balanceByAddr[Waddr]).to.equal(2); + expect(b.safeUnspentCount).to.equal(5); + expect(b.topAmount).to.equal(899800.02); + },false); })); - it('should set the rootScope', inject(function(controllerUtils, $rootScope) { - controllerUtils.setupGlobalVariables(function() { - expect($rootScope.txAlertCount).to.be.equal(0); - expect($rootScope.insightError).to.be.equal(0); - expect($rootScope.isCollapsed).to.be.equal(0); - expect($rootScope.unitName).to.be.equal('bits'); - }); - })); }); describe("Unit: Notification Service", function() { From f4fd6c24f763737b43a2246ed36f329961874a6b Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 30 Nov 2014 23:14:46 -0300 Subject: [PATCH 26/30] better state handling at home --- js/controllers/home.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/js/controllers/home.js b/js/controllers/home.js index 58c70e93e..c5da16cc0 100644 --- a/js/controllers/home.js +++ b/js/controllers/home.js @@ -16,6 +16,20 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc Compatibility.check($scope); + $scope.done = function() { + $rootScope.starting = false; + }; + + + $scope.$on("$destroy", function(){ + var iden = $rootScope.iden; + if (iden) { + iden.removeListener('newWallets', $scope.done ); + iden.removeListener('noWallets', $scope.done ); + } + }); + + $scope.openProfile = function(form) { $scope.confirmedEmail = false; if (form && form.$invalid) { @@ -24,8 +38,8 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc } $rootScope.starting = true; identityService.open(form.email.$modelValue, form.password.$modelValue, function(err, iden) { - $rootScope.starting = false; - if (err) { + if (err) { + $rootScope.starting = false; copay.logger.warn(err); if ((err.toString() || '').match('PNOTFOUND')) { $scope.error = 'Invalid email or password'; @@ -33,6 +47,11 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc $scope.error = 'Unknown error'; } } + + if (iden) { + iden.on('newWallet', $scope.done); + iden.on('noWallets', $scope.done); + } }); } From 6d72d00b998a17980b3c4117c14f601713e65804 Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Mon, 1 Dec 2014 01:27:58 -0300 Subject: [PATCH 27/30] Define rateService on history.js. Fix filter noFractionNumber if alternativeAmount is null. --- js/controllers/history.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/js/controllers/history.js b/js/controllers/history.js index 2779705f9..97836d698 100644 --- a/js/controllers/history.js +++ b/js/controllers/history.js @@ -2,7 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.controllers').controller('HistoryController', - function($scope, $rootScope, $filter) { + function($scope, $rootScope, $filter, rateService) { var w = $rootScope.wallet; $rootScope.title = 'History'; @@ -146,9 +146,8 @@ angular.module('copayApp.controllers').controller('HistoryController', if (!err && res) { _.each(res, function(r) { var tx = index[r.ts]; - tx.alternativeAmount = $filter('noFractionNumber')( - (r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null)) - ; + var alternativeAmount = (r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null); + tx.alternativeAmount = alternativeAmount ? $filter('noFractionNumber')(alternativeAmount) : null; }); setTimeout(function() { $scope.$digest(); From 92737bc49cd44bdf15eda680e6445815b2a2eaee Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Mon, 1 Dec 2014 05:24:19 -0300 Subject: [PATCH 28/30] Trying to fix bip72 --- js/controllers/paymentIntent.js | 39 +++++++++++++++---------------- js/controllers/send.js | 41 +++++++++++++++++++-------------- js/models/Wallet.js | 2 +- views/homeWallet.html | 4 ++-- views/modals/qr-address.html | 2 +- views/paymentIntent.html | 1 + views/profile.html | 2 +- views/send.html | 2 +- 8 files changed, 50 insertions(+), 43 deletions(-) diff --git a/js/controllers/paymentIntent.js b/js/controllers/paymentIntent.js index 2fe6f71c1..79f9e9435 100644 --- a/js/controllers/paymentIntent.js +++ b/js/controllers/paymentIntent.js @@ -2,27 +2,12 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', function($rootScope, $scope, $modal, $location, balanceService) { - $scope.wallets = []; - $rootScope.title = 'Payment intent'; - $scope.wallets = $rootScope.iden.listWallets(); + $rootScope.title = 'Payment intent'; - var l = $scope.wallet.length; - _.each($scope.wallets, function(w, i) { - balanceService.update(w, function(){ - if (i === l-1) - $rootScope.$digest(); - }); - }); - - $scope.open = function() { + $scope.open = function() { var modalInstance = $modal.open({ templateUrl: 'myModalContent.html', - controller: ModalInstanceCtrl, - resolve: { - items: function() { - return $scope.wallets; - } - } + controller: ModalInstanceCtrl }); }; @@ -30,8 +15,22 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', fun // Please note that $modalInstance represents a modal window (instance) dependency. // It is not the same as the $modal service used above. - var ModalInstanceCtrl = function($scope, $modalInstance, items, identityService) { - $scope.wallets = items; + var ModalInstanceCtrl = function($scope, $modalInstance, identityService) { + $scope.loading = true; + $scope.setWallets = function() { + if (!$rootScope.iden) return; + var ret = _.filter($rootScope.iden.listWallets(), function(w) { + return w.balanceInfo && w.balanceInfo.totalBalanceBTC; + }); + $scope.wallets = ret; + $scope.loading = false; + }; + if ($rootScope.iden) { + var iden = $rootScope.iden; + iden.on('newWallet', function() { + $scope.setWallets(); + }); + } $scope.ok = function(selectedItem) { identityService.setPaymentWallet(selectedItem); $modalInstance.close(); diff --git a/js/controllers/send.js b/js/controllers/send.js index 8c3879bfc..f1aec87ff 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -11,10 +11,10 @@ angular.module('copayApp.controllers').controller('SendController', $rootScope.title = 'Send'; $scope.loading = false; $scope.error = $scope.success = null; - var satToUnit = 1 / w.settings.unitToSatoshi; + var unitToSatoshi = w.settings.unitToSatoshi; + var satToUnit = 1 / unitToSatoshi; $scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit; - $scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN; - $scope.unitToSatoshi = w.settings.unitToSatoshi; + $scope.unitToBtc = unitToSatoshi / bitcore.util.COIN; $scope.alternativeName = w.settings.alternativeName; $scope.alternativeIsoCode = w.settings.alternativeIsoCode; @@ -33,9 +33,9 @@ angular.module('copayApp.controllers').controller('SendController', $scope.setAlternativeAmount = function(w, tx, cb) { rateService.whenAvailable(function() { _.each(tx.outs, function(out) { - var valueSat = out.value * w.settings.unitToSatoshi; - out.alternativeAmount = rateService.toFiat(valueSat, w.settings.alternativeIsoCode); - out.alternativeIsoCode = w.settings.alternativeIsoCode; + var valueSat = out.value * unitToSatoshi; + out.alternativeAmount = rateService.toFiat(valueSat, $scope.alternativeIsoCode); + out.alternativeIsoCode = $scope.alternativeIsoCode; }); if (cb) return cb(tx); }); @@ -78,7 +78,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO this._alternative = newValue; if (typeof(newValue) === 'number' && $scope.isRateAvailable) { this._amount = parseFloat( - (rateService.fromFiat(newValue, w.settings.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10); + (rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10); } else { this._amount = 0; } @@ -96,7 +96,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO if (typeof(newValue) === 'number' && $scope.isRateAvailable) { this._alternative = parseFloat( - (rateService.toFiat(newValue * w.settings.unitToSatoshi, w.settings.alternativeIsoCode)).toFixed(2), 10); + (rateService.toFiat(newValue * unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2), 10); } else { this._alternative = 0; } @@ -124,12 +124,19 @@ console.log('[send.js.44:updateTxs:]'); //TODO if ($rootScope.pendingPayment) { var pp = $rootScope.pendingPayment; - $scope.address = pp.address + ''; - var amount = pp.data.amount / w.settings.unitToSatoshi * 100000000; - $scope.amount = $filter('noFractionNumber')(amount); - var alternativeAmount = rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode); - $scope.alternativeAmountPayPro = $filter('noFractionNumber')(alternativeAmount, 2); + var amount = pp.data.amount * 100000000 * satToUnit; + var alternativeAmountPayPro = rateService.toFiat((amount + $scope.defaultFee) * unitToSatoshi, $scope.alternativeIsoCode); + if (pp.data.merchant) { + $scope.address = 'bitcoin:' + pp.address.data + '?amount=' + amount + '&r=' + pp.data.r; + } + else { + $scope.address = pp.address + ''; + $scope.amount = amount; + $scope.alternative = alternativeAmountPayPro; + } + $scope.alternativeAmountPayPro = $filter('noFractionNumber')(alternativeAmountPayPro, 2); $scope.commentText = pp.data.message; + $scope.onChanged(); } navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; @@ -165,7 +172,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO $scope.loading = true; var address = form.address.$modelValue; - var amount = parseInt((form.amount.$modelValue * w.settings.unitToSatoshi).toFixed(0)); + var amount = parseInt((form.amount.$modelValue * unitToSatoshi).toFixed(0)); var commentText = form.comment.$modelValue; @@ -539,7 +546,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO apply(); var balance = $rootScope.availableBalance; - var available = +(balance * w.settings.unitToSatoshi).toFixed(0); + var available = +(balance * unitToSatoshi).toFixed(0); if (merchantData && available < +merchantData.total) { err = new Error('Insufficient funds.'); err.amount = merchantData.total; @@ -547,7 +554,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO if (err) { if (err.amount) { - $scope.sendForm.amount.$setViewValue(+err.amount / w.settings.unitToSatoshi); + $scope.sendForm.amount.$setViewValue(+err.amount / unitToSatoshi); $scope.sendForm.amount.$render(); $scope.sendForm.amount.$isValid = false; $scope.notEnoughAmount = true; @@ -578,7 +585,7 @@ console.log('[send.js.44:updateTxs:]'); //TODO var url = merchantData.request_url; var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1]; - merchantData.unitTotal = (+merchantData.total / w.settings.unitToSatoshi) + ''; + merchantData.unitTotal = (+merchantData.total / unitToSatoshi) + ''; merchantData.expiration = new Date( merchantData.pr.pd.expires * 1000); merchantData.domain = domain; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 6c96a9b2a..be28d3808 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -629,7 +629,7 @@ Wallet.prototype._onAddressBook = function(senderId, data) { var self = this, hasChange; _.each(data.addressBook, function(value, key) { - if (!self.addressBook[key] && Address.validate(key)) { + if (key && !self.addressBook[key] && Address.validate(key)) { self.addressBook[key] = _.pick(value, ['createdTs', 'label']); diff --git a/views/homeWallet.html b/views/homeWallet.html index bba7198e8..5563d1fd5 100644 --- a/views/homeWallet.html +++ b/views/homeWallet.html @@ -17,13 +17,13 @@
- {{$root.wallet.balanceInfo.totalBalance || 0 |noFractionNumber}} + {{$root.wallet.balanceInfo.totalBalance || 0}} {{$root.wallet.settings.unitName}} - {{$root.wallet.balanceInfo.totalBalanceAlternative |noFractionNumber:2}} {{$root.wallet.balanceInfo.alternativeIsoCode}} + {{$root.wallet.balanceInfo.totalBalanceAlternative}} {{$root.wallet.balanceInfo.alternativeIsoCode}} N/A diff --git a/views/modals/qr-address.html b/views/modals/qr-address.html index fb82d6b03..d52da6d03 100644 --- a/views/modals/qr-address.html +++ b/views/modals/qr-address.html @@ -13,7 +13,7 @@ - {{address.balance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}} + {{address.balance || 0}} {{$root.wallet.settings.unitName}}

diff --git a/views/paymentIntent.html b/views/paymentIntent.html index cd267745a..4a1fb750a 100644 --- a/views/paymentIntent.html +++ b/views/paymentIntent.html @@ -1,6 +1,7 @@