-
-
Payment Proposals
-
-
-
-
- Total Locked Balance:
- {{index.lockedBalanceStr}}
- {{index.lockedBalanceAlternative}}
- {{index.alternativeIsoCode}}
-
+
Payment Proposals
+
Unsent transactions
+
-
-
Unsent transactions
-
-
+
+ Total Locked Balance:
+ {{index.lockedBalanceStr}}
+ {{index.lockedBalanceAlternative}}
+ {{index.alternativeIsoCode}}
@@ -275,7 +274,7 @@
@@ -411,7 +410,7 @@
{{index.feeOpts[fee.level]|translate}}
-
@@ -522,7 +521,7 @@
diff --git a/src/css/icons.css b/src/css/icons.css
index 68111592b..96c803836 100644
--- a/src/css/icons.css
+++ b/src/css/icons.css
@@ -24,214 +24,159 @@
-moz-osx-font-smoothing: grayscale;
}
-.icon-minus-circle:before {
- content: "\e62c";
+.icon-sell-btc:before {
+ content: "\e632";
}
-
-.icon-plus-circle:before {
- content: "\e62d";
+.icon-buy-btc:before {
+ content: "\e631";
}
-
-.icon-close-circle:before {
- content: "\e62e";
-}
-
-.icon-checkmark-circle:before {
- content: "\e62b";
-}
-
-.icon-circle:before {
- content: "\e629";
-}
-
-.icon-circle-active:before {
- content: "\e627";
-}
-
-.icon-scan:before {
- content: "\e62a";
-}
-
-.icon-trash:before {
- content: "\e626";
-}
-
-.icon-wallet:before {
- content: "\e622";
-}
-
-.icon-history:before {
- content: "\e623";
-}
-
-.icon-reference:before {
- content: "\e621";
-}
-
-.icon-bell:before {
- content: "\e61c";
-}
-
-.icon-trash:before {
- content: "\e626";
-}
-
-.icon-wallet:before {
- content: "\e622";
-}
-
-.icon-history:before {
- content: "\e623";
-}
-
-.icon-reference:before {
- content: "\e621";
-}
-
-.icon-bell:before {
- content: "\e61c";
-}
-
-.icon-receive:before {
- content: "\e625";
-}
-
-.icon-wrench:before {
- content: "\e61d";
-}
-
-.icon-download:before {
- content: "\e61e";
-}
-
-.icon-upload:before {
- content: "\e61f";
-}
-
-.icon-power:before {
- content: "\e620";
-}
-
-.icon-forward:before {
- content: "\e624";
-}
-
-.icon-compose:before {
- content: "\e610";
-}
-
-.icon-contact:before {
- content: "\e611";
-}
-
-.icon-email:before {
- content: "\e612";
-}
-
-.icon-gear:before {
- content: "\e613";
-}
-
-.icon-home:before {
- content: "\e614";
-}
-
-.icon-locked:before {
- content: "\e615";
-}
-
-.icon-paperplane:before {
- content: "\e617";
-}
-
-.icon-people:before {
- content: "\e618";
-}
-
-.icon-person:before {
- content: "\e619";
-}
-
-.icon-pricetag:before {
- content: "\e61a";
-}
-
-.icon-pricetags:before {
- content: "\e61b";
-}
-
-.icon-bitcoin:before {
- content: "\e60f";
-}
-
-.icon-usd:before {
- content: "\e616";
-}
-
.icon-erase:before {
content: "\e628";
}
-
.icon-receive2:before {
content: "\e62f";
}
-
.icon-arrow-left:before {
content: "\e600";
}
-
.icon-arrow-down:before {
content: "\e601";
}
-
.icon-arrow-up:before {
content: "\e602";
}
-
.icon-arrow-right:before {
content: "\e603";
}
-
.icon-arrow-left2:before {
content: "\e604";
}
-
.icon-arrow-down2:before {
content: "\e605";
}
-
.icon-arrow-up2:before {
content: "\e606";
}
-
.icon-arrow-right2:before {
content: "\e607";
}
-
.icon-arrow-left3:before {
content: "\e608";
}
-
.icon-arrow-down3:before {
content: "\e609";
}
-
.icon-arrow-up3:before {
content: "\e60a";
}
-
.icon-arrow-right3:before {
content: "\e60b";
}
-
.icon-arrow-left4:before {
content: "\e60c";
}
-
.icon-arrow-down4:before {
content: "\e60d";
}
-
.icon-arrow-up4:before {
content: "\e60e";
}
+.icon-bank:before {
+ content: "\e630";
+}
+.icon-minus-circle:before {
+ content: "\e62c";
+}
+.icon-plus-circle:before {
+ content: "\e62d";
+}
+.icon-close-circle:before {
+ content: "\e62e";
+}
+.icon-checkmark-circle:before {
+ content: "\e62b";
+}
+.icon-circle:before {
+ content: "\e629";
+}
+.icon-circle-active:before {
+ content: "\e627";
+}
+.icon-circle-active:before {
+ content: "\e627";
+}
+.icon-scan:before {
+ content: "\e62a";
+}
+.icon-trash:before {
+ content: "\e626";
+}
+.icon-wallet:before {
+ content: "\e622";
+}
+.icon-history:before {
+ content: "\e623";
+}
+.icon-reference:before {
+ content: "\e621";
+}
+.icon-bell:before {
+ content: "\e61c";
+}
+.icon-receive:before {
+ content: "\e625";
+}
+.icon-wrench:before {
+ content: "\e61d";
+}
+.icon-download:before {
+ content: "\e61e";
+}
+.icon-upload:before {
+ content: "\e61f";
+}
+.icon-power:before {
+ content: "\e620";
+}
+.icon-forward:before {
+ content: "\e624";
+}
+.icon-compose:before {
+ content: "\e610";
+}
+.icon-contact:before {
+ content: "\e611";
+}
+.icon-email:before {
+ content: "\e612";
+}
+.icon-gear:before {
+ content: "\e613";
+}
+.icon-home:before {
+ content: "\e614";
+}
+.icon-locked:before {
+ content: "\e615";
+}
+.icon-paperplane:before {
+ content: "\e617";
+}
+.icon-people:before {
+ content: "\e618";
+}
+.icon-person:before {
+ content: "\e619";
+}
+.icon-pricetag:before {
+ content: "\e61a";
+}
+.icon-pricetags:before {
+ content: "\e61b";
+}
+.icon-bitcoin:before {
+ content: "\e60f";
+}
+.icon-usd:before {
+ content: "\e616";
+}
diff --git a/src/css/main.css b/src/css/main.css
index a140d1787..f71619032 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -410,7 +410,6 @@ ul.manage li {
.line-t {
border-top: 1px solid #E9E9EC;
- padding-top: 0.5rem;
}
.line-b {
@@ -1098,7 +1097,16 @@ input.ng-invalid-match, input.ng-invalid-match:focus {
}
.preferences-icon {
- padding: 0.6rem 0.8rem !important;
+ width: auto;
+ height: 40px;
+}
+
+@media only screen and (max-width: 40em) {
+ .preferences-icon {
+ width: 40px;
+ height: 40px;
+ padding: 0.55rem !important;
+ }
}
.alertModal {
diff --git a/src/js/controllers/buyGlidera.js b/src/js/controllers/buyGlidera.js
new file mode 100644
index 000000000..eac8576a7
--- /dev/null
+++ b/src/js/controllers/buyGlidera.js
@@ -0,0 +1,80 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('buyGlideraController',
+ function($scope, $timeout, profileService, addressService, glideraService, gettext, gettextCatalog, bwsError) {
+
+ this.addr = {};
+ this.show2faCodeInput = null;
+ this.error = null;
+ this.success = null;
+ this.loading = null;
+
+ this.getBuyPrice = function(token, price) {
+ var self = this;
+ if (!price || (price && !price.qty && !price.fiat)) {
+ this.buyPrice = null;
+ return;
+ }
+ glideraService.buyPrice(token, price, function(err, buyPrice) {
+ if (err) {
+ self.error = gettext('Could not get exchange information. Please, try again.');
+ }
+ else {
+ self.buyPrice = buyPrice;
+ }
+ });
+ };
+
+ this.get2faCode = function(token) {
+ var self = this;
+ this.loading = gettext('Sending 2FA code...');
+ $timeout(function() {
+ glideraService.get2faCode(token, function(err, sent) {
+ self.loading = null;
+ if (err) {
+ self.error = gettext('Could not send confirmation code to your phone');
+ }
+ else {
+ self.error = null;
+ self.show2faCodeInput = sent;
+ }
+ });
+ }, 100);
+ };
+
+ this.sendRequest = function(token, permissions, twoFaCode) {
+ var fc = profileService.focusedClient;
+ if (!fc) return;
+ var self = this;
+ self.error = null;
+ addressService.getAddress(fc.credentials.walletId, null, function(err, addr) {
+ if (!addr) {
+ self.error = bwsError.msg(err);
+ $scope.$apply();
+ }
+ else {
+ self.loading = gettext('Buying bitcoin...');
+ var data = {
+ destinationAddress: addr,
+ qty: self.buyPrice.qty,
+ priceUuid: self.buyPrice.priceUuid,
+ useCurrentPrice: false,
+ ip: null
+ };
+ $timeout(function() {
+ glideraService.buy(token, twoFaCode, data, function(err, data) {
+ self.loading = null;
+ if (err) {
+ self.error = err;
+ }
+ else {
+ self.success = data;
+ $scope.$emit('Local/GlideraTx');
+ }
+ });
+ }, 100);
+ }
+ });
+ };
+
+ });
diff --git a/src/js/controllers/glidera.js b/src/js/controllers/glidera.js
new file mode 100644
index 000000000..4d54c61cb
--- /dev/null
+++ b/src/js/controllers/glidera.js
@@ -0,0 +1,76 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('glideraController',
+ function($scope, $timeout, $modal, profileService, configService, storageService, glideraService, isChromeApp) {
+
+ var config = configService.getSync().wallet.settings;
+
+ this.getAuthenticateUrl = function() {
+ return glideraService.getOauthCodeUrl();
+ };
+
+ this.submitOauthCode = function(code) {
+ var fc = profileService.focusedClient;
+ var self = this;
+ this.loading = true;
+ this.error = null;
+ $timeout(function() {
+ glideraService.getToken(code, function(err, data) {
+ self.loading = null;
+ if (err) {
+ self.error = err;
+ $timeout(function() {
+ $scope.$apply();
+ }, 100);
+ }
+ else if (data && data.access_token) {
+ storageService.setGlideraToken(fc.credentials.network, data.access_token, function() {
+ $scope.$emit('Local/GlideraTokenUpdated', data.access_token);
+ $timeout(function() {
+ $scope.$apply();
+ }, 100);
+ });
+ }
+ });
+ }, 100);
+ };
+
+ // DISABLE ANIMATION ON CHROMEAPP
+ if (isChromeApp) {
+ var animatedSlideRight = 'full';
+ }
+ else {
+ var animatedSlideRight = 'full animated slideInRight';
+ }
+
+ this.openTxModal = function(token, tx) {
+ var self = this;
+ var fc = profileService.focusedClient;
+ var ModalInstanceCtrl = function($scope, $modalInstance) {
+ $scope.tx = tx;
+ $scope.settings = config;
+ $scope.color = fc.backgroundColor;
+
+ glideraService.getTransaction(token, tx.transactionUuid, function(error, tx) {
+ $scope.tx = tx;
+ });
+
+ $scope.cancel = function() {
+ $modalInstance.dismiss('cancel');
+ };
+
+ };
+
+ var modalInstance = $modal.open({
+ templateUrl: 'views/modals/glidera-tx-details.html',
+ windowClass: animatedSlideRight,
+ controller: ModalInstanceCtrl,
+ });
+
+ modalInstance.result.finally(function() {
+ var m = angular.element(document.getElementsByClassName('reveal-modal'));
+ m.addClass('slideOutRight');
+ });
+ };
+
+ });
diff --git a/src/js/controllers/glideraUri.js b/src/js/controllers/glideraUri.js
new file mode 100644
index 000000000..56e031d03
--- /dev/null
+++ b/src/js/controllers/glideraUri.js
@@ -0,0 +1,37 @@
+'use strict';
+angular.module('copayApp.controllers').controller('glideraUriController',
+ function($scope, $stateParams, $timeout, profileService, glideraService, storageService, go) {
+
+ this.submitOauthCode = function(code) {
+ var fc = profileService.focusedClient;
+ var self = this;
+ this.loading = true;
+ this.error = null;
+ $timeout(function() {
+ glideraService.getToken(code, function(err, data) {
+ self.loading = null;
+ if (err) {
+ self.error = err;
+ $timeout(function() {
+ $scope.$apply();
+ }, 100);
+ }
+ else if (data && data.access_token) {
+ storageService.setGlideraToken(fc.credentials.network, data.access_token, function() {
+ $scope.$emit('Local/GlideraTokenUpdated', data.access_token);
+ $timeout(function() {
+ go.path('glidera');
+ $scope.$apply();
+ }, 100);
+ });
+ }
+ });
+ }, 100);
+ };
+
+ this.checkCode = function() {
+ this.code = $stateParams.code;
+ this.submitOauthCode(this.code);
+ };
+
+ });
diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js
index 55c0fda22..a3de513ad 100644
--- a/src/js/controllers/index.js
+++ b/src/js/controllers/index.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettextCatalog, gettext, amMoment, nodeWebkit, addonManager, feeService, isChromeApp, bwsError, utilService, $state) {
+angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettextCatalog, gettext, amMoment, nodeWebkit, addonManager, feeService, isChromeApp, bwsError, utilService, $state, glideraService) {
var self = this;
self.isCordova = isCordova;
self.onGoingProcess = {};
@@ -113,6 +113,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.pendingTxProposalsCountForUs = null;
self.setSpendUnconfirmed();
+ self.glideraToken = null;
+ self.glideraError = null;
+ self.glideraPermissions = null;
+ self.glideraEmail = null;
+ self.glideraPersonalInfo = null;
+ self.glideraTxs = null;
+
$timeout(function() {
self.hasProfile = true;
self.noFocusedWallet = false;
@@ -134,6 +141,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.copayers = [];
self.updateColor();
self.updateAlias();
+ self.initGlidera();
storageService.getBackupFlag(self.walletId, function(err, val) {
self.needsBackup = self.network == 'testnet' ? false : !val;
@@ -145,18 +153,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.setTab = function(tab, reset, tries) {
tries = tries || 0;
- // check if the whole menu item passed
- if (typeof tab == 'object') {
- if (tab.open) {
- if (tab.link) {
- self.tab = tab.link;
- }
- tab.open();
- return;
- } else {
- return self.setTab(tab.link);
- }
- }
if (self.tab === tab && !reset)
return;
@@ -166,30 +162,27 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}, 300);
}
- if (!self.tab || !$state.is('walletHome'))
+ if (!self.tab)
self.tab = 'walletHome';
- go.path('walletHome', function() {
-
- if (document.getElementById(self.tab)) {
- document.getElementById(self.tab).className = 'tab-out tab-view ' + self.tab;
- var old = document.getElementById('menu-' + self.tab);
- if (old) {
- old.className = '';
- }
+ if (document.getElementById(self.tab)) {
+ document.getElementById(self.tab).className = 'tab-out tab-view ' + self.tab;
+ var old = document.getElementById('menu-' + self.tab);
+ if (old) {
+ old.className = '';
}
+ }
- if (document.getElementById(tab)) {
- document.getElementById(tab).className = 'tab-in tab-view ' + tab;
- var newe = document.getElementById('menu-' + tab);
- if (newe) {
- newe.className = 'active';
- }
+ if (document.getElementById(tab)) {
+ document.getElementById(tab).className = 'tab-in tab-view ' + tab;
+ var newe = document.getElementById('menu-' + tab);
+ if (newe) {
+ newe.className = 'active';
}
+ }
- self.tab = tab;
- $rootScope.$emit('Local/TabChanged', tab);
- });
+ self.tab = tab;
+ $rootScope.$emit('Local/TabChanged', tab);
};
@@ -400,6 +393,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.setOngoingProcess('updatingPendingTxps', true);
$log.debug('Updating PendingTxps');
fc.getTxProposals({}, function(err, txps) {
+console.log('[index.js:395]',txps); //TODO
self.setOngoingProcess('updatingPendingTxps', false);
if (err) {
self.handleError(err);
@@ -843,6 +837,79 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}), 'name');
};
+ self.initGlidera = function(accessToken) {
+ if (self.isShared) return;
+ self.glideraStatus = null;
+
+ glideraService.setCredentials(self.network);
+
+ var getToken = function(cb) {
+ if (accessToken) {
+ cb(null, accessToken);
+ } else {
+ storageService.getGlideraToken(self.network, cb);
+ }
+ };
+
+ getToken(function(err, accessToken) {
+ if (err || !accessToken) return;
+ else {
+ self.glideraLoading = gettext('Connecting to Glidera...');
+ glideraService.getAccessTokenPermissions(accessToken, function(err, p) {
+ self.glideraLoading = null;
+ if (err) {
+ self.glideraError = err;
+ }
+ else {
+ self.glideraToken = accessToken;
+ self.glideraPermissions = p;
+ self.updateGlidera({ fullUpdate: true});
+ }
+ });
+ }
+ });
+ };
+
+ self.updateGlidera = function(opts) {
+ if (!self.glideraToken || !self.glideraPermissions) return;
+ var accessToken = self.glideraToken;
+ var permissions = self.glideraPermissions;
+
+ opts = opts || {};
+
+ glideraService.getStatus(accessToken, function(err, data) {
+ self.glideraStatus = data;
+ });
+
+ glideraService.getLimits(accessToken, function(err, limits) {
+ self.glideraLimits = limits;
+ });
+
+ if (permissions.transaction_history) {
+ self.glideraLoadingHistory = gettext('Getting Glidera transactions...');
+ glideraService.getTransactions(accessToken, function(err, data) {
+ self.glideraLoadingHistory = null;
+ self.glideraTxs = data;
+ });
+ }
+
+ if (permissions.view_email_address && opts.fullUpdate) {
+ self.glideraLoadingEmail = gettext('Getting Glidera Email...');
+ glideraService.getEmail(accessToken, function(err, data) {
+ self.glideraLoadingEmail = null;
+ self.glideraEmail = data.email;
+ });
+ }
+ if (permissions.personal_info && opts.fullUpdate) {
+ self.glideraLoadingPersonalInfo = gettext('Getting Glidera Personal Information...');
+ glideraService.getPersonalInfo(accessToken, function(err, data) {
+ self.glideraLoadingPersonalInfo = null;
+ self.glideraPersonalInfo = data;
+ });
+ }
+
+ };
+
// UX event handlers
$rootScope.$on('Local/ColorUpdated', function(event) {
self.updateColor();
@@ -892,6 +959,18 @@ angular.module('copayApp.controllers').controller('indexController', function($r
});
});
+ $rootScope.$on('Local/GlideraTokenUpdated', function(event, accessToken) {
+ self.initGlidera(accessToken);
+ });
+
+ $rootScope.$on('Local/GlideraTx', function(event, accessToken, permissions) {
+ self.updateGlidera();
+ });
+
+ $rootScope.$on('Local/GlideraError', function(event) {
+ self.debouncedUpdate();
+ });
+
$rootScope.$on('Local/UnitSettingUpdated', function(event) {
self.updateAll();
self.updateTxHistory();
@@ -989,8 +1068,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
});
});
- lodash.each(['NewTxProposal', 'TxProposalFinallyRejected', 'TxProposalRemoved',
- 'Local/NewTxProposal', 'Local/TxProposalAction', 'ScanFinished'
+ lodash.each(['NewTxProposal', 'TxProposalFinallyRejected', 'TxProposalRemoved', 'NewOutgoingTxByThirdParty',
+ 'Local/NewTxProposal', 'Local/TxProposalAction', 'ScanFinished', 'Local/GlideraTx'
], function(eventName) {
$rootScope.$on(eventName, function(event, untilItChanges) {
self.updateAll({
diff --git a/src/js/controllers/preferencesGlidera.js b/src/js/controllers/preferencesGlidera.js
new file mode 100644
index 000000000..43d270f31
--- /dev/null
+++ b/src/js/controllers/preferencesGlidera.js
@@ -0,0 +1,61 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('preferencesGlideraController',
+ function($scope, $modal, $timeout, profileService, applicationService, glideraService, storageService) {
+
+ this.getEmail = function(token) {
+ var self = this;
+ glideraService.getEmail(token, function(error, data) {
+ self.email = data;
+ });
+ };
+
+ this.getPersonalInfo = function(token) {
+ var self = this;
+ glideraService.getPersonalInfo(token, function(error, info) {
+ self.personalInfo = info;
+ });
+ };
+
+ this.getStatus = function(token) {
+ var self = this;
+ glideraService.getStatus(token, function(error, data) {
+ self.status = data;
+ });
+ };
+
+ this.getLimits = function(token) {
+ var self = this;
+ glideraService.getLimits(token, function(error, limits) {
+ self.limits = limits;
+ });
+ };
+
+ this.revokeToken = function() {
+ var fc = profileService.focusedClient;
+ var ModalInstanceCtrl = function($scope, $modalInstance) {
+ $scope.ok = function() {
+ $modalInstance.close(true);
+ };
+ $scope.cancel = function() {
+ $modalInstance.dismiss();
+ };
+ };
+
+ var modalInstance = $modal.open({
+ templateUrl: 'views/modals/glidera-confirmation.html',
+ windowClass: 'full',
+ controller: ModalInstanceCtrl
+ });
+ modalInstance.result.then(function(ok) {
+ if (ok) {
+ storageService.removeGlideraToken(fc.credentials.network, function() {
+ $timeout(function() {
+ applicationService.restart();
+ }, 100);
+ });
+ }
+ });
+ };
+
+ });
diff --git a/src/js/controllers/sellGlidera.js b/src/js/controllers/sellGlidera.js
new file mode 100644
index 000000000..998c57b32
--- /dev/null
+++ b/src/js/controllers/sellGlidera.js
@@ -0,0 +1,158 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('sellGlideraController',
+ function($scope, $timeout, $log, gettext, gettextCatalog, configService, profileService, addressService, feeService, glideraService, bwsError) {
+
+ var config = configService.getSync();
+ this.data = {};
+ this.show2faCodeInput = null;
+ this.success = null;
+ this.error = null;
+ this.loading = null;
+ this.currentSpendUnconfirmed = config.wallet.spendUnconfirmed;
+ this.currentFeeLevel = config.wallet.settings.feeLevel || 'normal';
+
+ this.getSellPrice = function(token, price) {
+ var self = this;
+ if (!price || (price && !price.qty && !price.fiat)) {
+ this.error = null;
+ this.sellPrice = null;
+ return;
+ }
+ glideraService.sellPrice(token, price, function(err, sellPrice) {
+ if (err) {
+ self.error = gettext('Could not get exchange information. Please, try again.');
+ }
+ else {
+ self.error = null;
+ self.sellPrice = sellPrice;
+ }
+ });
+ };
+
+ this.get2faCode = function(token) {
+ var self = this;
+ this.loading = gettext('Sending 2FA code...');
+ $timeout(function() {
+ glideraService.get2faCode(token, function(err, sent) {
+ self.loading = null;
+ if (err) {
+ self.error = gettext('Could not send confirmation code to your phone');
+ }
+ else {
+ self.show2faCodeInput = sent;
+ }
+ });
+ }, 100);
+ };
+
+ this.createTx = function(token, permissions, twoFaCode) {
+ var self = this;
+ var fc = profileService.focusedClient;
+ self.error = null;
+
+ this.loading = gettext('Selling Bitcoin...');
+ $timeout(function() {
+ addressService.getAddress(fc.credentials.walletId, null, function(err, refundAddress) {
+ if (!refundAddress) {
+ self.loading = null;
+ self.error = bwsError.msg(err);
+ return;
+ }
+ glideraService.getSellAddress(token, function(error, sellAddress) {
+ if (!sellAddress) {
+ self.loading = null;
+ self.error = gettext('Could not get the destination bitcoin address');
+ return;
+ }
+ var amount = parseInt((self.sellPrice.qty * 100000000).toFixed(0));
+
+ feeService.getCurrentFeeValue(self.currentFeeLevel, function(err, feePerKb) {
+ if (err) $log.debug(err);
+ fc.sendTxProposal({
+ toAddress: sellAddress,
+ amount: amount,
+ message: 'Glidera transaction',
+ customData: {'glideraToken': token},
+ payProUrl: null,
+ feePerKb: feePerKb,
+ excludeUnconfirmedUtxos: self.currentSpendUnconfirmed ? false : true
+ }, function(err, txp) {
+ if (err) {
+ profileService.lockFC();
+ $log.error(err);
+ $timeout(function() {
+ self.loading = null;
+ self.error = bwsError.msg(err, gettextCatalog.getString('Error'));
+ }, 1);
+ return;
+ }
+
+ if (!fc.canSign()) {
+ self.loading = null;
+ $log.info('No signing proposal: No private key');
+ return;
+ }
+
+ _signTx(txp, function(err, txp, rawTx) {
+ profileService.lockFC();
+ if (err) {
+ self.loading = null;
+ self.error = err;
+ $scope.$apply();
+ }
+ else {
+ var data = {
+ refundAddress: refundAddress,
+ signedTransaction: rawTx,
+ priceUuid: self.sellPrice.priceUuid,
+ useCurrentPrice: self.sellPrice.priceUuid ? false : true,
+ ip: null
+ };
+ glideraService.sell(token, twoFaCode, data, function(err, data) {
+ self.loading = null;
+ if (err) {
+ self.error = err;
+ fc.removeTxProposal(txp, function(err, txpb) {
+ $timeout(function() {
+ $scope.$emit('Local/GlideraError');
+ }, 100);
+ });
+ }
+ else {
+ self.success = data;
+ $scope.$emit('Local/GlideraTx');
+ }
+ });
+ }
+ });
+ });
+ });
+ });
+ });
+
+ }, 100);
+
+ };
+
+ var _signTx = function(txp, cb) {
+ var self = this;
+ var fc = profileService.focusedClient;
+ fc.signTxProposal(txp, function(err, signedTx) {
+ profileService.lockFC();
+ if (err) {
+ err = bwsError.msg(err, gettextCatalog.getString('Could not accept payment'));
+ return cb(err);
+ }
+ else {
+ if (signedTx.status == 'accepted') {
+ return cb(null, txp, signedTx.raw);
+
+ } else {
+ return cb(gettext('The transaction could not be signed'));
+ }
+ }
+ });
+ };
+
+ });
diff --git a/src/js/controllers/walletHome.js b/src/js/controllers/walletHome.js
index a307c188a..cc868c659 100644
--- a/src/js/controllers/walletHome.js
+++ b/src/js/controllers/walletHome.js
@@ -30,8 +30,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
if (isChromeApp) {
var animatedSlideUp = 'full';
var animatedSlideRight = 'full';
- }
- else {
+ } else {
var animatedSlideUp = 'full animated slideInUp';
var animatedSlideRight = 'full animated slideInRight';
}
@@ -174,18 +173,30 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
});
};
- this.openTxpModal = function(tx, copayers) {
+ var GLIDERA_LOCK_TIME = 6 * 60 * 60 ;
+ // isGlidera flag is a security mesure so glidera status is not
+ // only determined by the tx.message
+ this.openTxpModal = function(tx, copayers, isGlidera) {
var fc = profileService.focusedClient;
var refreshUntilItChanges = false;
var currentSpendUnconfirmed = $scope.currentSpendUnconfirmed;
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.error = null;
- $scope.tx = tx;
$scope.copayers = copayers
$scope.copayerId = fc.credentials.copayerId;
$scope.canSign = fc.canSign();
$scope.loading = null;
$scope.color = fc.backgroundColor;
+
+ // ToDo: use tx.customData instead of tx.message
+ if (tx.message === 'Glidera transaction' && isGlidera) {
+ tx.isGlidera = true;
+ if (tx.canBeRemoved) {
+ tx.canBeRemoved = (Date.now()/1000 - (tx.ts || tx.createdOn)) > GLIDERA_LOCK_TIME;
+ }
+ }
+ $scope.tx = tx;
+
refreshUntilItChanges = false;
$scope.currentSpendUnconfirmed = currentSpendUnconfirmed;
diff --git a/src/js/init.js b/src/js/init.js
index 81673661e..069440edc 100644
--- a/src/js/init.js
+++ b/src/js/init.js
@@ -9,8 +9,14 @@ angular.element(document).ready(function() {
var handleBitcoinURI = function(url) {
if (!url) return;
+ if (url.indexOf('glidera') != -1) {
+ url = '#/uri-glidera' + url.replace('bitcoin://glidera', '');
+ }
+ else {
+ url = '#/uri-payment/' + url;
+ }
setTimeout(function() {
- window.location = '#/uri-payment/' + url;
+ window.location = url;
}, 1000);
};
diff --git a/src/js/routes.js b/src/js/routes.js
index 3a36c10eb..28e8e0937 100644
--- a/src/js/routes.js
+++ b/src/js/routes.js
@@ -288,6 +288,56 @@ angular
},
}
})
+ .state('uriglidera', {
+ url: '/uri-glidera?code',
+ needProfile: true,
+ views: {
+ 'main': {
+ templateUrl: 'views/glideraUri.html'
+ },
+ }
+ })
+ .state('glidera', {
+ url: '/glidera',
+ walletShouldBeComplete: true,
+ needProfile: true,
+ views: {
+ 'main': {
+ templateUrl: 'views/glidera.html'
+ },
+ }
+ })
+ .state('buyGlidera', {
+ url: '/buy',
+ walletShouldBeComplete: true,
+ needProfile: true,
+ views: {
+ 'main': {
+ templateUrl: 'views/buyGlidera.html'
+ },
+ }
+ })
+ .state('sellGlidera', {
+ url: '/sell',
+ walletShouldBeComplete: true,
+ needProfile: true,
+ views: {
+ 'main': {
+ templateUrl: 'views/sellGlidera.html'
+ },
+ }
+ })
+
+ .state('preferencesGlidera', {
+ url: '/preferencesGlidera',
+ walletShouldBeComplete: true,
+ needProfile: true,
+ views: {
+ 'main': {
+ templateUrl: 'views/preferencesGlidera.html'
+ },
+ }
+ })
.state('preferencesAdvanced', {
url: '/preferencesAdvanced',
@@ -486,11 +536,16 @@ angular
copayers: -1,
cordova: -1,
payment: -1,
+ uriglidera: -1,
preferences: 11,
+ glidera: 11,
preferencesColor: 12,
backup: 12,
preferencesAdvanced: 12,
+ buyGlidera: 12,
+ sellGlidera: 12,
+ preferencesGlidera: 12,
delete: 13,
preferencesLanguage: 12,
preferencesUnit: 12,
diff --git a/src/js/services/glideraService.js b/src/js/services/glideraService.js
new file mode 100644
index 000000000..b57a1c0ab
--- /dev/null
+++ b/src/js/services/glideraService.js
@@ -0,0 +1,253 @@
+'use strict';
+
+angular.module('copayApp.services').factory('glideraService', function($http, $log, isCordova) {
+ var root = {};
+ var credentials = {};
+
+ root.setCredentials = function(network) {
+ if (network == 'testnet') {
+ credentials.HOST = 'https://sandbox.glidera.io';
+ if (isCordova) {
+ credentials.REDIRECT_URI = 'bitcoin://glidera';
+ credentials.CLIENT_ID = 'dfc56e4336e32bb8ba46dde34f3d7d6d';
+ credentials.CLIENT_SECRET = '5eb679058f6c7eb81123162323d4fba5';
+ }
+ else {
+ credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
+ credentials.CLIENT_ID = '9915b6ffa6dc3baffb87135ed3873d49';
+ credentials.CLIENT_SECRET = 'd74eda05b9c6a228fd5c85cfbd0eb7eb';
+ }
+ }
+ else {
+ credentials.HOST = 'https://glidera.io';
+ credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
+ credentials.CLIENT_ID = '';
+ credentials.CLIENT_SECRET = '';
+ };
+ };
+
+ root.getOauthCodeUrl = function() {
+ return credentials.HOST
+ + '/oauth2/auth?response_type=code&client_id='
+ + credentials.CLIENT_ID
+ + '&redirect_uri='
+ + credentials.REDIRECT_URI;
+ };
+
+ root.getToken = function(code, cb) {
+ var req = {
+ method: 'POST',
+ url: credentials.HOST + '/api/v1/oauth/token',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ data: {
+ grant_type : 'authorization_code',
+ code: code,
+ client_id : credentials.CLIENT_ID,
+ client_secret: credentials.CLIENT_SECRET,
+ redirect_uri: credentials.REDIRECT_URI
+ }
+ };
+
+ $http(req).then(function(data) {
+ $log.info('Glidera Authorization Access Token: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Authorization Access Token: ERROR ' + data.statusText);
+ return cb('Glidera Authorization Access Token: ERROR ' + data.statusText);
+ });
+ };
+
+ var _get = function(endpoint, token) {
+ return {
+ method: 'GET',
+ url: credentials.HOST + '/api/v1' + endpoint,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Authorization': 'Bearer ' + token
+ }
+ };
+ };
+
+ root.getAccessTokenPermissions = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/oauth/token', token)).then(function(data) {
+ $log.info('Glidera Access Token Permissions: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Access Token Permissions: ERROR ' + data.statusText);
+ return cb('Glidera Access Token Permissions: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getEmail = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/user/email', token)).then(function(data) {
+ $log.info('Glidera Get Email: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Get Email: ERROR ' + data.statusText);
+ return cb('Glidera Get Email: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getPersonalInfo = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/user/personalinfo', token)).then(function(data) {
+ $log.info('Glidera Get Personal Info: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Get Personal Info: ERROR ' + data.statusText);
+ return cb('Glidera Get Personal Info: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getStatus = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/user/status', token)).then(function(data) {
+ $log.info('Glidera User Status: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera User Status: ERROR ' + data.statusText);
+ return cb('Glidera User Status: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getLimits = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/user/limits', token)).then(function(data) {
+ $log.info('Glidera Transaction Limits: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Transaction Limits: ERROR ' + data.statusText);
+ return cb('Glidera Transaction Limits: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getTransactions = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/transaction', token)).then(function(data) {
+ $log.info('Glidera Transactions: SUCCESS');
+ return cb(null, data.data.transactions);
+ }, function(data) {
+ $log.error('Glidera Transactions: ERROR ' + data.statusText);
+ return cb('Glidera Transactions: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getTransaction = function(token, txid, cb) {
+ if (!token) return cb('Invalid Token');
+ if (!txid) return cb('TxId required');
+ $http(_get('/transaction/' + txid, token)).then(function(data) {
+ $log.info('Glidera Transaction: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Transaction: ERROR ' + data.statusText);
+ return cb('Glidera Transaction: ERROR ' + data.statusText);
+ });
+ };
+
+ root.getSellAddress = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/user/create_sell_address', token)).then(function(data) {
+ $log.info('Glidera Create Sell Address: SUCCESS');
+ return cb(null, data.data.sellAddress);
+ }, function(data) {
+ $log.error('Glidera Create Sell Address: ERROR ' + data.statusText);
+ return cb('Glidera Create Sell Address: ERROR ' + data.statusText);
+ });
+ };
+
+ root.get2faCode = function(token, cb) {
+ if (!token) return cb('Invalid Token');
+ $http(_get('/authentication/get2faCode', token)).then(function(data) {
+ $log.info('Glidera Sent 2FA code by SMS: SUCCESS');
+ return cb(null, data.status == 200 ? true : false);
+ }, function(data) {
+ $log.error('Glidera Sent 2FA code by SMS: ERROR ' + data.statusText);
+ return cb('Glidera Sent 2FA code by SMS: ERROR ' + data.statusText);
+ });
+ };
+
+ var _post = function(endpoint, token, twoFaCode, data) {
+ return {
+ method: 'POST',
+ url: credentials.HOST + '/api/v1' + endpoint,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Authorization': 'Bearer ' + token,
+ '2FA_CODE': twoFaCode
+ },
+ data: data
+ };
+ };
+
+ root.sellPrice = function(token, price, cb) {
+ var data = {
+ qty: price.qty,
+ fiat: price.fiat
+ };
+ $http(_post('/prices/sell', token, null, data)).then(function(data) {
+ $log.info('Glidera Sell Price: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Sell Price: ERROR ' + data.statusText);
+ return cb('Glidera Sell Price: ERROR ' + data.statusText);
+ });
+ };
+
+ root.sell = function(token, twoFaCode, data, cb) {
+ var data = {
+ refundAddress: data.refundAddress,
+ signedTransaction: data.signedTransaction,
+ priceUuid: data.priceUuid,
+ useCurrentPrice: data.useCurrentPrice,
+ ip: data.ip
+ };
+ $http(_post('/sell', token, twoFaCode, data)).then(function(data) {
+ $log.info('Glidera Sell: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Sell Request: ERROR ' + data.statusText);
+ return cb('Glidera Sell Request: ERROR ' + data.statusText);
+ });
+ };
+
+ root.buyPrice = function(token, price, cb) {
+ var data = {
+ qty: price.qty,
+ fiat: price.fiat
+ };
+ $http(_post('/prices/buy', token, null, data)).then(function(data) {
+ $log.info('Glidera Buy Price: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Buy Price: ERROR ' + data.statusText);
+ return cb('Glidera Buy Price: ERROR ' + data.statusText);
+ });
+ };
+
+ root.buy = function(token, twoFaCode, data, cb) {
+ var data = {
+ destinationAddress: data.destinationAddress,
+ qty: data.qty,
+ priceUuid: data.priceUuid,
+ useCurrentPrice: data.useCurrentPrice,
+ ip: data.ip
+ };
+ $http(_post('/buy', token, twoFaCode, data)).then(function(data) {
+ $log.info('Glidera Buy: SUCCESS');
+ return cb(null, data.data);
+ }, function(data) {
+ $log.error('Glidera Buy Request: ERROR ' + data.statusText);
+ return cb('Glidera Buy Request: ERROR ' + data.statusText);
+ });
+ };
+
+ return root;
+
+});
diff --git a/src/js/services/go.js b/src/js/services/go.js
index 3e55e2939..782683e7f 100644
--- a/src/js/services/go.js
+++ b/src/js/services/go.js
@@ -29,12 +29,13 @@ angular.module('copayApp.services').factory('go', function($window, $rootScope,
}
};
- root.openExternalLink = function(url) {
+ root.openExternalLink = function(url, target) {
if (nodeWebkit.isDefined()) {
nodeWebkit.openExternalLink(url);
}
else {
- var ref = window.open(url, '_blank', 'location=no');
+ target = target || '_blank';
+ var ref = window.open(url, target, 'location=no');
}
};
@@ -89,8 +90,8 @@ angular.module('copayApp.services').factory('go', function($window, $rootScope,
root.path(path);
};
- $rootScope.openExternalLink = function(url) {
- root.openExternalLink(url);
+ $rootScope.openExternalLink = function(url, target) {
+ root.openExternalLink(url, target);
};
diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js
index 1916ec032..b7208ea7a 100644
--- a/src/js/services/storageService.js
+++ b/src/js/services/storageService.js
@@ -192,5 +192,17 @@ angular.module('copayApp.services')
storage.get('remotePrefStored', cb);
};
+ root.setGlideraToken = function(network, token, cb) {
+ storage.set('glideraToken-' + network, token, cb);
+ };
+
+ root.getGlideraToken = function(network, cb) {
+ storage.get('glideraToken-' + network, cb);
+ };
+
+ root.removeGlideraToken = function(network, cb) {
+ storage.remove('glideraToken-' + network, cb);
+ };
+
return root;
});