Merge pull request #5334 from cmgustavo/feat/coinbase-integration

Re-enable Coinbase integration
This commit is contained in:
Matias Alejo Garcia 2017-01-17 14:50:19 -03:00 committed by GitHub
commit 6bbb1fd442
30 changed files with 1632 additions and 1241 deletions

View file

@ -20,6 +20,9 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.isGlidera = data.stateParams.isGlidera;
$scope.glideraAccessToken = data.stateParams.glideraAccessToken;
// Go to...
$scope.nextStep = data.stateParams.nextStep;
$scope.cardId = data.stateParams.cardId;
$scope.showMenu = $ionicHistory.backView() && $ionicHistory.backView().stateName == 'tabs.send';
var isWallet = data.stateParams.isWallet || 'false';
@ -27,13 +30,13 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail;
$scope.showAlternativeAmount = !!$scope.cardId || !!$scope.isGiftCard || !!$scope.isGlidera;
$scope.showAlternativeAmount = !!$scope.cardId || !!$scope.isGiftCard || !!$scope.isGlidera || !!$scope.nextStep;
$scope.toColor = data.stateParams.toColor;
$scope.showSendMax = false;
$scope.customAmount = data.stateParams.customAmount;
if (!$scope.cardId && !$scope.isGiftCard && !$scope.isGlidera && !data.stateParams.toAddress) {
if (!$scope.cardId && !$scope.isGiftCard && !$scope.isGlidera && !$scope.nextStep && !data.stateParams.toAddress) {
$log.error('Bad params at amount')
throw ('bad params');
}
@ -72,7 +75,11 @@ angular.module('copayApp.controllers').controller('amountController', function($
var config = configService.getSync().wallet.settings;
$scope.unitName = config.unitName;
$scope.alternativeIsoCode = !!$scope.cardId || !!$scope.isGiftCard ? 'USD' : config.alternativeIsoCode;
if (data.stateParams.currency) {
$scope.alternativeIsoCode = data.stateParams.currency;
} else {
$scope.alternativeIsoCode = !!$scope.cardId || !!$scope.isGiftCard ? 'USD' : config.alternativeIsoCode;
}
$scope.specificAmount = $scope.specificAlternativeAmount = '';
$scope.isCordova = platformInfo.isCordova;
unitToSatoshi = config.unitToSatoshi;
@ -350,6 +357,11 @@ angular.module('copayApp.controllers').controller('amountController', function($
isGlidera: $scope.isGlidera,
glideraAccessToken: $scope.glideraAccessToken
});
} else if ($scope.nextStep) {
$state.transitionTo($scope.nextStep, {
amount: _amount,
currency: $scope.showAlternativeAmount ? $scope.alternativeIsoCode : ''
});
} else {
var amount = $scope.showAlternativeAmount ? fromFiat(_amount) : _amount;
if ($scope.customAmount) {

View file

@ -1,175 +1,210 @@
'use strict';
angular.module('copayApp.controllers').controller('buyCoinbaseController',
function($scope, $log, $ionicModal, $timeout, lodash, profileService, coinbaseService, addressService, ongoingProcess) {
var self = this;
angular.module('copayApp.controllers').controller('buyCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService) {
this.init = function(testnet) {
self.allWallets = profileService.getWallets(testnet ? 'testnet' : 'livenet');
var amount;
var currency;
var client = profileService.focusedClient;
if (client) {
$timeout(function() {
self.selectedWalletId = client.credentials.walletId;
self.selectedWalletName = client.credentials.walletName;
$scope.$apply();
}, 100);
var showErrorAndBack = function(err) {
$scope.sendStatus = '';
$log.error(err);
err = err.errors ? err.errors[0].message : err;
popupService.showAlert('Error', err, function() {
$ionicHistory.goBack();
});
};
var showError = function(err) {
$scope.sendStatus = '';
$log.error(err);
err = err.errors ? err.errors[0].message : err;
popupService.showAlert('Error', err);
};
var statusChangeHandler = function (processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( processName == 'buyingBitcoin' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
$scope.sendStatus = showName;
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
coinbaseService.setCredentials();
$scope.isFiat = data.stateParams.currency ? true : false;
[amount, currency, $scope.amountUnitStr] = coinbaseService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
};
var accessToken = res.accessToken;
this.getPaymentMethods = function(token) {
coinbaseService.getPaymentMethods(token, function(err, p) {
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) {
self.error = err;
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
self.paymentMethods = [];
lodash.each(p.data, function(pm) {
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
self.paymentMethods.push(pm);
$scope.paymentMethods.push(pm);
}
if (pm.allow_buy && pm.primary_buy) {
$scope.selectedPaymentMethod = pm;
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
}
});
}
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
});
};
});
});
this.getPrice = function(token) {
var currency = 'USD';
coinbaseService.buyPrice(token, currency, function(err, b) {
if (err) return;
self.buyPrice = b.data || null;
});
};
$scope.openWalletsModal = function(wallets) {
self.error = null;
$scope.type = 'BUY';
$scope.wallets = wallets;
$scope.noColor = true;
$scope.self = self;
$ionicModal.fromTemplateUrl('views/modals/wallets.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.walletsModal = modal;
$scope.walletsModal.show();
});
$scope.$on('walletSelected', function(ev, walletId) {
$timeout(function() {
var client = profileService.getClient(walletId);
self.selectedWalletId = walletId;
self.selectedWalletName = client.credentials.walletName;
$scope.$apply();
}, 100);
$scope.walletsModal.hide();
});
};
this.buyRequest = function(token, account) {
self.error = null;
var accountId = account.id;
var amount = $scope.amount ? $scope.amount : $scope.fiat;
var currency = $scope.amount ? 'BTC' : 'USD';
if (!amount) return;
$scope.buyRequest = function() {
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var dataSrc = {
amount: amount,
currency: currency,
payment_method: $scope.selectedPaymentMethod.id || null
payment_method: $scope.selectedPaymentMethodId.value,
quote: true
};
ongoingProcess.set('Sending request...', true);
coinbaseService.buyRequest(token, accountId, dataSrc, function(err, data) {
ongoingProcess.set('Sending request...', false);
coinbaseService.buyRequest(accessToken, accountId, dataSrc, function(err, data) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
self.error = err;
showErrorAndBack(err);
return;
}
self.buyInfo = data.data;
$scope.buyRequestInfo = data.data;
$timeout(function() {
$scope.$apply();
}, 100);
});
};
});
};
this.confirmBuy = function(token, account, buy) {
self.error = null;
var accountId = account.id;
var buyId = buy.id;
ongoingProcess.set('Buying Bitcoin...', true);
coinbaseService.buyCommit(token, accountId, buyId, function(err, b) {
ongoingProcess.set('Buying Bitcoin...', false);
$scope.buyConfirm = function() {
var message = 'Buy bitcoin for ' + amount + ' ' + currency;
var okText = 'Confirm';
var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return;
ongoingProcess.set('buyingBitcoin', true, statusChangeHandler);
coinbaseService.init(function(err, res) {
if (err) {
self.error = err;
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return;
} else {
var tx = b.data.transaction;
if (!tx) return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var dataSrc = {
amount: amount,
currency: currency,
payment_method: $scope.selectedPaymentMethodId.value,
commit: true
};
coinbaseService.buyRequest(accessToken, accountId, dataSrc, function(err, b) {
if (err) {
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
var tx = b.data ? b.data.transaction : null;
if (!tx) {
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError('Transaction not found');
return;
}
ongoingProcess.set('Fetching transaction...', true);
coinbaseService.getTransaction(token, accountId, tx.id, function(err, updatedTx) {
ongoingProcess.set('Fetching transaction...', false);
if (err) $log.debug(err);
addressService.getAddress(self.selectedWalletId, false, function(err, addr) {
$timeout(function() {
coinbaseService.getTransaction(accessToken, accountId, tx.id, function(err, updatedTx) {
if (err) {
self.error = {
errors: [{
message: 'Could not create address'
}]
};
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
updatedTx.data['toAddr'] = addr;
coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) {
if (err) $log.debug(err);
if (updatedTx.data.status == 'completed') {
self.sendToCopay(token, account, updatedTx.data);
} else {
self.success = updatedTx.data;
$timeout(function() {
$scope.$emit('Local/CoinbaseTx');
}, 1000);
walletService.getAddress($scope.wallet, false, function(err, walletAddr) {
if (err) {
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
updatedTx.data['toAddr'] = walletAddr;
updatedTx.data['status'] = 'pending'; // Forcing "pending" status to process later
$log.debug('Saving transaction to process later...');
coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) {
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
if (err) $log.debug(err);
});
});
});
});
}
}, 8000);
});
});
};
});
};
this.sendToCopay = function(token, account, tx) {
self.error = null;
var accountId = account.id;
$scope.showWalletSelector = function() {
$scope.walletSelectorTitle = 'Receive in';
$scope.showWallets = true;
};
ongoingProcess.set('Sending funds to Copay...', true);
var data = {
to: tx.toAddr,
amount: tx.amount.amount,
currency: tx.amount.currency,
description: 'Copay Wallet: ' + self.selectedWalletName
};
coinbaseService.sendTo(token, accountId, data, function(err, res) {
ongoingProcess.set('Sending funds to Copay...', false);
if (err) {
self.error = err;
} else {
self.receiveInfo = res.data;
if (!res.data.id) return;
coinbaseService.getTransaction(token, accountId, res.data.id, function(err, sendTx) {
coinbaseService.savePendingTransaction(tx, {
remove: true
}, function(err) {
coinbaseService.savePendingTransaction(sendTx.data, {}, function(err) {
$timeout(function() {
$scope.$emit('Local/CoinbaseTx');
}, 1000);
});
});
});
}
$scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet;
};
});
};
});
$scope.goBackHome = function() {
$scope.sendStatus = '';
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.buyandsell.coinbase');
});
};
});

