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;
}

View file

@ -21,12 +21,9 @@
<span class="toggle-label" translate>Enable Glidera Service</span>
</ion-toggle>
<!-- disable coinbase for this release -->
<!-- <ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled.value" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<span class="toggle-label" translate>Enable Coinbase Service</span>
</ion-toggle> -->
</ion-toggle>
<div class="item item-divider" translate>Wallet Operation</div>

View file

@ -14,14 +14,14 @@
<ion-content scroll="false">
<div ng-if="!customAmount && !isGlidera">
<div ng-if="!customAmount && !isGlidera && !nextStep">
<div class="item item-no-bottom-border recipient-label" translate>Recipient</div>
<div class="item item-text-wrap item-icon-left bitcoin-address" ng-class="{'item-big-icon-left':cardId}">
<i class="icon big-icon-svg" ng-if="isWallet">
<img src="img/icon-wallet.svg" ng-style="{'background-color': toColor}" class="bg"/>
</i>
<span ng-if="!isWallet && !isGiftCard && !isGlidera">
<span ng-if="!isWallet && !isGiftCard">
<i class="icon big-icon-svg" ng-if="isChromeApp">
<img src="img/contact-placeholder.svg" class="bg"/>
</i>
@ -39,7 +39,8 @@
</div>
</div>
<div ng-class="{'amount-pane-recipient': !customAmount && !isGlidera, 'amount-pane-no-recipient': customAmount || isGlidera}">
<div ng-class="{'amount-pane-recipient': !customAmount && !isGlidera && !nextStep,
'amount-pane-no-recipient': customAmount || isGlidera || nextStep}">
<div class="amount-bar oh">
<div class="title">

View file

