Merge pull request #243 from Bitcoin-com/wallet/task/514

Wallet/task/514
This commit is contained in:
Jean-Baptiste Dominguez 2018-08-08 11:23:48 +09:00 committed by GitHub
commit a1250be73d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 3597 additions and 1196 deletions

View file

@ -268,5 +268,33 @@ div.onboarding-topic {
display: block; display: block;
float: left; float: left;
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
}
.bitpay-banner {
background: #1A3A8B;
padding: 10px;
box-shadow: 0px 5px 10px 0px #cccccc;
height: 5em;
}
.bitpay-logo {
display: block;
max-height: 100%;
width: 100%;
height: 4em;
}
.egifter-banner {
background: #1A3A8B;
padding: 10px;
box-shadow: 0px 5px 10px 0px #cccccc;
height: 5em;
text-align: center;
}
.egifter-logo {
max-height: 100%;
max-width: 100%;
height: 4em;
} }

View file

@ -2195,7 +2195,7 @@ msgid "Payment details"
msgstr "" msgstr ""
#: www/views/modals/paypro.html:6 #: www/views/modals/paypro.html:6
msgid "Payment request" msgid "Payment Request"
msgstr "" msgstr ""
#: www/views/mercadoLibreCards.html:22 #: www/views/mercadoLibreCards.html:22
@ -2667,6 +2667,7 @@ msgid "You can receive bitcoin from any wallet or service."
msgstr "" msgstr ""
#: www/views/tab-send.html:72 #: www/views/tab-send.html:72
#: www/views/shapeshift.html:23
msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin."
msgstr "" msgstr ""
@ -3128,6 +3129,26 @@ msgstr ""
msgid "Top up {{amountStr}} to debit card ({{cardLastNumber}})" msgid "Top up {{amountStr}} to debit card ({{cardLastNumber}})"
msgstr "" msgstr ""
#: www/views/shapeshift.html:30
msgid "Start ShapeShift"
msgstr ""
#: www/views/shapeshift.html:30
msgid "Exchange your BTC to BCH in minutes."
msgstr ""
#: www/views/shapeshift.html:30
msgid "To start the process you need to add funds to your wallet."
msgstr ""
#: www/views/shapeshift.html:30
msgid "he process is fast and you will receive the exchanged amount in your wallet."
msgstr ""
#: www/views/shapeshift.html:34
msgid "This service is provided by the third-party ShapeShift, who will charge a small fee for the service. The fee will be shown before you start the transaction."
msgstr ""
#: www/views/buyAmazon.html:61 #: www/views/buyAmazon.html:61
#: www/views/buyMercadoLibre.html:60 #: www/views/buyMercadoLibre.html:60
#: www/views/modals/wallet-balance.html:23 #: www/views/modals/wallet-balance.html:23
@ -3771,3 +3792,40 @@ msgstr ""
#: src/js/controllers/amount.js:49 #: src/js/controllers/amount.js:49
msgid "Address doesn\'t contain currency information, please make sure you are sending the correct currency." msgid "Address doesn\'t contain currency information, please make sure you are sending the correct currency."
msgstr "" msgstr ""
#: www/views/review.html:4
msgid "Review Transaction"
msgstr ""
#: src/js/controllers/review.controller.js:36
msgid "You are sending"
msgstr ""
#: src/js/controllers/review.controller.js:66
msgid "You are shifting"
msgstr ""
#: www/views/review.html:22
msgid "From:"
msgstr ""
#: www/views/review.html:36
msgid "To:"
msgstr ""
#: www/views/review.html:53
msgid "Add personal note"
msgstr ""
#: www/views/review.html:57
msgid "Personal note:"
msgstr ""
#: www/views/review.html:69
msgid "Less than 1 cent"
msgstr ""
#: src/js/services/incomingData.js:129
msgid "This invoice is no longer accepting payments"
msgstr ""

View file