View file

@ -1,78 +1,117 @@
'use strict';
angular.module('copayApp.controllers').controller('coinbaseController',
function($rootScope, $scope, $timeout, $ionicModal, profileService, configService, storageService, coinbaseService, lodash, platformInfo, ongoingProcess) {
angular.module('copayApp.controllers').controller('coinbaseController', function($scope, $timeout, $ionicModal, $log, coinbaseService, lodash, platformInfo, ongoingProcess, popupService, externalLinkService) {
var isNW = platformInfo.isNW;
var isNW = platformInfo.isNW;
var isCordova = platformInfo.isCordova;
if (platformInfo.isCordova && StatusBar.isVisible) {
StatusBar.backgroundColorByHexString("#4B6178");
}
this.openAuthenticateWindow = function() {
var oauthUrl = this.getAuthenticateUrl();
if (!isNW) {
$rootScope.openExternalLink(oauthUrl, '_system');
} else {
var self = this;
var gui = require('nw.gui');
var win = gui.Window.open(oauthUrl, {
focus: true,
position: 'center'
});
win.on('loaded', function() {
var title = win.title;
if (title.indexOf('Coinbase') == -1) {
$scope.code = title;
self.submitOauthCode(title);
win.close();
}
});
}
}
this.getAuthenticateUrl = function() {
return coinbaseService.getOauthCodeUrl();
};
this.submitOauthCode = function(code) {
var self = this;
var coinbaseTestnet = configService.getSync().coinbase.testnet;
var network = coinbaseTestnet ? 'testnet' : 'livenet';
ongoingProcess.set('connectingCoinbase', true);
this.error = null;
$timeout(function() {
coinbaseService.getToken(code, function(err, data) {
ongoingProcess.set('connectingCoinbase', false);
var init = function() {
$scope.currency = coinbaseService.getAvailableCurrency();
coinbaseService.getStoredToken(function(at) {
$scope.accessToken = at;
// Update Access Token if necessary
$scope.loading = true;
coinbaseService.init(function(err, data) {
$scope.loading = false;
if (err || lodash.isEmpty(data)) {
if (err) {
self.error = err;
$timeout(function() {
$scope.$apply();
}, 100);
} else if (data && data.access_token && data.refresh_token) {
storageService.setCoinbaseToken(network, data.access_token, function() {
storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() {
$scope.$emit('Local/CoinbaseUpdated', data.access_token);
$timeout(function() {
$scope.$apply();
}, 100);
});
});
popupService.showAlert('Error', err);
}
return;
}
// Show rates
coinbaseService.buyPrice(data.accessToken, $scope.currency, function(err, b) {
$scope.buyPrice = b.data || null;
});
coinbaseService.sellPrice(data.accessToken, $scope.currency, function(err, s) {
$scope.sellPrice = s.data || null;
});
}, 100);
};
this.openTxModal = function(tx) {
$scope.tx = tx;
$ionicModal.fromTemplateUrl('views/modals/coinbase-tx-details.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.coinbaseTxDetailsModal = modal;
$scope.coinbaseTxDetailsModal.show();
// Updating accessToken and accountId
$timeout(function() {
$scope.accessToken = data.accessToken;
$scope.accountId = data.accountId;
$scope.updateTransactions();
$scope.$apply();
}, 100);
});
};
});
};
$scope.updateTransactions = function() {
$log.debug('Getting transactions...');
$scope.pendingTransactions = { data: {} };
coinbaseService.getPendingTransactions($scope.pendingTransactions);
};
this.openAuthenticateWindow = function() {
var oauthUrl = this.getAuthenticateUrl();
if (!isNW) {
externalLinkService.open(oauthUrl);
} else {
var self = this;
var gui = require('nw.gui');
gui.Window.open(oauthUrl, {
focus: true,
position: 'center'
}, function(new_win) {
new_win.on('loaded', function() {
var title = new_win.window.document.title;
$timeout(function() {
if (title.indexOf('Coinbase') == -1) {
$scope.code = title;
self.submitOauthCode($scope.code);
new_win.close();
}
}, 100);
});
});
}
}
this.getAuthenticateUrl = function() {
$scope.showOauthForm = isCordova || isNW ? false : true;
return coinbaseService.getOauthCodeUrl();
};
this.submitOauthCode = function(code) {
var self = this;
ongoingProcess.set('connectingCoinbase', true);
$scope.error = null;
$timeout(function() {
coinbaseService.getToken(code, function(err, accessToken) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
popupService.showAlert('Error', err);
return;
}
$scope.accessToken = accessToken;
init();
});
}, 100);
};
this.openTxModal = function(tx) {
$scope.tx = tx;
$ionicModal.fromTemplateUrl('views/modals/coinbase-tx-details.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
$scope.modal.show();
});
};
var self = this;
$scope.$on("$ionicView.beforeEnter", function(event, data) {
coinbaseService.setCredentials();
if (data.stateParams && data.stateParams.code) {
self.submitOauthCode(data.stateParams.code);
} else {
init();
}
});
});

View file

@ -1,44 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('coinbaseUriController',
function($scope, $stateParams, $timeout, profileService, configService, coinbaseService, storageService, $state, ongoingProcess) {
this.submitOauthCode = function(code) {
var self = this;
var coinbaseTestnet = configService.getSync().coinbase.testnet;
var network = coinbaseTestnet ? 'testnet' : 'livenet';
ongoingProcess.set('connectingCoinbase', true);
this.error = null;
$timeout(function() {
coinbaseService.getToken(code, function(err, data) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
self.error = err;
$timeout(function() {
$scope.$apply();
}, 100);
} else if (data && data.access_token && data.refresh_token) {
storageService.setCoinbaseToken(network, data.access_token, function() {
storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() {
$scope.$emit('Local/CoinbaseUpdated', data.access_token);
$timeout(function() {
$state.go('coinbase');
$scope.$apply();
}, 100);
});
});
}
});
}, 100);
};
this.checkCode = function() {
if ($stateParams.url) {
var match = $stateParams.url.match(/code=(.+)&/);
if (match && match[1]) {
this.code = match[1];
return this.submitOauthCode(this.code);
}
}
$log.error('Bad state: ' + JSON.stringify($stateParams));
}
});