@ -1,169 +1,113 @@
<div
class="topbar-container"
ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Buy'; goBackToState = 'coinbase'; noColor = true">
</div>
<ion-view id="coinbase" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>Buy bitcoin</ion-nav-title>
</ion-nav-bar>
<ion-content class="add-bottom-for-cta">
<!-- BUY -->
<div class="list" ng-if="buyRequestInfo">
<div class="content coinbase" ng-controller="buyCoinbaseController as buy">
<div class="row m20t" ng-show="buy.error || index.coinbaseError" ng-click="buy.error = null">
<div class="columns">
<div class="box-notification">
<ul class="no-bullet m0 size-12 text-warning">
<li ng-repeat="err in (buy.error.errors || index.coinbaseError.errors)" ng-bind-html="err.message"></li>
</ul>
</div>
</div>
</div>
<div class="row m20ti"
ng-show="index.coinbaseAccount && !buy.buyInfo && !buy.receiveInfo">
<div class="columns">
<form name="buyCoinbaseForm"
ng-submit="buy.buyRequest(index.coinbaseToken, index.coinbaseAccount)" novalidate>
<div ng-if="index.coinbaseToken" ng-init="buy.getPaymentMethods(index.coinbaseToken)">
<label>Payment method</label>
<select
ng-model="selectedPaymentMethod.id"
ng-options="item.id as item.name for item in buy.paymentMethods">
</select>
<div class="item head">
<div class="sending-label">
<img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="35" class="item-img-buy">
<span>Buying</span>
</div>
<div class="amount-label">
<div class="amount">{{amountUnitStr}}</div>
<div class="alternative" ng-if="buyPrice">
<span ng-show="isFiat">{{buyRequestInfo.amount.amount}} {{buyRequestInfo.amount.currency}}</span>
@ ${{buyPrice.amount}} per BTC
</div>
</div>
</div>
<label>Amount
<span
ng-if="index.coinbaseToken"
ng-init="buy.getPrice(index.coinbaseToken)"
ng-show="buy.buyPrice"
class="size-11 text-light right">
1 BTC <i class="icon-arrow-right"></i> {{buy.buyPrice.amount}} {{buy.buyPrice.currency}}
</span>
<div class="info">
<label class="item item-input item-select">
<div class="input-label">
Payment Method
</div>
<select ng-model="selectedPaymentMethodId.value"
ng-options="item.id as item.name for item in paymentMethods"
ng-change="buyRequest()">
</select>
</label>
<div class="input">
<input ng-show="!showAlternative" type="number" id="amount" ignore-mouse-wheel
name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-minlength="0.00000001" ng-maxlength="10000000000"
ng-model="amount" autocomplete="off" ng-disabled="buy.loading">
<input ng-show="showAlternative" type="number" id="fiat" ignore-mouse-wheel
name="fiat" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-model="fiat" autocomplete="off" ng-disabled="buy.loading">
<a ng-show="!showAlternative" class="postfix button"
ng-click="showAlternative = true; amount = null">BTC</a>
<a ng-show="showAlternative" class="postfix button black"
ng-click="showAlternative = false; fiat = null">USD</a>
</div>
<div class="text-center text-gray size-12 m10b">
<span ng-show="!(amount || fiat)">
Enter the amount to get the exchange rate
</span>
<span ng-show="!buy.buyPrice && (amount || fiat)">
Not available
</span>
<span ng-show="buy.buyPrice && amount && !fiat">
~ {{buy.buyPrice.amount * amount | currency : 'USD ' : 2}}
</span>
</div>
<div class="text-center">
<i class="db fi-arrow-down size-24 m10v"></i>
</div>
<div
ng-if="index.coinbaseToken"
ng-init="buy.init(index.coinbaseTestnet)"
ng-click="openWalletsModal(buy.allWallets)">
<label>Copay Wallet</label>
<div class="input">
<input type="text" id="address" name="address" ng-disabled="buy.selectedWalletId"
ng-attr-placeholder="{{'Choose a wallet to receive bitcoin'}}" ng-model="buy.selectedWalletName" required>
<a class="postfix size-12 m0 text-gray">
<i class="icon-wallet size-18"></i>
</a>
<div class="item item-icon-right" ng-click="showWalletSelector()">
<div class="label">Receive in</div>
<div class="wallet">
<i class="icon big-icon-svg">
<img src="img/icon-wallet.svg" ng-style="{'background-color': wallet.color}" class="bg">
</i>
{{wallet ? wallet.name : '...'}}
</div>
<i class="icon bp-arrow-right"></i>
</div>
<div class="input m20t">
<input class="button black expand round"
ng-disabled="buy.loading || (!amount && !fiat) || !selectedPaymentMethod"
ng-style="{'background-color': '#2b71b1'}"
type="submit" value="{{'Continue'}}">
<div class="item item-divider">
Transaction details
</div>
<div class="item">
Amount
<span class="item-note">
{{buyRequestInfo.subtotal.amount}} {{buyRequestInfo.subtotal.currency}}
</span>
</div>
<div class="item" ng-repeat="fee in buyRequestInfo.fees">
<span class="capitalized">
{{fee.type}} fee
</span>
<span class="item-note">
{{fee.amount.amount}} {{fee.amount.currency}}
</span>
</div>
<div class="item">
Total to pay
<span class="item-note total-amount">
{{buyRequestInfo.total.amount}} {{buyRequestInfo.total.currency}}
</span>
</div>
</form>
</div>
</div>
<div class="m20ti row" ng-show="buy.receiveInfo && !buy.sellInfo && !buy.success">
<div class="columns">
<h1>Funds sent to Copay Wallet</h1>
<p class="size-12 text-gray">
Buy confirmed. Funds will be send soon to your selected Copay Wallet
</p>
<button class="m20t outline black round expand"
ng-style="{'background-color': '#2b71b1'}"
href ui-sref="coinbase">OK</button>
</div>
</div>
<div ng-show="buy.buyInfo && !buy.receiveInfo && !buy.success">
<h4 class="title">Confirm transaction</h4>
<ul class="no-bullet m10t size-12 white">
<li class="line-b line-t p15">
<span class="m10 text-normal text-bold">Amount</span>
<span class="right text-gray">{{buy.buyInfo.amount.amount}} {{buy.buyInfo.amount.currency}}</span>
</li>
<li class="line-b oh p15">
<span class="m10 text-normal text-bold">Fees</span>
<span class="right text-gray">
<div ng-repeat="fee in buy.buyInfo.fees">
<b>{{fee.type}}</b> {{fee.amount.amount}} {{fee.amount.currency}}
</div>
</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Subtotal</span>
<span class="right text-gray">{{buy.buyInfo.subtotal.amount}} {{buy.buyInfo.subtotal.currency}}</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Total</span>
<span class="right text-gray">{{buy.buyInfo.total.amount}} {{buy.buyInfo.total.currency}}</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Payout at</span>
<span class="right text-gray">{{buy.buyInfo.payout_at | amCalendar}}</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Deposit into Copay Wallet</span>
<span class="right text-gray">{{buy.selectedWalletName}}</span>
</li>
</ul>
<div class="row">
<div class="columns">
<button class="button black round expand"
ng-style="{'background-color': '#2b71b1'}"
ng-click="buy.confirmBuy(index.coinbaseToken, index.coinbaseAccount, buy.buyInfo)"
ng-disabled="buy.loading">
Buy
</button>
</div>
</div>
</div>
<div class="m20t row text-center" ng-show="buy.success">
<div class="columns">
<h1>Purchase initiated</h1>
<p class="text-gray">
Bitcoin purchase completed. Coinbase has queued the transfer to your selected Copay wallet.
</p>
<button class="outline dark-gray round expand" href ui-sref="coinbase">OK</button>
</ion-content>
<click-to-accept
ng-disabled="!selectedPaymentMethodId.value || !buyRequestInfo || !wallet"
ng-click="buyConfirm()"
ng-if="!isCordova && buyRequestInfo"
click-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="!selectedPaymentMethodId.value"
no-matching-wallet="!buyRequestInfo">
Confirm purchase
</click-to-accept>
<slide-to-accept
ng-disabled="!selectedPaymentMethodId.value || !buyRequestInfo || !wallet"
ng-if="isCordova && buyRequestInfo"
slide-on-confirm="buyConfirm()"
slide-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="!selectedPaymentMethodId.value"
no-matching-wallet="!buyRequestInfo">
Slide to buy
</slide-to-accept>
<slide-to-accept-success
slide-success-show="sendStatus === 'success'"
slide-success-on-confirm="goBackHome()"
slide-success-hide-on-confirm="true">
<span>Bought</span>
<div class="m10 size-14">
Bitcoin purchase completed. Coinbase has queued the transfer to your selected wallet
</div>
</div>
</slide-to-accept-success>
</div>
<div class="extra-margin-bottom"></div>
<wallet-selector
wallet-selector-title="walletSelectorTitle"
wallet-selector-wallets="wallets"
wallet-selector-selected-wallet="wallet"
wallet-selector-show="showWallets"
wallet-selector-on-select="onWalletSelect">
</wallet-selector>
</ion-view>

View file

@ -6,6 +6,10 @@
</ion-nav-bar>
<ion-content>
<ion-list>
<ion-item class="item item-icon-right" ui-sref="tabs.buyandsell.coinbase">
<img src="img/coinbase-logo.png" width="90">
<i class="icon bp-arrow-right"></i>
</ion-item>
<ion-item class="item item-icon-right" ui-sref="tabs.buyandsell.glidera">
<img src="img/glidera-logo.png" width="90">
<i class="icon bp-arrow-right"></i>

View file

