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

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

View file

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

View file

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