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

View file

@ -1,175 +1,210 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('buyCoinbaseController', angular.module('copayApp.controllers').controller('buyCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService) {
function($scope, $log, $ionicModal, $timeout, lodash, profileService, coinbaseService, addressService, ongoingProcess) {
var self = this;
this.init = function(testnet) { var amount;
self.allWallets = profileService.getWallets(testnet ? 'testnet' : 'livenet'); var currency;
var client = profileService.focusedClient; var showErrorAndBack = function(err) {
if (client) { $scope.sendStatus = '';
$timeout(function() { $log.error(err);
self.selectedWalletId = client.credentials.walletId; err = err.errors ? err.errors[0].message : err;
self.selectedWalletName = client.credentials.walletName; popupService.showAlert('Error', err, function() {
$scope.$apply(); $ionicHistory.goBack();
}, 100); });
};
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.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
coinbaseService.getPaymentMethods(token, function(err, p) { $scope.buyPrice = b.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) { if (err) {
self.error = err; ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return; 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) { if (pm.allow_buy) {
self.paymentMethods.push(pm); $scope.paymentMethods.push(pm);
} }
if (pm.allow_buy && pm.primary_buy) { 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) { $scope.buyRequest = function() {
var currency = 'USD'; ongoingProcess.set('connectingCoinbase', true);
coinbaseService.buyPrice(token, currency, function(err, b) { coinbaseService.init(function(err, res) {
if (err) return; if (err) {
self.buyPrice = b.data || null; ongoingProcess.set('connectingCoinbase', false);
}); showErrorAndBack(err);
}; return;
}
$scope.openWalletsModal = function(wallets) { var accessToken = res.accessToken;
self.error = null; var accountId = res.accountId;
$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;
var dataSrc = { var dataSrc = {
amount: amount, amount: amount,
currency: currency, currency: currency,
payment_method: $scope.selectedPaymentMethod.id || null payment_method: $scope.selectedPaymentMethodId.value,
quote: true
}; };
ongoingProcess.set('Sending request...', true); coinbaseService.buyRequest(accessToken, accountId, dataSrc, function(err, data) {
coinbaseService.buyRequest(token, accountId, dataSrc, function(err, data) { ongoingProcess.set('connectingCoinbase', false);
ongoingProcess.set('Sending request...', false);
if (err) { if (err) {
self.error = err; showErrorAndBack(err);
return; return;
} }
self.buyInfo = data.data; $scope.buyRequestInfo = data.data;
$timeout(function() {
$scope.$apply();
}, 100);
}); });
}; });
};
this.confirmBuy = function(token, account, buy) { $scope.buyConfirm = function() {
self.error = null; var message = 'Buy bitcoin for ' + amount + ' ' + currency;
var accountId = account.id; var okText = 'Confirm';
var buyId = buy.id; var cancelText = 'Cancel';
ongoingProcess.set('Buying Bitcoin...', true); popupService.showConfirm(null, message, okText, cancelText, function(ok) {
coinbaseService.buyCommit(token, accountId, buyId, function(err, b) { if (!ok) return;
ongoingProcess.set('Buying Bitcoin...', false);
ongoingProcess.set('buyingBitcoin', true, statusChangeHandler);
coinbaseService.init(function(err, res) {
if (err) { if (err) {
self.error = err; ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return; return;
} else { }
var tx = b.data.transaction; var accessToken = res.accessToken;
if (!tx) return; 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); $timeout(function() {
coinbaseService.getTransaction(token, accountId, tx.id, function(err, updatedTx) { coinbaseService.getTransaction(accessToken, accountId, tx.id, function(err, updatedTx) {
ongoingProcess.set('Fetching transaction...', false);
if (err) $log.debug(err);
addressService.getAddress(self.selectedWalletId, false, function(err, addr) {
if (err) { if (err) {
self.error = { ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
errors: [{ showError(err);
message: 'Could not create address'
}]
};
return; return;
} }
updatedTx.data['toAddr'] = addr; walletService.getAddress($scope.wallet, false, function(err, walletAddr) {
coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) { if (err) {
if (err) $log.debug(err); ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
if (updatedTx.data.status == 'completed') { showError(err);
self.sendToCopay(token, account, updatedTx.data); return;
} else {
self.success = updatedTx.data;
$timeout(function() {
$scope.$emit('Local/CoinbaseTx');
}, 1000);
} }
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) { $scope.showWalletSelector = function() {
self.error = null; $scope.walletSelectorTitle = 'Receive in';
var accountId = account.id; $scope.showWallets = true;
};
ongoingProcess.set('Sending funds to Copay...', true); $scope.onWalletSelect = function(wallet) {
var data = { $scope.wallet = wallet;
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.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'; 'use strict';
angular.module('copayApp.controllers').controller('coinbaseController', angular.module('copayApp.controllers').controller('coinbaseController', function($scope, $timeout, $ionicModal, $log, coinbaseService, lodash, platformInfo, ongoingProcess, popupService, externalLinkService) {
function($rootScope, $scope, $timeout, $ionicModal, profileService, configService, storageService, coinbaseService, lodash, platformInfo, ongoingProcess) {
var isNW = platformInfo.isNW; var isNW = platformInfo.isNW;
var isCordova = platformInfo.isCordova;
if (platformInfo.isCordova && StatusBar.isVisible) { var init = function() {
StatusBar.backgroundColorByHexString("#4B6178"); $scope.currency = coinbaseService.getAvailableCurrency();
} coinbaseService.getStoredToken(function(at) {
$scope.accessToken = at;
this.openAuthenticateWindow = function() {
var oauthUrl = this.getAuthenticateUrl(); // Update Access Token if necessary
if (!isNW) { $scope.loading = true;
$rootScope.openExternalLink(oauthUrl, '_system'); coinbaseService.init(function(err, data) {
} else { $scope.loading = false;
var self = this; if (err || lodash.isEmpty(data)) {
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);
if (err) { if (err) {
self.error = err; popupService.showAlert('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);
});
});
} }
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) { // Updating accessToken and accountId
$scope.tx = tx; $timeout(function() {
$scope.accessToken = data.accessToken;
$ionicModal.fromTemplateUrl('views/modals/coinbase-tx-details.html', { $scope.accountId = data.accountId;
scope: $scope, $scope.updateTransactions();
animation: 'slide-in-up' $scope.$apply();
}).then(function(modal) { }, 100);
$scope.coinbaseTxDetailsModal = modal;
$scope.coinbaseTxDetailsModal.show();
}); });
}; });
};
$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; return;
} }
ongoingProcess.set('creatingTx', true, onSendStatusChange); ongoingProcess.set('creatingTx', true, onSendStatusChange);
createTx(wallet, false, function(err, txp) { createTx(wallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false, onSendStatusChange); ongoingProcess.set('creatingTx', false, onSendStatusChange);
@ -577,12 +577,14 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$log.debug('statusChangeHandler: ', processName, showName, isOn); $log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( if (
( (
processName === 'broadcastingTx' || processName === 'broadcastingTx' ||
((processName === 'signingTx') && $scope.wallet.m > 1) || ((processName === 'signingTx') && $scope.wallet.m > 1) ||
(processName == 'sendingTx' && !$scope.wallet.canSign() && !$scope.wallet.isPrivKeyExternal()) (processName == 'sendingTx' && !$scope.wallet.canSign() && !$scope.wallet.isPrivKeyExternal())
) && !isOn) { ) && !isOn) {
$scope.sendStatus = 'success'; $scope.sendStatus = 'success';
$scope.$digest(); $timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) { } else if (showName) {
$scope.sendStatus = showName; $scope.sendStatus = showName;
} }
@ -831,7 +833,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
debounceCreate(count, dataSrc, onSendStatusChange); debounceCreate(count, dataSrc, onSendStatusChange);
} }
}, onSendStatusChange); }, onSendStatusChange);
} };
var debounceCreate = lodash.throttle(function(count, dataSrc) { var debounceCreate = lodash.throttle(function(count, dataSrc) {
debounceCreateGiftCard(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'; '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() { $scope.remove = function() {
coinbaseService.savePendingTransaction($scope.tx, { coinbaseService.setCredentials();
remove: true $scope.updateRequired = false;
}, function(err) { var message = 'Are you sure you want to remove this transaction?';
$rootScope.$emit('Local/CoinbaseTx'); popupService.showConfirm(null, message, null, null, function(ok) {
$scope.cancel(); if (!ok) {
return;
}
coinbaseService.savePendingTransaction($scope.tx, {
remove: true
}, function(err) {
$scope.updateRequired = true;
$scope.close();
});
}); });
}; };
$scope.cancel = function() { $scope.close = function() {
$scope.coinbaseTxDetailsModal.hide(); $scope.modal.hide().then(function() {
if ($scope.updateRequired) $scope.updateTransactions();
});
}; };
}); });

View file

@ -1,18 +1,41 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesCoinbaseController', angular.module('copayApp.controllers').controller('preferencesCoinbaseController', function($scope, $timeout, $state, $ionicHistory, lodash, ongoingProcess, popupService, coinbaseService) {
function($scope, $timeout, $ionicModal, applicationService, coinbaseService) {
this.revokeToken = function(testnet) { $scope.revokeToken = function() {
$scope.network = testnet ? 'testnet' : 'livenet'; 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.$on("$ionicView.enter", function(event, data){
scope: $scope, coinbaseService.setCredentials();
animation: 'slide-in-up' ongoingProcess.set('connectingCoinbase', true);
}).then(function(modal) { coinbaseService.init(function(err, data) {
$scope.coinbaseConfirmationModal = modal; if (err || lodash.isEmpty(data)) {
$scope.coinbaseConfirmationModal.show(); 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'; 'use strict';
angular.module('copayApp.controllers').controller('sellCoinbaseController', angular.module('copayApp.controllers').controller('sellCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, appConfigService, configService) {
function($rootScope, $scope, $log, $timeout, $ionicModal, lodash, profileService, coinbaseService, configService, walletService, fingerprintService, ongoingProcess, go) {
var self = this; var amount;
var client; var currency;
$scope.priceSensitivity = [ var showErrorAndBack = function(err) {
{ $scope.sendStatus = '';
value: 0.5, $log.error(err);
name: '0.5%' err = err.errors ? err.errors[0].message : err;
}, popupService.showAlert('Error', err, function() {
{ $ionicHistory.goBack();
value: 1, });
name: '1%' };
},
{
value: 2,
name: '2%'
},
{
value: 5,
name: '5%'
},
{
value: 10,
name: '10%'
}
];
$scope.selectedPriceSensitivity = $scope.priceSensitivity[1];
this.init = function(testnet) { var showError = function(err) {
self.allWallets = profileService.getWallets(testnet ? 'testnet' : 'livenet', 1); $scope.sendStatus = '';
$log.error(err);
err = err.errors ? err.errors[0].message : err;
popupService.showAlert('Error', err);
};
client = profileService.focusedClient; var publishAndSign = function (wallet, txp, onSendStatusChange, cb) {
if (client && client.credentials.m == 1) { if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
$timeout(function() { var err = 'No signing proposal: No private key';
self.selectedWalletId = client.credentials.walletId; $log.info(err);
self.selectedWalletName = client.credentials.walletName; return cb(err);
$scope.$apply(); }
}, 100);
}
};
this.getPaymentMethods = function(token) { walletService.publishAndSign(wallet, txp, function(err, txp) {
coinbaseService.getPaymentMethods(token, function(err, p) { if (err) return cb(err);
if (err) { return cb(null, txp);
self.error = err; }, onSendStatusChange);
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;
}
});
});
};
this.getPrice = function(token) { var checkTransaction = lodash.throttle(function(count, txp) {
var currency = 'USD'; $log.warn('Check if transaction has been received by Coinbase. Try ' + count + '/5');
coinbaseService.sellPrice(token, currency, function(err, s) { // TX amount in BTC
if (err) return; var satToBtc = 1 / 100000000;
self.sellPrice = s.data || null; var amountBTC = (txp.amount * satToBtc).toFixed(8);
}); coinbaseService.init(function(err, res) {
}; if (err) {
$log.error(err);
$scope.openWalletsModal = function(wallets) { checkTransaction(count, txp);
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';
return; 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; coinbaseService.getTransactions(accessToken, accountId, function(err, ctxs) {
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) {
if (err) { if (err) {
ongoingProcess.set('Creating Transaction...', false); $log.debug(err);
self.error = err; checkTransaction(count, txp);
return; 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; var statusChangeHandler = function (processName, showName, isOn) {
amount = parseInt((amount * 100000000).toFixed(0)); $log.debug('statusChangeHandler: ', processName, showName, isOn);
comment = 'Send funds to Coinbase Account: ' + account.name; 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({ outputs.push({
'toAddress': address, 'toAddress': toAddress,
'amount': amount, 'amount': amountSat,
'message': comment 'message': comment
}); });
var txp = { var txp = {
toAddress: address, toAddress: toAddress,
amount: amount, amount: amountSat,
outputs: outputs, outputs: outputs,
message: comment, message: comment,
payProUrl: null, payProUrl: null,
@ -189,73 +260,47 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
feeLevel: walletSettings.feeLevel || 'normal' feeLevel: walletSettings.feeLevel || 'normal'
}; };
walletService.createTx(client, txp, function(err, createdTxp) { walletService.createTx($scope.wallet, txp, function(err, ctxp) {
if (err) { if (err) {
$log.debug(err); ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
ongoingProcess.set('Creating Transaction...', false); showError(err);
self.error = {
errors: [{
message: 'Could not create transaction: ' + err.message
}]
};
$scope.$apply();
return; return;
} }
ongoingProcess.set('Creating Transaction...', false); $log.debug('Transaction created.');
$scope.$emit('Local/NeedsConfirmation', createdTxp, function(accept) { publishAndSign($scope.wallet, ctxp, function() {}, function(err, txSent) {
if (accept) { if (err) {
self.confirmTx(createdTxp, function(err, tx) { ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
ongoingProcess.clear(); showError(err);
if (err) { return;
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 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'; 'use strict';
angular.module('copayApp.controllers').controller('tabHomeController', 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 wallet;
var listeners = []; var listeners = [];
var notifications = []; var notifications = [];
@ -83,6 +83,10 @@ angular.module('copayApp.controllers').controller('tabHomeController',
var wallet = profileService.getWallet(walletId); var wallet = profileService.getWallet(walletId);
updateWallet(wallet); updateWallet(wallet);
if ($scope.recentTransactionsEnabled) getNotifications(); 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) { $rootScope.$on('Local/TxAction', function(e, walletId) {
$log.debug('Got action for wallet ' + walletId); $log.debug('Got action for wallet ' + walletId);

View file

@ -1,6 +1,6 @@
'use strict'; '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 updateConfig = function() {
var isCordova = platformInfo.isCordova; var isCordova = platformInfo.isCordova;
@ -26,6 +26,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.bitpayCardEnabled = config.bitpayCard.enabled; $scope.bitpayCardEnabled = config.bitpayCard.enabled;
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp; $scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
$scope.coinbaseEnabled = config.coinbase.enabled && !isWindowsPhoneApp;
if ($scope.bitpayCardEnabled) { if ($scope.bitpayCardEnabled) {
bitpayCardService.getBitpayDebitCards(function(err, cards) { 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', controller: 'glideraUriController',
templateUrl: 'views/glideraUri.html' 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', { .state('tabs.buyandsell.coinbase', {
url: '/coinbase', url: '/coinbase/:code',
templateUrl: 'views/coinbase.html' views: {
}) 'tab-home@tabs': {
.state('preferencesCoinbase', { controller: 'coinbaseController',
url: '/preferencesCoinbase', controllerAs: 'coinbase',
templateUrl: 'views/preferencesCoinbase.html' templateUrl: 'views/coinbase.html'
}) }
.state('buyCoinbase', { }
url: '/buycoinbase', })
templateUrl: 'views/buyCoinbase.html' .state('tabs.preferences.coinbase', {
}) url: '/coinbase',
.state('sellCoinbase', { views: {
url: '/sellcoinbase', 'tab-settings@tabs': {
templateUrl: 'views/sellCoinbase.html' 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'; '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 root = {};
var credentials = {}; var credentials = {};
var isCordova = platformInfo.isCordova; 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 = '' credentials.SCOPE = ''
+ 'wallet:accounts:read,' + 'wallet:accounts:read,'
+ 'wallet:addresses:read,' + 'wallet:addresses:read,'
@ -20,26 +60,78 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
+ 'wallet:transactions:send,' + 'wallet:transactions:send,'
+ 'wallet:payment-methods:read'; + 'wallet:payment-methods:read';
// NW has a bug with Window Object
if (isCordova) { if (isCordova) {
credentials.REDIRECT_URI = 'copay://coinbase'; credentials.REDIRECT_URI = coinbase.redirect_uri.mobile;
} else { } else {
credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'; credentials.REDIRECT_URI = coinbase.redirect_uri.desktop;
} }
if (network == 'testnet') { if (credentials.NETWORK == 'testnet') {
credentials.HOST = 'https://sandbox.coinbase.com'; credentials.HOST = coinbase.sandbox.host;
credentials.API = 'https://api.sandbox.coinbase.com'; credentials.API = coinbase.sandbox.api;
credentials.CLIENT_ID = '6cdcc82d5d46654c46880e93ab3d2a43c639776347dd88022904bd78cd067841'; credentials.CLIENT_ID = coinbase.sandbox.client_id;
credentials.CLIENT_SECRET = '228cb6308951f4b6f41ba010c7d7981b2721a493c40c50fd2425132dcaccce59'; credentials.CLIENT_SECRET = coinbase.sandbox.client_secret;
} }
else { else {
credentials.HOST = 'https://coinbase.com'; credentials.HOST = coinbase.production.host;
credentials.API = 'https://api.coinbase.com'; credentials.API = coinbase.production.api;
credentials.CLIENT_ID = window.coinbase_client_id; credentials.CLIENT_ID = coinbase.production.client_id;
credentials.CLIENT_SECRET = window.coinbase_client_secret; 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() { root.getOauthCodeUrl = function() {
return credentials.HOST return credentials.HOST
+ '/oauth/authorize?response_type=code&client_id=' + '/oauth/authorize?response_type=code&client_id='
@ -54,7 +146,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
root.getToken = function(code, cb) { root.getToken = function(code, cb) {
var req = { var req = {
method: 'POST', method: 'POST',
url: credentials.API + '/oauth/token', url: credentials.HOST + '/oauth/token',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
@ -71,18 +163,18 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) { $http(req).then(function(data) {
$log.info('Coinbase Authorization Access Token: SUCCESS'); $log.info('Coinbase Authorization Access Token: SUCCESS');
// Show pending task from the UI // Show pending task from the UI
storageService.setNextStep('BuyAndSell', true, function(err) {}); storageService.setNextStep('BuyAndSell', 'true', function(err) {});
return cb(null, data.data); _afterTokenReceived(data.data, cb);
}, function(data) { }, function(data) {
$log.error('Coinbase Authorization Access Token: ERROR ' + data.statusText); $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 = { var req = {
method: 'POST', method: 'POST',
url: credentials.API + '/oauth/token', url: credentials.HOST + '/oauth/token',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
@ -98,10 +190,58 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) { $http(req).then(function(data) {
$log.info('Coinbase Refresh Access Token: SUCCESS'); $log.info('Coinbase Refresh Access Token: SUCCESS');
return cb(null, data.data); _afterTokenReceived(data.data, cb);
}, function(data) { }, function(data) {
$log.error('Coinbase Refresh Access Token: ERROR ' + data.statusText); $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); return cb(null, data.data);
}, function(data) { }, function(data) {
$log.error('Coinbase Get Accounts: ERROR ' + data.statusText); $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) { root.getTransactions = function(token, accountId, cb) {
if (!token) return cb('Invalid Token'); if (!token) return cb('Invalid Token');
$http(_get('/accounts/' + accountId + '/transactions', token)).then(function(data) { $http(_get('/accounts/' + accountId + '/transactions', token)).then(function(data) {
@ -252,7 +403,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
amount: data.amount, amount: data.amount,
currency: data.currency, currency: data.currency,
payment_method: data.payment_method || null, 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) { $http(_post('/accounts/' + accountId + '/sells', token, data)).then(function(data) {
$log.info('Coinbase Sell Request: SUCCESS'); $log.info('Coinbase Sell Request: SUCCESS');
@ -278,7 +430,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
amount: data.amount, amount: data.amount,
currency: data.currency, currency: data.currency,
payment_method: data.payment_method || null, 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) { $http(_post('/accounts/' + accountId + '/buys', token, data)).then(function(data) {
$log.info('Coinbase Buy Request: SUCCESS'); $log.info('Coinbase Buy Request: SUCCESS');
@ -330,10 +483,13 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
}; };
// Pending transactions // Pending transactions
root.savePendingTransaction = function(ctx, opts, cb) { root.savePendingTransaction = function(ctx, opts, cb) {
var network = configService.getSync().coinbase.testnet ? 'testnet' : 'livenet'; _savePendingTransaction(ctx, opts, cb);
storageService.getCoinbaseTxs(network, function(err, oldTxs) { };
var _savePendingTransaction = function(ctx, opts, cb) {
storageService.getCoinbaseTxs(credentials.NETWORK, function(err, oldTxs) {
if (lodash.isString(oldTxs)) { if (lodash.isString(oldTxs)) {
oldTxs = JSON.parse(oldTxs); oldTxs = JSON.parse(oldTxs);
} }
@ -350,23 +506,200 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
} }
tx = JSON.stringify(tx); tx = JSON.stringify(tx);
storageService.setCoinbaseTxs(network, tx, function(err) { storageService.setCoinbaseTxs(credentials.NETWORK, tx, function(err) {
return cb(err); return cb(err);
}); });
}); });
}; };
root.getPendingTransactions = function(cb) { root.getPendingTransactions = function(coinbasePendingTransactions) {
var network = configService.getSync().coinbase.testnet ? 'testnet' : 'livenet'; storageService.getCoinbaseTxs(credentials.NETWORK, function(err, txs) {
storageService.getCoinbaseTxs(network, function(err, txs) { txs = txs ? JSON.parse(txs) : {};
var _txs = txs ? JSON.parse(txs) : {}; coinbasePendingTransactions.data = lodash.isEmpty(txs) ? null : txs;
return cb(err, _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) { root.updatePendingTransactions = lodash.throttle(function() {
storageService.removeCoinbaseToken(network, function() { $log.debug('Updating pending transactions...');
storageService.removeCoinbaseRefreshToken(network, function() { 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(); return cb();
}); });
}); });

View file

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

View file

@ -126,9 +126,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
url: data url: data
}); });
} else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) { } else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
return $state.go('uricoinbase', { var code = getParameterByName('code', data);
url: 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 // BitPayCard Authentication
} else if (data && data.indexOf(appConfigService.name + '://') === 0) { } else if (data && data.indexOf(appConfigService.name + '://') === 0) {

View file

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

View file

@ -1,21 +1,129 @@
.coinbase-preferences { #coinbase {
ul { $item-lateral-padding: 20px;
font-size: 14px; $item-vertical-padding: 10px;
background: white; $item-border-color: #EFEFEF;
li { $item-label-color: #6C6C6E;
padding: 16px 10px 16px 16px; @extend .deflash-blue;
border-bottom: 1px solid #E9E9EC;
.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> <span class="toggle-label" translate>Enable Glidera Service</span>
</ion-toggle> </ion-toggle>
<!-- disable coinbase for this release --> <ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled.value" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<!-- <ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<span class="toggle-label" translate>Enable Coinbase Service</span> <span class="toggle-label" translate>Enable Coinbase Service</span>
</ion-toggle> --> </ion-toggle>
<div class="item item-divider" translate>Wallet Operation</div> <div class="item item-divider" translate>Wallet Operation</div>

View file

@ -14,14 +14,14 @@
<ion-content scroll="false"> <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-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}"> <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"> <i class="icon big-icon-svg" ng-if="isWallet">
<img src="img/icon-wallet.svg" ng-style="{'background-color': toColor}" class="bg"/> <img src="img/icon-wallet.svg" ng-style="{'background-color': toColor}" class="bg"/>
</i> </i>
<span ng-if="!isWallet && !isGiftCard && !isGlidera"> <span ng-if="!isWallet && !isGiftCard">
<i class="icon big-icon-svg" ng-if="isChromeApp"> <i class="icon big-icon-svg" ng-if="isChromeApp">
<img src="img/contact-placeholder.svg" class="bg"/> <img src="img/contact-placeholder.svg" class="bg"/>
</i> </i>
@ -39,7 +39,8 @@
</div> </div>
</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="amount-bar oh">
<div class="title"> <div class="title">

View file

@ -1,169 +1,113 @@
<div <ion-view id="coinbase" hide-tabs>
class="topbar-container" <ion-nav-bar class="bar-royal">
ng-include="'views/includes/topbar.html'" <ion-nav-back-button>
ng-init="titleSection='Buy'; goBackToState = 'coinbase'; noColor = true"> </ion-nav-back-button>
</div> <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="item head">
<div class="sending-label">
<div class="row m20t" ng-show="buy.error || index.coinbaseError" ng-click="buy.error = null"> <img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="35" class="item-img-buy">
<div class="columns"> <span>Buying</span>
<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> </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 <div class="info">
<span <label class="item item-input item-select">
ng-if="index.coinbaseToken" <div class="input-label">
ng-init="buy.getPrice(index.coinbaseToken)" Payment Method
ng-show="buy.buyPrice" </div>
class="size-11 text-light right"> <select ng-model="selectedPaymentMethodId.value"
1 BTC <i class="icon-arrow-right"></i> {{buy.buyPrice.amount}} {{buy.buyPrice.currency}} ng-options="item.id as item.name for item in paymentMethods"
</span> ng-change="buyRequest()">
</select>
</label> </label>
<div class="input"> <div class="item item-icon-right" ng-click="showWalletSelector()">
<input ng-show="!showAlternative" type="number" id="amount" ignore-mouse-wheel <div class="label">Receive in</div>
name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}" <div class="wallet">
ng-minlength="0.00000001" ng-maxlength="10000000000" <i class="icon big-icon-svg">
ng-model="amount" autocomplete="off" ng-disabled="buy.loading"> <img src="img/icon-wallet.svg" ng-style="{'background-color': wallet.color}" class="bg">
</i>
<input ng-show="showAlternative" type="number" id="fiat" ignore-mouse-wheel {{wallet ? wallet.name : '...'}}
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> </div>
<i class="icon bp-arrow-right"></i>
</div> </div>
<div class="input m20t"> <div class="item item-divider">
<input class="button black expand round" Transaction details
ng-disabled="buy.loading || (!amount && !fiat) || !selectedPaymentMethod" </div>
ng-style="{'background-color': '#2b71b1'}" <div class="item">
type="submit" value="{{'Continue'}}"> 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> </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>
</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>
</div> </slide-to-accept-success>
</div> <wallet-selector
<div class="extra-margin-bottom"></div> 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-nav-bar>
<ion-content> <ion-content>
<ion-list> <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"> <ion-item class="item item-icon-right" ui-sref="tabs.buyandsell.glidera">
<img src="img/glidera-logo.png" width="90"> <img src="img/glidera-logo.png" width="90">
<i class="icon bp-arrow-right"></i> <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"> <ion-content>
<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>
<section class="right-small" ng-show="index.coinbaseAccount"> <div class="box-notification error" ng-show="error">
<a class="p10" href ui-sref="preferencesCoinbase"> <ul class="no-bullet m0 size-12">
<i class="fi-widget size-24"></i> <li ng-repeat="err in error.errors" ng-bind-html="err.message"></li>
</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>
</ul> </ul>
</div> </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 ng-if="!accessToken && !error" ng-init="showOauthForm = false">
<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 class="text-center m20v"> <div class="text-center m20v">
<img src="img/coinbase-logo.png" width="200"> <img src="img/coinbase-logo.png" width="200">
</div> </div>
@ -61,111 +21,112 @@
<p class="m20t text-gray size-12">Connect your Coinbase account to get started</p> <p class="m20t text-gray size-12">Connect your Coinbase account to get started</p>
<a class="button light-gray outline round small" <button class="button button-standard button-primary"
ng-click="coinbase.openAuthenticateWindow(); showOauthForm = true"> ng-click="coinbase.openAuthenticateWindow()">
Connect to Coinbase Connect to Coinbase
</a> </button>
<div> <div class="m20t">
<a href ng-click="showOauthForm = true" class="text-gray size-12"> <a href ng-click="showOauthForm = true" class="text-gray size-12">
Do you already have the Oauth Code? Do you already have the Oauth Code?
</a> </a>
</div> </div>
</div> </div>
<div class="text-center" ng-show="showOauthForm"> <div ng-show="showOauthForm">
<div class="text-left box-notification" ng-show="coinbase.error"> <div class="text-left box-notification" ng-show="coinbase.error">
<ul class="no-bullet m0 text-warning size-12"> <ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in coinbase.error.errors" ng-bind-html="err.message"></li> <li ng-repeat="err in coinbase.error.errors" ng-bind-html="err.message"></li>
</ul> </ul>
</div> </div>
<form name="oauthCodeForm" ng-submit="coinbase.submitOauthCode(code)" novalidate> <form name="oauthCodeForm" ng-submit="coinbase.submitOauthCode(code)" novalidate>
<label>OAuth Code</label> <div class="list settings-input-group">
<input type="text" ng-model="code" ng-disabled="coinbase.loading" <label class="item item-input item-stacked-label">
ng-attr-placeholder="{{'Paste the authorization code here'}}" required> <span class="input-label">OAuth Code</span>
<input <input type="text"
class="button expand round" ng-model="code"
ng-style="{'background-color': '#2b71b1'}" ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
type="submit" value="Get started" ng-disabled="oauthCodeForm.$invalid || coinbase.loading"> </label>
</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> </div>
</div> <input
<div class="large-4 medium-4 small-4 columns text-right"> class="button button-standard button-primary"
<div ng-show="tx.error" class="m5t size-12 text-warning"> 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 Error
</div> </div>
<div ng-show="!tx.error" class="m5t size-12 text-gray"> <div ng-show="!tx.error">
<div ng-show="tx.status == 'completed'"> <div ng-show="tx.status == 'completed'">
<time ng-if="tx.created_at">{{tx.created_at | amTimeAgo}}</time> <time ng-if="tx.created_at">{{tx.created_at | amTimeAgo}}</time>
</div> </div>
<div ng-show="tx.status == 'pending'"> <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>
</div> </span>
<div class="large-1 medium-1 small-1 columns text-right">
<i class="icon-arrow-right3 size-18"></i> <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'">
</div> <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'">
</div> <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> </div>
</ion-content>
<div class="extra-margin-bottom"></div> </ion-view>
</div>

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">{{remainingTimeStr.value}}</span>
<span class="item-note" ng-if="paymentExpired.value" ng-style="{'color': 'red'}" translate>Expired</span> <span class="item-note" ng-if="paymentExpired.value" ng-style="{'color': 'red'}" translate>Expired</span>
</div> </div>
<div class="item"> <div class="item">
<span class="label" ng-if="!isGlidera" translate>To</span> <span class="label" ng-if="!isGlidera" translate>To</span>
<span class="label" ng-if="isGlidera == 'buy'">From</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-modal-view ng-controller="coinbaseTxDetailsController">
<ion-header-bar align-title="center" class="tab-bar" ng-style="{'background-color': '#2b71b1'}"> <ion-header-bar align-title="center" class="bar-royal">
<div class="left-small"> <button class="button button-clear" ng-click="close()">
<a ng-click="cancel()"> Close
<i class="icon-arrow-left3 icon-back"></i> </button>
<span class="text-back">Back</span> <div class="title">
</a> Details
</div> </div>
<h1 class="title ellipsis" translate>Details</h1>
</ion-header-bar> </ion-header-bar>
<ion-content> <ion-content>
<div class="fix-modals-touch"> <div class="text-center m20v">
<div class="header-modal bg-gray text-center"> <div>
<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.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"
<img src="img/bought-pending.svg" alt="bought" width="65" ng-show="(tx.type == 'buy' || (tx.type == 'send' && tx.to)) && tx.status != 'completed'">
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.svg" alt="bought" width="65" ng-show="tx.type == 'sell' && tx.status == 'completed'"> <img src="img/sold-pending.svg" alt="bought" width="65"
<img src="img/sold-pending.svg" alt="bought" width="65" ng-show="(tx.type == 'sell' || (tx.type == 'send' && tx.from)) && tx.status != 'completed'">
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> </div>
<div ng-show="tx.status == 'completed'">
<span ng-show="tx.type == 'buy' || tx.type == 'send'">Bought</span>
<div class="m20b box-notification" ng-show="tx.error"> <span ng-show="tx.type == 'sell'">Sold</span>
<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> </div>
<div ng-show="tx.type == 'send' && (tx.to || tx.from) && tx.status != 'completed'">
<ul class="no-bullet size-14"> <span ng-show="tx.to">Receiving purchased bitcoin</span>
<span ng-show="tx.from">Sending bitcoin to sell</span>
<li ng-show="tx.details && tx.status != 'pending'" class="line-b p10 oh"> </div>
<span class="text-gray">{{tx.details.title}}</span> <div ng-show="(tx.type == 'sell' || tx.type == 'buy') && tx.status != 'completed'">
<span class="right">{{tx.details.subtitle}}</span> <span ng-show="tx.type == 'buy'">Buying bitcoin</span>
</li> <span ng-show="tx.type == 'sell'">Selling bitcoin</span>
</div>
<li class="line-b p10 oh"> <div class="size-24 text-bold">
<span class="text-gray">Status</span> <span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
<span class="text-success right" ng-if="tx.status == 'completed'">Completed</span> {{tx.amount.currency}}
<span class="text-info right" ng-if="tx.status == 'pending'">Pending</span> </div>
<span class="text-warning right" ng-if="tx.status == 'error'">Error</span> <div class="size-12">
</li> <span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.native_amount.amount.replace('-','')}}
{{tx.native_amount.currency}}
<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> </div>
<div class="extra-margin-bottom"></div>
</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-content>
</ion-modal-view> </ion-modal-view>

View file

@ -1,60 +1,62 @@
<div <ion-view>
class="topbar-container" <ion-nav-bar class="bar-royal">
ng-include="'views/includes/topbar.html'" <ion-nav-back-button>
ng-init="titleSection='Preferences'; goBackToState = 'coinbase'; noColor = true"> </ion-nav-back-button>
</div> <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"> <ul ng-if="coinbaseAccount && !error" class="list">
<h4 class="title m0">Account</h4> <div class="item item-divider">
<li> Account
<span>ID</span> </div>
<span class="right text-gray enable_text_select"> <li class="item">
{{index.coinbaseAccount.id}} <span>ID</span>
</span> <span class="item-note">
</li> {{coinbaseAccount.id}}
<li> </span>
<span>Name</span> </li>
<span class="right text-gray"> <li class="item">
{{index.coinbaseAccount.name}} <span>Name</span>
</span> <span class="item-note">
</li> {{coinbaseAccount.name}}
<li> </span>
<span>Balance</span> </li>
<span class="right text-gray"> <li class="item">
{{index.coinbaseAccount.balance.amount}} {{index.coinbaseAccount.balance.currency}} <span>Balance</span>
</span> <span class="item-note">
</li> {{coinbaseAccount.balance.amount}} {{coinbaseAccount.balance.currency}}
<li> </span>
<span>Native Balance</span> </li>
<span class="right text-gray"> <li class="item">
{{index.coinbaseAccount.native_balance.amount}} {{index.coinbaseAccount.native_balance.currency}} <span>Native Balance</span>
</span> <span class="item-note">
</li> {{coinbaseAccount.native_balance.amount}} {{coinbaseAccount.native_balance.currency}}
</span>
</li>
<h4 class="title m0">User Information</h4> <div class="item item-divider">
<li> User Information
<span>ID</span> </div>
<span class="right text-gray enable_text_select"> <li class="item">
{{index.coinbaseUser.id}} <span>ID</span>
</span> <span class="item-note">
</li> {{coinbaseUser.id}}
<li> </span>
<span>Email</span> </li>
<span class="right text-gray"> <li class="item">
{{index.coinbaseUser.email}} <span>Email</span>
</span> <span class="item-note">
</li> {{coinbaseUser.email}}
</ul> </span>
<ul class="no-bullet m0"> </li>
<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> <div class="item item-divider"></div>
<div class="extra-margin-bottom"></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 <ion-view id="coinbase" hide-tabs>
class="topbar-container" <ion-nav-bar class="bar-royal">
ng-include="'views/includes/topbar.html'" <ion-nav-back-button>
ng-init="titleSection='Sell'; goBackToState = 'coinbase'; noColor = true"> </ion-nav-back-button>
</div> <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="item head">
<div class="sending-label">
<div class="row m20t" ng-show="sell.error || index.coinbaseError" ng-click="sell.error = null"> <img src="img/sell-bitcoin.svg" alt="sell bitcoin" width="35" class="item-img-sell">
<div class="columns"> <span>Selling</span>
<div class="box-notification"> </div>
<ul class="no-bullet m0 size-12 text-warning"> <div class="amount-label">
<li ng-repeat="err in (sell.error.errors || index.coinbaseError.errors)" ng-bind-html="err.message"></li> <div class="amount">{{amountUnitStr}}</div>
</ul> <div class="alternative" ng-if="sellPrice">
</div> <span ng-show="isFiat">{{sellRequestInfo.amount.amount}} {{sellRequestInfo.amount.currency}}</span>
</div> @ ${{sellPrice.amount}} per BTC
</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> </div>
</div> </div>
</div>
<div ng-show="showPriceSensitivity"> <div class="info">
<h1>Price Sensitivity</h1>
<p class="size-14 text-gray"> <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 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 will be sent to your Coinbase account, and sold when Coinbase accepts the transaction (usually one
hour). hour).
</p> </div>
<label>At what percentage lower price would you accept to sell?</label> <div class="label" ng-if="sellRequestInfo">Estimated sale value:
<select <strong>
ng-model="selectedPriceSensitivity" {{sellRequestInfo.total.amount | currency : '' : 2}}
ng-options="item as item.name for item in priceSensitivity track by item.value"> {{sellRequestInfo.total.currency}}
</select> </strong>
<p class="size-12 text-gray"> </div>
Estimated sale value: {{sell.sellPrice.amount * amount | currency : 'USD ' : 2}} <br> <div class="label" ng-if="sellRequestInfo">Still sell if price fall until:
Still sell if price fall until: <strong>
{{(sell.sellPrice.amount - (selectedPriceSensitivity.value / 100) * sell.sellPrice.amount) * amount | currency : 'USD ' : 2}} {{(sellRequestInfo.total.amount -
</p> (selectedPriceSensitivity.data.value / 100) * sellRequestInfo.total.amount) | currency : '' : 2}}
{{sellRequestInfo.total.currency}}
<div class="input m20t row"> </strong>
<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>
</div> </div>
</form>
</div>
</div>
<div class="m20ti row" ng-show="sell.sendInfo && !sell.sellInfo && !sell.success"> <div class="item item-divider">
<div class="columns"> Transaction details
<h1>Funds sent to Coinbase Account</h1> </div>
<p class="size-12 text-gray"> <div class="item">
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. Amount
</p> <span class="item-note">
<button class="m20t outline black round expand" {{sellRequestInfo.subtotal.amount}} {{sellRequestInfo.subtotal.currency}}
ng-style="{'background-color': '#2b71b1'}" </span>
ng-click="$root.go('coinbase')">OK</button> </div>
</div> <div class="item" ng-repeat="fee in sellRequestInfo.fees">
</div> <span class="capitalized">
{{fee.type}} fee
<div ng-show="sell.sellInfo && !sell.sendInfo && !sell.success"> </span>
<h4 class="title">Confirm transaction</h4> <span class="item-note">
<span ng-if="fee.amount.amount != '0.00'">-</span>
<ul class="no-bullet m10t size-12 white"> {{fee.amount.amount}} {{fee.amount.currency}}
<li class="line-b line-t p15"> </span>
<span class="m10 text-normal text-bold">Amount</span> </div>
<span class="right text-gray">{{sell.sellInfo.amount.amount}} {{sell.sellInfo.amount.currency}}</span> <div class="item">
</li> Total to receive
<li class="line-b oh p15"> <span class="item-note total-amount">
<span class="m10 text-normal text-bold">Fees</span> {{sellRequestInfo.total.amount}} {{sellRequestInfo.total.currency}}
<span class="right text-gray"> </span>
<div ng-repeat="fee in sell.sellInfo.fees"> </div>
<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> </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" </ion-content>
ng-click="$root.go('coinbase')">OK</button>
<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>
</div> </slide-to-accept-success>
</div> <wallet-selector
<div class="extra-margin-bottom"></div> 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"/> <img src="img/glidera-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </a>
<!-- disable coinbase for this release --> <a ng-if="coinbaseEnabled" ui-sref="tabs.buyandsell.coinbase" class="item item-extra-padding item-sub item-icon-right">
<!-- <a ng-if="coinbaseEnabled" ui-sref="exchange.coinbase" class="item"> <img src="img/coinbase-logo.png" width="90">
<img src="img/coinbase-logo.png" width="90"> TODO <i class="icon bp-arrow-right"></i>
</a> --> </a>
</div> </div>
</div> </div>
@ -156,7 +156,7 @@
<span translate>Add BitPay Visa&reg; Card</span> <span translate>Add BitPay Visa&reg; Card</span>
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </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"> <i class="icon big-icon-svg">
<div class="bg icon-buy-bitcoin"></div> <div class="bg icon-buy-bitcoin"></div>
</i> </i>

View file

@ -133,6 +133,13 @@
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </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> <div class="item item-divider"></div>
<a class="item item-icon-right item-icon-left" href ui-sref="tabs.advanced"> <a class="item item-icon-right item-icon-left" href ui-sref="tabs.advanced">