@ -1,59 +1,19 @@
<ion-view id="coinbase">
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>Coinbase</ion-nav-title>
</ion-nav-bar>
<div class="topbar-container">
<nav ng-controller="topbarController as topbar"
class="tab-bar"
ng-style="{'background-color': '#2b71b1'}">
<section class="left-small">
<a class="p10"
ng-click="topbar.goHome()">
<span class="text-close">Close</span>
</a>
</section>
<ion-content>
<section class="right-small" ng-show="index.coinbaseAccount">
<a class="p10" href ui-sref="preferencesCoinbase">
<i class="fi-widget size-24"></i>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis">
Buy & Sell Bitcoin
</h1>
</section>
</nav>
</div>
<div class="content coinbase p20b" ng-controller="coinbaseController as coinbase">
<div class="row" ng-show="index.coinbaseError || (index.coinbaseToken && !index.coinbaseAccount)">
<div class="m20b box-notification" ng-show="index.coinbaseError">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in index.coinbaseError.errors" ng-bind-html="err.message"></li>
<div class="box-notification error" ng-show="error">
<ul class="no-bullet m0 size-12">
<li ng-repeat="err in error.errors" ng-bind-html="err.message"></li>
</ul>
</div>
<div class="m20b box-notification" ng-show="index.coinbaseToken && !index.coinbaseAccount">
<div class="text-warning">
<span>Your primary account should be a WALLET. Set your wallet account as primary and try again.</span>
</div>
</div>
<div class="m10t text-center">
<button
class="dark-gray outline round tiny"
ng-click="index.initCoinbase(index.coinbaseToken)">
Reconnect
</button>
<div class="m20t size-12">
Or go to <a class="text-gray" href ui-sref="preferencesCoinbase">Preferences</a> and log out manually.
</div>
</div>
</div>
<div ng-if="!index.coinbaseToken && !index.coinbaseError" class="row">
<div class="box-notification text-center size-12 text-warning" ng-show="index.coinbaseTestnet">
<i class="fi-info"></i>
Testnet wallets only work with Coinbase Sandbox Accounts
</div>
<div class="columns" ng-init="showOauthForm = false">
<div ng-if="!accessToken && !error" ng-init="showOauthForm = false">
<div class="text-center m20v">
<img src="img/coinbase-logo.png" width="200">
</div>
@ -61,111 +21,112 @@
<p class="m20t text-gray size-12">Connect your Coinbase account to get started</p>
<a class="button light-gray outline round small"
ng-click="coinbase.openAuthenticateWindow(); showOauthForm = true">
<button class="button button-standard button-primary"
ng-click="coinbase.openAuthenticateWindow()">
Connect to Coinbase
</a>
<div>
</button>
<div class="m20t">
<a href ng-click="showOauthForm = true" class="text-gray size-12">
Do you already have the Oauth Code?
</a>
</div>
</div>
<div class="text-center" ng-show="showOauthForm">
<div ng-show="showOauthForm">
<div class="text-left box-notification" ng-show="coinbase.error">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in coinbase.error.errors" ng-bind-html="err.message"></li>
</ul>
</div>
<form name="oauthCodeForm" ng-submit="coinbase.submitOauthCode(code)" novalidate>
<label>OAuth Code</label>
<input type="text" ng-model="code" ng-disabled="coinbase.loading"
ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
<input
class="button expand round"
ng-style="{'background-color': '#2b71b1'}"
type="submit" value="Get started" ng-disabled="oauthCodeForm.$invalid || coinbase.loading">
</form>
<button class="button light-gray expand outline round"
ng-click="showOauthForm = false; index.coinbaseError = null; coinbase.error = null">
<i class="fi-arrow-left"></i> <span class="tu">Back</span>
</button>
</div>
</div>
</div>
<div ng-if="index.coinbaseToken && index.coinbaseAccount && !index.coinbaseError">
<div class="p20v text-center" ng-show="index.coinbaseAccount" ng-click="index.updateCoinbase({updateAccount: true})">
<img src="img/coinbase-logo.png" width="100">
</div>
<ul ng-show="index.coinbaseAccount" class="no-bullet m0 size-12">
<li class="line-b line-t p15 coinbase-pointer"
href ui-sref="buyCoinbase">
<img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="30">
<span class="m10 text-normal text-bold">Buy Bitcoin</span>
<span class="right text-gray">
<i class="icon-arrow-right3 size-24 right"></i>
</span>
</li>
<li class="line-b p15 coinbase-pointer"
href ui-sref="sellCoinbase">
<img src="img/sell-bitcoin.svg" alt="sell bitcoin" width="30">
<span class="m10 text-normal text-bold">Sell Bitcoin</span>
<span class="right text-gray">
<i class="icon-arrow-right3 size-24 right"></i>
</span>
</li>
</ul>
<div ng-show="index.coinbasePendingTransactions && !index.coinbaseError">
<h4 class="title">Activity</h4>
<div class="m20b box-notification" ng-show="index.coinbasePendingError">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in index.coinbasePendingError.errors" ng-bind-html="err.message"></li>
</ul>
</div>
<div ng-repeat="(id, tx) in index.coinbasePendingTransactions | orderObjectBy:'updated_at':true track by $index"
ng-click="coinbase.openTxModal(tx)"
class="row collapse coinbase-last-transactions-content">
<div class="large-2 medium-2 small-2 columns">
<img src="img/bought-pending.svg" alt="bought" width="24" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status != 'completed'">
<img src="img/bought.svg" alt="bought" width="30" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status == 'completed'">
<img src="img/sold-pending.svg" alt="sold" width="24" ng-show="tx.from && tx.type == 'send'">
<img src="img/sold.svg" alt="sold" width="30" ng-show="!tx.from && tx.type == 'sell' && tx.status == 'completed'">
</div>
<div class="large-5 medium-5 small-5 columns">
<div class="size-12 m5t">
<span ng-show="tx.type == 'sell' && tx.status == 'completed'">Sold</span>
<span ng-show="tx.type == 'buy' && tx.status == 'completed'">Bought</span>
<span class="text-bold">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}}
</span>
<div class="list settings-input-group">
<label class="item item-input item-stacked-label">
<span class="input-label">OAuth Code</span>
<input type="text"
ng-model="code"
ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
</label>
</div>
</div>
<div class="large-4 medium-4 small-4 columns text-right">
<div ng-show="tx.error" class="m5t size-12 text-warning">
<input
class="button button-standard button-primary"
type="submit" value="Connect Coinbase Account" ng-disabled="oauthCodeForm.$invalid">
</form>
</div>
</div>
<div ng-if="accessToken && !error">
<div class="m20t text-center" ng-click="updateTransactions()">
<img src="img/coinbase-logo.png" width="200">
</div>
<div class="m10t size-12 text-center text-gray">
<span ng-show="!buyPrice || !sellPrice">...</span>
<span ng-show="buyPrice && sellPrice">
{{buyPrice.amount}} {{buyPrice.currency}}
|
{{sellPrice.amount}} {{sellPrice.currency}}
</span>
</div>
<div class="list card">
<a class="item item-icon-right"
href ui-sref="tabs.buyandsell.coinbase.amount({nextStep: 'tabs.buyandsell.coinbase.buy', currency: currency})">
<img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="30" class="item-img-buy" style="vertical-align:
text-bottom;">
Buy Bitcoin
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right"
href ui-sref="tabs.buyandsell.coinbase.amount({nextStep: 'tabs.buyandsell.coinbase.sell', currency: currency})">
<img src="img/sell-bitcoin.svg" alt="sell bitcoin" width="30" class="item-img-sell" style="vertical-align:
text-bottom;">
Sell Bitcoin
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div class="list card">
<div class="item item-heading" ng-click="updateTransactions()">
Activity
</div>
<a class="item"
ng-if="pendingTransactions.data && !error"
ng-repeat="(id, tx) in pendingTransactions.data | orderObjectBy:'updated_at':true track by $index"
ng-click="coinbase.openTxModal(tx)">
<span class="item-note">
<div class="assertive" ng-show="tx.error || tx.status == 'error'">
Error
</div>
<div ng-show="!tx.error" class="m5t size-12 text-gray">
<div ng-show="!tx.error">
<div ng-show="tx.status == 'completed'">
<time ng-if="tx.created_at">{{tx.created_at | amTimeAgo}}</time>
</div>
<div ng-show="tx.status == 'pending'">
<span class="label outline gray radius text-gray text-info" ng-if="tx.status == 'pending'">Pending</span>
<span ng-if="tx.status == 'pending'">Pending</span>
</div>
</div>
</div>
<div class="large-1 medium-1 small-1 columns text-right">
<i class="icon-arrow-right3 size-18"></i>
</div>
</div>
</span>
<img class="left m10r" src="img/bought-pending.svg" alt="bought" width="24" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status != 'completed'">
<img class="left m10r" src="img/bought.svg" alt="bought" width="30" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status == 'completed'">
<img class="left m10r" src="img/sold-pending.svg" alt="sold" width="24" ng-show="tx.from && tx.type == 'send'">
<img class="left m10r" src="img/sold.svg" alt="sold" width="30" ng-show="!tx.from && tx.type == 'sell' && tx.status == 'completed'">
<h2>
<span ng-show="tx.type == 'sell' && tx.status == 'completed'">Sold</span>
<span ng-show="tx.type == 'send' && tx.to && tx.status == 'completed'">Bought</span>
</h2>
<p>
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}}
</p>
</a>
</div>
<div class="m10t text-center" ng-if="loading">
<ion-spinner class="spinner-dark" icon="lines"></ion-spinner>
</div>
</div>
<div class="extra-margin-bottom"></div>
</div>
</ion-content>
</ion-view>

