From 4e2c06f69d5a93d63c137908b79ee9ef8d3155e1 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 14 Aug 2018 12:24:10 +1200 Subject: [PATCH 001/118] Changed "Bitcoin" to "Bitcoin Core" on the Sweep Paper Wallet screen and made other strings translatable. --- i18n/po/template.pot | 27 ++++++++++++++++++++++++++- www/views/paperWallet.html | 12 ++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 277be03bb..dc22630ad 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -2916,10 +2916,15 @@ msgid "Sweep" msgstr "" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "" @@ -3849,4 +3854,24 @@ msgstr "" #: src/js/services/incomingData.js:129 msgid "This invoice is no longer accepting payments" +msgstr "" + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "" + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "" + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "" + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." msgstr "" \ No newline at end of file diff --git a/www/views/paperWallet.html b/www/views/paperWallet.html index a8d3d2ee4..26415a5d0 100644 --- a/www/views/paperWallet.html +++ b/www/views/paperWallet.html @@ -1,6 +1,6 @@ - {{'Sweep paper wallet' | translate}} + {{'Sweep Paper Wallet' | translate}} @@ -45,19 +45,19 @@
- No Bitcoin Cash wallet to transfer funds to found. + No Bitcoin Cash wallet to transfer funds to found.
-

No Bitcoin Cash found

+

No Bitcoin Cash found.

-

Bitcoin found:

+

Bitcoin Core found:

{{btcBalanceText}}
@@ -95,13 +95,13 @@
- No Bitcoin wallet to transfer funds to found. + No Bitcoin Core wallet to transfer funds to found.
-

No Bitcoin found

+

No Bitcoin Core found.

Date: Wed, 15 Aug 2018 17:23:00 +0200 Subject: [PATCH 002/118] loading new txs using pagination --- src/js/controllers/walletDetails.js | 13 +++- src/js/services/walletService.js | 103 +++++++++++++++++++++++++--- www/views/walletDetails.html | 2 +- 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index ec787a5f4..68d26baf2 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -256,11 +256,18 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return !tx.confirmations || tx.confirmations === 0; }; + var loadingTxs = false; $scope.showMore = function() { + if (loadingTxs) + return; + loadingTxs = true; $timeout(function() { - currentTxHistoryPage++; - $scope.showHistory(); - $scope.$broadcast('scroll.infiniteScrollComplete'); + walletService.getMoreTxs($scope.wallet, function() { + currentTxHistoryPage++; + $scope.showHistory(); + $scope.$broadcast('scroll.infiniteScrollComplete'); + loadingTxs = false; + }); }, 100); }; diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 647c69734..4d293a01f 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -396,6 +396,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return ret; }; + var skipped = 0; + var updateLocalTxHistory = function(wallet, opts, cb) { var FIRST_LIMIT = 5; var LIMIT = 50; @@ -492,6 +494,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim } // + shouldContinue = false; + + skipped = skip; if (!shouldContinue) { $log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length); @@ -499,7 +504,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim } requestLimit = LIMIT; - getNewTxs(newTxs, skip, next); }); }; @@ -535,6 +539,87 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); } + + if (opts.getMoreTxs) { + var requestLimit = LIMIT; + getNewTxs([], skipped, function(err, txs) { + if (err) return cb(err); + + createReceivedEvents(txs); + + var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) { + return x.txid; + }); + + + function updateNotes(cb2) { + if (!endingTs) return cb2(); + + $log.debug('Syncing notes from: ' + endingTs); + wallet.getTxNotes({ + minTs: endingTs + }, function(err, notes) { + if (err) { + $log.warn(err); + return cb2(); + }; + lodash.each(notes, function(note) { + $log.debug('Note for ' + note.txid); + lodash.each(newHistory, function(tx) { + if (tx.txid == note.txid) { + $log.debug('...updating note for ' + note.txid); + tx.note = note; + } + }); + }); + return cb2(); + }); + } + + function updateLowAmount(txs) { + if (!opts.lowAmount) return; + + lodash.each(txs, function(tx) { + tx.lowAmount = tx.amount < opts.lowAmount; + }); + }; + + updateLowAmount(txs); + + updateNotes(function() { + + // + if (foundLimitTx) { + $log.debug('Tx history read until limitTx: ' + opts.limitTx); + return cb(null, newHistory); + } + // + + var historyToSave = JSON.stringify(newHistory); + + lodash.each(txs, function(tx) { + tx.recent = true; + }) + + $log.debug('Tx History synced. Total Txs: ' + newHistory.length); + + // Final update + if (walletId == wallet.credentials.walletId) { + wallet.completeHistory = newHistory; + } + + return storageService.setTxHistory(historyToSave, walletId, function() { + $log.debug('Tx History saved.'); + + return cb(); + }); + }); + }); + return; + } + + skipped = 0; + getNewTxs([], 0, function(err, txs) { if (err) return cb(err); @@ -611,6 +696,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; + root.getMoreTxs = function(wallet, cb) { + var opts = {}; + opts.getMoreTxs = true; + updateLocalTxHistory(wallet, opts, cb); + }; + root.getTxNote = function(wallet, txid, cb) { wallet.getTxNote({ txid: txid @@ -675,16 +766,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim root.getTxHistory = function(wallet, opts, cb) { opts = opts || {}; - var walletId = wallet.credentials.walletId; - if (!wallet.isComplete()) return cb(); - function isHistoryCached() { - return wallet.completeHistory && wallet.completeHistory.isValid; - }; - - // disable caching - //if (isHistoryCached() && !opts.force) return cb(null, wallet.completeHistory); + // var historyIsCached = wallet.completeHistory && wallet.completeHistory.isValid; + // if (historyIsCached && !opts.force) return cb(null, wallet.completeHistory); $log.debug('Updating Transaction History'); diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 6e05862aa..fc422ff1f 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -306,7 +306,7 @@
From 93b6c3f1be5fbf345c3628a0a9d64193e8154e57 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 16 Aug 2018 15:00:51 +1200 Subject: [PATCH 003/118] Spinner same size as refresh button. --- src/sass/views/walletDetails.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 858229d85..39118320b 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -341,4 +341,6 @@ a.item { .loading-wallet svg { margin-top: 0; + width: 16px; + height: 16px; } \ No newline at end of file From 33d780f2e561f1681a9a1db7fd4377a2406814a3 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 16 Aug 2018 15:44:59 +1200 Subject: [PATCH 004/118] The spinner in wallet details now spins when the infinite scroll spinner spins. --- src/js/controllers/walletDetails.js | 46 +++++++++++++++++++++++------ www/views/includes/walletInfo.html | 4 +-- www/views/walletDetails.html | 10 +++---- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 68d26baf2..171c71847 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -5,12 +5,39 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var HISTORY_SHOW_LIMIT = 10; var currentTxHistoryPage = 0; var listeners = []; - $scope.txps = []; + + // For gradual migration for doing it properly + $scope.vm = { + gettingInitialHistory: false, + updatingTxHistory: false + }; + + $scope.amountIsCollapsible = false; + $scope.color = '#888888'; $scope.completeTxHistory = []; - $scope.openTxpModal = txpModalService.open; + $scope.filteredTxHistory = []; $scope.isCordova = platformInfo.isCordova; $scope.isAndroid = platformInfo.isAndroid; $scope.isIOS = platformInfo.isIOS; + $scope.isSearching = false; + $scope.openTxpModal = txpModalService.open; + $scope.requiresMultipleSignatures = false; + $scope.showBalanceButton = false; + $scope.status = null; + $scope.txHistory = []; + $scope.txHistorySearchResults = []; + $scope.txHistoryShowMore = false; + $scope.txps = []; + $scope.updatingStatus = false; + $scope.updateStatusError = null; + $scope.updateTxHistoryError = null; + $scope.updatingTxHistoryProgress = 0; + $scope.wallet = null; + $scope.walletId = ''; + $scope.walletNotRegistered = false; + + + var channel = "ga"; if (platformInfo.isCordova) { @@ -172,7 +199,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun walletService.getTxHistory($scope.wallet, { feeLevels: levels }, function(err, txHistory) { - $scope.updatingTxHistory = false; + $scope.vm.gettingInitialHistory = false; if (err) { $scope.txHistory = null; $scope.updateTxHistoryError = true; @@ -256,17 +283,18 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return !tx.confirmations || tx.confirmations === 0; }; - var loadingTxs = false; + $scope.showMore = function() { - if (loadingTxs) + if ($scope.vm.updatingTxHistory) { return; - loadingTxs = true; + } + $scope.vm.updatingTxHistory = true; $timeout(function() { - walletService.getMoreTxs($scope.wallet, function() { + walletService.getMoreTxs($scope.wallet, function onMoreTxs() { currentTxHistoryPage++; $scope.showHistory(); $scope.$broadcast('scroll.infiniteScrollComplete'); - loadingTxs = false; + $scope.vm.updatingTxHistory = false; }); }, 100); }; @@ -400,7 +428,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun if (!$scope.wallet) return; $scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1; - $scope.updatingTxHistory = true; + $scope.vm.gettingInitialHistory = true; addressbookService.list(function(err, ab) { if (err) $log.error(err); diff --git a/www/views/includes/walletInfo.html b/www/views/includes/walletInfo.html index 1680d2c2c..aff10fc09 100644 --- a/www/views/includes/walletInfo.html +++ b/www/views/includes/walletInfo.html @@ -3,8 +3,8 @@ Tap to recreate Tap to retry
- -↻ +
diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index fc422ff1f..6021467ee 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -284,29 +284,29 @@
+ ng-show="!txHistory[0] && !vm.gettingInitialHistory && !updateTxHistoryError && !updateStatusError" translate> No transactions yet
+ ng-show="!txHistory[0] && !vm.gettingInitialHistory && updateTxHistoryError" translate> Could not update transaction history
-
+
Updating transaction history. Please stand by.
{{updatingTxHistoryProgress}} transactions downloaded
-
+
From 09871c9ebac97a7e7ef2ced62731b3a7825f953e Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 20 Aug 2018 11:46:34 +1200 Subject: [PATCH 005/118] Updated package.json. From 2a5a21f7d7306058c2b4be55e3f5547b677b0bad Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 20 Aug 2018 11:48:48 +1200 Subject: [PATCH 006/118] Updated package.json for real. From bbe2fb20c41c018cfea0bd2c875fd3ccb337b10d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 20 Aug 2018 12:02:11 +1200 Subject: [PATCH 007/118] Updated package.json attempt 4. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c1f37b2e6..e54e3d14d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "postinstall": "npm run apply:copay && echo && echo \"Repo configured for standard Copay distribution. To switch to the BitPay distribution, run 'npm run apply:bitpay'.\" && echo", "start": "echo && echo \"Choose a distribution by running 'npm run apply:copay' or 'npm run apply:bitpay'.\" && echo", "apply:copay": "npm i fs-extra@0.30 && cd app-template && node apply.js copay && cd .. && npm i", - "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare", + "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", "apply:bitpay": "npm i fs-extra@0.30 && cd app-template && node apply.js bitpay && cd .. && npm i", "unstage-package": "git reset package.json", "clean-all": "git clean -dfx" From 03fa87adbcac643b5b8a0547d42da7ad48f4b4e4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 20 Aug 2018 17:21:30 +1200 Subject: [PATCH 008/118] Displaying transactions from cache when first entering wallet details. --- src/js/app.js | 4 +- src/js/controllers/walletDetails.js | 93 ++++++++++++++-- src/js/services/wallet-history.service.js | 129 ++++++++++++++++++++++ src/js/services/walletService.js | 41 +++---- www/views/includes/walletInfo.html | 2 +- www/views/walletDetails.html | 6 +- 6 files changed, 239 insertions(+), 36 deletions(-) create mode 100644 src/js/services/wallet-history.service.js diff --git a/src/js/app.js b/src/js/app.js index 745ceef50..503da9f52 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,7 +19,8 @@ var modules = [ 'copayApp.controllers', 'copayApp.directives', 'copayApp.addons', - 'bitcoincom.directives' + 'bitcoincom.directives', + 'bitcoincom.services' ]; var copayApp = window.copayApp = angular.module('copayApp', modules); @@ -30,3 +31,4 @@ angular.module('copayApp.controllers', []); angular.module('copayApp.directives', []); angular.module('copayApp.addons', []); angular.module('bitcoincom.directives', []); +angular.module('bitcoincom.services', []); diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 171c71847..6df56c943 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) { +angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService, walletHistoryService) { var HISTORY_SHOW_LIMIT = 10; var currentTxHistoryPage = 0; @@ -8,8 +8,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // For gradual migration for doing it properly $scope.vm = { - gettingInitialHistory: false, - updatingTxHistory: false + gettingCachedHistory: true, + gettingInitialHistory: true, + updatingTxHistory: false, + //updateTxHistoryError: false + updateTxHistoryFailed: false }; $scope.amountIsCollapsible = false; @@ -30,7 +33,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.txps = []; $scope.updatingStatus = false; $scope.updateStatusError = null; - $scope.updateTxHistoryError = null; $scope.updatingTxHistoryProgress = 0; $scope.wallet = null; $scope.walletId = ''; @@ -192,7 +194,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var updateTxHistory = function(cb) { if (!cb) cb = function() {}; - $scope.updateTxHistoryError = false; + $scope.vm.updateTxHistoryFailed = false; $scope.updatingTxHistoryProgress = 0; feeService.getFeeLevels($scope.wallet.coin, function(err, levels) { @@ -202,10 +204,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.vm.gettingInitialHistory = false; if (err) { $scope.txHistory = null; - $scope.updateTxHistoryError = true; + $scope.vm.updateTxHistoryFailed = true; return; } - + applyCurrencyAliases(txHistory); var config = configService.getSync(); @@ -214,7 +216,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin); t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode; }); - + console.log('pagination Got tx history old way'); $scope.completeTxHistory = txHistory; $scope.showHistory(); @@ -230,12 +232,77 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var defaults = configService.getDefaults(); var configCache = configService.getSync(); - lodash.each(txHistory, function(t) { - t.amountUnitStr = $scope.wallet.coin == 'btc' + lodash.each(txHistory, function onTx(tx) { + tx.amountUnitStr = $scope.wallet.coin == 'btc' ? (configCache.bitcoinAlias || defaults.bitcoinAlias) : (configCache.bitcoinCashAlias || defaults.bitcoinCashAlias); - t.amountUnitStr = t.amountUnitStr.toUpperCase(); + tx.amountUnitStr = tx.amountUnitStr.toUpperCase(); + }); + } + + function formatTxHistoryForDisplay(txHistory) { + applyCurrencyAliases(txHistory); + + var config = configService.getSync(); + var fiatCode = config.wallet.settings.alternativeIsoCode; + lodash.each(txHistory, function(t) { + var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin); + t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode; + }); + } + + function updateTxHistoryUsingCachedData() { + walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ + $scope.vm.gettingCachedHistory = false; + if (err) { + // Don't display an error because we are also requesting the histroy. + $log.error('Error getting cached tx history.', err); + return; + } + + if (!txHistory) { + $log.debug('No cached tx history.'); + return; + } + + console.log('pagination Got cached txs, count: ', txHistory.length); + + /* + var shortHistory = []; + if (txHistory.length > 0) { + shortHistory = [ + txHistory[0] + ]; + } + txHistory = shortHistory; + */ + + formatTxHistoryForDisplay(txHistory); + + $scope.completeTxHistory = txHistory; + $scope.showHistory(); + $scope.$apply(); + console.log('pagination displayed cached history.'); + }); + } + + function updateTxHistoryFromSmallCache(getLatest) { + walletHistoryService.updateTxHistoryByPage($scope.wallet, getLatest, true, function onUpdateTxHistoryByPage(err, txHistory) { + console.log('pagination returned'); + $scope.vm.gettingInitialHistory = false; + if (err) { + console.error('pagination Failed to get history.', err); + $scope.txHistory = null; + $scope.vm.updateTxHistoryFailed = true; + return; + } + console.log('pagination txs returned in history: ' + txHistory.length); + formatTxHistoryForDisplay(txHistory); + + $scope.completeTxHistory = txHistory; + $scope.showHistory(); + $scope.$apply(); }); } @@ -308,7 +375,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.updateAll = function(force, cb)  { updateStatus(force); - updateTxHistory(cb); + //updateTxHistory(cb); + updateTxHistoryFromSmallCache(); }; $scope.hideToggle = function() { @@ -450,6 +518,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var refreshInterval; $scope.$on("$ionicView.afterEnter", function(event, data) { + updateTxHistoryUsingCachedData(); $scope.updateAll(); refreshAmountSection(); refreshInterval = $interval($scope.onRefresh, 10 * 1000); diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js new file mode 100644 index 000000000..3679dd088 --- /dev/null +++ b/src/js/services/wallet-history.service.js @@ -0,0 +1,129 @@ +'use strict'; + +(function(){ + + angular + .module('bitcoincom.services') + .factory('walletHistoryService', walletHistoryService); + + function walletHistoryService(configService, storageService, lodash, $log, txFormatService) { + // 8 Transactions fit on an iPhone Plus screen when in Wallet Details. + // Make it a little bit bigger so it doesn't have to load more immediately + var PAGE_SIZE = 50 / 1.5; + // How much to overlap on each end of the page, for mitigating inconsistent sort order. + var PAGE_OVERLAP_FRACTION = 0.5; + + var SAFE_CONFIRMATIONS = 6; + + var service = { + getCachedTxHistory: getCachedTxHistory, + updateTxHistoryByPage: updateTxHistoryByPage + }; + return service; + + /** + * @param {string} walletId + * @param {function(error, txs)} cb + */ + function getCachedTxHistory(walletId, cb) { + storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){ + if (err) { + return cb(err); + } + + if (!txHistoryString) { + return cb(null, txHistoryString); + } + + try { + var txHistory = JSON.parse(txHistoryString); + return cb(null, txHistory); + } catch (e) { + $log.error('Failed to parse tx history.', e); + return cb(e); + } + }); + } + + function processNewTxs(wallet, txs) { + var now = Math.floor(Date.now() / 1000); + var txHistoryUnique = {}; + var processedTxs = []; + wallet.hasUnsafeConfirmed = false; + + lodash.each(txs, function(tx) { + tx = txFormatService.processTx(wallet.coin, tx); + + // no future transactions... + if (tx.time > now) + tx.time = now; + + if (tx.confirmations >= SAFE_CONFIRMATIONS) { + tx.safeConfirmed = SAFE_CONFIRMATIONS + '+'; + } else { + tx.safeConfirmed = false; + wallet.hasUnsafeConfirmed = true; + } + + if (tx.note) { + delete tx.note.encryptedEditedByName; + delete tx.note.encryptedBody; + } + + if (!txHistoryUnique[tx.txid]) { + processedTxs.push(tx); + txHistoryUnique[tx.txid] = true; + } else { + $log.debug('Ignoring duplicate TX in history: ' + tx.txid) + } + }); + + // Update notes? + + return processedTxs; + }; + + /** + * A small cache of up to the 50 most recent transactions + */ + function saveTxHistory(processedTxs, walletId) { + storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){ + // Looks like callback only gets called on error + $log.error('pagination Failed to save tx history.', error); + }); + + } + + // Only clear the cache once we have received new transactions from the server. + function updateTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { + var skip = 0; + var limit = Math.floor(PAGE_SIZE * (1 + PAGE_OVERLAP_FRACTION)); + + var opts = { + skip: skip, + limit: limit + }; + wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) { + if (err) { + return cb(err); + } + + if (!txsFromServer.length) { + return cb(null, []); + } + + var processedTxs = processNewTxs(wallet, txsFromServer); + + if (getLatest) { + console.log('pagination Saving retrieved txs.'); + saveTxHistory(wallet, processedTxs); + } + + return cb(null, processedTxs); + }); + } + + } + + +})(); \ No newline at end of file diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 4d293a01f..6f0c93697 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -398,6 +398,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var skipped = 0; + function fixTxsUnit(txs) { + if (!txs || !txs[0] || !txs[0].amountStr) return; + + var cacheCoin = txs[0].amountStr.split(' ')[1]; + + if (cacheCoin == 'bits') { + + $log.debug('Fixing Tx Cache Unit to: ' + wallet.coin) + lodash.each(txs, function(tx) { + tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount); + tx.feeStr = txFormatService.formatAmountStr(wallet.coin, tx.fees); + }); + } + }; + var updateLocalTxHistory = function(wallet, opts, cb) { var FIRST_LIMIT = 5; var LIMIT = 50; @@ -408,25 +423,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var progressFn = opts.progressFn || function() {}; var foundLimitTx = false; - if (opts.feeLevels) { opts.lowAmount = root.getLowAmount(wallet, opts.feeLevels); } - var fixTxsUnit = function(txs) { - if (!txs || !txs[0] || !txs[0].amountStr) return; - - var cacheCoin = txs[0].amountStr.split(' ')[1]; - - if (cacheCoin == 'bits') { - - $log.debug('Fixing Tx Cache Unit to: ' + wallet.coin) - lodash.each(txs, function(tx) { - tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount); - tx.feeStr = txFormatService.formatAmountStr(wallet.coin, tx.fees); - }); - } - }; getSavedTxs(walletId, function(err, txsFromLocal) { if (err) return cb(err); @@ -437,13 +437,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null; var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null; - $log.debug('Confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length); + console.log('pagination Hard confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length); // First update progressFn(txsFromLocal, 0); wallet.completeHistory = txsFromLocal; function getNewTxs(newTxs, skip, next) { + console.log('pagination getNewTxs skip: ' + skip); getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res) { if (err) { $log.warn(bwcError.msg(err, 'Server Error')); //TODO @@ -456,6 +457,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return next(err); } + console.log('pagination Result count: ' + res.length); // Check if new txs are founds, if yes, lets investigate in the 50 next // To be sure we are not missing txs by sorting (maybe a new tx is after the "endingTxid" var newDiscoveredTxs = res.filter(function (x) { @@ -464,7 +466,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }).length == 0; }); - $log.debug('Discovering TXs. Got:' + newDiscoveredTxs.length); + console.log('pagination Discovering new TXs. Got:' + newDiscoveredTxs.length); var shouldContinue = newDiscoveredTxs.length > 0; @@ -477,7 +479,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim skip = skip + requestLimit; - $log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue); + console.log('pagination Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue); // TODO Dirty // do not sync all history, just looking for a single TX. @@ -499,7 +501,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim skipped = skip; if (!shouldContinue) { - $log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length); + console.log('pagination Finished Sync: New / soft confirmed Txs: ' + newTxs.length); return next(null, newTxs); } @@ -545,6 +547,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim getNewTxs([], skipped, function(err, txs) { if (err) return cb(err); + console.log(); createReceivedEvents(txs); var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) { diff --git a/www/views/includes/walletInfo.html b/www/views/includes/walletInfo.html index aff10fc09..f97ebf9ed 100644 --- a/www/views/includes/walletInfo.html +++ b/www/views/includes/walletInfo.html @@ -5,7 +5,7 @@
+ !walletNotRegistered && !updateStatusError && !vm.updateTxHistoryFailed">
Auditable diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 6021467ee..2548aa4b9 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -284,13 +284,13 @@
+ ng-show="!txHistory[0] && !vm.gettingInitialHistory && !vm.updateTxHistoryFailed && !updateStatusError" translate> No transactions yet
+ ng-show="!txHistory[0] && !vm.gettingInitialHistory && vm.updateTxHistoryFailed" translate> Could not update transaction history
@@ -300,7 +300,7 @@ {{updatingTxHistoryProgress}} transactions downloaded
-
+
From 72cb94d2122b3b20d47873c8365679c22423045d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 21 Aug 2018 08:58:19 +1200 Subject: [PATCH 009/118] Using infinite scroll on cached data. --- src/js/controllers/walletDetails.js | 83 +++++++++++++++++------------ www/views/walletDetails.html | 2 +- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 6df56c943..59c0ddb20 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -1,9 +1,10 @@ 'use strict'; angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService, walletHistoryService) { - - var HISTORY_SHOW_LIMIT = 10; - var currentTxHistoryPage = 0; + // Desktop can display 13 rows of transactions, bump it up to a nice round 15. + var DISPLAY_PAGE_SIZE = 15; + var currentTxHistoryDisplayPage = 0; + var completeTxHistory = [] var listeners = []; // For gradual migration for doing it properly @@ -15,9 +16,12 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun updateTxHistoryFailed: false }; + // Need flag for when to allow infinite scroll at bottom + // - ie not when loading initial data and there is no more cached data + $scope.amountIsCollapsible = false; $scope.color = '#888888'; - $scope.completeTxHistory = []; + $scope.filteredTxHistory = []; $scope.isCordova = platformInfo.isCordova; $scope.isAndroid = platformInfo.isAndroid; @@ -27,9 +31,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.requiresMultipleSignatures = false; $scope.showBalanceButton = false; $scope.status = null; - $scope.txHistory = []; + // Displaying 50 transactions when entering the screen takes a while, so only display a subset + // of everything we have, not the complete history. + $scope.txHistory = []; // This is what is displayed $scope.txHistorySearchResults = []; - $scope.txHistoryShowMore = false; + //$scope.txHistoryShowMore = false; // Is this used anywhere? $scope.txps = []; $scope.updatingStatus = false; $scope.updateStatusError = null; @@ -217,9 +223,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode; }); console.log('pagination Got tx history old way'); - $scope.completeTxHistory = txHistory; + completeTxHistory = txHistory; - $scope.showHistory(); + //$scope.showHistory(); $timeout(function() { $scope.$apply(); }); @@ -256,7 +262,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ $scope.vm.gettingCachedHistory = false; if (err) { - // Don't display an error because we are also requesting the histroy. + // Don't display an error because we are also requesting the history. $log.error('Error getting cached tx history.', err); return; } @@ -267,27 +273,24 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } console.log('pagination Got cached txs, count: ', txHistory.length); - - /* - var shortHistory = []; - if (txHistory.length > 0) { - shortHistory = [ - txHistory[0] - ]; - } - txHistory = shortHistory; - */ - formatTxHistoryForDisplay(txHistory); - $scope.completeTxHistory = txHistory; - $scope.showHistory(); + completeTxHistory = txHistory; + showHistory(); + console.log('pagination Showing tx history items:', $scope.txHistory.length); $scope.$apply(); console.log('pagination displayed cached history.'); }); } function updateTxHistoryFromSmallCache(getLatest) { + if (completeTxHistory.length > $scope.txHistory.length) { + console.log('pagination Showing history we already have.'); + currentTxHistoryDisplayPage++; + showHistory(); + return + } + walletHistoryService.updateTxHistoryByPage($scope.wallet, getLatest, true, function onUpdateTxHistoryByPage(err, txHistory) { console.log('pagination returned'); $scope.vm.gettingInitialHistory = false; @@ -300,18 +303,20 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun console.log('pagination txs returned in history: ' + txHistory.length); formatTxHistoryForDisplay(txHistory); - $scope.completeTxHistory = txHistory; - $scope.showHistory(); + completeTxHistory = txHistory; + showHistory(); $scope.$apply(); }); } - $scope.showHistory = function() { - if ($scope.completeTxHistory) { - $scope.txHistory = $scope.completeTxHistory.slice(0, (currentTxHistoryPage + 1) * HISTORY_SHOW_LIMIT); - $scope.txHistoryShowMore = $scope.completeTxHistory.length > $scope.txHistory.length; + + function showHistory() { + if (completeTxHistory) { + $scope.txHistory = completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); + $scope.txHistoryShowMore = completeTxHistory.length > $scope.txHistory.length; } - }; + } + $scope.getDate = function(txCreated) { var date = new Date(txCreated * 1000); @@ -350,22 +355,34 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return !tx.confirmations || tx.confirmations === 0; }; - + // on-infinite="showMore()" $scope.showMore = function() { + console.log('pagination showMore()'); if ($scope.vm.updatingTxHistory) { return; } + + // Check if we have more than we are displaying + if (completeTxHistory.length > $scope.txHistory.length) { + currentTxHistoryDisplayPage++; + showHistory(); + $scope.$broadcast('scroll.infiniteScrollComplete'); + return; + } + /* $scope.vm.updatingTxHistory = true; $timeout(function() { walletService.getMoreTxs($scope.wallet, function onMoreTxs() { - currentTxHistoryPage++; - $scope.showHistory(); + currentTxHistoryDisplayPage++; + //$scope.showHistory(); $scope.$broadcast('scroll.infiniteScrollComplete'); $scope.vm.updatingTxHistory = false; }); }, 100); + */ }; + // on-refresh="onRefresh()" $scope.onRefresh = function() { $timeout(function() { $scope.$broadcast('scroll.refreshComplete'); @@ -376,7 +393,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.updateAll = function(force, cb)  { updateStatus(force); //updateTxHistory(cb); - updateTxHistoryFromSmallCache(); + //updateTxHistoryFromSmallCache(); }; $scope.hideToggle = function() { diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 2548aa4b9..ea1afb197 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -306,7 +306,7 @@
From 0bd94601aed7e3714b41e69667d5c08e78b5f218 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 21 Aug 2018 09:17:56 +1200 Subject: [PATCH 010/118] Controlling when infinite scroll is available. --- src/js/controllers/walletDetails.js | 7 +++++-- www/views/walletDetails.html | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 59c0ddb20..173cb73d2 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -9,6 +9,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // For gradual migration for doing it properly $scope.vm = { + allowInfiniteScroll: false, gettingCachedHistory: true, gettingInitialHistory: true, updatingTxHistory: false, @@ -35,7 +36,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // of everything we have, not the complete history. $scope.txHistory = []; // This is what is displayed $scope.txHistorySearchResults = []; - //$scope.txHistoryShowMore = false; // Is this used anywhere? $scope.txps = []; $scope.updatingStatus = false; $scope.updateStatusError = null; @@ -258,6 +258,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); } + function updateTxHistoryUsingCachedData() { walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ $scope.vm.gettingCachedHistory = false; @@ -313,7 +314,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function showHistory() { if (completeTxHistory) { $scope.txHistory = completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); - $scope.txHistoryShowMore = completeTxHistory.length > $scope.txHistory.length; + $scope.vm.allowInfiniteScroll = completeTxHistory.length > $scope.txHistory.length || !$scope.vm.gettingInitialHistory; + } else { + $scope.vm.allowInfiniteScroll = false; } } diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index ea1afb197..cb250a0ed 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -306,7 +306,7 @@
From 78f0ff28cdc319df55b778644a273cc313fdaebc Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 21 Aug 2018 12:43:03 +1200 Subject: [PATCH 011/118] Getting earlier transactions with pagination. --- src/js/controllers/walletDetails.js | 44 +++--- src/js/services/wallet-history.service.js | 183 ++++++++++++++++------ 2 files changed, 163 insertions(+), 64 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 173cb73d2..d22ab388f 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -198,6 +198,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }; + var updateTxHistory = function(cb) { if (!cb) cb = function() {}; $scope.vm.updateTxHistoryFailed = false; @@ -233,6 +234,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }); }; + function applyCurrencyAliases(txHistory) { var defaults = configService.getDefaults(); @@ -259,7 +261,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } - function updateTxHistoryUsingCachedData() { + function updateTxHistoryFromCachedData() { walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ $scope.vm.gettingCachedHistory = false; if (err) { @@ -277,27 +279,24 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun formatTxHistoryForDisplay(txHistory); completeTxHistory = txHistory; - showHistory(); + showHistory(false); console.log('pagination Showing tx history items:', $scope.txHistory.length); $scope.$apply(); console.log('pagination displayed cached history.'); }); } - function updateTxHistoryFromSmallCache(getLatest) { - if (completeTxHistory.length > $scope.txHistory.length) { - console.log('pagination Showing history we already have.'); - currentTxHistoryDisplayPage++; - showHistory(); - return - } + function fetchAndShowTxHistory(getLatest, flushCacheOnNew) { + $scope.vm.updatingTxHistory = true; - walletHistoryService.updateTxHistoryByPage($scope.wallet, getLatest, true, function onUpdateTxHistoryByPage(err, txHistory) { + walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory) { console.log('pagination returned'); $scope.vm.gettingInitialHistory = false; + $scope.vm.updatingTxHistory = false; + $scope.$broadcast('scroll.infiniteScrollComplete'); + if (err) { console.error('pagination Failed to get history.', err); - $scope.txHistory = null; $scope.vm.updateTxHistoryFailed = true; return; } @@ -305,16 +304,17 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun formatTxHistoryForDisplay(txHistory); completeTxHistory = txHistory; - showHistory(); + showHistory(true); $scope.$apply(); }); } - function showHistory() { + function showHistory(showAll) { if (completeTxHistory) { - $scope.txHistory = completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); + $scope.txHistory = showAll ? completeTxHistory : completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); $scope.vm.allowInfiniteScroll = completeTxHistory.length > $scope.txHistory.length || !$scope.vm.gettingInitialHistory; + console.log('pagination Showing txs: ', $scope.txHistory.length); } else { $scope.vm.allowInfiniteScroll = false; } @@ -372,6 +372,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.$broadcast('scroll.infiniteScrollComplete'); return; } + + fetchAndShowTxHistory(false, false); /* $scope.vm.updatingTxHistory = true; $timeout(function() { @@ -393,10 +395,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.updateAll(true); }; - $scope.updateAll = function(force, cb)  { - updateStatus(force); + $scope.updateAll = function(forceStatusUpdate, getLatestTx, flushTxCacheOnNew)  { + console.log('pagination updateAll()'); + updateStatus(forceStatusUpdate); //updateTxHistory(cb); - //updateTxHistoryFromSmallCache(); + fetchAndShowTxHistory(getLatestTx, flushTxCacheOnNew); }; $scope.hideToggle = function() { @@ -538,10 +541,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var refreshInterval; $scope.$on("$ionicView.afterEnter", function(event, data) { - updateTxHistoryUsingCachedData(); - $scope.updateAll(); + updateTxHistoryFromCachedData(); + $scope.updateAll(false, true, true); refreshAmountSection(); - refreshInterval = $interval($scope.onRefresh, 10 * 1000); + //refreshInterval = $interval($scope.onRefresh, 10 * 1000); + //refreshInterval = $interval($scope.onRefresh, 120 * 1000); // For testing }); $scope.$on("$ionicView.afterLeave", function(event, data) { diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 3679dd088..00847cbbf 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -7,32 +7,109 @@ .factory('walletHistoryService', walletHistoryService); function walletHistoryService(configService, storageService, lodash, $log, txFormatService) { - // 8 Transactions fit on an iPhone Plus screen when in Wallet Details. - // Make it a little bit bigger so it doesn't have to load more immediately - var PAGE_SIZE = 50 / 1.5; + //var PAGE_SIZE = 50; + var PAGE_SIZE = 20; // For dev only // How much to overlap on each end of the page, for mitigating inconsistent sort order. - var PAGE_OVERLAP_FRACTION = 0.5; + var PAGE_OVERLAP_FRACTION = 0.2; + var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION); + // The amount of transactions in the new overlapping resultset that we already know about. + // If we know about at least this many, then there are probably no gaps. + var MIN_KNOWN_TX_OVERLAP = Math.floor(PAGE_OVERLAP * 0.5); var SAFE_CONFIRMATIONS = 6; var service = { getCachedTxHistory: getCachedTxHistory, - updateTxHistoryByPage: updateTxHistoryByPage + updateLocalTxHistoryByPage: updateLocalTxHistoryByPage, }; return service; + function addEarlyTransactions(walletId, cachedTxs, newTxs) { + + var cachedTxIds = {}; + cachedTxs.forEach(function forCachedTx(tx){ + cachedTxIds[tx.txid] = true; + }); + + var someTransactionWereNew = false; + var overlappingTxsCount = 0; + + newTxs.forEach(function forNewTx(tx){ + if (cachedTxIds[tx.txid]) { + overlappingTxsCount++; + } else { + someTransactionWereNew = true; + cachedTxs.push(tx); + } + }); + + console.log('pagination Overlapping transactions:', overlappingTxsCount); + if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good + if (someTransactionWereNew) { + console.log('pagination someTransactionsWereNew'); + saveTxHistory(walletId, cachedTxs); + } + return cachedTxs; + } else { + // We might be missing some txs. + console.error('We might be missing some txs in the history.'); + // Our history is wrong, so remove it - we could instead, try to fetch data that was not so early. + storageService.removeTxHistory(walletId, function onRemoveTxHistory(){}); + return []; + } + + } + + function addLatestTransactions(cachedTxs, newTxs) { + } + + // Only clear the cache once we have received new transactions from the server. + /** + * @param {function(err, txs)} cb - transactions is always an array, may be empty + */ + function fetchTxHistoryByPage(wallet, start, cb) { + var skip = Math.max(0, start - PAGE_OVERLAP); + var limit = PAGE_SIZE; + + var opts = { + skip: skip, + limit: limit + }; + wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) { + if (err) { + return cb(err, []); + } + + if (txsFromServer.length === 0) { + return cb(null, []); + } + console.log('pagination Transactions fetched:', txsFromServer.length); + + var processedTxs = processNewTxs(wallet, txsFromServer); + + /* + if (getLatest) { + console.log('pagination Saving retrieved txs.'); + saveTxHistory(wallet, processedTxs); + } + */ + + return cb(null, processedTxs); + }); + } + /** * @param {string} walletId - * @param {function(error, txs)} cb + * @param {function(error, txs)} cb - txs is always an array, may be empty */ function getCachedTxHistory(walletId, cb) { storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){ if (err) { - return cb(err); + return cb(err, []); } if (!txHistoryString) { - return cb(null, txHistoryString); + return cb(null, []); } try { @@ -40,7 +117,7 @@ return cb(null, txHistory); } catch (e) { $log.error('Failed to parse tx history.', e); - return cb(e); + return cb(e, []); } }); } @@ -83,46 +160,64 @@ return processedTxs; }; - /** - * A small cache of up to the 50 most recent transactions - */ - function saveTxHistory(processedTxs, walletId) { + function saveTxHistory(walletId, processedTxs) { + console.log('pagination Saving transactions:', processedTxs.length); storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){ - // Looks like callback only gets called on error - $log.error('pagination Failed to save tx history.', error); - }); - - } - - // Only clear the cache once we have received new transactions from the server. - function updateTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { - var skip = 0; - var limit = Math.floor(PAGE_SIZE * (1 + PAGE_OVERLAP_FRACTION)); - - var opts = { - skip: skip, - limit: limit - }; - wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) { - if (err) { - return cb(err); + if (error) { + $log.error('pagination Failed to save tx history.', error); + } else { + console.log('pagination Save successful.'); } - - if (!txsFromServer.length) { - return cb(null, []); - } - - var processedTxs = processNewTxs(wallet, txsFromServer); - - if (getLatest) { - console.log('pagination Saving retrieved txs.'); - saveTxHistory(wallet, processedTxs); - } - - return cb(null, processedTxs); }); } + + function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { + + if (flushCacheOnNew) { + console.log('pagination Getting latest txs.'); + fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ + if (err) { + return cb(err, txs); + } + saveTxHistory(wallet.id, txs); + return cb(null, txs); + }); + } else { + console.log('pagination Getting early txs.'); + getCachedTxHistory(wallet.id, function onCachedHistory(err, cachedTxs){ + if (err) { + $log.error('Failed to get cached tx history.', err); + return cb(err, []); + } + + var start = getLatest ? 0 : cachedTxs.length; + console.log('pagination Transaction fetch start:', start); + fetchTxHistoryByPage(wallet, start, function onFetchHistory(err, fetchedTxs){ + if (err) { + return cb(err); + } + + if (fetchedTxs.length === 0) { + return cb(null, cachedTxs); + } + + var txs = []; + if (getLatest) { + txs = addLatestTransactions(wallet.id, cachedTxs, fetchedTxs); + } else { + txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs); + } + return cb(null, txs); + }); + + + }); + } + } + + + } From d5f01e9713b5c7338662900f8679e631e598880e Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 21 Aug 2018 21:36:55 +0200 Subject: [PATCH 012/118] stop fetching the wallet history when everything is fetched --- src/js/controllers/walletDetails.js | 54 +++++-------------- src/js/services/wallet-history.service.js | 63 +++++++++++++++++++++-- 2 files changed, 72 insertions(+), 45 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index d22ab388f..fece19906 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -13,6 +13,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun gettingCachedHistory: true, gettingInitialHistory: true, updatingTxHistory: false, + fetchedAllTxHistory: false, //updateTxHistoryError: false updateTxHistoryFailed: false }; @@ -108,6 +109,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.updatingStatus = true; $scope.updateStatusError = null; $scope.walletNotRegistered = false; + $scope.vm.fetchedAllTxHistory = false; walletService.getStatus($scope.wallet, { force: !!force, @@ -198,44 +200,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }; - - var updateTxHistory = function(cb) { - if (!cb) cb = function() {}; - $scope.vm.updateTxHistoryFailed = false; - $scope.updatingTxHistoryProgress = 0; - - feeService.getFeeLevels($scope.wallet.coin, function(err, levels) { - walletService.getTxHistory($scope.wallet, { - feeLevels: levels - }, function(err, txHistory) { - $scope.vm.gettingInitialHistory = false; - if (err) { - $scope.txHistory = null; - $scope.vm.updateTxHistoryFailed = true; - return; - } - - applyCurrencyAliases(txHistory); - - var config = configService.getSync(); - var fiatCode = config.wallet.settings.alternativeIsoCode; - lodash.each(txHistory, function(t) { - var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin); - t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode; - }); - console.log('pagination Got tx history old way'); - completeTxHistory = txHistory; - - //$scope.showHistory(); - $timeout(function() { - $scope.$apply(); - }); - return cb(); - }); - }); - }; - - function applyCurrencyAliases(txHistory) { var defaults = configService.getDefaults(); var configCache = configService.getSync(); @@ -289,7 +253,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function fetchAndShowTxHistory(getLatest, flushCacheOnNew) { $scope.vm.updatingTxHistory = true; - walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory) { + walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory, fetchedAllTransactions) { console.log('pagination returned'); $scope.vm.gettingInitialHistory = false; $scope.vm.updatingTxHistory = false; @@ -300,6 +264,12 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.vm.updateTxHistoryFailed = true; return; } + + if (fetchedAllTransactions) { + console.log("All transactions seem to be fetched.."); + $scope.vm.fetchedAllTxHistory = true; + } + console.log('pagination txs returned in history: ' + txHistory.length); formatTxHistoryForDisplay(txHistory); @@ -313,7 +283,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function showHistory(showAll) { if (completeTxHistory) { $scope.txHistory = showAll ? completeTxHistory : completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); - $scope.vm.allowInfiniteScroll = completeTxHistory.length > $scope.txHistory.length || !$scope.vm.gettingInitialHistory; + $scope.vm.allowInfiniteScroll = !$scope.vm.fetchedAllTxHistory;//(completeTxHistory.length > $scope.txHistory.length || !$scope.vm.gettingInitialHistory) || (!$scope.vm.gettingInitialHistory && !$scope.vm.fetchedAllTxHistory); console.log('pagination Showing txs: ', $scope.txHistory.length); } else { $scope.vm.allowInfiniteScroll = false; @@ -392,7 +362,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $timeout(function() { $scope.$broadcast('scroll.refreshComplete'); }, 300); - $scope.updateAll(true); + $scope.updateAll(true, true, false); }; $scope.updateAll = function(forceStatusUpdate, getLatestTx, flushTxCacheOnNew)  { @@ -544,7 +514,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun updateTxHistoryFromCachedData(); $scope.updateAll(false, true, true); refreshAmountSection(); - //refreshInterval = $interval($scope.onRefresh, 10 * 1000); + refreshInterval = $interval($scope.onRefresh, 10 * 1000); //refreshInterval = $interval($scope.onRefresh, 120 * 1000); // For testing }); diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 00847cbbf..e8ba8cf52 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -18,12 +18,46 @@ var SAFE_CONFIRMATIONS = 6; + var allTransactionsFetched = false; var service = { getCachedTxHistory: getCachedTxHistory, updateLocalTxHistoryByPage: updateLocalTxHistoryByPage, }; return service; +/* + function hasAllTransactionsFetched(walletId, cachedTxs, newTxs) { + var cachedTxIds = {}; + cachedTxs.forEach(function forCachedTx(tx){ + cachedTxIds[tx.txid] = true; + }); + + var someTransactionWereNew = false; + var overlappingTxsCount = 0; + + newTxs.forEach(function forNewTx(tx){ + if (cachedTxIds[tx.txid]) { + overlappingTxsCount++; + } else { + someTransactionWereNew = true; + } + }); + + console.log('pagination Overlapping transactions:', overlappingTxsCount); + if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good + if (!someTransactionWereNew && overlappingTxsCount === newTxs.length) { + console.log("We probably have all of the transactions fetched!!") + return true; + } + } else { + console.log("Something went wrong") + return true; // Something went wrong, so stop fetching.. + } + return false; + + } +*/ + function addEarlyTransactions(walletId, cachedTxs, newTxs) { var cachedTxIds = {}; @@ -48,6 +82,9 @@ if (someTransactionWereNew) { console.log('pagination someTransactionsWereNew'); saveTxHistory(walletId, cachedTxs); + } else if (overlappingTxsCount === newTxs.length) { + console.log('We probably have all transactions now'); + allTransactionsFetched = true; } return cachedTxs; } else { @@ -60,7 +97,26 @@ } - function addLatestTransactions(cachedTxs, newTxs) { + function addLatestTransactions(walletId, cachedTxs, newTxs) { + var cachedTxIds = {}; + var someTransactionWereNew = false; + cachedTxs.forEach(function forCachedTx(tx){ + cachedTxIds[tx.txid] = true; + }); + + newTxs.forEach(function forNewTx(tx){ + if (!cachedTxIds[tx.txid]) { + console.log("Brand new transactions pushed to top of the cache.") + someTransactionWereNew = true; + cachedTxs.unshift(tx); + } + }); + + if (someTransactionWereNew) { + saveTxHistory(walletId, cachedTxs); + } + + return cachedTxs; } // Only clear the cache once we have received new transactions from the server. @@ -199,7 +255,7 @@ } if (fetchedTxs.length === 0) { - return cb(null, cachedTxs); + return cb(null, cachedTxs, true /*fetchedAllTransactions*/); } var txs = []; @@ -207,11 +263,12 @@ txs = addLatestTransactions(wallet.id, cachedTxs, fetchedTxs); } else { txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs); + return cb(null, txs, allTransactionsFetched/*, hasAllTransactionsFetched(wallet.id, cachedTxs, fetchedTxs)*/); } return cb(null, txs); }); - + }); } } From e5560bf63a3e3f411f07e688428576971ef763d1 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 11:48:19 +1200 Subject: [PATCH 013/118] Images with config file changes. --- app-template/config-template.xml | 41 ++++++++++-------- .../bitcoin.com/ios/icon/AppIcon24x24@2x.png | Bin 0 -> 1503 bytes .../ios/icon/AppIcon27.5x27.5@2x.png | Bin 0 -> 1835 bytes .../bitcoin.com/ios/icon/AppIcon44x44@2x.png | Bin 0 -> 2894 bytes .../bitcoin.com/ios/icon/AppIcon86x86@2x.png | Bin 0 -> 5925 bytes .../bitcoin.com/ios/icon/AppIcon98x98@2x.png | Bin 0 -> 7083 bytes resources/bitcoin.com/ios/icon/icon-1024.png | Bin 0 -> 23536 bytes resources/bitcoin.com/ios/icon/icon-20.png | Bin 0 -> 425 bytes 8 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png create mode 100644 resources/bitcoin.com/ios/icon/AppIcon27.5x27.5@2x.png create mode 100644 resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png create mode 100644 resources/bitcoin.com/ios/icon/AppIcon86x86@2x.png create mode 100644 resources/bitcoin.com/ios/icon/AppIcon98x98@2x.png create mode 100644 resources/bitcoin.com/ios/icon/icon-1024.png create mode 100644 resources/bitcoin.com/ios/icon/icon-20.png diff --git a/app-template/config-template.xml b/app-template/config-template.xml index 2f8e3db04..3f8abc26e 100644 --- a/app-template/config-template.xml +++ b/app-template/config-template.xml @@ -85,23 +85,30 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png b/resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67dddbb02b402dfc95b9cab72e32ab0c11144fd4 GIT binary patch literal 1503 zcmWku2~^B$6#r{Vn?Vbcy-k#ao>KDKNaZ9$y~fm`EA>=p-A`_B39x%b@r`+nzM<@$QEb>`|&6vg)T+OUne zSN?P@7K0(}+PM^^c9ZMl?*aVTAeaM!xganC{w&DThFnd^oeKH7z~?}o76>>qej1@MDq9W((@}{f!3y*t5b|}P+EYa%6-`uR z0))1x%$mk>m5PSi-H@w+icL|?7A93iCQ-#YByym!LdDeixF5YWLHTUp8==}@3}a-x zn@kRniC#SUouFYd-i76#@sJRtNl5ICm~IR8pe2$ijAS{e*aBKkW91iAY6%51QMCv3 zzro$lU_bz}T-=j}d()ve9m{fvycLFos9*-Xp`c;)nR+?RSuA1GM@t|bPO7gpei@S@X+@P28uwI4?X`ve-`{FhA+3FDFO=g5F?@beUL=~qmW8x zRI-%72s9r@<*TqlN>2{ZXb6whqmspl&p`tM8b;}fK7s~;QG#F&Dq2v@FG;5;mbUi> zH-E3H4@rT6jjo2#3GZVEGh%f4lBP5-ZkN@v4IX8iR$0B|agu)HYGyHFh=5=4sNFYmm z^wN3CwLeUBp7~~nutLwU&+E$8KMtw%d9eMw;=QSrxx{{Xw?p4d&ii}e+`;IT(hofL z^l)p6V{KUQu-x=+ZD#21qKj$A+#a#!=-OH@-;sCLWAUwU7D$z?@Dv%!4ND;e>z&om@{YA<|lDc199ayTiM)H>p}AD0_tc8nVEXQ(}?PCobb zDQo9yUq{~ukNhaf_dUy$CaXTVs7neR{k5ZtM`B7GZT9G&Fm>Or*OO91A}ojAHBl{E zqPpgSmr>M`_UIjH^_0dM#j7g@e-JyX#oOzh<20rA{nsWkoFZ4W|8N>#v~Cy6_VjUk z7c!dAYIu2TMA!4Py+)6t*PfJTtA9RP8yxg&jIn7~u#Pfrg-m;7(^(R0b7Ycb&5_R# zi3HNOs*%x#aB0dbX|Zall`h|r8@F-RESI5HNn!xDy?xSd+O-Z}%@tvTO206r-v#}k e%|j{BP5 zgouI=ASB8Y9}+y!J|K9Z;-eLXwrUGOD3k~sQm0@V9LMXg_3phhhlhLpmBik=wy{%* z(Ma~r-nsMhnK@_9{Kt|$wTD~2=yn7ApK^;!)O8PWAPXM?7H-yVnBPW9qdfru1Ol#x zR>43>8(0bghD1dD+eK+eWQV_?)dPgoG!A1=YU3qb&HTLWf5;6U$2W1+F!>_?eo`a> z+Z0ME8;L~8W^CXc(eYm|&Z{5)Wq9!;67`$w)%(x6Xh+fRXTvjZldOS6n*v(Sd&D6T z;zWA@EG4u6LC^#gS5Zp=LK~1UkE>SU$?l?29Keoy#Aou6p~8?d2!Yz|u+o`cD}-D{~{n z16_fZS(idK3E-QAbfuXCp;c7NR@BAz(85x&pz%O_;FJ39XKl}iNv{>Nv1ycM77YNn zs%gB;XAM!)3VAKOWT9$h*;jcl!%`R5qhmj@q4$$D%7b>Calz8C?_=8cRpYa`7^o0Z-Oq=W%!P zIl-`ixN!Ap|NHk!R9V_5Z+O3nqvtjmJ{|3U2n&c}DH5k6XXL_{NQSVi6m7O?0Ubs zv1(N`v5RL65G|rr?J(6tgjN}bEy>(0!_}C1CCvRD1T!$vSSow5G>5(gC;?Rb9Ie-L z>fGB_b-V4OG;k(K@kzuW6v3!7=+;mQ<}DeN+Tr3OLOMzFG8&gL=Y}a|#gCgmp$X{( zyFcWTU6{{}XN^BFe;KWmv1Rusit7-HAyyyBGUl(?z`NY=X#i2o2MD9z6$>RZfsIlc zLCkDbN+B8IwvW3bU)H#e1@K7h$DZKI&xq?KbB^^=iULxGwWpfBKa8IV34_z*sM(1w_NtIe-3h z;;L);xa*~Nn0*hrfp<#IN{EI|rT6^MUjC>^sinDYTN*&tVB)v9zP0RO0sFaK`c`)E zk?1X76>*@1b~N;!?BF9@JXIBdrMjYBq-fMN zQ_Mhn+@iYP^-UFr`CK&g?QzC`we~@|Jf)B|us}9nD1(b7q?S%fXszL-hT>}MI;D4h z#qGEo^N1^?GR>G2c&%!*VA#j%h(bT zm24Wo&!I6AFMb1INh>Tg=|U1-JcWTE0>zp($i|-FFQ1oe3Rl~_6NUMpRRLTrAAgxG z)wHsRyu8nid8eg~F!8iq{+|5jscseFQr=@19?R;-$fl{@5#8|_VIjYq8}Y9`E}9tX z4n3;LP`(U8X#0PBL(++S6`G%AevFw{q&X@g#5)$!l(MCaGAQ_2EWMdB_#6M+`$Q8H zr}v+8y+?BYu+a3++|BF=u7X)H?WPW_2k-B(QCceekGN`kcTu&$IU{1i(?Sx|0!V}3_lxMR|-A|X~WV9Skw*u zGG|&(DSdYaAXsMocS$mYrHHes^O8=WF`;dh{nB?N*LiLJHJSYz+i=6T2T}v zT{sK~*2cFGFhdixXR{Dl&z){mTM9di3lu{V;-00)x^dx6r)2fvH~CI{d3j^}ufZFG Z^1rK|a?W!K7^?sP002ovPDHLkV1iQxe~$nF literal 0 HcmV?d00001 diff --git a/resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png b/resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ac78920fc1240fdffd9dce06b3832b0b449967f2 GIT binary patch literal 2894 zcmV-U3$gTxP)T!fKb&)Wp93@Z+?n>) zSvi^ZoV)Ltvwvspwbov1?_s!o5X%C{nSOw?MV3J%vJ4`TWe|xhgGgi`VmZqnCC2r;rO#rN1i9fZyRTn!I1nEmIw*i~( zkf5I^s{oBV{J1&tJ4hbzF$HhBap5;iels)z2zW5#CjP;LKRja?+6IE6uE61Y?7C0Y zb%v`y?4Es>%#1>^pdg&kOxHiWJXXC5 z2>_}qNZ%CzYR!`Xz^V_aI})m8O;j%Ku?K8ZD|p;3@oZ8EEI-qn8iE_ZCZ-~WI){tRq?B@%0gq;Q|H ze#YjF=1q*{yqZZ>v-psNoqA{_fL(o^efM^E(Rb9(K;4{ncS%7cZ)4Hzd7>Az0I5#k zgWIg5j>)c=CCgc;5Bqj6sW_7r9MXaiy$$A2Wjze^N6F6rj`4 z{q0(r+6|LS=~G(|sHh<({$4&4hNlotlpnFG9UzmtaAF5+?|hz>@g3Adoszz5t#0^| z-EyxaKB8*!u1*-Z&3EYwpCDiyNRQM2?K^%O@PwxXwlks)l)tZ>9a~T8x1+9c9aO6n< zHTq8iV15|3J7qejBS0Wd{P+_)T>UeXfCd9FJt>nqodbdaXGS%#1vR6<+8#Sy5#aAqMfkHGe5 z@7sC)CuGAHtYo0BGAPu85&`gU?nt4|DcPm5d?yyqO$-oNwlDeB2=ZkJdJwicHPR+hY<|4g~C#R z004L_ED9z7l8DoG6;TgHN1nn<_exS$v7vHFD^o==ct;t8S<>1lQiAzSMG$ItN6Y ze()uZ{#4_$+Fo$rS_J2z)dA8;AfQequP2^ERGXnB0Z4u$@|)p{jjP$tIS^1tfYK42 zIRv08Dk0QEGMe8A|No#704Vgc2*(pHRg4HQ+xjBrP6GizebdFt3ee){O!Mh-gv^`@u|W(~S-SLHMH2t;4#{0;mb49KO$X zt!2HBR0Z6zKZq;T@0mHvK(OlPLi-;tKK7V>&2PHhUhqZ2*Y}pzMUy!E1WmqW$B}Xe z56j_QU2?H03oej_LLGdlZU&hFjBE=w-_Ay$T2f~G=%X_AZ%vV#NwI^+7gZyQg#r}y zMb43TQ6Iw3(gzDMV}w|5yO~{!RUgFq&)DwaWG!ml_Aq9mnS0MZryTHV3pi@xU~ zUc8mTie$Th579jg)S9ulLNT%p(KP3sv()&Drb~i8$qoqv0MMv@i-UU5I6!yaBBi6S zE0!Dt4cs)(zsat8|LNz_%D?scZ+qXSX_80kK-LEcN#PHE=Pfe69YLyuWvd{7crgzE z_)`3c1d=Rsv#zj(^o=YR@DqWmwp@hDXHKhURAsU{HMjzpU2cjyh=1a_PF})+TEL3US)E-Rlfn=y2P3RgXau66b6y+G{!CdU3s z+oKc~PwAGsb>w?gS8)XDNXw&7>5KO;+Xo+&^Ki;!FQGuPkS@fr<09-LZ+&P$GB91q z8HFGiG;K=Lji3*vmn3{x`BqgE0F*3(uJY#AnvW`$vVCKrT_BKzz?HMPH?HXEN|)4@ zw(D+JwQFU`rCqg8txP5AR>smQg*9G7%HYbyJ9VHT%o6M&U2G5poOd%o^?pzwcCD34 zoPzYxNdX|)s&5+rF23H`AXFh){C8Uz^oi|(dN972@kgHKTtQx&VK3GJF7QL5P!ip}UTP=u)meqR~t3>Zhl#uArMUNVy*I zdt2@6`@a9c`@=nF=FB;B@7#Hw^UOR^S{g4&2pI?g004=q3QXrAm;V0)0X;-}-H|u| zfM!<}rl99Jx0mgMGMz>)wmI$jnG3CstRAv_^8TH`^ZRq@TseXxGMfRu+lO4Hfmm_` zQs*k`u&FaJYgkgbFxh{GJ}2vX4gIAa@|1Lb?gJ{f+_){6&7Go|@O^)YuJ&k%cl6T| zde?9vw*wj5ii&_NsfqZkbf55_74$hwd@7_H^L|X%QVs_#=W`Cvug4VhOlD^qF!J4( zB`_`RsN63U3b@?ldb$7S1c+Sm$Dw5fz^GJ*ft1K2B`~FG^sR}v}BpblX#qg)Dx%H zX3=}He-pS83_=>@+dVld8KjOToyZpgF7M)MSiQ&-=+cGgz zlj~2~)&E2@IrruBM~J^}KL6_GUJ@+}QBbi9-w9|X4Xt@qIz90&Bt`Q5-uP#8|H5o$*AgufmPYv*V6b!2X$=6q{)hBnNPG$ckbe7R!-EpFfY$Wis)24Z zSHC4U{9_aN4CTZS8C`pz&00a%b;6XK!DYJ0!?h|_n3ju|9<(9w+2}WvRg?`KFTq?QZTH z>b1qwm^X#VOA`1)xqAFEj&jChf0^fF@sZ+4h1hFG0 z8m62szvk7UcOu)Rvq^jwsRHjb9y0*YO%iQ8k3tN`ye5;RLxSgftk+UW2I}4uW>w(~j*DXwSabA!@RCJm)+$@G= z9@EeNlNDqifzRu3|0JBx819gsVxOIObdiYxphFFbSo-)9v2a>VEc0AfF~EWBRXg z>!&$B%Ep{1KFJ>@^)ZoWJnBcH)5P+`07fmNK~3NEVLwLxdt4r)tnjY{v>kn^k)hrf z@uHV9PvJm=0Aa4hqB1_)PmLBI26%U4BkrqOwgRvV=tI-`??U*+r{1>GuTC@n>XV^z z$u$nhicQaQ=48x|G6N*MB1(R&b#FsTU;+lj0zZ@y#M^1Oi{}-m7kA3n4 z9hEAmgs`8wQir0gTPL&S2ipF4Sw8`>aBvqZv%mz|&U!ZXwCpH{zd*(b=8jjYJ}2#R z3T+6s6gA*sFHZoRnoA&lA0>1OKHXg zkp;Ya)tc??XTJX3%{E7m*`0r9u~{V z3s>;H0w$}5hVgpVCo!gcW%s)+tMeT!U0<5nP|eTDi>G1Exx{pOUq2(xYYu(W(*0`ERAkVvO%aUs(cn`>oi&kL{<@`xhL8Qm_F3lOk5YxsV< zZC?FyKSDj^3C&mj-rmixC7|57oS0Tt0^%VZ-m_OfSoXstbhPOAk>y7)1CwlUj`fUR+>-UTn;qVm^T&rVBPDkv_J9C=hfol;T ziMXtmBPqe%bF4-F?xpQsMF4ZawwO@6jH5rFJFl=Pann9de1kX45~cU&OLb{a5`oIP zX`dFxAyORMahi=*Lvhz{v#N+fe&NW4ZcQ^Ig+F+>vIqZ`=j+?X?-FXu{_mO@VO(i3 zKyS1)(!3OHg%K%(IZIG!<3`2YE|=gp@%wX+>TuID+k@ZVw^VP%{IEqu!oeZ(i1*t^ z0f`6ohsRV`jfE0?@Mgy31J%H;sf`_aa~NkP~yRdN}7cdoY*<_;2TL-3rj`j&&4zmp+& z;hfyVnzsaIhOa&laxqUn;uP+Aj5doxAkyZoRy$c$`+Yh#GqoG2ORFAPyjxj)#p<=O z;=P<55g3T~Cg;x_KiTMY1vw7^@wHvq@nVmKRusY+f{<^eYd!(YU?>DAo(m&H;A_V} zh{NDu9N3r9X7~wcAkBm3s=DWijb0RDq%WPHW~1=h!~j;>peSqhL=Q?Q-ExAwt&3pS ze34VejKqz7UR^-ZAx{o-`)ehSSq^liIMh8}8?b6fB$FttluG_Y;SAjV!%_wiRSwW* z5AIMHp>!C3$M~XcL>$0Ig1Gkb{8|l`!U3&su8xaEtXuR=@_!s=2O3-&$4=^ost!8- zG$p5&MoOGq-~dlalJP$I;@L6*x4TfWyPE95LCYy*@7$N3fCt#-K8sWy2lFy2*`dVB z3m8c8LhMqqy9Xav9`FLNnLvp^;d|uA*e-@izJKl!N@Fn1*q!_I(OjJ)Al}mIJ!dUO z+?t!*M3xlqEs_5w)8S#9=OVAm=1fO?N?cfC0K@zG9!VB?_F$mAmC>J_>wkMpB4gNC zoWQSa?dF$??rq=6)Tll_=9~O=c#H^aB3=4}soHv@OW+q*z%^VJetzP+|A@?%PdZ+L zlv~Z&p6Xd3!`P?ai@bt87wdBC#!#EGY0qOJ{M)~_Zzk5lsfrMhbo_4@C{nM}-~ zuKRd|m0JoNf!3A>C`B~7N?{eSpE`*-5IpVYG(m(OEv%U#^+DoPVw4xWY=o!#X*$2nu8v4Rqx}`TWs3BL7&-CdefPRH%X+_4mn*4!v zF@TS*Q@6XA<(Z0JT3Momqui17nPZhiL4}3qkrW709=Wh{mx|#|sdAxQY62dP_yG!H zM$l*@1!=1;K4$CLunor@t@$PY@?clZ)6R(ua7v;jHMg1o0us;S%FP*%acc=8+LYRyj@QEkt4V5PYQM1NV??EQ zjdh*S-^q?(z!-!XEU(>P-lSQUdE#_WY3$GEQ1h?X&Mi58pdfmp0 z#5!Z2YAnRAD+p>iJ9WLwv$oW67?Y~q%9Qj7kd!%%O-uQ;J>S938l-2`qlV6VB-J4T zCTt9%l9_moUOU}EZptz?@e4mvjIZ{0VVCFUP{0{j*`F?w`cDOO_x#74CkNhNL$D0x zyW+ht4`t_~L}o}Tan!79#yxaH)=AS0UGBy2bh)J^2VRrsOnx6oUP4*!?IC+1Z3Tl!mrR~*v%G;NpUpf^y{j7Dn5sDg3P8L?_umt?$Wg07& zN=oVRRNRM$}DvMnQE1MTy9xhBBXn3BL|`A`myzyDfNrd-GCK`UsraHd1PMtoTJ7PEid%4F}t zgRFfZs30;^>HCUvNHv9feq@XYlDflGCvLPuVV+GeDlRs_xAXOq_3_HE$tMCknBo zlFMaO7r26^h8MQH9HqQo)E^>nC#EpTv*BplZ3R86I?M+y4K92ePK#^8Q)UldX2v&e zJ!Yf5({Je_%2XV1#NySO?a6LuY$rp)!-xk{YcB1Mbkl9Y4zAd@F}{>}6dfbE5}*u0cP`Ja1KrUTAXv-&#Zn@ajclP59>I>IX)~ThRZE;(XbV zpY`4*9WkoZp+3V?VGqt@(c!F0ZSa-=1m-EsMN($zL+SpbqU@v!6wEgI#YHBI-Nk*l zAqv#BI4MMQTOI~3NK&=`)%+mh;kX3cUOR}{9TuD<{+K(uPIpq_?Dw2(ltt#c*jK^8 z%H0Api7H9x1E1L-Hq|nselHJtTQ%cd+yC-cWl-)?ZRQy6;9-zbde9R;?RWHu>&677 zY5a#rEz5VNBA3c&j1Noab)iWsyH=IjwAvLg0cRMQ^NjGLHV)8c<|QL%?8Mc8OPMt9 z_c#8*dn5{@N$9g%r|BHAW=WnC_TRw6U9r#EAa1qFAs0}v@kq=)G}qecoNw%RfPzQt zPieCUjfH**x}l2wSy6Xz9jsI7je(G-!Y(E>+{{BJfBR(MD?R9DXXMA8ujaHLt~|g4 z1$<=k?0G5cZ8U#m-_<#shG{))AyQ>ejz{YAS4UcCn?_fIz}%$3(|-XX!#4J(5fl%& zgG{BRP~sfi!1i7{6&xohO097TS6zB6|4JqMx@4vDS}STvkcCb`(H-i<*s%~`jUNy{ z(4jTA!vVY-#F>)8o$5{K{Wd2xBiOx6mMDQUq2DS$b(?V!VH_~Ta6Ked2{l)3n+;4z zN*6({JAJ$vFCIp$Hsj%1Q79ZJH_D%Zap>#JUigC7oCv{D6f&uEDaSusnIabRb^->@ z{hYvoV2Afx(R~1!yXAPa_PplDo`h2CRVbMaLiW*BcX%9XAm#I1q29<}TbDwKqPlR_ zV6#xfkrB#LW-?=HW~1zHXX`<&;zgKg1@2M_$#+Ly>5*oxnz8Xm#}l;F7uqY@0A+rV zb~&}_ne5;Dy0d}FG+#rX0|G!PM(hRX5DT2YLsVNc=szbI1>o$I?&&*vKzwGKxPPT< zv6bqB5K~~aM;q`2AG_sKR?mNL^7q^eM$w9XYte(PdZODFWEF37(33H8PW88cV?LyV z)8uVxw0f4g*;t5?Cub%Y8XN-vqXaIE*V8&lPlDLknZx%Q=Jzahabxar^`eWMyugxB z@fe5wI5HhuWb>{`IDSw~5^H4vNI!G09Vm33fJ>tHNHW!~IyFQ;l!b84^5SCP8$tR^ zd~@rWKi|E1&#hLYV^7}mv1NIRQkqu)8m$>P+RQN+1^8@c-DBQX`l2*F&b8hd5cw$NdM4=o>X7_s;XijSHa&`n{w z7LmPu{9NMScP)1(dl>2^c%|Y*U_x`FzZN>dJ7Kz+bp!W2#!Ro@6T0w&o)+j}je1x? zkm4m|miko|hX5T>L?maTE?JuI zAqKxWm6!#f+2?B;CsXS3G=!KxUKnE2Cno?_wf!D=EO9?w@RZv;jqH=N;6Slo=}Qs| zRK%-S{x8Z!akCB>NjGtWRQglVQ#WGH+e8`nMs{th`t_A+;FcM7dhe%qYHue#SQS#E zy;#jIMrWFo+^am|a$M1QYz%7(^P$hDZLODZ% zbu~Pu4Lx6$>|9kN+oIR3jHEvmwk^+c%t|cNDB2AaG2=h&BpGQlR^|o)1TJjCi|WhQ zl(LoahBZzmnZB8y_;a8AO>%2LPPRRXz&I?9daZpLIoaNbZrGc4c(Z_yjHy-EV^=Mx zHy>oW(_U9Y~be6846RB8on=WRf~Y}!z~rFMymUE1Y;+I7;{U*qVZLU2zgWb?;;mddFVsu=|}p|<{q;L+$H+1 zvj(a6VyA&Xdloro{ttJ_;z%&9XW~>%imyb#9c9c{S;Fse*J^Yk53U~BK5<`)PO+im zxkMSaFv^u|tbPahJp0io0uDio0`2i@WO;rxbT6lp@8Yhx73N z^5*^gJlX7SGTF>-COeb)&BSS_y~D+#!~y^SxQYt0nlJm%e-i`wWmO-d5CZ_HJQQW6 zw0&2O^8%8r^}IHEU5|rKj(vU@Q7V>%LO@_;9&(BuWXoY|EliI@B=id&;-w@dYH1N~ z>g0I#tt4d4T_w#TW4St+tfACgkE11N;&j!zNh2{}Ix3pHzJi@Vc2@q&2wn@AGBtY= zZhcphb&};7;DQiY_YC%O%n$k}g2+Fu>HcBpSPOsX39_@NK-EQ{HaXE6@-`Z))u zl#pnRkJ!QI`1t^QRoz?!kaGVkD@2o}81>5q z7K3PNoWONaa}<3%GBT$ESNz37jagqh0nvd6_6auIMR6xBY-`Q|7HQru#+9qh6Y0)k z;$bCKUuQbVCZWQ3$n$o)9^iNTE`X`c`te2toH- zo2#flv;Wj>K^0 z@;SrgQi~=mLqKr%9Pjt%AjZH)GLFQeF#&(UdJ>zU?0e*zfFK_yR|RnmAkMZSx8D^! zY0?UPBi%C#tIO^L1ObvHES>M1XBQmaP-yilCt^}qxhbR8hUD4wnAzJq2KwiWq~8e3 zj=j#e22ALxfEEM4n4|TnbBo}3XU`JmDW~GPzMT% z>P$mT#3Xt4N71PLv_^;Pe*p#MDQJQesg2sGQU zaCb$a&tbx4)k*0AL)Dj8L{ZEOSy8D(-yOum0vz&l(kDvEi7aXLdTeb_@B5xDUO;-a zp#ux$HoS4sUFFk1uc~38I+iX?scOT*I`XG6b&Lz=m1NW5<6)PVI*ilH?}L-`9(P5m z%y=vDlXKC7&l7xN9j(z z2;7!m`zNKg3Hq64;*^h}VEBm|I~`YWlO!LD7%gTXiZB@D7Kg+eeYnXY6=Vx5K8;ks zkVR%{OT{SPJ44=h7rby^J}qhUIN0M$uKd~Ic2z%E?Dz05?R|Z66j2o}((Co`_nKd= zi+RnOayAHkdRT~U3AVxR`v;Ohg>1$M{k!I`pb2Dp4gdBardYlk9iHhJoLWLG^RVf- z>G-0=-2fyI05t`Y`5m1Zdk-35Psd5~FDk}uyYcO{LGBZ20<$<3elBqnt_Q(k-XF%l z6#+DWa*D$F0xnF({y5>eYBNQL6pRtwCtO*Iea{gfE;Dlqt3%xfwqXvr6J#(s6A2*B zob6fF`V)$in94de%tdXnOKD^HAm(9TT-e|B?WG1pLx{_qDa=!utJ=YoKW+0bK)BON z|FWlszfK7X&lC@`lek{3`Io zz4NdP6>ic|rchM|M*)A~6T{G+Sc*q*kAv8aLv^X7JrVqe5#xBY%82cr#(<6Gex9H zp_RoBtxBYNug7iUkk2{YYuC;uhmwt8PG5$%`L;63_N{wHPY^h@8K?d}Y^<{a4@S!$ zp#n4P)R^rAMt!3KipUFhIX;!I1~;}npIY4pVv$mEly|^O>8YPnPoGwCi1M0yf4b0I z@=f3sAEJEP`t3W$MqBsPij=woi>b8&1~w+KEso^8iJZ{}SW5O;oXT`|_{EwI0rQgq7zlNIuePPzQ~4Da<Ec3 z9Xat<(m?K<__y+<)BX9YX2?{vsI~)> z1rLAd{WDUd{fOSoBc<#PmtBrDK(k=kRMaozZe=qA(PzQWhO3m@Q@wNxx(P)2d)owE zp%ze()ayS{5qqlK{`*`75W5a+<1}_*L_Cqt@TfE7$i1lPlxZ`v{Ve{J1OKV+OP+#X zFDQor*gnE9pZb9E2K=JL3PZzIflhXKk~wip9@p9UKa^>P)K;veF9{TDJBf87GDLdOap>U$6fe2??Zn>(?op{jd1 zJ}J8a#}%&4EpI0MmeY+&+sF>C&Ca-XATCXU5M^w~_8a?>jD|~^%{Si+tx9VsE4K#f zz`a;8PD0BYuXg{8RFslv<)ywiTb_`M%3TlxD=_L)`@GnsELB&<>-|6U;wS^##k7cw z&(oNIWR$}nbj|hc>7Wj~KjI&J*r$}zgt7^s2hGM3IM=K8HK>4nV%A;zJ3n4>tEv{g zrVm;dS7s(P%}%&Y&?;`glLvtPSUsWIIhNJB2!9TrAlwq_lU z_TPqC98gl}}G@k9Kth zwsH6@UEpa5J!INOSG_`k9X1K?IQ((@q_;eIDgcf?*Ql1(xG=TQ6}t^N9(u&srC#J< zCNku8rf1~8E@1iBP2c$#g429L$^j&N6DIRa^(ij8cPFfvX^sYFeN~_LzMZkfk#BiV*b;->4T7fb7IDz-1VynAE zt@j(WaKT#4*&qA>d%_}5KUM6|gwmurnat_d4NLWnzw7I;uCTu+o=$qagb>0`_(w^y z_*WmxJZ&S?*$fI*N!PnAwka~(1gSz6!22*Dfkp9(up(PuX4e@{(dnb>(`3MMRvDS9 zE~uq7Q@VwH?QUa|!>TmRbF~ocYk-w2BWMd!#Yp$ZBJ#a>b$@3x0v4FxZBEoNsy9W?#({fzG&T_1vU{o79nlm?yGmJVMRp#t8XsPGXg+ubw6j$63fgF}&If>S~ z!a%YmecnT2&-p*6lkvk73)?rEQh#OE*laMX(Yw55V~Q)yg^2$3*0ep%yYydLj*^C* zEOba!TUXJb>>jONIcN5)gA1PJ-LDwV1AH8Bc@t*M->KiSQ8|~jB%if10Y}Ms34i@e zP}!f5sP(+;yp1Se%YXc|)*+XTK3t)~VpJiRp{p`UH$bo83(fv)8L&Nhx{d!J;?qk{ zq8|QO_-XFj;}+RVSo2Hz%?MP+UcPF)E#5>Jg>=8excrWhI%%6@SKM-)t*E)Incc%R z{3FTM=oST?lV|Z3ET10iiE6E3Q7C@tEaAl?ek1?KGd@LoQ;djeZ**+|3N`2&k-hv_ zKoiRs3`@fy3aR?TC^E;$!xAr@`esCIZzBUHDtU;%MnbAix(*@1O`LbOPmko+XkX*o9o`Cg#lZ6IabEixCZk#ov$4mAE(5!0VFinwJFPQ% z!7JLaA6I{Nn!tgn?l)z_`nimalixg!z)A1oPKrejmhsf6IcmC<7dJ?TT~h(q{c6{| zm;-N5qK<V0a+Koma}@uDUYoZvLx0-PUm^?J>sER; z@DP>n@7CFj6Q%AQYvln46pg~fB)2V9`{8k8&u<-z);pUf>tt?TzjozgWGh^kdwjPq z57=vu5$ePO2-7q|VY}m0AC4te*NoEdO~Eekre(Y7yjPlGCpEqojZYb_=^pQNci$J3 z~?nsN>5)4ewrUMu;E1~12 zGZj|LrT-p*m#+vNxj(-!qDw03b(biS+CvuYBe@guda10K6ShuM8=E zh>1rBndeR~N%&SF{Ax(FJP9l`3+!K!LouiLoWZNg^TymSd5dLXM6qaK#TYa48WQ$w zRY0#RJM52JjizhQMU0XHUSAdI#2Tc<2k$0bnsH50-%pE8RUa9h#_yX8x0YiTrEDcu z!tW!EVcg`MuBC4d3IFl6NUTc}E8M=YLo^_XU88ijqqdhsD*`;NWlNfOP=wd$;i}_q zSkW%8#14a*W6d;t-W=jV@r7e1fH?`wpM9xv94R3FAg2pFfJ`&05BN6uZde}?ef|PI z{mQ31Q)Ku7xppIaSp{IxYoT&i_#A=Cp~ZHncej4cm~#Oj4v;}>@my+GZQZd#hky+8d?tr^@N|VC1l(8Bi)^Z%AQnz+h2iG{GTYF*@v@f$jYPgwOX7CGceWF4 z-uNpW0f;ae;r5Up%SF%@H-E1CUoIINDK+kwIdn|hG{q6)$j0oS|IH}(p=mNC${l2E zv+mRQaz@|nH!EuA4$AVUJuTE2zgUK(ec)AhCnqO%^a|p#1-kNKY;2Muo_{X0_pEih zr=WCZ`U^q3Qh93$u<|45r{2ia|3YC zOgBN-S{W;3^U-xC?vM5-M}E#X1mzI4HyRpld1*g*MUz}^cc=KDtT8B3%`IgpNGc1l zS}|#!v0vSyz}I2YxID^siJ4r>-|~fODskI`n0hLb-Q$5B458J(uzq)VX zx<8<$$Vg}>_yfXOJZ)Gx|4!FZrWdga!s>n3p`bqSRc66Z`?vmbshK41!2oi4ZXEzC zLV)aoNIFJ=s#}6dLuj5r3JpBIllk#uz@CVZSLgBLdE4GDsSk4g3@Fn=yhuRGufbjN z$|e6Ue8AKr%R%Y-PX}C9S$Mqd4b4rt#Ze!n_#&r$m?)l21SMbEchsZ?gN=v$QVEN- zn|wq1Pym`f`f}p73Pz*F(OWNXAQi1~E7tVH_ZXLeVjy{H0QvAw$$6j6f@M70UJ)ha zzL&zKV7`gc;n6~W=im|c!eIqdU)UC&@W=VZ9BDi&IKeHoUL@C@Bl)+C1TYGYu`9v` zNdXnh@{L*GkOTtjmT-`flD|J~&LN9jBbUYHChg=1n5b1_GUgLa=Q+0@7tIf*-#~Qz*^ccP{d9zQKeRH>qGaw4bB?Z1CTgPC znujV9Uc6k^5G0C46#RQ9aNk*1EX)ZuDpdIbAhQY|Mqu$z(sxt6yCt+3BLp^-^QCT_ zFAVh&%&2EZ&zWSfnUw!f;=8|oKA}hPFSfFt{RI1kboix_e)K2OA&=}y#GO7Y3L|X4 zd}>o4V-={bzcdtpsIYHztzaMF52lgSzCV~pCm+d_p18lXI8py_(|WvQoBK#$8!3CO z;lmkURH3KBC`K}l>2`YC?yp0(@w%UA5(jOCAZ|wK(|{GGTFsMMyCxHRf7R|e8DRee z$8blQrQzE^^L8gAS~p9itUNC=9nw|4T2TE5HM_G>u_cEQ`aqHcweyqU)C6T5zlUR(}q z>fU;$vnn^c#Wri{oJTtszL62ZC5Kh4z+|OrKR^L^yC5wIj)6Af?gp<#5Adxp^47Pjk*iKtnaqXqxchbcw+72s5|RS zMrXH2<r)jlOz5A}E~E)tT~40KKql);I`6q$+^(Y=KRTf*xab^&m$y zSQppG&amS#Tw{ehXUJopVjrD}W@qy3#ftV%-q4w#x@*CwUeEi+yA;y^g>yCX|Me`E zYM>4oe1df*aM@g=Aj^++!zBC8Lfi zs|-xLB$aW$-06+Z?HeR;bqg4@pTu-^p@s0->q&)k2*TJjJ(rN7$PiyBj|SX;ks_XN zuICOPn@1}*x!A<}qns2Cba|HkwMD8L63;twJ2x!>_+qS)NR)OJflF9zzbvU*^zJFus}&c0uuq4~%Iz?kRBdD{Z|_ z$Ab?Qxs>rY$ty&Pn>70f^SFGSy;(OuKX%QCcMEMJ;%07W@9*&1o{@c#j%pn!k? literal 0 HcmV?d00001 diff --git a/resources/bitcoin.com/ios/icon/icon-1024.png b/resources/bitcoin.com/ios/icon/icon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..2950ff8de1af9698fda7f69d27e6ea5e19d10199 GIT binary patch literal 23536 zcmeIa2UJtp7B?P5VMawnK$JQI3QAK9y~Pdf&{v@xAxW`v2E@H>{h>DSPkV@0@e*xo4l9-HzS& zfTLG+uIK<*SXcnRFh79ZZonnLepc2GfBRYYAK1VD0NX)!Hs-IoR0u?LWZ6`VBMJ@F)v_WgqLl z1Dxy!5A0)OWtL(7yZ-Y@bwDG$Cx`msESK&Iu=%)uyax0FDUKjxWn3l&U2LHKrj{VZS)~Vetz+qOV z7A{sU00>Ze+e@4z0aEMbBIWiLCY#$O}J045Va3Py-9`z|Djd)C7 zn%EI;XUNSpZ(%}@QiK60()aS29SLC2>;)(@` z`OTJJkK&d-v@pl40@ByR{v6}LI)+J|OjDw)Q}c%0z_Rof#Xhg35QFEuQv@|!Ulk(| zg7T7vH+iV`D_inmK+7|3K*t4^dQlW4WK^<6FwV^!xR-r6Soo*iGPuvBFShlQt%Hh*BW;;4L zRFQ65y8ydyxwD*?Z@9_6);>N}ebbw+N8@jgXt%UoaR`my1w0FFtjSr_OXrL4n^Q(3 zS8+->75tRIT>pTlZ|*}kMl&TAz#s1T-x(E(7p6}Q<#xC#0H5|z^!Zl`QWdN^f;37L zVtlh`Jl#cLh@P&z&WKW;`m_3i8BDFEcTWx$~ zMO)BJeyj8LiwY@V-%1jPo@N6|Q& z*Lq}31l`3r5V1s2nerJwR ziJVG`ZEqSezlMOF0=L3+Ac^L6&yU|q@Sb#|aRIwA{tNZ#-TuXXeKialdP24#yjPJ? z6YUfk8SB^sRnVgw(T%tASXTW%U}LY#X!T)u5@g8rXX+R0NGABqqA2``sF*jD^eA{M z1X`X2=DR>1&`gR+zDdDoH>_`xYI~lp=sxrhy*B-*Q!O$4mGXf;Z1*>sp%rK7U2hINMBUy~ zc2*;IMAo)dcOc@3W#gAii`*1{Di@8o`OI-ZS)X?lH?D55&?htY#Ip>N2Xq$}a(MgKr0ozDaU&yE zQsr}(R%}ME^)q(H-ST3Ebjv3s*Vt9FZtPX~k59gZUqC;^yjh4lHxc}RBH=aH!49C% zHf`9O*R=U5rhvTr&-YXvkSJ7t)(~)c>-<1H`R|trKo8|XRi9ArY(WY3B7){GfVK;- z03J6(r%WBTp*d8J<14y51qq>av#ne8ujh!ClA*;HO7ZB?D-TE%zn{=4VcgL?QepL8$h5C>jcSR&zPz9;@<8Hu-@(jFsea(7@ z!wf$mHO>wr{JJNfJ$me}NPS%iEcAWuqvnI4#7BOlUBGM3&WPl5p+%cd3hR`8gm(c? ziSGp`b8mk%>EAxHh<0i`+PD-+*b#Lu3M){BLDiS!JvD(OFTiL!Us7@Cg?VzQD!#AD~UaZ`Z1f$Puj72ON6}?z{M;kIKs?G{14oD{x z<{oW8+BFy=pR%#?Hv*eE$1^>g<}>j%_!yIA?`2)j0jVWCDX^0x2<-hHo2)t|Iqbe9(kOqmPtyjD4g`+)FY2(D%1 z(S@4L1BTW=SR(S9mRMtCZ0R&+vHowZpJ^caTGrE9`}{(+uY8uYg5^gCIr|mF!N5jH z;bY}j1I9|9easBUdK$of#=)H`OmJ|YD5l|6MY_{A(>2qELWVtCA8DGteN+ARp1pG6 zWfJVRyzjP3V~{VlFr*|RSj_?X)6kea&BBo&PgPshtC|Rp`%Us;^`FR}`b1d>lg|Vu zp9@|V#mM*PX}f^@Yr#_u1LqgZ-Lc_naUao)8eDqRJYv^zWxfO7vkuKS)i7UZkoL>g z3P=r=YHzxwY*xg8%rPtTWt93V5i#W1TZGQT4~BzXi{CLJ8x^>_9@n)9wm5C%mjh)a zUlElUJn2X!O4Q4SWNC2kbj(Dw$@!s?B(TL3q_S$Ad*~x1uX7NeXz;Dp^4F;3_UuLe z@i~~YfbXQP_tiW^N1&OiiLMy1+oxe1erDeGP4n{0K=(RRc+-DqNi>Y~`^AA?_Z#QMgi zFD`E>Oe0ni0Y7@&$x%Q z?eh-oKs&d33XLxF9l!D3bYible8oxDGIF?#wc2_p^LyPwu?K6MyrHpA@4oW~rQF&+ z7+T2d4!qMd0jB@LLE-sRQrVkEirrh}EpU>!j8>N-kmc$-L z_zle(cVZy-YG)fC2WJ+5Qq(30YM$zofl3`KYT`uwF7K9VBags!33M>+Y%J&b+#|JKEmM99?*|i#$EG!14^op<(sVT z)&E%SkFMFc<+yR92{wr~`RW=bL3RXpLv>m=qiXRQGIOvM%u}Y&#q5FFo;ZmR3E$J3 zy!t?H?ldSH&DCD)blVOmq5LPt#B%n|YFOo@j`gq3Y3PYbdr_yPRyee*&DGl6$FC<1 z<)$lCFOoihfBk-R84`G^49OS$|!RYN_uv0Tv1Hoz`nO!yeQO%&;ePqbD& zyf5eK#Xqw9H}d#otGujP26N`${gU3zRU5<}T~59J`}dL#(p*7?&Uu{CsXOS&{iv%$YjrA_)Hu%B~9zfEwQA8zNlEA2KO-h04{ z>`jeT>nAxq{vVO5>+()cPCiTlqc}9>bwWPp25Y_voGHI#{c>Rx#*h#Cn_sLHolZZi zh~?)Ga=f?wIrj@OBMmuSj%&Yu9c|m!nx65aO5acxaxPhur`<*`fi@-_6n9Q-YI z0UM5MPsXa|H9t@%`fVI*n1`#E8a6)3JWfbKmn2|Z7$#l1V{Chu5NIu9Q08XJV{alr zd1#du+6xY>HVItXA&6_xdL}2HoUiDulcmwkN$3d%O|?>G&{Y&}tEb6%G2qLn6xKX( zPoo5ip6;AXq^JW*bXwv`JwC0ejV@Us=)}ZvT^dRwR{{OI}9fCRTzmA&#cr0r6+ zi`F{c1WT5#_obd~*g!j0`IL6<0^C>WSj~yW%#1mMoa;SD8WvqoRap{bAv+npQz-8C zXBp)W24r`Vx|)@|%YSqs59{olSv+fjb$Q~M5q0_w#sTX=44@g${ z47|qsu2Y4uuA(~)ZGxX{m(w1PbtV%QW)^ha?DVDvoko=fOk|!Sdd_& zC9gSwIrr9cH+X59YXswxEB%g>^WtY4{`5D)HSWy9PGYB29`dZl{Sjg7*3Hwt1|tK{ zkFX%|D;1pBxo8F7JQh2#8+#%ThTb`12x~E%)NYMEs-kVJt((b*I1)VOs%!8p<@Wo8 z<%_I3&A*|XWm?~;0jk%ipf$zK{j{otW58w_)4l9t{%TBN+i9>UGP(afv44z_b;M~f z4!GE_#9!5c%0iF!nI^-4R#SYEsm#{|&1NMh4_BBS)PnH%h-x-a?I9i14ZFFCkN(?q zyJWPeM+eHYTj+W2Mf1pCKiMF4gli!)KRP$58$W3o7f;@7DGthA`&ib=C9l#-={+lWTd7*mMMh!Iird&By zy=$-f_>RAEm`@$aU7+h)cDBakb~B(O%agxMRfAy3SEic~D#9N2%Mj4_)@w-Sk|(yd z{0&7EPJlb5`t*#WqQ|&XZivbnq$I3LjtygjJQk^{#f|4f&W0{fM~J>^Hp?s*oIlzj zq7wN?;DxeC3L?v0SnMi%nywYnFi-83YQEe8n|pL;U9kDvD4cRm7utcUw@HY*K?-AN{uyrD zp5|~ZtWYtXE8qXF`cexob-O@olV5#>de6=zCUhM8&}rKKy~~^*(FZ|oMil4)MtL#6 zZS)_ce)TdHe(iI|JTt)h5V~X(a36Votk1W~d(!mfk#9YW0P?QC(}Dv2#!^{8L0K_N z(cUimAhY@Ht^)Q}dRGKyietVJ`bn!muZ8u9@rt1wbcC4XVvShatmgM`(% zFy1gr4P$O^&EpEH*h;xBCy+7q5#t5X1w8Gw^N}kb1dg z!y76JZoiBD_cH#v`#b>9xA}fa(Q25N z4aQV935dNiXTFJ71LX#i+JfCnbXMePJP;llKSa>*@Or9sME-Ht7H3d`sA`&6E~F>N z!=hasap4Wyr8gg7%8ly!cI^Vpr`v#6S_y+_v3etyRp(!pEv&To?vz*?CcdBBnC~rB z@IMt!YGon|wHb)=r}L1S+=>6OIa(etoy@V1#qb*k)&wg)-; zjm;#P`Shms0-*>lo|=4Mm*Gjwg)FLEHGFax5Ws+H`KDW)S(LKGzz5s*_3=yi7Qfic z?A>TM<M=Eeb>+i0J>w#i*>gCT?u1=@cSKbiZHw$Lm z5djVGI5TeUtJ8V4CxM6QrT8@6eDg) z1$QQ##fcMe-jR_*$x2kP!v&{iV4!la#Be5tv~1X@<*_)kK$<**>*dntsnV9KJ{mV2 z-b}kclB<-ay0G)e64UOCmB=yr*)KghTd7z8o-99NB2>`P;x#(GsLK;rG=}ki)aQnp zr)-?X)*B4_Hg69G-`3~9wP*T%Yj@z8`UQb^Ek@cs;5DN){6ZPSXbDlJGfebAm{1*r z+?=yr-w!$tj*&v8I)$}No4REhY|l5kL_6p!?gFHl@^i+YFWghQT~XDvZC=`cE@Piy z&+R?oUvc{D?hW%<8sVK+Gnh-%btuXHh$18RZSso$COG_!>&>2Z^$<^wNnIhLLI^TE zgW;zs{wCIcbhO278Vp~CK$W-LROQ`idb)+RQsrTJWlq`)(#*cVU%4jXNtCjBPn!#1*N4g!bs-p;7PUDvr@{K$z1mYrL z#b5nQVJ*V3&TE5+3)QerNJUU*P`v>H>P6owmuNviI3)*4e;*2J3Dt)fHA0N0fa(i# zg1tbzukq!t;Q3S0zliQZb^Yn`(+xn=Xs<1q`r@5X$TQ}^ju*s-MWxC*C=H5*)|#%+ zHQy{0C5LDy+arX}IeU9Gjc6b`pXF59&b@Tp1zgDP(_f$b@w{n>)P*XD?E!nrsslQy zcOZE*?qFSK>D~N<#uJb0Y`LTQ_0`6UG&|>Z0W0x0E;NXn!lmE48rsLw<*1q}{hG$Z zF4USLFO7%+ug;i`hj;_#S%c`mtj|JKyW)^8azVEf;$7Vd?l*Kg| zFgm(k@Hmkj!5x(fPa4EyGDjnWWP6JuilE)`X=q!$hIuUwh(46gd}i2NXcRUNoq1&- zzzN%7E&}t+&$sAmSTS9X0}wqIe&6n7j9sWA)uMWk&_;^TN&_`>lgbaKpoNf4MBqwHCuP| zd1&wSNkn9X3Boq)5H@RKM`SZWHaz!yG+gmq{9q0=fRU{LRkin}do-8k7bOqL81g`@ zh7ms&Pg`0~U1pY!t)}n1h#S}ikZs2u*LFB-HNeZzz72nCp^2?@vW2f~&hpbx&TZ<4 z`?;Aev*CJjm*he0!x;PqVOBjSc4^13hor^_+h_$v> zW_ZN=t@wAl0I22Iqxn8;o!X;+Fy^y;^&*!p&4pZ<3?+~j>(*w$BSLjwjQMQcNZgYr zYoJrhAUe!{BnXLLh#mQ2%NOgssBhnHi986vP7+HN%G7A$Q*d9*e=*b2iSP#s*pFW+7Z7@AQlC_0V+TsVMw<1#-wZ@ek7oC(es*1M6Fkk^h7>9Db*0z zu4+;wmy|+z)RVlr>KMqy#jnew!8Q^3sT&^_yNnWUc%9>dFc(1O->z(rthR_Tc0@T7 z{IQZ#OqZ`EB26sBZj(#FAhO>-rrPAy0I>>AjPcE`DS)ciA)OS%XWrV1XSUsw;jc~! z&R{f$Tf5=AV#wP&xPGX|0j*s?gs#U5VQ%C8#3uQjahHLzIMX!N77jqqF>-7Vuc$mGi;$oEqZiI zLwz~^hK9xORd)IZ=5nF;u$k4?v1iGk`{0e9` zGrTl9$Hh4nbh-VktPkeKg1S=O;8bMtR*d&N-Dpj$D-*b#R_%6Rg2=smeyRCgKKHV1 zza}4u4^?G5OOLGSB`_ve8zJ*gH6@iaJ2fSglM8f*3u>wA%V7H@L>=OG9b-Vu!zHbx38yOFXo zPS%>m6~o~{ku^@wO|4YLvlO_0rgRsqgY5lEx+K-RZT#VO*^A3387DOykzzr- z9&~HuZWg)HhxLA1=y^PshmlR^1{@Z@_`CG|QO4Ik;QvS#0HEpFohqn4X6H3MMj&!| z49_-rTBgYp>$?{yxxzm>7|=Mm@8;htQp{=Lg{VXLLuX#LoO?!j)J1f2_Xb}`ZuY$- z^1ufsjv-f2H(%(CZ)c1y`&qD$tKgFHnFLWA_AdRfg6QaZ6(xu*R~ZqaY6y#2a+sNy zv{*Pp)dTRz2X@l0#ypLq<<#Qqt7nJXve}T+7L}GH`i5kYS8}Tp zK|U$rU?IUinYnL|`&$b=DE|$Xk&Qpb_nWz2xpKj&ya|@a(1wgfd%2$>&*B0hMwgmO za(lD4Uk2eATx6h-XWw!GMu&Q`rhTejsI9*{EJqq?9_lWFIo|(_nYq zK2-k#;{Dtj&w`Bws!!3%`%h{sx|h;PYK_?zs8}E>r#(R}yS#|~V{PG&rnor|yLvu$ z3t^h?w*6A|W9_-Z?K4xci!LxR+j~PU%OB1ARQqnxgsufL#f&T<=pbPI5!aEAwbfh&d}rBK`g@!ZvCM|ib8c_FIsi^9UMezU{< zLE_imDgV7U()|W0FH?@sccg$u;OER~OD;*v*DM0liWyC4aa*H?U~yq*oYc1Av(8F4 zE1&SeqNF|o$Q_eoebxjvo?}HhP3SMo=XL%~MxTE4%uN2)T@$Ph|8JZ5U5Z zKKVgUYZhf~p((j@WZ0s@iVPoiCqyMZq_X7q*kwk0`VPJviL2Mh1&O0~F4>oME9e88 zDMC{_%41gNYMM69;#aaDG!6QyAk%an(UG_@^m!TI+tao#XXLRzteR?Sk)aAl*n zwr2rBzT#bHc2O?c=Y$0{vvP1OTEg-Sd5{7A>3pjixH&L>`$4y9a?$N3cNcCU{VZ)O z0=|39LO`H1A{>1y`Oc2pcupdk6cRi!)KV5PY6=RAqC}vYlxJp4zmxGc+M|JG*gc5U znYLWa=*3ADSuz`;X(Fnhvv3Su*-)kD_~5jzoA?zg-Q@NNlmjQMJ>O`NrrFUmOcKg{ zQ|4diysirOR8%0l=r4BT>qUZo#Ec>mY8OJqhDOn1Tg9tRj5zPD7t*ddCx;S5owp6i z8as3Q4GVv}0e(OAD__nI{5#I&&)TJBBD%?DA;u60_e+;uM4oFeXb~nV0W1HeY zBtD)hJux+xhc$K&dP$LOE5aehn7;e#hNCN@0+Mw@zR8cw=Bfv^On!4vz%_?= zup^09YCOCWLb1>>Sx!Q_+#SB(~%bL8Z2oaJ%oRfe`a9c zU$>t6!KNcM^`jsKhooyX&;T7)+q2#EvTWw;f^0`C++G9&!~GDuUN7-_5a&an7}A>= zx;)$SQlU3abfP_&0YMC2-EyzM)0~|LQh5yn6u@n2mVZ0mCH@gB$_>MrJ-6HpEJZE9 zsBVHh-Y%k?G;{faDAAQ3nx@WkxEP_0-9~CppiUUsSa*yS-AKz+`GV?2c(OLw3@GDgx%$) zzi#4ES*>L}`O5T{eJ)MzvnywPB=k?S@>f2R0f5|>7b+)g&zH-sLtC`K(c%vC zKa$N;SnuA`Y%(F(Ud?HTb}mkc_?&+{Ey1ajAK)^WhQ^PiX=WesM>vI{{q4nya-~_` zp7~}E9_v9{WR%6NRI8%AE|5-2{1~k#Pu#l&I`=4$*z~+DeewLfrYRx#?Vah?v9MZq zU1^$#h6buh-=JFnEDvtsU}<(a{HJ-^EBKd9E%&soL`zbD`cLAMCEY70u_tYGgAEFz$mF@x(yYz1A3Mp=&#cK6jRwrNL zvUz0fqNjSOe37G+fa3IOpsJJ*w!iB}R_79dHb)J5N)ai-ZTl?fwuRbf9Mo6Z6ZfZ2 zf9+5_LTVb}budjO@`Gz3%QN&YoYMNLzv&5AjRwm}X)}}pRE}#lh}4T~%A8SW`5-SDvP)El=au(gkYxEUJddzBaFao-o(8nu+2c ziw$1HK|@5BO=9R4#8}hYothu{IHL1*D8!@IvjgQ#NCEORGoOUPiwBjwrFV|WF$Z|U zz}&;;wknHB!gg2adeWlQNfg{h%XNxxU{TdoYsMU>I@ya&xHBf2|DsYKu|->8hLard zyMOynll`IK*DjEq{kJ!rG3P(iT68&{s)lABEm@9V-h|w&w;gecrIbHImIzuHt!p68 zG`*f~=%*OC#p1VAmFJe|B9B1N24g)qqwKUiJ!ReQ4g=xp>5*}bMj@Anqxy;crGnih zj#fT#G4X>dV_r(j3xQ?D$+ru*K00;TfVewOSu2fmqhl(MRIDr`tLgYPZ#^1!v+;PG zX58JW7bZo}t}$)-s)otP)@6Ml)KJf(j?^7Q&B>p7*PZIt5m^9^1xd{&$B8Q81yrX9 z3?G=6Jz+8#jq@KhA-jmg5QSpMQ8^*xf0hIDzfAEUul&fBmU z;1pF5kqV@$qscpQ&zLpS&J^iE^yQw(S2=MB;otJfYiT*&k^j6@Wy7zl`1or7ouHu+ zchhxUn;*g1ffTF0Sa>cYUs4<(s73dJoG1vGW*&$H34Ua|6yh9Pc_nCHP2*Q zS>Zd?;F&Mv5?1adu~-}x9kf`rX_oK1ZEnu=bvJT@E+;k-ADW{S3{Y--k7+r_>=Ne? zNpMwk?yB3snXyO=DTfj>tCyF3uP%=qJ(1tem%d$3qdN_vT1KxeY)6bHbw-iFVfcjX zT|l@}z>>m=dqdwkcqMm(l|okO<~KK`lE;Ah#;%Qz)pr3D#3T(Lh6#!h`M&co+clnE zv;%i^+^S1lEw(e(!%UZ}T6oCnZ10$vzfQH+76Cp?y+amhgdNZ5Lj~qHMGr3O=}0Z? zp#8-PtuJzm|N4pP6Hfdy3UAx5`n5D>bsh9l-@lb=*p7rd6@XoeV9^=@=X3pt%7Pkx zF|-dp(OIWFOEF`?pd%8K=8Qv4oQ zyo|~ru1f_h>f8s8wW-%16cy!Tt1diuhjJG$a1ZaHFhk=m<*HZDS5?sZo{V$yP06=! z7;h<84=}_~*Ux#9C!EzphD&S4o@Jil-UWO&qS>fh$-x;1-@dt=wjH-%HH2t==L@P0 zL^WT#{x{(t>Vna6;he(N8?{r~n;$D?Oou*L6~H7s{Z&2e;iiJfVPTD!<@mPP8@@_s z>vN6gl>-&3yAjzyC2_y4D?D0S=kZgX^LlHi6Yk4NPb+eA{ETy~?2Z;idTTlM+V)Vq zmL|wX4U73K==Ixs#EGpZ)Y*{>B}j4!1i}`Lr=c1l__uc(tPk{@7Wje{t{B|&p%omZ zS=26c(;MrP0>m?WlbMZLFZtL?G#ZX}*V96zTmCZ=^>y~ME6$oH0_LZ!voJ5Lb9b)w zxCfZUe&XaR>l+ntzTw>Kp^kD>4aaiZt;Cl-O9euo#5`pG#S3enJ`hID4ao~=3HfIx zIY08q{$HRmX5}OKh8@gj^^B5+%$1A&A5B*eYrZZ8~xOuq{{@3(TU z4QzJqb2Z&h5KglU3*tL7)^7YafP4IUQsfqSf?y|b;Fp>$8N^U2tWx!nUyOUq!TiOQgV;Z-G*se2+Z*%7jDf(n8LT&;>1nZed1Dk_TLi0ximGQMIC`BLWKJB14y6x?~Wb+*RuXzLT;ILB(lo9 zqKm}Xm?(0NY$Fg-MZX^;q$MRMw;bJ$Oin#&L5eY+B#jugXv+bi5OqVL0!X2NxiP}` z)=Np;;{)7-Df!)4c4zgnbBZ}GQ%yu7$4}_&8%|iBUQExLn&CVb^lV=0!=?)k2Y$vb z>;lj^*U%u^GQC{BE-!~QZFzjF$FO#DTj92p{Numd^{=m3TyJ^Su1`!Y|7fXMr5hFt zZvMc{#EPneZJA@Gw^)Ixy`bz2`R2YKU;S%gpG(-X<*1i9;@-NE_{7(*op8tV#^7Tz zqc&cE8Q?t4QHnrprCpo-iug-h()uFv2Wl2CG3*=bml-lO$w;7j_o0V24qS0${bDh1 z4?-qG3sHyb(#WV9TlF5w55xJSH5lvyI4sG_D&z#G=*C>`NNP_?qJ)q7JF2r7nm-zJ zQ$VUb#VOZT|2SEBasq+ynJ{2J+4{H_+bPy{3dq~vHGbvPK_>Qy3P?jEB;y*sBXaHw zUsSzmWaA0OBR(IH-2))bLshbeFZ zQaAN(RE^DZZ$CZlH)$$ky1RhjmUGz@*g0rHkzT{%sh}oJj~B{<58lyZ%X!*yFS_xu zgc3~WAZ)$p8ifD3~S8g+M8wdllo&2n3&GEdR?!MWlU? z{)vN%v7?mjxYp1r-gHE@nS*lGVG1hDPcAWB zO?yv}>kOBYqYQW|G(r|ohNWDY(v<_*HW^)JcrC#Blb<`mpZz{05at@JU{ZabwSYq9fivEJW-8L_Dws6FV1{SdQ>-iz5(TAKZSc4CPS^mGIH z%j(j`A#AI^i}Z&O&{=gb_lL;H_>Ym1JE_AO+nPU*Eny87gmsks0Zl)RTlx*9+>DAMj}Bf+~)}{hyuw7@=E^_vZ`+ z|4QDLpnYN_5&Oyh*?#V-4Q4FwsoyIZUvIico&Q%dhqm$v%IZtoE|xXN5I%(7VtNe5 zx^=q5#pNrH+v*J8*4RT_ArWKJvqq#&&5Rns=Zy-gBn%K*CZ$G>;nroW6U5A7R* zd%k>pb1J6Fs(f=*JjeWpsLq`us0Ix*0TawabmKJdoR>r__S3o(}{r zx@Qz<7yG`@i5vw4iCp}%x1E13_y20iYTtSth;}~sfXLnA)-P960wS-{(?u>D(RYdq zn%hhPwSFJ$VLOxajm2YfYs=Kt`&LKEDu`$4ZEqcRoT`>29d0OFmfp&Eg%EA}XUqS+ z|Nn<~FADZQ_741iwtcPbNY$xN>WRc?O=Gd})cR!4NCTDo zM2}mKmIutJx!3_Q^2Bi<+fRVU9CIcTaO1*70Dv#yN=OP>hiybK*fJV)Sk-LX60Y>C z8p{jgsaykCG~p~+tDz%#YaFA3r*h|1Npv}>){IAhZ+1+X##%cslWAlwbs86!oQCCf zVs3&|r8OZsJq`gGXZ5T4Fs#@FEiA?(zCli;e;1j$dJe)hn!IwFCdV_CrKPj}NVkyk zgN_F(t>!o>=TJnOXhyFkq|Ekpqp*>ZUDBqBI*!2$9wiHy#9FG#H)X`ubW7%1PFn)G z$OhAC?^SwjKWO+b9NqN@3oA%uOovpyb17YCji3e*Wc;vH;5!mMI8uy5rw7=2*$ft+ zWyV-CJ@h8Z2HZHuNb38Lat&kRAokQ|cWL~E_5QG`UBJ0g6VK9XjA^PWbA4|B?nC3& zKxv!G9?YZ_v=HnZeDz)-F%s*+V>cVv{Rcx6`?7S)Ysl?*a3!av*6RRZyOdgjX%$v6zy0CxN#0002bP)t-s`??nV z!$|(=#r)EY{KQQB(vJMYMg7@}|NsB~>Bs%qj{M12`@uH;<-q>z&HKJ8{KZfG+L8Y1 z%KXl2{L+5@?9cw^zx>Q*{o}s-ydMAi@&EVc|NHa((t`fy!T;QiyX`@0$b?9lwnYW>uP{ouF#+M)g5wEW3l|M=_w|Ni{QTm9j*|NZ&@ z{`~*__y7I){o0%S(2D-)$o}%;{`2Gg;l2IarToQ9{K!@R`04%Jru@fI{obiAHk*I| z005OqL_t&-(_>&D4KOk>voNuuim|bCaB^{Qa`PYy@^U}{A3s7+K#)U7SXfv@ltTp_)B>T<(u%{Hlf%Z=&K@~L92}jT9bHHaZx8?gPsj~B T69Jp!00000NkvXXu0mjfGD-cW literal 0 HcmV?d00001 From 14f77cf03fec4f01eda2306bccab5bdff64d8519 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 12:07:20 +1200 Subject: [PATCH 014/118] Using generic *PACKAGENAME* in template. --- app-template/config-template.xml | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/app-template/config-template.xml b/app-template/config-template.xml index 3f8abc26e..8686b7b36 100644 --- a/app-template/config-template.xml +++ b/app-template/config-template.xml @@ -85,30 +85,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + From ecb3b2391de29bc0eff2ab0e831bd65209ae200f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 18:17:33 +1200 Subject: [PATCH 015/118] Grunt task for signing. --- Gruntfile.js | 5 ++++- app-template/package-template.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index eb4bb2eb0..58829e2ed 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -69,7 +69,10 @@ module.exports = function(grunt) { command: 'cd cordova/project && cordova build android --release', }, androidsign: { - command: 'rm -f cordova/project/platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar cordova/project/platforms/android/build/outputs/apk/android-release-signed.apk cordova/project/platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && ../android-sdk-macosx/build-tools/27.0.1/zipalign -v 4 cordova/project/platforms/android/build/outputs/apk/android-release-signed.apk cordova/project/platforms/android/build/outputs/apk/android-release-signed-aligned.apk ', + // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". + // It looks like it simply lists all apk files starting with "android-release" so I have added that prefix to the final apk + // so that its filename shows up in the log output. + command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned-bitcoin-com-wallet-<%= pkg.fullVersion %>-android.apk', stdin: true, }, desktopsign: { diff --git a/app-template/package-template.json b/app-template/package-template.json index c42ef81b4..f7c067c85 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -119,7 +119,7 @@ "run:android": "cordova run android --device", "run:android-release": "cordova run android --device --release", "log:android": "adb logcat | grep chromium", - "sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk", + "sign:android": "grunt android-release", "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare", "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare", "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", From ad6a1fbe8dbfcb4f07d68cff772bb06840043849 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 19:28:24 +1200 Subject: [PATCH 016/118] Adding latest transactions. --- src/js/controllers/walletDetails.js | 26 ++++------ src/js/services/wallet-history.service.js | 58 ++++++++++++++++------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index d22ab388f..fc45d70f6 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -191,7 +191,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun if (err) return; $timeout(function() { walletService.startScan($scope.wallet, function() { - $scope.updateAll(); + $scope.updateAll(true, true); $scope.$apply(); }); }); @@ -287,6 +287,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } function fetchAndShowTxHistory(getLatest, flushCacheOnNew) { + console.log('pagination fetchAndShowTxHistory() getLatest:', getLatest, ', flushCacheOnNew:', flushCacheOnNew); $scope.vm.updatingTxHistory = true; walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory) { @@ -374,17 +375,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } fetchAndShowTxHistory(false, false); - /* - $scope.vm.updatingTxHistory = true; - $timeout(function() { - walletService.getMoreTxs($scope.wallet, function onMoreTxs() { - currentTxHistoryDisplayPage++; - //$scope.showHistory(); - $scope.$broadcast('scroll.infiniteScrollComplete'); - $scope.vm.updatingTxHistory = false; - }); - }, 100); - */ }; // on-refresh="onRefresh()" @@ -392,14 +382,14 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $timeout(function() { $scope.$broadcast('scroll.refreshComplete'); }, 300); - $scope.updateAll(true); + $scope.updateAll(true, false); }; - $scope.updateAll = function(forceStatusUpdate, getLatestTx, flushTxCacheOnNew)  { + $scope.updateAll = function(forceStatusUpdate, flushTxCacheOnNew)  { console.log('pagination updateAll()'); updateStatus(forceStatusUpdate); //updateTxHistory(cb); - fetchAndShowTxHistory(getLatestTx, flushTxCacheOnNew); + fetchAndShowTxHistory(true, flushTxCacheOnNew); }; $scope.hideToggle = function() { @@ -529,11 +519,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun listeners = [ $rootScope.$on('bwsEvent', function(e, walletId) { if (walletId == $scope.wallet.id && e.type != 'NewAddress') - $scope.updateAll(); + $scope.updateAll(false, false); }), $rootScope.$on('Local/TxAction', function(e, walletId) { if (walletId == $scope.wallet.id) - $scope.updateAll(); + $scope.updateAll(false, false); }), ]; }); @@ -542,7 +532,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.$on("$ionicView.afterEnter", function(event, data) { updateTxHistoryFromCachedData(); - $scope.updateAll(false, true, true); + $scope.updateAll(false, true); refreshAmountSection(); //refreshInterval = $interval($scope.onRefresh, 10 * 1000); //refreshInterval = $interval($scope.onRefresh, 120 * 1000); // For testing diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 00847cbbf..8d6345e1d 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -31,21 +31,21 @@ cachedTxIds[tx.txid] = true; }); - var someTransactionWereNew = false; + var someTransactionsWereNew = false; var overlappingTxsCount = 0; newTxs.forEach(function forNewTx(tx){ if (cachedTxIds[tx.txid]) { overlappingTxsCount++; } else { - someTransactionWereNew = true; + someTransactionsWereNew = true; cachedTxs.push(tx); } }); - console.log('pagination Overlapping transactions:', overlappingTxsCount); + console.log('pagination Early transactions overlapping:', overlappingTxsCount); if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good - if (someTransactionWereNew) { + if (someTransactionsWereNew) { console.log('pagination someTransactionsWereNew'); saveTxHistory(walletId, cachedTxs); } @@ -60,7 +60,42 @@ } - function addLatestTransactions(cachedTxs, newTxs) { + function addLatestTransactions(walletId, cachedTxs, newTxs) { + var cachedTxIds = {}; + cachedTxs.forEach(function forCachedTx(tx){ + cachedTxIds[tx.txid] = true; + }); + + var someTransactionsWereNew = false; + var overlappingTxsCount = 0; + var uniqueNewTxs = []; + + newTxs.forEach(function forNewTx(tx){ + if (cachedTxIds[tx.txid]) { + overlappingTxsCount++; + } else { + someTransactionWereNew = true; + uniqueNewTxs.push(tx); + } + }); + + console.log('pagination Latest transactions overlapping:', overlappingTxsCount); + if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good + if (someTransactionsWereNew) { + console.log('pagination someTransactionsWereNew'); + var allTxs = uniqueNewTxs.concat(cachedTxs); + saveTxHistory(walletId, allTxs); + return allTxs; + } else { + return cachedTxs; + } + } else { + // We might be missing some txs. + // Our history is wrong, so just include the latest ones + saveTxHistory(walletId, newTxs); + return newTxs; + } + } // Only clear the cache once we have received new transactions from the server. @@ -87,13 +122,6 @@ var processedTxs = processNewTxs(wallet, txsFromServer); - /* - if (getLatest) { - console.log('pagination Saving retrieved txs.'); - saveTxHistory(wallet, processedTxs); - } - */ - return cb(null, processedTxs); }); } @@ -154,8 +182,6 @@ $log.debug('Ignoring duplicate TX in history: ' + tx.txid) } }); - - // Update notes? return processedTxs; }; @@ -175,7 +201,7 @@ function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { if (flushCacheOnNew) { - console.log('pagination Getting latest txs.'); + console.log('pagination Getting latest txs, will then flush cache.'); fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ if (err) { return cb(err, txs); @@ -184,7 +210,7 @@ return cb(null, txs); }); } else { - console.log('pagination Getting early txs.'); + console.log('pagination Getting txs to add to cache.'); getCachedTxHistory(wallet.id, function onCachedHistory(err, cachedTxs){ if (err) { $log.error('Failed to get cached tx history.', err); From 745737ef73d5b38511062b0febe538fd081b67d9 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 19:39:32 +1200 Subject: [PATCH 017/118] Adding missing parameter. --- src/js/controllers/walletDetails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index d0afee8ca..e5f85d21b 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -339,7 +339,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // Check if we have more than we are displaying if (completeTxHistory.length > $scope.txHistory.length) { currentTxHistoryDisplayPage++; - showHistory(); + showHistory(false); $scope.$broadcast('scroll.infiniteScrollComplete'); return; } From e6beb6fed12822c0cd2998640ed30d25121f4a7a Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 19:51:10 +1200 Subject: [PATCH 018/118] Displaying more cached data while waiting for first tx history fetch. --- src/js/controllers/walletDetails.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index e5f85d21b..c52982445 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -267,7 +267,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } if (fetchedAllTransactions) { - console.log("All transactions seem to be fetched.."); + console.log("pagination Fetched all transactions."); $scope.vm.fetchedAllTxHistory = true; } @@ -284,7 +284,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function showHistory(showAll) { if (completeTxHistory) { $scope.txHistory = showAll ? completeTxHistory : completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); - $scope.vm.allowInfiniteScroll = !$scope.vm.fetchedAllTxHistory;//(completeTxHistory.length > $scope.txHistory.length || !$scope.vm.gettingInitialHistory) || (!$scope.vm.gettingInitialHistory && !$scope.vm.fetchedAllTxHistory); + $scope.vm.allowInfiniteScroll = !$scope.vm.fetchedAllTxHistory && !(completeTxHistory.length === $scope.txHistory.length && $scope.vm.gettingInitialHistory); console.log('pagination Showing txs: ', $scope.txHistory.length); } else { $scope.vm.allowInfiniteScroll = false; @@ -332,18 +332,19 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // on-infinite="showMore()" $scope.showMore = function() { console.log('pagination showMore()'); - if ($scope.vm.updatingTxHistory) { - return; - } - // Check if we have more than we are displaying if (completeTxHistory.length > $scope.txHistory.length) { + console.log('pagination We have more data than we are displaying.'); currentTxHistoryDisplayPage++; showHistory(false); $scope.$broadcast('scroll.infiniteScrollComplete'); return; } + if ($scope.vm.updatingTxHistory) { + return; + } + fetchAndShowTxHistory(false, false); }; From feca8b5807cc00f6a1b4da8aeeca03a4d1bc5947 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 22 Aug 2018 20:01:30 +1200 Subject: [PATCH 019/118] Checking every time we get earlier data, if all transactions have been fetched. --- src/js/services/wallet-history.service.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 1f9e03152..a607fc870 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -269,6 +269,7 @@ if (getLatest) { txs = addLatestTransactions(wallet.id, cachedTxs, fetchedTxs); } else { + allTransactionsFetched = false; txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs); return cb(null, txs, allTransactionsFetched/*, hasAllTransactionsFetched(wallet.id, cachedTxs, fetchedTxs)*/); } From 4cf268682bf0da47992a6f3a7a24cf2674271c65 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 22 Aug 2018 16:21:57 +0200 Subject: [PATCH 020/118] removed comments --- src/js/controllers/walletDetails.js | 11 ----- src/js/services/wallet-history.service.js | 51 ++--------------------- 2 files changed, 3 insertions(+), 59 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index c52982445..728238d50 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -239,23 +239,18 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return; } - console.log('pagination Got cached txs, count: ', txHistory.length); formatTxHistoryForDisplay(txHistory); completeTxHistory = txHistory; showHistory(false); - console.log('pagination Showing tx history items:', $scope.txHistory.length); $scope.$apply(); - console.log('pagination displayed cached history.'); }); } function fetchAndShowTxHistory(getLatest, flushCacheOnNew) { - console.log('pagination fetchAndShowTxHistory() getLatest:', getLatest, ', flushCacheOnNew:', flushCacheOnNew); $scope.vm.updatingTxHistory = true; walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory, fetchedAllTransactions) { - console.log('pagination returned'); $scope.vm.gettingInitialHistory = false; $scope.vm.updatingTxHistory = false; $scope.$broadcast('scroll.infiniteScrollComplete'); @@ -267,11 +262,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } if (fetchedAllTransactions) { - console.log("pagination Fetched all transactions."); $scope.vm.fetchedAllTxHistory = true; } - console.log('pagination txs returned in history: ' + txHistory.length); formatTxHistoryForDisplay(txHistory); completeTxHistory = txHistory; @@ -285,7 +278,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun if (completeTxHistory) { $scope.txHistory = showAll ? completeTxHistory : completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE); $scope.vm.allowInfiniteScroll = !$scope.vm.fetchedAllTxHistory && !(completeTxHistory.length === $scope.txHistory.length && $scope.vm.gettingInitialHistory); - console.log('pagination Showing txs: ', $scope.txHistory.length); } else { $scope.vm.allowInfiniteScroll = false; } @@ -331,10 +323,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // on-infinite="showMore()" $scope.showMore = function() { - console.log('pagination showMore()'); // Check if we have more than we are displaying if (completeTxHistory.length > $scope.txHistory.length) { - console.log('pagination We have more data than we are displaying.'); currentTxHistoryDisplayPage++; showHistory(false); $scope.$broadcast('scroll.infiniteScrollComplete'); @@ -357,7 +347,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }; $scope.updateAll = function(forceStatusUpdate, flushTxCacheOnNew)  { - console.log('pagination updateAll()'); updateStatus(forceStatusUpdate); //updateTxHistory(cb); fetchAndShowTxHistory(true, flushTxCacheOnNew); diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index a607fc870..e10e763e9 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -25,39 +25,6 @@ }; return service; -/* - function hasAllTransactionsFetched(walletId, cachedTxs, newTxs) { - var cachedTxIds = {}; - cachedTxs.forEach(function forCachedTx(tx){ - cachedTxIds[tx.txid] = true; - }); - - var someTransactionWereNew = false; - var overlappingTxsCount = 0; - - newTxs.forEach(function forNewTx(tx){ - if (cachedTxIds[tx.txid]) { - overlappingTxsCount++; - } else { - someTransactionWereNew = true; - } - }); - - console.log('pagination Overlapping transactions:', overlappingTxsCount); - if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good - if (!someTransactionWereNew && overlappingTxsCount === newTxs.length) { - console.log("We probably have all of the transactions fetched!!") - return true; - } - } else { - console.log("Something went wrong") - return true; // Something went wrong, so stop fetching.. - } - return false; - - } -*/ - function addEarlyTransactions(walletId, cachedTxs, newTxs) { var cachedTxIds = {}; @@ -77,13 +44,10 @@ } }); - console.log('pagination Early transactions overlapping:', overlappingTxsCount); if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good if (someTransactionsWereNew) { - console.log('pagination someTransactionsWereNew'); saveTxHistory(walletId, cachedTxs); } else if (overlappingTxsCount === newTxs.length) { - console.log('We probably have all transactions now'); allTransactionsFetched = true; } return cachedTxs; @@ -111,15 +75,13 @@ if (cachedTxIds[tx.txid]) { overlappingTxsCount++; } else { - someTransactionWereNew = true; + someTransactionsWereNew = true; uniqueNewTxs.push(tx); } }); - console.log('pagination Latest transactions overlapping:', overlappingTxsCount); if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good if (someTransactionsWereNew) { - console.log('pagination someTransactionsWereNew'); var allTxs = uniqueNewTxs.concat(cachedTxs); saveTxHistory(walletId, allTxs); return allTxs; @@ -155,8 +117,7 @@ if (txsFromServer.length === 0) { return cb(null, []); } - console.log('pagination Transactions fetched:', txsFromServer.length); - + var processedTxs = processNewTxs(wallet, txsFromServer); return cb(null, processedTxs); @@ -224,12 +185,9 @@ }; function saveTxHistory(walletId, processedTxs) { - console.log('pagination Saving transactions:', processedTxs.length); storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){ if (error) { $log.error('pagination Failed to save tx history.', error); - } else { - console.log('pagination Save successful.'); } }); } @@ -238,7 +196,6 @@ function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { if (flushCacheOnNew) { - console.log('pagination Getting latest txs, will then flush cache.'); fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ if (err) { return cb(err, txs); @@ -247,7 +204,6 @@ return cb(null, txs); }); } else { - console.log('pagination Getting txs to add to cache.'); getCachedTxHistory(wallet.id, function onCachedHistory(err, cachedTxs){ if (err) { $log.error('Failed to get cached tx history.', err); @@ -255,7 +211,6 @@ } var start = getLatest ? 0 : cachedTxs.length; - console.log('pagination Transaction fetch start:', start); fetchTxHistoryByPage(wallet, start, function onFetchHistory(err, fetchedTxs){ if (err) { return cb(err); @@ -271,7 +226,7 @@ } else { allTransactionsFetched = false; txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs); - return cb(null, txs, allTransactionsFetched/*, hasAllTransactionsFetched(wallet.id, cachedTxs, fetchedTxs)*/); + return cb(null, txs, allTransactionsFetched); } return cb(null, txs); }); From ed998e90364eb0a5a6595de2adb0b528a0195855 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 22 Aug 2018 16:27:30 +0200 Subject: [PATCH 021/118] Make "Share the Wallet App" translatable --- i18n/po/template.pot | 4 ++++ www/views/includes/community.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 9c0e3bdc6..2cb675045 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3783,6 +3783,10 @@ msgstr "" msgid "Bitcoin Cash Games" msgstr "" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "" diff --git a/www/views/includes/community.html b/www/views/includes/community.html index 86841a77c..ad7dcb169 100644 --- a/www/views/includes/community.html +++ b/www/views/includes/community.html @@ -26,7 +26,7 @@
- Share the Wallet App + Share the Wallet App From 771872495822a35dceb99f4865459d42bed1a5ec Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 27 Aug 2018 12:00:02 +0900 Subject: [PATCH 022/118] pkg building script --- Gruntfile.js | 5 +--- app-template/create-pkg-dist.sh | 28 ++++++++++++++++++++++ resources/bitcoin.com/mac/pkg/build_mas.py | 19 +++++++++++---- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index eb4bb2eb0..128221895 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,9 +8,6 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), exec: { - get_nwjs_for_pkg: { - command: 'if [ ! -d ./cache/0.19.5-pkg/osx64/nwjs.app ]; then cd ./cache; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output nwjs.zip; unzip nwjs.zip; mkdir -p ./0.19.5-pkg/osx64; cp -R ./nwjs-mas-v0.19.5-osx-x64/nwjs.app ./0.19.5-pkg/osx64/; fi' - }, create_others_dist: { command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' }, @@ -364,7 +361,7 @@ module.exports = function(grunt) { grunt.registerTask('desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']); // Build desktop osx pkg - grunt.registerTask('desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']); + grunt.registerTask('desktop-osx-pkg', ['prod', 'nwjs:pkg', 'exec:create_pkg_dist']); // Build desktop osx dmg grunt.registerTask('desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']); diff --git a/app-template/create-pkg-dist.sh b/app-template/create-pkg-dist.sh index c0b4d266d..ee45e21e2 100644 --- a/app-template/create-pkg-dist.sh +++ b/app-template/create-pkg-dist.sh @@ -25,6 +25,7 @@ rm build_mas.py ln -s ../resources/bitcoin.com/mac/pkg/build_mas.py build_mas.py echo "Signing ${APP_NAME}" +export CURRENT_PATH=`pwd` export APP_PATH="pkg/${APP_NAME}/osx64/${APP_NAME}" export TMP_PATH="tmp" export DIST_PATH="dist" @@ -36,6 +37,33 @@ if [ ! -d $DIST_PATH ]; then mkdir $DIST_PATH fi +cd "${APP_PATH}.app/Contents/Versions" +ln -s "55.0.2883.87" "Current" + +cd "55.0.2883.87/nwjs Framework.framework" +mkdir -p "Versions/A" + +mv "libffmpeg.dylib" "Versions/A/libffmpeg.dylib" +ln -s "Versions/Current/libffmpeg.dylib" "libffmpeg.dylib" + +mv "libnode.dylib" "Versions/A/libnode.dylib" +ln -s "Versions/Current/libnode.dylib" "libnode.dylib" + +mv "Helpers" "Versions/A/Helpers" +ln -s "Versions/Current/Helpers" "Helpers" + +mv "Resources" "Versions/A/Resources" +ln -s "Versions/Current/Resources" "Resources" + +mv "nwjs Framework" "Versions/A/nwjs Framework" +ln -s "Versions/Current/nwjs Framework" "nwjs Framework" + +cd "Versions" +ln -s "A" "Current" + +cd $CURRENT_PATH +chmod -vR 777 "${APP_PATH}.app/Contents" + python build_mas.py -C build.cfg -O "${TMP_PATH}/${APP_NAME}.app" -I "${APP_PATH}.app" -P "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg" echo "Signing Done" diff --git a/resources/bitcoin.com/mac/pkg/build_mas.py b/resources/bitcoin.com/mac/pkg/build_mas.py index d067abacd..7b902b1ab 100755 --- a/resources/bitcoin.com/mac/pkg/build_mas.py +++ b/resources/bitcoin.com/mac/pkg/build_mas.py @@ -178,11 +178,22 @@ def codesign_app(config, args): plistlib.writePlist(child_entitlements, tmp_child_entitlements) info('Child entitlements: %s' % tmp_child_entitlements) - framework = glob(args.output, 'nwjs Framework.framework', returnOnFound=True) - system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, framework)) - helperApp = glob(args.output, 'nwjs Helper.app', returnOnFound=True) - system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, helperApp)) + appModeLoader = glob(args.output, 'app_mode_loader', returnOnFound=True) + system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, appModeLoader)) + + crashpadHandler = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'crashpad_handler', returnOnFound=True) + system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, crashpadHandler)) + + libffmpeg = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'libffmpeg.dylib', returnOnFound=True) + system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, libffmpeg)) + libnode = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'libnode.dylib', returnOnFound=True) + system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, libnode)) + helperApp = glob(args.output, 'nwjs Helper.app', returnOnFound=True) + system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, helperApp)) + framework = glob(args.output, 'nwjs Framework.framework', returnOnFound=True) + system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, framework)) + ## sign parent app (_, tmp_parent_entitlements) = tempfile.mkstemp() if config.has_option('Sign', 'ParentEntitlements'): From 43592a16897a5d967b24d89d5d5226ed3625fa28 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 27 Aug 2018 18:04:55 +1200 Subject: [PATCH 023/118] Fix package.json. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c1f37b2e6..e54e3d14d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "postinstall": "npm run apply:copay && echo && echo \"Repo configured for standard Copay distribution. To switch to the BitPay distribution, run 'npm run apply:bitpay'.\" && echo", "start": "echo && echo \"Choose a distribution by running 'npm run apply:copay' or 'npm run apply:bitpay'.\" && echo", "apply:copay": "npm i fs-extra@0.30 && cd app-template && node apply.js copay && cd .. && npm i", - "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare", + "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", "apply:bitpay": "npm i fs-extra@0.30 && cd app-template && node apply.js bitpay && cd .. && npm i", "unstage-package": "git reset package.json", "clean-all": "git clean -dfx" From 82173511417a627738cbb908e41823696d41dc3a Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 27 Aug 2018 10:59:14 +0200 Subject: [PATCH 024/118] possible fix for selecting the right currency on new wallets --- src/js/controllers/onboarding/tour.js | 142 +++++++++++++++----------- 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/src/js/controllers/onboarding/tour.js b/src/js/controllers/onboarding/tour.js index 47261006b..f6cf0afe2 100644 --- a/src/js/controllers/onboarding/tour.js +++ b/src/js/controllers/onboarding/tour.js @@ -1,6 +1,6 @@ 'use strict'; angular.module('copayApp.controllers').controller('tourController', - function($scope, $state, $log, $timeout, $filter, ongoingProcess, profileService, rateService, popupService, gettextCatalog, startupService, storageService, walletService, $q) { + function ($scope, $state, $log, $timeout, $filter, ongoingProcess, configService, profileService, rateService, popupService, gettextCatalog, lodash, startupService, storageService, uxLanguage, walletService, $q) { $scope.data = { index: 0 @@ -46,62 +46,90 @@ angular.module('copayApp.controllers').controller('tourController', creatingWallet = true; ongoingProcess.set('creatingWallet', true); $timeout(function() { - profileService.createDefaultWallet(function(err, walletClients) { - if (err) { - $log.warn(err); + uxLanguage.init(function(lang) { + var rateCode = uxLanguage.getRateCode(lang); + console.log("When Available: rateService"); + rateService.whenAvailable(function() { + var alternatives = rateService.listAlternatives(true); - return $timeout(function() { - $log.warn('Retrying to create default wallet.....:' + ++retryCount); - if (retryCount > 3) { - ongoingProcess.set('creatingWallet', false); - popupService.showAlert( - gettextCatalog.getString('Cannot Create Wallet'), err, - function() { - retryCount = 0; - return $scope.createDefaultWallet(); - }, gettextCatalog.getString('Retry')); - } else { - return $scope.createDefaultWallet(); - } - }, 2000); - }; - - ongoingProcess.set('creatingWallet', false); - var bchWallet = walletClients[0]; - var btcWallet = walletClients[1]; - var bchWalletId = bchWallet.credentials.walletId; - var btcWalletId = btcWallet.credentials.walletId; - - function createAddressPromise(wallet) { - return $q(function(resolve, reject) { - walletService.getAddress(wallet, true, function(e, addr) { - if (e) reject(e); - resolve(addr); + var newAltCurrency = lodash.find(alternatives, { + 'isoCode': rateCode }); + + configService.whenAvailable(function(config) { + var opts = { + wallet: { + settings: { + alternativeName: newAltCurrency.name, + alternativeIsoCode: newAltCurrency.isoCode, + } + } + }; + configService.set(opts, function(err) { + if (err) $log.warn(err); + + profileService.createDefaultWallet(function(err, walletClients) { + if (err) { + $log.warn(err); + + return $timeout(function() { + $log.warn('Retrying to create default wallet.....:' + ++retryCount); + if (retryCount > 3) { + ongoingProcess.set('creatingWallet', false); + popupService.showAlert( + gettextCatalog.getString('Cannot Create Wallet'), err, + function() { + retryCount = 0; + return $scope.createDefaultWallet(); + }, gettextCatalog.getString('Retry')); + } else { + return $scope.createDefaultWallet(); + } + }, 2000); + } + ; + + ongoingProcess.set('creatingWallet', false); + var bchWallet = walletClients[0]; + var btcWallet = walletClients[1]; + var bchWalletId = bchWallet.credentials.walletId; + var btcWalletId = btcWallet.credentials.walletId; + + function createAddressPromise(wallet) { + return $q(function (resolve, reject) { + walletService.getAddress(wallet, true, function (e, addr) { + if (e) reject(e); + resolve(addr); + }); + }); + } + + function goToCollectEmail() { + $state.go('onboarding.collectEmail', { + bchWalletId: bchWalletId, + btcWalletId: btcWalletId + }); + } + + var bchAddressPromise = createAddressPromise(bchWallet); + var btcAddressPromise = createAddressPromise(btcWallet); + ongoingProcess.set('generatingNewAddress', true); + + $q.all([bchAddressPromise, btcAddressPromise]).then(function (addresses) { + ongoingProcess.set('generatingNewAddress', false); + $state.go('tabs.home'); + }, function (e) { + ongoingProcess.set('generatingNewAddress', false); + $log.warn(e); + popupService.showAlert(gettextCatalog.getString('Error'), e); + $state.go('tabs.home'); + }); + }); + }); + }); + $log.debug('Setting default currency : ' + newAltCurrency); }); - } - - function goToCollectEmail() { - $state.go('onboarding.collectEmail', { - bchWalletId: bchWalletId, - btcWalletId: btcWalletId - }); - } - - var bchAddressPromise = createAddressPromise(bchWallet); - var btcAddressPromise = createAddressPromise(btcWallet); - ongoingProcess.set('generatingNewAddress', true); - - $q.all([bchAddressPromise, btcAddressPromise]).then(function(addresses) { - ongoingProcess.set('generatingNewAddress', false); - $state.go('tabs.home'); - }, function(e) { - ongoingProcess.set('generatingNewAddress', false); - $log.warn(e); - popupService.showAlert(gettextCatalog.getString('Error'), e); - $state.go('tabs.home'); - }); - }); - }, 300); - }; - }); + }) + }, 300); + }; + }); From 2c2c21bcdfa71e06fd339f64060604a01791f84b Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 27 Aug 2018 14:06:10 +0200 Subject: [PATCH 025/118] Update notifications now able to parse "rc"'s and also show release notes from GitHub. --- src/js/controllers/tab-home.js | 27 +++++------ src/js/services/latestReleaseService.js | 64 ++++++++++++++++++++----- src/sass/views/tab-home.scss | 5 ++ www/views/tab-home.html | 4 +- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 318fcece2..a7332cb76 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -43,20 +43,19 @@ angular.module('copayApp.controllers').controller('tabHomeController', }); } - if ($scope.isNW) { - latestReleaseService.checkLatestRelease(function(err, newRelease) { - if (err) { - $log.warn(err); - return; - } - if (newRelease) { - $scope.newRelease = true; - $scope.updateText = gettextCatalog.getString('There is a new version of {{appName}} available', { - appName: $scope.name - }); - } - }); - } + latestReleaseService.checkLatestRelease(function(err, newReleaseData) { + if (err) { + $log.warn(err); + return; + } + if (newReleaseData) { + $scope.newRelease = true; + $scope.newReleaseText = gettextCatalog.getString('There is a new version of {{appName}} available', { + appName: $scope.name + }); + $scope.newReleaseNotes = newReleaseData.releaseNotes; + } + }); }; function onEnter(event, data) { diff --git a/src/js/services/latestReleaseService.js b/src/js/services/latestReleaseService.js index 87aace87f..96614715d 100644 --- a/src/js/services/latestReleaseService.js +++ b/src/js/services/latestReleaseService.js @@ -1,16 +1,16 @@ 'use strict'; angular.module('copayApp.services') - .factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, configService) { + .factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, configService, platformInfo) { var root = {}; root.checkLatestRelease = function(cb) { var releaseURL = configService.getDefaults().release.url; - requestLatestRelease(releaseURL, function(err, release) { + requestLatestRelease(releaseURL, function(err, releaseData) { if (err) return cb(err); var currentVersion = window.version; - var latestVersion = release.data.tag_name; + var latestVersion = releaseData.tag_name; if (!verifyTagFormat(currentVersion)) return cb('Cannot verify the format of version tag: ' + currentVersion); @@ -20,30 +20,72 @@ angular.module('copayApp.services') var current = formatTagNumber(currentVersion); var latest = formatTagNumber(latestVersion); - if (latest.major < current.major || (latest.major == current.major && latest.minor <= current.minor)) + if (latest.major < current.major || (latest.major === current.major && latest.minor <= current.minor)) { return cb(null, false); + } + + var releaseSearchTerm = ""; + if (platformInfo.isNW) { // XX SP: DESKTOP: Check if the latest release is already available for current OS + var platform = process.platform; + if (platform === "darwin") { + releaseSearchTerm = "osx"; + } else if (platform === "win32") { + releaseSearchTerm = "win"; + } else if (platform === "linux") { + releaseSearchTerm = "linux"; + } + var foundNewVersion = false; + for (var i in releaseData.assets) { + if (releaseData.assets[i].name.indexOf(releaseSearchTerm) !== -1) { + foundNewVersion = true; + break; + } + } + } $log.debug('A new version is available: ' + latestVersion); - return cb(null, true); + + // + var releaseNotes = false; + if (releaseData.body) { + var releaseLines = releaseData.body.split('\n'); + for (var lineNum in releaseLines) { + if (releaseLines[lineNum].substring(0, 2) === "# ") { + releaseLines[lineNum] = ""+releaseLines[lineNum].substring(2)+""; + } else if (releaseLines[lineNum].substring(0, 2) === "- ") { + releaseLines[lineNum] = "• "+releaseLines[lineNum].substring(2); + } + } + releaseNotes = releaseLines.join('\n'); + } + + return cb(null, {latestVersion: latestVersion, releaseNotes: releaseNotes}); }); function verifyTagFormat(tag) { - var regex = /^v?\d+\.\d+\.\d+$/i; + var regex = /^v?\d+\.\d+(\.\d+)?(-rc\d)?$/i; return regex.exec(tag); }; function formatTagNumber(tag) { + var label = false; + if (tag.split("-")[1]) { // Move postfixes like "-rc2" to a variable + label = tag.split("-")[1]; + tag = tag.split("-")[0]; + } + var formattedNumber = tag.replace(/^v/i, '').split('.'); return { - major: +formattedNumber[0], - minor: +formattedNumber[1], - patch: +formattedNumber[2] + major: +(formattedNumber[0]?+formattedNumber[0]:0), + minor: +(formattedNumber[1]?+formattedNumber[1]:0), + patch: +(formattedNumber[2]?+formattedNumber[2]:0), + label: label /* XX SP: we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */ }; }; }; function requestLatestRelease(releaseURL, cb) { - $log.debug('Retrieving latest relsease information...'); + $log.debug('Retrieving latest release information...'); var request = { url: releaseURL, @@ -53,7 +95,7 @@ angular.module('copayApp.services') $http(request).then(function(release) { $log.debug('Latest release: ' + release.data.name); - return cb(null, release); + return cb(null, release.data); }, function(err) { return cb('Cannot get the release information: ' + err); }); diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index 55080ab7b..4cc5ce5ed 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -173,6 +173,11 @@ font-weight: 700; color: #444; } + .release-notes { + white-space: pre; + white-space: pre-line; + text-align: left; + } .button { width: 100%; border: none; diff --git a/www/views/tab-home.html b/www/views/tab-home.html index 8ef922918..01182f09d 100644 --- a/www/views/tab-home.html +++ b/www/views/tab-home.html @@ -15,7 +15,9 @@
-
{{updateText}}
+
{{newReleaseText}}
+
+
Download From 3cab1146184ac427b3e1b5766b85dea6a2f9f5c0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 27 Aug 2018 14:09:06 +0200 Subject: [PATCH 026/118] template.pot change --- i18n/po/template.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 9c0e3bdc6..f5ea3ac3d 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -511,7 +511,7 @@ msgid "Cannot Create Wallet" msgstr "" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "" #: www/views/includes/bitpayCardsCard.html:2 From 4564040459bff6791c0e4930921f8a0edfaa96fb Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 28 Aug 2018 10:50:53 +0900 Subject: [PATCH 027/118] fixes for pkg mas --- Gruntfile.js | 9 ++++-- app-template/create-pkg-dist.sh | 29 +++---------------- resources/bitcoin.com/mac/pkg/build_mas.py | 5 ---- .../mac/pkg/entitlements-parent.plist | 4 ++- 4 files changed, 13 insertions(+), 34 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 128221895..8771b85eb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,6 +8,9 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), exec: { + get_nwjs_for_pkg: { + command: 'if [ ! -d ./cache/0.19.4/osx64/nwjs.app ]; then mkdir -p ./cache/0.19.4/osx64; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output ./cache/nwjs.zip; unzip ./cache/nwjs.zip -d ./cache; cp -R ./cache/nwjs-mas-v0.19.5-osx-x64/nwjs.app ./cache/0.19.4/osx64/; fi' + }, create_others_dist: { command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' }, @@ -293,10 +296,10 @@ module.exports = function(grunt) { }, pkg: { options: { - appName: '<%= pkg.nameCaseNoSpace %>', + appName: '<%= pkg.title %>', platforms: ['osx64'], buildDir: './webkitbuilds/pkg', - version: '0.19.5', + version: '0.19.4', macIcns: './resources/<%= pkg.name %>/mac/pkg/app.icns', exeIco: './www/img/app/logo.ico', macPlist: { @@ -361,7 +364,7 @@ module.exports = function(grunt) { grunt.registerTask('desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']); // Build desktop osx pkg - grunt.registerTask('desktop-osx-pkg', ['prod', 'nwjs:pkg', 'exec:create_pkg_dist']); + grunt.registerTask('desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']); // Build desktop osx dmg grunt.registerTask('desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']); diff --git a/app-template/create-pkg-dist.sh b/app-template/create-pkg-dist.sh index ee45e21e2..66fe589d0 100644 --- a/app-template/create-pkg-dist.sh +++ b/app-template/create-pkg-dist.sh @@ -24,9 +24,9 @@ ln -s ../resources/bitcoin.com/mac/pkg/build.cfg build.cfg rm build_mas.py ln -s ../resources/bitcoin.com/mac/pkg/build_mas.py build_mas.py -echo "Signing ${APP_NAME}" +echo "Signing ${APP_FULLNAME}" export CURRENT_PATH=`pwd` -export APP_PATH="pkg/${APP_NAME}/osx64/${APP_NAME}" +export APP_PATH="pkg/${APP_FULLNAME}/osx64/${APP_FULLNAME}" export TMP_PATH="tmp" export DIST_PATH="dist" @@ -40,34 +40,13 @@ fi cd "${APP_PATH}.app/Contents/Versions" ln -s "55.0.2883.87" "Current" -cd "55.0.2883.87/nwjs Framework.framework" -mkdir -p "Versions/A" - -mv "libffmpeg.dylib" "Versions/A/libffmpeg.dylib" -ln -s "Versions/Current/libffmpeg.dylib" "libffmpeg.dylib" - -mv "libnode.dylib" "Versions/A/libnode.dylib" -ln -s "Versions/Current/libnode.dylib" "libnode.dylib" - -mv "Helpers" "Versions/A/Helpers" -ln -s "Versions/Current/Helpers" "Helpers" - -mv "Resources" "Versions/A/Resources" -ln -s "Versions/Current/Resources" "Resources" - -mv "nwjs Framework" "Versions/A/nwjs Framework" -ln -s "Versions/Current/nwjs Framework" "nwjs Framework" - -cd "Versions" -ln -s "A" "Current" - cd $CURRENT_PATH chmod -vR 777 "${APP_PATH}.app/Contents" -python build_mas.py -C build.cfg -O "${TMP_PATH}/${APP_NAME}.app" -I "${APP_PATH}.app" -P "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg" +python build_mas.py -C build.cfg -O "${TMP_PATH}/${APP_FULLNAME}.app" -I "${APP_PATH}.app" -P "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg" echo "Signing Done" echo "Done." -exit +exit \ No newline at end of file diff --git a/resources/bitcoin.com/mac/pkg/build_mas.py b/resources/bitcoin.com/mac/pkg/build_mas.py index 7b902b1ab..90f7ad5f0 100755 --- a/resources/bitcoin.com/mac/pkg/build_mas.py +++ b/resources/bitcoin.com/mac/pkg/build_mas.py @@ -178,11 +178,6 @@ def codesign_app(config, args): plistlib.writePlist(child_entitlements, tmp_child_entitlements) info('Child entitlements: %s' % tmp_child_entitlements) - appModeLoader = glob(args.output, 'app_mode_loader', returnOnFound=True) - system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, appModeLoader)) - - crashpadHandler = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'crashpad_handler', returnOnFound=True) - system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, crashpadHandler)) libffmpeg = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'libffmpeg.dylib', returnOnFound=True) system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, libffmpeg)) diff --git a/resources/bitcoin.com/mac/pkg/entitlements-parent.plist b/resources/bitcoin.com/mac/pkg/entitlements-parent.plist index 12d6997e3..b39edb569 100644 --- a/resources/bitcoin.com/mac/pkg/entitlements-parent.plist +++ b/resources/bitcoin.com/mac/pkg/entitlements-parent.plist @@ -5,7 +5,9 @@ com.apple.security.app-sandbox com.apple.security.application-groups - $GROUPID + + 299HJ3G3BP.com.bitcoin.mwallet.mac + com.apple.security.files.user-selected.read-only com.apple.security.network.client From d65e4401ddb334e39d1749cbce24abe73449e422 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 30 Aug 2018 14:10:46 +0200 Subject: [PATCH 028/118] Update popup --- src/js/controllers/tab-home.js | 18 +++----- src/js/services/latestReleaseService.js | 58 +++++++++++++++++++++++-- src/sass/views/tab-home.scss | 10 +++++ www/views/tab-home.html | 2 +- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index a7332cb76..825959464 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -56,7 +56,7 @@ angular.module('copayApp.controllers').controller('tabHomeController', $scope.newReleaseNotes = newReleaseData.releaseNotes; } }); - }; + } function onEnter(event, data) { $ionicNavBarDelegate.showBar(true); @@ -108,13 +108,13 @@ angular.module('copayApp.controllers').controller('tabHomeController', $scope.$apply(); }, 10); }); - }; + } function onLeave (event, data) { lodash.each(listeners, function(x) { x(); }); - }; + } $scope.createdWithinPastDay = function(time) { return timeService.withinPastDay(time); @@ -123,16 +123,10 @@ angular.module('copayApp.controllers').controller('tabHomeController', $scope.startFreshSend = function() { sendFlowService.clear(); $state.go('tabs.send'); - } + }; - $scope.openExternalLink = function() { - var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest'; - var optIn = true; - var title = gettextCatalog.getString('Update Available'); - var message = gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.'); - var okText = gettextCatalog.getString('View Update'); - var cancelText = gettextCatalog.getString('Go Back'); - externalLinkService.open(url, optIn, title, message, okText, cancelText); + $scope.showUpdatePopup = function() { + latestReleaseService.showUpdatePopup(); }; $scope.openBannerUrl = function() { diff --git a/src/js/services/latestReleaseService.js b/src/js/services/latestReleaseService.js index 96614715d..fbcf01ada 100644 --- a/src/js/services/latestReleaseService.js +++ b/src/js/services/latestReleaseService.js @@ -1,6 +1,6 @@ 'use strict'; angular.module('copayApp.services') - .factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, configService, platformInfo) { + .factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, $ionicPopup, configService, externalLinkService, gettextCatalog, platformInfo) { var root = {}; @@ -65,7 +65,7 @@ angular.module('copayApp.services') function verifyTagFormat(tag) { var regex = /^v?\d+\.\d+(\.\d+)?(-rc\d)?$/i; return regex.exec(tag); - }; + } function formatTagNumber(tag) { var label = false; @@ -79,9 +79,9 @@ angular.module('copayApp.services') major: +(formattedNumber[0]?+formattedNumber[0]:0), minor: +(formattedNumber[1]?+formattedNumber[1]:0), patch: +(formattedNumber[2]?+formattedNumber[2]:0), - label: label /* XX SP: we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */ + label: label /* XX SP: Maybe we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */ }; - }; + } }; function requestLatestRelease(releaseURL, cb) { @@ -99,6 +99,56 @@ angular.module('copayApp.services') }, function(err) { return cb('Cannot get the release information: ' + err); }); + } + + root.showUpdatePopup = function () { + var buttons = [{ + text: "GitHub", + type: 'button-positive', + onTap: function () { + var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest'; + externalLinkService.open(url, false); + } + }]; + + if (platformInfo.isAndroid) { + buttons.unshift({ + text: "Google Play Store", + type: 'button-positive', + onTap: function () { + var url = 'https://play.google.com/store/apps/details?id=com.bitcoin.mwallet'; + externalLinkService.open(url, false); + } + }); + } + if (platformInfo.isIOS) { + buttons.unshift({ + text: "App Store", + type: 'button-positive', + onTap: function () { + var url = 'https://itunes.apple.com/app/bitcoin-com-wallet/id1383072453'; + externalLinkService.open(url, false); + } + }); + } + + if (buttons.length === 1) { // There is only one source to download (probably on desktop, so open GitHub release page..) + buttons[0].onTap(); + } else { + buttons.push({ + text: gettextCatalog.getString('Go Back'), + type: 'button-positive', + onTap: function () { + return true; + } + }); + $ionicPopup.show({ + title: gettextCatalog.getString('Update Available'), + subTitle: gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.'), + cssClass: 'popup-update', + buttons: buttons + }); + } }; return root; diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index 4cc5ce5ed..c4b3a3ec4 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -195,3 +195,13 @@ top:11px; } } +.popup-update { + .popup-buttons { + display: block; + } + .popup-buttons .button{ + display:block; + min-width: 100% !important; + margin-top: 4px; + } +} \ No newline at end of file diff --git a/www/views/tab-home.html b/www/views/tab-home.html index 01182f09d..7191c1c16 100644 --- a/www/views/tab-home.html +++ b/www/views/tab-home.html @@ -19,7 +19,7 @@
-
+
Download
From 8908b5ef802be475cd0ceeb41191ae7016b8e973 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 3 Sep 2018 20:12:20 +1200 Subject: [PATCH 029/118] Send max button behaviour is now conditional based on min and max limits. --- i18n/po/template.pot | 6 +- src/js/controllers/amount.js | 187 +++++++++++++++++++++++++++-------- www/views/amount.html | 7 +- 3 files changed, 157 insertions(+), 43 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 9c0e3bdc6..eed30bbb8 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3849,4 +3849,8 @@ msgstr "" #: src/js/services/incomingData.js:129 msgid "This invoice is no longer accepting payments" -msgstr "" \ No newline at end of file +msgstr "" + +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "" diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index f796f9559..e814bc92f 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -11,6 +11,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.alternativeUnit = ''; vm.amount = '0'; vm.availableFunds = ''; + vm.canSendAllAvailableFunds = true; // Use insufficient for logic, as when the amount is invalid, funds being // either sufficent or insufficient doesn't make sense. vm.fundsAreInsufficient = false; @@ -20,9 +21,12 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.lastUsedPopularList = []; vm.maxAmount = 0; vm.minAmount = 0; + vm.showSendMaxButton = false; + vm.showSendLimitMaxButton = false; vm.thirdParty = false; vm.unit = ''; + // Functions vm.changeUnit = changeUnit; vm.close = close; vm.findCurrency = findCurrency; @@ -34,6 +38,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.pushDigit = pushDigit; vm.removeDigit = removeDigit; vm.save = save; + vm.sendableFunds = ''; vm.sendMax = sendMax; vm.errorMessage = ''; @@ -46,10 +51,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var altCurrencyModal = null; var altUnitIndex = 0; - var availableFundsInCrypto = ''; - var availableFundsInFiat = ''; - var availableSatoshis = null; var availableUnits = []; + var canSendMax = true; var fiatCode; var isNW = platformInfo.isNW; var isAndroid = platformInfo.isAndroid; @@ -57,10 +60,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var lastUsedAltCurrencyList = []; var passthroughParams = {}; var satToUnit; + var transactionSendableAmount = { + crypto: '', + satoshis: null + }; var unitDecimals; var unitIndex = 0; var unitToSatoshi; var useSendMax = false; + var walletSpendableAmount = { + crypto: '', + satoshis: null + }; function onLeave() { angular.element($window).off('keydown'); @@ -81,27 +92,17 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.minAmount = parseFloat(passthroughParams.minAmount); vm.maxAmount = parseFloat(passthroughParams.maxAmount); + vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId; + vm.showSendMaxButton = !vm.isRequestingSpecificAmount; + if (passthroughParams.thirdParty) { vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object if (vm.thirdParty) { - if (vm.thirdParty.id === 'shapeshift') { - if (!vm.thirdParty.data) { - vm.thirdParty.data = {}; - } - vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; - - vm.fromWallet = profileService.getWallet(vm.fromWalletId); - vm.toWallet = profileService.getWallet(vm.toWalletId); - - shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { - vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); - vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); - }); - } + initForShapeshift(); } } - vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId; + var config = configService.getSync().wallet.settings; @@ -217,6 +218,30 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicHistory.goBack(); } + function initForShapeshift() { + if (vm.thirdParty.id === 'shapeshift') { + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } + vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; + + vm.fromWallet = profileService.getWallet(vm.fromWalletId); + vm.toWallet = profileService.getWallet(vm.toWalletId); + + vm.showSendMaxButton = false; + vm.showSendLimitMaxButton = false; + vm.canSendAllAvailableFunds = false; + + shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { + vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); + vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); + + setMaximumButtonFromWallet(vm.fromWallet); + }); + + } + } + function paste(value) { vm.amount = value; processAmount(); @@ -232,7 +257,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function sendMax() { - useSendMax = true; + if (canSendMax) { + useSendMax = true; + + } else { + // Need to be precise, so use crypto directly rather than fiat with exchange rate + if (availableUnits[unitIndex].isFiat) { + var tempIndex = altUnitIndex; + altUnitIndex = unitIndex; + unitIndex = tempIndex; + } + vm.amount = (transactionSendableAmount.satoshis * unitToSatoshi).toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); + } finish(); } @@ -353,8 +389,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, amountInCrypto = a; var amountInSatoshis = a * unitToSatoshi; vm.fundsAreInsufficient = !!passthroughParams.fromWalletId - && availableSatoshis !== null - && availableSatoshis < amountInSatoshis; + && walletSpendableAmount.satoshis !== null + && walletSpendableAmount.satoshis < amountInSatoshis; vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); vm.allowSend = lodash.isNumber(a) @@ -374,8 +410,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } else { amountInCrypto = result; vm.fundsAreInsufficient = passthroughParams.fromWalletId - && availableSatoshis !== null - && availableSatoshis < result * unitToSatoshi; + && walletSpendableAmount.satoshis !== null + && walletSpendableAmount.satoshis < result * unitToSatoshi; vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); vm.allowSend = lodash.isNumber(result) @@ -584,24 +620,23 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, availableUnits[altUnitIndex].shortName = newAltCurrency.isoCode; fiatCode = newAltCurrency.isoCode; updateAvailableFundsStringIfNeeded(); + updateMaximumButtonIfNeeded(); updateUnitUI(); close(); }); } function updateAvailableFundsStringIfNeeded() { - if (passthroughParams.fromWalletId && availableSatoshis !== null) { - availableFundsInFiat = ''; - vm.availableFunds = availableFundsInCrypto; + if (passthroughParams.fromWalletId && walletSpendableAmount.satoshis !== null) { + vm.availableFunds = walletSpendableAmount.crypto; if (availableUnits[unitIndex].isFiat) { var coin = availableUnits[altUnitIndex].id; - txFormatService.formatAlternativeStr(coin, availableSatoshis, function formatCallback(formatted){ - if (formatted) { - availableFundsInFiat = formatted; + txFormatService.formatAlternativeStr(coin, walletSpendableAmount.satoshis, function formatCallback(formatted){ + if (formatted) { $scope.$apply(function() { - vm.availableFunds = availableFundsInFiat; + vm.availableFunds = formatted; }); } }); @@ -610,9 +645,10 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function updateAvailableFundsFromWallet(wallet) { + var availableFundsInFiat = ''; if (wallet.status && wallet.status.isValid) { - availableFundsInCrypto = wallet.status.spendableBalanceStr; - availableSatoshis = wallet.status.spendableAmount; + walletSpendableAmount.crypto = wallet.status.spendableBalanceStr; + walletSpendableAmount.satoshis = wallet.status.spendableAmount; if (wallet.status.alternativeBalanceAvailable) { availableFundsInFiat = wallet.status.spendableBalanceAlternative + ' ' + wallet.status.alternativeIsoCode; } else { @@ -626,20 +662,93 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } else { availableFundsInFiat = ''; } - availableFundsInCrypto = wallet.cachedStatus.spendableBalanceStr; - availableSatoshis = wallet.cachedStatus.spendableAmount; + walletSpendableAmount.crypto = wallet.cachedStatus.spendableBalanceStr; + walletSpendableAmount.satoshis = wallet.cachedStatus.spendableAmount; } else { - availableFundsInFiat = ''; - availableFundsInCrypto = ''; - availableSatoshis = null; + walletSpendableAmount.crypto = ''; + walletSpendableAmount.satoshis = null; } if (availableUnits[unitIndex].isFiat) { - vm.availableFunds = availableFundsInFiat || availableFundsInCrypto; + vm.availableFunds = availableFundsInFiat || walletSpendableAmount.crypto; } else { - vm.availableFunds = availableFundsInCrypto; + vm.availableFunds = walletSpendableAmount.crypto; + } + + setMaximumButtonFromWallet(wallet); + } + + function updateMaximumButtonIfNeeded() { + if (vm.showSendMaxButton || vm.showSendLimitMaxButton) { + transactionSendableAmount.fiat = ''; + vm.sendableFunds = transactionSendableAmount.crypto; + + if (availableUnits[unitIndex].isFiat) { + var coin = availableUnits[altUnitIndex].id; + txFormatService.formatAlternativeStr(coin, transactionSendableAmount.satoshis, function formatCallback(formatted){ + if (formatted) { + $scope.$apply(function onApply() { + vm.sendableFunds = formatted; + }); + } + }); + } } } + + function setMaximumButtonFromWallet(wallet) { + var minSatoshis = vm.minAmount * unitToSatoshi; + var maxSatoshis = vm.maxAmount * unitToSatoshi; + + if (minSatoshis > walletSpendableAmount.satoshis) { + console.log('sendmax Hiding max buttons as minimum is too high.'); + canSendMax = false; + vm.showSendMaxButton = false; + vm.showSendLimitMaxButton = false; + transactionSendableAmount.satoshis = 0; + + } else if (maxSatoshis) { + if (walletSpendableAmount.satoshis > maxSatoshis) { + console.log('sendmax Showing max limit button as available is greater than max limit.'); + canSendMax = false; + vm.showSendMaxButton = false; + vm.showSendLimitMaxButton = true; + transactionSendableAmount.satoshis = maxSatoshis; + } else { + console.log('sendmax Showing sendmax as all available as less than max limit.'); + // Enabling send max here is a little dangerous, if they receive funds between pressing + // this and the calculation in the Review screen. + canSendMax = true; + vm.showSendMaxButton = true; + vm.showSendLimitMaxButton = false; + transactionSendableAmount.satoshis = walletSpendableAmount.satoshis; + } + + } else { + console.log('sendmax Showing sendmax as all available because no limits.'); + canSendMax = true; + vm.showSendMaxButton = true; + vm.showSendLimitMaxButton = false; + transactionSendableAmount.satoshis = walletSpendableAmount.satoshis; + } + + if (vm.showSendMaxButton || vm.showSendLimitMaxButton) { + console.log('sendmax Setting max button text'); + transactionSendableAmount.crypto = txFormatService.formatAmountStr(wallet.coin, transactionSendableAmount.satoshis); + vm.sendableFunds = transactionSendableAmount.crypto; + + if (availableUnits[unitIndex].isFiat) { + txFormatService.formatAlternativeStr(wallet.coin, transactionSendableAmount.satoshis, function onFormat(formatted){ + if (formatted) { + $scope.$apply(function onApply() { + vm.sendableFunds = formatted; + }); + } + }); + } + } + + } } diff --git a/www/views/amount.html b/www/views/amount.html index 939937be8..114292026 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -53,11 +53,12 @@
-
+
From b7dda8b6cabe6b30be43a9526c51893bab12f1e3 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 3 Sep 2018 20:17:46 +1200 Subject: [PATCH 030/118] Currency in send max button now updates with currency change. --- src/js/controllers/amount.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index e814bc92f..60912e42b 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -620,7 +620,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, availableUnits[altUnitIndex].shortName = newAltCurrency.isoCode; fiatCode = newAltCurrency.isoCode; updateAvailableFundsStringIfNeeded(); - updateMaximumButtonIfNeeded(); updateUnitUI(); close(); }); @@ -641,6 +640,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } }); } + updateMaximumButtonIfNeeded(); } } @@ -681,6 +681,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function updateMaximumButtonIfNeeded() { + console.log('sendmax updateMaximumButtonIfNeeded()'); if (vm.showSendMaxButton || vm.showSendLimitMaxButton) { transactionSendableAmount.fiat = ''; vm.sendableFunds = transactionSendableAmount.crypto; From b1bf269ca2f9af676c1546c4a90f88fdadeadb5f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 3 Sep 2018 11:54:15 +0200 Subject: [PATCH 031/118] Review transaction screen: iPhone X fee summary display fix --- src/sass/components/fee-summary.scss | 8 ++++---- src/sass/views/review.scss | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss index e09af4be3..5c9488b60 100644 --- a/src/sass/components/fee-summary.scss +++ b/src/sass/components/fee-summary.scss @@ -1,11 +1,11 @@ .fee-summary { - position: relative; + background-color: #F2F2F2; + box-sizing: border-box; display: flex; flex-direction: column; - width: 100%; padding: 5px 12px 15px; - box-sizing: border-box; - background-color: #F2F2F2; + position: relative; + width: 100%; &:before { content: ''; diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index c530a1cef..474e1ea3f 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -6,8 +6,10 @@ } .fee-summary { - position: absolute; bottom: 92px; + bottom: calc(92px + constant(safe-area-inset-bottom)); /* iOS 11.0 */ + bottom: calc(92px + env(safe-area-inset-bottom)); /* iOS 11.2 */ + position: absolute; } .shapeshift-banner, .bitpay-banner, .egifter-banner { From 274d221af05e682f449c498b570723e170f06983 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 3 Sep 2018 13:45:40 +0200 Subject: [PATCH 032/118] Links changed, app store added and some logic added --- src/js/services/latestReleaseService.js | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/js/services/latestReleaseService.js b/src/js/services/latestReleaseService.js index fbcf01ada..d853624a8 100644 --- a/src/js/services/latestReleaseService.js +++ b/src/js/services/latestReleaseService.js @@ -102,15 +102,18 @@ angular.module('copayApp.services') } root.showUpdatePopup = function () { - var buttons = [{ - text: "GitHub", - type: 'button-positive', - onTap: function () { - var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest'; - externalLinkService.open(url, false); - } - }]; + var buttons = []; + if (!platformInfo.isIOS) { // There is no GitHub-release for iPhone + buttons.push({ + text: "GitHub", + type: 'button-positive', + onTap: function () { + var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest'; + externalLinkService.open(url, false); + } + }); + } if (platformInfo.isAndroid) { buttons.unshift({ text: "Google Play Store", @@ -126,10 +129,21 @@ angular.module('copayApp.services') text: "App Store", type: 'button-positive', onTap: function () { - var url = 'https://itunes.apple.com/app/bitcoin-com-wallet/id1383072453'; + var url = 'https://itunes.apple.com/app/id1252903728'; externalLinkService.open(url, false); } }); + } else if (platformInfo.isNW) { + if (process.platform === 'darwin') { + buttons.unshift({ + text: "Mac App Store", + type: 'button-positive', + onTap: function () { + var url = 'https://itunes.apple.com/app/bitcoin-com-wallet/id1383072453'; + externalLinkService.open(url, false); + } + }); + } } if (buttons.length === 1) { // There is only one source to download (probably on desktop, so open GitHub release page..) From 383d811067b670594ffb6d4347ab6f0612b213c7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 3 Sep 2018 14:44:09 +0200 Subject: [PATCH 033/118] Multi-line buttons fix --- src/sass/buttons.scss | 2 +- src/sass/mixins/layout.scss | 2 ++ src/sass/views/tab-home.scss | 4 ++-- src/sass/views/tab-send.scss | 2 +- src/sass/views/walletDetails.scss | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sass/buttons.scss b/src/sass/buttons.scss index df0ce8945..886d730c7 100644 --- a/src/sass/buttons.scss +++ b/src/sass/buttons.scss @@ -1,5 +1,5 @@ %button-standard { - width: 85%; + width: 90%; max-width: 300px; margin-left: auto; margin-right: auto; diff --git a/src/sass/mixins/layout.scss b/src/sass/mixins/layout.scss index 269a50320..e7a1af9da 100644 --- a/src/sass/mixins/layout.scss +++ b/src/sass/mixins/layout.scss @@ -59,6 +59,8 @@ .button { font-weight: bold; font-size: 19px; + line-height: 26px; + padding: 8px 6px; } } .button-first-contact img { diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index 55080ab7b..f9ace4655 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -83,14 +83,14 @@ .button { border: 2px solid; border-radius: 47px; - padding: 0 15px 0 15px; + padding: 8px 2px 8px 2px; text-align: center; width: 100%; max-width: 300px; font-size: 19px; font-weight: bolder; min-height: auto; - line-height: 36px; + line-height: 19px; } } .wallet-coin-logo { diff --git a/src/sass/views/tab-send.scss b/src/sass/views/tab-send.scss index bf77984fd..564e90d66 100644 --- a/src/sass/views/tab-send.scss +++ b/src/sass/views/tab-send.scss @@ -105,7 +105,7 @@ width: auto; margin: 2px 0 4px; } - height: 60px; + min-height: 65px; line-height: 16px; margin-right: 0px; width: 95%; diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 858229d85..5c235ad97 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -207,14 +207,14 @@ .button { border: 2px solid; border-radius: 47px; - padding: 0 15px 0 15px; + padding: 6px 2px 6px 2px; text-align: center; width: 100%; max-width: 300px; font-size: 19px; font-weight: bolder; min-height: auto; - line-height: 36px; + line-height: 19px; } } } From 52f18c3c9bfe34e9f374aceaa843f3f89e78b8ed Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 4 Sep 2018 09:13:09 +1200 Subject: [PATCH 034/118] Got the Enter Amount screen unit tests running. --- src/js/controllers/amount.spec.js | 34 ++++++++++++++++++++++--------- test/karma.conf.js | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js index ed64da836..00d82e520 100644 --- a/src/js/controllers/amount.spec.js +++ b/src/js/controllers/amount.spec.js @@ -7,6 +7,9 @@ describe('amountController', function(){ platformInfo, profileService, rateService, + sendFlowService, + shapeshiftService, + txFormatService, $stateParams; @@ -39,9 +42,12 @@ describe('amountController', function(){ isIos: true }; - profileService = jasmine.createSpyObj(['getWallets']); + profileService = jasmine.createSpyObj(['getWallet', 'getWallets']); rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']); + sendFlowService = jasmine.createSpyObj(['getStateClone']); + shapeshiftService = jasmine.createSpyObj(['shiftIt']); + txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']); $stateParams = {}; @@ -61,6 +67,11 @@ describe('amountController', function(){ stateName: 'ignoreme' }; $ionicHistory.backView.and.returnValue(backView); + + var wallet = { + + }; + profileService.getWallet.and.returnValue(wallet); profileService.getWallets.and.returnValue([{}]); rateService.fromFiat.and.returnValue(12); // satoshis or coins? @@ -80,22 +91,25 @@ describe('amountController', function(){ popupService: {}, rateService: rateService, $scope: $scope, + sendFlowService: sendFlowService, + shapeshiftService: shapeshiftService, $state: {}, $stateParams: $stateParams, - txFormatService: {}, + txFormatService: txFormatService, walletService: {} }); - var data = { - stateParams: { - fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b', - toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s' - } + var sendFlowState = { + fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b', + toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s' }; - $scope.$emit('$ionicView.beforeEnter', data); - expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b'); - expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'); + sendFlowService.getStateClone.and.returnValue(sendFlowState); + + $scope.$emit('$ionicView.beforeEnter', {}); + + //expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b'); + //expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'); }); }); \ No newline at end of file diff --git a/test/karma.conf.js b/test/karma.conf.js index b4f64af73..22efcd1c8 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -17,7 +17,7 @@ module.exports = function(config) { files: [ 'node_modules/angular/angular.js', - 'bitanalytics/bitanalytics-0.1.0.js', + 'bitanalytics/bitanalytics.js', // From Gruntfile.js 'bower_components/qrcode-generator/js/qrcode.js', From b2178c84e31aa5a9b09b89057250826be310beec Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 4 Sep 2018 11:02:52 +1200 Subject: [PATCH 035/118] When available funds are higher than Shapeshift max limit, the send max button changes to send max limit. --- src/js/controllers/amount.js | 29 ++++---- src/js/controllers/amount.spec.js | 117 +++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 17 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 60912e42b..05c4b9d8b 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -81,11 +81,12 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (data.direction == "back") { sendFlowService.popState(); } - console.log('amount onBeforeEnter after back sendflow ', sendFlowService.state); + //console.log('amount onBeforeEnter after back sendflow ', sendFlowService.state); initCurrencies(); passthroughParams = sendFlowService.getStateClone(); + console.log('sendflow Amount on BeforeEnter after back', passthroughParams); vm.fromWalletId = passthroughParams.fromWalletId; vm.toWalletId = passthroughParams.toWalletId; @@ -95,6 +96,14 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId; vm.showSendMaxButton = !vm.isRequestingSpecificAmount; + var config = configService.getSync().wallet.settings; + unitToSatoshi = config.unitToSatoshi; + satToUnit = 1 / unitToSatoshi; + unitDecimals = config.unitDecimals; + + setAvailableUnits(); + updateUnitUI(); + if (passthroughParams.thirdParty) { vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object if (vm.thirdParty) { @@ -102,12 +111,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } } - - - var config = configService.getSync().wallet.settings; - - setAvailableUnits(); - updateUnitUI(); var reNr = /^[1234567890\.]$/; var reOp = /^[\*\+\-\/]$/; @@ -136,10 +139,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, }); } - unitToSatoshi = config.unitToSatoshi; - satToUnit = 1 / unitToSatoshi; - unitDecimals = config.unitDecimals; - + resetAmount(); processAmount(); @@ -220,9 +220,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, function initForShapeshift() { if (vm.thirdParty.id === 'shapeshift') { - if (!vm.thirdParty.data) { - vm.thirdParty.data = {}; - } + vm.thirdParty.data = vm.thirdParty.data || {}; + vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; vm.fromWallet = profileService.getWallet(vm.fromWalletId); @@ -232,6 +231,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.showSendLimitMaxButton = false; vm.canSendAllAvailableFunds = false; + shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); @@ -239,6 +239,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, setMaximumButtonFromWallet(vm.fromWallet); }); + } } diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js index 00d82e520..d198f3591 100644 --- a/src/js/controllers/amount.spec.js +++ b/src/js/controllers/amount.spec.js @@ -10,6 +10,7 @@ describe('amountController', function(){ sendFlowService, shapeshiftService, txFormatService, + $scope, $stateParams; @@ -21,7 +22,7 @@ describe('amountController', function(){ configCache = { wallet: { settings: { - + unitToSatoshi: 100000000 } } }; @@ -44,9 +45,9 @@ describe('amountController', function(){ profileService = jasmine.createSpyObj(['getWallet', 'getWallets']); - rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']); + rateService = jasmine.createSpyObj(['fromFiat', 'listAlternatives', 'updateRates', 'whenAvailable']); sendFlowService = jasmine.createSpyObj(['getStateClone']); - shapeshiftService = jasmine.createSpyObj(['shiftIt']); + shapeshiftService = jasmine.createSpyObj(['getMarketData']); txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']); $stateParams = {}; @@ -112,4 +113,114 @@ describe('amountController', function(){ //expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'); }); + describe('Shapeshift', function() { + var walletFrom; + var walletTo; + + beforeEach(function(){ + walletFrom = {}; + walletTo = {}; + + profileService.getWallet.and.callFake(function(walletId){ + if (walletId === '4cd7673e-7320-4dfa-86e5-d4edb51d460a') { + return walletFrom; + } else if (walletId === 'bf00af8f-0788-4b57-b30a-0390747407e9') { + return walletTo; + } else { + return null; + } + }); + + rateService.listAlternatives.and.returnValue([ + {name: "Australian Dollar", isoCode: "AUD"}, + {name: "United States Dollar", isoCode: "USD"} + ]); + + }); + + fit ('with available balance higher than max does not allow sendMax', function() { + + walletFrom.coin = 'btc'; + walletFrom.status = { + isValid: true, + spendableAmount: 123456789 + }; + walletTo.coin = 'bch'; + + profileService.getWallets.and.returnValue([{}]); + rateService.fromFiat.and.returnValue(12); // satoshis or coins? + + var $scope = $rootScope.$new(); + + var amountController = $controller('amountController', { + configService: configService, + gettextCatalog: {}, + $ionicHistory: $ionicHistory, + $ionicModal: {}, + $ionicScrollDelegate: {}, + nodeWebkitService: {}, + ongoingProcess: {}, + platformInfo: platformInfo, + profileService: profileService, + popupService: {}, + rateService: rateService, + $scope: $scope, + sendFlowService: sendFlowService, + shapeshiftService: shapeshiftService, + $state: {}, + $stateParams: $stateParams, + txFormatService: txFormatService, + walletService: {} + }); + + rateService.whenAvailable.and.callFake(function(cb){ + cb(); + }); + + var sendFlowState = { + amount: '', + displayAddress: null, + fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a', + sendMax: false, + thirdParty: { + id: 'shapeshift', + data: { + fromWalletId: "4cd7673e-7320-4dfa-86e5-d4edb51d460a" + }, + }, + toAddress: '', + toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9' + }; + + sendFlowService.getStateClone.and.returnValue(sendFlowState); + + var reqCoinIn = ''; + var reqCoinOut = ''; + shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){ + reqCoinIn = coinIn; + reqCoinOut = coinOut; + cb({ + maxLimit: '0.6846239', + minimum: '0.00013692' + }); + }); + + $scope.$emit('$ionicView.beforeEnter', {}); + + expect(rateService.updateRates.calls.any()).toEqual(true); + + expect(reqCoinIn).toBe('btc'); + expect(reqCoinOut).toBe('bch'); + + expect(amountController.maxAmount).toBe(0.6846239); + expect(amountController.minAmount).toBe(0.00013692); + + expect(amountController.showSendMaxButton).toEqual(false); + expect(amountController.showSendLimitMaxButton).toEqual(true); + + + + }); + }); + }); \ No newline at end of file From 4dc3e7c2e83202080d8762f3ff22834e6bed3858 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 4 Sep 2018 12:21:57 +1200 Subject: [PATCH 036/118] Enter Amount displaying ongoing progress indicator when contacting Shapeshift. Send max button now displays max limit amount when available funds are above the Shapeshift limit. --- src/js/controllers/amount.js | 9 +++++---- www/views/amount.html | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 05c4b9d8b..ff4ec3628 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) { +function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, profileService, walletService, $window) { var vm = this; vm.allowSend = false; @@ -231,8 +231,9 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.showSendLimitMaxButton = false; vm.canSendAllAvailableFunds = false; - - shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { + ongoingProcess.set('connectingShapeshift', true); + shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(data) { + ongoingProcess.set('connectingShapeshift', false); vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); @@ -740,7 +741,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, console.log('sendmax Setting max button text'); transactionSendableAmount.crypto = txFormatService.formatAmountStr(wallet.coin, transactionSendableAmount.satoshis); vm.sendableFunds = transactionSendableAmount.crypto; - + if (availableUnits[unitIndex].isFiat) { txFormatService.formatAlternativeStr(wallet.coin, transactionSendableAmount.satoshis, function onFormat(formatted){ if (formatted) { diff --git a/www/views/amount.html b/www/views/amount.html index 114292026..51397f3bf 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -53,11 +53,11 @@
-
+
From 4315d16f7333b6320e46697be3b02121e34c749d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 4 Sep 2018 12:52:54 +1200 Subject: [PATCH 037/118] The Enter Amount screen now correctly sets the amount when sendMax() is called when available funds exceed the max limit. --- src/js/controllers/amount.js | 6 ++--- src/js/controllers/amount.spec.js | 31 +++++++++++++++++++++---- src/js/controllers/review.controller.js | 8 +++++-- src/js/services/sendFlowService.js | 4 ++-- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index ff4ec3628..8d1f65058 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -269,7 +269,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, altUnitIndex = unitIndex; unitIndex = tempIndex; } - vm.amount = (transactionSendableAmount.satoshis * unitToSatoshi).toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); + vm.amount = (transactionSendableAmount.satoshis * satToUnit).toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); } finish(); } @@ -487,9 +487,9 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var satoshis = 0; if (unit.isFiat) { - satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0); + satoshis = Math.floor(fromFiat(uiAmount) * unitToSatoshi); } else { - satoshis = (uiAmount * unitToSatoshi).toFixed(0); + satoshis = Math.floor(uiAmount * unitToSatoshi); } var confirmData = { diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js index d198f3591..e4083611b 100644 --- a/src/js/controllers/amount.spec.js +++ b/src/js/controllers/amount.spec.js @@ -4,6 +4,7 @@ describe('amountController', function(){ $controller, $ionicHistory, $rootScope, + ongoingProcess, platformInfo, profileService, rateService, @@ -11,6 +12,7 @@ describe('amountController', function(){ shapeshiftService, txFormatService, $scope, + $state, $stateParams; @@ -37,6 +39,8 @@ describe('amountController', function(){ $ionicHistory = jasmine.createSpyObj(['backView']); + ongoingProcess = jasmine.createSpyObj(['set']); + platformInfo = { isChromeApp: false, isAndroid: false, @@ -46,10 +50,10 @@ describe('amountController', function(){ profileService = jasmine.createSpyObj(['getWallet', 'getWallets']); rateService = jasmine.createSpyObj(['fromFiat', 'listAlternatives', 'updateRates', 'whenAvailable']); - sendFlowService = jasmine.createSpyObj(['getStateClone']); + sendFlowService = jasmine.createSpyObj(['getStateClone', 'pushState']); shapeshiftService = jasmine.createSpyObj(['getMarketData']); txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']); - + $state = jasmine.createSpyObj(['transitionTo']); $stateParams = {}; inject(function(_$controller_, _$rootScope_){ @@ -86,7 +90,7 @@ describe('amountController', function(){ $ionicModal: {}, $ionicScrollDelegate: {}, nodeWebkitService: {}, - ongoingProcess: {}, + ongoingProcess: ongoingProcess, platformInfo: platformInfo, profileService: profileService, popupService: {}, @@ -159,7 +163,7 @@ describe('amountController', function(){ $ionicModal: {}, $ionicScrollDelegate: {}, nodeWebkitService: {}, - ongoingProcess: {}, + ongoingProcess: ongoingProcess, platformInfo: platformInfo, profileService: profileService, popupService: {}, @@ -167,7 +171,7 @@ describe('amountController', function(){ $scope: $scope, sendFlowService: sendFlowService, shapeshiftService: shapeshiftService, - $state: {}, + $state: $state, $stateParams: $stateParams, txFormatService: txFormatService, walletService: {} @@ -218,8 +222,25 @@ describe('amountController', function(){ expect(amountController.showSendMaxButton).toEqual(false); expect(amountController.showSendLimitMaxButton).toEqual(true); + // Now hit the Send Max button + var pushedState = null; + sendFlowService.pushState.and.callFake(function (sendFlowState){ + pushedState = sendFlowState; + }); + amountController.sendMax(); + expect(pushedState.amount).toEqual(68462390); + expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a'); + expect(pushedState.sendMax).toEqual(false); + expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9'); + + expect(pushedState.thirdParty.id).toEqual('shapeshift'); + expect(pushedState.thirdParty.data.maxAmount).toEqual(0.6846239); + expect(pushedState.thirdParty.data.minAmount).toEqual(0.00013692); + + expect($state.transitionTo.calls.count()).toEqual(1); + expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review'); }); }); diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index b377bef58..554774252 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -78,11 +78,15 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { - console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.state); + console.log('review onBeforeEnter sendflow ', sendFlowService.state); defaults = configService.getDefaults(); sendFlowData = sendFlowService.getStateClone(); originWalletId = sendFlowData.fromWalletId; - satoshis = parseInt(sendFlowData.amount, 10); + if (typeof sendFlowData.amount === 'string') { + satoshis = parseInt(sendFlowData.amount, 10); + } else { + satoshis = sendFlowData.amount; + } toAddress = sendFlowData.toAddress; destinationWalletId = sendFlowData.toWalletId; diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index 62989b3c5..9981fdbcb 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -12,7 +12,7 @@ angular // A separate state variable so we can ensure it is cleared of everything, // even other properties added that this service does not know about. (such as "coin") state: { - amount: '', + amount: 0, displayAddress: null, fromWalletId: '', sendMax: false, @@ -42,7 +42,7 @@ angular function clearCurrent() { console.log("sendFlow clearCurrent()"); service.state = { - amount: '', + amount: 0, displayAddress: null, fromWalletId: '', sendMax: false, From d934a9241ad171889c3fc53d1ffedc54611c7612 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 4 Sep 2018 15:54:24 +0900 Subject: [PATCH 038/118] Organize the package.json and grunt file. Some optimizations and renaming. --- Gruntfile.js | 27 +++++++++++-- app-template/package-template.json | 64 +++++++++++++++++------------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 58829e2ed..b191a751f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -51,7 +51,7 @@ module.exports = function(grunt) { command: 'npm run build:ios', }, ios: { - command: 'npm run build:ios-release', + command: 'cordova prepare ios && cordova build ios --release', }, xcode: { command: 'npm run open:ios', @@ -60,7 +60,7 @@ module.exports = function(grunt) { command: 'npm run build:android', }, android: { - command: 'npm run build:android-release', + command: 'cordova prepare android && cordova build android --release', }, androidrun: { command: 'npm run run:android && npm run log:android', @@ -72,7 +72,7 @@ module.exports = function(grunt) { // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". // It looks like it simply lists all apk files starting with "android-release" so I have added that prefix to the final apk // so that its filename shows up in the log output. - command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned-bitcoin-com-wallet-<%= pkg.fullVersion %>-android.apk', + command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk', stdin: true, }, desktopsign: { @@ -357,9 +357,28 @@ module.exports = function(grunt) { grunt.registerTask('cordovaclean', ['exec:cordovaclean']); grunt.registerTask('android-debug', ['exec:androiddebug', 'exec:androidrun']); grunt.registerTask('android', ['exec:android']); - grunt.registerTask('android-release', ['prod', 'exec:android', 'exec:androidsign']); grunt.registerTask('desktopsign', ['exec:desktopsign', 'exec:desktopverify']); + // Build all + grunt.registerTask('app-release', ['mobile-release', 'desktop-release']); + + /** + * Mobile app + */ + + // Build mobile app + grunt.registerTask('mobile-release', ['ios-release', 'android-release']); + + // Build ios + grunt.registerTask('ios-release', ['prod', 'exec:ios']); + + // Build android + grunt.registerTask('android-release', ['prod', 'exec:android', 'exec:androidsign']); + + /** + * Desktop app + */ + // Build desktop grunt.registerTask('desktop-build', ['desktop-others', 'desktop-osx-dmg', 'desktop-osx-pkg']); diff --git a/app-template/package-template.json b/app-template/package-template.json index f7c067c85..7b8e2955c 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -88,45 +88,55 @@ }, "scripts": { "postinstall": "bower install", + "watch": "grunt watch", + "log:android": "adb logcat | grep chromium", + "test": "karma start test/karma.conf.js --single-run", + "clean": "trash platforms && trash plugins && cordova prepare", + "unstage-package": "git reset package.json", + "clean-all": "git clean -dfx", + + "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare", + "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare", + "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", + "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0", "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"", - "start:ios": "npm run build:www && npm run build:ios && npm run open:ios", - "start:android": "npm run build:www && npm run build:android && npm run run:android", + "start:ios": "npm run build:www && npm run build:ios", + "start:android": "npm run build:www && npm run build:android", "start:windows": "npm run build:www && npm run build:windows", "start:desktop": "npm start", - "watch": "grunt watch", + + "open:ios": "open platforms/ios/*.xcodeproj", + "open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android", + + "run:android": "cordova run android --device", + "run:android-release": "cordova run android --device --release", + "build:www": "grunt", "build:www-release": "grunt prod", "build:ios": "cordova prepare ios && cordova build ios --debug", "build:android": "cordova prepare android && cordova build android --debug", "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"", - "build:ios-release": "cordova prepare ios && cordova build ios --release", - "build:android-release": "cordova prepare android && cordova build android --release", "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"", - "build:desktop-release": "grunt desktop-release", + + "build:ios-release": "grunt ios-release", + "build:android-release": "grunt android-release", + "build:desktop-release": "grunt desktop-release", "build:desktop": "grunt desktop-build", - "build:osx-pkg": "grunt desktop-osx-pkg", - "build:osx-dmg": "grunt desktop-osx-dmg", - "build:others": "grunt desktop-others", - "sign:desktop": "grunt desktop-sign", - "open:ios": "open platforms/ios/*.xcodeproj", - "open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android", + "build:osx-pkg": "grunt desktop-osx-pkg", + "build:osx-dmg": "grunt desktop-osx-dmg", + "build:others": "grunt desktop-others", + + "sign:desktop": "grunt desktop-sign", + "sign:android": "grunt exec:androidsign", + "final:www": "npm run build:www-release", - "final:ios": "npm run final:www && npm run build:ios-release && npm run open:ios", - "final:android": "npm run final:www && npm run build:android-release && npm run sign:android && npm run run:android-release", - "final:windows": "npm run final:www && npm run build:windows-release", - "final:desktop": "npm run final:www && npm run build:desktop-release", - "run:android": "cordova run android --device", - "run:android-release": "cordova run android --device --release", - "log:android": "adb logcat | grep chromium", - "sign:android": "grunt android-release", - "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare", - "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare", - "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", - "test": "karma start test/karma.conf.js --single-run", - "clean": "trash platforms && trash plugins && cordova prepare", - "unstage-package": "git reset package.json", - "clean-all": "git clean -dfx" + "final:ios": "npm run build:ios-release", + "final:android": "npm run build:android-release", + "final:windows": "npm run build:windows-release", + "final:desktop": "npm run build:desktop-release", + "final:mobile": "npm run build:mobile-release", + "final:app": "npm run build:app-release" }, "devDependencies": { "cordova": "^6.3.1", From 8b505141d7e0e2de91eb93c2728d373c98902cda Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 4 Sep 2018 16:45:52 +0900 Subject: [PATCH 039/118] renaming, clean up --- Gruntfile.js | 73 ++++++++++++++---------------- app-template/package-template.json | 40 ++++++++-------- 2 files changed, 51 insertions(+), 62 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b191a751f..7aa700729 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -47,43 +47,37 @@ module.exports = function(grunt) { wpcopy: { command: 'make -C cordova wp-copy', }, - iosdebug: { - command: 'npm run build:ios', - }, - ios: { - command: 'cordova prepare ios && cordova build ios --release', - }, xcode: { command: 'npm run open:ios', }, - androiddebug: { - command: 'npm run build:android', + build_ios_debug: { + command: 'npm run build:ios', }, - android: { + build_ios_release: { + command: 'cordova prepare ios && cordova build ios --release', + }, + android_studio: { + command: ' open -a open -a /Applications/Android\\ Studio.app platforms/android', + }, + build_android_debug: { + command: 'cordova prepare android && cordova build android --debug', + }, + build_android_release: { command: 'cordova prepare android && cordova build android --release', }, - androidrun: { - command: 'npm run run:android && npm run log:android', + run_android: { + command: 'cordova run android --device && npm run log:android', }, - androidbuild: { - command: 'cd cordova/project && cordova build android --release', + log_android: { + command: 'adb logcat | grep chromium', }, - androidsign: { + sign_android: { // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". // It looks like it simply lists all apk files starting with "android-release" so I have added that prefix to the final apk // so that its filename shows up in the log output. command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk', stdin: true, - }, - desktopsign: { - cmd: 'gpg -u E0AE67E7 --output webkitbuilds/others/<%= pkg.title %>-linux.zip.sig --detach-sig webkitbuilds/others/<%= pkg.title %>-linux.zip ; gpg -u E0AE67E7 --output webkitbuilds/others/<%= pkg.title %>.exe.sig --detach-sig webkitbuilds/others/<%= pkg.title %>.exe' - }, - desktopverify: { - cmd: 'gpg --verify webkitbuilds/<%= pkg.title %>-linux.zip.sig webkitbuilds/<%= pkg.title %>-linux.zip; gpg --verify webkitbuilds/<%= pkg.title %>.exe.sig webkitbuilds/<%= pkg.title %>.exe' - }, - osxsign: { - cmd: 'gpg -u E0AE67E7 --output webkitbuilds/<%= pkg.title %>.dmg.sig --detach-sig webkitbuilds/<%= pkg.title %>.dmg' - }, + } }, watch: { options: { @@ -351,49 +345,48 @@ module.exports = function(grunt) { grunt.registerTask('wp', ['prod', 'exec:wp']); grunt.registerTask('wp-copy', ['default', 'exec:wpcopy']); grunt.registerTask('wp-init', ['default', 'exec:wpinit']); - grunt.registerTask('ios', ['exec:ios']); - grunt.registerTask('ios-debug', ['exec:iosdebug']); - grunt.registerTask('ios-run', ['exec:xcode']); grunt.registerTask('cordovaclean', ['exec:cordovaclean']); - grunt.registerTask('android-debug', ['exec:androiddebug', 'exec:androidrun']); - grunt.registerTask('android', ['exec:android']); - grunt.registerTask('desktopsign', ['exec:desktopsign', 'exec:desktopverify']); // Build all - grunt.registerTask('app-release', ['mobile-release', 'desktop-release']); + grunt.registerTask('build-app-release', ['build-mobile-release', 'build-desktop-release']); /** * Mobile app */ // Build mobile app - grunt.registerTask('mobile-release', ['ios-release', 'android-release']); + grunt.registerTask('build-mobile-release', ['build-ios-release', 'build-android-release']); // Build ios - grunt.registerTask('ios-release', ['prod', 'exec:ios']); + grunt.registerTask('start-ios', ['ios-debug', 'exec:xcode']); + grunt.registerTask('build-ios-debug', ['exec:build_ios_debug']); + grunt.registerTask('build-ios-release', ['prod', 'exec:build_ios_release']); // Build android - grunt.registerTask('android-release', ['prod', 'exec:android', 'exec:androidsign']); + grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android', 'exec:log_android']); + grunt.registerTask('build-android-debug', ['exec:build_android_debug']); + grunt.registerTask('build-android-release', ['prod', 'exec:build_android_release', 'sign-android']); + grunt.registerTask('sign-android', ['exec:sign_android']); /** * Desktop app */ // Build desktop - grunt.registerTask('desktop-build', ['desktop-others', 'desktop-osx-dmg', 'desktop-osx-pkg']); + grunt.registerTask('build-desktop', ['build-desktop-others', 'build-desktop-osx-dmg', 'build-desktop-osx-pkg']); // Build desktop win64 & linux64 - grunt.registerTask('desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']); + grunt.registerTask('build-desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']); // Build desktop osx pkg - grunt.registerTask('desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']); + grunt.registerTask('build-desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']); // Build desktop osx dmg - grunt.registerTask('desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']); + grunt.registerTask('build-desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']); // Sign desktop - grunt.registerTask('desktop-sign', ['exec:sign_desktop_dist']); + grunt.registerTask('sign-desktop', ['exec:sign_desktop_dist']); // Release desktop - grunt.registerTask('desktop-release', ['desktop-build', 'desktop-sign']); + grunt.registerTask('build-desktop-release', ['build-desktop', 'sign-desktop']); }; diff --git a/app-template/package-template.json b/app-template/package-template.json index 7b8e2955c..4d6082b26 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -95,46 +95,42 @@ "unstage-package": "git reset package.json", "clean-all": "git clean -dfx", - "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare", - "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare", "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0", "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"", - "start:ios": "npm run build:www && npm run build:ios", - "start:android": "npm run build:www && npm run build:android", "start:windows": "npm run build:www && npm run build:windows", - "start:desktop": "npm start", - "open:ios": "open platforms/ios/*.xcodeproj", - "open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android", + "open:ios": "grunt exec:xcode", + "open:android": "grunt exec:android_studio", - "run:android": "cordova run android --device", - "run:android-release": "cordova run android --device --release", - "build:www": "grunt", "build:www-release": "grunt prod", - "build:ios": "cordova prepare ios && cordova build ios --debug", - "build:android": "cordova prepare android && cordova build android --debug", "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"", "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"", - "build:ios-release": "grunt ios-release", - "build:android-release": "grunt android-release", - "build:desktop-release": "grunt desktop-release", - "build:desktop": "grunt desktop-build", - "build:osx-pkg": "grunt desktop-osx-pkg", - "build:osx-dmg": "grunt desktop-osx-dmg", - "build:others": "grunt desktop-others", + "start:ios": "grunt start-ios", + "build:ios-debug": "grunt build-ios-debug", + "build:ios-release": "grunt build-ios-release", + + "start:android": "grunt start-android", + "build:android-debug": "grunt build-android-debug", + "build:android-release": "grunt build-android-release", + + "build:desktop-release": "grunt build-desktop-release", + "build:desktop": "grunt build-desktop", + "build:osx-pkg": "grunt build-desktop-osx-pkg", + "build:osx-dmg": "grunt build-desktop-osx-dmg", + "build:others": "grunt build-desktop-others", - "sign:desktop": "grunt desktop-sign", - "sign:android": "grunt exec:androidsign", + "sign:desktop": "grunt sign-desktop", + "sign:android": "grunt sign-android", "final:www": "npm run build:www-release", "final:ios": "npm run build:ios-release", "final:android": "npm run build:android-release", "final:windows": "npm run build:windows-release", - "final:desktop": "npm run build:desktop-release", + "final:desktop": "npm run build:build-desktop-release", "final:mobile": "npm run build:mobile-release", "final:app": "npm run build:app-release" }, From 3ab535a36be601eb5beedb51d6d4b2280080fa18 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 4 Sep 2018 19:47:50 +1200 Subject: [PATCH 040/118] Improved handling of available funds below min amount. --- src/js/controllers/amount.js | 44 +++--- src/js/controllers/amount.spec.js | 216 ++++++++++++++++++++++++++++-- 2 files changed, 236 insertions(+), 24 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 8d1f65058..2d5a0aa66 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,9 +2,10 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, profileService, walletService, $window) { +function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, popupService, profileService, walletService, $window) { var vm = this; + // Variables vm.allowSend = false; vm.altCurrencyList = []; vm.alternativeAmount = ''; @@ -12,6 +13,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.amount = '0'; vm.availableFunds = ''; vm.canSendAllAvailableFunds = true; + vm.errorMessage = ''; // Use insufficient for logic, as when the amount is invalid, funds being // either sufficent or insufficient doesn't make sense. vm.fundsAreInsufficient = false; @@ -21,6 +23,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.lastUsedPopularList = []; vm.maxAmount = 0; vm.minAmount = 0; + vm.sendableFunds = ''; vm.showSendMaxButton = false; vm.showSendLimitMaxButton = false; vm.thirdParty = false; @@ -38,9 +41,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.pushDigit = pushDigit; vm.removeDigit = removeDigit; vm.save = save; - vm.sendableFunds = ''; vm.sendMax = sendMax; - vm.errorMessage = ''; + $scope.$on('$ionicView.beforeEnter', onBeforeEnter); $scope.$on('$ionicView.leave', onLeave); @@ -219,11 +221,10 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function initForShapeshift() { + console.log('initForShapeshift()'); if (vm.thirdParty.id === 'shapeshift') { vm.thirdParty.data = vm.thirdParty.data || {}; - vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; - vm.fromWallet = profileService.getWallet(vm.fromWalletId); vm.toWallet = profileService.getWallet(vm.toWalletId); @@ -233,6 +234,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, ongoingProcess.set('connectingShapeshift', true); shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(data) { + console.log('sendmax onMarketData()'); ongoingProcess.set('connectingShapeshift', false); vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); @@ -261,17 +263,27 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, function sendMax() { if (canSendMax) { useSendMax = true; - + finish(); + } else { - // Need to be precise, so use crypto directly rather than fiat with exchange rate - if (availableUnits[unitIndex].isFiat) { - var tempIndex = altUnitIndex; - altUnitIndex = unitIndex; - unitIndex = tempIndex; + var transactionSendableAmountInUnits = transactionSendableAmount.satoshis * satToUnit; + if (vm.minAmount && transactionSendableAmountInUnits < vm.minAmount) { + popupService.showAlert( + gettextCatalog.getString('Insufficient funds'), + gettextCatalog.getString('Amount below minimum allowed') + ); + } else { + // Need to be precise, so use crypto directly rather than fiat with exchange rate + if (availableUnits[unitIndex].isFiat) { + var tempIndex = altUnitIndex; + altUnitIndex = unitIndex; + unitIndex = tempIndex; + } + vm.amount = transactionSendableAmountInUnits.toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); + finish(); + } - vm.amount = (transactionSendableAmount.satoshis * satToUnit).toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); } - finish(); } function updateUnitUI() { @@ -647,6 +659,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function updateAvailableFundsFromWallet(wallet) { + console.log('amount updateAvailableFundsFromWallet()'); var availableFundsInFiat = ''; if (wallet.status && wallet.status.isValid) { walletSpendableAmount.crypto = wallet.status.spendableBalanceStr; @@ -702,15 +715,16 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function setMaximumButtonFromWallet(wallet) { + console.log('sendmax setMaximumButtonFromWallet()'); var minSatoshis = vm.minAmount * unitToSatoshi; var maxSatoshis = vm.maxAmount * unitToSatoshi; if (minSatoshis > walletSpendableAmount.satoshis) { console.log('sendmax Hiding max buttons as minimum is too high.'); canSendMax = false; - vm.showSendMaxButton = false; + vm.showSendMaxButton = true; vm.showSendLimitMaxButton = false; - transactionSendableAmount.satoshis = 0; + transactionSendableAmount.satoshis = walletSpendableAmount.satoshis; } else if (maxSatoshis) { if (walletSpendableAmount.satoshis > maxSatoshis) { diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js index e4083611b..3da170f3b 100644 --- a/src/js/controllers/amount.spec.js +++ b/src/js/controllers/amount.spec.js @@ -1,11 +1,13 @@ describe('amountController', function(){ var configCache, - configService, + configService, + gettextCatalog, $controller, $ionicHistory, $rootScope, ongoingProcess, platformInfo, + popupService, profileService, rateService, sendFlowService, @@ -37,6 +39,8 @@ describe('amountController', function(){ }); configService.getSync.and.returnValue(configCache); + gettextCatalog = jasmine.createSpyObj(['getString']); + gettextCatalog.getString.and.callFake(function(str){ return str; }); $ionicHistory = jasmine.createSpyObj(['backView']); ongoingProcess = jasmine.createSpyObj(['set']); @@ -46,13 +50,21 @@ describe('amountController', function(){ isAndroid: false, isIos: true }; - + popupService = jasmine.createSpyObj(['showAlert']); profileService = jasmine.createSpyObj(['getWallet', 'getWallets']); rateService = jasmine.createSpyObj(['fromFiat', 'listAlternatives', 'updateRates', 'whenAvailable']); sendFlowService = jasmine.createSpyObj(['getStateClone', 'pushState']); shapeshiftService = jasmine.createSpyObj(['getMarketData']); txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']); + txFormatService.formatAlternativeStr.and.callFake(function(coin, satoshis, cb) { + var units = satoshis / 100000000; + var formatted = (units * 10000).toFixed(2) + ' USD'; + cb(formatted); + }); + txFormatService.formatAmountStr.and.callFake(function(coin, satoshis) { + return (satoshis * 100000000).toFixed(8) + ' ' + (coin || 'bch').toUpperCase(); + }); $state = jasmine.createSpyObj(['transitionTo']); $stateParams = {}; @@ -74,7 +86,7 @@ describe('amountController', function(){ $ionicHistory.backView.and.returnValue(backView); var wallet = { - + }; profileService.getWallet.and.returnValue(wallet); profileService.getWallets.and.returnValue([{}]); @@ -85,7 +97,7 @@ describe('amountController', function(){ var amountController = $controller('amountController', { configService: configService, - gettextCatalog: {}, + gettextCatalog: gettextCatalog, $ionicHistory: $ionicHistory, $ionicModal: {}, $ionicScrollDelegate: {}, @@ -93,7 +105,7 @@ describe('amountController', function(){ ongoingProcess: ongoingProcess, platformInfo: platformInfo, profileService: profileService, - popupService: {}, + popupService: popupService, rateService: rateService, $scope: $scope, sendFlowService: sendFlowService, @@ -142,7 +154,195 @@ describe('amountController', function(){ }); - fit ('with available balance higher than max does not allow sendMax', function() { + it ('with available balance below limit, shows sendMax for triggering alert', function() { + + walletFrom.coin = 'btc'; + walletFrom.status = { + isValid: true, + spendableAmount: 789 + }; + walletTo.coin = 'bch'; + + profileService.getWallets.and.returnValue([{}]); + rateService.fromFiat.and.returnValue(12); + + var $scope = $rootScope.$new(); + + var amountController = $controller('amountController', { + configService: configService, + gettextCatalog: gettextCatalog, + $ionicHistory: $ionicHistory, + $ionicModal: {}, + $ionicScrollDelegate: {}, + nodeWebkitService: {}, + ongoingProcess: ongoingProcess, + platformInfo: platformInfo, + profileService: profileService, + popupService: popupService, + rateService: rateService, + $scope: $scope, + sendFlowService: sendFlowService, + shapeshiftService: shapeshiftService, + $state: $state, + $stateParams: $stateParams, + txFormatService: txFormatService, + walletService: {} + }); + + rateService.whenAvailable.and.callFake(function(cb){ + cb(); + }); + + var sendFlowState = { + amount: '', + displayAddress: null, + fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a', + sendMax: false, + thirdParty: { + id: 'shapeshift', + data: {}, + }, + toAddress: '', + toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9' + }; + + sendFlowService.getStateClone.and.returnValue(sendFlowState); + + var reqCoinIn = ''; + var reqCoinOut = ''; + shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){ + reqCoinIn = coinIn; + reqCoinOut = coinOut; + cb({ + maxLimit: '0.6846239', + minimum: '0.00013692' + }); + }); + + $scope.$emit('$ionicView.beforeEnter', {}); + + expect(rateService.updateRates.calls.any()).toEqual(true); + + expect(reqCoinIn).toBe('btc'); + expect(reqCoinOut).toBe('bch'); + + expect(amountController.maxAmount).toBe(0.68462390); + expect(amountController.minAmount).toBe(0.00013692); + + expect(amountController.showSendMaxButton).toEqual(true); + expect(amountController.showSendLimitMaxButton).toEqual(false); + + expect(amountController.sendableFunds).toEqual('0.08 USD'); + + // Now hit the Send Max button + amountController.sendMax(); + + expect(popupService.showAlert.calls.argsFor(0)[0]).toEqual('Insufficient funds'); + expect(popupService.showAlert.calls.argsFor(0)[1]).toEqual('Amount below minimum allowed'); + expect(sendFlowService.pushState.calls.any()).toEqual(false); + expect($state.transitionTo.calls.any()).toEqual(false); + }); + + it ('with available balance between limits, uses sendMax', function() { + + walletFrom.coin = 'btc'; + walletFrom.status = { + isValid: true, + spendableAmount: 456789 + }; + walletTo.coin = 'bch'; + + profileService.getWallets.and.returnValue([{}]); + rateService.fromFiat.and.returnValue(12); + + var $scope = $rootScope.$new(); + + var amountController = $controller('amountController', { + configService: configService, + gettextCatalog: {}, + $ionicHistory: $ionicHistory, + $ionicModal: {}, + $ionicScrollDelegate: {}, + nodeWebkitService: {}, + ongoingProcess: ongoingProcess, + platformInfo: platformInfo, + profileService: profileService, + popupService: {}, + rateService: rateService, + $scope: $scope, + sendFlowService: sendFlowService, + shapeshiftService: shapeshiftService, + $state: $state, + $stateParams: $stateParams, + txFormatService: txFormatService, + walletService: {} + }); + + rateService.whenAvailable.and.callFake(function(cb){ + cb(); + }); + + var sendFlowState = { + amount: '', + displayAddress: null, + fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a', + sendMax: false, + thirdParty: { + id: 'shapeshift', + data: {}, + }, + toAddress: '', + toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9' + }; + + sendFlowService.getStateClone.and.returnValue(sendFlowState); + + var reqCoinIn = ''; + var reqCoinOut = ''; + shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){ + reqCoinIn = coinIn; + reqCoinOut = coinOut; + cb({ + maxLimit: '0.6846239', + minimum: '0.00013692' + }); + }); + + $scope.$emit('$ionicView.beforeEnter', {}); + + expect(rateService.updateRates.calls.any()).toEqual(true); + + expect(reqCoinIn).toBe('btc'); + expect(reqCoinOut).toBe('bch'); + + expect(amountController.maxAmount).toBe(0.68462390); + expect(amountController.minAmount).toBe(0.00013692); + + expect(amountController.showSendMaxButton).toEqual(true); + expect(amountController.showSendLimitMaxButton).toEqual(false); + + // Now hit the Send Max button + var pushedState = null; + sendFlowService.pushState.and.callFake(function (sendFlowState){ + pushedState = sendFlowState; + }); + + amountController.sendMax(); + + expect(pushedState.amount).toBeUndefined(); + expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a'); + expect(pushedState.sendMax).toEqual(true); + expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9'); + + expect(pushedState.thirdParty.id).toEqual('shapeshift'); + expect(pushedState.thirdParty.data.maxAmount).toEqual(0.6846239); + expect(pushedState.thirdParty.data.minAmount).toEqual(0.00013692); + + expect($state.transitionTo.calls.count()).toEqual(1); + expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review'); + }); + + it ('with available balance higher than max, uses send limit max instead of sendMax', function() { walletFrom.coin = 'btc'; walletFrom.status = { @@ -188,9 +388,7 @@ describe('amountController', function(){ sendMax: false, thirdParty: { id: 'shapeshift', - data: { - fromWalletId: "4cd7673e-7320-4dfa-86e5-d4edb51d460a" - }, + data: {}, }, toAddress: '', toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9' From 6c8a1cfd5a2f3e49672f07da81728524c995385b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 4 Sep 2018 20:48:16 +1200 Subject: [PATCH 041/118] Bugfix for using cached status in Enter Amount screen. --- src/js/controllers/amount.js | 2 +- src/js/controllers/amount.spec.js | 161 +++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 2d5a0aa66..d30da03ad 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -670,7 +670,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, availableFundsInFiat = ''; } - } else if (wallet.cachedStatus && wallet.status.isValid) { + } else if (wallet.cachedStatus && wallet.cachedStatus.isValid) { if (wallet.cachedStatus.alternativeBalanceAvailable) { availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode; diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js index 3da170f3b..fdef97109 100644 --- a/src/js/controllers/amount.spec.js +++ b/src/js/controllers/amount.spec.js @@ -57,14 +57,23 @@ describe('amountController', function(){ sendFlowService = jasmine.createSpyObj(['getStateClone', 'pushState']); shapeshiftService = jasmine.createSpyObj(['getMarketData']); txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']); + txFormatService.formatAlternativeStr.and.callFake(function(coin, satoshis, cb) { + if (typeof satoshis !== "number") { + throw "satoshis in formatAlternativeStr() is not a number." + } var units = satoshis / 100000000; var formatted = (units * 10000).toFixed(2) + ' USD'; cb(formatted); }); + txFormatService.formatAmountStr.and.callFake(function(coin, satoshis) { + if (typeof satoshis !== "number") { + throw "satoshis in formatAmountStr() is not a number." + } return (satoshis * 100000000).toFixed(8) + ' ' + (coin || 'bch').toUpperCase(); }); + $state = jasmine.createSpyObj(['transitionTo']); $stateParams = {}; @@ -86,7 +95,10 @@ describe('amountController', function(){ $ionicHistory.backView.and.returnValue(backView); var wallet = { - + status: { + isValid: true, + spendableAmount: 123456 + } }; profileService.getWallet.and.returnValue(wallet); profileService.getWallets.and.returnValue([{}]); @@ -129,6 +141,8 @@ describe('amountController', function(){ //expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'); }); + + describe('Shapeshift', function() { var walletFrom; var walletTo; @@ -442,4 +456,149 @@ describe('amountController', function(){ }); }); + + describe('Wallet transfer', function() { + var walletFrom; + var walletTo; + + beforeEach(function(){ + walletFrom = {}; + walletTo = {}; + + profileService.getWallet.and.callFake(function(walletId){ + if (walletId === '4cd7673e-7320-4dfa-86e5-d4edb51d460a') { + return walletFrom; + } else if (walletId === 'bf00af8f-0788-4b57-b30a-0390747407e9') { + return walletTo; + } else { + return null; + } + }); + + rateService.listAlternatives.and.returnValue([ + {name: "Australian Dollar", isoCode: "AUD"}, + {name: "United States Dollar", isoCode: "USD"} + ]); + + }); + + it('wallet transfer send max.', function() { + + walletFrom.coin = 'btc'; + walletFrom.status = { + isValid: true, + spendableAmount: 123456789 + }; + + profileService.getWallets.and.returnValue([{}]); + + var $scope = $rootScope.$new(); + + var amountController = $controller('amountController', { + configService: configService, + gettextCatalog: gettextCatalog, + $ionicHistory: $ionicHistory, + $ionicModal: {}, + $ionicScrollDelegate: {}, + nodeWebkitService: {}, + ongoingProcess: ongoingProcess, + platformInfo: platformInfo, + profileService: profileService, + popupService: popupService, + rateService: rateService, + $scope: $scope, + sendFlowService: sendFlowService, + shapeshiftService: shapeshiftService, + $state: $state, + $stateParams: $stateParams, + txFormatService: txFormatService, + walletService: {} + }); + + var sendFlowState = { + fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a', + toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9' + }; + + sendFlowService.getStateClone.and.returnValue(sendFlowState); + + $scope.$emit('$ionicView.beforeEnter', {}); + + expect(amountController.showSendMaxButton).toEqual(true); + expect(amountController.showSendLimitMaxButton).toEqual(false); + + expect(amountController.sendableFunds).toEqual('12345.68 USD'); + + // Now hit the Send Max button + var pushedState = null; + sendFlowService.pushState.and.callFake(function (sendFlowState){ + pushedState = sendFlowState; + }); + + amountController.sendMax(); + + expect(pushedState.amount).toBeUndefined(); + expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a'); + expect(pushedState.sendMax).toEqual(true); + expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9'); + + expect($state.transitionTo.calls.count()).toEqual(1); + expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review'); + }); + + + // This situation was seen in real life + it('wallet transfer with valid cached status only.', function() { + + walletFrom.coin = 'btc'; + walletFrom.status = { + isValid: false, + }; + walletFrom.cachedStatus = { + isValid: true, + spendableAmount: 5678 + }; + + profileService.getWallets.and.returnValue([{}]); + + var $scope = $rootScope.$new(); + + var amountController = $controller('amountController', { + configService: configService, + gettextCatalog: gettextCatalog, + $ionicHistory: $ionicHistory, + $ionicModal: {}, + $ionicScrollDelegate: {}, + nodeWebkitService: {}, + ongoingProcess: ongoingProcess, + platformInfo: platformInfo, + profileService: profileService, + popupService: popupService, + rateService: rateService, + $scope: $scope, + sendFlowService: sendFlowService, + shapeshiftService: shapeshiftService, + $state: $state, + $stateParams: $stateParams, + txFormatService: txFormatService, + walletService: {} + }); + + var sendFlowState = { + fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a', + toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9' + }; + + sendFlowService.getStateClone.and.returnValue(sendFlowState); + + $scope.$emit('$ionicView.beforeEnter', {}); + + expect(amountController.showSendMaxButton).toEqual(true); + expect(amountController.showSendLimitMaxButton).toEqual(false); + + expect(amountController.sendableFunds).toEqual('0.57 USD'); + }); + + }); + }); \ No newline at end of file From 793bfee6a2652a7b722c665f00f5c22b6659a56e Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 4 Sep 2018 16:36:05 +0200 Subject: [PATCH 042/118] Changed wallet details transition behaviour --- src/js/controllers/walletDetails.js | 40 +++++++++++++++++------------ src/sass/views/walletDetails.scss | 17 +++++++----- www/views/walletDetails.html | 2 +- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index ec787a5f4..f984a9209 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -283,25 +283,28 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }; var prevPos; - + $scope.txHistoryPaddingBottom = 0; function getScrollPosition() { var scrollPosition = $ionicScrollDelegate.getScrollPosition(); if (!scrollPosition) { - $window.requestAnimationFrame(function() { + $timeout(function() { getScrollPosition(); - }); + }, 25); return; } var pos = scrollPosition.top; + if (pos > 0) { + $scope.txHistoryPaddingBottom = "200px"; + } if (pos === prevPos) { - $window.requestAnimationFrame(function() { + $timeout(function() { getScrollPosition(); - }); + }, 25); return; } prevPos = pos; refreshAmountSection(pos); - }; + } function refreshAmountSection(scrollPos) { var AMOUNT_HEIGHT_BASE = 210; @@ -355,18 +358,23 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } var t = amountTop; - - $scope.altAmountOpacity = (amountHeight - 100) / 80; - $scope.buttonsOpacity = (amountHeight - 140) / 70; - $window.requestAnimationFrame(function() { - $scope.amountHeight = amountHeight + 'px'; - $scope.contentMargin = contentMargin + 'px'; - $scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)'; - $scope.$digest(); - getScrollPosition(); - }); + if (scrollPos > 50) { + contentMargin = amountHeight = 80; + $scope.altAmountOpacity = 0.01; + $scope.buttonsOpacity = 0.01; + } else { + contentMargin = amountHeight = 210; + $scope.altAmountOpacity = 1; + $scope.buttonsOpacity = 1; + } + $scope.amountHeight = amountHeight + 'px'; + $scope.contentMargin = contentMargin + 'px'; + $scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)'; + $scope.$digest(); + getScrollPosition(); } + var scrollEffectTimeout; var scrollWatcherInitialized; $scope.$on("$ionicView.enter", function(event, data) { diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 858229d85..30b5da430 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -159,6 +159,7 @@ padding-top: 0; top: 0; + transition: margin-top 0.5s ease-in-out; margin-bottom: 16px; .scroll { @@ -199,7 +200,8 @@ width: 100%; position: absolute; bottom: 20px; - + transition-delay: 0.25s; + transition: opacity 0.4s ease-in-out; >.col { padding: 5px 10px; margin-bottom: 0; @@ -219,14 +221,15 @@ } } .amount { - width: 100%; - text-align: center; - color: #fff; - height: 230px; - padding-top: 40px; - display: block; align-items: center; + color: #fff; + display: block; + height: 230px; justify-content: center; + padding-top: 40px; + text-align: center; + transition: height 0.5s ease-in-out; + width: 100%; &__balance { -webkit-transform: scale3d(1, 1, 1) translateY(45px); diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 6e05862aa..2341adcba 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -300,7 +300,7 @@ {{updatingTxHistoryProgress}} transactions downloaded
-
+
From 001cd82afb1b7dac54bbd820adf203e8b19a74cf Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 4 Sep 2018 17:52:53 +0200 Subject: [PATCH 043/118] getMarketData can return an error --- src/js/controllers/amount.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index d30da03ad..24a3cc2d1 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -236,6 +236,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(data) { console.log('sendmax onMarketData()'); ongoingProcess.set('connectingShapeshift', false); + + if (data.error) { + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), typeof data.error === 'string'?data.error:(data.error.message?data.error.message:''), function () { + $ionicHistory.goBack(); + }); + } + vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); From 2121655249f7940e95cb90e152f2f225d9651bbc Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 09:48:42 +1200 Subject: [PATCH 044/118] Grouping scripts in alphabetical order so it is easier to find things, and know what is there. --- app-template/package-template.json | 64 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/app-template/package-template.json b/app-template/package-template.json index 4d6082b26..691fc0a62 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -87,44 +87,24 @@ "bitcoincashjs-fork": "^1.0.3" }, "scripts": { - "postinstall": "bower install", - "watch": "grunt watch", - "log:android": "adb logcat | grep chromium", - "test": "karma start test/karma.conf.js --single-run", - "clean": "trash platforms && trash plugins && cordova prepare", - "unstage-package": "git reset package.json", - "clean-all": "git clean -dfx", - "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", - "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0", - "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"", - "start:windows": "npm run build:www && npm run build:windows", - - "open:ios": "grunt exec:xcode", - "open:android": "grunt exec:android_studio", - + "build:android-debug": "grunt build-android-debug", + "build:android-release": "grunt build-android-release", + "build:desktop-release": "grunt build-desktop-release", + "build:desktop": "grunt build-desktop", + "build:ios-debug": "grunt build-ios-debug", + "build:ios-release": "grunt build-ios-release", + "build:osx-pkg": "grunt build-desktop-osx-pkg", + "build:osx-dmg": "grunt build-desktop-osx-dmg", + "build:others": "grunt build-desktop-others", "build:www": "grunt", "build:www-release": "grunt prod", "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"", "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"", - "start:ios": "grunt start-ios", - "build:ios-debug": "grunt build-ios-debug", - "build:ios-release": "grunt build-ios-release", - - "start:android": "grunt start-android", - "build:android-debug": "grunt build-android-debug", - "build:android-release": "grunt build-android-release", - - "build:desktop-release": "grunt build-desktop-release", - "build:desktop": "grunt build-desktop", - "build:osx-pkg": "grunt build-desktop-osx-pkg", - "build:osx-dmg": "grunt build-desktop-osx-dmg", - "build:others": "grunt build-desktop-others", - - "sign:desktop": "grunt sign-desktop", - "sign:android": "grunt sign-android", + "clean": "trash platforms && trash plugins && cordova prepare", + "clean-all": "git clean -dfx", "final:www": "npm run build:www-release", "final:ios": "npm run build:ios-release", @@ -132,7 +112,27 @@ "final:windows": "npm run build:windows-release", "final:desktop": "npm run build:build-desktop-release", "final:mobile": "npm run build:mobile-release", - "final:app": "npm run build:app-release" + "final:app": "npm run build:app-release", + + "log:android": "adb logcat | grep chromium", + + "open:android": "grunt exec:android_studio", + "open:ios": "grunt exec:xcode", + + "postinstall": "bower install", + + "sign:android": "grunt sign-android", + "sign:desktop": "grunt sign-desktop", + + "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0", + "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"", + "start:android": "grunt start-android", + "start:ios": "grunt start-ios", + "start:windows": "npm run build:www && npm run build:windows", + + "test": "karma start test/karma.conf.js --single-run", + "unstage-package": "git reset package.json", + "watch": "grunt watch" }, "devDependencies": { "cordova": "^6.3.1", From ea23b2679569ab443036210874ffcb83a3cdc336 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 09:56:19 +1200 Subject: [PATCH 045/118] Removed duplicate logging for Android, and created start:android-log. --- Gruntfile.js | 4 ++-- app-template/package-template.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 7aa700729..887f77c41 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -66,7 +66,7 @@ module.exports = function(grunt) { command: 'cordova prepare android && cordova build android --release', }, run_android: { - command: 'cordova run android --device && npm run log:android', + command: 'cordova run android --device', }, log_android: { command: 'adb logcat | grep chromium', @@ -363,7 +363,7 @@ module.exports = function(grunt) { grunt.registerTask('build-ios-release', ['prod', 'exec:build_ios_release']); // Build android - grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android', 'exec:log_android']); + grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android']); grunt.registerTask('build-android-debug', ['exec:build_android_debug']); grunt.registerTask('build-android-release', ['prod', 'exec:build_android_release', 'sign-android']); grunt.registerTask('sign-android', ['exec:sign_android']); diff --git a/app-template/package-template.json b/app-template/package-template.json index 691fc0a62..59430637a 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -127,6 +127,7 @@ "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0", "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"", "start:android": "grunt start-android", + "start:android-log": "grunt start-android && npm run log:android", "start:ios": "grunt start-ios", "start:windows": "npm run build:www && npm run build:windows", From 8dedc04fe8695de29944187314511d2dc1a895ee Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 10:03:00 +1200 Subject: [PATCH 046/118] Edited obsolete comment. --- Gruntfile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 887f77c41..22275d26b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -73,8 +73,7 @@ module.exports = function(grunt) { }, sign_android: { // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". - // It looks like it simply lists all apk files starting with "android-release" so I have added that prefix to the final apk - // so that its filename shows up in the log output. + // It looks like it simply lists all apk files starting with "android-release" command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk', stdin: true, } From ba2fa1f26ea7b301ae5a544b8cdc20b60d92bea0 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 10:33:47 +1200 Subject: [PATCH 047/118] Fixed start:ios and put Grunt tasks in alphabetical order to make it easier to find definitions. --- Gruntfile.js | 104 +++++++++++++++-------------- app-template/package-template.json | 16 ++--- 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 22275d26b..c0161b0e6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,54 +8,9 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), exec: { - get_nwjs_for_pkg: { - command: 'if [ ! -d ./cache/0.19.5-pkg/osx64/nwjs.app ]; then cd ./cache; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output nwjs.zip; unzip nwjs.zip; mkdir -p ./0.19.5-pkg/osx64; cp -R ./nwjs-mas-v0.19.5-osx-x64/nwjs.app ./0.19.5-pkg/osx64/; fi' - }, - create_others_dist: { - command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' - }, - create_dmg_dist: { - command: 'sh webkitbuilds/create-dmg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' - }, - create_pkg_dist: { - command: 'sh webkitbuilds/create-pkg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' - }, - sign_desktop_dist: { - command: 'sh webkitbuilds/sign-desktop-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>"' - }, appConfig: { command: 'node ./util/buildAppConfig.js' }, - externalServices: { - command: 'node ./util/buildExternalServices.js' - }, - clean: { - command: 'rm -Rf bower_components node_modules' - }, - cordovaclean: { - command: 'make -C cordova clean' - }, - coveralls: { - command: 'cat coverage/report-lcov/lcov.info |./node_modules/coveralls/bin/coveralls.js' - }, - chrome: { - command: 'make -C chrome-app ' - }, - wpinit: { - command: 'make -C cordova wp-init', - }, - wpcopy: { - command: 'make -C cordova wp-copy', - }, - xcode: { - command: 'npm run open:ios', - }, - build_ios_debug: { - command: 'npm run build:ios', - }, - build_ios_release: { - command: 'cordova prepare ios && cordova build ios --release', - }, android_studio: { command: ' open -a open -a /Applications/Android\\ Studio.app platforms/android', }, @@ -65,18 +20,69 @@ module.exports = function(grunt) { build_android_release: { command: 'cordova prepare android && cordova build android --release', }, - run_android: { - command: 'cordova run android --device', + build_ios_debug: { + command: 'cordova prepare ios && cordova build ios --debug', + options: { + maxBuffer: 3200 * 1024 + } + }, + build_ios_release: { + command: 'cordova prepare ios && cordova build ios --release', + options: { + maxBuffer: 1600 * 1024 + } + }, + chrome: { + command: 'make -C chrome-app ' + }, + clean: { + command: 'rm -Rf bower_components node_modules' + }, + cordovaclean: { + command: 'make -C cordova clean' + }, + coveralls: { + command: 'cat coverage/report-lcov/lcov.info |./node_modules/coveralls/bin/coveralls.js' + }, + create_dmg_dist: { + command: 'sh webkitbuilds/create-dmg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' + }, + create_others_dist: { + command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' + }, + create_pkg_dist: { + command: 'sh webkitbuilds/create-pkg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' + }, + externalServices: { + command: 'node ./util/buildExternalServices.js' + }, + get_nwjs_for_pkg: { + command: 'if [ ! -d ./cache/0.19.5-pkg/osx64/nwjs.app ]; then cd ./cache; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output nwjs.zip; unzip nwjs.zip; mkdir -p ./0.19.5-pkg/osx64; cp -R ./nwjs-mas-v0.19.5-osx-x64/nwjs.app ./0.19.5-pkg/osx64/; fi' }, log_android: { command: 'adb logcat | grep chromium', }, + run_android: { + command: 'cordova run android --device', + }, sign_android: { // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". // It looks like it simply lists all apk files starting with "android-release" command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk', stdin: true, - } + }, + sign_desktop_dist: { + command: 'sh webkitbuilds/sign-desktop-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>"' + }, + wpinit: { + command: 'make -C cordova wp-init', + }, + wpcopy: { + command: 'make -C cordova wp-copy', + }, + xcode: { + command: 'open platforms/ios/*.xcodeproj', + } }, watch: { options: { @@ -357,7 +363,7 @@ module.exports = function(grunt) { grunt.registerTask('build-mobile-release', ['build-ios-release', 'build-android-release']); // Build ios - grunt.registerTask('start-ios', ['ios-debug', 'exec:xcode']); + grunt.registerTask('start-ios', ['exec:build_ios_debug', 'exec:xcode']); grunt.registerTask('build-ios-debug', ['exec:build_ios_debug']); grunt.registerTask('build-ios-release', ['prod', 'exec:build_ios_release']); diff --git a/app-template/package-template.json b/app-template/package-template.json index 59430637a..845a8d246 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -98,21 +98,21 @@ "build:osx-pkg": "grunt build-desktop-osx-pkg", "build:osx-dmg": "grunt build-desktop-osx-dmg", "build:others": "grunt build-desktop-others", - "build:www": "grunt", - "build:www-release": "grunt prod", "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"", "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"", - + "build:www": "grunt", + "build:www-release": "grunt prod", + "clean": "trash platforms && trash plugins && cordova prepare", "clean-all": "git clean -dfx", - "final:www": "npm run build:www-release", - "final:ios": "npm run build:ios-release", "final:android": "npm run build:android-release", - "final:windows": "npm run build:windows-release", - "final:desktop": "npm run build:build-desktop-release", - "final:mobile": "npm run build:mobile-release", "final:app": "npm run build:app-release", + "final:desktop": "npm run build:build-desktop-release", + "final:ios": "npm run build:ios-release", + "final:mobile": "npm run build:mobile-release", + "final:windows": "npm run build:windows-release", + "final:www": "npm run build:www-release", "log:android": "adb logcat | grep chromium", From 8c62bc445acd6dd83f34fb3f6718ed4d05429478 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 12:37:39 +1200 Subject: [PATCH 048/118] Shapeshift item on home tab now goes to the Shapeshift website. --- src/js/controllers/servicesController.js | 6 +++++- src/js/services/servicesService.js | 2 +- www/views/includes/services.html | 9 ++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/servicesController.js b/src/js/controllers/servicesController.js index 62b13c041..0f9b6d4f8 100644 --- a/src/js/controllers/servicesController.js +++ b/src/js/controllers/servicesController.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('servicesController', function($scope, $ionicScrollDelegate, $timeout, servicesService, configService) { +angular.module('copayApp.controllers').controller('servicesController', function(externalLinkService, $scope, $ionicScrollDelegate, $timeout, servicesService, configService) { $scope.hide = false; configService.whenAvailable(function(config) { @@ -20,4 +20,8 @@ angular.module('copayApp.controllers').controller('servicesController', function }, 10); }; + $scope.open = function(url) { + externalLinkService.open(url, false); + } + }); diff --git a/src/js/services/servicesService.js b/src/js/services/servicesService.js index 316009957..ddd9db394 100644 --- a/src/js/services/servicesService.js +++ b/src/js/services/servicesService.js @@ -5,7 +5,7 @@ angular.module('copayApp.services').factory('servicesService', function(configSe name: 'shapeshift', title: 'Shapeshift', icon: 'icon-shapeshift', - sref: 'tabs.shapeshift', + href: 'https://shapeshift.io/' }]; root.register = function(serviceInfo) { diff --git a/www/views/includes/services.html b/www/views/includes/services.html index f57cd1004..05e6dcf2d 100644 --- a/www/views/includes/services.html +++ b/www/views/includes/services.html @@ -6,7 +6,14 @@
- + + +
+
+ {{service.title || service.name}} + +
+
From 5c5558fbd45080e384a9a71b712ceeab5c5e0260 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 10:28:01 +0900 Subject: [PATCH 049/118] remove final --- app-template/package-template.json | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app-template/package-template.json b/app-template/package-template.json index 845a8d246..4dbcfd767 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -89,15 +89,21 @@ "scripts": { "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", + "build:app-release": "grunt build-app-release", + "build:mobile-release": "grunt build-mobile-release", + "build:desktop-release": "grunt build-desktop-release", + "build:android-debug": "grunt build-android-debug", "build:android-release": "grunt build-android-release", - "build:desktop-release": "grunt build-desktop-release", - "build:desktop": "grunt build-desktop", + "build:ios-debug": "grunt build-ios-debug", "build:ios-release": "grunt build-ios-release", + + "build:desktop": "grunt build-desktop", "build:osx-pkg": "grunt build-desktop-osx-pkg", "build:osx-dmg": "grunt build-desktop-osx-dmg", "build:others": "grunt build-desktop-others", + "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"", "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"", "build:www": "grunt", @@ -106,14 +112,6 @@ "clean": "trash platforms && trash plugins && cordova prepare", "clean-all": "git clean -dfx", - "final:android": "npm run build:android-release", - "final:app": "npm run build:app-release", - "final:desktop": "npm run build:build-desktop-release", - "final:ios": "npm run build:ios-release", - "final:mobile": "npm run build:mobile-release", - "final:windows": "npm run build:windows-release", - "final:www": "npm run build:www-release", - "log:android": "adb logcat | grep chromium", "open:android": "grunt exec:android_studio", From 9ff9e2ea91f190f9b4fc76632a36461fd2891484 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 10:30:06 +0900 Subject: [PATCH 050/118] tab missing --- app-template/package-template.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app-template/package-template.json b/app-template/package-template.json index 4dbcfd767..200c269f6 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -90,7 +90,8 @@ "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh", "build:app-release": "grunt build-app-release", - "build:mobile-release": "grunt build-mobile-release", + + "build:mobile-release": "grunt build-mobile-release", "build:desktop-release": "grunt build-desktop-release", "build:android-debug": "grunt build-android-debug", From c40eabf807fbd31f9c288df36f67fc5b31b22da4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 10:59:09 +0900 Subject: [PATCH 051/118] Clean shapeshift service and add the correct callback --- src/js/services/shapeShiftApiService.js | 13 ++- src/js/services/shapeshift.service.js | 76 +++++++++++++ src/js/services/shapeshiftService.js | 141 ------------------------ 3 files changed, 85 insertions(+), 145 deletions(-) create mode 100644 src/js/services/shapeshift.service.js delete mode 100644 src/js/services/shapeshiftService.js diff --git a/src/js/services/shapeShiftApiService.js b/src/js/services/shapeShiftApiService.js index 210ae0fbd..411c68653 100644 --- a/src/js/services/shapeShiftApiService.js +++ b/src/js/services/shapeShiftApiService.js @@ -328,10 +328,13 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q) $scope.amount, $scope.withdrawalAddress, $scope.coinIn, $scope.coinOut ); + console.log('shapeshiftApiService.FixedAmountTx()'); console.log(fixedTx); SSA.FixedAmountTx(fixedTx, function (data) { - console.log(data) - return promise.resolve({ fixedTxData : data.success }); + console.log(data); + promise.resolve(data.success); + }, function (err) { + promise.reject(err); }); return promise.promise; }, @@ -361,8 +364,10 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q) }, ValidateAddress : function(address, coin) { var promise = $q.defer(); - SSA.ValidateAdddress(address, coin, function(data){ - promise.resolve(data); + SSA.ValidateAdddress(address, coin, function onSuccess(data){ + promise.resolve(data); + }, function onError(err) { + promise.reject(err); }); return promise.promise; } diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js new file mode 100644 index 000000000..7c1896ac7 --- /dev/null +++ b/src/js/services/shapeshift.service.js @@ -0,0 +1,76 @@ +'use strict'; + +angular.module('bitcoincom.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingDataService, platformInfo, servicesService) { + var root = {}; + root.ShiftState = 'Shift'; + root.coinIn = ''; + root.coinOut = ''; + root.withdrawalAddress = ''; + root.returnAddress = ''; + root.amount = ''; + root.marketData = {}; + + root.getMarketData = function (coinIn, coinOut, cb) { + root.coinIn = coinIn; + root.coinOut = coinOut; + if (root.coinIn !== undefined && root.coinOut !== undefined){ + shapeshiftApiService + .marketInfo(root.coinIn, root.coinOut) + .then(function (marketData) { + root.marketData = marketData; + root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); + if (cb) { + cb(marketData); + } + }); + } + }; + + root.coins = { + 'BTC': {name: 'Bitcoin', symbol: 'BTC'}, + 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'} + }; + + root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) { + ongoingProcess.set('connectingShapeshift', true); + root.withdrawalAddress = withdrawalAddress; + root.returnAddress = returnAddress; + root.coinIn = coinIn; + root.coinOut = coinOut; + shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function onSuccess(data) { + shapeshiftApiService.FixedAmountTx(root).then(function onSuccess(txData) { + if (txData.err) { + cb(txData.err); + } else { + if (!txData.orderId || !txData.deposit) { + cb(new Error('Invalid response')); + } else { + var coinPair = txData.pair.split('_'); + txData.depositType = coinPair[0].toUpperCase(); + txData.withdrawalType = coinPair[1].toUpperCase(); + coin = root.coins[txData.depositType].name.toLowerCase(); + txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount; + root.txFixedPending = true; + root.depositInfo = txData; + var shapeshiftData = { + coinIn: coinIn, + coinOut: coinOut, + toWalletId: root.toWalletId, + minAmount: root.marketData.minimum, + maxAmount: root.marketData.maxLimit, + orderId: txData.orderId, + toAddress: txData.deposit + }; + + cb(null, shapeshiftData); + } + } + }, function onError(err) { + cb(err); + }); + }, function onError(err) { + cb(err); + }); + }; + return root; +}); diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js deleted file mode 100644 index b1d2f6e7d..000000000 --- a/src/js/services/shapeshiftService.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingDataService, platformInfo, servicesService) { - var root = {}; - root.ShiftState = 'Shift'; - root.coinIn = ''; - root.coinOut = ''; - root.withdrawalAddress = ''; - root.returnAddress = ''; - root.amount = ''; - root.marketData = {}; - - root.getMarketDataIn = function (coin) { - if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn); - return root.getMarketData(coin, root.coinOut); - }; - root.getMarketDataOut = function (coin) { - if (coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn); - return root.getMarketData(root.coinIn, coin); - }; - root.getMarketData = function (coinIn, coinOut, cb) { - root.coinIn = coinIn; - root.coinOut = coinOut; - if (root.coinIn === undefined || root.coinOut === undefined) return; - shapeshiftApiService - .marketInfo(root.coinIn, root.coinOut) - .then(function (marketData) { - root.marketData = marketData; - root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); - if (cb) { - cb(marketData); - } - }); - }; - - /*shapeshiftApiService.coins().then(function(coins){ - root.coins = coins; - root.coinIn = coins['BTC'].symbol; - root.coinOut = coins['BCH'].symbol; - root.getMarketData(root.coinIn, root.coinOut); - });*/ - - root.coins = { - 'BTC': {name: 'Bitcoin', symbol: 'BTC'}, - 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'} - }; - - function checkForError(data) { - if (data.err) return true; - return false; - } - - root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) { - ongoingProcess.set('connectingShapeshift', true); - root.withdrawalAddress = withdrawalAddress; - root.returnAddress = returnAddress; - root.coinIn = coinIn; - root.coinOut = coinOut; - shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) { - var tx = ShapeShift(); - var coin; - console.log("Starting"); - tx.then(function (txData) { - console.log("Got txData", txData); - if (txData['fixedTxData']) { - txData = txData.fixedTxData; - if (checkForError(txData)) return cb(txData.err); - //console.log(txData) - var coinPair = txData.pair.split('_'); - txData.depositType = coinPair[0].toUpperCase(); - txData.withdrawalType = coinPair[1].toUpperCase(); - coin = root.coins[txData.depositType].name.toLowerCase(); - - txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount; - - root.txFixedPending = true; - - } else if (txData['normalTxData']) { - txData = txData.normalTxData; - if (checkForError(txData)) return cb(txData.err); - coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase(); - txData.depositQR = coin + ":" + txData.deposit; - } else if (txData['cancelTxData']) { - txData = txData.cancelTxData; - if (checkForError(txData)) return cb(txData.err); - if (root.txFixedPending) { - root.txFixedPending = false; - } - root.ShiftState = 'Shift'; - } - root.depositInfo = txData; - //console.log(root.marketData); - //console.log(root.depositInfo); - var sendAddress = txData.depositQR; - if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0) - sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash'); - - ongoingProcess.set('connectingShapeshift', false); - - root.ShiftState = 'Cancel'; - //root.GetStatus(); - //root.txInterval=$interval(root.GetStatus, 8000); - - var shapeshiftData = { - coinIn: coinIn, - coinOut: coinOut, - toWalletId: root.toWalletId, - minAmount: root.marketData.minimum, - maxAmount: root.marketData.maxLimit, - orderId: root.depositInfo.orderId, - toAddress: txData.deposit - }; - // - // if (incomingDataService.redir(sendAddress, 'shapeshift', shapeshiftData)) { - ongoingProcess.set('connectingShapeshift', false); - // return; - // } - cb(null, shapeshiftData); - }); - }) - }; - - function ShapeShift() { - if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root); - return shapeshiftApiService.NormalTx(root); - } - - root.GetStatus = function () { - var address = root.depositInfo.deposit - shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function (data) { - root.DepositStatus = data; - if (root.DepositStatus.status === 'complete') { - $interval.cancel(root.txInterval); - root.depositInfo = null; - root.ShiftState = 'Shift' - } - }); - }; - - return root; -}); From c553b51817f8b4c55609af6a3237f90df845aae7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 11:02:10 +0900 Subject: [PATCH 052/118] clean up --- src/js/controllers/review.controller.js | 2 +- src/js/services/shapeshift.service.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index dbf14937f..e34d90995 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -508,7 +508,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) { - if (err && err != null) { + if (err) { ongoingProcess.set('connectingShapeshift', false); popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { $ionicHistory.goBack(); diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 7c1896ac7..1efabffd4 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -32,7 +32,6 @@ angular.module('bitcoincom.services').factory('shapeshiftService', function ($ht }; root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) { - ongoingProcess.set('connectingShapeshift', true); root.withdrawalAddress = withdrawalAddress; root.returnAddress = returnAddress; root.coinIn = coinIn; From 5e034e7b8b008a04d8467232bcacbad70aa6c777 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 11:37:29 +0900 Subject: [PATCH 053/118] clean up --- src/js/services/shapeshift.service.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 1efabffd4..c9c1e428a 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -13,17 +13,15 @@ angular.module('bitcoincom.services').factory('shapeshiftService', function ($ht root.getMarketData = function (coinIn, coinOut, cb) { root.coinIn = coinIn; root.coinOut = coinOut; - if (root.coinIn !== undefined && root.coinOut !== undefined){ - shapeshiftApiService - .marketInfo(root.coinIn, root.coinOut) - .then(function (marketData) { - root.marketData = marketData; - root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); - if (cb) { - cb(marketData); - } - }); - } + shapeshiftApiService + .marketInfo(root.coinIn, root.coinOut) + .then(function (marketData) { + root.marketData = marketData; + root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); + if (cb) { + cb(marketData); + } + }); }; root.coins = { From 00e4f8ac392c0219704331bd581c02f00bf90a67 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 15:28:10 +1200 Subject: [PATCH 054/118] Hotfix version. --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index e4d6c8a84..9a155362b 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.0.2", - "fullVersion": "5.0-rc3", - "androidVersion": "500200", + "version": "5.0.3", + "fullVersion": "5.0-hotfix1", + "androidVersion": "500300", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From f7cde7a071f73967929da42baf95f3d8f10662b1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 12:45:20 +0900 Subject: [PATCH 055/118] clean up, migrate to no-fix amount --- src/js/controllers/amount.js | 7 ++- src/js/controllers/review.controller.js | 9 ++- src/js/services/shapeShiftApiService.js | 23 ++++---- src/js/services/shapeshift.service.js | 76 ++++++++++++------------- 4 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index e861b36ff..c24c567ed 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) { +function amountController(configService, $filter, gettextCatalog, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window, ongoingProcess) { var vm = this; vm.allowSend = false; @@ -74,7 +74,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, initCurrencies(); passthroughParams = sendFlowService.state.getClone(); - console.log('amount onBeforeEnter after back sendflow ', passthroughParams); vm.fromWalletId = passthroughParams.fromWalletId; @@ -94,9 +93,11 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.fromWallet = profileService.getWallet(vm.fromWalletId); vm.toWallet = profileService.getWallet(vm.toWalletId); + ongoingProcess.set('connectingShapeshift', true); shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); + ongoingProcess.set('connectingShapeshift', false); }); } } @@ -113,7 +114,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var reOp = /^[\*\+\-\/]$/; if (!isAndroid && !isIos) { - var disableKeys = angular.element($window).on('keydown', function(e) { + angular.element($window).on('keydown', function(e) { if (!e.key) return; if (e.which === 8) { // you can add others here inside brackets. if (!altCurrencyModal) { diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index e34d90995..9be5f7902 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -507,10 +507,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit return; } - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) { + // Need to use the correct service to do it. + var amount = parseFloat(satoshis / 100000000); + + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { if (err) { - ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err, function () { $ionicHistory.goBack(); }); } else { @@ -520,6 +522,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.destination.address = toAddress; vm.destination.kind = 'shapeshift'; } + ongoingProcess.set('connectingShapeshift', false); }); }); }); diff --git a/src/js/services/shapeShiftApiService.js b/src/js/services/shapeShiftApiService.js index 411c68653..cc5fb0792 100644 --- a/src/js/services/shapeShiftApiService.js +++ b/src/js/services/shapeShiftApiService.js @@ -332,17 +332,19 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q) console.log(fixedTx); SSA.FixedAmountTx(fixedTx, function (data) { console.log(data); - promise.resolve(data.success); - }, function (err) { - promise.reject(err); + promise.resolve(data); }); return promise.promise; }, NormalTx : function($scope){ var promise = $q.defer(); var normalTx = SSA.CreateNormalTx($scope.withdrawalAddress, $scope.coinIn, $scope.coinOut); + + console.log('shapeshiftApiService.NormalTx()'); + console.log(normalTx); SSA.NormalTx(normalTx, function (data) { - promise.resolve({ normalTxData : data }); + console.log(data); + promise.resolve(data); }); return promise.promise; }, @@ -363,13 +365,12 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q) return promise.promise; }, ValidateAddress : function(address, coin) { - var promise = $q.defer(); - SSA.ValidateAdddress(address, coin, function onSuccess(data){ - promise.resolve(data); - }, function onError(err) { - promise.reject(err); - }); - return promise.promise; + var promise = $q.defer(); + SSA.ValidateAdddress(address, coin, function onRequest(data){ + console.log(data); + promise.resolve(data); + }); + return promise.promise; } }; }); diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index c9c1e428a..face3e08a 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('bitcoincom.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingDataService, platformInfo, servicesService) { +angular.module('bitcoincom.services').factory('shapeshiftService', function (shapeshiftApiService) { var root = {}; root.ShiftState = 'Shift'; root.coinIn = ''; @@ -29,45 +29,43 @@ angular.module('bitcoincom.services').factory('shapeshiftService', function ($ht 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'} }; - root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) { - root.withdrawalAddress = withdrawalAddress; - root.returnAddress = returnAddress; - root.coinIn = coinIn; - root.coinOut = coinOut; - shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function onSuccess(data) { - shapeshiftApiService.FixedAmountTx(root).then(function onSuccess(txData) { - if (txData.err) { - cb(txData.err); - } else { - if (!txData.orderId || !txData.deposit) { - cb(new Error('Invalid response')); - } else { - var coinPair = txData.pair.split('_'); - txData.depositType = coinPair[0].toUpperCase(); - txData.withdrawalType = coinPair[1].toUpperCase(); - coin = root.coins[txData.depositType].name.toLowerCase(); - txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount; - root.txFixedPending = true; - root.depositInfo = txData; - var shapeshiftData = { - coinIn: coinIn, - coinOut: coinOut, - toWalletId: root.toWalletId, - minAmount: root.marketData.minimum, - maxAmount: root.marketData.maxLimit, - orderId: txData.orderId, - toAddress: txData.deposit - }; - - cb(null, shapeshiftData); - } - } - }, function onError(err) { - cb(err); - }); - }, function onError(err) { + root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { + if (typeof amount !== 'number' || amount < root.marketData.minimum || amount > root.marketData.maxLimit) { + var err = new Error('Invalid amount'); cb(err); - }); + } else { + root.withdrawalAddress = withdrawalAddress; + root.returnAddress = returnAddress; + root.coinIn = coinIn; + root.coinOut = coinOut; + root.amount = amount; + shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function onSuccess(data) { + if (data && data.isvalid) { + shapeshiftApiService.NormalTx(root).then(function onResponse(data) { + var txData = data; + if (!txData || !txData.orderId || !txData.deposit) { + cb(new Error('Invalid response')); + } else { + root.depositInfo = txData; + var shapeshiftData = { + coinIn: coinIn, + coinOut: coinOut, + toWalletId: root.toWalletId, + minAmount: root.marketData.minimum, + maxAmount: root.marketData.maxLimit, + orderId: txData.orderId, + toAddress: txData.deposit + }; + + cb(null, shapeshiftData); + } + }); + } else { + var err = new Error('Invalid address or coin'); + cb(err); + } + }); + } }; return root; }); From 887270bfa95cb45940e4b9a0cb3fd385272fecb4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 14:42:27 +0900 Subject: [PATCH 056/118] clean ";" --- src/js/services/send-flow-state.service.js | 8 ++++---- src/js/services/send-flow.service.js | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/js/services/send-flow-state.service.js b/src/js/services/send-flow-state.service.js index bec2c8a3c..c19317515 100644 --- a/src/js/services/send-flow-state.service.js +++ b/src/js/services/send-flow-state.service.js @@ -101,7 +101,7 @@ angular Object.keys(params).forEach(function forNewParam(key) { service.state[key] = params[key]; }); - }; + } /** * Pop state @@ -116,7 +116,7 @@ angular } else { clear(); } - }; + } /** * Push state @@ -129,14 +129,14 @@ angular service.previousStates.push(currentParams); clearCurrent(); map(params); - }; + } /** * Is empty stack */ function isEmpty() { return service.previousStates.length == 0; - }; + } }; })(); \ No newline at end of file diff --git a/src/js/services/send-flow.service.js b/src/js/services/send-flow.service.js index 1b02c0d34..e8be2e487 100644 --- a/src/js/services/send-flow.service.js +++ b/src/js/services/send-flow.service.js @@ -144,6 +144,5 @@ angular sendFlowStateService.pop(); sendFlowRouterService.goBack(); } - }; - + } })(); \ No newline at end of file From 9128d3ebe2f01ca04d6e018948e68793446b54ca Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 14:43:16 +0900 Subject: [PATCH 057/118] Migrate to our new standard for services --- src/js/services/shapeshift.service.js | 149 +++++++++++++++----------- 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index face3e08a..f2d0876e7 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -1,71 +1,92 @@ 'use strict'; -angular.module('bitcoincom.services').factory('shapeshiftService', function (shapeshiftApiService) { - var root = {}; - root.ShiftState = 'Shift'; - root.coinIn = ''; - root.coinOut = ''; - root.withdrawalAddress = ''; - root.returnAddress = ''; - root.amount = ''; - root.marketData = {}; +(function(){ - root.getMarketData = function (coinIn, coinOut, cb) { - root.coinIn = coinIn; - root.coinOut = coinOut; - shapeshiftApiService - .marketInfo(root.coinIn, root.coinOut) - .then(function (marketData) { - root.marketData = marketData; - root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); - if (cb) { - cb(marketData); - } - }); - }; +angular + .module('bitcoincom.services') + .factory('shapeshiftService', shapeshiftService); + + function shapeshiftService(shapeshiftApiService) { - root.coins = { - 'BTC': {name: 'Bitcoin', symbol: 'BTC'}, - 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'} - }; + var service = { + // Variables + coinIn: '', + coinOut: '', + withdrawalAddress: '', + returnAddress: '', + amount: '', + marketData: {}, + coins: { + 'BTC': {name: 'Bitcoin', symbol: 'BTC'}, + 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'} + }, - root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { - if (typeof amount !== 'number' || amount < root.marketData.minimum || amount > root.marketData.maxLimit) { - var err = new Error('Invalid amount'); - cb(err); - } else { - root.withdrawalAddress = withdrawalAddress; - root.returnAddress = returnAddress; - root.coinIn = coinIn; - root.coinOut = coinOut; - root.amount = amount; - shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function onSuccess(data) { - if (data && data.isvalid) { - shapeshiftApiService.NormalTx(root).then(function onResponse(data) { - var txData = data; - if (!txData || !txData.orderId || !txData.deposit) { - cb(new Error('Invalid response')); - } else { - root.depositInfo = txData; - var shapeshiftData = { - coinIn: coinIn, - coinOut: coinOut, - toWalletId: root.toWalletId, - minAmount: root.marketData.minimum, - maxAmount: root.marketData.maxLimit, - orderId: txData.orderId, - toAddress: txData.deposit - }; + // Functions + getMarketData: getMarketData, + shiftIt: shiftIt + }; - cb(null, shapeshiftData); - } - }); - } else { - var err = new Error('Invalid address or coin'); - cb(err); - } - }); + return service; + + function getMarketData(coinIn, coinOut, cb) { + service.coinIn = coinIn; + service.coinOut = coinOut; + shapeshiftApiService + .marketInfo(service.coinIn, service.coinOut) + .then(function (marketData) { + service.marketData = marketData; + service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); + if (cb) { + cb(marketData); + } + }); } - }; - return root; -}); + + function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { + // Test if the amount is correct depending on the min and max + if (typeof amount !== 'number' || amount < service.marketData.minimum || amount > service.marketData.maxLimit) { + var err = new Error('Invalid amount'); + cb(err); + } else { + // Init service data + service.withdrawalAddress = withdrawalAddress; + service.returnAddress = returnAddress; + service.coinIn = coinIn; + service.coinOut = coinOut; + service.amount = amount; + + // Check the address + shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function onSuccess(data) { + if (data && data.isvalid) { + + // Prepare the transaction shapeshift side + shapeshiftApiService.NormalTx(service).then(function onResponse(data) { + var txData = data; + + // If the content is not that it was expected, get back an error + if (!txData || !txData.orderId || !txData.deposit) { + cb(new Error('Invalid response')); + } else { + // Get back the data + service.depositInfo = txData; + var shapeshiftData = { + coinIn: coinIn, + coinOut: coinOut, + toWalletId: service.toWalletId, + minAmount: service.marketData.minimum, + maxAmount: service.marketData.maxLimit, + orderId: txData.orderId, + toAddress: txData.deposit + }; + cb(null, shapeshiftData); + } + }); + } else { + var err = new Error('Invalid address or coin'); + cb(err); + } + }); + } + } + } +})(); \ No newline at end of file From 2c398bbe03440d68835f7150f5b459a1d28c9f7a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 14:51:06 +0900 Subject: [PATCH 058/118] Fix amount check --- src/js/services/shapeshift.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index f2d0876e7..c290f1fc3 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -44,7 +44,7 @@ angular function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { // Test if the amount is correct depending on the min and max - if (typeof amount !== 'number' || amount < service.marketData.minimum || amount > service.marketData.maxLimit) { + if (!amount || typeof amount !== 'number' || amount < service.marketData.minimum || amount > service.marketData.maxLimit) { var err = new Error('Invalid amount'); cb(err); } else { From d01baa606082983565b7418539388ec296664517 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 5 Sep 2018 18:16:01 +1200 Subject: [PATCH 059/118] Better handling of Shapeshift error. --- src/js/controllers/amount.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 24a3cc2d1..fdbcfe5bf 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -238,15 +238,20 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, ongoingProcess.set('connectingShapeshift', false); if (data.error) { - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), typeof data.error === 'string'?data.error:(data.error.message?data.error.message:''), function () { - $ionicHistory.goBack(); + // TODO: translatable string. use goBack(), don't execute the code beyond. + popupService.showAlert( + gettextCatalog.getString('Shapeshift Error'), + typeof data.error === 'string' ? data.error : (data.error.message ? data.error.message : ''), + function () { + goBack(); }); + } else { + + vm.thirdParty.data.minAmount = vm.minAmount = parseFloat(data.minimum); + vm.thirdParty.data.maxAmount = vm.maxAmount = parseFloat(data.maxLimit); + + setMaximumButtonFromWallet(vm.fromWallet); } - - vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); - vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); - - setMaximumButtonFromWallet(vm.fromWallet); }); From fa8ce4779f5c2ec6a0076119299dc1895742bf44 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 15:32:47 +0900 Subject: [PATCH 060/118] async series with shapeshift & fee calculation --- src/js/controllers/review.controller.js | 159 +++++++++++++----------- 1 file changed, 84 insertions(+), 75 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 9be5f7902..f606e4898 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -93,26 +93,48 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (sendFlowData.thirdParty) { vm.thirdParty = sendFlowData.thirdParty; - handleThirdPartyInitIfBip70(); - handleThirdPartyInitIfShapeshift(); + switch (vm.thirdParty.id) { + case 'shapeshift': + initShapeshift(function (err) { + if (err) { + // Error stop here + ongoingProcess.set('connectingShapeshift', false); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); + } else { + _next(data); + } + }); + break; + case 'bip70': + initBip70(); + default: + _next(data); + break; + } + } else { + _next(data); } - configService.get(function onConfig(err, configCache) { - if (err) { - $log.err('Error getting config.', err); - } else { - config = configCache; - priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; - vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); - console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); - unitFromSat = 1 / config.wallet.settings.unitToSatoshi; - } - updateSendAmounts(); - getOriginWalletBalance(vm.originWallet); - handleDestinationAsAddress(toAddress, coin); - handleDestinationAsWallet(sendFlowData.toWalletId); - createVanityTransaction(data); - }); + function _next() { + configService.get(function onConfig(err, configCache) { + if (err) { + $log.err('Error getting config.', err); + } else { + config = configCache; + priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; + vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); + console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); + unitFromSat = 1 / config.wallet.settings.unitToSatoshi; + } + updateSendAmounts(); + getOriginWalletBalance(vm.originWallet); + handleDestinationAsAddress(toAddress, coin); + handleDestinationAsWallet(sendFlowData.toWalletId); + createVanityTransaction(data); + }); + } } vm.approve = function() { @@ -458,75 +480,62 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.destination.balanceCurrency = balanceText.currency; } - function handleThirdPartyInitIfBip70() { - if (vm.thirdParty.id === 'bip70') { - vm.sendingTitle = gettextCatalog.getString('You are paying'); - vm.memo = vm.thirdParty.memo; - vm.memoExpanded = !!vm.memo; - vm.destination.name = vm.thirdParty.name; + function initBip70() { + vm.sendingTitle = gettextCatalog.getString('You are paying'); + vm.memo = vm.thirdParty.memo; + vm.memoExpanded = !!vm.memo; + vm.destination.name = vm.thirdParty.name; - txPayproData = { - caTrusted: vm.thirdParty.caTrusted, - domain: vm.thirdParty.domain, - expires: vm.thirdParty.expires, - toAddress: toAddress, - url: vm.thirdParty.url, - verified: vm.thirdParty.verified, - }; - } + txPayproData = { + caTrusted: vm.thirdParty.caTrusted, + domain: vm.thirdParty.domain, + expires: vm.thirdParty.expires, + toAddress: toAddress, + url: vm.thirdParty.url, + verified: vm.thirdParty.verified, + }; } - function handleThirdPartyInitIfShapeshift() { - if (vm.thirdParty.id === 'shapeshift') { - vm.sendingTitle = gettextCatalog.getString('You are shifting'); - if (!vm.thirdParty.data) { - vm.thirdParty.data = {}; - } + function initShapeshift(cb) { + vm.sendingTitle = gettextCatalog.getString('You are shifting'); + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } - var toWallet = profileService.getWallet(destinationWalletId); - vm.destination.name = toWallet.name; - vm.destination.color = toWallet.color; - vm.destination.currency = toWallet.coin.toUpperCase(); + var toWallet = profileService.getWallet(destinationWalletId); + vm.destination.name = toWallet.name; + vm.destination.color = toWallet.color; + vm.destination.currency = toWallet.coin.toUpperCase(); - ongoingProcess.set('connectingShapeshift', true); - walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { + ongoingProcess.set('connectingShapeshift', true); + walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { + if (err) { + return cb(err); + } + walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { if (err) { - ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { - $ionicHistory.goBack(); - }); - return; + return cb(err); } - walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { + + // Need to use the correct service to do it. + var amount = parseFloat(satoshis / 100000000); + + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { if (err) { + return cb(err); + } else { + vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; + vm.memoExpanded = !!vm.memo; + tx.toAddress = shapeshiftData.toAddress; + vm.destination.address = toAddress; + vm.destination.kind = 'shapeshift'; ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { - $ionicHistory.goBack(); - }); - return; - } - - // Need to use the correct service to do it. - var amount = parseFloat(satoshis / 100000000); - - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { - if (err) { - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err, function () { - $ionicHistory.goBack(); - }); - } else { - vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; - vm.memoExpanded = !!vm.memo; - tx.toAddress = shapeshiftData.toAddress; - vm.destination.address = toAddress; - vm.destination.kind = 'shapeshift'; - } - ongoingProcess.set('connectingShapeshift', false); - }); + cb(); + } }); }); - } + }); } function onShareTransaction() { From 2a607d245da983062b0939287d796240dd90f852 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 16:10:05 +0900 Subject: [PATCH 061/118] using ionicModal for all devices --- src/js/services/onGoingProcess.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js index 147985dbb..a8b8a4f1d 100644 --- a/src/js/services/onGoingProcess.js +++ b/src/js/services/onGoingProcess.js @@ -82,17 +82,12 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti if (customHandler) { customHandler(processName, showName, isOn); } else if (root.onGoingProcessName) { - if (isCordova && !isWindowsPhoneApp) { - window.plugins.spinnerDialog.show(null, showName, root.clear); - } else { - - var tmpl; - if (isWindowsPhoneApp) tmpl = '
' + showName + '
'; - else tmpl = '
' + showName + '
'; - $ionicLoading.show({ - template: tmpl - }); - } + var tmpl; + if (isWindowsPhoneApp) tmpl = '
' + showName + '
'; + else tmpl = '
' + showName + '
'; + $ionicLoading.show({ + template: tmpl, + }); } else { if (isCordova && !isWindowsPhoneApp) { window.plugins.spinnerDialog.hide(); From 3b6bb1fcddfe4e95c3593d98d43b843071e2d3d0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 16:10:21 +0900 Subject: [PATCH 062/118] ionicModal using for all devices --- src/js/services/onGoingProcess.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js index a8b8a4f1d..9c25c3c26 100644 --- a/src/js/services/onGoingProcess.js +++ b/src/js/services/onGoingProcess.js @@ -52,11 +52,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti root.clear = function() { ongoingProcess = {}; - if (isCordova && !isWindowsPhoneApp) { - window.plugins.spinnerDialog.hide(); - } else { - $ionicLoading.hide(); - } + $ionicLoading.hide(); }; root.get = function(processName) { @@ -89,11 +85,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti template: tmpl, }); } else { - if (isCordova && !isWindowsPhoneApp) { - window.plugins.spinnerDialog.hide(); - } else { - $ionicLoading.hide(); - } + $ionicLoading.hide(); } }; From 2d6a1528c10fd5f02ff7170ba34731cf15b44f93 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 5 Sep 2018 17:23:09 +0900 Subject: [PATCH 063/118] handle errors properly --- src/js/controllers/amount.js | 19 ++++-- src/js/services/shapeshift.service.js | 95 +++++++++++++++++---------- 2 files changed, 73 insertions(+), 41 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index c24c567ed..c1cd9a118 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, gettextCatalog, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window, ongoingProcess) { +function amountController(configService, $filter, gettextCatalog, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window, ongoingProcess, popupService) { var vm = this; vm.allowSend = false; @@ -94,10 +94,19 @@ function amountController(configService, $filter, gettextCatalog, $ionicModal, $ vm.toWallet = profileService.getWallet(vm.toWalletId); ongoingProcess.set('connectingShapeshift', true); - shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { - vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); - vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); - ongoingProcess.set('connectingShapeshift', false); + shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(err, data) { + + if (err) { + // Error stop here + ongoingProcess.set('connectingShapeshift', false); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); + } else { + vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); + vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); + ongoingProcess.set('connectingShapeshift', false); + } }); } } diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index c290f1fc3..21a5368fe 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -28,16 +28,32 @@ angular return service; + function handleError(response, defaultMessage, cb) { + if (!response || !response.error || !response.error.message) { + if (cb) { + cb(new Error(defaultMessage)); + } + } else { + if (cb) { + cb(new Error(response.error.message)); + } + } + } + function getMarketData(coinIn, coinOut, cb) { service.coinIn = coinIn; service.coinOut = coinOut; shapeshiftApiService .marketInfo(service.coinIn, service.coinOut) - .then(function (marketData) { - service.marketData = marketData; - service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); - if (cb) { - cb(marketData); + .then(function (response) { + if (!response || response.error) { + handleError(response, 'Invalid response', cb); + } else { + service.marketData = response; + service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); + if (cb) { + cb(null, response); + } } }); } @@ -45,8 +61,7 @@ angular function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { // Test if the amount is correct depending on the min and max if (!amount || typeof amount !== 'number' || amount < service.marketData.minimum || amount > service.marketData.maxLimit) { - var err = new Error('Invalid amount'); - cb(err); + cb(new Error('Invalid amount')); } else { // Init service data service.withdrawalAddress = withdrawalAddress; @@ -56,36 +71,44 @@ angular service.amount = amount; // Check the address - shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function onSuccess(data) { - if (data && data.isvalid) { + shapeshiftApiService + .ValidateAddress(withdrawalAddress, coinOut) + .then(function onSuccess(response) { + if (response && response.isvalid) { - // Prepare the transaction shapeshift side - shapeshiftApiService.NormalTx(service).then(function onResponse(data) { - var txData = data; - - // If the content is not that it was expected, get back an error - if (!txData || !txData.orderId || !txData.deposit) { - cb(new Error('Invalid response')); - } else { - // Get back the data - service.depositInfo = txData; - var shapeshiftData = { - coinIn: coinIn, - coinOut: coinOut, - toWalletId: service.toWalletId, - minAmount: service.marketData.minimum, - maxAmount: service.marketData.maxLimit, - orderId: txData.orderId, - toAddress: txData.deposit - }; - cb(null, shapeshiftData); - } - }); - } else { - var err = new Error('Invalid address or coin'); - cb(err); - } - }); + // Prepare the transaction shapeshift side + shapeshiftApiService.NormalTx(service).then(function onResponse(response) { + // If error, return it + if (!response || response.error) { + handleError(response, 'Invalid response', cb); + } else { + var txData = response; + + // If the content is not that it was expected, get back an error + if (!txData || !txData.orderId || !txData.deposit) { + if (cb) { + cb(new Error('Invalid response')); + } + } else { + // Get back the data + service.depositInfo = txData; + var shapeshiftData = { + coinIn: coinIn, + coinOut: coinOut, + toWalletId: service.toWalletId, + minAmount: service.marketData.minimum, + maxAmount: service.marketData.maxLimit, + orderId: txData.orderId, + toAddress: txData.deposit + }; + cb(null, shapeshiftData); + } + } + }); + } else if (cb) { + cb(new Error('Invalid address or coin')); + } + }); } } } From 477a9b69953d1d2a31e3aa424d5ac9da4de58224 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 5 Sep 2018 14:03:01 +0200 Subject: [PATCH 064/118] Android KitKat (4.4) fixes --- src/sass/views/amount.scss | 1 + src/sass/views/review.scss | 5 +++++ src/sass/views/tab-home.scss | 2 +- src/sass/views/walletDetails.scss | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sass/views/amount.scss b/src/sass/views/amount.scss index bf4d3506a..aba2fe6d9 100644 --- a/src/sass/views/amount.scss +++ b/src/sass/views/amount.scss @@ -350,6 +350,7 @@ .primary-amount-display { margin-right: 5px; word-break: break-all; + width: 100%; } } diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 474e1ea3f..487cef423 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -19,4 +19,9 @@ .warning { color: $v-warning-color-2; } + + .item .item-content.item-content-avatar > img:first-child, .item .item-content.item-content-avatar > i:first-child { + top: calc(50% - 20px); // XX SP: Icons are 40px + transform: none; + } } \ No newline at end of file diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index f9ace4655..909bc5791 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -89,7 +89,7 @@ max-width: 300px; font-size: 19px; font-weight: bolder; - min-height: auto; + min-height: 0; line-height: 19px; } } diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 5c235ad97..1db684153 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -213,7 +213,7 @@ max-width: 300px; font-size: 19px; font-weight: bolder; - min-height: auto; + min-height: 0; line-height: 19px; } } From 22e1c397d0a74cc17a4d6f1ea92a5faa30ed7347 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 5 Sep 2018 16:19:31 +0200 Subject: [PATCH 065/118] Removed duplicate android header + refactored animation to use CSS animations instead of javascript calculated animations --- .../controllers/wallet-details.controller.js | 92 +--------- src/sass/views/walletDetails.scss | 40 +++- www/views/walletDetails.html | 173 +++--------------- 3 files changed, 66 insertions(+), 239 deletions(-) diff --git a/src/js/controllers/wallet-details.controller.js b/src/js/controllers/wallet-details.controller.js index 365ce86a3..7a06d4fb6 100644 --- a/src/js/controllers/wallet-details.controller.js +++ b/src/js/controllers/wallet-details.controller.js @@ -19,8 +19,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var log = new window.BitAnalytics.LogEvent("wallet_details_open", [], [channel]); window.BitAnalytics.LogEventHandlers.postEvent(log); - $scope.amountIsCollapsible = !$scope.isAndroid; - $scope.openExternalLink = function(url, target) { externalLinkService.open(url, target); }; @@ -265,10 +263,12 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.txHistoryPaddingBottom = 0; function getScrollPosition() { var scrollPosition = $ionicScrollDelegate.getScrollPosition(); + + $timeout(function() { + getScrollPosition(); + }, 200); + if (!scrollPosition) { - $timeout(function() { - getScrollPosition(); - }, 25); return; } var pos = scrollPosition.top; @@ -276,91 +276,16 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.txHistoryPaddingBottom = "200px"; } if (pos === prevPos) { - $timeout(function() { - getScrollPosition(); - }, 25); return; } prevPos = pos; - refreshAmountSection(pos); + $scope.scrollPosition = pos; } - function refreshAmountSection(scrollPos) { - var AMOUNT_HEIGHT_BASE = 210; - $scope.showBalanceButton = false; - if ($scope.status) { - $scope.showBalanceButton = ($scope.status.totalBalanceSat != $scope.status.spendableAmount); - if ($scope.showBalanceButton) { - AMOUNT_HEIGHT_BASE = 270; - } - } - if (!$scope.amountIsCollapsible) { - var t = ($scope.showBalanceButton ? 15 : 45); - $scope.amountScale = 'translateY(' + t + 'px)'; - return; - } - - scrollPos = scrollPos || 0; - var amountHeight = AMOUNT_HEIGHT_BASE - scrollPos; - if (amountHeight < 80) { - amountHeight = 80; - } - var contentMargin = amountHeight; - if (contentMargin > AMOUNT_HEIGHT_BASE) { - contentMargin = AMOUNT_HEIGHT_BASE; - } - - var amountScale = (amountHeight / AMOUNT_HEIGHT_BASE); - if (amountScale < 0.5) { - amountScale = 0.5; - } - if (amountScale > 1.1) { - amountScale = 1.1; - } - - var s = amountScale; - - // Make space for the balance button when it needs to display. - var TOP_NO_BALANCE_BUTTON = 115; - var TOP_BALANCE_BUTTON = 30; - var top = TOP_NO_BALANCE_BUTTON; - if ($scope.showBalanceButton) { - top = TOP_BALANCE_BUTTON; - } - - var amountTop = ((amountScale - 0.80) / 0.80) * top; - if (amountTop < -2) { - amountTop = -2; - } - if (amountTop > top) { - amountTop = top; - } - - var t = amountTop; - if (scrollPos > 50) { - contentMargin = amountHeight = 80; - $scope.altAmountOpacity = 0.01; - $scope.buttonsOpacity = 0.01; - } else { - contentMargin = amountHeight = 210; - $scope.altAmountOpacity = 1; - $scope.buttonsOpacity = 1; - } - $scope.amountHeight = amountHeight + 'px'; - $scope.contentMargin = contentMargin + 'px'; - $scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)'; - $scope.$digest(); - getScrollPosition(); - } - - var scrollEffectTimeout; var scrollWatcherInitialized; $scope.$on("$ionicView.enter", function(event, data) { if ($scope.isCordova && $scope.isAndroid) setAndroidStatusBarColor(); - if (scrollWatcherInitialized || !$scope.amountIsCollapsible) { - return; - } scrollWatcherInitialized = true; }); @@ -401,8 +326,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.$on("$ionicView.afterEnter", function(event, data) { $scope.updateAll(); - refreshAmountSection(); + // refreshAmountSection(); refreshInterval = $interval($scope.onRefresh, 10 * 1000); + $timeout(function() { + getScrollPosition(); + }, 1000); }); $scope.$on("$ionicView.afterLeave", function(event, data) { diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 4e94f6a29..3461d57b6 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -1,3 +1,4 @@ +$wallet-details-transition: all 0.25s ease-in-out; .wallet-details { &__tx-amount { font-size: 16px; @@ -137,6 +138,20 @@ margin-top: 20px; margin-top: env(safe-area-inset-top); } + &.collapse { + ion-content { + margin-top: 40px; + } + .amount { + &__scale, &__error { + -webkit-transform: scale3d(0.5, 0.5, 0.5) translateY(0px); + transform: scale3d(0.5, 0.5, 0.5) translateY(0px); + } + } + .amount-alternative, .send-receive-buttons, .wallet-details-wallet-info { + opacity: 0; + } + } } .bar-header { border: 0; @@ -152,14 +167,14 @@ background-color: inherit !important; } ion-content { - - &.collapsible { - margin-top: 230px; - } - padding-top: 0; top: 0; - transition: margin-top 0.5s ease-in-out; + transition: $wallet-details-transition; + + margin-top: 185px; + @media only screen and (max-height:500px) { + margin-top: 165px; + } margin-bottom: 16px; .scroll { @@ -200,8 +215,7 @@ width: 100%; position: absolute; bottom: 20px; - transition-delay: 0.25s; - transition: opacity 0.4s ease-in-out; + transition: $wallet-details-transition; >.col { padding: 5px 10px; margin-bottom: 0; @@ -224,16 +238,22 @@ align-items: center; color: #fff; display: block; + height: 230px; + @media only screen and (max-height:500px) { + height: 210px; + } + justify-content: center; padding-top: 40px; text-align: center; - transition: height 0.5s ease-in-out; + transition: $wallet-details-transition; width: 100%; &__balance { -webkit-transform: scale3d(1, 1, 1) translateY(45px); transform: scale3d(1, 1, 1) translateY(45px); + transition: $wallet-details-transition; } &__updating { @@ -243,6 +263,7 @@ &-alternative { line-height: 36px; + transition: $wallet-details-transition; } &__button-balance { @@ -258,6 +279,7 @@ &__error { font-size: 14px; padding: 35px 20px; + opacity: 1; } } diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 2341adcba..520cc4ab6 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -16,79 +16,74 @@ } -
+
-
+
-
+
{{updateStatusError}}
-
+
This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.
+ class="amount__balance amount__scale">
+ ng-if="status.totalBalanceStr && wallet.network == 'livenet'">
+ ng-if="status.totalBalanceStr && selectedPriceDisplay=='crypto' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning" + class="amount__balance amount__scale">
+ ng-if="status.totalBalanceAlternative && wallet.network == 'livenet'">
-
[Balance Hidden] -
+
Tap and hold to show
-
+
[Scanning Funds] -
+
Please wait
-
+
-
+
Receive @@ -118,136 +113,18 @@
-
+
- + - -
- -
- -
- {{updateStatusError}} -
- -
- This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information. -
- -
- -
- -
-
- -
- -
- -
-
- -
- [Balance Hidden] -
- Tap and hold to show -
-
- - -
- [Scanning Funds] -
- Please wait -
-
- - -
- -
- -
- -
- -
-
-
- Receive -
-
-
-
- Buy Bitcoin -
-
- Send -
-
-
- -
-
- -
-
- - - Wallet not backed up From 525afac92e4231a23bf066031bd00dbdf0e8ea82 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 5 Sep 2018 16:36:53 +0200 Subject: [PATCH 066/118] scss var name change --- src/sass/views/walletDetails.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 3461d57b6..6126c6b8b 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -1,4 +1,4 @@ -$wallet-details-transition: all 0.25s ease-in-out; +$wallet-details-collapse-transition: all 0.25s ease-in-out; .wallet-details { &__tx-amount { font-size: 16px; @@ -169,7 +169,7 @@ $wallet-details-transition: all 0.25s ease-in-out; ion-content { padding-top: 0; top: 0; - transition: $wallet-details-transition; + transition: $wallet-details-collapse-transition; margin-top: 185px; @media only screen and (max-height:500px) { @@ -215,7 +215,7 @@ $wallet-details-transition: all 0.25s ease-in-out; width: 100%; position: absolute; bottom: 20px; - transition: $wallet-details-transition; + transition: $wallet-details-collapse-transition; >.col { padding: 5px 10px; margin-bottom: 0; @@ -247,13 +247,13 @@ $wallet-details-transition: all 0.25s ease-in-out; justify-content: center; padding-top: 40px; text-align: center; - transition: $wallet-details-transition; + transition: $wallet-details-collapse-transition; width: 100%; &__balance { -webkit-transform: scale3d(1, 1, 1) translateY(45px); transform: scale3d(1, 1, 1) translateY(45px); - transition: $wallet-details-transition; + transition: $wallet-details-collapse-transition; } &__updating { @@ -263,7 +263,7 @@ $wallet-details-transition: all 0.25s ease-in-out; &-alternative { line-height: 36px; - transition: $wallet-details-transition; + transition: $wallet-details-collapse-transition; } &__button-balance { From f52b2e7f09b36fdb4c01e33f2d594b76a487f53f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 5 Sep 2018 16:44:33 +0200 Subject: [PATCH 067/118] Better fix for KitKat 4.4 --- src/sass/components/item.scss | 1 + src/sass/views/review.scss | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sass/components/item.scss b/src/sass/components/item.scss index bb75ae8e0..a6ac4143c 100644 --- a/src/sass/components/item.scss +++ b/src/sass/components/item.scss @@ -27,6 +27,7 @@ left: 13px; top: 50%; padding: 0; + -webkit-transform: translate(0,-50%); transform: translate(0,-50%); } } diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 487cef423..d4f02f2f4 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -20,8 +20,4 @@ color: $v-warning-color-2; } - .item .item-content.item-content-avatar > img:first-child, .item .item-content.item-content-avatar > i:first-child { - top: calc(50% - 20px); // XX SP: Icons are 40px - transform: none; - } } \ No newline at end of file From 394317bc46b516db3a477fbcfced7e09e668054c Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 6 Sep 2018 11:27:48 +1200 Subject: [PATCH 068/118] Better default error message for Shapeshift error. --- i18n/po/template.pot | 4 ++++ src/js/controllers/amount.js | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index eed30bbb8..10b3de712 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3854,3 +3854,7 @@ msgstr "" #: www/views/amount.html.js:60 msgid "Send Maximum Amount" msgstr "" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "" \ No newline at end of file diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index fdbcfe5bf..cc78058bd 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -221,7 +221,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function initForShapeshift() { - console.log('initForShapeshift()'); if (vm.thirdParty.id === 'shapeshift') { vm.thirdParty.data = vm.thirdParty.data || {}; @@ -234,17 +233,17 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, ongoingProcess.set('connectingShapeshift', true); shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(data) { - console.log('sendmax onMarketData()'); ongoingProcess.set('connectingShapeshift', false); if (data.error) { - // TODO: translatable string. use goBack(), don't execute the code beyond. + var defaultErrorMessage = gettextCatalog.getString('Unknown error.'); popupService.showAlert( gettextCatalog.getString('Shapeshift Error'), - typeof data.error === 'string' ? data.error : (data.error.message ? data.error.message : ''), + typeof data.error === 'string' ? data.error : (data.error.message ? data.error.message : defaultErrorMessage), function () { goBack(); - }); + } + ); } else { vm.thirdParty.data.minAmount = vm.minAmount = parseFloat(data.minimum); From 3de34dbf156aeb680471b57f95f9c37693e05938 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 6 Sep 2018 11:34:00 +1200 Subject: [PATCH 069/118] Renaming to follow new convention. --- src/js/app.js | 2 ++ src/js/controllers/{amount.js => amount.controller.js} | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) rename src/js/controllers/{amount.js => amount.controller.js} (99%) diff --git a/src/js/app.js b/src/js/app.js index 745ceef50..6bef6f615 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,6 +19,7 @@ var modules = [ 'copayApp.controllers', 'copayApp.directives', 'copayApp.addons', + 'bitcoincom.controllers', 'bitcoincom.directives' ]; @@ -29,4 +30,5 @@ angular.module('copayApp.services', []); angular.module('copayApp.controllers', []); angular.module('copayApp.directives', []); angular.module('copayApp.addons', []); +angular.module('bitcoincom.controllers', []); angular.module('bitcoincom.directives', []); diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.controller.js similarity index 99% rename from src/js/controllers/amount.js rename to src/js/controllers/amount.controller.js index cc78058bd..1e6af76b1 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.controller.js @@ -1,6 +1,10 @@ 'use strict'; -angular.module('copayApp.controllers').controller('amountController', amountController); +(function(){ + +angular + .module('bitcoincom.controllers') + .controller('amountController', amountController); function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, popupService, profileService, walletService, $window) { var vm = this; @@ -780,3 +784,4 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } } +})(); \ No newline at end of file From 0d8812509d362116da2a2cb62defe0351825c1d7 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 6 Sep 2018 15:45:20 +1200 Subject: [PATCH 070/118] Updated readme. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 56ae84eab..75ced5808 100644 --- a/README.md +++ b/README.md @@ -113,14 +113,14 @@ npm run start:desktop Before building the release version for a platform, run the `clean-all` command to delete any untracked files in your current working directory. (Be sure to stash any uncommited changes you've made.) This guarantees consistency across builds for the current state of this repository. -The `final` commands build the production version of the app, and bundle it with the release version of the platform being built. +The `build:*-release` commands build the production version of the app, and bundle it with the release version of the platform being built. ### Android ```sh npm run clean-all npm run apply:bitcoincom -npm run final:android +npm run build:android-release ``` ### iOS @@ -128,7 +128,7 @@ npm run final:android ```sh npm run clean-all npm run apply:bitcoincom -npm run final:ios +npm run build:ios-release ``` ### Desktop (Linux, macOS, and Windows) @@ -136,7 +136,7 @@ npm run final:ios ```sh npm run clean-all npm run apply:bitcoincom -npm run final:desktop +npm run build:desktop-release ``` ## About The Bitcoin.com Wallet From 98e6e8ac7574f3c5a8bcfade29b7be64cc6d7e27 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 6 Sep 2018 15:17:37 +0900 Subject: [PATCH 071/118] Recommendation #brendon :) --- i18n/po/template.pot | 20 +++++++++++++++ src/js/controllers/review.controller.js | 6 ++--- src/js/services/shapeshift.service.js | 33 +++++++++++-------------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index f61012914..ca6288cbe 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3873,4 +3873,24 @@ msgstr "" #: www/views/includes/incomingDataMenu.html:90 msgid "Open in web browser" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" msgstr "" \ No newline at end of file diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index f606e4898..c82838f7c 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -525,11 +525,11 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (err) { return cb(err); } else { + vm.destination.kind = 'shapeshift'; + vm.destination.address = toAddress; + tx.toAddress = shapeshiftData.toAddress; vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; vm.memoExpanded = !!vm.memo; - tx.toAddress = shapeshiftData.toAddress; - vm.destination.address = toAddress; - vm.destination.kind = 'shapeshift'; ongoingProcess.set('connectingShapeshift', false); cb(); } diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 21a5368fe..fdfe5bcca 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -6,7 +6,7 @@ angular .module('bitcoincom.services') .factory('shapeshiftService', shapeshiftService); - function shapeshiftService(shapeshiftApiService) { + function shapeshiftService(shapeshiftApiService, gettext) { var service = { // Variables @@ -30,13 +30,9 @@ angular function handleError(response, defaultMessage, cb) { if (!response || !response.error || !response.error.message) { - if (cb) { - cb(new Error(defaultMessage)); - } + cb(new Error(defaultMessage)); } else { - if (cb) { - cb(new Error(response.error.message)); - } + cb(new Error(response.error.message)); } } @@ -51,17 +47,19 @@ angular } else { service.marketData = response; service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); - if (cb) { - cb(null, response); - } + cb(null, response); } }); } function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { // Test if the amount is correct depending on the min and max - if (!amount || typeof amount !== 'number' || amount < service.marketData.minimum || amount > service.marketData.maxLimit) { - cb(new Error('Invalid amount')); + if (!amount || typeof amount !== 'number') { + cb(new Error(gettext('Amount is not defined')))); + } else if (amount < service.marketData.minimum) { + cb(new Error(gettext('Amount is below the minimun'))); + } else if (amount > service.marketData.maxLimit) { + cb(new Error(gettext('Amount is above the limit'))); } else { // Init service data service.withdrawalAddress = withdrawalAddress; @@ -75,20 +73,17 @@ angular .ValidateAddress(withdrawalAddress, coinOut) .then(function onSuccess(response) { if (response && response.isvalid) { - // Prepare the transaction shapeshift side shapeshiftApiService.NormalTx(service).then(function onResponse(response) { // If error, return it if (!response || response.error) { - handleError(response, 'Invalid response', cb); + handleError(response, gettext('Invalid response from Shapeshift'), cb); } else { var txData = response; // If the content is not that it was expected, get back an error if (!txData || !txData.orderId || !txData.deposit) { - if (cb) { - cb(new Error('Invalid response')); - } + cb(new Error(gettext('Invalid response from Shapeshift'))); } else { // Get back the data service.depositInfo = txData; @@ -105,8 +100,8 @@ angular } } }); - } else if (cb) { - cb(new Error('Invalid address or coin')); + } else { + cb(new Error(gettext('Invalid address'))); } }); } From a397e83cf051d3da377d631706d0d8376200dacf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 6 Sep 2018 15:24:16 +0900 Subject: [PATCH 072/118] error case without .message --- src/js/services/shapeshift.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index fdfe5bcca..7b3076b87 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -29,8 +29,10 @@ angular return service; function handleError(response, defaultMessage, cb) { - if (!response || !response.error || !response.error.message) { + if (!response) { cb(new Error(defaultMessage)); + } else if (response.error && !response.error.message) { + cb(new Error(response.error)); } else { cb(new Error(response.error.message)); } From 71e530f535c948ac30c267c506081f5dd79742df Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 6 Sep 2018 15:31:22 +0900 Subject: [PATCH 073/118] Unify the translation service --- src/js/services/shapeshift.service.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 7b3076b87..2bc6a20c4 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -6,7 +6,7 @@ angular .module('bitcoincom.services') .factory('shapeshiftService', shapeshiftService); - function shapeshiftService(shapeshiftApiService, gettext) { + function shapeshiftService(shapeshiftApiService, gettextCatalog) { var service = { // Variables @@ -45,7 +45,7 @@ angular .marketInfo(service.coinIn, service.coinOut) .then(function (response) { if (!response || response.error) { - handleError(response, 'Invalid response', cb); + handleError(response, 'Invalid response from Shapeshift', cb); } else { service.marketData = response; service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); @@ -57,11 +57,11 @@ angular function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { // Test if the amount is correct depending on the min and max if (!amount || typeof amount !== 'number') { - cb(new Error(gettext('Amount is not defined')))); + cb(new Error(gettextCatalog.getString('Amount is not defined')))); } else if (amount < service.marketData.minimum) { - cb(new Error(gettext('Amount is below the minimun'))); + cb(new Error(gettextCatalog.getString('Amount is below the minimun'))); } else if (amount > service.marketData.maxLimit) { - cb(new Error(gettext('Amount is above the limit'))); + cb(new Error(gettextCatalog.getString('Amount is above the limit'))); } else { // Init service data service.withdrawalAddress = withdrawalAddress; @@ -79,13 +79,13 @@ angular shapeshiftApiService.NormalTx(service).then(function onResponse(response) { // If error, return it if (!response || response.error) { - handleError(response, gettext('Invalid response from Shapeshift'), cb); + handleError(response, gettextCatalog.getString('Invalid response from Shapeshift'), cb); } else { var txData = response; // If the content is not that it was expected, get back an error if (!txData || !txData.orderId || !txData.deposit) { - cb(new Error(gettext('Invalid response from Shapeshift'))); + cb(new Error(gettextCatalog.getString('Invalid response from Shapeshift'))); } else { // Get back the data service.depositInfo = txData; @@ -103,7 +103,7 @@ angular } }); } else { - cb(new Error(gettext('Invalid address'))); + cb(new Error(gettextCatalog.getString('Invalid address'))); } }); } From c79ca6d1afa92f56f506fb2d411cc14e16f626e8 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 6 Sep 2018 18:34:59 +1200 Subject: [PATCH 074/118] More specific Shapeshift error handling. --- src/js/services/shapeshift.service.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 2bc6a20c4..77f0de297 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -29,12 +29,12 @@ angular return service; function handleError(response, defaultMessage, cb) { - if (!response) { - cb(new Error(defaultMessage)); - } else if (response.error && !response.error.message) { + if (response && typeof response.error === "string") { cb(new Error(response.error)); - } else { + } else if (response && response.error && response.error.message) { cb(new Error(response.error.message)); + } else { + cb(new Error(defaultMessage)); } } From b0fba31040139356339f6f1086cbf3e2a8470ff1 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 6 Sep 2018 20:18:31 +1200 Subject: [PATCH 075/118] Fix fee display on Android 4.4 KitKat. --- src/sass/components/fee-summary.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss index 5c9488b60..88c43ab02 100644 --- a/src/sass/components/fee-summary.scss +++ b/src/sass/components/fee-summary.scss @@ -18,12 +18,11 @@ } .amount { - display: flex; - flex-direction: row; - justify-content: space-between; width: 100%; .fee-fiat { + display: inline; + &.positive { color: #70955F; } @@ -35,6 +34,7 @@ .fee-crypto { color: #A7A7A7; + float: right; } } } \ No newline at end of file From 57ce93ccb80bb64beed61d345113aa2a57f2ba5f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 6 Sep 2018 18:28:27 +0900 Subject: [PATCH 076/118] fix parenthesis --- src/js/services/shapeshift.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 77f0de297..73410e478 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -57,7 +57,7 @@ angular function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { // Test if the amount is correct depending on the min and max if (!amount || typeof amount !== 'number') { - cb(new Error(gettextCatalog.getString('Amount is not defined')))); + cb(new Error(gettextCatalog.getString('Amount is not defined'))); } else if (amount < service.marketData.minimum) { cb(new Error(gettextCatalog.getString('Amount is below the minimun'))); } else if (amount > service.marketData.maxLimit) { From c14820ea34eb5d7430bc355852396fb92a6e5c7b Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 6 Sep 2018 13:04:19 +0200 Subject: [PATCH 077/118] Shim for Array.includes (fixing receive notifications) + Android KitKat fixes (effect on slide + received payment notification) --- src/js/directives/slideToAccept.js | 3 +- src/sass/views/tab-receive.scss | 3 ++ src/shim/shim.js | 52 ++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/js/directives/slideToAccept.js b/src/js/directives/slideToAccept.js index 3f4db0ef9..830bed677 100644 --- a/src/js/directives/slideToAccept.js +++ b/src/js/directives/slideToAccept.js @@ -116,7 +116,8 @@ angular.module('copayApp.directives') function getTransformStyle(translatePct) { return { - 'transform': 'translateX(' + translatePct + '%)' + 'transform': 'translateX(' + translatePct + '%)', + '-webkit-transform': 'translateX(' + translatePct + '%)' }; } diff --git a/src/sass/views/tab-receive.scss b/src/sass/views/tab-receive.scss index 40dc79504..0b406254d 100644 --- a/src/sass/views/tab-receive.scss +++ b/src/sass/views/tab-receive.scss @@ -84,6 +84,9 @@ width: 100%; } .payment-received-container { + svg { + max-height: 400px; + } margin: 0 20px; .payment-received-amount { font-size: 1.8em; diff --git a/src/shim/shim.js b/src/shim/shim.js index 495848f05..6f9abc0ae 100644 --- a/src/shim/shim.js +++ b/src/shim/shim.js @@ -8,4 +8,56 @@ if (!ArrayBuffer['isView']) { ArrayBuffer.isView = function(a) { return a !== null && typeof(a) === "object" && a['buffer'] instanceof ArrayBuffer; }; +} + +// https://tc39.github.io/ecma262/#sec-array.prototype.includes +if (!Array.prototype.includes) { + Object.defineProperty(Array.prototype, 'includes', { + value: function(searchElement, fromIndex) { + + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + // 1. Let O be ? ToObject(this value). + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If len is 0, return false. + if (len === 0) { + return false; + } + + // 4. Let n be ? ToInteger(fromIndex). + // (If fromIndex is undefined, this step produces the value 0.) + var n = fromIndex | 0; + + // 5. If n ≥ 0, then + // a. Let k be n. + // 6. Else n < 0, + // a. Let k be len + n. + // b. If k < 0, let k be 0. + var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + + function sameValueZero(x, y) { + return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); + } + + // 7. Repeat, while k < len + while (k < len) { + // a. Let elementK be the result of ? Get(O, ! ToString(k)). + // b. If SameValueZero(searchElement, elementK) is true, return true. + if (sameValueZero(o[k], searchElement)) { + return true; + } + // c. Increase k by 1. + k++; + } + + // 8. Return false + return false; + } + }); } \ No newline at end of file From 176f0c3141ebdf0d8a267d16f938406ebe6cb715 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 6 Sep 2018 20:16:25 +0900 Subject: [PATCH 078/118] Fix to provide an amount (canSendMax was never set to false when shapeshift) --- src/js/controllers/amount.controller.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/js/controllers/amount.controller.js b/src/js/controllers/amount.controller.js index 0c3be6bd3..398872c03 100644 --- a/src/js/controllers/amount.controller.js +++ b/src/js/controllers/amount.controller.js @@ -109,14 +109,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, setAvailableUnits(); updateUnitUI(); - if (passthroughParams.thirdParty) { - vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object - if (vm.thirdParty) { - initShapeshift(); - } - } - - var reNr = /^[1234567890\.]$/; var reOp = /^[\*\+\-\/]$/; @@ -216,6 +208,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var fromWallet = profileService.getWallet(passthroughParams.fromWalletId); updateAvailableFundsFromWallet(fromWallet); } + + if (passthroughParams.thirdParty) { + vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object + if (vm.thirdParty) { + initShapeshift(); + } + } } } @@ -269,7 +268,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (canSendMax) { useSendMax = true; finish(); - } else { var transactionSendableAmountInUnits = transactionSendableAmount.satoshis * satToUnit; if (vm.minAmount && transactionSendableAmountInUnits < vm.minAmount) { @@ -286,7 +284,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } vm.amount = transactionSendableAmountInUnits.toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); finish(); - } } } @@ -526,7 +523,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, $state.transitionTo('tabs.paymentRequest.confirm', confirmData); } else { sendFlowService.goNext(confirmData); - $scope.useSendMax = null; + useSendMax = false; } } @@ -741,7 +738,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, console.log('sendmax Showing sendmax as all available as less than max limit.'); // Enabling send max here is a little dangerous, if they receive funds between pressing // this and the calculation in the Review screen. - canSendMax = true; + canSendMax = false; vm.showSendMaxButton = true; vm.showSendLimitMaxButton = false; transactionSendableAmount.satoshis = walletSpendableAmount.satoshis; From 828cb927dc919e76efc1eb5cb370be5dc3826455 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 6 Sep 2018 13:16:32 +0200 Subject: [PATCH 079/118] refactor latestReleaseService to new format --- ...seService.js => latest-release.service.js} | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) rename src/js/services/{latestReleaseService.js => latest-release.service.js} (82%) diff --git a/src/js/services/latestReleaseService.js b/src/js/services/latest-release.service.js similarity index 82% rename from src/js/services/latestReleaseService.js rename to src/js/services/latest-release.service.js index d853624a8..85c50bb03 100644 --- a/src/js/services/latestReleaseService.js +++ b/src/js/services/latest-release.service.js @@ -1,13 +1,26 @@ 'use strict'; -angular.module('copayApp.services') - .factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, $ionicPopup, configService, externalLinkService, gettextCatalog, platformInfo) { - var root = {}; +(function() { - root.checkLatestRelease = function(cb) { + angular + .module('bitcoincom.services') + .factory('latestReleaseService', latestReleaseService); + + function latestReleaseService($log, $http, $ionicPopup, configService, externalLinkService, gettextCatalog, platformInfo) { + + var service = { + // Functions + checkLatestRelease: checkLatestRelease, + requestLatestRelease: requestLatestRelease, + showUpdatePopup: showUpdatePopup + }; + + return service; + + function checkLatestRelease(cb) { var releaseURL = configService.getDefaults().release.url; - requestLatestRelease(releaseURL, function(err, releaseData) { + requestLatestRelease(releaseURL, function (err, releaseData) { if (err) return cb(err); var currentVersion = window.version; var latestVersion = releaseData.tag_name; @@ -45,15 +58,14 @@ angular.module('copayApp.services') $log.debug('A new version is available: ' + latestVersion); - // var releaseNotes = false; if (releaseData.body) { var releaseLines = releaseData.body.split('\n'); for (var lineNum in releaseLines) { if (releaseLines[lineNum].substring(0, 2) === "# ") { - releaseLines[lineNum] = ""+releaseLines[lineNum].substring(2)+""; + releaseLines[lineNum] = "" + releaseLines[lineNum].substring(2) + ""; } else if (releaseLines[lineNum].substring(0, 2) === "- ") { - releaseLines[lineNum] = "• "+releaseLines[lineNum].substring(2); + releaseLines[lineNum] = "• " + releaseLines[lineNum].substring(2); } } releaseNotes = releaseLines.join('\n'); @@ -76,13 +88,13 @@ angular.module('copayApp.services') var formattedNumber = tag.replace(/^v/i, '').split('.'); return { - major: +(formattedNumber[0]?+formattedNumber[0]:0), - minor: +(formattedNumber[1]?+formattedNumber[1]:0), - patch: +(formattedNumber[2]?+formattedNumber[2]:0), + major: +(formattedNumber[0] ? +formattedNumber[0] : 0), + minor: +(formattedNumber[1] ? +formattedNumber[1] : 0), + patch: +(formattedNumber[2] ? +formattedNumber[2] : 0), label: label /* XX SP: Maybe we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */ }; } - }; + } function requestLatestRelease(releaseURL, cb) { $log.debug('Retrieving latest release information...'); @@ -93,15 +105,15 @@ angular.module('copayApp.services') json: true }; - $http(request).then(function(release) { + $http(request).then(function (release) { $log.debug('Latest release: ' + release.data.name); return cb(null, release.data); - }, function(err) { + }, function (err) { return cb('Cannot get the release information: ' + err); }); } - root.showUpdatePopup = function () { + function showUpdatePopup() { var buttons = []; if (!platformInfo.isIOS) { // There is no GitHub-release for iPhone @@ -163,7 +175,6 @@ angular.module('copayApp.services') buttons: buttons }); } - }; - - return root; - }); + } + } +})(); \ No newline at end of file From 6a8f8ca33b3c723f4283bd23976add38c3da5d46 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 6 Sep 2018 20:29:50 +0900 Subject: [PATCH 080/118] Fix to send the amount if shapeshift case --- src/js/controllers/amount.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/amount.controller.js b/src/js/controllers/amount.controller.js index 398872c03..07f31bb3e 100644 --- a/src/js/controllers/amount.controller.js +++ b/src/js/controllers/amount.controller.js @@ -283,6 +283,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, unitIndex = tempIndex; } vm.amount = transactionSendableAmountInUnits.toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT); + useSendMax = true; finish(); } } @@ -507,7 +508,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } var confirmData = { - amount: useSendMax ? undefined : satoshis, + amount: (useSendMax && canSendMax) ? undefined : satoshis, displayAddress: passthroughParams.displayAddress, fromWalletId: passthroughParams.fromWalletId, sendMax: useSendMax, From 81852836dd71f14cd6fd93c4fa59fd3d43a7b912 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 7 Sep 2018 01:18:54 +0900 Subject: [PATCH 081/118] Fix : callback the newHistory --- src/js/services/walletService.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 6f0c93697..d29f99272 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -613,8 +613,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return storageService.setTxHistory(historyToSave, walletId, function() { $log.debug('Tx History saved.'); - - return cb(); + return cb(null, newHistory); }); }); }); @@ -691,8 +690,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return storageService.setTxHistory(historyToSave, walletId, function() { $log.debug('Tx History saved.'); - - return cb(); + return cb(null, newHistory); }); }); }); @@ -728,7 +726,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; root.getTx = function(wallet, txid, cb) { - function finish(list) { var tx = lodash.find(list, { txid: txid @@ -745,7 +742,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim limitTx: txid }, function(err, txHistory) { if (err) return cb(err); - finish(txHistory); }); } From 3d3fdd74256c2f968c06f58c0802f22c704f0103 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 7 Sep 2018 15:31:54 +0900 Subject: [PATCH 082/118] Fix languages --- src/js/services/uxLanguage.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/js/services/uxLanguage.js b/src/js/services/uxLanguage.js index 1fa446f01..b8e7c3b09 100644 --- a/src/js/services/uxLanguage.js +++ b/src/js/services/uxLanguage.js @@ -10,7 +10,7 @@ angular.module('copayApp.services') isoCode: 'en', rateCode: 'USD' }, { - name: 'català', + name: 'Català', isoCode: 'ca', rateCode: 'EUR' },{ @@ -59,10 +59,6 @@ angular.module('copayApp.services') name: 'Português', isoCode: 'pt', rateCode: 'EUR' - }, { - name: 'русский язык', - isoCode: 'ru', - rateCode: 'RUB' }, { name: '한국어', isoCode: 'ko', From 51c35101a6c10215173ef4f3c46a0063c6e94562 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 7 Sep 2018 17:08:39 +0900 Subject: [PATCH 083/118] appConfig beta release --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index e4d6c8a84..f9ae8ab3f 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.0.2", - "fullVersion": "5.0-rc3", - "androidVersion": "500200", + "version": "5.1.0", + "fullVersion": "5.1-beta1", + "androidVersion": "501000", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From 101e33ec367c3c3ea1e3103384b66bef986c5488 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 10 Sep 2018 20:26:13 +1200 Subject: [PATCH 084/118] Remove Games link on Android. --- src/js/services/bitcoincomService.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/services/bitcoincomService.js b/src/js/services/bitcoincomService.js index 68fd51a8a..62a75b4d1 100644 --- a/src/js/services/bitcoincomService.js +++ b/src/js/services/bitcoincomService.js @@ -85,7 +85,9 @@ angular.module('copayApp.services').factory('bitcoincomService', function(gettex }; var register = function() { - nextStepsService.register(cashGamesItem); + if (!platformInfo.isAndroid) { // To comply with Google Play policies + nextStepsService.register(cashGamesItem); + } nextStepsService.register(newsItem); nextStepsService.register(poolItem); nextStepsService.register(toolsItem); From 8bc73353d5154d9476d79fb57992cacfc6791f41 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 10 Sep 2018 20:26:33 +1200 Subject: [PATCH 085/118] Run command for Android emulator. --- Gruntfile.js | 4 ++++ app-template/package-template.json | 1 + 2 files changed, 5 insertions(+) diff --git a/Gruntfile.js b/Gruntfile.js index 160db85d5..1092b3de2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -65,6 +65,9 @@ module.exports = function(grunt) { run_android: { command: 'cordova run android --device', }, + run_android_emulator: { + command: 'cordova run android --emulator', + }, sign_android: { // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". // It looks like it simply lists all apk files starting with "android-release" @@ -370,6 +373,7 @@ module.exports = function(grunt) { // Build android grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android']); grunt.registerTask('build-android-debug', ['exec:build_android_debug']); + grunt.registerTask('start-android-emulator', ['build-android-debug', 'exec:run_android_emulator']); grunt.registerTask('build-android-release', ['prod', 'exec:build_android_release', 'sign-android']); grunt.registerTask('sign-android', ['exec:sign_android']); diff --git a/app-template/package-template.json b/app-template/package-template.json index 200c269f6..11b15bfcd 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -126,6 +126,7 @@ "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0", "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"", "start:android": "grunt start-android", + "start:android-emulator": "grunt start-android", "start:android-log": "grunt start-android && npm run log:android", "start:ios": "grunt start-ios", "start:windows": "npm run build:www && npm run build:windows", From c1f6e56b56cfc538265b62b4ede6749ec4cf79b1 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 11 Sep 2018 15:37:47 +1200 Subject: [PATCH 086/118] Increment version. --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index f9ae8ab3f..970432be2 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.1.0", - "fullVersion": "5.1-beta1", - "androidVersion": "501000", + "version": "5.1.1", + "fullVersion": "5.1-beta2", + "androidVersion": "501001", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From ad4aa76902325dc3a761dad47049742e77a29483 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 11 Sep 2018 14:55:22 +0200 Subject: [PATCH 087/118] Empty origin wallet screen bug --- .../controllers/wallet-selector.controller.js | 42 +++++++++---------- www/views/walletSelector.html | 2 +- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 06e6179da..3fe10b931 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $state, sendFlowService, configService, gettextCatalog, profileService, txFormatService) { +angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $state, sendFlowService, configService, gettextCatalog, ongoingProcess, profileService, walletService, txFormatService) { var fromWalletId = ''; var priceDisplayAsFiat = false; @@ -116,15 +116,27 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu if ($scope.type === 'origin') { $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); - if ($scope.params.amount) { + if ($scope.params.amount || $scope.coin) { walletsAll = profileService.getWallets({coin: $scope.coin}); - - walletsAll.forEach(function forWallet(wallet){ - if (wallet.status.availableBalanceSat > $scope.params.amount) { - walletsSufficientFunds.push(wallet); + ongoingProcess.set('scanning', true); + walletsAll.forEach(function forWallet(wallet) { + if (!wallet.status) { + walletService.getStatus(wallet, {}, function(err, status) { + if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + ongoingProcess.set('scanning', false); + }); } else { - $scope.walletsInsufficientFunds.push(wallet); + if (wallet.status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + ongoingProcess.set('scanning', false); } }); @@ -133,22 +145,6 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } else { $scope.walletsBch = walletsSufficientFunds; } - - } else if ($scope.coin) { - walletsAll = profileService.getWallets({coin: $scope.coin}); - walletsAll.forEach(function forWallet(wallet){ - if (wallet.status.availableBalanceSat > 0) { - walletsSufficientFunds.push(wallet); - } else { - $scope.walletsInsufficientFunds.push(wallet); - } - }); - - if ($scope.coin === 'btc') { - $scope.walletsBtc = walletsSufficientFunds; - } else { - $scope.walletsBch = walletsSufficientFunds; - } } else { $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true}); $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true}); diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index 2e4c4bc31..a9712adf9 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -13,7 +13,7 @@
{{requestAmountSecondary}} {{requestCurrencySecondary}}
-
+
{{headerTitle}}
From d55fc70e7d2d84a312bff06bc66d57af6ab10f71 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 12 Sep 2018 18:13:50 +1200 Subject: [PATCH 088/118] Remove Games link. --- src/js/services/bitcoincomService.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/services/bitcoincomService.js b/src/js/services/bitcoincomService.js index 68fd51a8a..20e326460 100644 --- a/src/js/services/bitcoincomService.js +++ b/src/js/services/bitcoincomService.js @@ -85,7 +85,9 @@ angular.module('copayApp.services').factory('bitcoincomService', function(gettex }; var register = function() { - nextStepsService.register(cashGamesItem); + if (!platformInfo.isAndroid) { + nextStepsService.register(cashGamesItem); + } nextStepsService.register(newsItem); nextStepsService.register(poolItem); nextStepsService.register(toolsItem); From 9438960e24b3cedf74a75d7235749e6006ef0ef7 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 12 Sep 2018 18:39:56 +1200 Subject: [PATCH 089/118] Hotfix version. --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 9a155362b..c65f67248 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.0.3", - "fullVersion": "5.0-hotfix1", - "androidVersion": "500300", + "version": "5.0.4", + "fullVersion": "5.0-hotfix2", + "androidVersion": "500400", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From 6692812d55bbd69e261af164e18f9eb1983ed78b Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 12 Sep 2018 15:09:21 +0200 Subject: [PATCH 090/118] Websocket reconnect (and close old connection on leave) --- src/js/controllers/tab-receive.js | 95 ++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 320afe320..6642c2e1c 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -24,6 +24,52 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi }); }; + + function connectSocket() { + //close existing socket if not connected with current address + if (currentAddressSocket && typeof currentAddressSocket.close === 'function') { + currentAddressSocket.onclose = function(e) {}; // Overwrite onclose-function to prevent reconnecting old address socket. + currentAddressSocket.close(); + } + if ($scope.wallet.coin === 'bch') { + // listen to bch address + currentAddressSocket = new WebSocket("wss://ws.blockchain.info/bch/inv"); + paymentSubscriptionObj.addr = $scope.addrBchLegacy; + } else { + // listen to btc address + currentAddressSocket = new WebSocket("wss://ws.blockchain.info/inv"); + paymentSubscriptionObj.addr = $scope.addr; + } + + // create subscription to address + var msg = JSON.stringify(paymentSubscriptionObj); + currentAddressSocket.onopen = function (event) { + currentAddressSocket.send(msg); + }; + + // listen for response + currentAddressSocket.onmessage = function (event) { + //console.log("message received:" + event.data); + receivedPayment(event.data); + }; + + currentAddressSocket.onclose = function(e) { + console.log('Socket is closed. Reconnect will be attempted in 1 second.'); + $timeout(function() { + connectSocket(); + }, 1000); + }; + + currentAddressSocket.onerror = function(err) { + console.error('Socket encountered error: ', err, 'Closing socket'); + currentAddressSocket.close(); + }; + + $timeout(function() { + $scope.$apply(); + }, 10); + } + $scope.setAddress = function(newAddr, copyAddress) { $scope.addr = null; if (!$scope.wallet || $scope.generatingAddress || !$scope.wallet.isComplete()) return; @@ -36,28 +82,16 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi popupService.showAlert(err); } - //close existing socket - if (currentAddressSocket.close === 'function') { - currentAddressSocket.close(); - } - - if ($scope.wallet.coin == 'bch') { - bchAddresses = bitcoinCashJsService.translateAddresses(addr); - $scope.addr = bchAddresses[$scope.bchAddressType.type]; - $scope.addrBchLegacy = bchAddresses['legacy']; - - // listen to bch address - currentAddressSocket = new WebSocket("wss://ws.blockchain.info/bch/inv"); - paymentSubscriptionObj.addr = bchAddresses['legacy']; - + if ($scope.wallet.coin === 'bch') { + bchAddresses = bitcoinCashJsService.translateAddresses(addr); + $scope.addr = bchAddresses[$scope.bchAddressType.type]; + $scope.addrBchLegacy = bchAddresses['legacy']; } else { - $scope.addr = addr; - - // listen to btc address - currentAddressSocket = new WebSocket("wss://ws.blockchain.info/inv"); - paymentSubscriptionObj.addr = $scope.addr + $scope.addr = addr; } + connectSocket(); + if (copyAddress === true) { try { clipboardService.copyToClipboard($scope.wallet.coin == 'bch' && $scope.bchAddressType.type == 'cashaddr' ? 'bitcoincash:' + $scope.addr : $scope.addr); @@ -66,19 +100,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi $log.debug(error); } } - // create subscription - var msg = JSON.stringify(paymentSubscriptionObj); - currentAddressSocket.onopen = function (event) { - //console.log("message sent: " + msg); - currentAddressSocket.send(msg); - } - - - // listen for response - currentAddressSocket.onmessage = function (event) { - //console.log("message received:" + event.data); - receivedPayment(event.data); - } $timeout(function() { $scope.$apply(); @@ -233,6 +254,16 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi } }; + $scope.$on("$ionicView.beforeLeave", function() { + // Close the old connection! + if (currentAddressSocket && typeof currentAddressSocket.close === 'function') { + console.log("Close open websocket address connection."); + currentAddressSocket.onclose = function(e) {}; // Overwrite onclose-function to prevent reconnecting old address socket. + currentAddressSocket.close(); + } + + }); + $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.wallets = profileService.getWallets(); $scope.singleWallet = $scope.wallets.length == 1; From 908dcbf9680a32ab1dcb8add6a3c085aca2b6451 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 12 Sep 2018 16:50:26 +0200 Subject: [PATCH 091/118] Add the received wallet status to the wallet API object --- src/js/controllers/wallet-selector.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 3fe10b931..c0210213f 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -123,6 +123,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu walletsAll.forEach(function forWallet(wallet) { if (!wallet.status) { walletService.getStatus(wallet, {}, function(err, status) { + wallet.status = status; if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { walletsSufficientFunds.push(wallet); } else { From eb3067d369cbc0de58b16715c2c4d4fc97b69c10 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 13 Sep 2018 09:47:45 +0200 Subject: [PATCH 092/118] Use cachedStatus when available and a fix for the review controller --- src/js/controllers/review.controller.js | 4 ++-- src/js/controllers/wallet-selector.controller.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 15f982f2f..b8379b769 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -392,12 +392,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit var balanceCryptoAmount = ''; var balanceCryptoCurrencyCode = ''; var balanceFiatAmount = ''; - var balanceFiatCurrency = '' + var balanceFiatCurrency = ''; var displayAmount = ''; var displayCurrency = ''; var walletStatus = null; - if (wallet.status.isValid) { + if (wallet.status && wallet.status.isValid) { walletStatus = wallet.status; } else if (wallet.cachedStatus.isValid) { walletStatus = wallet.cachedStatus; diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index c0210213f..62ac6a57a 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -121,7 +121,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu walletsAll = profileService.getWallets({coin: $scope.coin}); ongoingProcess.set('scanning', true); walletsAll.forEach(function forWallet(wallet) { - if (!wallet.status) { + if (!wallet.status && !wallet.cachedStatus) { walletService.getStatus(wallet, {}, function(err, status) { wallet.status = status; if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { @@ -132,7 +132,14 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu ongoingProcess.set('scanning', false); }); } else { - if (wallet.status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { + var walletStatus = null; + if (wallet.status && wallet.status.isValid) { + walletStatus = wallet.status; + } else if (wallet.cachedStatus) { + walletStatus = wallet.cachedStatus; + } + + if (walletStatus.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { walletsSufficientFunds.push(wallet); } else { $scope.walletsInsufficientFunds.push(wallet); From e6d8ddb360d64f7d6c23e8ef45dfbba0d29ed305 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 13 Sep 2018 09:54:53 +0200 Subject: [PATCH 093/118] update wallets in the promise callback --- src/js/controllers/wallet-selector.controller.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 62ac6a57a..1a191870d 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -129,6 +129,11 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } else { $scope.walletsInsufficientFunds.push(wallet); } + if ($scope.coin === 'btc') { // As this is a promise + $scope.walletsBtc = walletsSufficientFunds; + } else { + $scope.walletsBch = walletsSufficientFunds; + } ongoingProcess.set('scanning', false); }); } else { From ac82b51848235b4b39f41385bbb993407a582dee Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 15 Sep 2018 14:45:39 +1200 Subject: [PATCH 094/118] Takes into account size of current transaction list when determining if overlap in transactions is enough to assume all have been retrieved. --- src/js/services/wallet-history.service.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index e10e763e9..7a6f18a13 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -12,9 +12,9 @@ // How much to overlap on each end of the page, for mitigating inconsistent sort order. var PAGE_OVERLAP_FRACTION = 0.2; var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION); - // The amount of transactions in the new overlapping resultset that we already know about. + // The fraction of transactions in the new overlapping resultset that we already know about. // If we know about at least this many, then there are probably no gaps. - var MIN_KNOWN_TX_OVERLAP = Math.floor(PAGE_OVERLAP * 0.5); + var MIN_KNOWN_TX_OVERLAP_FRACTION = 0.5; var SAFE_CONFIRMATIONS = 6; @@ -44,7 +44,10 @@ } }); - if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good + var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP); + console.log('overlappingTxFraction:', overlappingTxFraction); + + if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good if (someTransactionsWereNew) { saveTxHistory(walletId, cachedTxs); } else if (overlappingTxsCount === newTxs.length) { @@ -80,7 +83,9 @@ } }); - if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good + var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP); + + if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good if (someTransactionsWereNew) { var allTxs = uniqueNewTxs.concat(cachedTxs); saveTxHistory(walletId, allTxs); From 8bf31f97d8034e83d434ea181f51a6d3024de25f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 15 Sep 2018 18:00:34 +1200 Subject: [PATCH 095/118] Updated translations from Crowdin. --- i18n/po/ca/template-ca.po | 87 ++++++++++++++++++++++++++- i18n/po/cs/template-cs.po | 93 ++++++++++++++++++++++++++-- i18n/po/de/template-de.po | 87 ++++++++++++++++++++++++++- i18n/po/es-ES/template-es-ES.po | 91 ++++++++++++++++++++++++++-- i18n/po/fa/template-fa.po | 89 +++++++++++++++++++++++++-- i18n/po/fr/template-fr.po | 91 ++++++++++++++++++++++++++-- i18n/po/it/template-it.po | 87 ++++++++++++++++++++++++++- i18n/po/ja/template-ja.po | 89 +++++++++++++++++++++++++-- i18n/po/ko/template-ko.po | 93 ++++++++++++++++++++++++++-- i18n/po/nl/template-nl.po | 91 ++++++++++++++++++++++++++-- i18n/po/pl/template-pl.po | 87 ++++++++++++++++++++++++++- i18n/po/pt-BR/template-pt-BR.po | 97 +++++++++++++++++++++++++++--- i18n/po/ru/template-ru.po | 87 ++++++++++++++++++++++++++- i18n/po/sv-SE/template-sv-SE.po | 87 ++++++++++++++++++++++++++- i18n/po/vi/template-vi.po | 103 ++++++++++++++++++++++++++++---- i18n/po/zh-CN/template-zh-CN.po | 89 +++++++++++++++++++++++++-- 16 files changed, 1372 insertions(+), 76 deletions(-) diff --git a/i18n/po/ca/template-ca.po b/i18n/po/ca/template-ca.po index 54c5202f2..0fa97d8ab 100644 --- a/i18n/po/ca/template-ca.po +++ b/i18n/po/ca/template-ca.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Catalan\n" "Language: ca\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "No es pot crear la cartera" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "No us podeu unir a la mateixa cartera més d'un cop" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Escombratge" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Escombra la cartera de paper" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Escombra la cartera de paper" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "S'està escombrant la cartera..." @@ -3792,6 +3797,10 @@ msgstr "Explora Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Jocs de Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Comparteix l'aplicació de la cartera" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Notícies" @@ -3860,3 +3869,75 @@ msgstr "Menys d'1 cèntim" msgid "This invoice is no longer accepting payments" msgstr "Aquesta factura ja no accepta pagaments" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Envia la quantitat màxima" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Error desconegut." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "No hi ha cap cartera de Bitcoin Cash per transferir els fons trobats." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "No s'ha trobat cap Bitcoin Cash." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "S'ha trobat Bitcoin Core:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "No hi ha cap cartera de Bitcoin Core per transferir els fons trobats." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "No s'ha trobat cap Bitcoin Core." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "L'escaneig ha fallat" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "No s'han reconegut les dades." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "No compatible" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet no és compatible." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Obre a l'explorador web" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Adreça no vàlida" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "No s'ha definit l'import" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "L'import és inferior al mínim" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "L'import supera el límit" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Resposta no vàlida de Shapeshift" + diff --git a/i18n/po/cs/template-cs.po b/i18n/po/cs/template-cs.po index 9627e2119..970b81daf 100644 --- a/i18n/po/cs/template-cs.po +++ b/i18n/po/cs/template-cs.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Czech\n" "Language: cs\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,8 +520,8 @@ msgid "Cannot Create Wallet" msgstr "Není možné vytvořit peněženku" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" -msgstr "Není možné několikrát sloučit stejnou peněženku" +msgid "Cannot join the same wallet more than once" +msgstr "Nelze se připojit vícekrát ke stejné peněžence" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" @@ -1297,7 +1297,7 @@ msgstr "Nastavení filtru" #: src/js/services/fingerprintService.js:43 #: src/js/services/fingerprintService.js:48 msgid "Finger Scan Failed" -msgstr "Scan otisku prstu se nezdařil" +msgstr "Sken otisku prstu se nezdařil" #: src/js/controllers/feedback/send.js:34 #: www/views/feedback/complete.html:7 @@ -1667,7 +1667,7 @@ msgstr "Neplatná odvozovací cesta" #: src/js/controllers/copayers.js:90 msgid "Invitation to share a {{appName}} Wallet" -msgstr "Pozvání ke sdílení {{appName}} peněženky" +msgstr "Pozvánka ke sdílení {{appName}} peněženky" #: www/views/mercadoLibreCards.html:20 #: www/views/modals/mercadolibre-card-details.html:48 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Sweep" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Sweep papírové peněženky" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Sweep papírové peněženky" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "Probíhá sweep peněženky..." @@ -3792,6 +3797,10 @@ msgstr "Prohlédnout Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash hry" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Sdílet aplikaci peněženky" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Novinky" @@ -3860,3 +3869,75 @@ msgstr "Méně než 1 koruna" msgid "This invoice is no longer accepting payments" msgstr "Tato faktura již nepřijímá platby" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Odeslat maximální částku" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Neznámá chyba." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Žádná Bitcoin Cash peněženka pro transfer nalezených prostředků." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Žádný Bitcoin Cash nenalezen." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Nalezen Bitcoin Core:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Žádná Bitcoin Core peněženka pro transfer nalezených prostředků." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Žádný Bitcoin Core nenalezen." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Sken selhal" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Data nebyla rozpoznána." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Nepodporováno" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet není podporován." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Otevřít ve webovém prohlížeči" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Neplatná adresa" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Částka nedefinována" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Nižší než minimální částka" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Nadlimitní částka" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Neplatná odpověď z Shapeshift" + diff --git a/i18n/po/de/template-de.po b/i18n/po/de/template-de.po index e15683a63..d2ea6cc62 100644 --- a/i18n/po/de/template-de.po +++ b/i18n/po/de/template-de.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: German\n" "Language: de\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "Wallet kann nicht erstellt werden" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "An einem Wallet kann nicht mehrfach teilgenommen werden" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Leeren" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Paperwallet löschen" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Paperwallet löschen" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "Leere Wallet..." @@ -3792,6 +3797,10 @@ msgstr "Erkunden Sie Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash Spiele" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Die Wallet-App teilen" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Neuigkeiten" @@ -3860,3 +3869,75 @@ msgstr "Weniger als 1 Cent" msgid "This invoice is no longer accepting payments" msgstr "Diese Rechnung akzeptiert keine Zahlungen mehr" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Maximalen Betrag senden" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Unbekannter Fehler." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Keine Bitcoin Cash Wallet, zu welchem Geld verschickt werden kann, gefunden." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Kein Bitcoin Cash gefunden." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core gefunden:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Keine Bitcoin Core Wallet, zu welchem Geld verschickt werden kann, gefunden." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Kein Bitcoin Core gefunden." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Scan ist fehlgeschlagen" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Daten nicht erkannt." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Nicht unterstützt" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet wird nicht unterstützt." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Im Webbrowser öffnen" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Ungültige Adresse" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Betrag ist nicht definiert" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Menge liegt unter dem Minimum" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Betrag ist über dem Grenzwert" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Ungültige Antwort von Shapeshift" + diff --git a/i18n/po/es-ES/template-es-ES.po b/i18n/po/es-ES/template-es-ES.po index 76f004f41..2a22d3593 100644 --- a/i18n/po/es-ES/template-es-ES.po +++ b/i18n/po/es-ES/template-es-ES.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Spanish\n" "Language: es\n" -"PO-Revision-Date: 2018-08-21 03:40\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "No se pudo crear la billetera" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "No puede unirse a la misma billetera más de una vez" #: www/views/includes/bitpayCardsCard.html:2 @@ -2674,7 +2674,7 @@ msgstr "Guardar las direcciones que usas frecuentemente y envía Bitcoin en un c #: www/views/tab-send.html:55 msgid "Add your first contact" -msgstr "Añadie tu primer contacto" +msgstr "Añade tu primer contacto" #: www/views/tab-send.html:65 msgid "Your Bitcoin wallet is empty" @@ -2925,9 +2925,14 @@ msgid "Sweep" msgstr "Importar" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" -msgstr "Importar billetera en papel" +msgstr "Importar billetera de papel" + +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Importar billetera de papel" #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." @@ -3792,6 +3797,10 @@ msgstr "Explora Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Juegos de Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Comparte esta app" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Noticias" @@ -3860,3 +3869,75 @@ msgstr "Menos de 1 centavo" msgid "This invoice is no longer accepting payments" msgstr "Esta factura ya no está aceptando pagos" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Enviar cantidad máxima" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Error desconocido." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "No se encontró billetera BCH para transferir estos fondos." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "No se encontró Bitcoin Cash." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core encontrado:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "No se encontró billetera BTC para transferir estos fondos." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "No se encontró Bitcoin Core." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Falló el escaneado" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Datos no reconocidos." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "No compatible" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet no es compatible." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Abrir en navegador web" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Dirección inválida" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Cantidad no definida" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Cantidad por debajo del mínimo" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Cantidad por encima del límite" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Respuesta inválida de Shapeshift" + diff --git a/i18n/po/fa/template-fa.po b/i18n/po/fa/template-fa.po index 4be7a1e8d..d2b460320 100644 --- a/i18n/po/fa/template-fa.po +++ b/i18n/po/fa/template-fa.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Persian\n" "Language: fa\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "قادر به ایجاد کیف پول نیست" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "امکان بیش از یک بار پیوستن به یک کیف پول نیست" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,9 +2925,14 @@ msgid "Sweep" msgstr "وارد کردن و تغییر آدرس به جدید(sweep)" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" -msgstr "وارد کردن والت کاغذی و تغییر آدرس به جدید(sweep)" +msgstr "جاروب کردن کیف پول کاغذی" + +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "جاروب کردن کیف پول کاغذی" #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." @@ -3792,6 +3797,10 @@ msgstr "کاوش Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash Games" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "اشتراک گذاری نرم افزار کیف پول" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "اخبار" @@ -3860,3 +3869,75 @@ msgstr "کمتر از 1 سنت" msgid "This invoice is no longer accepting payments" msgstr "این صورت حساب دیگر پرداخت ها را نمی پذیرد" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "ارسال حداکثر مقدار" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "خطای ناشناخته." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "هیچ کیف پولی از نوع Bitcoin Cash برای انتقال وجه یافت نشد." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Bitcoin Cash یافت نشد." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core یافت شده:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "هیچ کیف پولی از نوع Bitcoin Core برای انتقال وجه یافت نشد." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Bitcoin Core یافت نشد." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "اسکن انجام نشد" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "داده ها شناسایی نشد." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "پشتیبانی نشده" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet پشتیبانی نمی شود." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "آدرس" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "باز کردن در مرورگر" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "آدرس نامعتبر" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "مبلغ تعریف نشده است" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "مقدار زیر مقدار حداقل است" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "مقدار بالاتر از حد است" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "پاسخ نامعتبر از Shapeshift" + diff --git a/i18n/po/fr/template-fr.po b/i18n/po/fr/template-fr.po index aa22631e2..ba3d98244 100644 --- a/i18n/po/fr/template-fr.po +++ b/i18n/po/fr/template-fr.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: French\n" "Language: fr\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,8 +520,8 @@ msgid "Cannot Create Wallet" msgstr "Impossible de créer le portefeuille" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" -msgstr "Impossible de rejoindre le même portefeuille plus d'une fois" +msgid "Cannot join the same wallet more than once" +msgstr "Impossible de joindre le même portefeuille plus d'une fois" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" @@ -2925,9 +2925,14 @@ msgid "Sweep" msgstr "Balayer" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" -msgstr "Balayer un portefeuille de papier" +msgstr "Balayer un portefeuille papier" + +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Balayer un portefeuille papier" #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." @@ -3792,6 +3797,10 @@ msgstr "Explorez Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Jeux Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Partager l’application Wallet" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Nouvelles" @@ -3860,3 +3869,75 @@ msgstr "Moins de 1 centime" msgid "This invoice is no longer accepting payments" msgstr "Cette facture n’accepte plus les paiements" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Envoi du montant maximal" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Erreur inconnue." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Aucun portefeuille Bitcoin Cash auquel transférer des fonds trouvé." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Pas de Bitcoin Cash trouvé." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core trouvé :" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Aucun portefeuille Bitcoin Core auquel transférer des fonds trouvé." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Pas de Bitcoin Core trouvé." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Échec de l'analyse" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Données non reconnues." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Non pris en charge" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet n’est pas pris en charge." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Ouvrir dans le navigateur web" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Adresse invalide" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Le montant n’est pas défini" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Le montant est inférieur au minimum" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Le montant est supérieur à la limite" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Réponse de Shapeshift invalide" + diff --git a/i18n/po/it/template-it.po b/i18n/po/it/template-it.po index d0ac86478..0ea7daa0a 100644 --- a/i18n/po/it/template-it.po +++ b/i18n/po/it/template-it.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Italian\n" "Language: it\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "Impossibile creare portafoglio" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "Non è possibile aggiungere un portafoglio più di una volta" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Spazzola" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Spazzare il portafoglio di carta" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Spazzare il portafoglio di carta" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "Spazzolamento Portafoglio..." @@ -3792,6 +3797,10 @@ msgstr "Esplora Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Giochi Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Condividere l'app Portafoglio" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "News" @@ -3860,3 +3869,75 @@ msgstr "Meno di 1 centesimo" msgid "This invoice is no longer accepting payments" msgstr "Questa fattura non accetta più pagamenti" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Inviare importo massimo" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Errore sconosciuto." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Nessun portafoglio Bitcoin Cash trovato per il trasferimento di fondi." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Nessun Bitcoin Cash trovato." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core trovato:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Nessun portafoglio Bitcoin Core trovato per il trasferimento di fondi." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Nessun Bitcoin Core trovato." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Scansione non riuscita" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Dati non riconosciuti." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Non supportato" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet non supportato." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Aprire nel browser web" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Indirizzo non valido" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Importo non definito" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Importo inferiore al minimo" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Importo sopra il limite" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Risposta non valida da Shapeshift" + diff --git a/i18n/po/ja/template-ja.po b/i18n/po/ja/template-ja.po index 7fb88de4f..896e8b06c 100644 --- a/i18n/po/ja/template-ja.po +++ b/i18n/po/ja/template-ja.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Japanese\n" "Language: ja\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -522,8 +522,8 @@ msgid "Cannot Create Wallet" msgstr "ウォレットを作成できません。" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" -msgstr "同じ端末で同じウォレットに複数回参加することができません。" +msgid "Cannot join the same wallet more than once" +msgstr "同じ端末で同じウォレットに複数回参加することはできません。" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" @@ -2929,10 +2929,15 @@ msgid "Sweep" msgstr "全残高インポート" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "ペーパーウォレットの全残高インポート" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "ペーパーウォレットの全残高インポート" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "ビットコイン回収中…" @@ -3798,6 +3803,10 @@ msgstr "Bitcoin.com を参照" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash ゲーム" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "ウォレットアプリを共有" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "ニュース" @@ -3866,3 +3875,75 @@ msgstr "1セント以下" msgid "This invoice is no longer accepting payments" msgstr "この請求書はもう支払を受け付けていません" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "全残高を送金" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "不明なエラーです。" + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "資金を送金できるビットコインキャッシュウォレットが見つかりません。" + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "ビットコインキャッシュが見つかりません。" + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "ビットコインが見つかりました:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "資金を送金できるビットコインウォレットが見つかりません。" + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "ビットコインが見つかりません。" + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "スキャンできませんでした" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "データが認識されていません。" + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "未対応" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet には対応していません。" + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "ウェブブラウザで開く" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "無効なアドレス" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "金額が定義されていません" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "金額が最少額を下回っています" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "金額が上限を超えています" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Shapeshift から無効な応答がありました" + diff --git a/i18n/po/ko/template-ko.po b/i18n/po/ko/template-ko.po index 815608a50..490acef30 100644 --- a/i18n/po/ko/template-ko.po +++ b/i18n/po/ko/template-ko.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Korean\n" "Language: ko\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -233,12 +233,12 @@ msgstr "가격 표시" #: src/js/controllers/tab-settings.js:19 #: www/views/preferencesPriceDisplay.html:12 msgid "Fiat" -msgstr "고정비" +msgstr "법정 화폐" #: src/js/controllers/tab-settings.js:19 #: www/views/preferencesPriceDisplay.html:15 msgid "Cryptocurrency" -msgstr "가상화폐" +msgstr "가상 화폐" #: src/js/controllers/buyAmazon.js:98 msgid "Amazon.com is not available at this moment. Please try back later." @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "지갑을 생성할 수 없습니다" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "같은 지갑에 한 번 이상 접속할 수 없습니다" #: www/views/includes/bitpayCardsCard.html:2 @@ -2658,7 +2658,7 @@ msgstr "주소 붙여넣기" #: www/views/tab-send.html:27 msgid "Transfer between wallets" -msgstr "월릿 간 전송" +msgstr "지갑 간 전송" #: www/views/tab-send.html:35 msgid "Scan QR Code" @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "스윕하기" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "종이 지갑 스윕하기" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "종이 지갑 스윕하기" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "지갑 스윕 중..." @@ -3792,6 +3797,10 @@ msgstr "Bitcoin.com 탐색" msgid "Bitcoin Cash Games" msgstr "BCH 게임" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "지갑 응용 프로그램 공유" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "소식" @@ -3860,3 +3869,75 @@ msgstr "1센트 미만" msgid "This invoice is no longer accepting payments" msgstr "이 청구서는 더 이상 결제를 수락하지 않습니다" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "최대 수량 보내는 중" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "알 수 없는 오류가 발생 했습니다." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "자금 찾을 없습니다 Bitcoin Cash 현금 지갑." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "찾을 수 없습니다 Bitcoin Cash 현금." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core 코어 발견:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "자금 찾을 없습니다 Bitcoin Core 현금 지갑." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "찾을 수 없습니다 Bitcoin Core 현금." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "검색 실패" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "데이터 인식 되지입니다." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "지원 되지 않는" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet는 지원 되지 않습니다." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "웹 브라우저에서 열기" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "주소 오류" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "금액은 정의 되지 않은" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Minimun 아래 금액은" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "제한 위에 금액은" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "변신에서 잘못 된 응답" + diff --git a/i18n/po/nl/template-nl.po b/i18n/po/nl/template-nl.po index 15e0cdde1..a42a1b370 100644 --- a/i18n/po/nl/template-nl.po +++ b/i18n/po/nl/template-nl.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Dutch\n" "Language: nl\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,8 +520,8 @@ msgid "Cannot Create Wallet" msgstr "Kan Portemonnee Niet Aanmaken" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" -msgstr "Kan niet meerdere keren tegelijk deelnemen aan een portemonnee" +msgid "Cannot join the same wallet more than once" +msgstr "Je kan niet meerdere keren tegelijk aan een portemonnee deelnemen" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" @@ -2925,9 +2925,14 @@ msgid "Sweep" msgstr "Saldo opnemen" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" -msgstr "Saldo papieren portemonnee opnemen" +msgstr "Paper wallet leeghalen" + +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Paper wallet leeghalen" #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." @@ -3792,6 +3797,10 @@ msgstr "Bitcoin.com verkennen" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash spellen" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Wallet app delen" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Nieuws" @@ -3860,3 +3869,75 @@ msgstr "Minder dan 1 cent" msgid "This invoice is no longer accepting payments" msgstr "Deze factuur accepteert geen betalingen meer" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Alles versturen" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Onbekende fout." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Geen Bitcoin Cash portemonnee gevonden om bedrag naartoe over te maken." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Geen Bitcoin Cash gevonden." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core gevonden:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Geen Bitcoin Core portemonnee gevonden om waarde naartoe te versturen." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Geen Bitcoin Core gevonden." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Scannen mislukt" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Gegevens niet herkend." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Niet ondersteund" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet wordt niet ondersteund." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Open in webbrowser" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Ongeldig adres" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Bedrag is niet gespecificeerd" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Bedrag lager is dan het minimum" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Bedrag is boven het limiet" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Ongeldig respons van Shapeshift" + diff --git a/i18n/po/pl/template-pl.po b/i18n/po/pl/template-pl.po index 584984472..63e8e47ee 100644 --- a/i18n/po/pl/template-pl.po +++ b/i18n/po/pl/template-pl.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Polish\n" "Language: pl\n" -"PO-Revision-Date: 2018-08-21 03:40\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "Nie można utworzyć portfela" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "Nie można dołączyć tego samego portfela więcej niż raz" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Opróżnij" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Wyczyść papierowy portfel" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Wyczyść papierowy portfel" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "Sczytywanie portfela..." @@ -3792,6 +3797,10 @@ msgstr "Poznaj Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Gry Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Udostępnij aplikację Portfel" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Aktualności" @@ -3860,3 +3869,75 @@ msgstr "Mniej niż 1 cent" msgid "This invoice is no longer accepting payments" msgstr "Nie można już opłacić tej faktury" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Wyślij maksymalną kwotę" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Nieznany błąd." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Nie znaleziono portfela Bitcoin Cash do przekazania środków." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Nie znaleziono Bitcoin Cash." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Znaleziono Bitcoin Core:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Nie znaleziono portfela Bitcoin Core do przekazania środków." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Nie znaleziono Bitcoin Core." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Skanowanie nie powiodło się" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Nie rozpoznano danych." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Nieobsługiwane" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Sieć testowa nie jest obsługiwana." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "Adres URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Otwórz w przeglądarce internetowej" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Nieprawidłowy adres" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Kwota nie jest określona" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Kwota niższa od minimalnej" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Kwota przekracza limit" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Nieprawidłowa odpowiedź z Shapeshift" + diff --git a/i18n/po/pt-BR/template-pt-BR.po b/i18n/po/pt-BR/template-pt-BR.po index 4dec70b33..7d15602e7 100644 --- a/i18n/po/pt-BR/template-pt-BR.po +++ b/i18n/po/pt-BR/template-pt-BR.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Portuguese, Brazilian\n" "Language: pt\n" -"PO-Revision-Date: 2018-08-21 03:40\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,8 +520,8 @@ msgid "Cannot Create Wallet" msgstr "Não é possível criar a carteira" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" -msgstr "Não pode juntar-se a mesma carteira mais que uma vez" +msgid "Cannot join the same wallet more than once" +msgstr "Não pode juntar-se à mesma carteira mais que uma vez" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" @@ -1623,7 +1623,7 @@ msgstr "Endereço de rede incorreto" #: src/js/controllers/confirm.js:306 #: src/js/services/bwcError.js:44 msgid "Insufficient confirmed funds" -msgstr "Insuficiência de fundos confirmados" +msgstr "Insuficiência de fundos confirmada" #: src/js/controllers/topup.js:165 #: src/js/controllers/topup.js:177 @@ -2090,7 +2090,7 @@ msgstr "Abrir Projeto no GitHub" #: src/js/controllers/bitpayCard.js:123 #: src/js/controllers/tx-details.js:192 msgid "Open Explorer" -msgstr "Abra o Explorer" +msgstr "Abrir o Explorer" #: www/views/tab-scan.html:22 msgid "Open Settings" @@ -2222,7 +2222,7 @@ msgstr "Detalhes do pagamento" #: www/views/modals/paypro.html:6 msgid "Payment Request" -msgstr "Pedido de pagamento" +msgstr "Solicitação de pagamento" #: www/views/mercadoLibreCards.html:22 #: www/views/modals/mercadolibre-card-details.html:39 @@ -2696,7 +2696,7 @@ msgstr "Para começar, você precisa criar uma carteira de bitcoins e obter algu #: www/views/tab-send.html:74 msgid "Buy Bitcoin now" -msgstr "Compre Bitcoin agora" +msgstr "Comprar Bitcoin agora" #: www/views/tab-send.html:76 msgid "Show my address" @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Limpar" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Varrer a carteira de papel" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Varrer a carteira de papel" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "Carteira de varredura..." @@ -3792,6 +3797,10 @@ msgstr "Explore Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Jogos Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Compartilhar o app de carteira" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Notícias" @@ -3860,3 +3869,75 @@ msgstr "Menos de 1 centavo" msgid "This invoice is no longer accepting payments" msgstr "Essa fatura não aceita mais pagamentos" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Enviar montante máximo" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Erro desconhecido." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Nenhuma carteira de Bitcoin Cash para transferir fundos encontrada." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Bitcoin Cash não encontrado." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Bitcoin Core encontrado:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Nenhuma carteira de Bitcoin Core para transferir fundos encontrada." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Nenhum Bitcoin Core encontrado." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Falha de verificação" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Dados não reconhecidos." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Não suportado" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet não suportado." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Abrir no navegador da Web" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Endereço inválido" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Montante não definido" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Montante abaixo do mínimo" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Montante acima do limite" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Resposta inválida do Shapeshift" + diff --git a/i18n/po/ru/template-ru.po b/i18n/po/ru/template-ru.po index ffdc6c0df..cb4ec3a36 100644 --- a/i18n/po/ru/template-ru.po +++ b/i18n/po/ru/template-ru.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Russian\n" "Language: ru\n" -"PO-Revision-Date: 2018-08-21 03:40\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "Не удаётся создать кошелёк" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "Нельзя присоединиться к одному и тому же кошельку более одного раза" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "Считать" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "Пополнить с бумажного кошелька" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Пополнить с бумажного кошелька" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "Считывание кошелька..." @@ -3792,6 +3797,10 @@ msgstr "Обзор Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Игры Bitcoin Cash" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "Поделиться программой «Кошелёк»" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "Новости" @@ -3860,3 +3869,75 @@ msgstr "менее 1 копейки" msgid "This invoice is no longer accepting payments" msgstr "По этому инвойсу платежи больше не принимаются" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "Отправить максимальную сумму" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "Неизвестная ошибка." + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "Не найден кошелек Bitcoin Cash для перевода средств." + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "Не найден Bitcoin Cash." + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "Найден Bitcoin Core:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "Не найден кошелек Bitcoin Core для перевода средств." + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "Не найден Bitcoin Core." + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "Ошибка сканирования" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "Данные не распознаны." + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "Не поддерживается" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "Testnet не поддерживается." + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "URL-адрес" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "Открыть в веб-браузере" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "Неверный адрес" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "Сумма не задана" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "Сумма ниже минимума" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "Сумма больше ограничения" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Неправильный ответ от Shapeshift" + diff --git a/i18n/po/sv-SE/template-sv-SE.po b/i18n/po/sv-SE/template-sv-SE.po index a4eeaada7..b80fca04b 100644 --- a/i18n/po/sv-SE/template-sv-SE.po +++ b/i18n/po/sv-SE/template-sv-SE.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Swedish\n" "Language: sv\n" -"PO-Revision-Date: 2018-08-21 03:40\n" +"PO-Revision-Date: 2018-09-06 16:45\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "Kan inte skapa plånbok" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,10 +2925,15 @@ msgid "Sweep" msgstr "" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" msgstr "" +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "" + #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." msgstr "" @@ -3792,6 +3797,10 @@ msgstr "" msgid "Bitcoin Cash Games" msgstr "" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "" @@ -3860,3 +3869,75 @@ msgstr "" msgid "This invoice is no longer accepting payments" msgstr "" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "" + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "" + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "" + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "" + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "" + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "" + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "" + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "" + diff --git a/i18n/po/vi/template-vi.po b/i18n/po/vi/template-vi.po index 3bdc71b26..29e62e498 100644 --- a/i18n/po/vi/template-vi.po +++ b/i18n/po/vi/template-vi.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Vietnamese\n" "Language: vi\n" -"PO-Revision-Date: 2018-08-21 03:40\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -36,7 +36,7 @@ msgstr "{{tx.feeRateStr}} của giao dịch" #: www/views/feedback/rateApp.html:7 msgid "5-star ratings help us get {{appName}} into more hands, and more users means more resources can be committed to the app!" -msgstr "5-star ratings help us get {{appName}} into more hands, and more users means more resources can be committed to the app!" +msgstr "{{appName}} 5-star ratings help us get {{appName}} into more hands, and more users means more resources can be committed to the app!" #: www/views/mercadoLibre.html:18 #: www/views/mercadoLibre.html:40 @@ -56,8 +56,8 @@ msgstr "A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size all #: src/js/controllers/confirm.js:395 msgid "A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided." -msgstr "Tổng cộng {{amountBelowFeeStr}} đã bị loại trừ. Số tiền này đến từ UTXOs nhỏ hơn chi phí mạng cung cấp.#\n" -"1" +msgstr "{{amountBelowFeeStr}} Tổng cộng {{amountBelowFeeStr}} đã bị loại trừ. Số tiền này đến từ UTXOs nhỏ hơn chi phí mạng cung cấp.#\n" +"1." #: src/js/controllers/preferencesAbout.js:6 #: www/views/tab-settings.html:156 @@ -67,7 +67,7 @@ msgstr "About" #: src/js/controllers/modals/txpDetails.js:62 #: src/js/controllers/tx-details.js:79 msgid "Accepted" -msgstr "Accepted" +msgstr "Chấp nhận" #: www/views/preferencesInformation.html:72 msgid "Account" @@ -78,7 +78,7 @@ msgstr "Account" #: www/views/tab-create-shared.html:74 #: www/views/tab-import-hardware.html:19 msgid "Account Number" -msgstr "Account Number" +msgstr "Account Number0941162662" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" @@ -241,7 +241,7 @@ msgstr "Fiat" #: src/js/controllers/tab-settings.js:19 #: www/views/preferencesPriceDisplay.html:15 msgid "Cryptocurrency" -msgstr "Cryptocurrency" +msgstr "Cryptocurrencyg" #: src/js/controllers/buyAmazon.js:98 msgid "Amazon.com is not available at this moment. Please try back later." @@ -523,8 +523,8 @@ msgid "Cannot Create Wallet" msgstr "Cannot Create Wallet" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" -msgstr "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" +msgstr "" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" @@ -2928,9 +2928,14 @@ msgid "Sweep" msgstr "Sweep" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" -msgstr "Sweep paper wallet" +msgstr "" + +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "Sweep Paper Wallet" #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." @@ -3795,6 +3800,10 @@ msgstr "Explore Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash Games" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "News" @@ -3863,3 +3872,75 @@ msgstr "Less than 1 cent" msgid "This invoice is no longer accepting payments" msgstr "This invoice is no longer accepting payments" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "" + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "" + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "" + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "" + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "" + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "" + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "" + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "" + diff --git a/i18n/po/zh-CN/template-zh-CN.po b/i18n/po/zh-CN/template-zh-CN.po index cd2681041..f648a8cd6 100644 --- a/i18n/po/zh-CN/template-zh-CN.po +++ b/i18n/po/zh-CN/template-zh-CN.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Chinese Simplified\n" "Language: zh\n" -"PO-Revision-Date: 2018-08-21 03:39\n" +"PO-Revision-Date: 2018-09-15 05:56\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -520,7 +520,7 @@ msgid "Cannot Create Wallet" msgstr "不能创建钱包" #: src/js/services/profileService.js:442 -msgid "Cannot join the same wallet more that once" +msgid "Cannot join the same wallet more than once" msgstr "无法重复加入同一个钱包" #: www/views/includes/bitpayCardsCard.html:2 @@ -2925,9 +2925,14 @@ msgid "Sweep" msgstr "扫描" #: www/views/includes/incomingDataMenu.html:89 -#: www/views/paperWallet.html:3 +msgctxt "List item" msgid "Sweep paper wallet" -msgstr "Sweep 纸钱包" +msgstr "清空纸钱包" + +#: www/views/paperWallet.html:3 +msgctxt "Page title" +msgid "Sweep Paper Wallet" +msgstr "清空纸钱包" #: src/js/services/onGoingProcess.js:33 msgid "Sweeping Wallet..." @@ -3792,6 +3797,10 @@ msgstr "探索 Bitcoin.com" msgid "Bitcoin Cash Games" msgstr "Bitcoin Cash 游戏" +#: www/views/includes/community.html:29 +msgid "Share the Wallet App" +msgstr "分享钱包应用" + #: src/js/services/bitcoincomService.js:28 msgid "News" msgstr "新闻" @@ -3860,3 +3869,75 @@ msgstr "少于 1 美分" msgid "This invoice is no longer accepting payments" msgstr "此发票不再接受付款" +#: www/views/amount.html.js:60 +msgid "Send Maximum Amount" +msgstr "发送最大金额" + +#: src/js/controllers/amount.controller.js:239 +msgid "Unknown error." +msgstr "未知错误。" + +#: www/views/paperWallet.html:48 +msgid "No Bitcoin Cash wallet to transfer funds to found." +msgstr "未找到可以转入资金的 Bitcoin Cash 钱包。" + +#: www/views/paperWallet.html:54 +msgid "No Bitcoin Cash found." +msgstr "未找到 Bitcoin Cash。" + +#: www/views/paperWallet.html:60 +msgid "Bitcoin Core found:" +msgstr "已找到 Bitcoin Core:" + +#: www/views/paperWallet.html:98 +msgid "No Bitcoin Core wallet to transfer funds to found." +msgstr "未找到可以转入资金的 Bitcoin Core 钱包。" + +#: www/views/paperWallet.html:104 +msgid "No Bitcoin Core found." +msgstr "未找到 Bitcoin Core。" + +#: src/js/controllers/tab-scan.js:120 +msgid "Scan Failed" +msgstr "扫描失败" + +#: src/js/controllers/tab-scan.js:121 +msgid "Data not recognised." +msgstr "数据未被识别。" + +#: src/js/controllers/tab-scan.js:121 +msgid "Unsupported" +msgstr "不受支持" + +#: src/js/controllers/tab-scan.js:121 +msgid "Testnet is not supported." +msgstr "不支持测试网。" + +#: www/views/includes/incomingDataMenu.html:81 +msgid "URL" +msgstr "网址" + +#: www/views/includes/incomingDataMenu.html:90 +msgid "Open in web browser" +msgstr "在网络浏览器中打开" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid address" +msgstr "地址无效" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is not defined" +msgstr "未定义金额" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is below the minimun" +msgstr "金额低于最低金额" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Amount is above the limit" +msgstr "金额高于限额" + +#: src/js/services/shapeshift.service.js.html:90 +msgid "Invalid response from Shapeshift" +msgstr "Shapeshift 的响应无效" + From e02368ea3cbae6f1cdf0c104d26477e31c74641c Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 15 Sep 2018 18:05:39 +1200 Subject: [PATCH 096/118] Incremented version for release candidate. --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 970432be2..a73fae33f 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.1.1", - "fullVersion": "5.1-beta2", - "androidVersion": "501001", + "version": "5.1.2", + "fullVersion": "5.1-rc1", + "androidVersion": "501002", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From ebc32a2d85867f3db73a0fd2a356ba2ec8543b64 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sun, 16 Sep 2018 21:04:41 -0700 Subject: [PATCH 097/118] Added 'default' to mobile debug build configs. --- Gruntfile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1092b3de2..7968f2510 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -366,13 +366,13 @@ module.exports = function(grunt) { grunt.registerTask('build-mobile-release', ['build-ios-release', 'build-android-release']); // Build ios - grunt.registerTask('start-ios', ['exec:build_ios_debug', 'exec:xcode']); - grunt.registerTask('build-ios-debug', ['exec:build_ios_debug']); + grunt.registerTask('start-ios', ['default', 'exec:build_ios_debug', 'exec:xcode']); + grunt.registerTask('build-ios-debug', ['default', 'exec:build_ios_debug']); grunt.registerTask('build-ios-release', ['prod', 'exec:build_ios_release']); // Build android grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android']); - grunt.registerTask('build-android-debug', ['exec:build_android_debug']); + grunt.registerTask('build-android-debug', ['default', 'exec:build_android_debug']); grunt.registerTask('start-android-emulator', ['build-android-debug', 'exec:run_android_emulator']); grunt.registerTask('build-android-release', ['prod', 'exec:build_android_release', 'sign-android']); grunt.registerTask('sign-android', ['exec:sign_android']); From 7b6efaa1db8b2675435db2d180e284f6116e16b0 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 17 Sep 2018 21:26:21 -0700 Subject: [PATCH 098/118] Clear memo before entering Review screen. --- src/js/controllers/review.controller.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 15f982f2f..58ae2c2f7 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -26,6 +26,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.feeIsHigh = false; vm.feeLessThanACent = false; vm.isCordova = platformInfo.isCordova; + vm.memo = ''; vm.notReadyMessage = ''; vm.origin = { balanceAmount: '', @@ -76,9 +77,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - + function onBeforeEnter(event, data) { console.log('review onBeforeEnter sendflow ', sendFlowService.state); + // Reset from last time + vm.memo = ''; + defaults = configService.getDefaults(); sendFlowData = sendFlowService.state.getClone(); originWalletId = sendFlowData.fromWalletId; @@ -94,7 +98,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.originWallet = profileService.getWallet(originWalletId); vm.origin.currency = vm.originWallet.coin.toUpperCase(); coin = vm.originWallet.coin; - + if (sendFlowData.thirdParty) { vm.thirdParty = sendFlowData.thirdParty; switch (vm.thirdParty.id) { From 4d6aeb4f910f105544f189955ac2b68e45a49a90 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 18 Sep 2018 15:26:30 -0700 Subject: [PATCH 099/118] Renamed directive and replaced old variable references with new ones. --- ...rective.js => wallet-balance.directive.js} | 27 +++++++++++++------ www/views/tab-send.html | 5 ++-- 2 files changed, 21 insertions(+), 11 deletions(-) rename src/js/directives/{walletBalanceDirective.js => wallet-balance.directive.js} (81%) diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/wallet-balance.directive.js similarity index 81% rename from src/js/directives/walletBalanceDirective.js rename to src/js/directives/wallet-balance.directive.js index 1fea59203..4d8ca2875 100644 --- a/src/js/directives/walletBalanceDirective.js +++ b/src/js/directives/wallet-balance.directive.js @@ -12,6 +12,7 @@ totalBalanceSat: '@', // The Wallet object is sometimes not stringify()-able, so not interpolatable, // so can't be passed to a directive. + walletCoin: '@', walletStatus: '@', walletCachedBalance: '@', walletCachedBalanceUpdatedOn: '@', @@ -31,7 +32,6 @@ }); function displayCryptoBalance(walletStatus, walletCachedBalance, walletCachedBalanceUpdatedOn, walletCachedStatus) { - console.log('displayCryptoBalance()'); if (walletStatus && walletStatus.isValid && walletStatus.totalBalanceStr) { setDisplay(walletStatus.totalBalanceStr, ''); @@ -52,7 +52,7 @@ setDisplay('', ''); } - function displayFiatBalance(walletStatus, walletCachedStatus) { + function displayFiatBalance(walletStatus, walletCachedStatus, walletCoin) { var displayAmount = ''; if (walletStatus && walletStatus.isValid && walletStatus.alternativeBalanceAvailable) { displayAmount = walletStatus.totalBalanceAlternative + ' ' + walletStatus.alternativeIsoCode; @@ -66,7 +66,7 @@ return; } - getFiatBalance(wallet); + getFiatBalance(walletStatus, walletCachedStatus, walletCoin); } function formatBalance() { @@ -94,19 +94,30 @@ } if (displayAsFiat) { - displayFiatBalance(walletStatusObj, walletCachedStatusObj); + displayFiatBalance(walletStatusObj, walletCachedStatusObj, $scope.walletCoin); } } - function getFiatBalance(wallet) { - if (!(wallet.status && wallet.status.isValid)) { - $log.warn('Abandoning call to get fiat balance, because no valid wallet status.'); + function getFiatBalance(walletStatus, walletCachedStatus, walletCoin) { + var totalBalanceSat = null; + + if (walletStatus && walletStatus.isValid) { + totalBalanceSat = walletStatus.totalBalanceSat + } else if (walletCachedStatus && walletCachedStatus.isValid) { + totalBalanceSat = walletCachedStatus.totalBalanceSat + } + + // 0 is valid + if (totalBalanceSat === null) { + $log.warn('Abandoning call to get fiat balance, because no valid wallet status (cached or otherwise).'); return; } - txFormatService.formatAlternativeStr(wallet.coin, wallet.status.totalBalanceSat, function onFormatAlernativeStr(formatted) { + txFormatService.formatAlternativeStr(walletCoin, totalBalanceSat, function onFormatAlernativeStr(formatted) { if (formatted) { setDisplay(formatted, ''); + } else { + $log.error('Failed to format fiat balance of wallet.'); } }); } diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 0b62fe0a5..b8c2b7884 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -17,14 +17,13 @@

{{fromWallet.name}}

- -
From 3cad7d7ad86f4f723b73f0033e67d377391c9d6f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 20 Sep 2018 15:58:42 +0200 Subject: [PATCH 100/118] Update confirmations on cached transactions --- src/js/services/wallet-history.service.js | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 7a6f18a13..0738d4348 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -66,16 +66,21 @@ function addLatestTransactions(walletId, cachedTxs, newTxs) { var cachedTxIds = {}; - cachedTxs.forEach(function forCachedTx(tx){ - cachedTxIds[tx.txid] = true; + cachedTxs.forEach(function forCachedTx(tx, txIndex){ + cachedTxIds[tx.txid] = txIndex; }); var someTransactionsWereNew = false; + var confirmationsUpdated = false; var overlappingTxsCount = 0; var uniqueNewTxs = []; newTxs.forEach(function forNewTx(tx){ - if (cachedTxIds[tx.txid]) { + if (typeof cachedTxIds[tx.txid] !== "undefined") { + if (cachedTxs[cachedTxIds[tx.txid]].confirmations < SAFE_CONFIRMATIONS && tx.confirmations >= SAFE_CONFIRMATIONS) { + cachedTxs[cachedTxIds[tx.txid]].confirmations = tx.confirmations; + confirmationsUpdated = true; + } overlappingTxsCount++; } else { someTransactionsWereNew = true; @@ -91,6 +96,9 @@ saveTxHistory(walletId, allTxs); return allTxs; } else { + if (confirmationsUpdated) { + saveTxHistory(walletId, cachedTxs); + } return cachedTxs; } } else { @@ -104,6 +112,8 @@ // Only clear the cache once we have received new transactions from the server. /** + * @param wallet + * @param start * @param {function(err, txs)} cb - transactions is always an array, may be empty */ function fetchTxHistoryByPage(wallet, start, cb) { @@ -187,7 +197,7 @@ }); return processedTxs; - }; + } function saveTxHistory(walletId, processedTxs) { storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){ @@ -197,7 +207,6 @@ }); } - function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { if (flushCacheOnNew) { @@ -240,10 +249,5 @@ }); } } - - - } - - })(); \ No newline at end of file From 5cb3d7521df0fbb42f44ebf01c641d6565441abb Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 21 Sep 2018 10:23:02 +0200 Subject: [PATCH 101/118] Fix modal --- src/js/services/onGoingProcess.js | 10 ++++++---- src/js/services/shapeshift.service.js | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js index 9c25c3c26..422be070f 100644 --- a/src/js/services/onGoingProcess.js +++ b/src/js/services/onGoingProcess.js @@ -74,10 +74,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti root.onGoingProcessName = name; var showName = $filter('translate')(processNames[name] || name); - - if (customHandler) { - customHandler(processName, showName, isOn); - } else if (root.onGoingProcessName) { + + if (root.onGoingProcessName) { var tmpl; if (isWindowsPhoneApp) tmpl = '
' + showName + '
'; else tmpl = '
' + showName + '
'; @@ -87,6 +85,10 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti } else { $ionicLoading.hide(); } + + if (customHandler) { + customHandler(processName, showName, isOn); + } }; return root; diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 77f0de297..73410e478 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -57,7 +57,7 @@ angular function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) { // Test if the amount is correct depending on the min and max if (!amount || typeof amount !== 'number') { - cb(new Error(gettextCatalog.getString('Amount is not defined')))); + cb(new Error(gettextCatalog.getString('Amount is not defined'))); } else if (amount < service.marketData.minimum) { cb(new Error(gettextCatalog.getString('Amount is below the minimun'))); } else if (amount > service.marketData.maxLimit) { From a3614566e3cf9a2128d24f508917e1c8e4faee08 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 02:27:34 -0700 Subject: [PATCH 102/118] Reconnecting socket based on CloseEvent.code. --- src/js/controllers/tab-receive.js | 54 +++++++++++++------------------ 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 6642c2e1c..645e46739 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -2,6 +2,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, sendFlowService, txFormatService, soundService, clipboardService) { + var CLOSE_NORMAL = 1000; var listeners = []; $scope.bchAddressType = { type: 'cashaddr' }; var bchAddresses = {}; @@ -10,12 +11,11 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi $scope.isCordova = platformInfo.isCordova; $scope.isNW = platformInfo.isNW; - var currentAddressSocket = {}; - var paymentSubscriptionObj = { op:"addr_sub" } - - var config; + var currentAddressSocket = null; + var paymentSubscriptionObj = { op:'addr_sub' }; $scope.displayBalanceAsFiat = true; + $scope.$on('$ionicView.beforeLeave', onBeforeLeave); $scope.requestSpecificAmount = function() { sendFlowService.start({ @@ -26,18 +26,18 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi function connectSocket() { - //close existing socket if not connected with current address - if (currentAddressSocket && typeof currentAddressSocket.close === 'function') { - currentAddressSocket.onclose = function(e) {}; // Overwrite onclose-function to prevent reconnecting old address socket. - currentAddressSocket.close(); + // Close existing socket if not connected with current address + if (currentAddressSocket) { + currentAddressSocket.close([CLOSE_NORMAL]); } + if ($scope.wallet.coin === 'bch') { // listen to bch address - currentAddressSocket = new WebSocket("wss://ws.blockchain.info/bch/inv"); + currentAddressSocket = new WebSocket('wss://ws.blockchain.info/bch/inv'); paymentSubscriptionObj.addr = $scope.addrBchLegacy; } else { // listen to btc address - currentAddressSocket = new WebSocket("wss://ws.blockchain.info/inv"); + currentAddressSocket = new WebSocket('wss://ws.blockchain.info/inv'); paymentSubscriptionObj.addr = $scope.addr; } @@ -53,21 +53,19 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi receivedPayment(event.data); }; - currentAddressSocket.onclose = function(e) { - console.log('Socket is closed. Reconnect will be attempted in 1 second.'); - $timeout(function() { - connectSocket(); - }, 1000); + currentAddressSocket.onclose = function(event) { + if (event.code !== CLOSE_NORMAL) { + $log.debug('Socket was closed abnormally. Reconnect will be attempted in 1 second.'); + $timeout(function() { + connectSocket(); + }, 1000); + } }; currentAddressSocket.onerror = function(err) { console.error('Socket encountered error: ', err, 'Closing socket'); currentAddressSocket.close(); }; - - $timeout(function() { - $scope.$apply(); - }, 10); } $scope.setAddress = function(newAddr, copyAddress) { @@ -96,7 +94,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi try { clipboardService.copyToClipboard($scope.wallet.coin == 'bch' && $scope.bchAddressType.type == 'cashaddr' ? 'bitcoincash:' + $scope.addr : $scope.addr); } catch (error) { - $log.debug("Error copying to clipboard:"); + $log.debug('Error copying to clipboard:'); $log.debug(error); } } @@ -185,7 +183,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi // Notify new tx $scope.$emit('bwsEvent', $scope.wallet.id); - $scope.$apply(function () { $scope.showingPaymentReceived = true; }); @@ -254,16 +251,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi } }; - $scope.$on("$ionicView.beforeLeave", function() { - // Close the old connection! - if (currentAddressSocket && typeof currentAddressSocket.close === 'function') { - console.log("Close open websocket address connection."); - currentAddressSocket.onclose = function(e) {}; // Overwrite onclose-function to prevent reconnecting old address socket. - currentAddressSocket.close(); - } - - }); - + function onBeforeLeave() { + currentAddressSocket.close([CLOSE_NORMAL]); + } + $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.wallets = profileService.getWallets(); $scope.singleWallet = $scope.wallets.length == 1; @@ -289,7 +280,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi configService.whenAvailable(function(_config) { $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat'; - config = _config; }); }); From 4250a98718c9bad3d3f0de9eb8e21253ad082f8d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 21 Sep 2018 11:31:27 +0200 Subject: [PATCH 103/118] walletStatus worst case, IIFE for both controllers. --- src/js/controllers/review.controller.js | 1752 +++++++++-------- .../controllers/wallet-selector.controller.js | 348 ++-- 2 files changed, 1054 insertions(+), 1046 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index b8379b769..53bae167e 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -1,952 +1,954 @@ 'use strict'; +(function () { + angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { - var vm = this; + function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { + var vm = this; - vm.buttonText = ''; - vm.destination = { - address: '', - balanceAmount: '', - balanceCurrency: '', - coin: '', - color: '', - currency: '', - currencyColor: '', - kind: '', // 'address', 'contact', 'wallet' - name: '' - }; - vm.displayAddress = ''; - vm.feeCrypto = ''; - vm.feeFiat = ''; - vm.fiatCurrency = ''; - vm.feeIsHigh = false; - vm.feeLessThanACent = false; - vm.isCordova = platformInfo.isCordova; - vm.notReadyMessage = ''; - vm.origin = { - balanceAmount: '', - balanceCurrency: '', - currency: '', - currencyColor: '', - }; - vm.originWallet = null; - vm.paymentExpired = false; - vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here'); - vm.primaryAmount = ''; - vm.primaryCurrency = ''; - vm.usingMerchantFee = false; - vm.readyToSend = false; - vm.remainingTimeStr = ''; - vm.secondaryAmount = ''; - vm.secondaryCurrency = ''; - vm.sendingTitle = gettextCatalog.getString('You are sending'); - vm.sendStatus = ''; - vm.showAddress = true; - vm.thirdParty = false; - vm.wallet = null; - vm.memoExpanded = false; + vm.buttonText = ''; + vm.destination = { + address: '', + balanceAmount: '', + balanceCurrency: '', + coin: '', + color: '', + currency: '', + currencyColor: '', + kind: '', // 'address', 'contact', 'wallet' + name: '' + }; + vm.displayAddress = ''; + vm.feeCrypto = ''; + vm.feeFiat = ''; + vm.fiatCurrency = ''; + vm.feeIsHigh = false; + vm.feeLessThanACent = false; + vm.isCordova = platformInfo.isCordova; + vm.notReadyMessage = ''; + vm.origin = { + balanceAmount: '', + balanceCurrency: '', + currency: '', + currencyColor: '', + }; + vm.originWallet = null; + vm.paymentExpired = false; + vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here'); + vm.primaryAmount = ''; + vm.primaryCurrency = ''; + vm.usingMerchantFee = false; + vm.readyToSend = false; + vm.remainingTimeStr = ''; + vm.secondaryAmount = ''; + vm.secondaryCurrency = ''; + vm.sendingTitle = gettextCatalog.getString('You are sending'); + vm.sendStatus = ''; + vm.showAddress = true; + vm.thirdParty = false; + vm.wallet = null; + vm.memoExpanded = false; - // Functions - vm.goBack = goBack; - vm.onSuccessConfirm = onSuccessConfirm; - vm.onShareTransaction = onShareTransaction; + // Functions + vm.goBack = goBack; + vm.onSuccessConfirm = onSuccessConfirm; + vm.onShareTransaction = onShareTransaction; - var sendFlowData; - var config = null; - var coin = ''; - var countDown = null; - var defaults = {}; - var usingCustomFee = false; - var usingMerchantFee = false; - var destinationWalletId = ''; - var lastTxId = ''; - var originWalletId = ''; - var priceDisplayIsFiat = true; - var satoshis = null; - var toAddress = ''; - var tx = {}; - var txPayproData = null; - var unitFromSat = 0; + var sendFlowData; + var config = null; + var coin = ''; + var countDown = null; + var defaults = {}; + var usingCustomFee = false; + var usingMerchantFee = false; + var destinationWalletId = ''; + var lastTxId = ''; + var originWalletId = ''; + var priceDisplayIsFiat = true; + var satoshis = null; + var toAddress = ''; + var tx = {}; + var txPayproData = null; + var unitFromSat = 0; - var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; + var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; - $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - function onBeforeEnter(event, data) { - console.log('review onBeforeEnter sendflow ', sendFlowService.state); - defaults = configService.getDefaults(); - sendFlowData = sendFlowService.state.getClone(); - originWalletId = sendFlowData.fromWalletId; - if (typeof sendFlowData.amount === 'string') { - satoshis = parseInt(sendFlowData.amount, 10); - } else { - satoshis = sendFlowData.amount; - } - toAddress = sendFlowData.toAddress; - destinationWalletId = sendFlowData.toWalletId; - - vm.displayAddress = sendFlowData.displayAddress; - vm.originWallet = profileService.getWallet(originWalletId); - vm.origin.currency = vm.originWallet.coin.toUpperCase(); - coin = vm.originWallet.coin; - - if (sendFlowData.thirdParty) { - vm.thirdParty = sendFlowData.thirdParty; - switch (vm.thirdParty.id) { - case 'shapeshift': - initShapeshift(function (err) { - if (err) { - // Error stop here - ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { - $ionicHistory.goBack(); - }); - } else { - _next(data); - } - }); - break; - case 'bip70': - initBip70(); - default: - _next(data); - break; + function onBeforeEnter(event, data) { + console.log('review onBeforeEnter sendflow ', sendFlowService.state); + defaults = configService.getDefaults(); + sendFlowData = sendFlowService.state.getClone(); + originWalletId = sendFlowData.fromWalletId; + if (typeof sendFlowData.amount === 'string') { + satoshis = parseInt(sendFlowData.amount, 10); + } else { + satoshis = sendFlowData.amount; } - } else { - _next(data); - } + toAddress = sendFlowData.toAddress; + destinationWalletId = sendFlowData.toWalletId; - function _next() { - configService.get(function onConfig(err, configCache) { - if (err) { - $log.err('Error getting config.', err); - } else { - config = configCache; - priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; - vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); - console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); - unitFromSat = 1 / config.wallet.settings.unitToSatoshi; - } - updateSendAmounts(); - getOriginWalletBalance(vm.originWallet); - handleDestinationAsAddress(toAddress, coin); - handleDestinationAsWallet(sendFlowData.toWalletId); - createVanityTransaction(data); - }); - } - } + vm.displayAddress = sendFlowData.displayAddress; + vm.originWallet = profileService.getWallet(originWalletId); + vm.origin.currency = vm.originWallet.coin.toUpperCase(); + coin = vm.originWallet.coin; - vm.approve = function() { - - if (!tx || !vm.originWallet) return; - - if (vm.paymentExpired) { - popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { - $ionicHistory.goBack(); - })); - vm.sendStatus = ''; - $timeout(function() { - $scope.$apply(); - }); - return; - } - - ongoingProcess.set('creatingTx', true, statusChangeHandler); - getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { - ongoingProcess.set('creatingTx', false, statusChangeHandler); - if (err) return; - - // confirm txs for more that 20usd, if not spending/touchid is enabled - function confirmTx(cb) { - if (walletService.isEncrypted(vm.originWallet)) - return cb(); - - var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); - return cb(); - }; - - function publishAndSign() { - if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { - $log.info('No signing proposal: No private key'); - - return walletService.onlyPublish(vm.originWallet, txp, function(err) { - if (err) setSendError(err); - }, statusChangeHandler); - } - - walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { - if (err) return setSendError(err); - if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { - txConfirmNotification.subscribe(vm.originWallet, { - txid: txp.txid + if (sendFlowData.thirdParty) { + vm.thirdParty = sendFlowData.thirdParty; + switch (vm.thirdParty.id) { + case 'shapeshift': + initShapeshift(function (err) { + if (err) { + // Error stop here + ongoingProcess.set('connectingShapeshift', false); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); + } else { + _next(data); + } }); - lastTxId = txp.txid; + break; + case 'bip70': + initBip70(); + default: + _next(data); + break; + } + } else { + _next(data); + } + + function _next() { + configService.get(function onConfig(err, configCache) { + if (err) { + $log.err('Error getting config.', err); + } else { + config = configCache; + priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; + vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); + console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); + unitFromSat = 1 / config.wallet.settings.unitToSatoshi; } - }, statusChangeHandler); + updateSendAmounts(); + getOriginWalletBalance(vm.originWallet); + handleDestinationAsAddress(toAddress, coin); + handleDestinationAsWallet(sendFlowData.toWalletId); + createVanityTransaction(data); + }); + } + } + + vm.approve = function() { + + if (!tx || !vm.originWallet) return; + + if (vm.paymentExpired) { + popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { + $ionicHistory.goBack(); + })); + vm.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + return; + } + + ongoingProcess.set('creatingTx', true, statusChangeHandler); + getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { + ongoingProcess.set('creatingTx', false, statusChangeHandler); + if (err) return; + + // confirm txs for more that 20usd, if not spending/touchid is enabled + function confirmTx(cb) { + if (walletService.isEncrypted(vm.originWallet)) + return cb(); + + var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); + return cb(); + }; + + function publishAndSign() { + if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { + $log.info('No signing proposal: No private key'); + + return walletService.onlyPublish(vm.originWallet, txp, function(err) { + if (err) setSendError(err); + }, statusChangeHandler); + } + + walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { + if (err) return setSendError(err); + if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { + txConfirmNotification.subscribe(vm.originWallet, { + txid: txp.txid + }); + lastTxId = txp.txid; + } + }, statusChangeHandler); + }; + + confirmTx(function(nok) { + if (nok) { + vm.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + return; + } + publishAndSign(); + }); + }); + }; + + vm.chooseFeeLevel = function(tx, wallet) { + + if (wallet.coin == 'bch') return; + if (usingMerchantFee) return; + + var scope = $rootScope.$new(true); + scope.network = tx.network; + scope.feeLevel = tx.feeLevel; + scope.noSave = true; + scope.coin = vm.originWallet.coin; + + if (usingCustomFee) { + scope.customFeePerKB = tx.feeRate; + scope.feePerSatByte = tx.feeRate / 1000; + } + + $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { + scope: scope, + backdropClickToClose: false, + hardwareBackButtonClose: false + }).then(function(modal) { + scope.chooseFeeLevelModal = modal; + scope.openModal(); + }); + scope.openModal = function() { + scope.chooseFeeLevelModal.show(); }; - confirmTx(function(nok) { - if (nok) { - vm.sendStatus = ''; - $timeout(function() { - $scope.$apply(); + scope.hideModal = function(newFeeLevel, customFeePerKB) { + scope.chooseFeeLevelModal.hide(); + $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); + + usingCustomFee = newFeeLevel == 'custom' ? true : false; + + if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; + + tx.feeLevel = newFeeLevel; + if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); + + updateTx(tx, vm.originWallet, { + clearCache: true, + dryRun: true + }, function() {}); + }; + }; + + function createVanityTransaction(data) { + console.log('createVanityTransaction()'); + var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; + + // Grab stateParams + tx = { + amount: parseInt(sendFlowData.amount), + sendMax: sendFlowData.sendMax, + fromWalletId: sendFlowData.fromWalletId, + toAddress: sendFlowData.toAddress, + paypro: txPayproData, + + feeLevel: configFeeLevel, + spendUnconfirmed: config.wallet.spendUnconfirmed, + + // Vanity tx info (not in the real tx) + recipientType: vm.destination.kind || null, + toName: vm.destination.name || null, + toEmail: vm.destination.email || null, + toColor: vm.destination.color || null, + network: false, + coin: vm.originWallet.coin, + txp: {}, + }; + + + if (data.stateParams.requiredFeeRate) { + vm.usingMerchantFee = true; + tx.feeRate = parseInt(data.stateParams.requiredFeeRate); + } + + if (tx.coin && tx.coin === 'bch') { + tx.feeLevel = 'normal'; + } + + var B = tx.coin === 'bch' ? bitcoreCash : bitcore; + var networkName; + try { + if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer + ongoingProcess.set('generatingNewAddress', true); + var toWallet = profileService.getWallet(destinationWalletId); + + // We need an address to send to, so we ask the walletService to create a new address for the toWallet. + console.log('Getting address for wallet...'); + walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { + console.log('getAddress cb called', err); + ongoingProcess.set('generatingNewAddress', false); + tx.toAddress = addr; + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + console.log('calling setupTx() for wallet.'); + setupTx(tx); }); - return; - } - publishAndSign(); - }); - }); - }; - - vm.chooseFeeLevel = function(tx, wallet) { - - if (wallet.coin == 'bch') return; - if (usingMerchantFee) return; - - var scope = $rootScope.$new(true); - scope.network = tx.network; - scope.feeLevel = tx.feeLevel; - scope.noSave = true; - scope.coin = vm.originWallet.coin; - - if (usingCustomFee) { - scope.customFeePerKB = tx.feeRate; - scope.feePerSatByte = tx.feeRate / 1000; - } - - $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { - scope: scope, - backdropClickToClose: false, - hardwareBackButtonClose: false - }).then(function(modal) { - scope.chooseFeeLevelModal = modal; - scope.openModal(); - }); - scope.openModal = function() { - scope.chooseFeeLevelModal.show(); - }; - - scope.hideModal = function(newFeeLevel, customFeePerKB) { - scope.chooseFeeLevelModal.hide(); - $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); - - usingCustomFee = newFeeLevel == 'custom' ? true : false; - - if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; - - tx.feeLevel = newFeeLevel; - if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); - - updateTx(tx, vm.originWallet, { - clearCache: true, - dryRun: true - }, function() {}); - }; - }; - - function createVanityTransaction(data) { - console.log('createVanityTransaction()'); - var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; - - // Grab stateParams - tx = { - amount: parseInt(sendFlowData.amount), - sendMax: sendFlowData.sendMax, - fromWalletId: sendFlowData.fromWalletId, - toAddress: sendFlowData.toAddress, - paypro: txPayproData, - - feeLevel: configFeeLevel, - spendUnconfirmed: config.wallet.spendUnconfirmed, - - // Vanity tx info (not in the real tx) - recipientType: vm.destination.kind || null, - toName: vm.destination.name || null, - toEmail: vm.destination.email || null, - toColor: vm.destination.color || null, - network: false, - coin: vm.originWallet.coin, - txp: {}, - }; - - - if (data.stateParams.requiredFeeRate) { - vm.usingMerchantFee = true; - tx.feeRate = parseInt(data.stateParams.requiredFeeRate); - } - - if (tx.coin && tx.coin === 'bch') { - tx.feeLevel = 'normal'; - } - - var B = tx.coin === 'bch' ? bitcoreCash : bitcore; - var networkName; - try { - if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer - ongoingProcess.set('generatingNewAddress', true); - var toWallet = profileService.getWallet(destinationWalletId); - - // We need an address to send to, so we ask the walletService to create a new address for the toWallet. - console.log('Getting address for wallet...'); - walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { - console.log('getAddress cb called', err); - ongoingProcess.set('generatingNewAddress', false); - tx.toAddress = addr; + } else { // This is a Wallet-to-address transfer networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; - console.log('calling setupTx() for wallet.'); + console.log('calling setupTx() for address.'); setupTx(tx); - }); - } else { // This is a Wallet-to-address transfer - networkName = (new B.Address(tx.toAddress)).network.name; - tx.network = networkName; - console.log('calling setupTx() for address.'); - setupTx(tx); - } - } catch (e) { - console.error('Error setting up tx', e); - var message = gettextCatalog.getString('Invalid address'); - popupService.showAlert(null, message, function () { - $ionicHistory.nextViewOptions({ - disableAnimate: true, - historyRoot: true - }); - $state.go('tabs.send').then(function () { - $ionicHistory.clearHistory(); - }); - }); - return; - } - } - function getOriginWalletBalance(originWallet) { - var balanceText = getWalletBalanceDisplayText(vm.originWallet); - vm.origin.balanceAmount = balanceText.amount; - vm.origin.balanceCurrency = balanceText.currency; - } - - function getSendMaxInfo(tx, wallet, cb) { - if (!tx.sendMax) return cb(); - - //ongoingProcess.set('retrievingInputs', true); - walletService.getSendMaxInfo(wallet, { - feePerKb: tx.feeRate, - excludeUnconfirmedUtxos: !tx.spendUnconfirmed, - returnInputs: true, - }, cb); - }; - - function getTxp(tx, wallet, dryRun, cb) { - - // ToDo: use a credential's (or fc's) function for this - if (tx.description && !wallet.credentials.sharedEncryptingKey) { - var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key'); - $log.warn(msg); - return setSendError(msg); - } - - if (tx.amount > Number.MAX_SAFE_INTEGER) { - var msg = gettextCatalog.getString('Amount too big'); - $log.warn(msg); - return setSendError(msg); - } - - var txp = {}; - - txp.outputs = [{ - 'toAddress': tx.toAddress, - 'amount': tx.amount, - 'message': vm.memo - }]; - - if (tx.sendMaxInfo) { - txp.inputs = tx.sendMaxInfo.inputs; - txp.fee = tx.sendMaxInfo.fee; - } else { - if (usingCustomFee || usingMerchantFee) { - txp.feePerKb = tx.feeRate; - } else txp.feeLevel = tx.feeLevel; - } - - txp.message = vm.memo; - - if (tx.paypro) { - txp.payProUrl = tx.paypro.url; - } - txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; - txp.dryRun = dryRun; - walletService.createTx(wallet, txp, function(err, ctxp) { - if (err) { - setSendError(err); - return cb(err); - } - return cb(null, ctxp); - }); - }; - - function getWalletBalanceDisplayText(wallet) { - var balanceCryptoAmount = ''; - var balanceCryptoCurrencyCode = ''; - var balanceFiatAmount = ''; - var balanceFiatCurrency = ''; - var displayAmount = ''; - var displayCurrency = ''; - - var walletStatus = null; - if (wallet.status && wallet.status.isValid) { - walletStatus = wallet.status; - } else if (wallet.cachedStatus.isValid) { - walletStatus = wallet.cachedStatus; - } - - if (walletStatus) { - var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); - balanceCryptoAmount = cryptoBalanceParts[0]; - balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; - - if (walletStatus.alternativeBalanceAvailable) { - balanceFiatAmount = walletStatus.spendableBalanceAlternative; - balanceFiatCurrency = walletStatus.alternativeIsoCode; - } - } - - if (priceDisplayIsFiat) { - displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; - displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; - } else { - displayAmount = balanceCryptoAmount; - displayCurrency = balanceCryptoCurrencyCode; - } - - return { - amount: displayAmount, - currency: displayCurrency - }; - } - - function goBack() { - sendFlowService.router.goBack(); - } - - function handleDestinationAsAddress(address, originCoin) { - if (!address) { - return; - } - - // Check if the recipient is a contact - addressbookService.get(originCoin + address, function(err, contact) { - if (!err && contact) { - handleDestinationAsAddressOfContact(contact); - } else { - if (originCoin === 'bch') { - vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; - } else { - vm.destination.address = address; } - vm.destination.kind = 'address'; + } catch (e) { + console.error('Error setting up tx', e); + var message = gettextCatalog.getString('Invalid address'); + popupService.showAlert(null, message, function () { + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function () { + $ionicHistory.clearHistory(); + }); + }); + return; } - }); - - } - - function handleDestinationAsAddressOfContact(contact) { - vm.destination.kind = 'contact'; - vm.destination.name = contact.name; - vm.destination.email = contact.email; - vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; - vm.destination.currency = contact.coin.toUpperCase(); - vm.destination.currencyColor = vm.destination.color; - } - - function handleDestinationAsWallet(walletId) { - destinationWalletId = walletId; - if (!destinationWalletId) { - return; + } + function getOriginWalletBalance(originWallet) { + var balanceText = getWalletBalanceDisplayText(vm.originWallet); + vm.origin.balanceAmount = balanceText.amount; + vm.origin.balanceCurrency = balanceText.currency; } - var destinationWallet = profileService.getWallet(destinationWalletId); - vm.destination.coin = destinationWallet.coin; - vm.destination.color = destinationWallet.color; - vm.destination.currency = destinationWallet.coin.toUpperCase(); - vm.destination.kind = 'wallet'; - vm.destination.name = destinationWallet.name; + function getSendMaxInfo(tx, wallet, cb) { + if (!tx.sendMax) return cb(); - if (defaults) { - vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; - } - - var balanceText = getWalletBalanceDisplayText(destinationWallet); - vm.destination.balanceAmount = balanceText.amount; - vm.destination.balanceCurrency = balanceText.currency; - } - - function initBip70() { - vm.sendingTitle = gettextCatalog.getString('You are paying'); - vm.memo = vm.thirdParty.memo; - vm.memoExpanded = !!vm.memo; - vm.destination.name = vm.thirdParty.name; - - txPayproData = { - caTrusted: vm.thirdParty.caTrusted, - domain: vm.thirdParty.domain, - expires: vm.thirdParty.expires, - toAddress: toAddress, - url: vm.thirdParty.url, - verified: vm.thirdParty.verified, + //ongoingProcess.set('retrievingInputs', true); + walletService.getSendMaxInfo(wallet, { + feePerKb: tx.feeRate, + excludeUnconfirmedUtxos: !tx.spendUnconfirmed, + returnInputs: true, + }, cb); }; - } - function initShapeshift(cb) { - vm.sendingTitle = gettextCatalog.getString('You are shifting'); - if (!vm.thirdParty.data) { - vm.thirdParty.data = {}; + function getTxp(tx, wallet, dryRun, cb) { + + // ToDo: use a credential's (or fc's) function for this + if (tx.description && !wallet.credentials.sharedEncryptingKey) { + var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key'); + $log.warn(msg); + return setSendError(msg); + } + + if (tx.amount > Number.MAX_SAFE_INTEGER) { + var msg = gettextCatalog.getString('Amount too big'); + $log.warn(msg); + return setSendError(msg); + } + + var txp = {}; + + txp.outputs = [{ + 'toAddress': tx.toAddress, + 'amount': tx.amount, + 'message': vm.memo + }]; + + if (tx.sendMaxInfo) { + txp.inputs = tx.sendMaxInfo.inputs; + txp.fee = tx.sendMaxInfo.fee; + } else { + if (usingCustomFee || usingMerchantFee) { + txp.feePerKb = tx.feeRate; + } else txp.feeLevel = tx.feeLevel; + } + + txp.message = vm.memo; + + if (tx.paypro) { + txp.payProUrl = tx.paypro.url; + } + txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; + txp.dryRun = dryRun; + walletService.createTx(wallet, txp, function(err, ctxp) { + if (err) { + setSendError(err); + return cb(err); + } + return cb(null, ctxp); + }); + }; + + function getWalletBalanceDisplayText(wallet) { + var balanceCryptoAmount = ''; + var balanceCryptoCurrencyCode = ''; + var balanceFiatAmount = ''; + var balanceFiatCurrency = ''; + var displayAmount = ''; + var displayCurrency = ''; + + var walletStatus = null; + if (wallet.status && wallet.status.isValid) { + walletStatus = wallet.status; + } else if (wallet.cachedStatus.isValid) { + walletStatus = wallet.cachedStatus; + } + + if (walletStatus) { + var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); + balanceCryptoAmount = cryptoBalanceParts[0]; + balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; + + if (walletStatus.alternativeBalanceAvailable) { + balanceFiatAmount = walletStatus.spendableBalanceAlternative; + balanceFiatCurrency = walletStatus.alternativeIsoCode; + } + } + + if (priceDisplayIsFiat) { + displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; + displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; + } else { + displayAmount = balanceCryptoAmount; + displayCurrency = balanceCryptoCurrencyCode; + } + + return { + amount: displayAmount, + currency: displayCurrency + }; } - var toWallet = profileService.getWallet(destinationWalletId); - vm.destination.name = toWallet.name; - vm.destination.color = toWallet.color; - vm.destination.currency = toWallet.coin.toUpperCase(); + function goBack() { + sendFlowService.router.goBack(); + } + + function handleDestinationAsAddress(address, originCoin) { + if (!address) { + return; + } + + // Check if the recipient is a contact + addressbookService.get(originCoin + address, function(err, contact) { + if (!err && contact) { + handleDestinationAsAddressOfContact(contact); + } else { + if (originCoin === 'bch') { + vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; + } else { + vm.destination.address = address; + } + vm.destination.kind = 'address'; + } + }); + + } + + function handleDestinationAsAddressOfContact(contact) { + vm.destination.kind = 'contact'; + vm.destination.name = contact.name; + vm.destination.email = contact.email; + vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; + vm.destination.currency = contact.coin.toUpperCase(); + vm.destination.currencyColor = vm.destination.color; + } + + function handleDestinationAsWallet(walletId) { + destinationWalletId = walletId; + if (!destinationWalletId) { + return; + } + + var destinationWallet = profileService.getWallet(destinationWalletId); + vm.destination.coin = destinationWallet.coin; + vm.destination.color = destinationWallet.color; + vm.destination.currency = destinationWallet.coin.toUpperCase(); + vm.destination.kind = 'wallet'; + vm.destination.name = destinationWallet.name; + + if (defaults) { + vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; + } + + var balanceText = getWalletBalanceDisplayText(destinationWallet); + vm.destination.balanceAmount = balanceText.amount; + vm.destination.balanceCurrency = balanceText.currency; + } + + function initBip70() { + vm.sendingTitle = gettextCatalog.getString('You are paying'); + vm.memo = vm.thirdParty.memo; + vm.memoExpanded = !!vm.memo; + vm.destination.name = vm.thirdParty.name; + + txPayproData = { + caTrusted: vm.thirdParty.caTrusted, + domain: vm.thirdParty.domain, + expires: vm.thirdParty.expires, + toAddress: toAddress, + url: vm.thirdParty.url, + verified: vm.thirdParty.verified, + }; + } + + function initShapeshift(cb) { + vm.sendingTitle = gettextCatalog.getString('You are shifting'); + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } + + var toWallet = profileService.getWallet(destinationWalletId); + vm.destination.name = toWallet.name; + vm.destination.color = toWallet.color; + vm.destination.currency = toWallet.coin.toUpperCase(); - ongoingProcess.set('connectingShapeshift', true); - walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { - if (err) { - return cb(err); - } - walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { + ongoingProcess.set('connectingShapeshift', true); + walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { if (err) { return cb(err); } - - // Need to use the correct service to do it. - var amount = parseFloat(satoshis / 100000000); - - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { + walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { if (err) { return cb(err); - } else { - vm.destination.kind = 'shapeshift'; - vm.destination.address = toAddress; - tx.toAddress = shapeshiftData.toAddress; - vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; - vm.memoExpanded = !!vm.memo; - ongoingProcess.set('connectingShapeshift', false); - cb(); - } + } + + // Need to use the correct service to do it. + var amount = parseFloat(satoshis / 100000000); + + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { + if (err) { + return cb(err); + } else { + vm.destination.kind = 'shapeshift'; + vm.destination.address = toAddress; + tx.toAddress = shapeshiftData.toAddress; + vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; + vm.memoExpanded = !!vm.memo; + ongoingProcess.set('connectingShapeshift', false); + cb(); + } + }); }); }); - }); - } - - function onShareTransaction() { - var explorerTxUrl = 'https://explorer.bitcoin.com/' + tx.coin + '/tx/' + lastTxId; - if (platformInfo.isCordova) { - var text = gettextCatalog.getString('Take a look at this Bitcoin Cash transaction here: ') + explorerTxUrl; - if (coin === 'btc') { - text = gettextCatalog.getString('Take a look at this Bitcoin transaction here: ') + explorerTxUrl; - } - window.plugins.socialsharing.share(text, null, null, null); - } else { - ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000); - clipboardService.copyToClipboard(explorerTxUrl); } - - } - function startExpirationTimer(expirationTime) { - vm.paymentExpired = false; - setExpirationTime(); + function onShareTransaction() { + var explorerTxUrl = 'https://explorer.bitcoin.com/' + tx.coin + '/tx/' + lastTxId; + if (platformInfo.isCordova) { + var text = gettextCatalog.getString('Take a look at this Bitcoin Cash transaction here: ') + explorerTxUrl; + if (coin === 'btc') { + text = gettextCatalog.getString('Take a look at this Bitcoin transaction here: ') + explorerTxUrl; + } + window.plugins.socialsharing.share(text, null, null, null); + } else { + ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000); + clipboardService.copyToClipboard(explorerTxUrl); + } + + } - countDown = $interval(function() { + function startExpirationTimer(expirationTime) { + vm.paymentExpired = false; setExpirationTime(); - }, 1000); - function setExpirationTime() { - console.log('setExpirationTime()'); - var now = Math.floor(Date.now() / 1000); + countDown = $interval(function() { + setExpirationTime(); + }, 1000); - if (now > expirationTime) { - setExpiredValues(); + function setExpirationTime() { + console.log('setExpirationTime()'); + var now = Math.floor(Date.now() / 1000); + + if (now > expirationTime) { + setExpiredValues(); + return; + } + + var totalSecs = expirationTime - now; + var m = Math.floor(totalSecs / 60); + var s = totalSecs % 60; + vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); + }; + + function setExpiredValues() { + vm.paymentExpired = true; + vm.remainingTimeStr = gettextCatalog.getString('Expired'); + vm.readyToSend = false; + if (countDown) $interval.cancel(countDown); + $timeout(function() { + $scope.$apply(); + }); + }; + }; + + function updateSendAmounts() { + if (typeof satoshis !== 'number') { return; } - var totalSecs = expirationTime - now; - var m = Math.floor(totalSecs / 60); - var s = totalSecs % 60; - vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); + var cryptoAmount = ''; + var cryptoCurrencyCode = ''; + var amountStr = txFormatService.formatAmountStr(coin, satoshis); + if (amountStr) { + var amountParts = amountStr.split(' '); + cryptoAmount = amountParts[0]; + cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : ''; + } + // Want to avoid flashing of amount strings so do all formatting after this has returned. + txFormatService.formatAlternativeStr(coin, satoshis, function(v) { + if (!v) { + vm.primaryAmount = cryptoAmount; + vm.primaryCurrency = cryptoCurrencyCode; + vm.secondaryAmount = ''; + vm.secondaryCurrency = ''; + return; + } + vm.secondaryAmount = vm.primaryAmount; + vm.secondaryCurrency = vm.primaryCurrency; + + var fiatParts = v.split(' '); + var fiatAmount = fiatParts[0]; + var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : ''; + + if (priceDisplayIsFiat) { + vm.primaryAmount = fiatAmount; + vm.primaryCurrency = fiatCurrency; + vm.secondaryAmount = cryptoAmount; + vm.secondaryCurrency = cryptoCurrencyCode; + } else { + vm.primaryAmount = cryptoAmount; + vm.primaryCurrency = cryptoCurrencyCode; + vm.secondaryAmount = fiatAmount; + vm.secondaryCurrency = fiatCurrency; + } + }); + } + + function onSuccessConfirm() { + vm.sendStatus = ''; + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function() { + $ionicHistory.clearHistory(); + $state.transitionTo('tabs.home'); + }); }; - function setExpiredValues() { - vm.paymentExpired = true; - vm.remainingTimeStr = gettextCatalog.getString('Expired'); + function setButtonText(isMultisig, isPayPro) { + if (isPayPro) { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to pay'); + } else { + vm.buttonText = gettextCatalog.getString('Click to pay'); + } + } else if (isMultisig) { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to accept'); + } else { + vm.buttonText = gettextCatalog.getString('Click to accept'); + } + } else { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to send'); + } else { + vm.buttonText = gettextCatalog.getString('Click to send'); + } + } + } + + function setNotReady(msg, criticalError) { + vn.readyToSend = false; + vm.notReadyMessage = msg; + $scope.criticalError = criticalError; + $log.warn('Not ready to make the payment:' + msg); + $timeout(function() { + $scope.$apply(); + }); + }; + + function setSendError(msg) { + $scope.sendStatus = ''; vm.readyToSend = false; - if (countDown) $interval.cancel(countDown); $timeout(function() { $scope.$apply(); }); - }; - }; - - function updateSendAmounts() { - if (typeof satoshis !== 'number') { - return; - } - - var cryptoAmount = ''; - var cryptoCurrencyCode = ''; - var amountStr = txFormatService.formatAmountStr(coin, satoshis); - if (amountStr) { - var amountParts = amountStr.split(' '); - cryptoAmount = amountParts[0]; - cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : ''; - } - // Want to avoid flashing of amount strings so do all formatting after this has returned. - txFormatService.formatAlternativeStr(coin, satoshis, function(v) { - if (!v) { - vm.primaryAmount = cryptoAmount; - vm.primaryCurrency = cryptoCurrencyCode; - vm.secondaryAmount = ''; - vm.secondaryCurrency = ''; - return; - } - vm.secondaryAmount = vm.primaryAmount; - vm.secondaryCurrency = vm.primaryCurrency; - - var fiatParts = v.split(' '); - var fiatAmount = fiatParts[0]; - var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : ''; - - if (priceDisplayIsFiat) { - vm.primaryAmount = fiatAmount; - vm.primaryCurrency = fiatCurrency; - vm.secondaryAmount = cryptoAmount; - vm.secondaryCurrency = cryptoCurrencyCode; - } else { - vm.primaryAmount = cryptoAmount; - vm.primaryCurrency = cryptoCurrencyCode; - vm.secondaryAmount = fiatAmount; - vm.secondaryCurrency = fiatCurrency; - } - }); - } - - function onSuccessConfirm() { - vm.sendStatus = ''; - $ionicHistory.nextViewOptions({ - disableAnimate: true, - historyRoot: true - }); - $state.go('tabs.send').then(function() { - $ionicHistory.clearHistory(); - $state.transitionTo('tabs.home'); - }); - }; - - function setButtonText(isMultisig, isPayPro) { - if (isPayPro) { - if (vm.isCordova) { - vm.buttonText = gettextCatalog.getString('Slide to pay'); - } else { - vm.buttonText = gettextCatalog.getString('Click to pay'); - } - } else if (isMultisig) { - if (vm.isCordova) { - vm.buttonText = gettextCatalog.getString('Slide to accept'); - } else { - vm.buttonText = gettextCatalog.getString('Click to accept'); - } - } else { - if (vm.isCordova) { - vm.buttonText = gettextCatalog.getString('Slide to send'); - } else { - vm.buttonText = gettextCatalog.getString('Click to send'); - } - } - } - - function setNotReady(msg, criticalError) { - vn.readyToSend = false; - vm.notReadyMessage = msg; - $scope.criticalError = criticalError; - $log.warn('Not ready to make the payment:' + msg); - $timeout(function() { - $scope.$apply(); - }); - }; - - function setSendError(msg) { - $scope.sendStatus = ''; - vm.readyToSend = false; - $timeout(function() { - $scope.$apply(); - }); - popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { - $ionicHistory.goBack(); - }); - }; - - function setupTx(tx) { - if (tx.coin === 'bch') { - tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; - } else { - tx.displayAddress = tx.toAddress; - } - - addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact - if (!err && addr) { - tx.toName = addr.name; - tx.toEmail = addr.email; - tx.recipientType = 'contact'; - } - }); - - vm.showAddress = false; - - - setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); - - if (tx.paypro) - startExpirationTimer(tx.paypro.expires); - - updateTx(tx, vm.originWallet, { - dryRun: true - }, function(err) { - $timeout(function() { - $scope.$apply(); - }, 10); - - }); - - // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { - // if (err) { - // return exitWithError('Could not update wallets'); - // } - // - // if (vm.wallets.length > 1) { - // vm.showWalletSelector(); - // } else if (vm.wallets.length) { - // setWallet(vm.wallets[0], tx); - // } - // }); - } - - function showSendMaxWarning(wallet, sendMaxInfo) { - var feeAlternative = '', - msg = ''; - - function verifyExcludedUtxos() { - var warningMsg = []; - if (sendMaxInfo.utxosBelowFee > 0) { - warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", { - amountBelowFeeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountBelowFee) - })); - } - - if (sendMaxInfo.utxosAboveMaxSize > 0) { - warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", { - amountAboveMaxSizeStr: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize) - })); - } - return warningMsg.join('\n'); + popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { + $ionicHistory.goBack(); + }); }; - feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee); - if (feeAlternative) { - msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { - fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee), - feeAlternative: feeAlternative - }); - } else { - msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { - fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee) - }); - } - - var warningMsg = verifyExcludedUtxos(); - - if (!lodash.isEmpty(warningMsg)) - msg += '\n' + warningMsg; - - popupService.showAlert(null, msg, function() {}); - }; - - function statusChangeHandler(processName, showName, isOn) { - $log.debug('statusChangeHandler: ', processName, showName, isOn); - if ( - ( - processName === 'broadcastingTx' || - ((processName === 'signingTx') && vm.originWallet.m > 1) || - (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) - ) && !isOn) { - // Show the popup - vm.sendStatus = 'success'; - - // Clear the send flow service state - sendFlowService.state.clear(); - - if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device. - soundService.play('misc/payment_sent.mp3'); - } - - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; - } - // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. - var amount = unitFromSat * satoshis; - var log = new window.BitAnalytics.LogEvent("transfer_success", [{ - "coin": vm.originWallet.coin, - "type": "outgoing", - "amount": amount, - "fees": vm.feeCrypto - }], [channel, "adjust"]); - window.BitAnalytics.LogEventHandlers.postEvent(log); - - $timeout(function() { - $scope.$digest(); - }, 100); - } else if (showName) { - vm.sendStatus = showName; - } - }; - - function updateTx(tx, wallet, opts, cb) { - ongoingProcess.set('calculatingFee', true); - - if (opts.clearCache) { - tx.txp = {}; - } - - // $scope.tx = tx; - - // function updateAmount() { - // if (!tx.amount) return; - // - // // Amount - // tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount); - // tx.amountValueStr = tx.amountStr.split(' ')[0]; - // tx.amountUnitStr = tx.amountStr.split(' ')[1]; - // txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { - // var parts = v.split(' '); - // tx.alternativeAmountStr = v; - // tx.alternativeAmountValueStr = parts[0]; - // tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; - // }); - // } - // - // updateAmount(); - // refresh(); - - var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; - feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { - if (err) { - ongoingProcess.set('calculatingFee', false); - return cb(err); - } - - var msg; - if (usingCustomFee) { - msg = gettextCatalog.getString('Custom'); - tx.feeLevelName = msg; - } else if (usingMerchantFee) { - $log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate); - msg = gettextCatalog.getString('Suggested by Merchant'); - tx.feeLevelName = msg; + function setupTx(tx) { + if (tx.coin === 'bch') { + tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; } else { - tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; - tx.feeRate = feeRate; + tx.displayAddress = tx.toAddress; } - getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { + addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact + if (!err && addr) { + tx.toName = addr.name; + tx.toEmail = addr.email; + tx.recipientType = 'contact'; + } + }); + + vm.showAddress = false; + + + setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); + + if (tx.paypro) + startExpirationTimer(tx.paypro.expires); + + updateTx(tx, vm.originWallet, { + dryRun: true + }, function(err) { + $timeout(function() { + $scope.$apply(); + }, 10); + + }); + + // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { + // if (err) { + // return exitWithError('Could not update wallets'); + // } + // + // if (vm.wallets.length > 1) { + // vm.showWalletSelector(); + // } else if (vm.wallets.length) { + // setWallet(vm.wallets[0], tx); + // } + // }); + } + + function showSendMaxWarning(wallet, sendMaxInfo) { + var feeAlternative = '', + msg = ''; + + function verifyExcludedUtxos() { + var warningMsg = []; + if (sendMaxInfo.utxosBelowFee > 0) { + warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", { + amountBelowFeeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountBelowFee) + })); + } + + if (sendMaxInfo.utxosAboveMaxSize > 0) { + warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", { + amountAboveMaxSizeStr: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize) + })); + } + return warningMsg.join('\n'); + }; + + feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee); + if (feeAlternative) { + msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { + fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee), + feeAlternative: feeAlternative + }); + } else { + msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { + fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee) + }); + } + + var warningMsg = verifyExcludedUtxos(); + + if (!lodash.isEmpty(warningMsg)) + msg += '\n' + warningMsg; + + popupService.showAlert(null, msg, function() {}); + }; + + function statusChangeHandler(processName, showName, isOn) { + $log.debug('statusChangeHandler: ', processName, showName, isOn); + if ( + ( + processName === 'broadcastingTx' || + ((processName === 'signingTx') && vm.originWallet.m > 1) || + (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) + ) && !isOn) { + // Show the popup + vm.sendStatus = 'success'; + + // Clear the send flow service state + sendFlowService.state.clear(); + + if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device. + soundService.play('misc/payment_sent.mp3'); + } + + var channel = "firebase"; + if (platformInfo.isNW) { + channel = "ga"; + } + // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. + var amount = unitFromSat * satoshis; + var log = new window.BitAnalytics.LogEvent("transfer_success", [{ + "coin": vm.originWallet.coin, + "type": "outgoing", + "amount": amount, + "fees": vm.feeCrypto + }], [channel, "adjust"]); + window.BitAnalytics.LogEventHandlers.postEvent(log); + + $timeout(function() { + $scope.$digest(); + }, 100); + } else if (showName) { + vm.sendStatus = showName; + } + }; + + function updateTx(tx, wallet, opts, cb) { + ongoingProcess.set('calculatingFee', true); + + if (opts.clearCache) { + tx.txp = {}; + } + + // $scope.tx = tx; + + // function updateAmount() { + // if (!tx.amount) return; + // + // // Amount + // tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount); + // tx.amountValueStr = tx.amountStr.split(' ')[0]; + // tx.amountUnitStr = tx.amountStr.split(' ')[1]; + // txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { + // var parts = v.split(' '); + // tx.alternativeAmountStr = v; + // tx.alternativeAmountValueStr = parts[0]; + // tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; + // }); + // } + // + // updateAmount(); + // refresh(); + + var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; + feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { if (err) { ongoingProcess.set('calculatingFee', false); - var msg = gettextCatalog.getString('Error getting SendMax information'); - return setSendError(msg); + return cb(err); } - if (sendMaxInfo) { - - $log.debug('Send max info', sendMaxInfo); - - if (tx.sendMax && sendMaxInfo.amount == 0) { - ongoingProcess.set('calculatingFee', false); - setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () { - $ionicHistory.goBack(); - }); - return cb('no_funds'); - } - - tx.sendMaxInfo = sendMaxInfo; - tx.amount = tx.sendMaxInfo.amount; - satoshis = tx.amount; - updateSendAmounts(); - ongoingProcess.set('calculatingFee', false); - $timeout(function() { - showSendMaxWarning(wallet, sendMaxInfo); - }, 200); + var msg; + if (usingCustomFee) { + msg = gettextCatalog.getString('Custom'); + tx.feeLevelName = msg; + } else if (usingMerchantFee) { + $log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate); + msg = gettextCatalog.getString('Suggested by Merchant'); + tx.feeLevelName = msg; + } else { + tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; + tx.feeRate = feeRate; } - // txp already generated for this wallet? - if (tx.txp[wallet.id]) { - ongoingProcess.set('calculatingFee', false); - vm.readyToSend = true; - updateSendAmounts(); - $scope.$apply(); - return cb(); - } - - console.log('calling getTxp() from getSendMaxInfo cb.'); - getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { - ongoingProcess.set('calculatingFee', false); + getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { if (err) { - if (err.message == 'Insufficient funds') { - setNotReady(gettextCatalog.getString('Insufficient funds')); - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); - return cb('no_funds'); - } else - return cb(err); + ongoingProcess.set('calculatingFee', false); + var msg = gettextCatalog.getString('Error getting SendMax information'); + return setSendError(msg); } - txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); - txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { - // txp.alternativeFeeStr = v; - // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') - // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; - vm.feeFiat = v; - vm.fiatCurrency = config.wallet.settings.alternativeIsoCode; - if (v.substring(0, 1) === "<") { - vm.feeLessThanACent = true; + if (sendMaxInfo) { + + $log.debug('Send max info', sendMaxInfo); + + if (tx.sendMax && sendMaxInfo.amount == 0) { + ongoingProcess.set('calculatingFee', false); + setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () { + $ionicHistory.goBack(); + }); + return cb('no_funds'); } - - console.log("fiat", vm.feeFiat); + tx.sendMaxInfo = sendMaxInfo; + tx.amount = tx.sendMaxInfo.amount; + satoshis = tx.amount; + updateSendAmounts(); + ongoingProcess.set('calculatingFee', false); + $timeout(function() { + showSendMaxWarning(wallet, sendMaxInfo); + }, 200); + } + + // txp already generated for this wallet? + if (tx.txp[wallet.id]) { + ongoingProcess.set('calculatingFee', false); + vm.readyToSend = true; + updateSendAmounts(); + $scope.$apply(); + return cb(); + } + + console.log('calling getTxp() from getSendMaxInfo cb.'); + getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { + ongoingProcess.set('calculatingFee', false); + if (err) { + if (err.message == 'Insufficient funds') { + setNotReady(gettextCatalog.getString('Insufficient funds')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); + return cb('no_funds'); + } else + return cb(err); + } + + txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); + txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { + // txp.alternativeFeeStr = v; + // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') + // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; + vm.feeFiat = v; + vm.fiatCurrency = config.wallet.settings.alternativeIsoCode; + if (v.substring(0, 1) === "<") { + vm.feeLessThanACent = true; + } + + console.log("fiat", vm.feeFiat); + + }); + + var per = (txp.fee / (txp.amount + txp.fee) * 100); + var perString = per.toFixed(2); + txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; + txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; + vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); + vm.feeIsHigh = txp.feeToHigh; + console.log("crypto", vm.feeCrypto); + + + tx.txp[wallet.id] = txp; + $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); + vm.readyToSend = true; + updateSendAmounts(); + console.log('readyToSend:', vm.readyToSend); + $scope.$apply(); + + return cb(); }); - - var per = (txp.fee / (txp.amount + txp.fee) * 100); - var perString = per.toFixed(2); - txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; - txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; - vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); - vm.feeIsHigh = txp.feeToHigh; - console.log("crypto", vm.feeCrypto); - - - tx.txp[wallet.id] = txp; - $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); - vm.readyToSend = true; - updateSendAmounts(); - console.log('readyToSend:', vm.readyToSend); - $scope.$apply(); - - return cb(); }); }); - }); + } } - -} +})(); diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 1a191870d..694852f0f 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -1,198 +1,204 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $state, sendFlowService, configService, gettextCatalog, ongoingProcess, profileService, walletService, txFormatService) { +(function () { - var fromWalletId = ''; - var priceDisplayAsFiat = false; - var unitDecimals = 0; - var unitsFromSatoshis = 0; +angular + .module('copayApp.controllers') + .controller('walletSelectorController', walletSelectorController); - $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - $scope.$on("$ionicView.enter", onEnter); - - function onBeforeEnter(event, data) { - if (data.direction == "back") { - sendFlowService.state.pop(); - } + function walletSelectorController ($scope, $state, sendFlowService, configService, gettextCatalog, ongoingProcess, profileService, walletService, txFormatService) { + var fromWalletId = ''; + var priceDisplayAsFiat = false; + var unitDecimals = 0; + var unitsFromSatoshis = 0; - $scope.params = sendFlowService.state.getClone(); - - console.log('walletSelector onBeforeEnter after back sendflow', $scope.params); - - var config = configService.getSync().wallet.settings; - priceDisplayAsFiat = config.priceDisplay === 'fiat'; - unitDecimals = config.unitDecimals; - unitsFromSatoshis = 1 / config.unitToSatoshi; - - if ($scope.params.isWalletTransfer) { - $scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets'); - } else if (!$scope.params.thirdParty) { - $scope.sendFlowTitle = gettextCatalog.getString('Send'); - } - - $scope.coin = false; // Wallets to show (for destination screen or contacts) - $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination - fromWalletId = $scope.params['fromWalletId']; - - if ($scope.type === 'destination' && $scope.params.toAddress) { - $state.transitionTo(getNextStep($scope.params)); - } - - if ($scope.params.coin) { - $scope.coin = $scope.params.coin; // Contacts have a coin embedded - } - - if ($scope.params.amount) { // There is an amount, so presume that it is a payment request - $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); - $scope.specificAmount = $scope.specificAlternativeAmount = ''; - $scope.isPaymentRequest = true; - } - if ($scope.params.thirdParty) { - $scope.thirdParty = $scope.params.thirdParty; - } - }; - - function onEnter (event, data) { - configService.whenAvailable(function(config) { - $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; - }); - - if ($scope.thirdParty) { - // Third party services specific logic - handleThirdPartyIfShapeshift(); - } - - prepareWalletLists(); - formatRequestedAmount(); - }; - - function formatRequestedAmount() { - if ($scope.params.amount) { - var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals); - var cryptoCoin = $scope.coin.toUpperCase(); - - txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){ - if (formatted) { - var fiatParts = formatted.split(' '); - var fiatAmount = fiatParts[0]; - var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : ''; - - if (priceDisplayAsFiat) { - $scope.requestAmount = fiatAmount; - $scope.requestCurrency = fiatCurrrency; - - $scope.requestAmountSecondary = cryptoAmount; - $scope.requestCurrencySecondary = cryptoCoin; - } else { - $scope.requestAmount = cryptoAmount; - $scope.requestCurrency = cryptoCoin; - - $scope.requestAmountSecondary = fiatAmount; - $scope.requestCurrencySecondary = fiatCurrrency; - } - $scope.$apply(); - } - }); - } - } - - function handleThirdPartyIfShapeshift() { - console.log($scope.thirdParty, $scope.coin); - if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the - $scope.coin = profileService.getWallet(fromWalletId).coin; - if ($scope.coin === 'bch') { - $scope.coin = 'btc'; - } else { - $scope.coin = 'bch'; + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + $scope.$on("$ionicView.enter", onEnter); + + function onBeforeEnter(event, data) { + if (data.direction == "back") { + sendFlowService.state.pop(); } - } - } - function prepareWalletLists() { - var walletsAll = []; - var walletsSufficientFunds = []; - $scope.walletsInsufficientFunds = []; // For origin screen + $scope.params = sendFlowService.state.getClone(); - if ($scope.type === 'origin') { - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + console.log('walletSelector onBeforeEnter after back sendflow', $scope.params); - if ($scope.params.amount || $scope.coin) { + var config = configService.getSync().wallet.settings; + priceDisplayAsFiat = config.priceDisplay === 'fiat'; + unitDecimals = config.unitDecimals; + unitsFromSatoshis = 1 / config.unitToSatoshi; - walletsAll = profileService.getWallets({coin: $scope.coin}); - ongoingProcess.set('scanning', true); - walletsAll.forEach(function forWallet(wallet) { - if (!wallet.status && !wallet.cachedStatus) { - walletService.getStatus(wallet, {}, function(err, status) { - wallet.status = status; - if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { + if ($scope.params.isWalletTransfer) { + $scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets'); + } else if (!$scope.params.thirdParty) { + $scope.sendFlowTitle = gettextCatalog.getString('Send'); + } + + $scope.coin = false; // Wallets to show (for destination screen or contacts) + $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination + fromWalletId = $scope.params['fromWalletId']; + + if ($scope.type === 'destination' && $scope.params.toAddress) { + $state.transitionTo(getNextStep($scope.params)); + } + + if ($scope.params.coin) { + $scope.coin = $scope.params.coin; // Contacts have a coin embedded + } + + if ($scope.params.amount) { // There is an amount, so presume that it is a payment request + $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); + $scope.specificAmount = $scope.specificAlternativeAmount = ''; + $scope.isPaymentRequest = true; + } + if ($scope.params.thirdParty) { + $scope.thirdParty = $scope.params.thirdParty; + } + }; + + function onEnter (event, data) { + configService.whenAvailable(function(config) { + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + }); + + if ($scope.thirdParty) { + // Third party services specific logic + handleThirdPartyIfShapeshift(); + } + + prepareWalletLists(); + formatRequestedAmount(); + }; + + function formatRequestedAmount() { + if ($scope.params.amount) { + var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals); + var cryptoCoin = $scope.coin.toUpperCase(); + + txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){ + if (formatted) { + var fiatParts = formatted.split(' '); + var fiatAmount = fiatParts[0]; + var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : ''; + + if (priceDisplayAsFiat) { + $scope.requestAmount = fiatAmount; + $scope.requestCurrency = fiatCurrrency; + + $scope.requestAmountSecondary = cryptoAmount; + $scope.requestCurrencySecondary = cryptoCoin; + } else { + $scope.requestAmount = cryptoAmount; + $scope.requestCurrency = cryptoCoin; + + $scope.requestAmountSecondary = fiatAmount; + $scope.requestCurrencySecondary = fiatCurrrency; + } + $scope.$apply(); + } + }); + } + } + + function handleThirdPartyIfShapeshift() { + console.log($scope.thirdParty, $scope.coin); + if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the + $scope.coin = profileService.getWallet(fromWalletId).coin; + if ($scope.coin === 'bch') { + $scope.coin = 'btc'; + } else { + $scope.coin = 'bch'; + } + } + } + + function prepareWalletLists() { + var walletsAll = []; + var walletsSufficientFunds = []; + $scope.walletsInsufficientFunds = []; // For origin screen + + if ($scope.type === 'origin') { + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + + if ($scope.params.amount || $scope.coin) { + + walletsAll = profileService.getWallets({coin: $scope.coin}); + ongoingProcess.set('scanning', true); + walletsAll.forEach(function forWallet(wallet) { + if (!wallet.status && !wallet.cachedStatus) { + walletService.getStatus(wallet, {}, function(err, status) { + wallet.status = status; + if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + if ($scope.coin === 'btc') { // As this is a promise + $scope.walletsBtc = walletsSufficientFunds; + } else { + $scope.walletsBch = walletsSufficientFunds; + } + ongoingProcess.set('scanning', false); + }); + } else { + var walletStatus = null; + if (wallet.status && wallet.status.isValid) { + walletStatus = wallet.status; + } else if (wallet.cachedStatus) { + walletStatus = wallet.cachedStatus; + } + + if (walletStatus && walletStatus.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { walletsSufficientFunds.push(wallet); } else { $scope.walletsInsufficientFunds.push(wallet); } - if ($scope.coin === 'btc') { // As this is a promise - $scope.walletsBtc = walletsSufficientFunds; - } else { - $scope.walletsBch = walletsSufficientFunds; - } ongoingProcess.set('scanning', false); - }); + } + }); + + if ($scope.coin === 'btc') { + $scope.walletsBtc = walletsSufficientFunds; } else { - var walletStatus = null; - if (wallet.status && wallet.status.isValid) { - walletStatus = wallet.status; - } else if (wallet.cachedStatus) { - walletStatus = wallet.cachedStatus; - } - - if (walletStatus.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { - walletsSufficientFunds.push(wallet); - } else { - $scope.walletsInsufficientFunds.push(wallet); - } - ongoingProcess.set('scanning', false); + $scope.walletsBch = walletsSufficientFunds; } - }); - - if ($scope.coin === 'btc') { - $scope.walletsBtc = walletsSufficientFunds; } else { - $scope.walletsBch = walletsSufficientFunds; + $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true}); + $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true}); + $scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); } - } else { - $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true}); - $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true}); - $scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); - } - - } else if ($scope.type === 'destination') { - if (!$scope.coin) { // Allow for the coin to be set by a third party - $scope.fromWallet = profileService.getWallet(fromWalletId); - $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin - } - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); + + } else if ($scope.type === 'destination') { + if (!$scope.coin) { // Allow for the coin to be set by a third party + $scope.fromWallet = profileService.getWallet(fromWalletId); + $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin + } + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); - if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc - $scope.walletsBtc = profileService.getWallets({coin: $scope.coin}); - } else { - $scope.walletsBch = profileService.getWallets({coin: $scope.coin}); + if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc + $scope.walletsBtc = profileService.getWallets({coin: $scope.coin}); + } else { + $scope.walletsBch = profileService.getWallets({coin: $scope.coin}); + } } } - } - + - $scope.useWallet = function(wallet) { - var params = sendFlowService.state.getClone(); - if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from - params.fromWalletId = wallet.id; - } else { // we're on the destination screen, set wallet to send to - params.toWalletId = wallet.id; + $scope.useWallet = function(wallet) { + var params = sendFlowService.state.getClone(); + if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from + params.fromWalletId = wallet.id; + } else { // we're on the destination screen, set wallet to send to + params.toWalletId = wallet.id; + } + sendFlowService.goNext(params); + }; + + $scope.goBack = function() { + sendFlowService.router.goBack(); } - sendFlowService.goNext(params); - }; - $scope.goBack = function() { - sendFlowService.router.goBack(); } - -}); \ No newline at end of file +})(); \ No newline at end of file From a8e5c212f08abc13cff85b5484166714ba5773f2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 21 Sep 2018 11:41:20 +0200 Subject: [PATCH 104/118] Fix : Check if the status could be set before checking the balance. --- src/js/controllers/shapeshift.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index 0dac21a11..652f54c34 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -10,7 +10,7 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi walletsBtc = profileService.getWallets({coin: 'btc'}); walletsBch = profileService.getWallets({coin: 'bch'}); $scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) { - return w.status.balance.availableAmount > 0; + return (w.status && w.status.balance && w.status.balance.availableAmount > 0); }); $scope.singleFromWallet = $scope.fromWallets.length === 1; From 0c8d145f73cd6a861fcaed5e8d349d0e381f39f7 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 02:54:06 -0700 Subject: [PATCH 105/118] Check validity of cachedStatus. --- src/js/controllers/wallet-selector.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 694852f0f..036727333 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -145,7 +145,7 @@ angular var walletStatus = null; if (wallet.status && wallet.status.isValid) { walletStatus = wallet.status; - } else if (wallet.cachedStatus) { + } else if (wallet.cachedStatus && wallet.status.isValid) { walletStatus = wallet.cachedStatus; } From ea51e035abd3fe8066669c1e13faed876b04404b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 03:56:51 -0700 Subject: [PATCH 106/118] Fixed updating of confirmations of cached transactions. Cache now properly cleared on first fetch from Wallet Details screen. --- .../controllers/wallet-details.controller.js | 4 +- src/js/services/wallet-history.service.js | 50 +++++++++++++------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/js/controllers/wallet-details.controller.js b/src/js/controllers/wallet-details.controller.js index f3109db8b..cef57925e 100644 --- a/src/js/controllers/wallet-details.controller.js +++ b/src/js/controllers/wallet-details.controller.js @@ -400,8 +400,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var refreshInterval; - $scope.$on("$ionicView.afterEnter", function(event, data) { - $scope.updateAll(); + $scope.$on("$ionicView.afterEnter", function onAfterEnter(event, data) { + $scope.updateAll(true, true); // refreshAmountSection(); refreshInterval = $interval($scope.onRefresh, 10 * 1000); $timeout(function() { diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 0738d4348..13fc8d636 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -27,20 +27,23 @@ function addEarlyTransactions(walletId, cachedTxs, newTxs) { - var cachedTxIds = {}; + var cachedTxIndexFromId = {}; cachedTxs.forEach(function forCachedTx(tx){ - cachedTxIds[tx.txid] = true; + cachedTxIndexFromId[tx.txid] = true; }); + var confirmationsUpdated = false; var someTransactionsWereNew = false; var overlappingTxsCount = 0; newTxs.forEach(function forNewTx(tx){ - if (cachedTxIds[tx.txid]) { - overlappingTxsCount++; - } else { + if (typeof cachedTxIndexFromId[tx.txid] === "undefined") { someTransactionsWereNew = true; cachedTxs.push(tx); + } else { + var txUpdated = updateCachedTx(cachedTxs, cachedTxIndexFromId, tx); + confirmationsUpdated = confirmationsUpdated || txUpdated; + overlappingTxsCount++; } }); @@ -50,6 +53,8 @@ if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good if (someTransactionsWereNew) { saveTxHistory(walletId, cachedTxs); + } else if (confirmationsUpdated) { + saveTxHistory(walletId, cachedTxs); } else if (overlappingTxsCount === newTxs.length) { allTransactionsFetched = true; } @@ -65,9 +70,9 @@ } function addLatestTransactions(walletId, cachedTxs, newTxs) { - var cachedTxIds = {}; + var cachedTxIndexFromId = {}; cachedTxs.forEach(function forCachedTx(tx, txIndex){ - cachedTxIds[tx.txid] = txIndex; + cachedTxIndexFromId[tx.txid] = txIndex; }); var someTransactionsWereNew = false; @@ -76,15 +81,13 @@ var uniqueNewTxs = []; newTxs.forEach(function forNewTx(tx){ - if (typeof cachedTxIds[tx.txid] !== "undefined") { - if (cachedTxs[cachedTxIds[tx.txid]].confirmations < SAFE_CONFIRMATIONS && tx.confirmations >= SAFE_CONFIRMATIONS) { - cachedTxs[cachedTxIds[tx.txid]].confirmations = tx.confirmations; - confirmationsUpdated = true; - } - overlappingTxsCount++; - } else { + if (typeof cachedTxIndexFromId[tx.txid] === "undefined") { someTransactionsWereNew = true; uniqueNewTxs.push(tx); + } else { + var txUpdated = updateCachedTx(cachedTxs, cachedTxIndexFromId, tx); + confirmationsUpdated = confirmationsUpdated || txUpdated; + overlappingTxsCount++; } }); @@ -207,8 +210,25 @@ }); } - function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { + /** + * Returns true if the cached tx was updated + * @param {*} cachedTxs + * @param {*} cachedTxIndexFromId - Indices for cachedTxs, based on txid + * @param {*} tx - The most recent tx info + */ + function updateCachedTx(cachedTxs, cachedTxIndexFromId, tx) { + var updated = false; + var txIndex = cachedTxIndexFromId[tx.txid]; + var cachedTx = cachedTxs[txIndex]; + if (cachedTx.confirmations < SAFE_CONFIRMATIONS && tx.confirmations > cachedTx.confirmations) { + cachedTxs[txIndex].confirmations = tx.confirmations; + updated = true; + } + return updated; + } + + function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { if (flushCacheOnNew) { fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ if (err) { From c0912c5f1768d9b093985d6f3204d2e5290a2645 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 12:31:55 -0700 Subject: [PATCH 107/118] IIFE for reviewController to make merging easier. --- src/js/controllers/review.controller.js | 1764 ++++++++++++----------- 1 file changed, 883 insertions(+), 881 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 58ae2c2f7..fcaa80455 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -1,956 +1,958 @@ 'use strict'; +(function () { + angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { - var vm = this; + function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { + var vm = this; - vm.buttonText = ''; - vm.destination = { - address: '', - balanceAmount: '', - balanceCurrency: '', - coin: '', - color: '', - currency: '', - currencyColor: '', - kind: '', // 'address', 'contact', 'wallet' - name: '' - }; - vm.displayAddress = ''; - vm.feeCrypto = ''; - vm.feeFiat = ''; - vm.fiatCurrency = ''; - vm.feeIsHigh = false; - vm.feeLessThanACent = false; - vm.isCordova = platformInfo.isCordova; - vm.memo = ''; - vm.notReadyMessage = ''; - vm.origin = { - balanceAmount: '', - balanceCurrency: '', - currency: '', - currencyColor: '', - }; - vm.originWallet = null; - vm.paymentExpired = false; - vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here'); - vm.primaryAmount = ''; - vm.primaryCurrency = ''; - vm.usingMerchantFee = false; - vm.readyToSend = false; - vm.remainingTimeStr = ''; - vm.secondaryAmount = ''; - vm.secondaryCurrency = ''; - vm.sendingTitle = gettextCatalog.getString('You are sending'); - vm.sendStatus = ''; - vm.showAddress = true; - vm.thirdParty = false; - vm.wallet = null; - vm.memoExpanded = false; - - // Functions - vm.goBack = goBack; - vm.onSuccessConfirm = onSuccessConfirm; - vm.onShareTransaction = onShareTransaction; - - var sendFlowData; - var config = null; - var coin = ''; - var countDown = null; - var defaults = {}; - var usingCustomFee = false; - var usingMerchantFee = false; - var destinationWalletId = ''; - var lastTxId = ''; - var originWalletId = ''; - var priceDisplayIsFiat = true; - var satoshis = null; - var toAddress = ''; - var tx = {}; - var txPayproData = null; - var unitFromSat = 0; - - var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; - - $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - - - function onBeforeEnter(event, data) { - console.log('review onBeforeEnter sendflow ', sendFlowService.state); - // Reset from last time + vm.buttonText = ''; + vm.destination = { + address: '', + balanceAmount: '', + balanceCurrency: '', + coin: '', + color: '', + currency: '', + currencyColor: '', + kind: '', // 'address', 'contact', 'wallet' + name: '' + }; + vm.displayAddress = ''; + vm.feeCrypto = ''; + vm.feeFiat = ''; + vm.fiatCurrency = ''; + vm.feeIsHigh = false; + vm.feeLessThanACent = false; + vm.isCordova = platformInfo.isCordova; vm.memo = ''; + vm.notReadyMessage = ''; + vm.origin = { + balanceAmount: '', + balanceCurrency: '', + currency: '', + currencyColor: '', + }; + vm.originWallet = null; + vm.paymentExpired = false; + vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here'); + vm.primaryAmount = ''; + vm.primaryCurrency = ''; + vm.usingMerchantFee = false; + vm.readyToSend = false; + vm.remainingTimeStr = ''; + vm.secondaryAmount = ''; + vm.secondaryCurrency = ''; + vm.sendingTitle = gettextCatalog.getString('You are sending'); + vm.sendStatus = ''; + vm.showAddress = true; + vm.thirdParty = false; + vm.wallet = null; + vm.memoExpanded = false; - defaults = configService.getDefaults(); - sendFlowData = sendFlowService.state.getClone(); - originWalletId = sendFlowData.fromWalletId; - if (typeof sendFlowData.amount === 'string') { - satoshis = parseInt(sendFlowData.amount, 10); - } else { - satoshis = sendFlowData.amount; - } - toAddress = sendFlowData.toAddress; - destinationWalletId = sendFlowData.toWalletId; + // Functions + vm.goBack = goBack; + vm.onSuccessConfirm = onSuccessConfirm; + vm.onShareTransaction = onShareTransaction; + + var sendFlowData; + var config = null; + var coin = ''; + var countDown = null; + var defaults = {}; + var usingCustomFee = false; + var usingMerchantFee = false; + var destinationWalletId = ''; + var lastTxId = ''; + var originWalletId = ''; + var priceDisplayIsFiat = true; + var satoshis = null; + var toAddress = ''; + var tx = {}; + var txPayproData = null; + var unitFromSat = 0; + + var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; + + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - vm.displayAddress = sendFlowData.displayAddress; - vm.originWallet = profileService.getWallet(originWalletId); - vm.origin.currency = vm.originWallet.coin.toUpperCase(); - coin = vm.originWallet.coin; - if (sendFlowData.thirdParty) { - vm.thirdParty = sendFlowData.thirdParty; - switch (vm.thirdParty.id) { - case 'shapeshift': - initShapeshift(function (err) { - if (err) { - // Error stop here - ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { - $ionicHistory.goBack(); - }); - } else { - _next(data); - } - }); - break; - case 'bip70': - initBip70(); - default: - _next(data); - break; + function onBeforeEnter(event, data) { + console.log('review onBeforeEnter sendflow ', sendFlowService.state); + // Reset from last time + vm.memo = ''; + + defaults = configService.getDefaults(); + sendFlowData = sendFlowService.state.getClone(); + originWalletId = sendFlowData.fromWalletId; + if (typeof sendFlowData.amount === 'string') { + satoshis = parseInt(sendFlowData.amount, 10); + } else { + satoshis = sendFlowData.amount; } - } else { - _next(data); - } + toAddress = sendFlowData.toAddress; + destinationWalletId = sendFlowData.toWalletId; - function _next() { - configService.get(function onConfig(err, configCache) { - if (err) { - $log.err('Error getting config.', err); - } else { - config = configCache; - priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; - vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); - console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); - unitFromSat = 1 / config.wallet.settings.unitToSatoshi; - } - updateSendAmounts(); - getOriginWalletBalance(vm.originWallet); - handleDestinationAsAddress(toAddress, coin); - handleDestinationAsWallet(sendFlowData.toWalletId); - createVanityTransaction(data); - }); - } - } - - vm.approve = function() { + vm.displayAddress = sendFlowData.displayAddress; + vm.originWallet = profileService.getWallet(originWalletId); + vm.origin.currency = vm.originWallet.coin.toUpperCase(); + coin = vm.originWallet.coin; - if (!tx || !vm.originWallet) return; - - if (vm.paymentExpired) { - popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { - $ionicHistory.goBack(); - })); - vm.sendStatus = ''; - $timeout(function() { - $scope.$apply(); - }); - return; - } - - ongoingProcess.set('creatingTx', true, statusChangeHandler); - getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { - ongoingProcess.set('creatingTx', false, statusChangeHandler); - if (err) return; - - // confirm txs for more that 20usd, if not spending/touchid is enabled - function confirmTx(cb) { - if (walletService.isEncrypted(vm.originWallet)) - return cb(); - - var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); - return cb(); - }; - - function publishAndSign() { - if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { - $log.info('No signing proposal: No private key'); - - return walletService.onlyPublish(vm.originWallet, txp, function(err) { - if (err) setSendError(err); - }, statusChangeHandler); - } - - walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { - if (err) return setSendError(err); - if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { - txConfirmNotification.subscribe(vm.originWallet, { - txid: txp.txid + if (sendFlowData.thirdParty) { + vm.thirdParty = sendFlowData.thirdParty; + switch (vm.thirdParty.id) { + case 'shapeshift': + initShapeshift(function (err) { + if (err) { + // Error stop here + ongoingProcess.set('connectingShapeshift', false); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); + } else { + _next(data); + } }); - lastTxId = txp.txid; + break; + case 'bip70': + initBip70(); + default: + _next(data); + break; + } + } else { + _next(data); + } + + function _next() { + configService.get(function onConfig(err, configCache) { + if (err) { + $log.err('Error getting config.', err); + } else { + config = configCache; + priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; + vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); + console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); + unitFromSat = 1 / config.wallet.settings.unitToSatoshi; } - }, statusChangeHandler); + updateSendAmounts(); + getOriginWalletBalance(vm.originWallet); + handleDestinationAsAddress(toAddress, coin); + handleDestinationAsWallet(sendFlowData.toWalletId); + createVanityTransaction(data); + }); + } + } + + vm.approve = function() { + + if (!tx || !vm.originWallet) return; + + if (vm.paymentExpired) { + popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { + $ionicHistory.goBack(); + })); + vm.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + return; + } + + ongoingProcess.set('creatingTx', true, statusChangeHandler); + getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { + ongoingProcess.set('creatingTx', false, statusChangeHandler); + if (err) return; + + // confirm txs for more that 20usd, if not spending/touchid is enabled + function confirmTx(cb) { + if (walletService.isEncrypted(vm.originWallet)) + return cb(); + + var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); + return cb(); + }; + + function publishAndSign() { + if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { + $log.info('No signing proposal: No private key'); + + return walletService.onlyPublish(vm.originWallet, txp, function(err) { + if (err) setSendError(err); + }, statusChangeHandler); + } + + walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { + if (err) return setSendError(err); + if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { + txConfirmNotification.subscribe(vm.originWallet, { + txid: txp.txid + }); + lastTxId = txp.txid; + } + }, statusChangeHandler); + }; + + confirmTx(function(nok) { + if (nok) { + vm.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + return; + } + publishAndSign(); + }); + }); + }; + + vm.chooseFeeLevel = function(tx, wallet) { + + if (wallet.coin == 'bch') return; + if (usingMerchantFee) return; + + var scope = $rootScope.$new(true); + scope.network = tx.network; + scope.feeLevel = tx.feeLevel; + scope.noSave = true; + scope.coin = vm.originWallet.coin; + + if (usingCustomFee) { + scope.customFeePerKB = tx.feeRate; + scope.feePerSatByte = tx.feeRate / 1000; + } + + $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { + scope: scope, + backdropClickToClose: false, + hardwareBackButtonClose: false + }).then(function(modal) { + scope.chooseFeeLevelModal = modal; + scope.openModal(); + }); + scope.openModal = function() { + scope.chooseFeeLevelModal.show(); }; - confirmTx(function(nok) { - if (nok) { - vm.sendStatus = ''; - $timeout(function() { - $scope.$apply(); + scope.hideModal = function(newFeeLevel, customFeePerKB) { + scope.chooseFeeLevelModal.hide(); + $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); + + usingCustomFee = newFeeLevel == 'custom' ? true : false; + + if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; + + tx.feeLevel = newFeeLevel; + if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); + + updateTx(tx, vm.originWallet, { + clearCache: true, + dryRun: true + }, function() {}); + }; + }; + + function createVanityTransaction(data) { + console.log('createVanityTransaction()'); + var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; + + // Grab stateParams + tx = { + amount: parseInt(sendFlowData.amount), + sendMax: sendFlowData.sendMax, + fromWalletId: sendFlowData.fromWalletId, + toAddress: sendFlowData.toAddress, + paypro: txPayproData, + + feeLevel: configFeeLevel, + spendUnconfirmed: config.wallet.spendUnconfirmed, + + // Vanity tx info (not in the real tx) + recipientType: vm.destination.kind || null, + toName: vm.destination.name || null, + toEmail: vm.destination.email || null, + toColor: vm.destination.color || null, + network: false, + coin: vm.originWallet.coin, + txp: {}, + }; + + + if (data.stateParams.requiredFeeRate) { + vm.usingMerchantFee = true; + tx.feeRate = parseInt(data.stateParams.requiredFeeRate); + } + + if (tx.coin && tx.coin === 'bch') { + tx.feeLevel = 'normal'; + } + + var B = tx.coin === 'bch' ? bitcoreCash : bitcore; + var networkName; + try { + if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer + ongoingProcess.set('generatingNewAddress', true); + var toWallet = profileService.getWallet(destinationWalletId); + + // We need an address to send to, so we ask the walletService to create a new address for the toWallet. + console.log('Getting address for wallet...'); + walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { + console.log('getAddress cb called', err); + ongoingProcess.set('generatingNewAddress', false); + tx.toAddress = addr; + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + console.log('calling setupTx() for wallet.'); + setupTx(tx); }); - return; - } - publishAndSign(); - }); - }); - }; - - vm.chooseFeeLevel = function(tx, wallet) { - - if (wallet.coin == 'bch') return; - if (usingMerchantFee) return; - - var scope = $rootScope.$new(true); - scope.network = tx.network; - scope.feeLevel = tx.feeLevel; - scope.noSave = true; - scope.coin = vm.originWallet.coin; - - if (usingCustomFee) { - scope.customFeePerKB = tx.feeRate; - scope.feePerSatByte = tx.feeRate / 1000; - } - - $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { - scope: scope, - backdropClickToClose: false, - hardwareBackButtonClose: false - }).then(function(modal) { - scope.chooseFeeLevelModal = modal; - scope.openModal(); - }); - scope.openModal = function() { - scope.chooseFeeLevelModal.show(); - }; - - scope.hideModal = function(newFeeLevel, customFeePerKB) { - scope.chooseFeeLevelModal.hide(); - $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); - - usingCustomFee = newFeeLevel == 'custom' ? true : false; - - if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; - - tx.feeLevel = newFeeLevel; - if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); - - updateTx(tx, vm.originWallet, { - clearCache: true, - dryRun: true - }, function() {}); - }; - }; - - function createVanityTransaction(data) { - console.log('createVanityTransaction()'); - var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; - - // Grab stateParams - tx = { - amount: parseInt(sendFlowData.amount), - sendMax: sendFlowData.sendMax, - fromWalletId: sendFlowData.fromWalletId, - toAddress: sendFlowData.toAddress, - paypro: txPayproData, - - feeLevel: configFeeLevel, - spendUnconfirmed: config.wallet.spendUnconfirmed, - - // Vanity tx info (not in the real tx) - recipientType: vm.destination.kind || null, - toName: vm.destination.name || null, - toEmail: vm.destination.email || null, - toColor: vm.destination.color || null, - network: false, - coin: vm.originWallet.coin, - txp: {}, - }; - - - if (data.stateParams.requiredFeeRate) { - vm.usingMerchantFee = true; - tx.feeRate = parseInt(data.stateParams.requiredFeeRate); - } - - if (tx.coin && tx.coin === 'bch') { - tx.feeLevel = 'normal'; - } - - var B = tx.coin === 'bch' ? bitcoreCash : bitcore; - var networkName; - try { - if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer - ongoingProcess.set('generatingNewAddress', true); - var toWallet = profileService.getWallet(destinationWalletId); - - // We need an address to send to, so we ask the walletService to create a new address for the toWallet. - console.log('Getting address for wallet...'); - walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { - console.log('getAddress cb called', err); - ongoingProcess.set('generatingNewAddress', false); - tx.toAddress = addr; + } else { // This is a Wallet-to-address transfer networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; - console.log('calling setupTx() for wallet.'); + console.log('calling setupTx() for address.'); setupTx(tx); - }); - } else { // This is a Wallet-to-address transfer - networkName = (new B.Address(tx.toAddress)).network.name; - tx.network = networkName; - console.log('calling setupTx() for address.'); - setupTx(tx); - } - } catch (e) { - console.error('Error setting up tx', e); - var message = gettextCatalog.getString('Invalid address'); - popupService.showAlert(null, message, function () { - $ionicHistory.nextViewOptions({ - disableAnimate: true, - historyRoot: true - }); - $state.go('tabs.send').then(function () { - $ionicHistory.clearHistory(); - }); - }); - return; - } - } - function getOriginWalletBalance(originWallet) { - var balanceText = getWalletBalanceDisplayText(vm.originWallet); - vm.origin.balanceAmount = balanceText.amount; - vm.origin.balanceCurrency = balanceText.currency; - } - - function getSendMaxInfo(tx, wallet, cb) { - if (!tx.sendMax) return cb(); - - //ongoingProcess.set('retrievingInputs', true); - walletService.getSendMaxInfo(wallet, { - feePerKb: tx.feeRate, - excludeUnconfirmedUtxos: !tx.spendUnconfirmed, - returnInputs: true, - }, cb); - }; - - function getTxp(tx, wallet, dryRun, cb) { - - // ToDo: use a credential's (or fc's) function for this - if (tx.description && !wallet.credentials.sharedEncryptingKey) { - var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key'); - $log.warn(msg); - return setSendError(msg); - } - - if (tx.amount > Number.MAX_SAFE_INTEGER) { - var msg = gettextCatalog.getString('Amount too big'); - $log.warn(msg); - return setSendError(msg); - } - - var txp = {}; - - txp.outputs = [{ - 'toAddress': tx.toAddress, - 'amount': tx.amount, - 'message': vm.memo - }]; - - if (tx.sendMaxInfo) { - txp.inputs = tx.sendMaxInfo.inputs; - txp.fee = tx.sendMaxInfo.fee; - } else { - if (usingCustomFee || usingMerchantFee) { - txp.feePerKb = tx.feeRate; - } else txp.feeLevel = tx.feeLevel; - } - - txp.message = vm.memo; - - if (tx.paypro) { - txp.payProUrl = tx.paypro.url; - } - txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; - txp.dryRun = dryRun; - walletService.createTx(wallet, txp, function(err, ctxp) { - if (err) { - setSendError(err); - return cb(err); - } - return cb(null, ctxp); - }); - }; - - function getWalletBalanceDisplayText(wallet) { - var balanceCryptoAmount = ''; - var balanceCryptoCurrencyCode = ''; - var balanceFiatAmount = ''; - var balanceFiatCurrency = '' - var displayAmount = ''; - var displayCurrency = ''; - - var walletStatus = null; - if (wallet.status.isValid) { - walletStatus = wallet.status; - } else if (wallet.cachedStatus.isValid) { - walletStatus = wallet.cachedStatus; - } - - if (walletStatus) { - var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); - balanceCryptoAmount = cryptoBalanceParts[0]; - balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; - - if (walletStatus.alternativeBalanceAvailable) { - balanceFiatAmount = walletStatus.spendableBalanceAlternative; - balanceFiatCurrency = walletStatus.alternativeIsoCode; - } - } - - if (priceDisplayIsFiat) { - displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; - displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; - } else { - displayAmount = balanceCryptoAmount; - displayCurrency = balanceCryptoCurrencyCode; - } - - return { - amount: displayAmount, - currency: displayCurrency - }; - } - - function goBack() { - sendFlowService.router.goBack(); - } - - function handleDestinationAsAddress(address, originCoin) { - if (!address) { - return; - } - - // Check if the recipient is a contact - addressbookService.get(originCoin + address, function(err, contact) { - if (!err && contact) { - handleDestinationAsAddressOfContact(contact); - } else { - if (originCoin === 'bch') { - vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; - } else { - vm.destination.address = address; } - vm.destination.kind = 'address'; + } catch (e) { + console.error('Error setting up tx', e); + var message = gettextCatalog.getString('Invalid address'); + popupService.showAlert(null, message, function () { + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function () { + $ionicHistory.clearHistory(); + }); + }); + return; } - }); - - } - - function handleDestinationAsAddressOfContact(contact) { - vm.destination.kind = 'contact'; - vm.destination.name = contact.name; - vm.destination.email = contact.email; - vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; - vm.destination.currency = contact.coin.toUpperCase(); - vm.destination.currencyColor = vm.destination.color; - } - - function handleDestinationAsWallet(walletId) { - destinationWalletId = walletId; - if (!destinationWalletId) { - return; + } + function getOriginWalletBalance(originWallet) { + var balanceText = getWalletBalanceDisplayText(vm.originWallet); + vm.origin.balanceAmount = balanceText.amount; + vm.origin.balanceCurrency = balanceText.currency; } - var destinationWallet = profileService.getWallet(destinationWalletId); - vm.destination.coin = destinationWallet.coin; - vm.destination.color = destinationWallet.color; - vm.destination.currency = destinationWallet.coin.toUpperCase(); - vm.destination.kind = 'wallet'; - vm.destination.name = destinationWallet.name; + function getSendMaxInfo(tx, wallet, cb) { + if (!tx.sendMax) return cb(); - if (defaults) { - vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; - } - - var balanceText = getWalletBalanceDisplayText(destinationWallet); - vm.destination.balanceAmount = balanceText.amount; - vm.destination.balanceCurrency = balanceText.currency; - } - - function initBip70() { - vm.sendingTitle = gettextCatalog.getString('You are paying'); - vm.memo = vm.thirdParty.memo; - vm.memoExpanded = !!vm.memo; - vm.destination.name = vm.thirdParty.name; - - txPayproData = { - caTrusted: vm.thirdParty.caTrusted, - domain: vm.thirdParty.domain, - expires: vm.thirdParty.expires, - toAddress: toAddress, - url: vm.thirdParty.url, - verified: vm.thirdParty.verified, + //ongoingProcess.set('retrievingInputs', true); + walletService.getSendMaxInfo(wallet, { + feePerKb: tx.feeRate, + excludeUnconfirmedUtxos: !tx.spendUnconfirmed, + returnInputs: true, + }, cb); }; - } - function initShapeshift(cb) { - vm.sendingTitle = gettextCatalog.getString('You are shifting'); - if (!vm.thirdParty.data) { - vm.thirdParty.data = {}; + function getTxp(tx, wallet, dryRun, cb) { + + // ToDo: use a credential's (or fc's) function for this + if (tx.description && !wallet.credentials.sharedEncryptingKey) { + var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key'); + $log.warn(msg); + return setSendError(msg); + } + + if (tx.amount > Number.MAX_SAFE_INTEGER) { + var msg = gettextCatalog.getString('Amount too big'); + $log.warn(msg); + return setSendError(msg); + } + + var txp = {}; + + txp.outputs = [{ + 'toAddress': tx.toAddress, + 'amount': tx.amount, + 'message': vm.memo + }]; + + if (tx.sendMaxInfo) { + txp.inputs = tx.sendMaxInfo.inputs; + txp.fee = tx.sendMaxInfo.fee; + } else { + if (usingCustomFee || usingMerchantFee) { + txp.feePerKb = tx.feeRate; + } else txp.feeLevel = tx.feeLevel; + } + + txp.message = vm.memo; + + if (tx.paypro) { + txp.payProUrl = tx.paypro.url; + } + txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; + txp.dryRun = dryRun; + walletService.createTx(wallet, txp, function(err, ctxp) { + if (err) { + setSendError(err); + return cb(err); + } + return cb(null, ctxp); + }); + }; + + function getWalletBalanceDisplayText(wallet) { + var balanceCryptoAmount = ''; + var balanceCryptoCurrencyCode = ''; + var balanceFiatAmount = ''; + var balanceFiatCurrency = '' + var displayAmount = ''; + var displayCurrency = ''; + + var walletStatus = null; + if (wallet.status.isValid) { + walletStatus = wallet.status; + } else if (wallet.cachedStatus.isValid) { + walletStatus = wallet.cachedStatus; + } + + if (walletStatus) { + var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); + balanceCryptoAmount = cryptoBalanceParts[0]; + balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; + + if (walletStatus.alternativeBalanceAvailable) { + balanceFiatAmount = walletStatus.spendableBalanceAlternative; + balanceFiatCurrency = walletStatus.alternativeIsoCode; + } + } + + if (priceDisplayIsFiat) { + displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; + displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; + } else { + displayAmount = balanceCryptoAmount; + displayCurrency = balanceCryptoCurrencyCode; + } + + return { + amount: displayAmount, + currency: displayCurrency + }; } - var toWallet = profileService.getWallet(destinationWalletId); - vm.destination.name = toWallet.name; - vm.destination.color = toWallet.color; - vm.destination.currency = toWallet.coin.toUpperCase(); + function goBack() { + sendFlowService.router.goBack(); + } + + function handleDestinationAsAddress(address, originCoin) { + if (!address) { + return; + } + + // Check if the recipient is a contact + addressbookService.get(originCoin + address, function(err, contact) { + if (!err && contact) { + handleDestinationAsAddressOfContact(contact); + } else { + if (originCoin === 'bch') { + vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; + } else { + vm.destination.address = address; + } + vm.destination.kind = 'address'; + } + }); + + } + + function handleDestinationAsAddressOfContact(contact) { + vm.destination.kind = 'contact'; + vm.destination.name = contact.name; + vm.destination.email = contact.email; + vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; + vm.destination.currency = contact.coin.toUpperCase(); + vm.destination.currencyColor = vm.destination.color; + } + + function handleDestinationAsWallet(walletId) { + destinationWalletId = walletId; + if (!destinationWalletId) { + return; + } + + var destinationWallet = profileService.getWallet(destinationWalletId); + vm.destination.coin = destinationWallet.coin; + vm.destination.color = destinationWallet.color; + vm.destination.currency = destinationWallet.coin.toUpperCase(); + vm.destination.kind = 'wallet'; + vm.destination.name = destinationWallet.name; + + if (defaults) { + vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; + } + + var balanceText = getWalletBalanceDisplayText(destinationWallet); + vm.destination.balanceAmount = balanceText.amount; + vm.destination.balanceCurrency = balanceText.currency; + } + + function initBip70() { + vm.sendingTitle = gettextCatalog.getString('You are paying'); + vm.memo = vm.thirdParty.memo; + vm.memoExpanded = !!vm.memo; + vm.destination.name = vm.thirdParty.name; + + txPayproData = { + caTrusted: vm.thirdParty.caTrusted, + domain: vm.thirdParty.domain, + expires: vm.thirdParty.expires, + toAddress: toAddress, + url: vm.thirdParty.url, + verified: vm.thirdParty.verified, + }; + } + + function initShapeshift(cb) { + vm.sendingTitle = gettextCatalog.getString('You are shifting'); + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } + + var toWallet = profileService.getWallet(destinationWalletId); + vm.destination.name = toWallet.name; + vm.destination.color = toWallet.color; + vm.destination.currency = toWallet.coin.toUpperCase(); - ongoingProcess.set('connectingShapeshift', true); - walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { - if (err) { - return cb(err); - } - walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { + ongoingProcess.set('connectingShapeshift', true); + walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { if (err) { return cb(err); } - - // Need to use the correct service to do it. - var amount = parseFloat(satoshis / 100000000); - - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { + walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { if (err) { return cb(err); - } else { - vm.destination.kind = 'shapeshift'; - vm.destination.address = toAddress; - tx.toAddress = shapeshiftData.toAddress; - vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; - vm.memoExpanded = !!vm.memo; - ongoingProcess.set('connectingShapeshift', false); - cb(); - } + } + + // Need to use the correct service to do it. + var amount = parseFloat(satoshis / 100000000); + + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { + if (err) { + return cb(err); + } else { + vm.destination.kind = 'shapeshift'; + vm.destination.address = toAddress; + tx.toAddress = shapeshiftData.toAddress; + vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; + vm.memoExpanded = !!vm.memo; + ongoingProcess.set('connectingShapeshift', false); + cb(); + } + }); }); }); - }); - } - - function onShareTransaction() { - var explorerTxUrl = 'https://explorer.bitcoin.com/' + tx.coin + '/tx/' + lastTxId; - if (platformInfo.isCordova) { - var text = gettextCatalog.getString('Take a look at this Bitcoin Cash transaction here: ') + explorerTxUrl; - if (coin === 'btc') { - text = gettextCatalog.getString('Take a look at this Bitcoin transaction here: ') + explorerTxUrl; - } - window.plugins.socialsharing.share(text, null, null, null); - } else { - ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000); - clipboardService.copyToClipboard(explorerTxUrl); } - - } - function startExpirationTimer(expirationTime) { - vm.paymentExpired = false; - setExpirationTime(); + function onShareTransaction() { + var explorerTxUrl = 'https://explorer.bitcoin.com/' + tx.coin + '/tx/' + lastTxId; + if (platformInfo.isCordova) { + var text = gettextCatalog.getString('Take a look at this Bitcoin Cash transaction here: ') + explorerTxUrl; + if (coin === 'btc') { + text = gettextCatalog.getString('Take a look at this Bitcoin transaction here: ') + explorerTxUrl; + } + window.plugins.socialsharing.share(text, null, null, null); + } else { + ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000); + clipboardService.copyToClipboard(explorerTxUrl); + } + + } - countDown = $interval(function() { + function startExpirationTimer(expirationTime) { + vm.paymentExpired = false; setExpirationTime(); - }, 1000); - function setExpirationTime() { - console.log('setExpirationTime()'); - var now = Math.floor(Date.now() / 1000); + countDown = $interval(function() { + setExpirationTime(); + }, 1000); - if (now > expirationTime) { - setExpiredValues(); + function setExpirationTime() { + console.log('setExpirationTime()'); + var now = Math.floor(Date.now() / 1000); + + if (now > expirationTime) { + setExpiredValues(); + return; + } + + var totalSecs = expirationTime - now; + var m = Math.floor(totalSecs / 60); + var s = totalSecs % 60; + vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); + }; + + function setExpiredValues() { + vm.paymentExpired = true; + vm.remainingTimeStr = gettextCatalog.getString('Expired'); + vm.readyToSend = false; + if (countDown) $interval.cancel(countDown); + $timeout(function() { + $scope.$apply(); + }); + }; + }; + + function updateSendAmounts() { + if (typeof satoshis !== 'number') { return; } - var totalSecs = expirationTime - now; - var m = Math.floor(totalSecs / 60); - var s = totalSecs % 60; - vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); + var cryptoAmount = ''; + var cryptoCurrencyCode = ''; + var amountStr = txFormatService.formatAmountStr(coin, satoshis); + if (amountStr) { + var amountParts = amountStr.split(' '); + cryptoAmount = amountParts[0]; + cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : ''; + } + // Want to avoid flashing of amount strings so do all formatting after this has returned. + txFormatService.formatAlternativeStr(coin, satoshis, function(v) { + if (!v) { + vm.primaryAmount = cryptoAmount; + vm.primaryCurrency = cryptoCurrencyCode; + vm.secondaryAmount = ''; + vm.secondaryCurrency = ''; + return; + } + vm.secondaryAmount = vm.primaryAmount; + vm.secondaryCurrency = vm.primaryCurrency; + + var fiatParts = v.split(' '); + var fiatAmount = fiatParts[0]; + var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : ''; + + if (priceDisplayIsFiat) { + vm.primaryAmount = fiatAmount; + vm.primaryCurrency = fiatCurrency; + vm.secondaryAmount = cryptoAmount; + vm.secondaryCurrency = cryptoCurrencyCode; + } else { + vm.primaryAmount = cryptoAmount; + vm.primaryCurrency = cryptoCurrencyCode; + vm.secondaryAmount = fiatAmount; + vm.secondaryCurrency = fiatCurrency; + } + }); + } + + function onSuccessConfirm() { + vm.sendStatus = ''; + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function() { + $ionicHistory.clearHistory(); + $state.transitionTo('tabs.home'); + }); }; - function setExpiredValues() { - vm.paymentExpired = true; - vm.remainingTimeStr = gettextCatalog.getString('Expired'); + function setButtonText(isMultisig, isPayPro) { + if (isPayPro) { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to pay'); + } else { + vm.buttonText = gettextCatalog.getString('Click to pay'); + } + } else if (isMultisig) { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to accept'); + } else { + vm.buttonText = gettextCatalog.getString('Click to accept'); + } + } else { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to send'); + } else { + vm.buttonText = gettextCatalog.getString('Click to send'); + } + } + } + + function setNotReady(msg, criticalError) { + vn.readyToSend = false; + vm.notReadyMessage = msg; + $scope.criticalError = criticalError; + $log.warn('Not ready to make the payment:' + msg); + $timeout(function() { + $scope.$apply(); + }); + }; + + function setSendError(msg) { + $scope.sendStatus = ''; vm.readyToSend = false; - if (countDown) $interval.cancel(countDown); $timeout(function() { $scope.$apply(); }); - }; - }; - - function updateSendAmounts() { - if (typeof satoshis !== 'number') { - return; - } - - var cryptoAmount = ''; - var cryptoCurrencyCode = ''; - var amountStr = txFormatService.formatAmountStr(coin, satoshis); - if (amountStr) { - var amountParts = amountStr.split(' '); - cryptoAmount = amountParts[0]; - cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : ''; - } - // Want to avoid flashing of amount strings so do all formatting after this has returned. - txFormatService.formatAlternativeStr(coin, satoshis, function(v) { - if (!v) { - vm.primaryAmount = cryptoAmount; - vm.primaryCurrency = cryptoCurrencyCode; - vm.secondaryAmount = ''; - vm.secondaryCurrency = ''; - return; - } - vm.secondaryAmount = vm.primaryAmount; - vm.secondaryCurrency = vm.primaryCurrency; - - var fiatParts = v.split(' '); - var fiatAmount = fiatParts[0]; - var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : ''; - - if (priceDisplayIsFiat) { - vm.primaryAmount = fiatAmount; - vm.primaryCurrency = fiatCurrency; - vm.secondaryAmount = cryptoAmount; - vm.secondaryCurrency = cryptoCurrencyCode; - } else { - vm.primaryAmount = cryptoAmount; - vm.primaryCurrency = cryptoCurrencyCode; - vm.secondaryAmount = fiatAmount; - vm.secondaryCurrency = fiatCurrency; - } - }); - } - - function onSuccessConfirm() { - vm.sendStatus = ''; - $ionicHistory.nextViewOptions({ - disableAnimate: true, - historyRoot: true - }); - $state.go('tabs.send').then(function() { - $ionicHistory.clearHistory(); - $state.transitionTo('tabs.home'); - }); - }; - - function setButtonText(isMultisig, isPayPro) { - if (isPayPro) { - if (vm.isCordova) { - vm.buttonText = gettextCatalog.getString('Slide to pay'); - } else { - vm.buttonText = gettextCatalog.getString('Click to pay'); - } - } else if (isMultisig) { - if (vm.isCordova) { - vm.buttonText = gettextCatalog.getString('Slide to accept'); - } else { - vm.buttonText = gettextCatalog.getString('Click to accept'); - } - } else { - if (vm.isCordova) { - vm.buttonText = gettextCatalog.getString('Slide to send'); - } else { - vm.buttonText = gettextCatalog.getString('Click to send'); - } - } - } - - function setNotReady(msg, criticalError) { - vn.readyToSend = false; - vm.notReadyMessage = msg; - $scope.criticalError = criticalError; - $log.warn('Not ready to make the payment:' + msg); - $timeout(function() { - $scope.$apply(); - }); - }; - - function setSendError(msg) { - $scope.sendStatus = ''; - vm.readyToSend = false; - $timeout(function() { - $scope.$apply(); - }); - popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { - $ionicHistory.goBack(); - }); - }; - - function setupTx(tx) { - if (tx.coin === 'bch') { - tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; - } else { - tx.displayAddress = tx.toAddress; - } - - addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact - if (!err && addr) { - tx.toName = addr.name; - tx.toEmail = addr.email; - tx.recipientType = 'contact'; - } - }); - - vm.showAddress = false; - - - setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); - - if (tx.paypro) - startExpirationTimer(tx.paypro.expires); - - updateTx(tx, vm.originWallet, { - dryRun: true - }, function(err) { - $timeout(function() { - $scope.$apply(); - }, 10); - - }); - - // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { - // if (err) { - // return exitWithError('Could not update wallets'); - // } - // - // if (vm.wallets.length > 1) { - // vm.showWalletSelector(); - // } else if (vm.wallets.length) { - // setWallet(vm.wallets[0], tx); - // } - // }); - } - - function showSendMaxWarning(wallet, sendMaxInfo) { - var feeAlternative = '', - msg = ''; - - function verifyExcludedUtxos() { - var warningMsg = []; - if (sendMaxInfo.utxosBelowFee > 0) { - warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", { - amountBelowFeeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountBelowFee) - })); - } - - if (sendMaxInfo.utxosAboveMaxSize > 0) { - warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", { - amountAboveMaxSizeStr: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize) - })); - } - return warningMsg.join('\n'); + popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { + $ionicHistory.goBack(); + }); }; - feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee); - if (feeAlternative) { - msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { - fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee), - feeAlternative: feeAlternative - }); - } else { - msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { - fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee) - }); - } - - var warningMsg = verifyExcludedUtxos(); - - if (!lodash.isEmpty(warningMsg)) - msg += '\n' + warningMsg; - - popupService.showAlert(null, msg, function() {}); - }; - - function statusChangeHandler(processName, showName, isOn) { - $log.debug('statusChangeHandler: ', processName, showName, isOn); - if ( - ( - processName === 'broadcastingTx' || - ((processName === 'signingTx') && vm.originWallet.m > 1) || - (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) - ) && !isOn) { - // Show the popup - vm.sendStatus = 'success'; - - // Clear the send flow service state - sendFlowService.state.clear(); - - if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device. - soundService.play('misc/payment_sent.mp3'); - } - - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; - } - // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. - var amount = unitFromSat * satoshis; - var log = new window.BitAnalytics.LogEvent("transfer_success", [{ - "coin": vm.originWallet.coin, - "type": "outgoing", - "amount": amount, - "fees": vm.feeCrypto - }], [channel, "adjust"]); - window.BitAnalytics.LogEventHandlers.postEvent(log); - - $timeout(function() { - $scope.$digest(); - }, 100); - } else if (showName) { - vm.sendStatus = showName; - } - }; - - function updateTx(tx, wallet, opts, cb) { - ongoingProcess.set('calculatingFee', true); - - if (opts.clearCache) { - tx.txp = {}; - } - - // $scope.tx = tx; - - // function updateAmount() { - // if (!tx.amount) return; - // - // // Amount - // tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount); - // tx.amountValueStr = tx.amountStr.split(' ')[0]; - // tx.amountUnitStr = tx.amountStr.split(' ')[1]; - // txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { - // var parts = v.split(' '); - // tx.alternativeAmountStr = v; - // tx.alternativeAmountValueStr = parts[0]; - // tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; - // }); - // } - // - // updateAmount(); - // refresh(); - - var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; - feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { - if (err) { - ongoingProcess.set('calculatingFee', false); - return cb(err); - } - - var msg; - if (usingCustomFee) { - msg = gettextCatalog.getString('Custom'); - tx.feeLevelName = msg; - } else if (usingMerchantFee) { - $log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate); - msg = gettextCatalog.getString('Suggested by Merchant'); - tx.feeLevelName = msg; + function setupTx(tx) { + if (tx.coin === 'bch') { + tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; } else { - tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; - tx.feeRate = feeRate; + tx.displayAddress = tx.toAddress; } - getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { + addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact + if (!err && addr) { + tx.toName = addr.name; + tx.toEmail = addr.email; + tx.recipientType = 'contact'; + } + }); + + vm.showAddress = false; + + + setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); + + if (tx.paypro) + startExpirationTimer(tx.paypro.expires); + + updateTx(tx, vm.originWallet, { + dryRun: true + }, function(err) { + $timeout(function() { + $scope.$apply(); + }, 10); + + }); + + // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { + // if (err) { + // return exitWithError('Could not update wallets'); + // } + // + // if (vm.wallets.length > 1) { + // vm.showWalletSelector(); + // } else if (vm.wallets.length) { + // setWallet(vm.wallets[0], tx); + // } + // }); + } + + function showSendMaxWarning(wallet, sendMaxInfo) { + var feeAlternative = '', + msg = ''; + + function verifyExcludedUtxos() { + var warningMsg = []; + if (sendMaxInfo.utxosBelowFee > 0) { + warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", { + amountBelowFeeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountBelowFee) + })); + } + + if (sendMaxInfo.utxosAboveMaxSize > 0) { + warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", { + amountAboveMaxSizeStr: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize) + })); + } + return warningMsg.join('\n'); + }; + + feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee); + if (feeAlternative) { + msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { + fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee), + feeAlternative: feeAlternative + }); + } else { + msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { + fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee) + }); + } + + var warningMsg = verifyExcludedUtxos(); + + if (!lodash.isEmpty(warningMsg)) + msg += '\n' + warningMsg; + + popupService.showAlert(null, msg, function() {}); + }; + + function statusChangeHandler(processName, showName, isOn) { + $log.debug('statusChangeHandler: ', processName, showName, isOn); + if ( + ( + processName === 'broadcastingTx' || + ((processName === 'signingTx') && vm.originWallet.m > 1) || + (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) + ) && !isOn) { + // Show the popup + vm.sendStatus = 'success'; + + // Clear the send flow service state + sendFlowService.state.clear(); + + if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device. + soundService.play('misc/payment_sent.mp3'); + } + + var channel = "firebase"; + if (platformInfo.isNW) { + channel = "ga"; + } + // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. + var amount = unitFromSat * satoshis; + var log = new window.BitAnalytics.LogEvent("transfer_success", [{ + "coin": vm.originWallet.coin, + "type": "outgoing", + "amount": amount, + "fees": vm.feeCrypto + }], [channel, "adjust"]); + window.BitAnalytics.LogEventHandlers.postEvent(log); + + $timeout(function() { + $scope.$digest(); + }, 100); + } else if (showName) { + vm.sendStatus = showName; + } + }; + + function updateTx(tx, wallet, opts, cb) { + ongoingProcess.set('calculatingFee', true); + + if (opts.clearCache) { + tx.txp = {}; + } + + // $scope.tx = tx; + + // function updateAmount() { + // if (!tx.amount) return; + // + // // Amount + // tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount); + // tx.amountValueStr = tx.amountStr.split(' ')[0]; + // tx.amountUnitStr = tx.amountStr.split(' ')[1]; + // txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { + // var parts = v.split(' '); + // tx.alternativeAmountStr = v; + // tx.alternativeAmountValueStr = parts[0]; + // tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; + // }); + // } + // + // updateAmount(); + // refresh(); + + var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; + feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { if (err) { ongoingProcess.set('calculatingFee', false); - var msg = gettextCatalog.getString('Error getting SendMax information'); - return setSendError(msg); + return cb(err); } - if (sendMaxInfo) { - - $log.debug('Send max info', sendMaxInfo); - - if (tx.sendMax && sendMaxInfo.amount == 0) { - ongoingProcess.set('calculatingFee', false); - setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () { - $ionicHistory.goBack(); - }); - return cb('no_funds'); - } - - tx.sendMaxInfo = sendMaxInfo; - tx.amount = tx.sendMaxInfo.amount; - satoshis = tx.amount; - updateSendAmounts(); - ongoingProcess.set('calculatingFee', false); - $timeout(function() { - showSendMaxWarning(wallet, sendMaxInfo); - }, 200); + var msg; + if (usingCustomFee) { + msg = gettextCatalog.getString('Custom'); + tx.feeLevelName = msg; + } else if (usingMerchantFee) { + $log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate); + msg = gettextCatalog.getString('Suggested by Merchant'); + tx.feeLevelName = msg; + } else { + tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; + tx.feeRate = feeRate; } - // txp already generated for this wallet? - if (tx.txp[wallet.id]) { - ongoingProcess.set('calculatingFee', false); - vm.readyToSend = true; - updateSendAmounts(); - $scope.$apply(); - return cb(); - } - - console.log('calling getTxp() from getSendMaxInfo cb.'); - getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { - ongoingProcess.set('calculatingFee', false); + getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { if (err) { - if (err.message == 'Insufficient funds') { - setNotReady(gettextCatalog.getString('Insufficient funds')); - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); - return cb('no_funds'); - } else - return cb(err); + ongoingProcess.set('calculatingFee', false); + var msg = gettextCatalog.getString('Error getting SendMax information'); + return setSendError(msg); } - txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); - txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { - // txp.alternativeFeeStr = v; - // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') - // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; - vm.feeFiat = v; - vm.fiatCurrency = config.wallet.settings.alternativeIsoCode; - if (v.substring(0, 1) === "<") { - vm.feeLessThanACent = true; + if (sendMaxInfo) { + + $log.debug('Send max info', sendMaxInfo); + + if (tx.sendMax && sendMaxInfo.amount == 0) { + ongoingProcess.set('calculatingFee', false); + setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () { + $ionicHistory.goBack(); + }); + return cb('no_funds'); } - - console.log("fiat", vm.feeFiat); + tx.sendMaxInfo = sendMaxInfo; + tx.amount = tx.sendMaxInfo.amount; + satoshis = tx.amount; + updateSendAmounts(); + ongoingProcess.set('calculatingFee', false); + $timeout(function() { + showSendMaxWarning(wallet, sendMaxInfo); + }, 200); + } + + // txp already generated for this wallet? + if (tx.txp[wallet.id]) { + ongoingProcess.set('calculatingFee', false); + vm.readyToSend = true; + updateSendAmounts(); + $scope.$apply(); + return cb(); + } + + console.log('calling getTxp() from getSendMaxInfo cb.'); + getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { + ongoingProcess.set('calculatingFee', false); + if (err) { + if (err.message == 'Insufficient funds') { + setNotReady(gettextCatalog.getString('Insufficient funds')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); + return cb('no_funds'); + } else + return cb(err); + } + + txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); + txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { + // txp.alternativeFeeStr = v; + // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') + // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; + vm.feeFiat = v; + vm.fiatCurrency = config.wallet.settings.alternativeIsoCode; + if (v.substring(0, 1) === "<") { + vm.feeLessThanACent = true; + } + + console.log("fiat", vm.feeFiat); + + }); + + var per = (txp.fee / (txp.amount + txp.fee) * 100); + var perString = per.toFixed(2); + txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; + txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; + vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); + vm.feeIsHigh = txp.feeToHigh; + console.log("crypto", vm.feeCrypto); + + + tx.txp[wallet.id] = txp; + $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); + vm.readyToSend = true; + updateSendAmounts(); + console.log('readyToSend:', vm.readyToSend); + $scope.$apply(); + + return cb(); }); - - var per = (txp.fee / (txp.amount + txp.fee) * 100); - var perString = per.toFixed(2); - txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; - txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; - vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); - vm.feeIsHigh = txp.feeToHigh; - console.log("crypto", vm.feeCrypto); - - - tx.txp[wallet.id] = txp; - $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); - vm.readyToSend = true; - updateSendAmounts(); - console.log('readyToSend:', vm.readyToSend); - $scope.$apply(); - - return cb(); }); }); - }); + } } - -} +})(); From a17c3518c3fd7be6f3039fd7b0e494b5ab4f2ad5 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 14:38:28 -0700 Subject: [PATCH 108/118] Manual merge from Sprint 22 for 575. --- src/js/controllers/review.controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index fcaa80455..2311b2cb6 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -79,7 +79,7 @@ angular $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - + function onBeforeEnter(event, data) { console.log('review onBeforeEnter sendflow ', sendFlowService.state); // Reset from last time @@ -100,7 +100,7 @@ angular vm.originWallet = profileService.getWallet(originWalletId); vm.origin.currency = vm.originWallet.coin.toUpperCase(); coin = vm.originWallet.coin; - + if (sendFlowData.thirdParty) { vm.thirdParty = sendFlowData.thirdParty; switch (vm.thirdParty.id) { @@ -398,12 +398,12 @@ angular var balanceCryptoAmount = ''; var balanceCryptoCurrencyCode = ''; var balanceFiatAmount = ''; - var balanceFiatCurrency = '' + var balanceFiatCurrency = ''; var displayAmount = ''; var displayCurrency = ''; var walletStatus = null; - if (wallet.status.isValid) { + if (wallet.status && wallet.status.isValid) { walletStatus = wallet.status; } else if (wallet.cachedStatus.isValid) { walletStatus = wallet.cachedStatus; From 2e1cc9fa872a33a10ce36e4c22820828a2708ef9 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 16:43:27 -0700 Subject: [PATCH 109/118] Showing cached history first when entering Wallet Details screen. --- src/js/controllers/wallet-details.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/controllers/wallet-details.controller.js b/src/js/controllers/wallet-details.controller.js index cef57925e..429420741 100644 --- a/src/js/controllers/wallet-details.controller.js +++ b/src/js/controllers/wallet-details.controller.js @@ -203,6 +203,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function updateTxHistoryFromCachedData() { + $scope.vm.gettingCachedHistory = true; walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ $scope.vm.gettingCachedHistory = false; if (err) { @@ -401,6 +402,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var refreshInterval; $scope.$on("$ionicView.afterEnter", function onAfterEnter(event, data) { + updateTxHistoryFromCachedData(); $scope.updateAll(true, true); // refreshAmountSection(); refreshInterval = $interval($scope.onRefresh, 10 * 1000); From ff40317d87ea74405bc0a52afd5b43e021783c65 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 16:45:46 -0700 Subject: [PATCH 110/118] Merged fix for wallet transaction history. --- src/js/services/wallet-history.service.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 13fc8d636..512a2d8b1 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -147,6 +147,7 @@ * @param {function(error, txs)} cb - txs is always an array, may be empty */ function getCachedTxHistory(walletId, cb) { + console.log('txhistory updateLocalTxHistoryByPage()'); storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){ if (err) { return cb(err, []); @@ -229,6 +230,7 @@ } function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { + console.log('txhistory updaetLocalTxHistoryByPage()'); if (flushCacheOnNew) { fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ if (err) { From d72a71d9205ef533dd0d941fb67902d3d5ac7a4d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 21 Sep 2018 17:21:59 -0700 Subject: [PATCH 111/118] RC version. --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index a73fae33f..2eab6232d 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.1.2", - "fullVersion": "5.1-rc1", - "androidVersion": "501002", + "version": "5.1.3", + "fullVersion": "5.1-rc2", + "androidVersion": "501003", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From a426a5abc0a14e4dfbf413b812712aaa42c7cdcf Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 26 Sep 2018 16:27:47 +1200 Subject: [PATCH 112/118] Now handles URIs that begin with 'bch:'. --- src/js/services/bitcoin-uri.service.js | 10 ++++++++-- src/js/services/bitcoin-uri.service.spec.js | 22 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/js/services/bitcoin-uri.service.js b/src/js/services/bitcoin-uri.service.js index 3da84f3da..669c14bf3 100644 --- a/src/js/services/bitcoin-uri.service.js +++ b/src/js/services/bitcoin-uri.service.js @@ -151,6 +151,7 @@ hasPassphrase: false, type: 1, }, + isTestnet: false, isValid: false, label: '', message: '', @@ -170,7 +171,6 @@ "req-param0": '', "req-param1": '' }, - testnet: false, url: '' // For BIP70 } @@ -204,7 +204,13 @@ } else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) { parsed.coin = 'bch'; - parsed.test = false; + parsed.isTestnet = false; + addressAndParams = colonSplit[2].trim(); + console.log('Is bch'); + + } else if (/^(?:bch)$/.test(preColonLower)) { + parsed.coin = 'bch'; + parsed.isTestnet = false; addressAndParams = colonSplit[2].trim(); console.log('Is bch'); diff --git a/src/js/services/bitcoin-uri.service.spec.js b/src/js/services/bitcoin-uri.service.spec.js index 2ddbd0d2e..bf6c9ce9e 100644 --- a/src/js/services/bitcoin-uri.service.spec.js +++ b/src/js/services/bitcoin-uri.service.spec.js @@ -30,7 +30,7 @@ describe('bitcoinUriService', function() { expect(parsed.isValid).toBe(true); expect(parsed.coin).toBe('bch'); expect(parsed.publicAddress).toBeUndefined(); - expect(parsed.isTestnet).toBeUndefined(); + expect(parsed.isTestnet).toBe(false); expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu'); }); @@ -171,6 +171,16 @@ describe('bitcoinUriService', function() { expect(parsed.isTestnet).toBe(false); }); + it('legacy address with bch prefix', function() { + + var parsed = bitcoinUriService.parse('bch:19yUdM2H7sADrabR6Afu9zTpmwqr6WYprX'); + + expect(parsed.isValid).toBe(true); + expect(parsed.coin).toBe('bch'); + expect(parsed.publicAddress.legacy).toBe('19yUdM2H7sADrabR6Afu9zTpmwqr6WYprX'); + expect(parsed.isTestnet).toBe(false); + }); + it('cashAddr testnet with prefix', function() { var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt'); @@ -191,6 +201,16 @@ describe('bitcoinUriService', function() { expect(parsed.isTestnet).toBe(false); }); + it('cashAddr with bch prefix', function() { + + var parsed = bitcoinUriService.parse('bch:qpqzqtjqqc00nsxj0e3kevz65ujg4yt5z5w99jap5f'); + + expect(parsed.isValid).toBe(true); + expect(parsed.coin).toBe('bch'); + expect(parsed.publicAddress.cashAddr).toBe('qpqzqtjqqc00nsxj0e3kevz65ujg4yt5z5w99jap5f'); + expect(parsed.isTestnet).toBe(false); + }); + it('cashAddr with dash', function() { var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0'); From 898b7d03fe938599c4f6e6a99e6d2b1bdfaef708 Mon Sep 17 00:00:00 2001 From: A R Hansen Date: Mon, 8 Oct 2018 01:55:55 +0200 Subject: [PATCH 113/118] 0 fee transaction feature added. --- .vscode/launch.json | 14 ++ src/js/controllers/confirm.js | 3 +- src/js/controllers/review.controller.js | 32 +++- src/js/services/feeService.js | 4 +- www/css/main.css | 209 +++++++++++++++--------- 5 files changed, 175 insertions(+), 87 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..29f920f4f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}/www/index.html" + } + ] +} \ No newline at end of file diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 76950df73..ae4bc7153 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -517,7 +517,8 @@ angular.module('copayApp.controllers').controller('confirmController', function( if (!lodash.isEmpty(warningMsg)) msg += '\n' + warningMsg; - popupService.showAlert(null, msg, function() {}); + popupService.showAlert(null, msg, function() {}); + }; $scope.onWalletSelect = function(wallet) { diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 2311b2cb6..ae30382fb 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -718,14 +718,23 @@ angular if (tx.paypro) startExpirationTimer(tx.paypro.expires); - updateTx(tx, vm.originWallet, { - dryRun: true - }, function(err) { - $timeout(function() { - $scope.$apply(); - }, 10); + popupService.showConfirm(null, 'Do you want this transaction to be sent without a fee?', 'Yes', 'No', function(ok) { + if(ok){ + tx.feeRate = 0; + // tx.feeLevel = 'free'; + usingCustomFee = true; + } + updateTx(tx, vm.originWallet, { + dryRun: true + }, function(err) { + $timeout(function() { + $scope.$apply(); + }, 10); + + }); + }); + - }); // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { // if (err) { @@ -778,6 +787,7 @@ angular msg += '\n' + warningMsg; popupService.showAlert(null, msg, function() {}); + //popupService.showConfirm(null, msg, null, null, function() {}); }; function statusChangeHandler(processName, showName, isOn) { @@ -855,7 +865,11 @@ angular } var msg; - if (usingCustomFee) { + // if (tx.feeLevel == 'free'){ + // tx.feeRate = 0; + // } + // else + if (usingCustomFee) { msg = gettextCatalog.getString('Custom'); tx.feeLevelName = msg; } else if (usingMerchantFee) { @@ -865,7 +879,9 @@ angular } else { tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; tx.feeRate = feeRate; + } + getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { if (err) { diff --git a/src/js/services/feeService.js b/src/js/services/feeService.js index bba972b81..edb1800a4 100644 --- a/src/js/services/feeService.js +++ b/src/js/services/feeService.js @@ -12,6 +12,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou normal: gettext('Normal'), economy: gettext('Economy'), superEconomy: gettext('Super Economy'), +// free: gettext('No fee (works only for BCH)'), custom: gettext('Custom') }; @@ -31,6 +32,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou root.getFeeRate = function(coin, network, feeLevel, cb) { if (feeLevel == 'custom') return cb(); + if (feeLevel == 'free') return cb(null, 0); network = network || 'livenet'; @@ -48,9 +50,9 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou }) }); } - var feeRate = feeLevelRate.feePerKb; + if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKb / 1000).toFixed() + ' SAT/B'); return cb(null, feeRate); diff --git a/www/css/main.css b/www/css/main.css index 8602ba35d..83c50fe17 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -10083,7 +10083,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm .onboarding .button.button-white.button-standard, .onboarding .button.button-green.button-standard, .onboarding .button.button-assertive.button-standard, #shapeshift .button-shapeshift { - width: 85%; + width: 90%; max-width: 300px; margin-left: auto; margin-right: auto; @@ -10195,6 +10195,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm .button { border-radius: 6px; } .button.button-full { + border-radius: 0; display: block; } .button-green { border-color: #FFF; @@ -10263,8 +10264,8 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm color: #FFFFFF; text-decoration: none; } .button-white-outline.active, .button-white-outline.activated { - border-color: #FFF; - background-color: #FAFAFA; } + border-color: #FFFFFF; + background-color: #FFFFFF; } .button-white-outline.button-outline { border-color: #FFFFFF; background: transparent; @@ -10632,7 +10633,7 @@ qrcode { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { font-size: 2em; } } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { - font-size: 0.9em; } + font-size: 1.2em; } @media (min-width: 375px) { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { font-size: 1.3em; } } @@ -10659,7 +10660,8 @@ qrcode { line-height: 1em; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { margin-right: 5px; - word-break: break-all; } + word-break: break-all; + width: 100%; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .alternative-amount { color: #6F6F70; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies { @@ -10680,30 +10682,33 @@ qrcode { justify-content: space-between; margin: 0 14px; } #view-amount .scroll-content .send-amount-extras .available-funds { - color: #6F6F70; } + color: #6F6F70; + text-align: left; } + #view-amount .scroll-content .send-amount-extras .change-currency { + text-align: right; } #view-amount .scroll-content .send-amount-extras .warning { color: #b7664d; } - #view-amount .scroll-content .send-amount-extras .extra, - #view-amount .scroll-content .send-amount-extras button.extra { - /*display: flex;*/ - flex: 0 1 auto; } - #view-amount .scroll-content .send-amount-extras button.extra { - background: none; - border: none; - color: #000; - font-family: 'ProximaNova'; - font-size: 14px; - line-height: normal; - min-height: auto; - min-width: auto; - padding: 0; } - #view-amount .scroll-content .send-amount-extras .button .icon:before { - font-size: 14px; + #view-amount .scroll-content .send-amount-extras .extra { + flex: 1; line-height: normal; } - #view-amount .scroll-content .send-amount-extras .button span { - display: flex; - align-items: center; - justify-content: center; } + #view-amount .scroll-content .send-amount-extras .extra .button { + background: none; + border: none; + border-radius: 0; + color: #000; + font-family: 'ProximaNova'; + font-size: 14px; + line-height: normal; + min-height: auto; + min-width: auto; + padding: 0; } + #view-amount .scroll-content .send-amount-extras .extra .button .icon:before { + font-size: 14px; + line-height: normal; } + #view-amount .scroll-content .send-amount-extras .extra .button span { + display: flex; + align-items: center; + justify-content: center; } #view-amount .scroll-content .button.no-margin { margin: 0; } #view-amount .scroll-content .notification-warning { @@ -10939,14 +10944,14 @@ qrcode { #tab-home .buttons .button { border: 2px solid; border-radius: 47px; - padding: 0 15px 0 15px; + padding: 8px 2px 8px 2px; text-align: center; width: 100%; max-width: 300px; font-size: 19px; font-weight: bolder; - min-height: auto; - line-height: 36px; } + min-height: 0; + line-height: 19px; } #tab-home .wallet-coin-logo { vertical-align: middle; @@ -11013,6 +11018,10 @@ qrcode { #tab-home .release .title { font-weight: 700; color: #444; } + #tab-home .release .release-notes { + white-space: pre; + white-space: pre-line; + text-align: left; } #tab-home .release .button { width: 100%; border: none; } @@ -11025,6 +11034,14 @@ qrcode { #tab-home .badge { top: 11px; } +.popup-update .popup-buttons { + display: block; } + +.popup-update .popup-buttons .button { + display: block; + min-width: 100% !important; + margin-top: 4px; } + #tab-receive .button-share { color: #fff; box-shadow: none; @@ -11101,6 +11118,8 @@ qrcode { #tab-receive .payment-received-container { margin: 0 20px; } + #tab-receive .payment-received-container svg { + max-height: 400px; } #tab-receive .payment-received-container .payment-received-amount { font-size: 1.8em; display: block; @@ -11207,7 +11226,7 @@ qrcode { margin: auto; margin-top: 18px; } #tab-send .send-wrapper .buttons .button { - height: 60px; + min-height: 65px; line-height: 16px; margin-right: 0px; width: 95%; @@ -11284,7 +11303,9 @@ qrcode { margin-top: 18px; } #tab-send .sendTip .buttons .button { font-weight: bold; - font-size: 19px; } + font-size: 19px; + line-height: 26px; + padding: 8px 6px; } #tab-send .sendTip .button-first-contact img { height: 19px; width: 19px; @@ -11920,6 +11941,13 @@ qrcode { #walletDetails .bp-content.status-bar { margin-top: 20px; margin-top: env(safe-area-inset-top); } + #walletDetails .bp-content.collapse ion-content { + margin-top: 40px; } + #walletDetails .bp-content.collapse .amount__scale, #walletDetails .bp-content.collapse .amount__error { + -webkit-transform: scale3d(0.5, 0.5, 0.5) translateY(0px); + transform: scale3d(0.5, 0.5, 0.5) translateY(0px); } + #walletDetails .bp-content.collapse .amount-alternative, #walletDetails .bp-content.collapse .send-receive-buttons, #walletDetails .bp-content.collapse .wallet-details-wallet-info { + opacity: 0; } #walletDetails .bar-header { border: 0; background: #eeb640; } @@ -11932,9 +11960,12 @@ qrcode { #walletDetails ion-content { padding-top: 0; top: 0; + transition: all 0.25s ease-in-out; + margin-top: 185px; margin-bottom: 16px; } - #walletDetails ion-content.collapsible { - margin-top: 230px; } + @media only screen and (max-height: 500px) { + #walletDetails ion-content { + margin-top: 165px; } } #walletDetails ion-content .scroll { background: #f8f8f9; min-height: 300px; } @@ -11965,38 +11996,45 @@ qrcode { justify-content: space-evenly; width: 100%; position: absolute; - bottom: 20px; } + bottom: 20px; + transition: all 0.25s ease-in-out; } #walletDetails .amount-wrapper .send-receive-buttons > .col { padding: 5px 10px; margin-bottom: 0; } #walletDetails .amount-wrapper .send-receive-buttons .button { border: 2px solid; border-radius: 47px; - padding: 0 15px 0 15px; + padding: 6px 2px 6px 2px; text-align: center; width: 100%; max-width: 300px; font-size: 19px; font-weight: bolder; - min-height: auto; - line-height: 36px; } + min-height: 0; + line-height: 19px; } #walletDetails .amount { - width: 100%; - text-align: center; - color: #fff; - height: 230px; - padding-top: 40px; - display: block; align-items: center; - justify-content: center; } + color: #fff; + display: block; + height: 230px; + justify-content: center; + padding-top: 40px; + text-align: center; + transition: all 0.25s ease-in-out; + width: 100%; } + @media only screen and (max-height: 500px) { + #walletDetails .amount { + height: 210px; } } #walletDetails .amount__balance { -webkit-transform: scale3d(1, 1, 1) translateY(45px); - transform: scale3d(1, 1, 1) translateY(45px); } + transform: scale3d(1, 1, 1) translateY(45px); + transition: all 0.25s ease-in-out; } #walletDetails .amount__updating { z-index: 999; margin-top: -2.1rem; } #walletDetails .amount-alternative { - line-height: 36px; } + line-height: 36px; + transition: all 0.25s ease-in-out; } #walletDetails .amount__button-balance { background-color: transparent; border: 1px solid rgba(255, 255, 255, 0.25); @@ -12006,7 +12044,8 @@ qrcode { vertical-align: middle; } #walletDetails .amount__error { font-size: 14px; - padding: 35px 20px; } + padding: 35px 20px; + opacity: 1; } #walletDetails .no-alternative { padding-top: 45px; } #walletDetails .item.item-footer { @@ -12071,7 +12110,9 @@ a.item { font-size: 0.9em; } .loading-wallet svg { - margin-top: 0; } + margin-top: 0; + width: 16px; + height: 16px; } #advanced-settings .list .item:before { display: block; @@ -12125,7 +12166,9 @@ a.item { margin-top: 18px; } #shapeshift .empty-case .buttons .button { font-weight: bold; - font-size: 19px; } + font-size: 19px; + line-height: 26px; + padding: 8px 6px; } #shapeshift .empty-case .button-first-contact img { height: 19px; width: 19px; @@ -13812,6 +13855,7 @@ click-to-accept { height: 92px; width: 100%; } click-to-accept .click-to-accept__button.button.button-primary.button-standard { + border-radius: 0; height: 100%; max-width: 9999px; width: 100%; } @@ -13918,6 +13962,8 @@ slide-to-accept { height: 92px; width: 100%; background: #494949; } + slide-to-accept .slide .button { + border-radius: 0; } slide-to-accept .slide__listener { height: 100%; width: 100%; @@ -15349,20 +15395,25 @@ log-options #check-bar .checkbox-icon { #cash-scan a { cursor: pointer; } -#view-review { - background-color: #494949; } - #view-review slide-to-accept, #view-review slide-to-accept-success { - margin-bottom: constant(safe-area-inset-bottom); - /* iOS 11.0 */ - margin-bottom: env(safe-area-inset-bottom); - /* iOS 11.2 */ } - #view-review .fee-summary { - position: absolute; - bottom: 92px; } - #view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { - box-shadow: none; } - #view-review .warning { - color: #b7664d; } +#view-review slide-to-accept, #view-review slide-to-accept-success { + margin-bottom: constant(safe-area-inset-bottom); + /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); + /* iOS 11.2 */ } + +#view-review .fee-summary { + bottom: 92px; + bottom: calc(92px + constant(safe-area-inset-bottom)); + /* iOS 11.0 */ + bottom: calc(92px + env(safe-area-inset-bottom)); + /* iOS 11.2 */ + position: absolute; } + +#view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { + box-shadow: none; } + +#view-review .warning { + color: #b7664d; } .gravatar { border-radius: 3px; @@ -15395,6 +15446,7 @@ log-options #check-bar .checkbox-icon { left: 13px; top: 50%; padding: 0; + -webkit-transform: translate(0, -50%); transform: translate(0, -50%); } .item .item-content.item-content-compact { @@ -15463,8 +15515,8 @@ ion-content.padded-bottom-cta-with-summary { overflow: hidden; text-overflow: ellipsis; } .address-frame.expanded { - white-space: pre-wrap; - word-break: break-all; } + white-space: normal; + text-overflow: clip; } .address-frame .prefix { color: #000000; } .address-frame .mid { @@ -15507,13 +15559,13 @@ ion-content.padded-bottom-cta-with-summary { transform: scale(1, 1); } .fee-summary { - position: relative; + background-color: #F2F2F2; + box-sizing: border-box; display: flex; flex-direction: column; - width: 100%; padding: 5px 12px 15px; - box-sizing: border-box; - background-color: #F2F2F2; } + position: relative; + width: 100%; } .fee-summary:before { content: ''; position: absolute; @@ -15523,16 +15575,16 @@ ion-content.padded-bottom-cta-with-summary { height: 15px; background: linear-gradient(to bottom, rgba(242, 242, 242, 0) 0%, #f2f2f2 100%); } .fee-summary .amount { - display: flex; - flex-direction: row; - justify-content: space-between; width: 100%; } - .fee-summary .amount .fee-fiat.positive { - color: #70955F; } - .fee-summary .amount .fee-fiat.negative { - color: #C24633; } + .fee-summary .amount .fee-fiat { + display: inline; } + .fee-summary .amount .fee-fiat.positive { + color: #70955F; } + .fee-summary .amount .fee-fiat.negative { + color: #C24633; } .fee-summary .amount .fee-crypto { - color: #A7A7A7; } + color: #A7A7A7; + float: right; } .formatted-amount { display: inline-block; } @@ -15557,6 +15609,9 @@ ion-content.padded-bottom-cta-with-summary { margin-left: 5px; text-transform: uppercase; } +.wallet-balance-directive { + display: inline-block; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ From d47393f16025571502a4bc418541799f1206e445 Mon Sep 17 00:00:00 2001 From: A R Hansen Date: Mon, 8 Oct 2018 02:00:36 +0200 Subject: [PATCH 114/118] .. From 4e524a7500abda0067c23132397870a771d22b64 Mon Sep 17 00:00:00 2001 From: A R Hansen Date: Mon, 8 Oct 2018 02:16:43 +0200 Subject: [PATCH 115/118] Not needed. --- src/js/services/feeService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/feeService.js b/src/js/services/feeService.js index edb1800a4..5189527b2 100644 --- a/src/js/services/feeService.js +++ b/src/js/services/feeService.js @@ -32,7 +32,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou root.getFeeRate = function(coin, network, feeLevel, cb) { if (feeLevel == 'custom') return cb(); - if (feeLevel == 'free') return cb(null, 0); +// if (feeLevel == 'free') return cb(null, 0); network = network || 'livenet'; From f1d2b00b2baf6df56a293e2b6b452bc2f7ae93de Mon Sep 17 00:00:00 2001 From: A R Hansen Date: Fri, 12 Oct 2018 11:53:16 +0200 Subject: [PATCH 116/118] set up my own bitcore server and changed the url. for some reason Bitcoin.com's server won't accept 0 fee txs. --- src/js/services/configService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/configService.js b/src/js/services/configService.js index 72cc4825f..6f2c0ec67 100644 --- a/src/js/services/configService.js +++ b/src/js/services/configService.js @@ -18,7 +18,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer }, bwscash: { - url: 'https://bwscash.bitcoin.com/bws/api' + url: 'https://bws.freepages.dk/bws/api' }, download: { From 70f520ea43b99365d65083960720309a93675313 Mon Sep 17 00:00:00 2001 From: A R Hansen Date: Fri, 12 Oct 2018 12:46:09 +0200 Subject: [PATCH 117/118] adding instructions --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 75ced5808..9224c2b34 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ +This is a fork of the Bitcoin.com wallet to add additional features. +Features included: + + - Zero fee transactions (only works for Bitcoin Cash). You will be asked for, if you want to send a transaction as zero fee on the confirmation page. + + ## Zero fee transactions: + Because most network nodes on the Bitcoin Cash network don't relay zero fee txs, you will experience some strange issues, but don't worry: for me personally the Bitcoin.com pool has included all my zero fee transactions, but please beware that the receiver probably won't see your tx before it has been confirmed and please do also keep in mind, that the transactions coming after it won't confirm or be seen before the zero fee one has been confirmed. + + If you do already have a Bitcoin.com wallet, you need to create a new one to use this feature or change the wallet URL to: https://bws.freepages.dk/bws/api + + ## Disclaimer + + Please beware this is my personal experimental project. You are more than welcome to play with it, but I don't take any responsibility of loss of funds due to errors in the code, so please make sure you made a backup before running this software. + + ## Builds + + You can build the software yourself using the instructions below or use prebuilt binaries which can be found here (currently Windows and Linux only): https://ipfs.io/ipfs/QmR1DaS3QsDS48SzAWKUWFfmtMfJc4tgMtkSk3JFmuzewe + + + + The Bitcoin.com wallet is a fork of the Copay Wallet (https://github.com/bitpay/copay). The Bitcoin.com wallet is a secure bitcoin wallet platform for both desktop and mobile devices. It uses [Bitcore Wallet Service](https://github.com/Bitcoin-com/bitcore-wallet-service) (our fork of the [Bitpay Bitcore Wallet Service](https://github.com/bitpay/bitcore-wallet-service)) (BWS) for peer synchronization and network interfacing. From 06f1f4df91b95133c2d8a9cf9c43a3285b84b59b Mon Sep 17 00:00:00 2001 From: A R Hansen Date: Fri, 12 Oct 2018 12:48:15 +0200 Subject: [PATCH 118/118] - ll - --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9224c2b34..ae2788217 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Features included: You can build the software yourself using the instructions below or use prebuilt binaries which can be found here (currently Windows and Linux only): https://ipfs.io/ipfs/QmR1DaS3QsDS48SzAWKUWFfmtMfJc4tgMtkSk3JFmuzewe - + ## The Bitcoin.com wallet is a fork of the Copay Wallet (https://github.com/bitpay/copay).