View file

@ -528,7 +528,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
});
return;
}
ongoingProcess.set('creatingTx', true, onSendStatusChange);
createTx(wallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false, onSendStatusChange);
@ -577,12 +577,14 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if (
(
processName === 'broadcastingTx' ||
((processName === 'signingTx') && $scope.wallet.m > 1) ||
processName === 'broadcastingTx' ||
((processName === 'signingTx') && $scope.wallet.m > 1) ||
(processName == 'sendingTx' && !$scope.wallet.canSign() && !$scope.wallet.isPrivKeyExternal())
) && !isOn) {
$scope.sendStatus = 'success';
$scope.$digest();
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
$scope.sendStatus = showName;
}
@ -831,7 +833,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
debounceCreate(count, dataSrc, onSendStatusChange);
}
}, onSendStatusChange);
}
};
var debounceCreate = lodash.throttle(function(count, dataSrc) {
debounceCreateGiftCard(count, dataSrc);

View file

@ -1,20 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('coinbaseConfirmationController', function($scope, $timeout, coinbaseService, applicationService) {
$scope.ok = function() {
coinbaseService.logout($scope.network, function() {
$timeout(function() {
applicationService.restart();
}, 1000);
});
$scope.cancel();
};
$scope.cancel = function() {
$scope.coinbaseConfirmationModal.hide();
};
});

View file

@ -1,18 +1,28 @@
'use strict';
angular.module('copayApp.controllers').controller('coinbaseTxDetailsController', function($scope, $rootScope, coinbaseService) {
angular.module('copayApp.controllers').controller('coinbaseTxDetailsController', function($scope, coinbaseService, popupService) {
$scope.remove = function() {
coinbaseService.savePendingTransaction($scope.tx, {
remove: true
}, function(err) {
$rootScope.$emit('Local/CoinbaseTx');
$scope.cancel();
coinbaseService.setCredentials();
$scope.updateRequired = false;
var message = 'Are you sure you want to remove this transaction?';
popupService.showConfirm(null, message, null, null, function(ok) {
if (!ok) {
return;
}
coinbaseService.savePendingTransaction($scope.tx, {
remove: true
}, function(err) {
$scope.updateRequired = true;
$scope.close();
});
});
};
$scope.cancel = function() {
$scope.coinbaseTxDetailsModal.hide();
$scope.close = function() {
$scope.modal.hide().then(function() {
if ($scope.updateRequired) $scope.updateTransactions();
});
};
});

View file

@ -1,18 +1,41 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesCoinbaseController',
function($scope, $timeout, $ionicModal, applicationService, coinbaseService) {
angular.module('copayApp.controllers').controller('preferencesCoinbaseController', function($scope, $timeout, $state, $ionicHistory, lodash, ongoingProcess, popupService, coinbaseService) {
this.revokeToken = function(testnet) {
$scope.network = testnet ? 'testnet' : 'livenet';
$scope.revokeToken = function() {
popupService.showConfirm('Coinbase', 'Are you sure you would like to log out of your Coinbase account?', null, null, function(res) {
if (res) {
coinbaseService.logout(function() {
$ionicHistory.clearHistory();
$timeout(function() {
$state.go('tabs.home');
}, 100);
});
}
});
};
$ionicModal.fromTemplateUrl('views/modals/coinbase-confirmation.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.coinbaseConfirmationModal = modal;
$scope.coinbaseConfirmationModal.show();
$scope.$on("$ionicView.enter", function(event, data){
coinbaseService.setCredentials();
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, data) {
if (err || lodash.isEmpty(data)) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
return;
}
var accessToken = data.accessToken;
var accountId = data.accountId;
coinbaseService.getAccount(accessToken, accountId, function(err, account) {
ongoingProcess.set('connectingCoinbase', false);
$scope.coinbaseAccount = account.data;
});
};
coinbaseService.getCurrentUser(accessToken, function(err, user) {
$scope.coinbaseUser = user.data;
});
});
});
});

View file

@ -1,187 +1,258 @@
'use strict';
angular.module('copayApp.controllers').controller('sellCoinbaseController',
function($rootScope, $scope, $log, $timeout, $ionicModal, lodash, profileService, coinbaseService, configService, walletService, fingerprintService, ongoingProcess, go) {
angular.module('copayApp.controllers').controller('sellCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, appConfigService, configService) {
var self = this;
var client;
var amount;
var currency;
$scope.priceSensitivity = [
{
value: 0.5,
name: '0.5%'
},
{
value: 1,
name: '1%'
},
{
value: 2,
name: '2%'
},
{
value: 5,
name: '5%'
},
{
value: 10,
name: '10%'
}
];
$scope.selectedPriceSensitivity = $scope.priceSensitivity[1];
var showErrorAndBack = function(err) {
$scope.sendStatus = '';
$log.error(err);
err = err.errors ? err.errors[0].message : err;
popupService.showAlert('Error', err, function() {
$ionicHistory.goBack();
});
};
this.init = function(testnet) {
self.allWallets = profileService.getWallets(testnet ? 'testnet' : 'livenet', 1);
var showError = function(err) {
$scope.sendStatus = '';
$log.error(err);
err = err.errors ? err.errors[0].message : err;
popupService.showAlert('Error', err);
};
client = profileService.focusedClient;
if (client && client.credentials.m == 1) {
$timeout(function() {
self.selectedWalletId = client.credentials.walletId;
self.selectedWalletName = client.credentials.walletName;
$scope.$apply();
}, 100);
}
};
var publishAndSign = function (wallet, txp, onSendStatusChange, cb) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
var err = 'No signing proposal: No private key';
$log.info(err);
return cb(err);
}
this.getPaymentMethods = function(token) {
coinbaseService.getPaymentMethods(token, function(err, p) {
if (err) {
self.error = err;
return;
}
self.paymentMethods = [];
lodash.each(p.data, function(pm) {
if (pm.allow_sell) {
self.paymentMethods.push(pm);
}
if (pm.allow_sell && pm.primary_sell) {
$scope.selectedPaymentMethod = pm;
}
});
});
};
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return cb(err);
return cb(null, txp);
}, onSendStatusChange);
};
this.getPrice = function(token) {
var currency = 'USD';
coinbaseService.sellPrice(token, currency, function(err, s) {
if (err) return;
self.sellPrice = s.data || null;
});
};
$scope.openWalletsModal = function(wallets) {
self.error = null;
$scope.type = 'SELL';
$scope.wallets = wallets;
$scope.noColor = true;
$scope.self = self;
$ionicModal.fromTemplateUrl('views/modals/wallets.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.walletsModal = modal;
$scope.walletsModal.show();
});
$scope.$on('walletSelected', function(ev, walletId) {
$timeout(function() {
client = profileService.getClient(walletId);
self.selectedWalletId = walletId;
self.selectedWalletName = client.credentials.walletName;
$scope.$apply();
}, 100);
$scope.walletsModal.hide();
});
};
this.depositFunds = function(token, account) {
self.error = null;
if ($scope.amount) {
this.createTx(token, account, $scope.amount)
} else if ($scope.fiat) {
var btcValue = ($scope.fiat / self.sellPrice.amount).toFixed(8);
this.createTx(token, account, btcValue);
}
};
this.sellRequest = function(token, account, ctx) {
self.error = null;
if (!ctx.amount) return;
var accountId = account.id;
var data = ctx.amount;
data['payment_method'] = $scope.selectedPaymentMethod.id || null;
ongoingProcess.set('Sending request...', true);
coinbaseService.sellRequest(token, accountId, data, function(err, sell) {
ongoingProcess.set('Sending request...', false);
if (err) {
self.error = err;
return;
}
self.sellInfo = sell.data;
});
};
this.confirmSell = function(token, account, sell) {
self.error = null;
var accountId = account.id;
var sellId = sell.id;
ongoingProcess.set('Selling Bitcoin...', true);
coinbaseService.sellCommit(token, accountId, sellId, function(err, data) {
ongoingProcess.set('Selling Bitcoin...', false);
if (err) {
self.error = err;
return;
}
self.success = data.data;
$scope.$emit('Local/CoinbaseTx');
});
};
this.createTx = function(token, account, amount) {
self.error = null;
if (!client) {
self.error = 'No wallet selected';
var checkTransaction = lodash.throttle(function(count, txp) {
$log.warn('Check if transaction has been received by Coinbase. Try ' + count + '/5');
// TX amount in BTC
var satToBtc = 1 / 100000000;
var amountBTC = (txp.amount * satToBtc).toFixed(8);
coinbaseService.init(function(err, res) {
if (err) {
$log.error(err);
checkTransaction(count, txp);
return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var sellPrice = null;
coinbaseService.sellPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, sell) {
if (err) {
$log.debug(err);
checkTransaction(count, txp);
return;
}
sellPrice = sell.data;
var accountId = account.id;
var dataSrc = {
name: 'Received from Copay: ' + self.selectedWalletName
};
var outputs = [];
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
ongoingProcess.set('Creating Transaction...', true);
$timeout(function() {
coinbaseService.createAddress(token, accountId, dataSrc, function(err, data) {
coinbaseService.getTransactions(accessToken, accountId, function(err, ctxs) {
if (err) {
ongoingProcess.set('Creating Transaction...', false);
self.error = err;
$log.debug(err);
checkTransaction(count, txp);
return;
}
var address, comment;
var coinbaseTransactions = ctxs.data;
var txFound = false;
var ctx;
for(var i = 0; i < coinbaseTransactions.length; i++) {
ctx = coinbaseTransactions[i];
if (ctx.type == 'send' && ctx.from && ctx.amount.amount == amountBTC ) {
$log.warn('Transaction found!', ctx);
txFound = true;
$log.debug('Saving transaction to process later...');
ctx['payment_method'] = $scope.selectedPaymentMethodId.value;
ctx['status'] = 'pending'; // Forcing "pending" status to process later
ctx['price_sensitivity'] = $scope.selectedPriceSensitivity.data;
ctx['sell_price_amount'] = sellPrice ? sellPrice.amount : '';
ctx['sell_price_currency'] = sellPrice ? sellPrice.currency : 'USD';
ctx['description'] = appConfigService.nameCase + ' Wallet: ' + $scope.wallet.name;
coinbaseService.savePendingTransaction(ctx, null, function(err) {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
if (err) $log.debug(err);
});
return;
}
}
if (!txFound) {
// Transaction sent, but could not be verified by Coinbase.com
$log.warn('Transaction not found in Coinbase.');
if (count < 5) {
checkTransaction(count + 1, txp);
} else {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError('No transaction found');
return;
}
}
});
});
});
}, 8000, {
'leading': true
});
address = data.data.address;
amount = parseInt((amount * 100000000).toFixed(0));
comment = 'Send funds to Coinbase Account: ' + account.name;
var statusChangeHandler = function (processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( processName == 'sellingBitcoin' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
$scope.sendStatus = showName;
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
coinbaseService.setCredentials();
$scope.isFiat = data.stateParams.currency ? true : false;
[amount, currency, $scope.amountUnitStr] = coinbaseService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
$scope.priceSensitivity = coinbaseService.priceSensitivity;
$scope.selectedPriceSensitivity = { data: coinbaseService.selectedPriceSensitivity };
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network
});
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
coinbaseService.sellPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, s) {
$scope.sellPrice = s.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_sell) {
$scope.paymentMethods.push(pm);
}
if (pm.allow_sell && pm.primary_sell) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
}
}
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to sell');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.sellRequest();
});
});
});
$scope.sellRequest = function() {
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var dataSrc = {
amount: amount,
currency: currency,
payment_method: $scope.selectedPaymentMethodId.value,
quote: true
};
coinbaseService.sellRequest(accessToken, accountId, dataSrc, function(err, data) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
showErrorAndBack(err);
return;
}
$scope.sellRequestInfo = data.data;
$timeout(function() {
$scope.$apply();
}, 100);
});
});
};
$scope.sellConfirm = function() {
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
var message = 'Selling bitcoin for ' + amount + ' ' + currency;
var okText = 'Confirm';
var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return;
ongoingProcess.set('sellingBitcoin', true, statusChangeHandler);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var dataSrc = {
name: 'Received from ' + appConfigService.nameCase
};
coinbaseService.createAddress(accessToken, accountId, dataSrc, function(err, data) {
if (err) {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
var outputs = [];
var toAddress = data.data.address;
var amountSat = parseInt(($scope.sellRequestInfo.amount.amount * 100000000).toFixed(0));
var comment = 'Sell bitcoin (Coinbase)';
outputs.push({
'toAddress': address,
'amount': amount,
'toAddress': toAddress,
'amount': amountSat,
'message': comment
});
var txp = {
toAddress: address,
amount: amount,
toAddress: toAddress,
amount: amountSat,
outputs: outputs,
message: comment,
payProUrl: null,
@ -189,73 +260,47 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
feeLevel: walletSettings.feeLevel || 'normal'
};
walletService.createTx(client, txp, function(err, createdTxp) {
walletService.createTx($scope.wallet, txp, function(err, ctxp) {
if (err) {
$log.debug(err);
ongoingProcess.set('Creating Transaction...', false);
self.error = {
errors: [{
message: 'Could not create transaction: ' + err.message
}]
};
$scope.$apply();
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
ongoingProcess.set('Creating Transaction...', false);
$scope.$emit('Local/NeedsConfirmation', createdTxp, function(accept) {
if (accept) {
self.confirmTx(createdTxp, function(err, tx) {
ongoingProcess.clear();
if (err) {
self.error = {
errors: [{
message: 'Could not create transaction: ' + err.message
}]
};
return;
}
ongoingProcess.set('Checking Transaction...', false);
coinbaseService.getTransactions(token, accountId, function(err, ctxs) {
if (err) {
$log.debug(err);
return;
}
lodash.each(ctxs.data, function(ctx) {
if (ctx.type == 'send' && ctx.from) {
ongoingProcess.clear();
if (ctx.status == 'completed') {
self.sellRequest(token, account, ctx);
} else {
// Save to localstorage
ctx['price_sensitivity'] = $scope.selectedPriceSensitivity;
ctx['sell_price_amount'] = self.sellPrice ? self.sellPrice.amount : '';
ctx['sell_price_currency'] = self.sellPrice ? self.sellPrice.currency : 'USD';
ctx['description'] = 'Copay Wallet: ' + client.credentials.walletName;
coinbaseService.savePendingTransaction(ctx, null, function(err) {
if (err) $log.debug(err);
self.sendInfo = ctx;
$timeout(function() {
$scope.$emit('Local/CoinbaseTx');
}, 1000);
});
}
return false;
}
});
});
});
} else {
go.path('coinbase');
$log.debug('Transaction created.');
publishAndSign($scope.wallet, ctxp, function() {}, function(err, txSent) {
if (err) {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
$log.debug('Transaction broadcasted. Wait for Coinbase confirmation...');
checkTransaction(1, txSent);
});
});
});
}, 100);
};
});
});
});
};
this.confirmTx = function(txp, cb) {
$scope.showWalletSelector = function() {
$scope.walletSelectorTitle = 'Sell From';
$scope.showWallets = true;
};
// TODO see walletService createAndPublish
};
$scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet;
};
});
$scope.goBackHome = function() {
$scope.sendStatus = '';
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.buyandsell.coinbase');
});
};
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('tabHomeController',
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, bitpayCardService, startupService, addressbookService, feedbackService, bwcError) {
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, bitpayCardService, startupService, addressbookService, feedbackService, bwcError, coinbaseService) {
var wallet;
var listeners = [];
var notifications = [];
@ -83,6 +83,10 @@ angular.module('copayApp.controllers').controller('tabHomeController',
var wallet = profileService.getWallet(walletId);
updateWallet(wallet);
if ($scope.recentTransactionsEnabled) getNotifications();
if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
// Update Coinbase
coinbaseService.updatePendingTransactions();
}
}),
$rootScope.$on('Local/TxAction', function(e, walletId) {
$log.debug('Got action for wallet ' + walletId);

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $ionicModal, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, gettextCatalog) {
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog) {
var updateConfig = function() {
var isCordova = platformInfo.isCordova;
@ -26,6 +26,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
$scope.coinbaseEnabled = config.coinbase.enabled && !isWindowsPhoneApp;
if ($scope.bitpayCardEnabled) {
bitpayCardService.getBitpayDebitCards(function(err, cards) {
@ -41,6 +42,13 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
});
}
if ($scope.coinbaseEnabled) {
coinbaseService.setCredentials();
coinbaseService.getStoredToken(function(at) {
$scope.coinbaseToken = at;
});
}
});
};