View file

@ -1,22 +0,0 @@
<div
class="topbar-container"
ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Coinbase'; closeToHome = true">
</div>
<div class="content coinbase" ng-controller="coinbaseUriController as coinbase" ng-init="coinbase.checkCode()">
<div class="row m20t">
<div class="large-12 columns">
<div class="text-center">
<img src="img/coinbase-logo.png"
ng-click="index.updateCoinbase()" width="100">
</div>
<div class="m10t text-center" ng-show="coinbase.error">
<div class="notification m10b size-12 text-warning">{{coinbase.error}}</div>
<button class="outline dark-gray tiny round" ng-click="coinbase.submitOauthCode(coinbase.code)">Try again</button>
</div>
</div>
</div>
</div>

View file

@ -29,6 +29,7 @@
<span class="item-note" ng-if="!paymentExpired.value">{{remainingTimeStr.value}}</span>
<span class="item-note" ng-if="paymentExpired.value" ng-style="{'color': 'red'}" translate>Expired</span>
</div>
<div class="item">
<span class="label" ng-if="!isGlidera" translate>To</span>
<span class="label" ng-if="isGlidera == 'buy'">From</span>

View file

@ -1,18 +0,0 @@
<ion-modal-view ng-controller="coinbaseConfirmationController">
<div class="m20tp text-center">
<div class="row">
<h1 class="text-center m20b p20h">Are you sure you would like to log out of your Coinbase account?</h1>
<p class="text-gray p20h">You will need to log back in to buy or sell bitcoin in Copay.</p>
<div class="large-6 medium-6 small-6 columns">
<button class="button light-gray expand outline round" ng-click="cancel()">
<i class="fi-arrow-left"></i> <span class="tu">Back</span>
</button>
</div>
<div class="large-6 medium-6 small-6 columns">
<button class="button warning expand round" ng-click="ok()">
<span>Log out</span>
</button>
</div>
</div>
</div>
</ion-modal-view>

View file