@ -2,7 +2,7 @@
angular.module('copayApp.controllers').controller('amountController', amountController); angular.module('copayApp.controllers').controller('amountController', amountController);
function amountController(configService, $filter, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $stateParams, $timeout, txFormatService, platformInfo, popupService, profileService, walletService, $window) { function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) {
var vm = this; var vm = this;
vm.allowSend = false; vm.allowSend = false;
@ -11,18 +11,16 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
vm.alternativeUnit = ''; vm.alternativeUnit = '';
vm.amount = '0'; vm.amount = '0';
vm.availableFunds = ''; vm.availableFunds = '';
vm.fromWalletId = '';
// Use insufficient for logic, as when the amount is invalid, funds being // Use insufficient for logic, as when the amount is invalid, funds being
// either sufficent or insufficient doesn't make sense. // either sufficent or insufficient doesn't make sense.
vm.fundsAreInsufficient = false; vm.fundsAreInsufficient = false;
vm.globalResult = ''; vm.globalResult = '';
vm.hello = 'hi';
vm.isRequestingSpecificAmount = false; vm.isRequestingSpecificAmount = false;
vm.listComplete = false; vm.listComplete = false;
vm.lastUsedPopularList = []; vm.lastUsedPopularList = [];
vm.maxShapeshiftAmount = 0; vm.maxAmount = 0;
vm.minShapeshiftAmount = 0; vm.minAmount = 0;
vm.shapeshiftOrderId = ''; vm.thirdParty = false;
vm.unit = ''; vm.unit = '';
vm.changeUnit = changeUnit; vm.changeUnit = changeUnit;
@ -36,6 +34,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
vm.removeDigit = removeDigit; vm.removeDigit = removeDigit;
vm.save = save; vm.save = save;
vm.sendMax = sendMax; vm.sendMax = sendMax;
vm.errorMessage = '';
$scope.$on('$ionicView.beforeEnter', onBeforeEnter); $scope.$on('$ionicView.beforeEnter', onBeforeEnter);
$scope.$on('$ionicView.leave', onLeave); $scope.$on('$ionicView.leave', onLeave);
@ -44,33 +43,22 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
var LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT = 8; var LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT = 8;
var LENGTH_AFTER_COMMA_EXPRESSION_LIMIT = 8; var LENGTH_AFTER_COMMA_EXPRESSION_LIMIT = 8;
var _id;
var altCurrencyModal = null; var altCurrencyModal = null;
var altUnitIndex = 0; var altUnitIndex = 0;
var availableFundsInCrypto = ''; var availableFundsInCrypto = '';
var availableFundsInFiat = ''; var availableFundsInFiat = '';
var availableSatoshis = null; var availableSatoshis = null;
var availableUnits = []; var availableUnits = [];
var displayAddress = null;
var fiatCode; var fiatCode;
var fixedUnit;
var hasMaxAmount = true;
var isNW = platformInfo.isNW; var isNW = platformInfo.isNW;
var isAndroid = platformInfo.isAndroid; var isAndroid = platformInfo.isAndroid;
var isIos = platformInfo.isIOS; var isIos = platformInfo.isIOS;
var lastUsedAltCurrencyList = []; var lastUsedAltCurrencyList = [];
var nextStep = null; var passthroughParams = {};
var unitToSatoshi;
var recipientType = null;
var satToUnit; var satToUnit;
var showMenu = false;
var showWarningMessage = false;
var toAddress = '';
var toColor = null;
var toEmail = null;
var toName = null;
var unitDecimals; var unitDecimals;
var unitIndex = 0; var unitIndex = 0;
var unitToSatoshi;
var useSendMax = false; var useSendMax = false;
function onLeave() { function onLeave() {
@ -80,51 +68,43 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
function onBeforeEnter(event, data) { function onBeforeEnter(event, data) {
initCurrencies(); initCurrencies();
vm.hello = 'greetings';
if (data.stateParams.shapeshiftOrderId && data.stateParams.shapeshiftOrderId.length > 0) {
vm.minShapeshiftAmount = parseFloat(data.stateParams.minShapeshiftAmount);
vm.maxShapeshiftAmount = parseFloat(data.stateParams.maxShapeshiftAmount);
vm.shapeshiftOrderId = data.stateParams.shapeshiftOrderId;
}
// To get the wallet from with the new flow passthroughParams = data.stateParams;
console.log('stateParams:', data.stateParams);
vm.fromWalletId = data.stateParams.fromWalletId; vm.fromWalletId = data.stateParams.fromWalletId;
vm.toWalletId = data.stateParams.toWalletId;
vm.minAmount = parseFloat(data.stateParams.minAmount);
vm.maxAmount = parseFloat(data.stateParams.maxAmount);
if (data.stateParams.noPrefix) { if (passthroughParams.thirdParty) {
showWarningMessage = data.stateParams.noPrefix != 0; vm.thirdParty = JSON.parse(passthroughParams.thirdParty); // Parse stringified JSON-object
if (showWarningMessage) { if (vm.thirdParty) {
var message = 'Address doesn\'t contain currency information, please make sure you are sending the correct currency.'; if (vm.thirdParty.id === 'shapeshift') {
popupService.showAlert('', message, function() {}, 'Ok'); if (!vm.thirdParty.data) {
vm.thirdParty.data = {};
}
vm.thirdParty.data['fromWalletId'] = vm.fromWalletId;
vm.fromWallet = profileService.getWallet(vm.fromWalletId);
vm.toWallet = profileService.getWallet(vm.toWalletId);
shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) {
console.log(data);
vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum);
vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit);
});
}
} }
} }
vm.isRequestingSpecificAmount = !!data.stateParams.id; vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId;
var config = configService.getSync().wallet.settings;
// Go to... var config = configService.getSync().wallet.settings;
_id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID)
nextStep = data.stateParams.nextStep;
setAvailableUnits(); setAvailableUnits();
updateUnitUI(); updateUnitUI();
if ($ionicHistory.backView().stateName == 'tabs.receive') {
hasMaxAmount = false;
}
showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard');
recipientType = data.stateParams.recipientType || null;
toAddress = data.stateParams.toAddress;
displayAddress = data.stateParams.displayAddress;
toName = data.stateParams.toName;
toEmail = data.stateParams.toEmail;
toColor = data.stateParams.toColor;
if (!nextStep && !data.stateParams.toAddress) {
$log.error('Bad params at amount')
throw ('bad params');
}
var reNr = /^[1234567890\.]$/; var reNr = /^[1234567890\.]$/;
var reOp = /^[\*\+\-\/]$/; var reOp = /^[\*\+\-\/]$/;
@ -158,11 +138,6 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
resetAmount(); resetAmount();
// in SAT ALWAYS
if ($stateParams.toAmount) {
vm.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals);
}
processAmount(); processAmount();
$timeout(function() { $timeout(function() {
@ -174,11 +149,16 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
var configCache = configService.getSync(); var configCache = configService.getSync();
availableUnits = []; availableUnits = [];
var hasBCHWallets = profileService.getWallets({ var coinFromWallet = '';
coin: 'bch' if (passthroughParams.fromWalletId) {
}).length; var fromWallet = profileService.getWallet(passthroughParams.fromWalletId);
coinFromWallet = fromWallet.coin;
} else {
var toWallet = profileService.getWallet(passthroughParams.toWalletId);
coinFromWallet = toWallet.coin;
}
if (hasBCHWallets) { if (coinFromWallet === 'bch') {
availableUnits.push({ availableUnits.push({
name: 'Bitcoin Cash', name: 'Bitcoin Cash',
id: 'bch', id: 'bch',
@ -186,11 +166,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
}); });
}; };
var hasBTCWallets = profileService.getWallets({ if (coinFromWallet === 'btc') {
coin: 'btc'
}).length;
if (hasBTCWallets) {
availableUnits.push({ availableUnits.push({
name: 'Bitcoin', name: 'Bitcoin',
id: 'btc', id: 'btc',
@ -200,26 +176,6 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
unitIndex = 0; unitIndex = 0;
if (data.stateParams.coin) {
var coins = data.stateParams.coin.split(',');
var newAvailableUnits = [];
lodash.each(coins, function(c) {
var coin = lodash.find(availableUnits, {
id: c
});
if (!coin) {
$log.warn('Could not find desired coin:' + data.stateParams.coin)
} else {
newAvailableUnits.push(coin);
}
});
if (newAvailableUnits.length > 0) {
availableUnits = newAvailableUnits;
}
}
// currency have preference // currency have preference
var fiatName; var fiatName;
@ -241,25 +197,21 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
isFiat: true, isFiat: true,
}); });
if (data.stateParams.fixedUnit) {
fixedUnit = true;
}
unitIndex = lodash.findIndex(availableUnits, { unitIndex = lodash.findIndex(availableUnits, {
isFiat: true isFiat: true
}); });
altUnitIndex = 0; altUnitIndex = 0;
if (vm.fromWalletId) { if (passthroughParams.fromWalletId) {
var fromWallet = profileService.getWallet(vm.fromWalletId); var fromWallet = profileService.getWallet(passthroughParams.fromWalletId);
updateAvailableFundsFromWallet(fromWallet); updateAvailableFundsFromWallet(fromWallet);
} }
}; };
}; };
function goBack() { function goBack() {
if (vm.shapeshiftOrderId) { if (vm.thirdParty && vm.thirdParty.id === 'shapeshift') {
$state.go('tabs.send').then(function() { $state.go('tabs.send').then(function() {
$ionicHistory.clearHistory(); $ionicHistory.clearHistory();
$state.go('tabs.home').then(function() { $state.go('tabs.home').then(function() {
@ -302,8 +254,6 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
vm.amount = '0'; vm.amount = '0';
if (fixedUnit) return;
if (!(availableUnits[unitIndex].isFiat && availableUnits.length > 2 && altUnitIndex == 0)) { if (!(availableUnits[unitIndex].isFiat && availableUnits.length > 2 && altUnitIndex == 0)) {
unitIndex++; unitIndex++;
if (unitIndex >= availableUnits.length) unitIndex = 0; if (unitIndex >= availableUnits.length) unitIndex = 0;
@ -397,6 +347,8 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
var formatedValue = format(vm.amount); var formatedValue = format(vm.amount);
var result = evaluate(formatedValue); var result = evaluate(formatedValue);
var amountInCrypto = 0;
if (lodash.isNumber(result)) { if (lodash.isNumber(result)) {
vm.globalResult = isExpression(vm.amount) ? '= ' + processResult(result) : ''; vm.globalResult = isExpression(vm.amount) ? '= ' + processResult(result) : '';
@ -404,16 +356,17 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
var a = fromFiat(result); var a = fromFiat(result);
if (a) { if (a) {
amountInCrypto = a;
var amountInSatoshis = a * unitToSatoshi; var amountInSatoshis = a * unitToSatoshi;
vm.fundsAreInsufficient = !!vm.fromWalletId vm.fundsAreInsufficient = !!passthroughParams.fromWalletId
&& availableSatoshis !== null && availableSatoshis !== null
&& availableSatoshis < amountInSatoshis; && availableSatoshis < amountInSatoshis;
vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true);
vm.allowSend = lodash.isNumber(a) vm.allowSend = lodash.isNumber(a)
&& a > 0 && a > 0
&& (!vm.shapeshiftOrderId && (!vm.minAmount || a >= vm.minAmount)
|| (a >= vm.minShapeshiftAmount && a <= vm.maxShapeshiftAmount)) && (!vm.maxAmount || a <= vm.maxAmount)
&& !vm.fundsAreInsufficient; && !vm.fundsAreInsufficient;
} else { } else {
if (result) { if (result) {
@ -425,21 +378,39 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
vm.allowSend = false; vm.allowSend = false;
} }
} else { } else {
vm.fundsAreInsufficient = vm.fromWalletId amountInCrypto = result;
vm.fundsAreInsufficient = passthroughParams.fromWalletId
&& availableSatoshis !== null && availableSatoshis !== null
&& availableSatoshis < result * unitToSatoshi; && availableSatoshis < result * unitToSatoshi;
vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result));
vm.allowSend = lodash.isNumber(result) vm.allowSend = lodash.isNumber(result)
&& result > 0 && result > 0
&& (!vm.shapeshiftOrderId && (!vm.minAmount || result >= vm.minAmount)
|| (result >= vm.minShapeshiftAmount && result <= vm.maxShapeshiftAmount)) && (!vm.maxAmount || result <= vm.maxAmount)
&& !vm.fundsAreInsufficient; && !vm.fundsAreInsufficient;
} }
} else { } else {
vm.fundsAreInsufficient = false; vm.fundsAreInsufficient = false;
} }
if (vm.fundsAreInsufficient) {
vm.errorMessage = gettextCatalog.getString('Not enough available funds');
} else if (amountInCrypto && vm.thirdParty && vm.thirdParty.id === 'shapeshift') {
if (amountInCrypto < vm.minAmount) {
vm.errorMessage = gettextCatalog.getString('Amount is below minimum');
} else if (amountInCrypto > vm.maxAmount) {
vm.errorMessage = gettextCatalog.getString('Amount is above maximum');
} else {
vm.errorMessage = '';
}
} else {
vm.errorMessage = '';
}
}; };
function processResult(val) { function processResult(val) {
@ -479,88 +450,36 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
}; };
function finish() { function finish() {
var unit = availableUnits[unitIndex];
var uiAmount = evaluate(format(vm.amount));
function finish() { var satoshis = 0;
var unit = availableUnits[unitIndex]; if (unit.isFiat) {
var _amount = evaluate(format(vm.amount)); satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0);
var coin = unit.id;
if (unit.isFiat) {
coin = availableUnits[altUnitIndex].id;
}
if (nextStep) {
$state.transitionTo(nextStep, {
id: _id,
amount: useSendMax ? null : _amount,
currency: unit.id.toUpperCase(),
coin: coin,
useSendMax: useSendMax,
fromWalletId: vm.fromWalletId
});
} else {
var amount = _amount;
if (unit.isFiat) {
amount = (fromFiat(amount) * unitToSatoshi).toFixed(0);
} else {
amount = (amount * unitToSatoshi).toFixed(0);
}
var confirmData = {
recipientType: recipientType,
toAmount: amount,
toAddress: toAddress,
displayAddress: displayAddress || toAddress,
toName: toName,
toEmail: toEmail,
toColor: toColor,
coin: coin,
useSendMax: useSendMax,
fromWalletId: vm.fromWalletId
};
if (vm.shapeshiftOrderId) {
var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/';
shapeshiftOrderUrl += vm.shapeshiftOrderId;
confirmData.description = shapeshiftOrderUrl;
confirmData.fromWalletId = vm.fromWalletId;
if (confirmData.useSendMax) {
var wallet = lodash.find(profileService.getWallets({ coin: coin }),
function(w) {
return w.id == vm.fromWalletId;
});
var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4));
if (balance < vm.minShapeshiftAmount * 1.04) {
confirmData.useSendMax = false;
confirmData.toAmount = vm.minShapeshiftAmount * unitToSatoshi;
} else if (balance > vm.maxShapeshiftAmount) {
confirmData.useSendMax = false;
confirmData.toAmount = vm.maxShapeshiftAmount * unitToSatoshi * 0.99;
}
}
}
$state.transitionTo('tabs.send.confirm', confirmData);
}
useSendMax = null;
}
if (showWarningMessage) {
var u = vm.unit == 'BCH' || vm.unit == 'BTC' ? vm.unit : vm.alternativeUnit;
var message = 'Are you sure you want to send ' + u.toUpperCase() + '?';
popupService.showConfirm(message, '', 'Yes', 'No', function(res) {
if (!res) {
useSendMax = null;
return;
};
finish();
});
} else { } else {
finish(); satoshis = (uiAmount * unitToSatoshi).toFixed(0);
} }
var confirmData = {
amount: useSendMax ? undefined : satoshis,
fromWalletId: passthroughParams.fromWalletId,
sendMax: useSendMax,
toAddress: passthroughParams.toAddress,
toWalletId: passthroughParams.toWalletId
};
if (vm.thirdParty) {
confirmData['thirdParty'] = JSON.stringify(this.thirdParty);
}
console.log('confirmData:', confirmData);
if (!confirmData.fromWalletId) {
$state.transitionTo('tabs.paymentRequest.confirm', confirmData);
} else {
$state.transitionTo('tabs.send.review', confirmData);
$scope.useSendMax = null;
}
}; };
@ -672,7 +591,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i
}; };
function updateAvailableFundsStringIfNeeded() { function updateAvailableFundsStringIfNeeded() {
if (vm.fromWalletId && availableSatoshis !== null) { if (passthroughParams.fromWalletId && availableSatoshis !== null) {
availableFundsInFiat = ''; availableFundsInFiat = '';
vm.availableFunds = availableFundsInCrypto; vm.availableFunds = availableFundsInCrypto;
var coin = availableUnits[altUnitIndex].isFiat ? availableUnits[unitIndex].id : availableUnits[altUnitIndex].id; var coin = availableUnits[altUnitIndex].isFiat ? availableUnits[unitIndex].id : availableUnits[altUnitIndex].id;

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, ionicToast, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification, externalLinkService, firebaseEventsService, soundService, clipboardService) { angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, $ionicLoading, ionicToast, addressbookService, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bitcoinCashJsService, bwcError, txConfirmNotification, externalLinkService, firebaseEventsService, soundService, clipboardService) {
var countDown = null; var countDown = null;
var FEE_TOO_HIGH_LIMIT_PER = 15; var FEE_TOO_HIGH_LIMIT_PER = 15;
@ -11,16 +11,10 @@ angular.module('copayApp.controllers').controller('confirmController', function(
// Config Related values // Config Related values
var config = configService.getSync(); var config = configService.getSync();
var walletConfig = config.wallet; var walletConfig = config.wallet;
var unitToSatoshi = walletConfig.settings.unitToSatoshi;
var unitDecimals = walletConfig.settings.unitDecimals;
var satToUnit = 1 / unitToSatoshi;
var configFeeLevel = walletConfig.settings.feeLevel ? walletConfig.settings.feeLevel : 'normal'; var configFeeLevel = walletConfig.settings.feeLevel ? walletConfig.settings.feeLevel : 'normal';
// Platform info // Platform info
var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova; var isCordova = platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
//custom fee flag //custom fee flag
var usingCustomFee = false; var usingCustomFee = false;
@ -56,7 +50,6 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$ionicConfig.views.swipeBackEnabled(false); $ionicConfig.views.swipeBackEnabled(false);
}); });
function exitWithError(err) { function exitWithError(err) {
$log.info('Error setting wallet selector:' + err); $log.info('Error setting wallet selector:' + err);
popupService.showAlert(gettextCatalog.getString(), bwcError.msg(err), function() { popupService.showAlert(gettextCatalog.getString(), bwcError.msg(err), function() {
@ -79,112 +72,108 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}); });
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) { var setWalletSelector = function(coin, network, minAmount, cb) {
function setWalletSelector(coin, network, minAmount, cb) { // no min amount? (sendMax) => look for no empty wallets
minAmount = minAmount || 1;
// no min amount? (sendMax) => look for no empty wallets $scope.wallets = profileService.getWallets({
minAmount = minAmount || 1; onlyComplete: true,
network: network,
coin: coin
});
$scope.wallets = profileService.getWallets({ if (tx.fromWalletId) {
onlyComplete: true, $scope.wallets = lodash.filter($scope.wallets, function (w) {
network: network, return w.id == tx.fromWalletId;
coin: coin
}); });
if (tx.fromWalletId) {
$scope.wallets = lodash.filter($scope.wallets, function(w) {
return w.id == tx.fromWalletId;
});
}
if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet(gettextCatalog.getString('No wallets available'), true);
return cb();
}
var filteredWallets = [];
var index = 0;
var walletsUpdated = 0;
lodash.each($scope.wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) {
if (err || !status) {
$log.error(err);
} else {
walletsUpdated++;
w.status = status;
if (!status.availableBalanceSat)
$log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > minAmount) {
filteredWallets.push(w);
}
}
if (++index == $scope.wallets.length) {
if (!walletsUpdated)
return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) {
setNoWallet(gettextCatalog.getString('Insufficient confirmed funds'), true);
}
$scope.wallets = lodash.clone(filteredWallets);
return cb();
}
});
});
};
// Setup $scope
var B = data.stateParams.coin == 'bch' ? bitcoreCash : bitcore;
var networkName;
try {
networkName = (new B.Address(data.stateParams.toAddress)).network.name;
} catch(e) {
var message = gettextCatalog.getString('Invalid address');
var backText = gettextCatalog.getString('Go back');
var learnText = gettextCatalog.getString('Learn more');
popupService.showConfirm(null, message, backText, learnText, function(back) {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$state.go('tabs.send').then(function() {
$ionicHistory.clearHistory();
if (!back) {
var url = 'https://support.bitpay.com/hc/en-us/articles/115004671663';
externalLinkService.open(url);
}
});
});
return;
} }
if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet(gettextCatalog.getString('No wallets available'), true);
return cb();
}
var filteredWallets = [];
var index = 0;
var walletsUpdated = 0;
lodash.each($scope.wallets, function (w) {
walletService.getStatus(w, {}, function (err, status) {
if (err || !status) {
$log.error(err);
} else {
walletsUpdated++;
w.status = status;
if (!status.availableBalanceSat)
$log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > minAmount) {
filteredWallets.push(w);
}
}
if (++index == $scope.wallets.length) {
if (!walletsUpdated)
return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) {
setNoWallet(gettextCatalog.getString('Insufficient confirmed funds'), true);
}
$scope.wallets = lodash.clone(filteredWallets);
return cb();
}
});
});
};
$scope.getContacts = function(addr) {
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) {
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()
});
});
return cb();
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); // Wallet to send from
// Grab stateParams // Grab stateParams
tx = { tx = {
toAmount: parseInt(data.stateParams.toAmount), amount: parseInt(data.stateParams.amount),
sendMax: data.stateParams.useSendMax == 'true' ? true : false, sendMax: data.stateParams.useSendMax == 'true' ? true : false,
fromWalletId: data.stateParams.fromWalletId, fromWalletId: data.stateParams.fromWalletId,
toAddress: data.stateParams.toAddress, toAddress: data.stateParams.toAddress,
displayAddress: data.stateParams.displayAddress,
description: data.stateParams.description,
paypro: data.stateParams.paypro,
feeLevel: configFeeLevel, feeLevel: configFeeLevel,
spendUnconfirmed: walletConfig.spendUnconfirmed, spendUnconfirmed: walletConfig.spendUnconfirmed,
// Vanity tx info (not in the real tx) // Vanity tx info (not in the real tx)
recipientType: data.stateParams.recipientType || null, recipientType: $scope.recipientType || null,
toName: data.stateParams.toName, toName: null,
toEmail: data.stateParams.toEmail, toEmail: null,
toColor: data.stateParams.toColor, toColor: null,
network: networkName, network: false,
coin: data.stateParams.coin, coin: $scope.fromWallet.coin,
txp: {}, txp: {},
}; };
@ -193,18 +182,71 @@ angular.module('copayApp.controllers').controller('confirmController', function(
tx.feeRate = parseInt(data.stateParams.requiredFeeRate); tx.feeRate = parseInt(data.stateParams.requiredFeeRate);
} }
if (tx.coin && tx.coin == 'bch') { if (tx.coin && tx.coin === 'bch') {
tx.feeLevel = 'normal'; tx.feeLevel = 'normal';
} }
var B = data.stateParams.coin === 'bch' ? bitcoreCash : bitcore;
var networkName;
$scope.recipientType = null;
try {
if (data.stateParams.toWalletId) { // There is a toWalletId, so we presume this is a wallet-to-wallet transfer
$scope.recipientType = 'wallet'; // set transaction type to wallet-to-wallet
$ionicLoading.show();
var toWallet = profileService.getWallet(data.stateParams.toWalletId);
tx.toColor = toWallet.color;
tx.toName = toWallet.name;
// We need an address to send to, so we ask the walletService to create a new address for the toWallet.
walletService.getAddress(toWallet, true, function (err, addr) {
$ionicLoading.hide();
tx.toAddress = addr;
networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName;
setupTx(tx);
});
} else { // This is a Wallet-to-address transfer
networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName;
setupTx(tx);
}
} catch (e) {
var message = gettextCatalog.getString('Invalid address');
popupService.showAlert(null, message, function () {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$state.go('tabs.send').then(function () {
$ionicHistory.clearHistory();
});
});
return;
}
});
var setupTx = function(tx) {
if (tx.coin === 'bch') {
tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr;
} else {
tx.displayAddress = entry.address;
}
addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact
if (!err && addr) {
tx.toName = addr.name;
tx.toEmail = addr.email;
tx.recipientType = 'contact';
}
});
// Other Scope vars // Other Scope vars
$scope.isCordova = isCordova; $scope.isCordova = isCordova;
$scope.isWindowsPhoneApp = isWindowsPhoneApp;
$scope.showAddress = false; $scope.showAddress = false;
$scope.walletSelectorTitle = gettextCatalog.getString('Send from'); $scope.walletSelectorTitle = gettextCatalog.getString('Send from');
setWalletSelector(tx.coin, tx.network, tx.toAmount, function(err) { setWalletSelector(tx.coin, tx.network, tx.amount, function(err) {
if (err) { if (err) {
return exitWithError('Could not update wallets'); return exitWithError('Could not update wallets');
} }
@ -218,7 +260,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.displayBalanceAsFiat = walletConfig.settings.priceDisplay === 'fiat'; $scope.displayBalanceAsFiat = walletConfig.settings.priceDisplay === 'fiat';
}); };
function getSendMaxInfo(tx, wallet, cb) { function getSendMaxInfo(tx, wallet, cb) {
@ -242,7 +284,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
return setSendError(msg); return setSendError(msg);
} }
if (tx.toAmount > Number.MAX_SAFE_INTEGER) { if (tx.amount > Number.MAX_SAFE_INTEGER) {
var msg = gettextCatalog.getString('Amount too big'); var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg); $log.warn(msg);
return setSendError(msg); return setSendError(msg);
@ -252,7 +294,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
txp.outputs = [{ txp.outputs = [{
'toAddress': tx.toAddress, 'toAddress': tx.toAddress,
'amount': tx.toAmount, 'amount': tx.amount,
'message': tx.description 'message': tx.description
}]; }];
@ -291,13 +333,13 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.tx = tx; $scope.tx = tx;
function updateAmount() { function updateAmount() {
if (!tx.toAmount) return; if (!tx.amount) return;
// Amount // Amount
tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.toAmount); tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount);
tx.amountValueStr = tx.amountStr.split(' ')[0]; tx.amountValueStr = tx.amountStr.split(' ')[0];
tx.amountUnitStr = tx.amountStr.split(' ')[1]; tx.amountUnitStr = tx.amountStr.split(' ')[1];
txFormatService.formatAlternativeStr(wallet.coin, tx.toAmount, function(v) { txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) {
var parts = v.split(' '); var parts = v.split(' ');
tx.alternativeAmountStr = v; tx.alternativeAmountStr = v;
tx.alternativeAmountValueStr = parts[0]; tx.alternativeAmountValueStr = parts[0];
@ -353,7 +395,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
} }
tx.sendMaxInfo = sendMaxInfo; tx.sendMaxInfo = sendMaxInfo;
tx.toAmount = tx.sendMaxInfo.amount; tx.amount = tx.sendMaxInfo.amount;
updateAmount(); updateAmount();
ongoingProcess.set('calculatingFee', false); ongoingProcess.set('calculatingFee', false);
$timeout(function() { $timeout(function() {
@ -404,7 +446,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
function useSelectedWallet() { function useSelectedWallet() {
if (!$scope.useSendMax) { if (!$scope.useSendMax) {
showAmount(tx.toAmount); showAmount(tx.amount);
} }
$scope.onWalletSelect($scope.wallet); $scope.onWalletSelect($scope.wallet);
@ -413,19 +455,19 @@ angular.module('copayApp.controllers').controller('confirmController', function(
function setButtonText(isMultisig, isPayPro) { function setButtonText(isMultisig, isPayPro) {
if (isPayPro) { if (isPayPro) {
if (isCordova && !isWindowsPhoneApp) { if (isCordova) {
$scope.buttonText = gettextCatalog.getString('Slide to pay'); $scope.buttonText = gettextCatalog.getString('Slide to pay');
} else { } else {
$scope.buttonText = gettextCatalog.getString('Click to pay'); $scope.buttonText = gettextCatalog.getString('Click to pay');
} }
} else if (isMultisig) { } else if (isMultisig) {
if (isCordova && !isWindowsPhoneApp) { if (isCordova) {
$scope.buttonText = gettextCatalog.getString('Slide to accept'); $scope.buttonText = gettextCatalog.getString('Slide to accept');
} else { } else {
$scope.buttonText = gettextCatalog.getString('Click to accept'); $scope.buttonText = gettextCatalog.getString('Click to accept');
} }
} else { } else {
if (isCordova && !isWindowsPhoneApp) { if (isCordova) {
$scope.buttonText = gettextCatalog.getString('Slide to send'); $scope.buttonText = gettextCatalog.getString('Slide to send');
} else { } else {
$scope.buttonText = gettextCatalog.getString('Click to send'); $scope.buttonText = gettextCatalog.getString('Click to send');
@ -433,7 +475,6 @@ angular.module('copayApp.controllers').controller('confirmController', function(
} }
}; };
$scope.toggleAddress = function() { $scope.toggleAddress = function() {
$scope.showAddress = !$scope.showAddress; $scope.showAddress = !$scope.showAddress;
}; };

View file

@ -17,7 +17,7 @@ angular.module('copayApp.controllers').controller('customAmountController', func
} }
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
var walletId = data.stateParams.id; var walletId = data.stateParams.toWalletId;
if (!walletId) { if (!walletId) {
showErrorAndBack('Error', 'No wallet selected'); showErrorAndBack('Error', 'No wallet selected');
@ -53,11 +53,12 @@ angular.module('copayApp.controllers').controller('customAmountController', func
$scope.address = bchAddresses[$scope.bchAddressType]; $scope.address = bchAddresses[$scope.bchAddressType];
} }
$scope.coin = data.stateParams.coin; $scope.coin = $scope.wallet.coin;
var satoshis = parseInt(data.stateParams.amount, 10);
var parsedAmount = txFormatService.parseAmount( var parsedAmount = txFormatService.parseAmount(
$scope.wallet.coin, $scope.wallet.coin,
data.stateParams.amount, satoshis,
data.stateParams.currency); 'sat');
// Amount in USD or BTC // Amount in USD or BTC
var amount = parsedAmount.amount; var amount = parsedAmount.amount;

View file

@ -0,0 +1,891 @@
'use strict';
angular
.module('copayApp.controllers')
.controller('reviewController', reviewController);
function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) {
var vm = this;
vm.buttonText = '';
vm.destination = {
address: '',
balanceAmount: '',
balanceCurrency: '',
coin: '',
color: '',
currency: '',
currencyColor: '',
kind: '', // 'address', 'contact', 'wallet'
name: ''
};
vm.feeCrypto = '';
vm.feeFiat = '';
vm.fiatCurrency = '';
vm.feeIsHigh = false;
vm.feeLessThanACent = false;
vm.isCordova = platformInfo.isCordova;
vm.notReadyMessage = '';
vm.origin = {
balanceAmount: '',
balanceCurrency: '',
currency: '',
currencyColor: '',
};
vm.originWallet = null;
vm.paymentExpired = false;
vm.primaryAmount = '';
vm.primaryCurrency = '';
vm.usingMerchantFee = false;
vm.readyToSend = false;
vm.remainingTimeStr = '';
vm.secondaryAmount = '';
vm.secondaryCurrency = '';
vm.sendingTitle = gettextCatalog.getString('You are sending');
vm.sendStatus = '';
vm.showAddress = true;
vm.thirdParty = false;
vm.wallet = null;
vm.memoExpanded = false;
// Functions
vm.onSuccessConfirm = onSuccessConfirm;
var config = null;
var countDown = null;
var defaults = {};
var coin = '';
var countDown = null;
var usingCustomFee = false;
var usingMerchantFee = false;
var destinationWalletId = '';
var originWalletId = '';
var priceDisplayIsFiat = true;
var satoshis = null;
var toAddress = '';
var tx = {};
var txPayproData = null;
var unitFromSat = 0;
var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15;
$scope.$on("$ionicView.beforeEnter", onBeforeEnter);
function onBeforeEnter(event, data) {
defaults = configService.getDefaults();
originWalletId = data.stateParams.fromWalletId;
satoshis = parseInt(data.stateParams.amount, 10);
toAddress = data.stateParams.toAddress;
destinationWalletId = data.stateParams.toWalletId;
vm.originWallet = profileService.getWallet(originWalletId);
vm.origin.currency = vm.originWallet.coin.toUpperCase();
coin = vm.originWallet.coin;
if (data.stateParams.thirdParty) {
vm.thirdParty = JSON.parse(data.stateParams.thirdParty); // Parse stringified JSON-object
if (vm.thirdParty) {
handleThirdPartyInitIfBip70();
handleThirdPartyInitIfShapeshift();
}
}
configService.get(function onConfig(err, configCache) {
if (err) {
$log.err('Error getting config.', err);
} else {
config = configCache;
priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat';
vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor);
console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc');
unitFromSat = 1 / config.wallet.settings.unitToSatoshi;
}
updateSendAmounts();
getOriginWalletBalance(vm.originWallet);
handleDestinationAsAddress(toAddress, coin);
handleDestinationAsWallet(data.stateParams.toWalletId);
createVanityTransaction(data);
});
}
vm.approve = function() {
if (!tx || !vm.originWallet) return;
if (vm.paymentExpired) {
popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.'));
vm.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
ongoingProcess.set('creatingTx', true, statusChangeHandler);
getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false, statusChangeHandler);
if (err) return;
// confirm txs for more that 20usd, if not spending/touchid is enabled
function confirmTx(cb) {
if (walletService.isEncrypted(vm.originWallet))
return cb();
var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount));
return cb();
};
function publishAndSign() {
if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
return walletService.onlyPublish(vm.originWallet, txp, function(err) {
if (err) setSendError(err);
}, statusChangeHandler);
}
walletService.publishAndSign(vm.originWallet, txp, function(err, txp) {
if (err) return setSendError(err);
if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) {
txConfirmNotification.subscribe(vm.originWallet, {
txid: txp.txid
});
}
}, statusChangeHandler);
};
confirmTx(function(nok) {
if (nok) {
vm.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
publishAndSign();
});
});
};
vm.chooseFeeLevel = function(tx, wallet) {
if (wallet.coin == 'bch') return;
if (usingMerchantFee) return;
var scope = $rootScope.$new(true);
scope.network = tx.network;
scope.feeLevel = tx.feeLevel;
scope.noSave = true;
scope.coin = vm.originWallet.coin;
if (usingCustomFee) {
scope.customFeePerKB = tx.feeRate;
scope.feePerSatByte = tx.feeRate / 1000;
}
$ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', {
scope: scope,
backdropClickToClose: false,
hardwareBackButtonClose: false
}).then(function(modal) {
scope.chooseFeeLevelModal = modal;
scope.openModal();
});
scope.openModal = function() {
scope.chooseFeeLevelModal.show();
};
scope.hideModal = function(newFeeLevel, customFeePerKB) {
scope.chooseFeeLevelModal.hide();
$log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel);
usingCustomFee = newFeeLevel == 'custom' ? true : false;
if (tx.feeLevel == newFeeLevel && !usingCustomFee) return;
tx.feeLevel = newFeeLevel;
if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB);
updateTx(tx, vm.originWallet, {
clearCache: true,
dryRun: true
}, function() {});
};
};
function createVanityTransaction(data) {
console.log('createVanityTransaction()');
var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal';
// Grab stateParams
tx = {
amount: parseInt(data.stateParams.amount),
sendMax: data.stateParams.sendMax === 'true' ? true : false,
fromWalletId: data.stateParams.fromWalletId,
toAddress: data.stateParams.toAddress,
paypro: txPayproData,
feeLevel: configFeeLevel,
spendUnconfirmed: config.wallet.spendUnconfirmed,
// Vanity tx info (not in the real tx)
recipientType: vm.destination.kind || null,
toName: vm.destination.name || null,
toEmail: vm.destination.email || null,
toColor: vm.destination.color || null,
network: false,
coin: vm.originWallet.coin,
txp: {},
};
if (data.stateParams.requiredFeeRate) {
vm.usingMerchantFee = true;
tx.feeRate = parseInt(data.stateParams.requiredFeeRate);
}
if (tx.coin && tx.coin === 'bch') {
tx.feeLevel = 'normal';
}
var B = data.stateParams.coin === 'bch' ? bitcoreCash : bitcore;
var networkName;
try {
if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer
$ionicLoading.show();
var toWallet = profileService.getWallet(destinationWalletId);
// We need an address to send to, so we ask the walletService to create a new address for the toWallet.
console.log('Getting address for wallet...');
walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) {
console.log('getAddress cb called', err);
$ionicLoading.hide();
tx.toAddress = addr;
networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName;
console.log('calling setupTx() for wallet.');
setupTx(tx);
});
} else { // This is a Wallet-to-address transfer
networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName;
console.log('calling setupTx() for address.');
setupTx(tx);
}
} catch (e) {
console.error('Error setting up tx', e);
var message = gettextCatalog.getString('Invalid address');
popupService.showAlert(null, message, function () {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$state.go('tabs.send').then(function () {
$ionicHistory.clearHistory();
});
});
return;
}
}
function getOriginWalletBalance(originWallet) {
var balanceText = getWalletBalanceDisplayText(vm.originWallet);
vm.origin.balanceAmount = balanceText.amount;
vm.origin.balanceCurrency = balanceText.currency;
}
function getSendMaxInfo(tx, wallet, cb) {
if (!tx.sendMax) return cb();
//ongoingProcess.set('retrievingInputs', true);
walletService.getSendMaxInfo(wallet, {
feePerKb: tx.feeRate,
excludeUnconfirmedUtxos: !tx.spendUnconfirmed,
returnInputs: true,
}, cb);
};
function getTxp(tx, wallet, dryRun, cb) {
// ToDo: use a credential's (or fc's) function for this
if (tx.description && !wallet.credentials.sharedEncryptingKey) {
var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key');
$log.warn(msg);
return setSendError(msg);
}
if (tx.amount > Number.MAX_SAFE_INTEGER) {
var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg);
return setSendError(msg);
}
var txp = {};
txp.outputs = [{
'toAddress': tx.toAddress,
'amount': tx.amount,
'message': vm.memo
}];
if (tx.sendMaxInfo) {
txp.inputs = tx.sendMaxInfo.inputs;
txp.fee = tx.sendMaxInfo.fee;
} else {
if (usingCustomFee || usingMerchantFee) {
txp.feePerKb = tx.feeRate;
} else txp.feeLevel = tx.feeLevel;
}
txp.message = vm.memo;
if (tx.paypro) {
txp.payProUrl = tx.paypro.url;
}
txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed;
txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
setSendError(err);
return cb(err);
}
return cb(null, ctxp);
});
};
function getWalletBalanceDisplayText(wallet) {
var balanceCryptoAmount = '';
var balanceCryptoCurrencyCode = '';
var balanceFiatAmount = '';
var balanceFiatCurrency = ''
var displayAmount = '';
var displayCurrency = '';
var walletStatus = null;
if (wallet.status.isValid) {
walletStatus = wallet.status;
} else if (wallet.cachedStatus.isValid) {
walletStatus = wallet.cachedStatus;
}
if (walletStatus) {
var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' ');
balanceCryptoAmount = cryptoBalanceParts[0];
balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : '';
if (walletStatus.alternativeBalanceAvailable) {
balanceFiatAmount = walletStatus.spendableBalanceAlternative;
balanceFiatCurrency = walletStatus.alternativeIsoCode;
}
}
if (priceDisplayIsFiat) {
displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount;
displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode;
} else {
displayAmount = balanceCryptoAmount;
displayCurrency = balanceCryptoCurrencyCode;
}
return {
amount: displayAmount,
currency: displayCurrency
};
}
function handleDestinationAsAddress(address, originCoin) {
if (!address) {
return;
}
// Check if the recipient is a contact
addressbookService.get(originCoin + address, function(err, contact) {
if (!err && contact) {
handleDestinationAsContact(contact);
} else {
vm.destination.address = address;
vm.destination.kind = 'address';
}
});
}
function handleDestinationAsContact(contact) {
vm.destination.kind = 'contact';
vm.destination.name = contact.name;
vm.destination.email = contact.email;
vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor;
vm.destination.currency = contact.coin.toUpperCase();
vm.destination.currencyColor = vm.destination.color;
}
function handleDestinationAsWallet(walletId) {
destinationWalletId = walletId;
if (!destinationWalletId) {
return;
}
var destinationWallet = profileService.getWallet(destinationWalletId);
vm.destination.coin = destinationWallet.coin;
vm.destination.color = destinationWallet.color;
vm.destination.currency = destinationWallet.coin.toUpperCase();
vm.destination.kind = 'wallet';
vm.destination.name = destinationWallet.name;
if (defaults) {
vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor;
}
var balanceText = getWalletBalanceDisplayText(destinationWallet);
vm.destination.balanceAmount = balanceText.amount;
vm.destination.balanceCurrency = balanceText.currency;
}
function handleThirdPartyInitIfBip70() {
if (vm.thirdParty.id === 'bip70') {
vm.sendingTitle = gettextCatalog.getString('You are paying');
vm.memo = vm.thirdParty.memo;
vm.memoExpanded = !!vm.memo;
vm.destination.name = vm.thirdParty.name;
txPayproData = {
caTrusted: vm.thirdParty.caTrusted,
domain: vm.thirdParty.domain,
expires: vm.thirdParty.expires,
toAddress: toAddress,
url: vm.thirdParty.url,
verified: vm.thirdParty.verified,
};
}
}
function handleThirdPartyInitIfShapeshift() {
if (vm.thirdParty.id === 'shapeshift') {
vm.sendingTitle = gettextCatalog.getString('You are shifting');
if (!vm.thirdParty.data) {
vm.thirdParty.data = {};
}
var toWallet = profileService.getWallet(destinationWalletId);
vm.destination.name = toWallet.name;
vm.destination.color = toWallet.color;
vm.destination.currency = toWallet.coin.toUpperCase();
$ionicLoading.show();
walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) {
if (err) {
$ionicLoading.hide();
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString());
return;
}
walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) {
if (err) {
$ionicLoading.hide();
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString());
return;
}
shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) {
if (err && err != null) {
$ionicLoading.hide();
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString());
} else {
vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId;
vm.memoExpanded = !!vm.memo;
tx.toAddress = shapeshiftData.toAddress;
vm.destination.address = toAddress;
vm.destination.kind = 'shapeshift';
}
});
});
});
}
}
function startExpirationTimer(expirationTime) {
vm.paymentExpired = false;
setExpirationTime();
countDown = $interval(function() {
setExpirationTime();
}, 1000);
function setExpirationTime() {
console.log('setExpirationTime()');
var now = Math.floor(Date.now() / 1000);
if (now > expirationTime) {
setExpiredValues();
return;
}
var totalSecs = expirationTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2);
};
function setExpiredValues() {
vm.paymentExpired = true;
vm.remainingTimeStr = gettextCatalog.getString('Expired');
vm.readyToSend = false;
if (countDown) $interval.cancel(countDown);
$timeout(function() {
$scope.$apply();
});
};
};
function updateSendAmounts() {
if (typeof satoshis !== 'number') {
return;
}
var cryptoAmount = '';
var cryptoCurrencyCode = '';
var amountStr = txFormatService.formatAmountStr(coin, satoshis);
if (amountStr) {
var amountParts = amountStr.split(' ');
cryptoAmount = amountParts[0];
cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : '';
}
// Want to avoid flashing of amount strings so do all formatting after this has returned.
txFormatService.formatAlternativeStr(coin, satoshis, function(v) {
if (!v) {
vm.primaryAmount = cryptoAmount;
vm.primaryCurrency = cryptoCurrencyCode;
vm.secondaryAmount = '';
vm.secondaryCurrency = '';
return;
}
vm.secondaryAmount = vm.primaryAmount;
vm.secondaryCurrency = vm.primaryCurrency;
var fiatParts = v.split(' ');
var fiatAmount = fiatParts[0];
var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : '';
if (priceDisplayIsFiat) {
vm.primaryAmount = fiatAmount;
vm.primaryCurrency = fiatCurrency;
vm.secondaryAmount = cryptoAmount;
vm.secondaryCurrency = cryptoCurrencyCode;
} else {
vm.primaryAmount = cryptoAmount;
vm.primaryCurrency = cryptoCurrencyCode;
vm.secondaryAmount = fiatAmount;
vm.secondaryCurrency = fiatCurrency;
}
});
}
function onSuccessConfirm() {
vm.sendStatus = '';
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$state.go('tabs.send').then(function() {
$ionicHistory.clearHistory();
$state.transitionTo('tabs.home');
});
};
function setButtonText(isMultisig, isPayPro) {
if (isPayPro) {
if (vm.isCordova) {
vm.buttonText = gettextCatalog.getString('Slide to pay');
} else {
vm.buttonText = gettextCatalog.getString('Click to pay');
}
} else if (isMultisig) {
if (vm.isCordova) {
vm.buttonText = gettextCatalog.getString('Slide to accept');
} else {
vm.buttonText = gettextCatalog.getString('Click to accept');
}
} else {
if (vm.isCordova) {
vm.buttonText = gettextCatalog.getString('Slide to send');
} else {
vm.buttonText = gettextCatalog.getString('Click to send');
}
}
}
function setNotReady(msg, criticalError) {
vn.readyToSend = false;
vm.notReadyMessage = msg;
$scope.criticalError = criticalError;
$log.warn('Not ready to make the payment:' + msg);
$timeout(function() {
$scope.$apply();
});
};
function setSendError(msg) {
$scope.sendStatus = '';
vm.readyToSend = false;
$timeout(function() {
$scope.$apply();
});
popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg));
};
function setupTx(tx) {
if (tx.coin === 'bch') {
tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr;
} else {
tx.displayAddress = tx.toAddress;
}
addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact
if (!err && addr) {
tx.toName = addr.name;
tx.toEmail = addr.email;
tx.recipientType = 'contact';
}
});
vm.showAddress = false;
setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro);
if (tx.paypro)
startExpirationTimer(tx.paypro.expires);
updateTx(tx, vm.originWallet, {
dryRun: true
}, function(err) {
$timeout(function() {
$scope.$apply();
}, 10);
});
// setWalletSelector(tx.coin, tx.network, tx.amount, function(err) {
// if (err) {
// return exitWithError('Could not update wallets');
// }
//
// if (vm.wallets.length > 1) {
// vm.showWalletSelector();
// } else if (vm.wallets.length) {
// setWallet(vm.wallets[0], tx);
// }
// });
}
function showSendMaxWarning(wallet, sendMaxInfo) {
var feeAlternative = '',
msg = '';
function verifyExcludedUtxos() {
var warningMsg = [];
if (sendMaxInfo.utxosBelowFee > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", {
amountBelowFeeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountBelowFee)
}));
}
if (sendMaxInfo.utxosAboveMaxSize > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", {
amountAboveMaxSizeStr: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize)
}));
}
return warningMsg.join('\n');
};
feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee);
if (feeAlternative) {
msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", {
fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee),
feeAlternative: feeAlternative
});
} else {
msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", {
fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee)
});
}
var warningMsg = verifyExcludedUtxos();
if (!lodash.isEmpty(warningMsg))
msg += '\n' + warningMsg;
popupService.showAlert(null, msg, function() {});
};
function statusChangeHandler(processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if (
(
processName === 'broadcastingTx' ||
((processName === 'signingTx') && vm.originWallet.m > 1) ||
(processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal())
) && !isOn) {
vm.sendStatus = 'success';
if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device.
soundService.play('misc/payment_sent.mp3');
}
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
}
// When displaying Fiat, if the formatting fails, the crypto will be the primary amount.
var amount = unitFromSat * satoshis;
var log = new window.BitAnalytics.LogEvent("transfer_success", [{
"coin": vm.originWallet.coin,
"type": "outgoing",
"amount": amount,
"fees": vm.feeCrypto
}], [channel, "adjust"]);
window.BitAnalytics.LogEventHandlers.postEvent(log);
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
vm.sendStatus = showName;
}
};
function updateTx(tx, wallet, opts, cb) {
ongoingProcess.set('calculatingFee', true);
if (opts.clearCache) {
tx.txp = {};
}
// $scope.tx = tx;
// function updateAmount() {
// if (!tx.amount) return;
//
// // Amount
// tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount);
// tx.amountValueStr = tx.amountStr.split(' ')[0];
// tx.amountUnitStr = tx.amountStr.split(' ')[1];
// txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) {
// var parts = v.split(' ');
// tx.alternativeAmountStr = v;
// tx.alternativeAmountValueStr = parts[0];
// tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : '';
// });
// }
//
// updateAmount();
// refresh();
var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel;
feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) {
if (err) {
ongoingProcess.set('calculatingFee', false);
return cb(err);
}
var msg;
if (usingCustomFee) {
msg = gettextCatalog.getString('Custom');
tx.feeLevelName = msg;
} else if (usingMerchantFee) {
$log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate);
msg = gettextCatalog.getString('Suggested by Merchant');
tx.feeLevelName = msg;
} else {
tx.feeLevelName = feeService.feeOpts[tx.feeLevel];
tx.feeRate = feeRate;
}
getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) {
if (err) {
ongoingProcess.set('calculatingFee', false);
var msg = gettextCatalog.getString('Error getting SendMax information');
return setSendError(msg);
}
if (sendMaxInfo) {
$log.debug('Send max info', sendMaxInfo);
if (tx.sendMax && sendMaxInfo.amount == 0) {
ongoingProcess.set('calculatingFee', false);
setNotReady(gettextCatalog.getString('Insufficient confirmed funds'));
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return cb('no_funds');
}
tx.sendMaxInfo = sendMaxInfo;
tx.amount = tx.sendMaxInfo.amount;
satoshis = tx.amount;
updateSendAmounts();
ongoingProcess.set('calculatingFee', false);
$timeout(function() {
showSendMaxWarning(wallet, sendMaxInfo);
}, 200);
}
// txp already generated for this wallet?
if (tx.txp[wallet.id]) {
ongoingProcess.set('calculatingFee', false);
vm.readyToSend = true;
updateSendAmounts();
$scope.$apply();
return cb();
}
console.log('calling getTxp() from getSendMaxInfo cb.');
getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) {
ongoingProcess.set('calculatingFee', false);
if (err) {
if (err.message == 'Insufficient funds') {
setNotReady(gettextCatalog.getString('Insufficient funds'));
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return cb('no_funds');
} else
return cb(err);
}
txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee);
txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) {
// txp.alternativeFeeStr = v;
// if (txp.alternativeFeeStr.substring(0, 4) == '0.00')
// txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr;
vm.feeFiat = v;
vm.fiatCurrency = config.wallet.settings.alternativeIsoCode;
if (v.substring(0, 1) === "<") {
vm.feeLessThanACent = true;
}
console.log("fiat", vm.feeFiat);
});
var per = (txp.fee / (txp.amount + txp.fee) * 100);
var perString = per.toFixed(2);
txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%';
txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE;
vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8);
vm.feeIsHigh = txp.feeToHigh;
console.log("crypto", vm.feeCrypto);
tx.txp[wallet.id] = txp;
$log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx);
vm.readyToSend = true;
updateSendAmounts();
console.log('readyToSend:', vm.readyToSend);
$scope.$apply();
return cb();
});
});
});
}
}

