diff --git a/src/js/controllers/tab-send.controller.js b/src/js/controllers/tab-send.controller.js
index 431b81c80..12da93cb8 100644
--- a/src/js/controllers/tab-send.controller.js
+++ b/src/js/controllers/tab-send.controller.js
@@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function tabSendController(
- bitcoinUriService
+ bitcoinUriService
, externalLinkService
, $scope
, $log
@@ -21,28 +21,26 @@ angular.module('copayApp.controllers').controller('tabSendController', function
, clipboardService
, incomingDataService
, moonPayService
+ , ionicToast
+ , opencapService
) {
+
var clipboardHasAddress = false;
var clipboardHasContent = false;
var originalList;
- var isBuyBitcoinAllowed = false;
$scope.displayBalanceAsFiat = true;
$scope.walletSelectorTitleForce = true;
- moonPayService.getCountryByIpAddress().then(function onGetCountryByIpAddress(user) {
- isBuyBitcoinAllowed = user.isAllowed;
- });
-
- $scope.addContact = function() {
- $state.go('tabs.send.addressbook');
+ $scope.addContact = function () {
+ $state.go('tabs.send.addressbook');
};
- $scope.pasteClipboard = function() {
+ $scope.pasteClipboard = function () {
if ($scope.clipboardHasAddress || $scope.clipboardHasContent) {
- clipboardService.readFromClipboard(function(text) {
- $scope.$apply(function() {
- $scope.formData.search = text;
- $scope.findContact($scope.formData.search);
+ clipboardService.readFromClipboard(function (text) {
+ $scope.$apply(function () {
+ $scope.formData.search = text;
+ $scope.findContact($scope.formData.search);
});
});
} else {
@@ -53,12 +51,12 @@ angular.module('copayApp.controllers').controller('tabSendController', function
}
};
- $scope.$on("$ionicView.enter", function(event, data) {
+ $scope.$on("$ionicView.enter", function (event, data) {
var stateParams = sendFlowService.state.getClone();
$scope.fromWallet = profileService.getWallet(stateParams.fromWalletId);
- clipboardService.readFromClipboard(function(text) {
+ clipboardService.readFromClipboard(function (text) {
if (text.length > 200) {
text = text.substring(0, 200);
}
@@ -82,33 +80,78 @@ angular.module('copayApp.controllers').controller('tabSendController', function
return;
}
updateHasFunds();
- updateContactsList(function() {
+ updateContactsList(function () {
updateList();
});
});
- $scope.findContact = function(search) {
+ $scope.closePopover = function () {
+ $scope.popover.hide();
+ };
+
+ $scope.$on('$destroy', function () {
+ $scope.popover.remove();
+ });
+
+ $scope.confirmOpenapAddress = function (address, coin) {
+ $scope.popover.remove();
+ var params = sendFlowService.state.getClone();
+ params.data = address;
+ params.coin = coin;
+ sendFlowService.start(params, function onError() {
+ return
+ });
+ }
+
+ $scope.resolveOpencapAlias = function (alias) {
+ opencapService.getAddress(alias)
+ .then(function (result) {
+ if (typeof $scope.fromWallet !== 'undefined') {
+ if ($scope.fromWallet.coin === 'bch') {
+ result.addresses = { bch: result.addresses.bch };
+ }
+ if ($scope.fromWallet.coin === 'btc') {
+ result.addresses = { btc: result.addresses.btc };
+ }
+ }
+
+ $scope.opencapAddresses = result.addresses;
+ $scope.opencapDnssec = result.dnssec;
+ $ionicPopover.fromTemplateUrl('templates/popoverOpencapSend.html', { scope: $scope })
+ .then(function onThen(popover) {
+ $scope.popover = popover;
+ popover.show(angular.element(document.querySelector('#search-input')))
+ });
+ })
+ .catch(function (status) {
+ // do nothing because they may have been typing
+ });
+ }
+
+ $scope.findContact = function (search) {
if (!search || search.length < 1) {
$scope.list = originalList;
- $timeout(function() {
+ $timeout(function () {
$scope.$apply();
});
return;
}
+ $scope.resolveOpencapAlias(search);
+
var params = sendFlowService.state.getClone();
params.data = search;
sendFlowService.start(params, function onError() {
- var result = lodash.filter(originalList, function(item) {
+ var result = lodash.filter(originalList, function (item) {
var val = item.name;
return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
});
-
+
$scope.list = result;
});
};
- var hasWallets = function() {
+ var hasWallets = function () {
$scope.walletsWithFunds = profileService.getWallets({
onlyComplete: true,
hasFunds: true
@@ -127,11 +170,11 @@ angular.module('copayApp.controllers').controller('tabSendController', function
$scope.hasWallets = lodash.isEmpty($scope.wallets) ? false : true;
};
- var updateHasFunds = function() {
+ var updateHasFunds = function () {
$scope.hasFunds = false;
var index = 0;
- lodash.each($scope.wallets, function(w) {
- walletService.getStatus(w, {}, function(err, status) {
+ lodash.each($scope.wallets, function (w) {
+ walletService.getStatus(w, {}, function (err, status) {
++index;
if (err && !status) {
@@ -146,7 +189,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function
if (index === $scope.wallets.length) {
$scope.checkingBalance = false;
- $timeout(function() {
+ $timeout(function () {
$scope.$apply();
});
}
@@ -154,26 +197,26 @@ angular.module('copayApp.controllers').controller('tabSendController', function
});
};
- var updateContactsList = function(cb) {
+ var updateContactsList = function (cb) {
var config = configService.getSync();
var defaults = configService.getDefaults();
- addressbookService.list(function(err, ab) {
+ addressbookService.list(function (err, ab) {
if (err) $log.error(err);
$scope.hasContacts = lodash.isEmpty(ab) ? false : true;
if (!$scope.hasContacts) return cb();
var completeContacts = [];
- lodash.each(ab, function(v, k) {
+ lodash.each(ab, function (v, k) {
completeContacts.push({
name: lodash.isObject(v) ? v.name : v,
address: k,
email: lodash.isObject(v) ? v.email : null,
recipientType: 'contact',
coin: v.coin,
- displayCoin: (v.coin == 'bch'
- ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias)
- : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase()
+ displayCoin: (v.coin == 'bch'
+ ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias)
+ : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase()
});
});
originalList = completeContacts;
@@ -181,19 +224,19 @@ angular.module('copayApp.controllers').controller('tabSendController', function
});
};
- var updateList = function() {
+ var updateList = function () {
$scope.list = lodash.clone(originalList);
- $timeout(function() {
+ $timeout(function () {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
- $scope.searchInFocus = function() {
+ $scope.searchInFocus = function () {
$scope.searchFocus = true;
};
- $scope.searchBlurred = function() {
+ $scope.searchBlurred = function () {
if ($scope.formData.search == null || $scope.formData.search.length === 0) {
$scope.searchFocus = false;
}
@@ -210,7 +253,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function
}
$log.debug('Got toAddress:' + toAddress + ' | ' + item.name);
-
+
var stateParams = sendFlowService.state.getClone();
stateParams.toAddress = toAddress;
stateParams.coin = item.coin;
@@ -218,7 +261,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function
});
};
- $scope.startWalletToWalletTransfer = function() {
+ $scope.startWalletToWalletTransfer = function () {
console.log('startWalletToWalletTransfer()');
var params = sendFlowService.state.getClone();
params.isWalletTransfer = true;
@@ -226,22 +269,18 @@ angular.module('copayApp.controllers').controller('tabSendController', function
}
// This could probably be enhanced refactoring the routes abstract states
- $scope.createWallet = function() {
- $state.go('tabs.home').then(function() {
+ $scope.createWallet = function () {
+ $state.go('tabs.home').then(function () {
$state.go('tabs.add.create-personal');
});
};
- $scope.buyBitcoin = function() {
- if (isBuyBitcoinAllowed) {
- moonPayService.start();
- } else {
- var os = platformInfo.isAndroid ? 'android' : platformInfo.isIOS ? 'ios' : 'desktop';
- externalLinkService.open('https://purchase.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os);
- }
+ $scope.buyBitcoin = function () {
+ var os = platformInfo.isAndroid ? 'android' : platformInfo.isIOS ? 'ios' : 'desktop';
+ externalLinkService.open('https://purchase.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os);
};
- $scope.$on("$ionicView.beforeEnter", function(event, data) {
+ $scope.$on("$ionicView.beforeEnter", function (event, data) {
console.log(data);
console.log('tab-send onBeforeEnter sendflow ', sendFlowService.state);
$scope.isIOS = platformInfo.isIOS && platformInfo.isCordova;
@@ -254,7 +293,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function
originalList = [];
hasWallets();
- configService.whenAvailable(function(_config) {
+ configService.whenAvailable(function (_config) {
$scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat';
});
@@ -263,4 +302,4 @@ angular.module('copayApp.controllers').controller('tabSendController', function
}
});
-});
\ No newline at end of file
+});
diff --git a/src/js/services/opencap.service.js b/src/js/services/opencap.service.js
new file mode 100644
index 000000000..b5b5797bb
--- /dev/null
+++ b/src/js/services/opencap.service.js
@@ -0,0 +1,132 @@
+'use strict';
+
+(function() {
+ angular.module('bitcoincom.services').factory('opencapService', function($q, $http) {
+ function getAddress(alias) {
+ let aliasData = validateAlias(alias);
+ if (aliasData.username === '' || aliasData.domain === '') {
+ return $q(function onQ(resolve, reject) {
+ return reject('Invalid OpenCAP alias');
+ });
+ }
+
+ let deferred = $q.defer();
+ $http
+ .get(`https://dns.google.com/resolve?name=_opencap._tcp.${aliasData.domain}&type=SRV`)
+ .then(function onThen(response) {
+ deferred.resolve(
+ parseSRV(response.data)
+ .then(function onThen(data){
+ return getAddresses(alias, data.host, data.dnssec)
+ })
+ .catch(function onCatch(error) {
+ return $q(function onQ(resolve, reject) {
+ return reject(error);
+ });
+ })
+ );
+ })
+ .catch(function onCatch(response) {
+ deferred.reject('Couldn\'t find srv record for the provided domain');
+ });
+ return deferred.promise;
+ }
+
+ function parseSRV(respData) {
+ return $q(function onQ(resolve, reject) {
+ let dnssec = respData.AD;
+
+ if (typeof respData.Answer === 'undefined') {
+ return reject('Error contacting google dns server, no srv data');
+ }
+ if (respData.Answer.length < 1) {
+ return reject('Error contacting google dns server, not enough srv data');
+ }
+
+ let record = respData.Answer[0].data.split(' ');
+ if (record.length != 4) {
+ return reject('Error contacting google dns server, improper srv data');
+ }
+
+ if (record[3].slice(-1) == '.') {
+ record[3] = record[3].substring(0, record[3].length - 1);
+ }
+
+ return resolve({ host: record[3], dnssec });
+ });
+ };
+
+ function getAddresses(alias, host, dnssec) {
+ let deferred = $q.defer();
+ $http
+ .get(`https://${host}/v1/addresses?alias=${alias}`)
+ .then(function onThen(response) {
+ deferred.resolve(parseAddresses(response.data, dnssec).then());
+ })
+ .catch(function onCatch(response) {
+ deferred.reject('Address not found for the specified alias');
+ });
+ return deferred.promise;
+ };
+
+ function parseAddresses(respData, dnssec) {
+ let addresses = {}
+ return $q(function onQ(resolve, reject) {
+ for (let i = 0; i < respData.length; i++) {
+ if (respData[i].address_type === 'undefined') {
+ continue;
+ }
+ if (respData[i].address === 'undefined') {
+ continue;
+ }
+ // Take the last BCH address we hit, shouldn't matter which one
+ if (respData[i].address_type == 200 || respData[i].address_type == 201 || respData[i].address_type == 202) {
+ addresses.bch = respData[i].address;
+ }
+ // Take the last BTC address we hit, shouldn't matter which one
+ if (respData[i].address_type == 100 || respData[i].address_type == 101) {
+ addresses.btc = respData[i].address;
+ }
+ }
+
+ if (addresses.btc === 'undefined' && addresses.btc === 'undefined'){
+ return reject('Error contacting opencap server, no response');
+ }
+
+ return resolve({addresses, dnssec});
+ });
+ };
+
+ function validateUsername(username) {
+ return /^[a-z0-9._-]{1,25}$/.test(username);
+ }
+
+ function validateDomain(username) {
+ return /^[a-z0-9.\-]+\.[a-z]{2,4}$/.test(username);
+ }
+
+ function validateAlias(alias) {
+ let splitAlias = alias.split('$');
+ if (splitAlias.length != 2) {
+ return { username: '', domain: '' };
+ }
+ let username = splitAlias[0];
+ let domain = splitAlias[1];
+
+ if (!validateUsername(username)) {
+ return { username: '', domain: '' };
+ }
+ if (!validateDomain(domain)) {
+ return { username: '', domain: '' };
+ }
+
+ return { username, domain };
+ }
+
+ var service = {
+ getAddress,
+ };
+
+ return service;
+ });
+})();
diff --git a/www/templates/popoverOpencapSend.html b/www/templates/popoverOpencapSend.html
new file mode 100644
index 000000000..9d6d2482a
--- /dev/null
+++ b/www/templates/popoverOpencapSend.html
@@ -0,0 +1,21 @@
+