@ -1,104 +1,88 @@
<ion-modal-view ng-controller="coinbaseTxDetailsController">
<ion-header-bar align-title="center" class="tab-bar" ng-style="{'background-color': '#2b71b1'}">
<div class="left-small">
<a ng-click="cancel()">
<i class="icon-arrow-left3 icon-back"></i>
<span class="text-back">Back</span>
</a>
<ion-header-bar align-title="center" class="bar-royal">
<button class="button button-clear" ng-click="close()">
Close
</button>
<div class="title">
Details
</div>
<h1 class="title ellipsis" translate>Details</h1>
</ion-header-bar>
<ion-content>
<div class="fix-modals-touch">
<div class="header-modal bg-gray text-center">
<div class="p20">
<img src="img/bought.svg" alt="bought" width="65" ng-show="(tx.type == 'buy' || (tx.type == 'send' && tx.to)) && tx.status == 'completed'">
<img src="img/bought-pending.svg" alt="bought" width="65"
ng-show="(tx.type == 'buy' || (tx.type == 'send' && tx.to)) && tx.status != 'completed'">
<img src="img/sold.svg" alt="bought" width="65" ng-show="tx.type == 'sell' && tx.status == 'completed'">
<img src="img/sold-pending.svg" alt="bought" width="65"
ng-show="(tx.type == 'sell' || (tx.type == 'send' && tx.from)) && tx.status != 'completed'">
</div>
<div ng-show="tx.status == 'completed'">
<span ng-show="tx.type == 'buy' || tx.type == 'send'">Bought</span>
<span ng-show="tx.type == 'sell'">Sold</span>
</div>
<div ng-show="tx.type == 'send' && (tx.to || tx.from) && tx.status != 'completed'">
<span ng-show="tx.to">Receiving purchased bitcoin</span>
<span ng-show="tx.from">Sending bitcoin to sell</span>
</div>
<div ng-show="(tx.type == 'sell' || tx.type == 'buy') && tx.status != 'completed'">
<span ng-show="tx.type == 'buy'">Buying bitcoin</span>
<span ng-show="tx.type == 'sell'">Selling bitcoin</span>
</div>
<div class="size-24 text-bold">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}}
</div>
<div class="label gray radius m10b">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.native_amount.amount.replace('-','')}}
{{tx.native_amount.currency}}
</div>
<div class="text-center m20v">
<div>
<img src="img/bought.svg" alt="bought" width="65" ng-show="(tx.type == 'buy' || (tx.type == 'send' && tx.to)) && tx.status == 'completed'">
<img src="img/bought-pending.svg" alt="bought" width="65"
ng-show="(tx.type == 'buy' || (tx.type == 'send' && tx.to)) && tx.status != 'completed'">
<img src="img/sold.svg" alt="bought" width="65" ng-show="tx.type == 'sell' && tx.status == 'completed'">
<img src="img/sold-pending.svg" alt="bought" width="65"
ng-show="(tx.type == 'sell' || (tx.type == 'send' && tx.from)) && tx.status != 'completed'">
</div>
<div class="m20b box-notification" ng-show="tx.error">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in tx.error.errors" ng-bind-html="err.message"></li>
</ul>
<div ng-show="tx.status == 'completed'">
<span ng-show="tx.type == 'buy' || tx.type == 'send'">Bought</span>
<span ng-show="tx.type == 'sell'">Sold</span>
</div>
<ul class="no-bullet size-14">
<li ng-show="tx.details && tx.status != 'pending'" class="line-b p10 oh">
<span class="text-gray">{{tx.details.title}}</span>
<span class="right">{{tx.details.subtitle}}</span>
</li>
<li class="line-b p10 oh">
<span class="text-gray">Status</span>
<span class="text-success right" ng-if="tx.status == 'completed'">Completed</span>
<span class="text-info right" ng-if="tx.status == 'pending'">Pending</span>
<span class="text-warning right" ng-if="tx.status == 'error'">Error</span>
</li>
<li ng-show="tx.created_at" class="line-b p10 oh">
<span class="text-gray">Date</span>
<span class="right">{{tx.created_at | amCalendar}}</span>
</li>
<li ng-show="tx.price_sensitivity" class="line-b p10 oh">
<span class="text-gray">Price Sensitivity</span>
<span class="right">{{tx.price_sensitivity.name}}</span>
</li>
<li ng-show="tx.sell_price_amount" class="line-b p10 oh">
<span class="text-gray">Sell Price</span>
<span class="right">{{tx.sell_price_amount}} {{tx.sell_price_currency}}</span>
</li>
<li ng-show="tx.description" class="line-b p10 oh">
<span class="text-gray" ng-show="tx.from && tx.type == 'send'">Sent bitcoin from</span>
<span class="text-gray" ng-show="tx.to && tx.type == 'send'">Receive bitcoin in</span>
<span class="right text-bold">{{tx.description}}</span>
</li>
</ul>
<div class="row m20t" ng-show="tx.status == 'error'">
<div class="columns">
<p class="text-center size-12 text-gray">
This action will remove the transaction.
</p>
<button class="button outline round dark-gray expand tiny"
ng-click="remove()">
<i class="fi-x"></i>
Remove
</button>
</div>
<div ng-show="tx.type == 'send' && (tx.to || tx.from) && tx.status != 'completed'">
<span ng-show="tx.to">Receiving purchased bitcoin</span>
<span ng-show="tx.from">Sending bitcoin to sell</span>
</div>
<div ng-show="(tx.type == 'sell' || tx.type == 'buy') && tx.status != 'completed'">
<span ng-show="tx.type == 'buy'">Buying bitcoin</span>
<span ng-show="tx.type == 'sell'">Selling bitcoin</span>
</div>
<div class="size-24 text-bold">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}}
</div>
<div class="size-12">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.native_amount.amount.replace('-','')}}
{{tx.native_amount.currency}}
</div>
<div class="extra-margin-bottom"></div>
</div>
<ul class="card list" ng-show="tx.error">
<li class="item" ng-repeat="err in tx.error.errors" ng-bind-html="err.message"></li>
</ul>
<ul class="list">
<li ng-show="tx.details && tx.status != 'pending'" class="item">
{{tx.details.title}}
<span class="item-note">{{tx.details.subtitle}}</span>
</li>
<li class="item">
Status
<span class="item-note">
<span class="balanced" ng-if="tx.status == 'completed'">Completed</span>
<span class="dark" ng-if="tx.status == 'pending'">Pending</span>
<span class="assertive" ng-if="tx.status == 'error'">Error</span>
</span>
</li>
<li ng-show="tx.created_at" class="item">
Date
<span class="item-note">{{tx.created_at | amCalendar}}</span>
</li>
<li ng-show="tx.price_sensitivity" class="item">
Price Sensitivity
<span class="item-note">{{tx.price_sensitivity.name}}</span>
</li>
<li ng-show="tx.sell_price_amount" class="item">
Sell Price
<span class="item-note">{{tx.sell_price_amount}} {{tx.sell_price_currency}}</span>
</li>
<li ng-show="tx.description" class="item">
<span ng-show="tx.from && tx.type == 'send'">Sent bitcoin from</span>
<span ng-show="tx.to && tx.type == 'send'">Receive bitcoin in</span>
<span class="item-note">{{tx.description}}</span>
</li>
<div ng-show="tx.status == 'error'" class="item item-divider"></div>
<div ng-show="tx.status == 'error'" class="item assertive" ng-click="remove()">
Remove transaction
</div>
</ul>
</ion-content>
</ion-modal-view>