View file

@ -1,7 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $state, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) {
var walletsBtc = []; var walletsBtc = [];
var walletsBch = []; var walletsBch = [];
@ -16,24 +15,9 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
} }
function showToWallets() { function showToWallets() {
$scope.toWallets = $scope.fromWallet.coin == 'btc' ? walletsBch : walletsBtc; $scope.toWallets = $scope.fromWallet.coin === 'btc' ? walletsBch : walletsBtc;
$scope.onToWalletSelect($scope.toWallets[0]); $scope.onToWalletSelect($scope.toWallets[0]);
$scope.singleToWallet = $scope.toWallets.length == 1; $scope.singleToWallet = $scope.toWallets.length === 1;
}
$scope.onFromWalletSelect = function(wallet) {
$scope.fromWallet = wallet;
showToWallets();
generateAddress(wallet, function(addr) {
$scope.fromWalletAddress = addr;
});
};
$scope.onToWalletSelect = function(wallet) {
$scope.toWallet = wallet;
generateAddress(wallet, function(addr) {
$scope.toWalletAddress = addr;
});
} }
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
@ -42,15 +26,15 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
$scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) { $scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) {
return w.status.balance.availableAmount > 0; return w.status.balance.availableAmount > 0;
}); });
if ($scope.fromWallets.length == 0) return;
$scope.onFromWalletSelect($scope.fromWallets[0]); $scope.singleFromWallet = $scope.fromWallets.length === 1;
$scope.onToWalletSelect($scope.toWallets[0]);
$scope.singleFromWallet = $scope.fromWallets.length == 1;
$scope.singleToWallet = $scope.toWallets.length == 1;
$scope.fromWalletSelectorTitle = 'From'; $scope.fromWalletSelectorTitle = 'From';
$scope.toWalletSelectorTitle = 'To'; $scope.toWalletSelectorTitle = 'To';
$scope.showFromWallets = false; $scope.showFromWallets = false;
$scope.showToWallets = false; $scope.showToWallets = false;
$scope.walletsWithFunds = profileService.getWallets({onlyComplete: true, hasFunds: true});
$scope.wallets = profileService.getWallets({onlyComplete: true});
$scope.hasWallets = !lodash.isEmpty($scope.wallets);
}); });
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
@ -59,9 +43,31 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
$scope.showFromWalletSelector = function() { $scope.showFromWalletSelector = function() {
$scope.showFromWallets = true; $scope.showFromWallets = true;
} };
$scope.showToWalletSelector = function() { $scope.showToWalletSelector = function() {
$scope.showToWallets = true; $scope.showToWallets = true;
};
// This could probably be enhanced refactoring the routes abstract states
$scope.createWallet = function() {
$state.go('tabs.home').then(function() {
$state.go('tabs.add.create-personal');
});
};
$scope.buyBitcoin = function() {
$state.go('tabs.home').then(function() {
$state.go('tabs.buyandsell');
});
};
$scope.shapeshift = function() {
var params = {
thirdParty: JSON.stringify({id: 'shapeshift'})
};
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.send.origin', params);
});
} }
}); });