View file

@ -142,10 +142,6 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
controller: 'glideraUriController',
templateUrl: 'views/glideraUri.html'
})
.state('uricoinbase', {
url: '/uri-coinbase/:url',
templateUrl: 'views/coinbaseUri.html'
})
/*
*
@ -926,22 +922,52 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
*
*/
.state('coinbase', {
url: '/coinbase',
templateUrl: 'views/coinbase.html'
})
.state('preferencesCoinbase', {
url: '/preferencesCoinbase',
templateUrl: 'views/preferencesCoinbase.html'
})
.state('buyCoinbase', {
url: '/buycoinbase',
templateUrl: 'views/buyCoinbase.html'
})
.state('sellCoinbase', {
url: '/sellcoinbase',
templateUrl: 'views/sellCoinbase.html'
})
.state('tabs.buyandsell.coinbase', {
url: '/coinbase/:code',
views: {
'tab-home@tabs': {
controller: 'coinbaseController',
controllerAs: 'coinbase',
templateUrl: 'views/coinbase.html'
}
}
})
.state('tabs.preferences.coinbase', {
url: '/coinbase',
views: {
'tab-settings@tabs': {
controller: 'preferencesCoinbaseController',
templateUrl: 'views/preferencesCoinbase.html'
}
}
})
.state('tabs.buyandsell.coinbase.amount', {
url: '/amount/:nextStep/:currency',
views: {
'tab-home@tabs': {
controller: 'amountController',
templateUrl: 'views/amount.html'
}
}
})
.state('tabs.buyandsell.coinbase.buy', {
url: '/buy/:amount/:currency',
views: {
'tab-home@tabs': {
controller: 'buyCoinbaseController',
templateUrl: 'views/buyCoinbase.html'
}
}
})
.state('tabs.buyandsell.coinbase.sell', {
url: '/sell/:amount/:currency',
views: {
'tab-home@tabs': {
controller: 'sellCoinbaseController',
templateUrl: 'views/sellCoinbase.html'
}
}
})
/*
*

View file

@ -1,11 +1,51 @@
'use strict';
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, platformInfo, lodash, storageService, configService) {
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService) {
var root = {};
var credentials = {};
var isCordova = platformInfo.isCordova;
var isNW = platformInfo.isNW;
root.setCredentials = function(network) {
root.priceSensitivity = [
{
value: 0.5,
name: '0.5%'
},
{
value: 1,
name: '1%'
},
{
value: 2,
name: '2%'
},
{
value: 5,
name: '5%'
},
{
value: 10,
name: '10%'
}
];
root.selectedPriceSensitivity = root.priceSensitivity[1];
root.setCredentials = function() {
if (!$window.externalServices || !$window.externalServices.coinbase) {
return;
}
var coinbase = $window.externalServices.coinbase;
/*
* Development: 'testnet'
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
// Coinbase permissions
credentials.SCOPE = ''
+ 'wallet:accounts:read,'
+ 'wallet:addresses:read,'
@ -20,26 +60,78 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
+ 'wallet:transactions:send,'
+ 'wallet:payment-methods:read';
// NW has a bug with Window Object
if (isCordova) {
credentials.REDIRECT_URI = 'copay://coinbase';
credentials.REDIRECT_URI = coinbase.redirect_uri.mobile;
} else {
credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
credentials.REDIRECT_URI = coinbase.redirect_uri.desktop;
}
if (network == 'testnet') {
credentials.HOST = 'https://sandbox.coinbase.com';
credentials.API = 'https://api.sandbox.coinbase.com';
credentials.CLIENT_ID = '6cdcc82d5d46654c46880e93ab3d2a43c639776347dd88022904bd78cd067841';
credentials.CLIENT_SECRET = '228cb6308951f4b6f41ba010c7d7981b2721a493c40c50fd2425132dcaccce59';
if (credentials.NETWORK == 'testnet') {
credentials.HOST = coinbase.sandbox.host;
credentials.API = coinbase.sandbox.api;
credentials.CLIENT_ID = coinbase.sandbox.client_id;
credentials.CLIENT_SECRET = coinbase.sandbox.client_secret;
}
else {
credentials.HOST = 'https://coinbase.com';
credentials.API = 'https://api.coinbase.com';
credentials.CLIENT_ID = window.coinbase_client_id;
credentials.CLIENT_SECRET = window.coinbase_client_secret;
credentials.HOST = coinbase.production.host;
credentials.API = coinbase.production.api;
credentials.CLIENT_ID = coinbase.production.client_id;
credentials.CLIENT_SECRET = coinbase.production.client_secret;
};
};
var _afterTokenReceived = function(data, cb) {
if (data && data.access_token && data.refresh_token) {
storageService.setCoinbaseToken(credentials.NETWORK, data.access_token, function() {
storageService.setCoinbaseRefreshToken(credentials.NETWORK, data.refresh_token, function() {
return cb(null, data.access_token);
});
});
} else {
return cb('Could not get the access token');
}
};
root.getNetwork = function() {
return credentials.NETWORK;
};
root.getStoredToken = function(cb) {
storageService.getCoinbaseToken(credentials.NETWORK, function(err, accessToken) {
if (err || !accessToken) return cb();
return cb(accessToken);
});
};
root.getAvailableCurrency = function() {
var config = configService.getSync().wallet.settings;
// ONLY "USD"
switch(config.alternativeIsoCode) {
default : return 'USD'
};
};
root.parseAmount = function(amount, currency) {
var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi;
var amountUnitStr;
// IF 'USD'
if (currency) {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
} else {
var amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = txFormatService.formatAmountStr(amountSat);
// convert unit to BTC
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
}
return [amount, currency, amountUnitStr];
};
root.getOauthCodeUrl = function() {
return credentials.HOST
+ '/oauth/authorize?response_type=code&client_id='
@ -54,7 +146,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
root.getToken = function(code, cb) {
var req = {
method: 'POST',
url: credentials.API + '/oauth/token',
url: credentials.HOST + '/oauth/token',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
@ -71,18 +163,18 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) {
$log.info('Coinbase Authorization Access Token: SUCCESS');
// Show pending task from the UI
storageService.setNextStep('BuyAndSell', true, function(err) {});
return cb(null, data.data);
storageService.setNextStep('BuyAndSell', 'true', function(err) {});
_afterTokenReceived(data.data, cb);
}, function(data) {
$log.error('Coinbase Authorization Access Token: ERROR ' + data.statusText);
return cb(data.data);
return cb(data.data || 'Could not get the access token');
});
};
root.refreshToken = function(refreshToken, cb) {
var _refreshToken = function(refreshToken, cb) {
var req = {
method: 'POST',
url: credentials.API + '/oauth/token',
url: credentials.HOST + '/oauth/token',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
@ -98,10 +190,58 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) {
$log.info('Coinbase Refresh Access Token: SUCCESS');
return cb(null, data.data);
_afterTokenReceived(data.data, cb);
}, function(data) {
$log.error('Coinbase Refresh Access Token: ERROR ' + data.statusText);
return cb(data.data);
return cb(data.data || 'Could not get the access token');
});
};
var _getMainAccountId = function(accessToken, cb) {
root.getAccounts(accessToken, function(err, a) {
if (err) return cb(err);
var data = a.data;
for (var i = 0; i < data.length; i++) {
if (data[i].primary && data[i].type == 'wallet') {
return cb(null, data[i].id);
}
}
root.logout(function() {});
return cb('Your primary account should be a WALLET. Set your wallet account as primary and try again');
});
};
root.init = function(cb) {
if (lodash.isEmpty(credentials.CLIENT_ID)) {
return cb('Coinbase is Disabled');
}
$log.debug('Trying to initialise Coinbase...');
storageService.getCoinbaseToken(credentials.NETWORK, function(err, accessToken) {
if (err || !accessToken) return cb();
else {
_getMainAccountId(accessToken, function(err, accountId) {
if (err) {
if (err.errors && err.errors[0] && err.errors[0].id == 'expired_token') {
$log.debug('Refresh token');
storageService.getCoinbaseRefreshToken(credentials.NETWORK, function(err, refreshToken) {
if (err) return cb(err);
_refreshToken(refreshToken, function(err, newToken) {
if (err) return cb(err);
_getMainAccountId(newToken, function(err, accountId) {
if (err) return cb(err);
return cb(null, {accessToken: newToken, accountId: accountId});
});
});
});
} else {
return cb(err);
}
} else {
return cb(null, {accessToken: accessToken, accountId: accountId});
}
});
}
});
};
@ -124,7 +264,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
return cb(null, data.data);
}, function(data) {
$log.error('Coinbase Get Accounts: ERROR ' + data.statusText);
return cb(data.data);
return cb(data.data || 'Could not get the accounts');
});
};
@ -172,6 +312,17 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
});
};
root.getAddressTransactions = function(token, accountId, addressId, cb) {
if (!token) return cb('Invalid Token');
$http(_get('/accounts/' + accountId + '/addresses/' + addressId + '/transactions', token)).then(function(data) {
$log.info('Coinbase Address s Transactions: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('Coinbase Address s Transactions: ERROR ' + data.statusText);
return cb(data.data);
});
};
root.getTransactions = function(token, accountId, cb) {
if (!token) return cb('Invalid Token');
$http(_get('/accounts/' + accountId + '/transactions', token)).then(function(data) {
@ -252,7 +403,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
amount: data.amount,
currency: data.currency,
payment_method: data.payment_method || null,
commit: data.commit || false
commit: data.commit || false,
quote: data.quote || false
};
$http(_post('/accounts/' + accountId + '/sells', token, data)).then(function(data) {
$log.info('Coinbase Sell Request: SUCCESS');
@ -278,7 +430,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
amount: data.amount,
currency: data.currency,
payment_method: data.payment_method || null,
commit: false
commit: data.commit || false,
quote: data.quote || false
};
$http(_post('/accounts/' + accountId + '/buys', token, data)).then(function(data) {
$log.info('Coinbase Buy Request: SUCCESS');
@ -330,10 +483,13 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
};
// Pending transactions
root.savePendingTransaction = function(ctx, opts, cb) {
var network = configService.getSync().coinbase.testnet ? 'testnet' : 'livenet';
storageService.getCoinbaseTxs(network, function(err, oldTxs) {
_savePendingTransaction(ctx, opts, cb);
};
var _savePendingTransaction = function(ctx, opts, cb) {
storageService.getCoinbaseTxs(credentials.NETWORK, function(err, oldTxs) {
if (lodash.isString(oldTxs)) {
oldTxs = JSON.parse(oldTxs);
}
@ -350,23 +506,200 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
}
tx = JSON.stringify(tx);
storageService.setCoinbaseTxs(network, tx, function(err) {
storageService.setCoinbaseTxs(credentials.NETWORK, tx, function(err) {
return cb(err);
});
});
};
root.getPendingTransactions = function(cb) {
var network = configService.getSync().coinbase.testnet ? 'testnet' : 'livenet';
storageService.getCoinbaseTxs(network, function(err, txs) {
var _txs = txs ? JSON.parse(txs) : {};
return cb(err, _txs);
root.getPendingTransactions = function(coinbasePendingTransactions) {
storageService.getCoinbaseTxs(credentials.NETWORK, function(err, txs) {
txs = txs ? JSON.parse(txs) : {};
coinbasePendingTransactions.data = lodash.isEmpty(txs) ? null : txs;
root.init(function(err, data) {
if (err || lodash.isEmpty(data)) {
if (err) $log.error(err);
return;
}
var accessToken = data.accessToken;
var accountId = data.accountId;
lodash.forEach(coinbasePendingTransactions.data, function(dataFromStorage, txId) {
if ((dataFromStorage.type == 'sell' && dataFromStorage.status == 'completed') ||
(dataFromStorage.type == 'buy' && dataFromStorage.status == 'completed') ||
dataFromStorage.status == 'error' ||
(dataFromStorage.type == 'send' && dataFromStorage.status == 'completed'))
return;
root.getTransaction(accessToken, accountId, txId, function(err, tx) {
if (err || lodash.isEmpty(tx) || (tx.data && tx.data.error)) {
_savePendingTransaction(dataFromStorage, {
status: 'error',
error: (tx.data && tx.data.error) ? tx.data.error : err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
return;
}
_updateCoinbasePendingTransactions(dataFromStorage, tx.data);
coinbasePendingTransactions.data[txId] = dataFromStorage;
if (tx.data.type == 'send' && tx.data.status == 'completed' && tx.data.from) {
root.sellPrice(accessToken, dataFromStorage.sell_price_currency, function(err, s) {
if (err) {
_savePendingTransaction(dataFromStorage, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
return;
}
var newSellPrice = s.data.amount;
var variance = Math.abs((newSellPrice - dataFromStorage.sell_price_amount) / dataFromStorage.sell_price_amount * 100);
if (variance < dataFromStorage.price_sensitivity.value) {
_sellPending(dataFromStorage, accessToken, accountId, coinbasePendingTransactions);
} else {
var error = {
errors: [{
message: 'Price falls over the selected percentage'
}]
};
_savePendingTransaction(dataFromStorage, {
status: 'error',
error: error
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
}
});
} else if (tx.data.type == 'buy' && tx.data.status == 'completed' && tx.data.buy) {
_sendToWallet(dataFromStorage, accessToken, accountId, coinbasePendingTransactions);
} else {
_savePendingTransaction(dataFromStorage, {}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
}
});
});
});
});
};
root.logout = function(network, cb) {
storageService.removeCoinbaseToken(network, function() {
storageService.removeCoinbaseRefreshToken(network, function() {
root.updatePendingTransactions = lodash.throttle(function() {
$log.debug('Updating pending transactions...');
root.setCredentials();
var pendingTransactions = { data: {} };
root.getPendingTransactions(pendingTransactions);
}, 20000);
var _updateTxs = function(coinbasePendingTransactions) {
storageService.getCoinbaseTxs(credentials.NETWORK, function(err, txs) {
txs = txs ? JSON.parse(txs) : {};
coinbasePendingTransactions.data = lodash.isEmpty(txs) ? null : txs;
});
};
var _sellPending = function(tx, accessToken, accountId, coinbasePendingTransactions) {
var data = tx.amount;
data['payment_method'] = tx.payment_method || null;
data['commit'] = true;
root.sellRequest(accessToken, accountId, data, function(err, res) {
if (err) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
} else {
if (res.data && !res.data.transaction) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
return;
}
_savePendingTransaction(tx, {
remove: true
}, function(err) {
root.getTransaction(accessToken, accountId, res.data.transaction.id, function(err, updatedTx) {
_savePendingTransaction(updatedTx.data, {}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
});
});
}
});
};
var _sendToWallet = function(tx, accessToken, accountId, coinbasePendingTransactions) {
if (!tx) return;
var desc = appConfigService.nameCase + ' Wallet';
var data = {
to: tx.toAddr,
amount: tx.amount.amount,
currency: tx.amount.currency,
description: desc
};
root.sendTo(accessToken, accountId, data, function(err, res) {
if (err) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
} else {
if (res.data && !res.data.id) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
return;
}
root.getTransaction(accessToken, accountId, res.data.id, function(err, sendTx) {
_savePendingTransaction(tx, {
remove: true
}, function(err) {
_savePendingTransaction(sendTx.data, {}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
});
});
}
});
};
var _updateCoinbasePendingTransactions = function(obj /*, …*/ ) {
for (var i = 1; i < arguments.length; i++) {
for (var prop in arguments[i]) {
var val = arguments[i][prop];
if (typeof val == "object")
_updateCoinbasePendingTransactions(obj[prop], val);
else
obj[prop] = val ? val : obj[prop];
}
}
return obj;
};
root.logout = function(cb) {
storageService.removeCoinbaseToken(credentials.NETWORK, function() {
storageService.removeCoinbaseRefreshToken(credentials.NETWORK, function() {
return cb();
});
});

View file

@ -49,7 +49,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer
},
coinbase: {
enabled: false, //disable coinbase for this release
enabled: true,
testnet: false
},
@ -222,10 +222,6 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.aliasFor = configCache.aliasFor || {};
configCache.emailFor = configCache.emailFor || {};
// Coinbase
// Disabled for testnet
configCache.coinbase.testnet = false;
$log.debug('Preferences read:', configCache)
lodash.each(root._queue, function(x) {

View file

@ -126,9 +126,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
url: data
});
} else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
return $state.go('uricoinbase', {
url: data
var code = getParameterByName('code', data);
$state.go('tabs.home', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.home' ? false : true
}).then(function() {
$state.transitionTo('tabs.buyandsell.coinbase', {
code: code
});
});
return true;
// BitPayCard Authentication
} else if (data && data.indexOf(appConfigService.name + '://') === 0) {

View file

@ -80,7 +80,6 @@
padding: 24px 0;
font-size: 18px;
.title {
float: left;
padding-top: 10px;
color: $dark-gray;
font-weight: bold;
@ -89,6 +88,9 @@
color: $light-gray;
font-size: 12px;
}
.select {
margin: 10px 1px;
}
}
}
.amount {

View file

@ -1,21 +1,129 @@
.coinbase-preferences {
ul {
font-size: 14px;
background: white;
li {
padding: 16px 10px 16px 16px;
border-bottom: 1px solid #E9E9EC;
#coinbase {
$item-lateral-padding: 20px;
$item-vertical-padding: 10px;
$item-border-color: #EFEFEF;
$item-label-color: #6C6C6E;
@extend .deflash-blue;
.add-bottom-for-cta {
bottom: 92px;
}
.head {
padding: 30px $item-lateral-padding 4rem;
border-top: 0;
.sending-label {
display: flex;
font-size: 18px;
align-items: center;
margin-bottom: 1.5rem;
img {
margin-right: 1rem;
height: 35px;
width: 35px;
}
span {
text-transform: capitalize;
}
}
.amount-label{
line-height: 30px;
.amount{
font-size: 38px;
margin-bottom: .5rem;
> .unit {
font-family: "Roboto-Light";
}
}
.alternative {
font-size: 16px;
font-family: "Roboto-Light";
color: #9B9B9B;
}
}
}
.item {
border-color: $item-border-color;
}
.info {
.badge {
border-radius: 0;
padding: .5rem;
}
.item {
color: #4A4A4A;
padding-top: $item-vertical-padding;
padding-bottom: $item-vertical-padding;
padding-left: $item-lateral-padding;
&:not(.item-icon-right) {
padding-right: $item-lateral-padding;
}
.label {
font-size: 14px;
color: $item-label-color;
margin-bottom: 8px;
}
.capitalized {
text-transform: capitalize;
}
.wallet .big-icon-svg > .bg {
height: 24px;
width: 24px;
padding: 2px;
box-shadow: none;
vertical-align: middle;
}
.total-amount {
font-weight: bold;
}
&.single-line {
display: flex;
align-items: center;
padding-top: 17px;
padding-bottom: 17px;
.label {
margin: 0;
flex-grow: 1;
}
}
}
.item-divider {
padding-top: 1.2rem;
color: $item-label-color;
font-size: 15px;
}
.wallet {
display: flex;
align-items: center;
padding: .2rem 0;
margin-bottom: 5px;
~ .bp-arrow-right {
top: 14px;
}
> i {
padding: 0;
position: static;
> img {
height: 24px;
width: 24px;
padding: 2px;
margin-right: .7rem;
box-shadow: none;
}
}
}
}
}
.coinbase-last-transactions-content {
background: #fff;
padding: 0.8rem 1rem;
cursor: pointer;
border-bottom: 1px solid #E4E8EC;
}
.coinbase-pointer {
cursor: pointer;
}