View file

@ -1,60 +1,62 @@
<div
class="topbar-container"
ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Preferences'; goBackToState = 'coinbase'; noColor = true">
</div>
<ion-view>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>Preferences</ion-nav-title>
</ion-nav-bar>
<div class="content coinbase-preferences" ng-controller="preferencesCoinbaseController as coinbase">
<ion-content>
<ul ng-if="index.coinbaseAccount && !index.coinbaseError" class="no-bullet m0">
<h4 class="title m0">Account</h4>
<li>
<span>ID</span>
<span class="right text-gray enable_text_select">
{{index.coinbaseAccount.id}}
</span>
</li>
<li>
<span>Name</span>
<span class="right text-gray">
{{index.coinbaseAccount.name}}
</span>
</li>
<li>
<span>Balance</span>
<span class="right text-gray">
{{index.coinbaseAccount.balance.amount}} {{index.coinbaseAccount.balance.currency}}
</span>
</li>
<li>
<span>Native Balance</span>
<span class="right text-gray">
{{index.coinbaseAccount.native_balance.amount}} {{index.coinbaseAccount.native_balance.currency}}
</span>
</li>
<ul ng-if="coinbaseAccount && !error" class="list">
<div class="item item-divider">
Account
</div>
<li class="item">
<span>ID</span>
<span class="item-note">
{{coinbaseAccount.id}}
</span>
</li>
<li class="item">
<span>Name</span>
<span class="item-note">
{{coinbaseAccount.name}}
</span>
</li>
<li class="item">
<span>Balance</span>
<span class="item-note">
{{coinbaseAccount.balance.amount}} {{coinbaseAccount.balance.currency}}
</span>
</li>
<li class="item">
<span>Native Balance</span>
<span class="item-note">
{{coinbaseAccount.native_balance.amount}} {{coinbaseAccount.native_balance.currency}}
</span>
</li>
<h4 class="title m0">User Information</h4>
<li>
<span>ID</span>
<span class="right text-gray enable_text_select">
{{index.coinbaseUser.id}}
</span>
</li>
<li>
<span>Email</span>
<span class="right text-gray">
{{index.coinbaseUser.email}}
</span>
</li>
</ul>
<ul class="no-bullet m0">
<h4></h4>
<li ng-click="coinbase.revokeToken(index.coinbaseTestnet)">
<i class="icon-arrow-right3 size-24 right text-gray"></i>
<span class="text-warning">Log out</span>
</li>
</ul>
<h4></h4>
<div class="item item-divider">
User Information
</div>
<li class="item">
<span>ID</span>
<span class="item-note">
{{coinbaseUser.id}}
</span>
</li>
<li class="item">
<span>Email</span>
<span class="item-note">
{{coinbaseUser.email}}
</span>
</li>
</div>
<div class="extra-margin-bottom"></div>
<div class="item item-divider"></div>
<li class="item" ng-click="revokeToken()">
<span class="assertive">Log out</span>
</li>
</ul>
</ion-content>
</ion-view>

View file