View file

@ -19,8 +19,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
$scope.requestSpecificAmount = function() { $scope.requestSpecificAmount = function() {
$state.go('tabs.paymentRequest.amount', { $state.go('tabs.paymentRequest.amount', {
id: $scope.wallet.credentials.walletId, toWalletId: $scope.wallet.credentials.walletId
coin: $scope.wallet.coin
}); });
}; };

View file

@ -55,42 +55,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
}); });
}); });
var wallets;
var walletsBch;
var walletsBtc;
var walletToWalletFrom = false;
$scope.onWalletSelect = function(wallet) {
if (!$scope.walletToWalletFrom) {
$scope.walletToWalletFrom = wallet;
if (wallet.coin === 'bch') {
$scope.showWalletsBch = true;
} else if (wallet.coin === 'btc') {
$scope.showWalletsBtc = true;
}
$scope.walletSelectorTitleTo = gettextCatalog.getString('Send to');
} else {
$ionicLoading.show();
walletService.getAddress(wallet, true, function(err, addr) {
$ionicLoading.hide();
return $state.transitionTo('tabs.send.amount', {
displayAddress: $scope.walletToWalletFrom.coin === 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr,
recipientType: 'wallet',
fromWalletId: $scope.walletToWalletFrom.id,
toAddress: addr,
coin: $scope.walletToWalletFrom.coin
});
});
}
};
$scope.showWalletSelector = function() {
$scope.walletToWalletFrom = false;
$scope.walletSelectorTitleFrom = gettextCatalog.getString('Send from');
$scope.showWallets = true;
};
$scope.findContact = function(search) { $scope.findContact = function(search) {
if (incomingData.redir(search)) { if (incomingData.redir(search)) {
@ -133,7 +97,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
}; };
var updateHasFunds = function() { var updateHasFunds = function() {
$scope.hasFunds = false; $scope.hasFunds = false;
var index = 0; var index = 0;
lodash.each($scope.wallets, function(w) { lodash.each($scope.wallets, function(w) {
@ -179,10 +142,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
coin: v.coin, coin: v.coin,
displayCoin: (v.coin == 'bch' displayCoin: (v.coin == 'bch'
? (config.bitcoinCashAlias || defaults.bitcoinCashAlias) ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias)
: (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase(), : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase()
getAddress: function(cb) {
return cb(null, k);
},
}); });
}); });
originalList = completeContacts; originalList = completeContacts;
@ -203,35 +163,26 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
}; };
$scope.searchBlurred = function() { $scope.searchBlurred = function() {
if ($scope.formData.search == null || $scope.formData.search.length == 0) { if ($scope.formData.search == null || $scope.formData.search.length === 0) {
$scope.searchFocus = false; $scope.searchFocus = false;
} }
}; };
$scope.goToAmount = function(item) { $scope.sendToContact = function (item) {
$timeout(function() { $timeout(function () {
item.getAddress(function(err, addr) { var toAddress = item.address;
if (err || !addr) {
//Error is already formated
return popupService.showAlert(err);
}
if (item.recipientType && item.recipientType == 'contact') { if (item.recipientType && item.recipientType === 'contact') {
if (addr.indexOf('bch') == 0 || addr.indexOf('btc') == 0) { if (toAddress.indexOf('bch') === 0 || toAddress.indexOf('btc') === 0) {
addr = addr.substring(3); toAddress = toAddress.substring(3);
}
} }
}
$log.debug('Got toAddress:' + addr + ' | ' + item.name); $log.debug('Got toAddress:' + toAddress + ' | ' + item.name);
return $state.transitionTo('tabs.send.amount', {
recipientType: item.recipientType, return $state.transitionTo('tabs.send.origin', {
displayAddress: item.coin == 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr, toAddress: toAddress,
toAddress: addr, coin: item.coin
toName: item.name,
toEmail: item.email,
toColor: item.color,
coin: item.coin
});
}); });
}); });
}; };

View file

@ -0,0 +1,193 @@
'use strict';
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, configService, gettextCatalog, profileService, txFormatService) {
var fromWalletId = '';
var priceDisplayAsFiat = false;
var unitDecimals = 0;
var unitsFromSatoshis = 0;
$scope.$on("$ionicView.beforeEnter", function(event, data) {
var config = configService.getSync().wallet.settings;
priceDisplayAsFiat = config.priceDisplay === 'fiat';
unitDecimals = config.unitDecimals;
unitsFromSatoshis = 1 / config.unitToSatoshi;
switch($state.current.name) {
case 'tabs.send.wallet-to-wallet':
$scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer');
break;
case 'tabs.send.destination':
if (data.stateParams.fromWalletId) {
$scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer');
}
break;
default:
// nop
}
$scope.params = $state.params;
$scope.coin = false; // Wallets to show (for destination screen or contacts)
$scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination
fromWalletId = data.stateParams && data.stateParams.fromWalletId;
if ($scope.params.coin) {
$scope.coin = $scope.params.coin; // Contacts have a coin embedded
}
if ($scope.params.amount) { // There is an amount, so presume that it is a payment request
$scope.sendFlowTitle = gettextCatalog.getString('Payment Request');
$scope.specificAmount = $scope.specificAlternativeAmount = '';
$scope.isPaymentRequest = true;
}
if ($scope.params.thirdParty) {
$scope.thirdParty = JSON.parse($scope.params.thirdParty); // Parse stringified JSON-object
}
});
$scope.$on("$ionicView.enter", function(event, data) {
configService.whenAvailable(function(config) {
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
});
if ($scope.thirdParty) {
// Third party services specific logic
handleThirdPartyIfShapeshift();
}
prepareWalletLists();
formatRequestedAmount();
});
function formatRequestedAmount() {
if ($scope.params.amount) {
var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals);
var cryptoCoin = $scope.coin.toUpperCase();
txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){
if (formatted) {
var fiatParts = formatted.split(' ');
var fiatAmount = fiatParts[0];
var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : '';
if (priceDisplayAsFiat) {
$scope.requestAmount = fiatAmount;
$scope.requestCurrency = fiatCurrrency;
$scope.requestAmountSecondary = cryptoAmount;
$scope.requestCurrencySecondary = cryptoCoin;
} else {
$scope.requestAmount = cryptoAmount;
$scope.requestCurrency = cryptoCoin;
$scope.requestAmountSecondary = fiatAmount;
$scope.requestCurrencySecondary = fiatCurrrency;
}
}
});
}
}
function getNextStep() {
if ($scope.thirdParty) {
$scope.params.thirdParty = JSON.stringify($scope.thirdParty) // re-stringify JSON-object
}
if (!$scope.params.toWalletId && !$scope.params.toAddress) { // If we have no toAddress or fromWallet
return 'tabs.send.destination';
} else if (!$scope.params.amount) { // If we have no amount
return 'tabs.send.amount';
} else { // If we do have them
return 'tabs.send.review';
}
}
function handleThirdPartyIfShapeshift() {
console.log($scope.thirdParty, $scope.coin);
if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the
$scope.coin = profileService.getWallet(fromWalletId).coin;
if ($scope.coin === 'bch') {
$scope.coin = 'btc';
} else {
$scope.coin = 'bch';
}
}
}
function prepareWalletLists() {
var walletsAll = [];
var walletsSufficientFunds = [];
$scope.walletsInsufficientFunds = []; // For origin screen
if ($scope.type === 'origin') {
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from');
if ($scope.params.amount) {
walletsAll = profileService.getWallets({coin: $scope.coin});
walletsAll.forEach(function forWallet(wallet){
if (wallet.status.availableBalanceSat > $scope.params.amount) {
walletsSufficientFunds.push(wallet);
} else {
$scope.walletsInsufficientFunds.push(wallet);
}
});
if ($scope.coin === 'btc') {
$scope.walletsBtc = walletsSufficientFunds;
} else {
$scope.walletsBch = walletsSufficientFunds;
}
} else if ($scope.coin) {
walletsAll = profileService.getWallets({coin: $scope.coin});
walletsAll.forEach(function forWallet(wallet){
if (wallet.status.availableBalanceSat > 0) {
walletsSufficientFunds.push(wallet);
} else {
$scope.walletsInsufficientFunds.push(wallet);
}
});
if ($scope.coin === 'btc') {
$scope.walletsBtc = walletsSufficientFunds;
} else {
$scope.walletsBch = walletsSufficientFunds;
}
} else {
$scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true});
$scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true});
$scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true});
}
} else if ($scope.type === 'destination') {
if (!$scope.coin) { // Allow for the coin to be set by a third party
$scope.fromWallet = profileService.getWallet(fromWalletId);
$scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin
}
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to');
if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc
$scope.walletsBtc = profileService.getWallets({coin: $scope.coin});
} else {
$scope.walletsBch = profileService.getWallets({coin: $scope.coin});
}
}
}
$scope.useWallet = function(wallet) {
if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from
$scope.params['fromWalletId'] = wallet.id;
} else { // we're on the destination screen, set wallet to send to
$scope.params['toWalletId'] = wallet.id;
}
$state.transitionTo(getNextStep(), $scope.params);
};
$scope.goBack = function() {
$ionicHistory.goBack();
}
});

View file

@ -48,37 +48,44 @@ angular.module('bitcoincom.directives')
return '2'; return '2';
}; };
switch (getDecimalPlaces($scope.currency)) { var formatNumbers = function(currency, value) {
case '0': switch (getDecimalPlaces(currency)) {
var valueFormatted = numberWithCommas(Math.round(parseFloat($scope.value))); case '0':
buildAmount(valueFormatted, '', ''); var valueFormatted = numberWithCommas(Math.round(parseFloat(value)));
break;
case '2':
var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(2));
var valueFormatted = numberWithCommas(valueProcessing);
buildAmount(valueFormatted, '', '');
break;
case '3':
var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(3));
var valueFormatted = numberWithCommas(valueProcessing);
buildAmount(valueFormatted, '', '');
break;
case '8':
var valueFormatted = parseFloat($scope.value).toFixed(8);
if (parseFloat($scope.value) == 0) {
buildAmount('0', '', '');
} else {
buildAmount(valueFormatted, '', ''); buildAmount(valueFormatted, '', '');
var start = numberWithCommas(valueFormatted.slice(0, -5)); break;
var middle = valueFormatted.slice(-5, -2);
var end = valueFormatted.substr(valueFormatted.length - 2); case '2':
buildAmount(start, middle, end); var valueProcessing = parseFloat(parseFloat(value).toFixed(2));
} var valueFormatted = numberWithCommas(valueProcessing);
break; buildAmount(valueFormatted, '', '');
break;
case '3':
var valueProcessing = parseFloat(parseFloat(value).toFixed(3));
var valueFormatted = numberWithCommas(valueProcessing);
buildAmount(valueFormatted, '', '');
break;
case '8':
var valueFormatted = parseFloat(value).toFixed(8);
if (parseFloat(value) == 0) {
buildAmount('0', '', '');
} else {
buildAmount(valueFormatted, '', '');
var start = numberWithCommas(valueFormatted.slice(0, -5));
var middle = valueFormatted.slice(-5, -2);
var end = valueFormatted.substr(valueFormatted.length - 2);
buildAmount(start, middle, end);
}
break;
}
} }
formatNumbers($scope.currency, $scope.value);
$scope.$watchGroup(['currency', 'value'], function() {
formatNumbers($scope.currency, $scope.value);
});
}] }]
}; };
} }

View file

@ -111,7 +111,7 @@ angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function
orderId: $scope.depositInfo.orderId orderId: $scope.depositInfo.orderId
}; };
if (incomingData.redir(sendAddress, shapeshiftData)) { if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
ongoingProcess.set('connectingShapeshift', false); ongoingProcess.set('connectingShapeshift', false);
return; return;
} }

View file

@ -287,7 +287,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
*/ */
.state('tabs.send.amount', { .state('tabs.send.amount', {
url: '/amount/:recipientType/:toAddress/:toName/:toEmail/:toColor/:coin/:fixedUnit/:fromWalletId/:minShapeshiftAmount/:maxShapeshiftAmount/:shapeshiftOrderId/:displayAddress/:noPrefix', url: '/amount/:thirdParty/:fromWalletId/:maxAmount/:minAmount/:toWalletId/:toAddress',
views: { views: {
'tab-send@tabs': { 'tab-send@tabs': {
controller: 'amountController', controller: 'amountController',
@ -296,8 +296,35 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
} }
}) })
.state('tabs.send.wallet-to-wallet', {
url: '/wallet-to-wallet',
views: {
'tab-send@tabs': {
controller: 'walletSelectorController',
templateUrl: 'views/walletSelector.html'
}
}
})
.state('tabs.send.origin', {
url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId/:coin',
views: {
'tab-send@tabs': {
controller: 'walletSelectorController',
templateUrl: 'views/walletSelector.html',
}
}
})
.state('tabs.send.destination', {
url: '/destination/:thirdParty/:amount/:fromWalletId',
views: {
'tab-send@tabs': {
controller: 'walletSelectorController',
templateUrl: 'views/walletSelector.html',
}
}
})
.state('tabs.send.confirm', { .state('tabs.send.confirm', {
url: '/confirm/:recipientType/:toAddress/:toName/:toAmount/:toEmail/:toColor/:description/:coin/:useSendMax/:fromWalletId/:displayAddress/:requiredFeeRate', url: '/confirm/:thirdParty/:amount/:fromWalletId/:toWalletId/:toAddress',
views: { views: {
'tab-send@tabs': { 'tab-send@tabs': {
controller: 'confirmController', controller: 'confirmController',
@ -317,6 +344,19 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
} }
}) })
.state('tabs.send.review', {
url: '/review/:thirdParty/:amount/:fromWalletId/:sendMax/:toAddress/:toWalletId',
views: {
'tab-send@tabs': {
controller: 'reviewController',
controllerAs: 'vm',
templateUrl: 'views/review.html'
}
},
params: {
paypro: null
}
})
/* /*
* *
@ -696,7 +736,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}) })
.state('tabs.paymentRequest.amount', { .state('tabs.paymentRequest.amount', {
url: '/amount/:coin', url: '/amount/:toWalletId',
views: { views: {
'tab-receive@tabs': { 'tab-receive@tabs': {
controller: 'amountController', controller: 'amountController',
@ -706,7 +746,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.paymentRequest.confirm', { .state('tabs.paymentRequest.confirm', {
url: '/confirm/:amount/:currency/:coin', url: '/confirm/:amount/:toWalletId',
views: { views: {
'tab-receive@tabs': { 'tab-receive@tabs': {
controller: 'customAmountController', controller: 'customAmountController',
@ -972,7 +1012,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
/* Shapeshift */ /* Shapeshift */
.state('tabs.shapeshift', { .state('tabs.shapeshift', {
url: '/shapeshift', url: '/shapeshift/:fromWalletId/:toWalletId',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'shapeshiftController', controller: 'shapeshiftController',
@ -1271,7 +1311,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
if (screen.width < 768 && platformInfo.isCordova) if (screen.width < 768 && platformInfo.isCordova)
screen.lockOrientation('portrait'); screen.lockOrientation('portrait');
if (ionic.Platform.isAndroid() && StatusBar) { if (ionic.Platform.isAndroid() && platformInfo.isCordova && StatusBar) {
StatusBar.backgroundColorByHexString('#000000'); StatusBar.backgroundColorByHexString('#000000');
} }

View file

@ -8,7 +8,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
$rootScope.$broadcast('incomingDataMenu.showMenu', data); $rootScope.$broadcast('incomingDataMenu.showMenu', data);
}; };
root.redir = function(data, shapeshiftData) { root.redir = function(data, serviceId, serviceData) {
var originalAddress = null; var originalAddress = null;
var noPrefixInAddress = 0; var noPrefixInAddress = 0;
@ -75,35 +75,40 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
return true; return true;
} }
function goSend(addr, amount, message, coin, shapeshiftData) { function goSend(addr, amount, message, coin, serviceId, serviceData) {
$state.go('tabs.send', {}, { $state.go('tabs.send', {}, {
'reload': true, 'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true 'notify': $state.current.name == 'tabs.send' ? false : true
}); });
// Timeout is required to enable the "Back" button // Timeout is required to enable the "Back" button
$timeout(function() { $timeout(function() {
var params = {};
if (amount) { if (amount) {
$state.transitionTo('tabs.send.confirm', { params.amount = amount;
toAmount: amount, }
toAddress: addr,
displayAddress: originalAddress ? originalAddress : addr, if (addr) {
description: message, params.toAddress = addr;
coin: coin params.displayAddress = originalAddress ? originalAddress : addr;
}); }
} else {
var params = { if (coin) {
toAddress: addr, params.coin = coin;
coin: coin, }
displayAddress: originalAddress ? originalAddress : addr,
noPrefix: noPrefixInAddress if (noPrefixInAddress) {
}; params.noPrefixInAddress = noPrefixInAddress;
if (shapeshiftData) { }
params['fromWalletId'] = shapeshiftData.fromWalletId;
params['minShapeshiftAmount'] = shapeshiftData.minAmount; if (serviceId) {
params['maxShapeshiftAmount'] = shapeshiftData.maxAmount; params.thirdParty = [];
params['shapeshiftOrderId'] = shapeshiftData.orderId; params.thirdParty.id = serviceId;
} params.thirdParty.data = serviceData;
params.thirdParty = JSON.stringify(params.thirdParty);
$state.transitionTo('tabs.send.amount', params); $state.transitionTo('tabs.send.amount', params);
} else {
$state.transitionTo('tabs.send.origin', params);
} }
}, 100); }, 100);
} }
@ -112,15 +117,20 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc'; var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, '')); data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
if (coin == 'bch') { if (coin == 'bch') {
payproService.getPayProDetailsViaHttp(data, function(err, details) { payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err) var message = err.toString();
if (typeof err.data === 'string') {
// i.e. 'This invoice is no longer accepting payments'
message = gettextCatalog.getString(err.data);
}
popupService.showAlert(gettextCatalog.getString('Error'), message)
} else { } else {
handlePayPro(createBchPayProObject(details), coin); handlePayPro(details, coin);
} }
}); });
} else { } else {
payproService.getPayProDetails(data, coin, function(err, details) { payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err); popupService.showAlert(gettextCatalog.getString('Error'), err);
} else { } else {
@ -146,12 +156,12 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
if (parsed.r) { if (parsed.r) {
payproService.getPayProDetails(parsed.r, coin, function(err, details) { payproService.getPayProDetails(parsed.r, coin, function(err, details) {
if (err) { if (err) {
if (addr && amount) goSend(addr, amount, message, coin, shapeshiftData); if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
else popupService.showAlert(gettextCatalog.getString('Error'), err); else popupService.showAlert(gettextCatalog.getString('Error'), err);
} else handlePayPro(details, coin); } else handlePayPro(details, coin);
}); });
} else { } else {
goSend(addr, amount, message, coin, shapeshiftData); goSend(addr, amount, message, coin, serviceId, serviceData);
} }
return true; return true;
// Cash URI // Cash URI
@ -169,14 +179,14 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
payproService.getPayProDetails(parsed.r, coin, function(err, details) { payproService.getPayProDetails(parsed.r, coin, function(err, details) {
if (err) { if (err) {
if (addr && amount) if (addr && amount)
goSend(addr, amount, message, coin, shapeshiftData); goSend(addr, amount, message, coin, serviceId, serviceData);
else else
popupService.showAlert(gettextCatalog.getString('Error'), err); popupService.showAlert(gettextCatalog.getString('Error'), err);
} }
handlePayPro(details, coin); handlePayPro(details, coin);
}); });
} else { } else {
goSend(addr, amount, message, coin, shapeshiftData); goSend(addr, amount, message, coin, serviceId, serviceData);
} }
return true; return true;
@ -212,14 +222,14 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
payproService.getPayProDetails(parsed.r, coin, function(err, details) { payproService.getPayProDetails(parsed.r, coin, function(err, details) {
if (err) { if (err) {
if (addr && amount) if (addr && amount)
goSend(addr, amount, message, coin, shapeshiftData); goSend(addr, amount, message, coin, serviceId, serviceData);
else else
popupService.showAlert(gettextCatalog.getString('Error'), err); popupService.showAlert(gettextCatalog.getString('Error'), err);
} }
handlePayPro(details, coin); handlePayPro(details, coin);
}); });
} else { } else {
goSend(addr, amount, message, coin, shapeshiftData); goSend(addr, amount, message, coin, serviceId, serviceData);
} }
} }
); );
@ -377,7 +387,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
'notify': $state.current.name == 'tabs.send' ? false : true 'notify': $state.current.name == 'tabs.send' ? false : true
}); });
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.amount', { $state.transitionTo('tabs.send.origin', {
toAddress: toAddress, toAddress: toAddress,
coin: coin, coin: coin,
noPrefix: 1 noPrefix: 1
@ -385,38 +395,61 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
}, 100); }, 100);
} }
function createBchPayProObject(payProData) { function handlePayPro(payProData, coin) {
var displayAddr = payProData.outputs[0].address;
var toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy; console.log(payProData);
return {
amount: payProData.outputs[0].amount, var toAddr = payProData.toAddress;
var amount = payProData.amount;
var paymentUrl = payProData.url;
var expires = payProData.expires;
var time = payProData.time;
if (coin === 'bch') {
var displayAddr = payProData.outputs[0].address;
toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
amount = payProData.outputs[0].amount;
paymentUrl = payProData.paymentUrl;
expires = Math.floor(new Date(expires).getTime() / 1000)
time = Math.ceil(new Date(time).getTime() / 1000)
}
var name = payProData.domain;
if (payProData.memo.indexOf('eGifter') > -1) {
name = 'eGifter'
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
name = 'BitPay';
}
var thirdPartyData = {
id: 'bip70',
amount: amount,
caTrusted: true, caTrusted: true,
domain: 'bitpay.com', name: name,
expires: Math.floor(new Date(payProData.expires).getTime() / 1000), domain: payProData.domain,
expires: expires,
memo: payProData.memo, memo: payProData.memo,
network: 'livenet', network: 'livenet',
requiredFeeRate: payProData.requiredFeeRate, requiredFeeRate: payProData.requiredFeeRate,
selfSigned: 0, selfSigned: 0,
time: Math.ceil(new Date(payProData.time).getTime() / 1000), time: time,
displayAddress: displayAddr, displayAddress: displayAddr,
toAddress: toAddr, toAddress: toAddr,
url: payProData.paymentUrl, url: paymentUrl,
verified: true verified: true
}; };
}
function handlePayPro(payProDetails, coin) {
var stateParams = { var stateParams = {
toAmount: payProDetails.amount, amount: thirdPartyData.amount,
toAddress: payProDetails.toAddress, toAddress: thirdPartyData.toAddress,
description: payProDetails.memo,
paypro: payProDetails,
coin: coin, coin: coin,
thirdParty: JSON.stringify(thirdPartyData)
}; };
// fee // fee
if (payProDetails.requiredFeeRate) { if (thirdPartyData.requiredFeeRate) {
stateParams.requiredFeeRate = payProDetails.requiredFeeRate * 1024; stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
} }
scannerService.pausePreview(); scannerService.pausePreview();
@ -425,7 +458,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
'notify': $state.current.name == 'tabs.send' ? false : true 'notify': $state.current.name == 'tabs.send' ? false : true
}).then(function() { }).then(function() {
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.confirm', stateParams); $state.transitionTo('tabs.send.origin', stateParams);
}); });
}); });
} }