@ -1,202 +1,146 @@
<div
class="topbar-container"
ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Sell'; goBackToState = 'coinbase'; noColor = true">
</div>
<ion-view id="coinbase" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>Sell bitcoin</ion-nav-title>
</ion-nav-bar>
<ion-content class="add-bottom-for-cta">
<!-- SELL -->
<div class="list" ng-if="sellRequestInfo">
<div class="content coinbase" ng-controller="sellCoinbaseController as sell">
<div class="row m20t" ng-show="sell.error || index.coinbaseError" ng-click="sell.error = null">
<div class="columns">
<div class="box-notification">
<ul class="no-bullet m0 size-12 text-warning">
<li ng-repeat="err in (sell.error.errors || index.coinbaseError.errors)" ng-bind-html="err.message"></li>
</ul>
</div>
</div>
</div>
<div class="row m20ti"
ng-show="index.coinbaseAccount && !sell.sellInfo && !sell.sendInfo">
<div class="columns">
<form
name="sellCoinbaseForm"
ng-submit="sell.depositFunds(index.coinbaseToken, index.coinbaseAccount)"
novalidate>
<div ng-show="!showPriceSensitivity">
<div
ng-if="index.coinbaseToken"
ng-init="sell.init(index.coinbaseTestnet)"
ng-click="openWalletsModal(sell.allWallets)">
<label>Copay Wallet</label>
<div class="input">
<input type="text" id="address" name="address" ng-disabled="sell.selectedWalletId"
ng-attr-placeholder="{{'Choose your source wallet'}}"
ng-model="sell.selectedWalletName" required>
<a class="postfix size-12 m0 text-gray">
<i class="icon-wallet size-18"></i>
</a>
</div>
</div>
<label>
Amount
<span
ng-if="index.coinbaseToken"
ng-init="sell.getPrice(index.coinbaseToken)"
ng-show="sell.sellPrice"
class="size-11 text-light right">
1 BTC <i class="icon-arrow-right"></i> {{sell.sellPrice.amount}} {{sell.sellPrice.currency}}
</span>
</label>
<div class="input">
<input ng-show="!showAlternative" type="number" id="amount" ignore-mouse-wheel
name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-minlength="0.00000001" ng-maxlength="10000000000"
ng-model="amount" autocomplete="off">
<input ng-show="showAlternative" type="number" id="fiat" ignore-mouse-wheel
name="fiat" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-model="fiat" autocomplete="off">
<a ng-show="!showAlternative" class="postfix button"
ng-click="showAlternative = true; amount = null">BTC</a>
<a ng-show="showAlternative" class="postfix button black"
ng-click="showAlternative = false; fiat = null">USD</a>
</div>
<div class="text-center text-gray size-12 m10b">
<span ng-show="!(amount || fiat)">
Enter the amount to get the exchange rate
</span>
<span ng-show="!sell.sellPrice && (amount || fiat)">
Not available
</span>
<span ng-show="sell.sellPrice && amount && !fiat">
~ {{sell.sellPrice.amount * amount | currency : 'USD ' : 2}}
</span>
</div>
<div class="text-center">
<i class="db fi-arrow-down size-24 m10v"></i>
</div>
<div ng-if="index.coinbaseToken" ng-init="sell.getPaymentMethods(index.coinbaseToken)">
<label>Deposit into</label>
<select
ng-model="selectedPaymentMethod.id"
ng-options="item.id as item.name for item in sell.paymentMethods">
</select>
</div>
<div class="input m20t">
<a href class="button black expand round"
ng-disabled=" (!amount && !fiat) || !sell.sellPrice.amount"
ng-style="{'background-color': '#2b71b1'}"
ng-click="showPriceSensitivity = true">Continue</a>
<div class="item head">
<div class="sending-label">
<img src="img/sell-bitcoin.svg" alt="sell bitcoin" width="35" class="item-img-sell">
<span>Selling</span>
</div>
<div class="amount-label">
<div class="amount">{{amountUnitStr}}</div>
<div class="alternative" ng-if="sellPrice">
<span ng-show="isFiat">{{sellRequestInfo.amount.amount}} {{sellRequestInfo.amount.currency}}</span>
@ ${{sellPrice.amount}} per BTC
</div>
</div>
</div>
<div ng-show="showPriceSensitivity">
<h1>Price Sensitivity</h1>
<p class="size-14 text-gray">
<div class="info">
<div class="item item-icon-right" ng-click="showWalletSelector()">
<div class="label">From</div>
<div class="wallet">
<i class="icon big-icon-svg">
<img src="img/icon-wallet.svg" ng-style="{'background-color': wallet.color}" class="bg">
</i>
{{wallet ? wallet.name : '...'}}
</div>
<i class="icon bp-arrow-right"></i>
</div>
<label class="item item-input item-select">
<div class="input-label">
Deposit into
</div>
<select ng-model="selectedPaymentMethodId.value"
ng-options="item.id as item.name for item in paymentMethods"
ng-change="sellRequest()">
</select>
</label>
<div class="item item-divider">
At what percentage lower price would you accept to sell?
</div>
<label class="item item-input item-select" ng-if="priceSensitivity">
<div class="input-label">Price Sensitivity</div>
<select
ng-model="selectedPriceSensitivity.data"
ng-options="item as item.name for item in priceSensitivity track by item.value">
</select>
</label>
<div class="item size-12" ng-if="selectedPriceSensitivity">
<div class="m10b">
Coinbase has not yet implemented an immediate method to sell bitcoin from a wallet. To make this sale, funds
will be sent to your Coinbase account, and sold when Coinbase accepts the transaction (usually one
hour).
</p>
<label>At what percentage lower price would you accept to sell?</label>
<select
ng-model="selectedPriceSensitivity"
ng-options="item as item.name for item in priceSensitivity track by item.value">
</select>
<p class="size-12 text-gray">
Estimated sale value: {{sell.sellPrice.amount * amount | currency : 'USD ' : 2}} <br>
Still sell if price fall until:
{{(sell.sellPrice.amount - (selectedPriceSensitivity.value / 100) * sell.sellPrice.amount) * amount | currency : 'USD ' : 2}}
</p>
<div class="input m20t row">
<div class="columns large-6 medium-6 small-6">
<a href class="button outline dark-gray expand round" ng-click="showPriceSensitivity = false">Back</a>
</div>
<div class="columns large-6 medium-6 small-6">
<input class="button black expand round"
ng-disabled="(!amount && !fiat) || !sell.sellPrice.amount"
ng-style="{'background-color': '#2b71b1'}"
type="submit" value="Confirm">
</div>
</div>
<div class="label" ng-if="sellRequestInfo">Estimated sale value:
<strong>
{{sellRequestInfo.total.amount | currency : '' : 2}}
{{sellRequestInfo.total.currency}}
</strong>
</div>
<div class="label" ng-if="sellRequestInfo">Still sell if price fall until:
<strong>
{{(sellRequestInfo.total.amount -
(selectedPriceSensitivity.data.value / 100) * sellRequestInfo.total.amount) | currency : '' : 2}}
{{sellRequestInfo.total.currency}}
</strong>
</div>
</div>
</form>
</div>
</div>
<div class="m20ti row" ng-show="sell.sendInfo && !sell.sellInfo && !sell.success">
<div class="columns">
<h1>Funds sent to Coinbase Account</h1>
<p class="size-12 text-gray">
The transaction is not yet confirmed, and will show as "Processing" in your Activity. The bitcoin sale will be completed automatically once it is confirmed by Coinbase.
</p>
<button class="m20t outline black round expand"
ng-style="{'background-color': '#2b71b1'}"
ng-click="$root.go('coinbase')">OK</button>
</div>
</div>
<div ng-show="sell.sellInfo && !sell.sendInfo && !sell.success">
<h4 class="title">Confirm transaction</h4>
<ul class="no-bullet m10t size-12 white">
<li class="line-b line-t p15">
<span class="m10 text-normal text-bold">Amount</span>
<span class="right text-gray">{{sell.sellInfo.amount.amount}} {{sell.sellInfo.amount.currency}}</span>
</li>
<li class="line-b oh p15">
<span class="m10 text-normal text-bold">Fees</span>
<span class="right text-gray">
<div ng-repeat="fee in sell.sellInfo.fees">
<b>{{fee.type}}</b> {{fee.amount.amount}} {{fee.amount.currency}}
</div>
</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Subtotal</span>
<span class="right text-gray">{{sell.sellInfo.subtotal.amount}} {{sell.sellInfo.subtotal.currency}}</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Total</span>
<span class="right text-gray">{{sell.sellInfo.total.amount}} {{sell.sellInfo.total.currency}}</span>
</li>
<li class="line-b p15">
<span class="m10 text-normal text-bold">Payout at</span>
<span class="right text-gray">{{sell.sellInfo.payout_at | amCalendar}}</span>
</li>
</ul>
<div class="row">
<div class="columns">
<button class="button black round expand"
ng-style="{'background-color': '#2b71b1'}"
ng-click="sell.confirmSell(index.coinbaseToken, index.coinbaseAccount, sell.sellInfo)"
>
Sell
</button>
<div class="item item-divider">
Transaction details
</div>
<div class="item">
Amount
<span class="item-note">
{{sellRequestInfo.subtotal.amount}} {{sellRequestInfo.subtotal.currency}}
</span>
</div>
<div class="item" ng-repeat="fee in sellRequestInfo.fees">
<span class="capitalized">
{{fee.type}} fee
</span>
<span class="item-note">
<span ng-if="fee.amount.amount != '0.00'">-</span>
{{fee.amount.amount}} {{fee.amount.currency}}
</span>
</div>
<div class="item">
Total to receive
<span class="item-note total-amount">
{{sellRequestInfo.total.amount}} {{sellRequestInfo.total.currency}}
</span>
</div>
</div>
</div>
</div>
<div class="m20t row text-center" ng-show="sell.success">
<div class="columns">
<h1>Sale initiated</h1>
<p class="text-gray">
A transfer has been initiated to your bank account and should arrive at {{sell.success.payout_at | amCalendar}}.
</p>
<button class="outline dark-gray round expand"
ng-click="$root.go('coinbase')">OK</button>
</ion-content>
<click-to-accept
ng-disabled="!selectedPaymentMethodId.value || !sellRequestInfo || !wallet"
ng-click="sellConfirm()"
ng-if="!isCordova && sellRequestInfo"
click-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="!selectedPaymentMethodId.value"
no-matching-wallet="!sellRequestInfo">
Confirm sale
</click-to-accept>
<slide-to-accept
ng-disabled="!selectedPaymentMethodId.value || !sellRequestInfo || !wallet"
ng-if="isCordova && sellRequestInfo"
slide-on-confirm="sellConfirm()"
slide-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="!selectedPaymentMethodId.value"
no-matching-wallet="!sellRequestInfo">
Slide to sell
</slide-to-accept>
<slide-to-accept-success
slide-success-show="sendStatus === 'success'"
slide-success-on-confirm="goBackHome()"
slide-success-hide-on-confirm="true">
<span>Funds sent to Coinbase Account</span>
<div class="m10 size-14">
The transaction is not yet confirmed, and will show as "Pending" in your Activity. The bitcoin sale will be completed automatically once it is confirmed by Coinbase.
</div>
</div>
</slide-to-accept-success>
</div>
<div class="extra-margin-bottom"></div>
<wallet-selector
wallet-selector-title="walletSelectorTitle"
wallet-selector-wallets="wallets"
wallet-selector-selected-wallet="wallet"
wallet-selector-show="showWallets"
wallet-selector-on-select="onWalletSelect">
</wallet-selector>
</ion-view>