View file

@ -0,0 +1,78 @@
'use strict';
// For BIP70 Payment Protocol
angular
.module('copayApp.services')
.factory('payproService', payproService);
function payproService(gettextCatalog, $http, $log, ongoingProcess, platformInfo, profileService) {
var service = {
getPayProDetails: getPayProDetails,
getPayProDetailsViaHttp: getPayProDetailsViaHttp,
broadcastBchTx: broadcastBchTx
};
return service;
function getPayProDetails(uri, coin, cb, disableLoader) {
if (!cb) cb = function() {};
var wallet = profileService.getWallets({
onlyComplete: true,
coin: coin
})[0];
if (!wallet) return cb();
if (platformInfo.isChromeApp) {
return cb(gettextCatalog.getString('Payment Protocol not supported on Chrome App'));
}
$log.debug('Fetch PayPro Request...', uri);
if (!disableLoader) ongoingProcess.set('fetchingPayPro', true);
wallet.fetchPayPro({
payProUrl: uri,
}, function(err, paypro) {
if (!disableLoader) ongoingProcess.set('fetchingPayPro', false);
if (err) return cb(gettextCatalog.getString('Could Not Fetch Payment: Check if it is still valid'));
else if (!paypro.verified) {
$log.warn('Failed to verify payment protocol signatures');
return cb(gettextCatalog.getString('Payment Protocol Invalid'));
}
return cb(null, paypro);
});
}
function getPayProDetailsViaHttp(uri, cb) {
var config = {
headers: {'Accept': 'application/payment-request'}
};
$http.get(uri, config).then(function onGetPayProDetailsSuccess(response) {
return cb(null, response.data);
}, function onGetPayProDetailsError(error) {
return cb(error, null);
});
}
function broadcastBchTx(signedTxp, cb) {
var config = {
headers: {'Content-Type': 'application/payment'}
};
var data = {
currency: 'BCH',
transactions: [signedTxp.raw]
};
$http.post(signedTxp.payProUrl, data, config).then(function(response) {
signedTxp.response = response.data;
return cb(null, signedTxp);
}, function(error) {
return cb(error.data, null);
});
}
}

View file

@ -1,69 +0,0 @@
'use strict';
angular.module('copayApp.services').factory('payproService',
function(profileService, platformInfo, gettextCatalog, ongoingProcess, $log, $http) {
var ret = {};
ret.getPayProDetails = function(uri, coin, cb, disableLoader) {
if (!cb) cb = function() {};
var wallet = profileService.getWallets({
onlyComplete: true,
coin: coin
})[0];
if (!wallet) return cb();
if (platformInfo.isChromeApp) {
return cb(gettextCatalog.getString('Payment Protocol not supported on Chrome App'));
}
$log.debug('Fetch PayPro Request...', uri);
if (!disableLoader) ongoingProcess.set('fetchingPayPro', true);
wallet.fetchPayPro({
payProUrl: uri,
}, function(err, paypro) {
if (!disableLoader) ongoingProcess.set('fetchingPayPro', false);
if (err) return cb(gettextCatalog.getString('Could Not Fetch Payment: Check if it is still valid'));
else if (!paypro.verified) {
$log.warn('Failed to verify payment protocol signatures');
return cb(gettextCatalog.getString('Payment Protocol Invalid'));
}
return cb(null, paypro);
});
};
ret.getPayProDetailsViaHttp = function(uri, cb) {
var config = {
headers: {'Accept': 'application/payment-request'}
};
$http.get(uri, config).then(function(response) {
return cb(null, response.data);
}, function(error) {
return cb(error, null);
});
}
ret.broadcastBchTx = function(signedTxp, cb) {
var config = {
headers: {'Content-Type': 'application/payment'}
};
var data = {
currency: 'BCH',
transactions: [signedTxp.raw]
};
$http.post(signedTxp.payProUrl, data, config).then(function(response) {
signedTxp.response = response.data;
return cb(null, signedTxp);
}, function(error) {
return cb(error.data, null);
});
}
return ret;
});

View file

@ -847,6 +847,13 @@ angular.module('copayApp.services')
}); });
} }
if (opts.hasNoFunds) {
ret = lodash.filter(ret, function(w) {
if (!w.status) return;
return (w.status.availableBalanceSat === 0);
});
}
if (opts.minAmount) { if (opts.minAmount) {
ret = lodash.filter(ret, function(w) { ret = lodash.filter(ret, function(w) {
if (!w.status) return; if (!w.status) return;

View file

@ -1,18 +1,139 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('shapeshiftService', function(gettextCatalog, servicesService) {
angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) {
var root = {}; var root = {};
root.ShiftState = 'Shift';
root.coinIn = '';
root.coinOut = '';
root.withdrawalAddress = '';
root.returnAddress = '';
root.amount = '';
root.marketData = {};
var servicesItem = { root.getMarketDataIn = function (coin) {
name: 'shapeshift', if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn);
title: gettextCatalog.getString('Shapeshift'), return root.getMarketData(coin, root.coinOut);
icon: 'icon-shapeshift', };
sref: 'tabs.shapeshift', root.getMarketDataOut = function (coin) {
if (coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn);
return root.getMarketData(root.coinIn, coin);
};
root.getMarketData = function (coinIn, coinOut, cb) {
root.coinIn = coinIn;
root.coinOut = coinOut;
if (root.coinIn === undefined || root.coinOut === undefined) return;
shapeshiftApiService
.marketInfo(root.coinIn, root.coinOut)
.then(function (marketData) {
root.marketData = marketData;
root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
if (cb) {
cb(marketData);
}
});
}; };
var register = function() { /*shapeshiftApiService.coins().then(function(coins){
servicesService.register(servicesItem); root.coins = coins;
root.coinIn = coins['BTC'].symbol;
root.coinOut = coins['BCH'].symbol;
root.getMarketData(root.coinIn, root.coinOut);
});*/
root.coins = {
'BTC': {name: 'Bitcoin', symbol: 'BTC'},
'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
}; };
register(); function checkForError(data) {
return root; if (data.err) return true;
return false;
}
root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) {
ongoingProcess.set('connectingShapeshift', true);
root.withdrawalAddress = withdrawalAddress;
root.returnAddress = returnAddress;
root.coinIn = coinIn;
root.coinOut = coinOut;
shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) {
var tx = ShapeShift();
var coin;
console.log("Starting");
tx.then(function (txData) {
console.log("Got txData", txData);
if (txData['fixedTxData']) {
txData = txData.fixedTxData;
if (checkForError(txData)) return cb(txData.err);
//console.log(txData)
var coinPair = txData.pair.split('_');
txData.depositType = coinPair[0].toUpperCase();
txData.withdrawalType = coinPair[1].toUpperCase();
coin = root.coins[txData.depositType].name.toLowerCase();
txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount;
root.txFixedPending = true;
} else if (txData['normalTxData']) {
txData = txData.normalTxData;
if (checkForError(txData)) return cb(txData.err);
coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase();
txData.depositQR = coin + ":" + txData.deposit;
} else if (txData['cancelTxData']) {
txData = txData.cancelTxData;
if (checkForError(txData)) return cb(txData.err);
if (root.txFixedPending) {
root.txFixedPending = false;
}
root.ShiftState = 'Shift';
}
root.depositInfo = txData;
//console.log(root.marketData);
//console.log(root.depositInfo);
var sendAddress = txData.depositQR;
if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0)
sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash');
ongoingProcess.set('connectingShapeshift', false);
root.ShiftState = 'Cancel';
//root.GetStatus();
//root.txInterval=$interval(root.GetStatus, 8000);
var shapeshiftData = {
coinIn: coinIn,
coinOut: coinOut,
toWalletId: root.toWalletId,
minAmount: root.marketData.minimum,
maxAmount: root.marketData.maxLimit,
orderId: root.depositInfo.orderId,
toAddress: txData.deposit
};
//
// if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
ongoingProcess.set('connectingShapeshift', false);
// return;
// }
cb(null, shapeshiftData);
});
})
};
function ShapeShift() {
if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root);
return shapeshiftApiService.NormalTx(root);
}
root.GetStatus = function () {
var address = root.depositInfo.deposit
shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function (data) {
root.DepositStatus = data;
if (root.DepositStatus.status === 'complete') {
$interval.cancel(root.txInterval);
root.depositInfo = null;
root.ShiftState = 'Shift'
}
});
};
}); });

View file

@ -884,7 +884,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var createAddress = function(wallet, cb) { var createAddress = function(wallet, cb) {
$log.debug('Creating address for wallet:', wallet.id); $log.debug('Creating address for wallet:', wallet.id);
wallet.createAddress({}, function(err, addr) { wallet.createAddress({}, function onWalletCreatedAddress(err, addr) {
if (err) { if (err) {
var prefix = gettextCatalog.getString('Could not create address'); var prefix = gettextCatalog.getString('Could not create address');
if (err instanceof errors.CONNECTION_ERROR || (err.message && err.message.match(/5../))) { if (err instanceof errors.CONNECTION_ERROR || (err.message && err.message.match(/5../))) {
@ -902,6 +902,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (err) return cb(err); if (err) return cb(err);
return cb(null, addr[0].address); return cb(null, addr[0].address);
}); });
return;
} }
return bwcError.cb(err, prefix, cb); return bwcError.cb(err, prefix, cb);
} }

View file

@ -0,0 +1,24 @@
.action-minor {
margin: 20px 14px;
font-size: 14px;
&.mt-negative {
margin-top: 0;
}
&.text-right {
text-align: right;
}
> .action-icon {
width: 15px;
height: 15px;
vertical-align: middle;
margin-right: 3px;
}
> .action-text {
vertical-align: middle;
color: #444444;
}
}

View file

@ -0,0 +1,27 @@
.address-frame {
background-color: #F8F8F8;
border: 0.5px solid #EDEBEB;
border-radius: 3px;
padding: 9px;
text-align: center;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
&.expanded {
white-space: pre-wrap;
word-break: break-all;
}
.prefix {
color: #000000;
}
.mid {
color: #919191;
}
.suffix {
color: #000000;
}
}

View file

@ -0,0 +1,5 @@
.card {
&.card-gutter-compact {
margin: 10px 12px;
}
}

View file

@ -1 +1,11 @@
@import "item";
@import "ion-content";
@import "card";
@import "header";
@import "content-frame";
@import "address-frame";
@import "action-minor";
@import "expand-content";
@import "fee-summary";
@import "amount.scss"; @import "amount.scss";

View file

@ -0,0 +1,11 @@
.content-frame {
&.negative-top {
margin-top: -40px;
.card {
&:first-child {
margin-top: 0;
}
}
}
}

View file

@ -0,0 +1,26 @@
.expand-content-frame {
position: relative;
.expand-content-trigger {
position: absolute;
top: 0;
transition: opacity 0.3s ease;
right: 0;
&.expand-content-revealed {
opacity: 0;
}
}
.expand-content {
opacity: 0;
transform-origin: 100% 0%;
transform: scale(0,0);
transition: opacity 0.3s ease, transform 0.3s ease;
&.expand-content-revealed {
opacity: 1;
transform: scale(1,1);
}
}
}

View file

@ -0,0 +1,40 @@
.fee-summary {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
padding: 5px 12px 15px;
box-sizing: border-box;
background-color: #F2F2F2;
&:before {
content: '';
position: absolute;
left: 0;
top: -15px;
width: 100%;
height: 15px;
background: linear-gradient(to bottom, rgba(242,242,242,0) 0%,rgba(242,242,242,1) 100%);
}
.amount {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
.fee-fiat {
&.positive {
color: #70955F;
}
&.negative {
color: #C24633;
}
}
.fee-crypto {
color: #A7A7A7;
}
}
}

View file

@ -0,0 +1,39 @@
.header {
padding: 29px 12px 61px;
background-color: $v-bitcoin-orange;
&.btc {
background-color: $v-bitcoin-core;
}
color: #FFFFFF;
.title {
font-size: 18px;
font-weight: 400;
line-height: 1em;
color: #FFFFFF;
text-align: center;
+ .content {
margin-top: 23px;
}
}
.content {
text-align: center;
p {
margin: 0;
line-height: 1em;
font-size: 18px;
&.large {
font-size: 29px;
font-weight: 600;
}
+ p {
margin-top: 8px;
}
}
}
}

View file

@ -0,0 +1,17 @@
/*
* Extends Ionic v1 ion-content
*/
ion-content {
&.bg-neutral {
background-color: #F2F2F2;
}
&.padded-bottom-cta {
bottom: 92px;
}
&.padded-bottom-cta-with-summary {
bottom: 134px;
}
}

View file

@ -0,0 +1,48 @@
/*
* Extends Ionic v1 item
*/
.item {
&.item-compact {
padding: 11px 13px;
}
&.item-gutterless {
padding: 0;
}
.item-content {
&.item-content-avatar {
min-height: 69px;
padding: 13px 11px 13px 68px;
> img,
> i {
&:first-child {
position: absolute;
max-width: 40px;
max-height: 40px;
width: 100%;
height: 100%;
border-radius: 50%;
left: 13px;
top: 50%;
padding: 0;
transform: translate(0,-50%);
}
}
}
&.item-content-compact {
min-height: 0;
padding: 13px 11px;
}
.highlight {
color: #FAB915
}
+ .item-content {
padding-top: 0;
}
}
}

View file

@ -1 +1,2 @@
@import "gravatar"; @import "gravatar";
@import "elastic";

View file

@ -0,0 +1,4 @@
.elastic {
width: 100%;
font-size: 14px;
}

View file

@ -88,6 +88,13 @@
background-image: url('../img/icon-faucet.svg'); background-image: url('../img/icon-faucet.svg');
background-size: 70%; background-size: 70%;
} }
&.icon-wallet {
background-color: #FAB915;
background-image: url('../img/icon-wallet.svg');
border: none;
box-shadow: 0 0 0 1px rgba(0,0,0,0.3) inset;
}
} }
} }

View file

@ -18,3 +18,53 @@
.absolute-center{ .absolute-center{
@include absolute-center(); @include absolute-center();
} }
.third-party-notice {
font-size: 12px;
margin: 0px 14px;
font-weight: 600;
color: #6F6F70;
@media (min-width: 768px) {
text-align: center;
}
}
@mixin empty-case() {
padding-top: 5vh;
text-align: center;
.item {
border-style: none;
}
& > .title {
font-size: 20px;
color: $v-dark-gray;
margin: 20px 10px;
}
& > .subtitle {
font-size: 1rem;
line-height: 1.5em;
font-weight: 300;
color: #6F6F70;
margin: 20px 1em 2.5em;
}
.big-icon-svg {
.bg.green {
padding: 0 10px;
box-shadow: none;
}
}
.buttons {
margin-top: 18px;
.button {
font-weight: bold;
font-size: 19px;
}
}
.button-first-contact img {
height: 19px;
width: 19px;
margin-right: 6px;
vertical-align: sub;
}
}

View file

@ -8,6 +8,7 @@ $v-font-family-light: "Roboto-Light", sans-serif-
/* Colors */ /* Colors */
$v-bitcoin-orange: #fab915 !default; $v-bitcoin-orange: #fab915 !default;
$v-bitcoin-core: #535353 !default;
$v-off-black: #262424; $v-off-black: #262424;
$v-dark-gray: #445 !default; $v-dark-gray: #445 !default;

View file