View file

@ -124,10 +124,10 @@
<img src="img/glidera-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i>
</a>
<!-- disable coinbase for this release -->
<!-- <a ng-if="coinbaseEnabled" ui-sref="exchange.coinbase" class="item">
<img src="img/coinbase-logo.png" width="90"> TODO
</a> -->
<a ng-if="coinbaseEnabled" ui-sref="tabs.buyandsell.coinbase" class="item item-extra-padding item-sub item-icon-right">
<img src="img/coinbase-logo.png" width="90">
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
@ -156,7 +156,7 @@
<span translate>Add BitPay Visa&reg; Card</span>
<i class="icon bp-arrow-right"></i>
</a>
<a ng-if="!externalServices.BuyAndSell && (coinbaseEnabled || glideraEnabled)" ui-sref="tabs.buyandsell.glidera" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<a ng-if="!externalServices.BuyAndSell && (coinbaseEnabled || glideraEnabled)" ui-sref="tabs.buyandsell" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg icon-buy-bitcoin"></div>
</i>

View file

@ -133,6 +133,13 @@
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right"
ng-if="coinbaseEnabled && coinbaseToken"
ui-sref="tabs.preferences.coinbase">
<img src="img/coinbase-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i>
</a>
<div class="item item-divider"></div>
<a class="item item-icon-right item-icon-left" href ui-sref="tabs.advanced">