@ -254,6 +254,9 @@
padding: 0 6px 6px 6px; padding: 0 6px 6px 6px;
text-align: center; text-align: center;
} }
&__max {
float: right;
}
} }
.send-amount-tool { .send-amount-tool {

View file

@ -0,0 +1,21 @@
#view-review {
background-color: #494949;
slide-to-accept, slide-to-accept-success {
margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */
margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */
}
.fee-summary {
position: absolute;
bottom: 92px;
}
.shapeshift-banner, .bitpay-banner, .egifter-banner {
box-shadow: none;
}
.warning {
color: $v-warning-color-2;
}
}

View file

@ -0,0 +1,23 @@
#shapeshift {
.swap-image {
width: auto;
max-width: 400px;
max-height: 25vh;
}
.empty-case {
@include empty-case();
}
.button-shapeshift {
@extend %button-standard;
@include button-style(#243F5D, #FFF, #606060, #FFF, #FFF);
@include button-clear(#FFF);
@include button-outline(#C1C1C1);
border: 0px;
@include button-shadow();
}
}
.header.shapeshift {
background: url(../img/shapeshiftbg.jpg) center center repeat #28394d;
opacity: 0.99;
}

View file

@ -88,6 +88,8 @@
&.contains-address { &.contains-address {
.address { .address {
display: inline; display: inline;
border: none;
background-color: transparent;
} }
.non-address { .non-address {
display: none; display: none;
@ -133,42 +135,7 @@
padding-left: 30px; padding-left: 30px;
} }
.sendTip { .sendTip {
padding-top: 5vh; @include empty-case();
text-align: center;
.item {
border-style: none;
}
& > .title {
font-size: 20px;
color: $v-dark-gray;
margin: 20px 10px;
}
& > .subtitle {
font-size: 1rem;
line-height: 1.5em;
font-weight: 300;
color: #6F6F70;
margin: 20px 1em 2.5em;
}
.big-icon-svg {
.bg.green {
padding: 0 10px;
box-shadow: none;
}
}
.buttons {
margin-top: 18px;
.button {
font-weight: bold;
font-size: 19px;
}
}
.button-first-contact img {
height: 19px;
width: 19px;
margin-right: 6px;
vertical-align: sub;
}
} }
.item-heading { .item-heading {
line-height: 16px; line-height: 16px;

View file

@ -8,11 +8,13 @@
@import "tab-receive"; @import "tab-receive";
@import "tab-scan"; @import "tab-scan";
@import "tab-send"; @import "tab-send";
@import "wallet-origin-destination";
@import "tab-settings"; @import "tab-settings";
@import "wallet-colors"; @import "wallet-colors";
@import "walletBalance"; @import "walletBalance";
@import "walletDetails"; @import "walletDetails";
@import "advancedSettings"; @import "advancedSettings";
@import "shapeshift";
@import "bitpayCard"; @import "bitpayCard";
@import "bitpayCardIntro"; @import "bitpayCardIntro";
@import "buyandsell"; @import "buyandsell";
@ -48,3 +50,4 @@
@import "includes/logOptions"; @import "includes/logOptions";
@import "includes/checkBar"; @import "includes/checkBar";
@import "cashScan"; @import "cashScan";
@import "review";

View file

@ -0,0 +1,74 @@
#wallet-origin-destination {
.header--request {
padding: 30px 24px;
width: 100%;
height: 139px;
background-color: #fff;
&__title {
width: 46px;
height: 20px;
font-size: 16px;
font-weight: 600;
letter-spacing: -0.4px;
color: #000000;
}
&__amount {
font-size: 29px;
font-weight: 600;
letter-spacing: -0.7px;
color: #000000;
margin: 11px 0 2px;
}
&__amount-alt {
opacity: 0.45;
font-size: 16px;
font-weight: 600;
letter-spacing: -0.4px;
color: #000000;
}
}
.wallets-header {
margin: 20px 14px 0px;
.title {
font-size: 16px;
font-weight: bold;
color: $v-dark-gray;
margin-bottom: -12px;
}
}
.card {
font-size: 12px;
margin: 20px 14px 0px;
.item-heading {
.subtitle {
font-size: 12px;
}
font-weight: 600;
}
&-insufficient {
.wallet {
opacity: 0.4;
}
.item-heading {
font-size: 12px;
>div {
display: inline-block;
vertical-align: text-bottom;
}
}
&__dot {
display: inline-block;
width: 16px;
height: 16px;
background-color: #ec5959;
border-radius: 8px;
margin: 2px 6px 2px 2px;
}
}
}
}

View file

@ -318,5 +318,33 @@ div.slide-success__background.fill-screen {
display: block; display: block;
float: left; float: left;
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
}
.bitpay-banner {
background: #1A3A8B;
padding: 10px;
box-shadow: 0px 5px 10px 0px #cccccc;
height: 5em;
}
.bitpay-logo {
display: block;
max-height: 100%;
width: 100%;
height: 4em;
}
.egifter-banner {
background: #066EAA;
padding: 10px;
box-shadow: 0px 5px 10px 0px #cccccc;
height: 5em;
text-align: center;
}
.egifter-logo {
max-height: 100%;
max-width: 100%;
height: 4em;
} }

File diff suppressed because it is too large Load diff

13
www/img/bitpay_banner.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
www/img/egifter_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

11
www/img/icon-bookmark.svg Normal file
View file

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11.25 15">
<defs>
<style>
.cls-1 {
fill: #444;
opacity: 0.564;
}
</style>
</defs>
<path id="_ionicons_svg_md-bookmark" class="cls-1" d="M121.688,64h-8.125A1.567,1.567,0,0,0,112,65.563V79l5.625-2.5L123.25,79V65.563A1.567,1.567,0,0,0,121.688,64Z" transform="translate(-112 -64)"/>
</svg>

After

Width:  |  Height:  |  Size: 381 B

BIN
www/img/icon-egifter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
www/img/shapeshift_swap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View file

@ -6,21 +6,17 @@
<ion-nav-back-button ng-click="vm.goBack()"></ion-nav-back-button> <ion-nav-back-button ng-click="vm.goBack()"></ion-nav-back-button>
</ion-nav-bar> </ion-nav-bar>
<ion-content scroll="false"> <ion-content scroll="false">
<div ng-if="vm.thirdParty && vm.thirdParty.id === 'shapeshift'" ng-include="'views/thirdparty/shapeshift-header.html'"></div>
<div style="order: 0; position: relative;"> <div style="order: 0; position: relative;">
<div class="card item send-amount"> <div class="card item send-amount">
<div class="send-amount-header-footer"> <div class="send-amount-header-footer">
<div ng-if="vm.shapeshiftOrderId"> <span class="send-amount-header-footer__min" ng-if="vm.minAmount">Min: {{vm.minAmount}}</span> <span class="send-amount-header-footer__max" ng-if="vm.maxAmount">Max: {{vm.maxAmount}}</span>
Minimum amount: {{vm.minShapeshiftAmount}} <br/>
Maximum amount: {{vm.maxShapeshiftAmount}} <br/>
</div>
</div> </div>
<div class="send-amount-tool"> <div class="send-amount-tool">
<div class="send-amount-tool-input amount"> <div class="send-amount-tool-input amount">
<div class="primary-amount" <div class="primary-amount"
ng-class="{long: vm.amount.length > 5, 'very-long': vm.amount.length > 10}"> ng-class="{long: vm.amount.length > 5, 'very-long': vm.amount.length > 10}">
<span class="primary-amount-display text-selectable">{{vm.amount}} {{vm.unit}}</span> <span class="primary-amount-display text-selectable">{{vm.amount || '0'}} {{vm.unit}}</span>
</div> </div>
<span ng-show="vm.globalResult">{{vm.globalResult}} {{vm.unit}}</span> <span ng-show="vm.globalResult">{{vm.globalResult}} {{vm.unit}}</span>
<div class="alternative-amount"> <div class="alternative-amount">
@ -29,8 +25,8 @@
<div class="switch-currencies" ng-click="vm.changeUnit()"><img src="img/icon-convert.svg"></div> <div class="switch-currencies" ng-click="vm.changeUnit()"><img src="img/icon-convert.svg"></div>
</div> </div>
<div class="send-amount-header-footer"> <div class="send-amount-header-footer">
<div class="warning" ng-show="vm.fundsAreInsufficient"> <div class="warning" ng-show="vm.errorMessage">
Not enough available funds {{vm.errorMessage}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -105,13 +105,13 @@
</ion-content> </ion-content>
<click-to-accept <click-to-accept
ng-click="approve(tx, wallet, statusChangeHandler)" ng-click="approve(tx, wallet, statusChangeHandler)"
ng-if="(!isCordova || isWindowsPhoneApp) && wallet" ng-if="(!isCordova) && wallet"
click-send-status="sendStatus" click-send-status="sendStatus"
is-disabled="!wallet"> is-disabled="!wallet">
{{buttonText}} {{buttonText}}
</click-to-accept> </click-to-accept>
<slide-to-accept <slide-to-accept
ng-if="isCordova && !isWindowsPhoneApp && wallet" ng-if="isCordova && wallet"
slide-on-confirm="approve(tx, wallet, statusChangeHandler)" slide-on-confirm="approve(tx, wallet, statusChangeHandler)"
slide-send-status="sendStatus" slide-send-status="sendStatus"
is-disabled="!wallet"> is-disabled="!wallet">

119
www/views/review.html Normal file
View file

@ -0,0 +1,119 @@
<ion-view id="view-review" hide-tabs>
<ion-nav-bar class="bar-royal {{vm.origin.currency.toLowerCase()}}">
<ion-nav-title>
{{'Review Transaction' | translate}}
</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content class="padded-bottom-cta-with-summary bg-neutral">
<div ng-if="vm.thirdParty && vm.thirdParty.id === 'shapeshift'" ng-include="'views/thirdparty/shapeshift-header.html'"></div>
<div class="header {{vm.origin.currency.toLowerCase()}}" ng-class="vm.thirdParty.id">
<div class="content">
<p>{{vm.sendingTitle}}</p>
<p class="large">{{vm.primaryAmount}} {{vm.primaryCurrency}}</p>
<p ng-show="vm.secondaryAmount">{{vm.secondaryAmount}} {{vm.secondaryCurrency}}</p>
</div>
</div>
<div class="content-frame negative-top">
<div class="card card-gutter-compact">
<div class="item item-compact" translate>From:</div>
<div class="item item-gutterless item-complex item-avatar">
<div class="item-content item-content-avatar">
<i class="icon big-icon-svg theme-circle theme-circle-services">
<div class="bg icon-wallet"
style="background-color: {{vm.originWallet.color}}"
></div>
</i>
<h2>{{vm.originWallet.name}} <span class="highlight" style="color: {{vm.origin.currencyColor}}">({{vm.origin.currency}})</span></h2>
<p ng-show="vm.origin.balanceAmount">{{vm.origin.balanceAmount}} {{vm.origin.balanceCurrency}}</p>
</div>
</div>
</div>
<div class="card card-gutter-compact">
<div class="item item-compact" translate>To:</div>
<div class="item item-gutterless item-complex item-avatar">
<div class="item-content item-content-avatar"
ng-if="vm.destination.kind === 'contact' || vm.destination.kind === 'wallet' || vm.destination.kind == 'shapeshift'">
<img src="img/contact-placeholder.svg" class="bg" ng-if="vm.destination.kind === 'contact'">
<i class="icon big-icon-svg theme-circle theme-circle-services" ng-if="vm.destination.kind === 'wallet' || vm.destination.kind === 'shapeshift'">
<div class="bg icon-wallet"
style="background-color: {{vm.destination.color}}"
></div>
</i>
<h2>{{vm.destination.name}}<span class="highlight" style="color: {{vm.destination.currencyColor}}" ng-if="vm.destination.currency"> ({{vm.destination.currency}})</span></h2></h2>
<p ng-if="vm.destination.balanceAmount">{{vm.destination.balanceAmount}} {{vm.destination.balanceCurrency}}</p>
</div>
<div class="item-content item-content-avatar"
ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70'
&& (vm.thirdParty.name === 'BitPay' || vm.thirdParty.name === 'eGifter')">
<img ng-if="vm.thirdParty.name === 'BitPay'" src="img/icon-bitpay.svg" class="bg">
<img ng-if="vm.thirdParty.name === 'eGifter'" src="img/icon-egifter.png" class="bg">
<h2>{{vm.destination.name}}</h2>
<p translate ng-if="!vm.paymentExpired">Payment expires: {{vm.remainingTimeStr}}</p>
<p class="warning" translate ng-if="vm.paymentExpired">Payment request has expired</p>
</div>
<div class="item-content item-content-compact" ng-init="addressExpanded = false" ng-if="vm.destination.kind === 'address' && !vm.thirdParty">
<div class="address-frame" ng-class="{ 'expanded': addressExpanded }" ng-click="addressExpanded = !addressExpanded">
<span class="prefix">{{vm.destination.address.substring(0,5)}}</span><span class="mid">{{vm.destination.address.substring(5,vm.destination.address.length-4)}}</span><span class="suffix">{{vm.destination.address.substring(vm.destination.address.length-4)}}</span>
</div>
</div>
</div>
</div>
<div class="expand-content-frame">
<div class="action-minor mt-negative text-right expand-content-trigger"
ng-class="{ 'expand-content-revealed': vm.memoExpanded }"
ng-click="vm.memoExpanded = !vm.memoExpanded">
<img src="img/icon-bookmark.svg" class="action-icon">
<span class="action-text">Add personal note</span>
</div>
<div class="card card-gutter-compact expand-content"
ng-class="{ 'expand-content-revealed': vm.memoExpanded }">
<div class="item item-compact" translate>Personal Note:</div>
<div class="item">
<div class="item-content">
<textarea elastic placeholder="{{btx.note.body || btx.message || 'Enter text here'}}" class="elastic" ng-model="vm.memo"></textarea>
</div>
</div>
</div>
</div>
</div>
</ion-content>
<div class="fee-summary">
<div ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70'" translate="">Suggested by merchant:</div>
<div class="amount">
<div class="fee-fiat positive" ng-if="vm.feeLessThanACent">Fee: Less than 1 cent</div>
<div class="fee-fiat" ng-class="vm.feeIsHigh ? 'negative' : 'positive'" ng-if="!vm.feeLessThanACent">Fee: {{vm.feeFiat}} {{vm.feeCurrency}}</div>
<div class="fee-crypto" ng-if="vm.feeCrypto">
{{vm.feeCrypto}} {{vm.origin.currency}}
</div>
</div>
</div>
<click-to-accept
ng-click="vm.approve()"
ng-if="!vm.isCordova"
click-send-status="vm.sendStatus"
is-disabled="!vm.readyToSend">
{{vm.buttonText}}
</click-to-accept>
<slide-to-accept
ng-if="vm.isCordova"
slide-on-confirm="vm.approve()"
slide-send-status="vm.sendStatus"
is-disabled="!vm.readyToSend">
{{vm.buttonText}}
</slide-to-accept>
<slide-to-accept-success
slide-success-show="vm.sendStatus === 'success'"
slide-success-on-confirm="vm.onSuccessConfirm()"
slide-success-hide-on-confirm="true">
<span ng-show="vm.originWallet.m == 1 && (vm.originWallet.canSign() || vm.originWallet.isPrivKeyExternal())" translate>Payment Sent</span>
<span ng-show="vm.originWallet.m > 1 && (vm.originWallet.canSign() || vm.originWallet.isPrivKeyExternal())" translate>Proposal Created</span>
<span ng-show="!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()" translate>Transaction Created</span>
</slide-to-accept-success>
</ion-view>

View file

@ -1,4 +1,4 @@
<ion-view class="settings" show-tabs> <ion-view id="shapeshift" class="settings" show-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title> <ion-nav-title>
{{'Shapeshift'|translate}} {{'Shapeshift'|translate}}
@ -7,14 +7,29 @@
</ion-nav-back-button> </ion-nav-back-button>
</ion-nav-bar> </ion-nav-bar>
<ion-content> <ion-content>
<div class="send-header-wrapper shapeshift-banner"> <div ng-include="'views/thirdparty/shapeshift-header.html'"></div>
<img class="shapeshift-logo" src="img/shapeshiftlogo.svg"/> <div class="list card empty-case">
</div> <div>
<div class="list card ng-hide" ng-show="fromWallets.length == 0 || toWallets.length == 0"> <img class="swap-image" src="img/shapeshift_swap.png"/>
<div class="item item-heading"> </div>
<span translate>No available wallets to convert between.</span> <div class="subtitle">
<p translate>Exchange your BTC to BCH in minutes.</p>
<div ng-show="!walletsWithFunds.length">
<p translate>To start the process you need to add funds to your wallet.</p>
</div>
<div ng-show="walletsWithFunds.length">
<p translate>The process is fast and you will receive the exchanged amount in your wallet.</p>
</div>
<div ng-show="!hasWallets" translate>To get started, you'll need to create a bitcoin wallet and get some bitcoin.</div>
<div class="padding buttons">
<button class="button button-standard button-green" ng-click="buyBitcoin()" ng-show="!walletsWithFunds.length" translate>Buy Bitcoin now</button>
<button class="button button-standard button-green" ng-click="createWallet()" ng-show="!hasWallets" translate>Create bitcoin wallet</button>
<button class="button button-standard button-shapeshift track_shapeshift_start_click" ng-click="shapeshift()" ng-show="walletsWithFunds.length" translate>Start ShapeShift</button>
</div>
</div> </div>
</div> </div>
<div class="third-party-notice" translate>This service is provided by the third-party ShapeShift, who will charge a small fee for the service. The fee will be shown before you start the transaction.</div>
<shapeshift-coin-trader class="ng-hide" ng-show="fromWallets.length > 0 && toWallets.length > 0"> <shapeshift-coin-trader class="ng-hide" ng-show="fromWallets.length > 0 && toWallets.length > 0">
<div class="list card"> <div class="list card">
<div class="item item-heading"> <div class="item item-heading">
@ -94,21 +109,21 @@
</shapeshift-coin-trader> </shapeshift-coin-trader>
</ion-content> </ion-content>
<wallet-selector <!--<wallet-selector-->
wallet-selector-title="fromWalletSelectorTitle" <!--wallet-selector-title="fromWalletSelectorTitle"-->
wallet-selector-wallets="fromWallets" <!--wallet-selector-wallets="fromWallets"-->
wallet-selector-selected-wallet="fromWallet" <!--wallet-selector-selected-wallet="fromWallet"-->
wallet-selector-show="showFromWallets" <!--wallet-selector-show="showFromWallets"-->
wallet-selector-on-select="onFromWalletSelect" <!--wallet-selector-on-select="onFromWalletSelect"-->
wallet-selector-always-display-bitcoin-core="true"> <!--wallet-selector-always-display-bitcoin-core="true">-->
</wallet-selector> <!--</wallet-selector>-->
<wallet-selector <!--<wallet-selector-->
wallet-selector-title="toWalletSelectorTitle" <!--wallet-selector-title="toWalletSelectorTitle"-->
wallet-selector-wallets="toWallets" <!--wallet-selector-wallets="toWallets"-->
wallet-selector-selected-wallet="toWallet" <!--wallet-selector-selected-wallet="toWallet"-->
wallet-selector-show="showToWallets" <!--wallet-selector-show="showToWallets"-->
wallet-selector-on-select="onToWalletSelect" <!--wallet-selector-on-select="onToWalletSelect"-->
wallet-selector-always-display-bitcoin-core="true"> <!--wallet-selector-always-display-bitcoin-core="true">-->
</wallet-selector> <!--</wallet-selector>-->
</ion-view> <!--</ion-view>-->

View file

@ -22,7 +22,7 @@
</button> </button>
</div> </div>
<div class="col-60"> <div class="col-60">
<button class="button button-standard button-primary button-outline" ng-click="showWalletSelector()"> <button class="button button-standard button-primary button-outline" ui-sref="tabs.send.wallet-to-wallet">
<img src="img/icon-w2w.svg"/><br/> <img src="img/icon-w2w.svg"/><br/>
<span translate>Wallet to Wallet Transfer</span> <span translate>Wallet to Wallet Transfer</span>
</button> </button>
@ -90,7 +90,7 @@
</div> </div>
<div class="list"> <div class="list">
<a class="item item-icon-left item-icon-right" ng-repeat="item in list" <a class="item item-icon-left item-icon-right" ng-repeat="item in list"
ng-if="!item.isWallet && item.recipientType != 'wallet'" ng-click="goToAmount(item)"> ng-if="!item.isWallet && item.recipientType != 'wallet'" ng-click="sendToContact(item)">
<i class="icon big-icon-svg"> <i class="icon big-icon-svg">
<gravatar class="send-gravatar" name="{{item.name}}" width="120" email="{{item.email}}"></gravatar> <gravatar class="send-gravatar" name="{{item.name}}" width="120" email="{{item.email}}"></gravatar>
</i> </i>
@ -105,31 +105,31 @@
</div> </div>
</div> </div>
</ion-content> </ion-content>
<wallet-selector <!--<wallet-selector-->
wallet-selector-title="walletSelectorTitleFrom" <!--wallet-selector-title="walletSelectorTitleFrom"-->
wallet-selector-force-title="walletSelectorTitleForce" <!--wallet-selector-force-title="walletSelectorTitleForce"-->
wallet-selector-wallets="walletsWithFunds" <!--wallet-selector-wallets="walletsWithFunds"-->
wallet-selector-selected-wallet="wallet" <!--wallet-selector-selected-wallet="wallet"-->
wallet-selector-show="showWallets" <!--wallet-selector-show="showWallets"-->
wallet-selector-on-select="onWalletSelect" <!--wallet-selector-on-select="onWalletSelect"-->
wallet-selector-display-balance-as-fiat="displayBalanceAsFiat"> <!--wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">-->
</wallet-selector> <!--</wallet-selector>-->
<wallet-selector <!--<wallet-selector-->
wallet-selector-on-hide="" <!--wallet-selector-on-hide=""-->
wallet-selector-title="walletSelectorTitleTo" <!--wallet-selector-title="walletSelectorTitleTo"-->
wallet-selector-wallets="walletsBch" <!--wallet-selector-wallets="walletsBch"-->
wallet-selector-selected-wallet="wallet" <!--wallet-selector-selected-wallet="wallet"-->
wallet-selector-show="showWalletsBch" <!--wallet-selector-show="showWalletsBch"-->
wallet-selector-on-select="onWalletSelect" <!--wallet-selector-on-select="onWalletSelect"-->
wallet-selector-display-balance-as-fiat="displayBalanceAsFiat"> <!--wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">-->
</wallet-selector> <!--</wallet-selector>-->
<wallet-selector <!--<wallet-selector-->
wallet-selector-on-hide="" <!--wallet-selector-on-hide=""-->
wallet-selector-title="walletSelectorTitleTo" <!--wallet-selector-title="walletSelectorTitleTo"-->
wallet-selector-wallets="walletsBtc" <!--wallet-selector-wallets="walletsBtc"-->
wallet-selector-selected-wallet="wallet" <!--wallet-selector-selected-wallet="wallet"-->
wallet-selector-show="showWalletsBtc" <!--wallet-selector-show="showWalletsBtc"-->
wallet-selector-on-select="onWalletSelect" <!--wallet-selector-on-select="onWalletSelect"-->
wallet-selector-display-balance-as-fiat="displayBalanceAsFiat"> <!--wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">-->
</wallet-selector> <!--</wallet-selector>-->
</ion-view> </ion-view>

View file

@ -0,0 +1,3 @@
<div class="send-header-wrapper bitpay-banner">
<img class="bitpay-logo" src="img/bitpay_banner.svg"/>
</div>

View file

@ -0,0 +1,3 @@
<div class="send-header-wrapper egifter-banner">
<img class="egifter-logo" src="img/egifter_banner.png"/>
</div>

View file

@ -0,0 +1,3 @@
<div class="send-header-wrapper shapeshift-banner">
<img class="shapeshift-logo" src="img/shapeshiftlogo.svg"/>
</div>

View file

@ -0,0 +1,59 @@
<ion-view id="wallet-origin-destination" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{sendFlowTitle}}</ion-nav-title>
<ion-nav-back-button ng-click="goBack()"></ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<div ng-if="thirdParty && thirdParty.id === 'shapeshift'" ng-include="'views/thirdparty/shapeshift-header.html'"></div>
<div ng-if="thirdParty && thirdParty.id === 'bip70' && thirdParty.name === 'BitPay'" ng-include="'views/thirdparty/bitpay-header.html'"></div>
<div ng-if="thirdParty && thirdParty.id === 'bip70' && thirdParty.name === 'eGifter'" ng-include="'views/thirdparty/egifter-header.html'"></div>
<div class="header--request" ng-if="isPaymentRequest">
<div class="header--request__title" translate>Paying</div>
<div class="header--request__amount" translate>{{requestAmount}} {{requestCurrency}}</div>
<div class="header--request__amount-alt" ng-show="requestAmountSecondary" translate>{{requestAmountSecondary}} {{requestCurrencySecondary}}</div>
</div>
<div class="wallets-header">
<div class="title">
{{headerTitle}}
</div>
</div>
<div class="list card" ng-if="walletsBch.length > 0">
<div class="item item-icon-right item-heading">
<div translate>Bitcoin Cash (BCH)</div>
<div translate class="subtitle">Instant transactions with low fees</div>
</div>
<div>
<a ng-repeat="wallet in walletsBch track by $index"
class="item item-sub item-icon-left item-big-icon-left item-icon-right wallet"
ng-click="useWallet(wallet)">
<span ng-include="'views/includes/walletList.html'"></span>
</a>
</div>
</div>
<div class="list card" ng-if="walletsBtc.length > 0">
<div class="item item-icon-right item-heading">
<div translate>Bitcoin Core (BTC)</div>
</div>
<div>
<a ng-repeat="wallet in walletsBtc track by $index"
class="item item-sub item-icon-left item-big-icon-left item-icon-right wallet"
ng-click="useWallet(wallet)">
<span ng-include="'views/includes/walletList.html'"></span>
</a>
</div>
</div>
<div class="list card card-insufficient" ng-if="walletsInsufficientFunds.length > 0">
<div class="item item-icon-right item-heading">
<span class="card-insufficient__dot"></span><div translate>Insufficient funds</div>
</div>
<div>
<a ng-repeat="wallet in walletsInsufficientFunds track by $index"
class="item item-sub item-icon-left item-big-icon-left item-icon-right wallet">
<span ng-include="'views/includes/walletList.html'"></span>
</a>
</div>
</div>
</ion-content>
</ion-view>