Compare commits

..

8 commits

Author SHA1 Message Date
A R Hansen
8e73e4c16d Merge branch 'develop' 2018-10-12 12:50:54 +02:00
A R Hansen
06f1f4df91 - ll - 2018-10-12 12:48:15 +02:00
A R Hansen
70f520ea43 adding instructions 2018-10-12 12:46:09 +02:00
A R Hansen
f1d2b00b2b set up my own bitcore server and changed the url. for some reason Bitcoin.com's server won't accept 0 fee txs. 2018-10-12 11:53:16 +02:00
A R Hansen
4e524a7500 Not needed. 2018-10-08 02:16:43 +02:00
A R Hansen
c0a14fe11c Merge ZeroFeeTxs 2018-10-08 02:01:15 +02:00
A R Hansen
d47393f160 .. 2018-10-08 02:00:36 +02:00
A R Hansen
898b7d03fe 0 fee transaction feature added. 2018-10-08 01:55:55 +02:00
21 changed files with 361 additions and 333 deletions

14
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/www/index.html"
}
]
}

View file

@ -57,7 +57,7 @@ module.exports = function(grunt) {
command: 'node ./util/buildExternalServices.js' command: 'node ./util/buildExternalServices.js'
}, },
get_nwjs_for_pkg: { get_nwjs_for_pkg: {
command: 'if [ ! -d ./cache/0.19.4/osx64/nwjs.app ]; then mkdir -p ./cache/0.19.4/osx64; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output ./cache/nwjs.zip; unzip ./cache/nwjs.zip -d ./cache; cp -R ./cache/nwjs-mas-v0.19.5-osx-x64/nwjs.app ./cache/0.19.4/osx64/; fi' command: 'if [ ! -d ./cache/0.19.5-pkg/osx64/nwjs.app ]; then cd ./cache; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output nwjs.zip; unzip nwjs.zip; mkdir -p ./0.19.5-pkg/osx64; cp -R ./nwjs-mas-v0.19.5-osx-x64/nwjs.app ./0.19.5-pkg/osx64/; fi'
}, },
log_android: { log_android: {
command: 'adb logcat | grep chromium', command: 'adb logcat | grep chromium',
@ -71,7 +71,7 @@ module.exports = function(grunt) {
sign_android: { sign_android: {
// When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release". // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release".
// It looks like it simply lists all apk files starting with "android-release" // It looks like it simply lists all apk files starting with "android-release"
command: 'rm -f platforms/android/build/outputs/apk/release/*-android-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/release/android-release-signed.apk platforms/android/build/outputs/apk/release/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/release/android-release-signed.apk platforms/android/build/outputs/apk/release/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk', command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk',
stdin: true, stdin: true,
}, },
sign_desktop_dist: { sign_desktop_dist: {

View file

@ -1,3 +1,24 @@
This is a fork of the Bitcoin.com wallet to add additional features.
Features included:
- Zero fee transactions (only works for Bitcoin Cash). You will be asked for, if you want to send a transaction as zero fee on the confirmation page.
## Zero fee transactions:
Because most network nodes on the Bitcoin Cash network don't relay zero fee txs, you will experience some strange issues, but don't worry: for me personally the Bitcoin.com pool has included all my zero fee transactions, but please beware that the receiver probably won't see your tx before it has been confirmed and please do also keep in mind, that the transactions coming after it won't confirm or be seen before the zero fee one has been confirmed.
If you do already have a Bitcoin.com wallet, you need to create a new one to use this feature or change the wallet URL to: https://bws.freepages.dk/bws/api
## Disclaimer
Please beware this is my personal experimental project. You are more than welcome to play with it, but I don't take any responsibility of loss of funds due to errors in the code, so please make sure you made a backup before running this software.
## Builds
You can build the software yourself using the instructions below or use prebuilt binaries which can be found here (currently Windows and Linux only): https://ipfs.io/ipfs/QmR1DaS3QsDS48SzAWKUWFfmtMfJc4tgMtkSk3JFmuzewe
##
The Bitcoin.com wallet is a fork of the Copay Wallet (https://github.com/bitpay/copay). The Bitcoin.com wallet is a fork of the Copay Wallet (https://github.com/bitpay/copay).
The Bitcoin.com wallet is a secure bitcoin wallet platform for both desktop and mobile devices. It uses [Bitcore Wallet Service](https://github.com/Bitcoin-com/bitcore-wallet-service) (our fork of the [Bitpay Bitcore Wallet Service](https://github.com/bitpay/bitcore-wallet-service)) (BWS) for peer synchronization and network interfacing. The Bitcoin.com wallet is a secure bitcoin wallet platform for both desktop and mobile devices. It uses [Bitcore Wallet Service](https://github.com/Bitcoin-com/bitcore-wallet-service) (our fork of the [Bitpay Bitcore Wallet Service](https://github.com/bitpay/bitcore-wallet-service)) (BWS) for peer synchronization and network interfacing.

View file

@ -24,9 +24,9 @@
"windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c",
"pushSenderId": "1036948132229", "pushSenderId": "1036948132229",
"description": "A Secure Bitcoin Wallet", "description": "A Secure Bitcoin Wallet",
"version": "5.1.6", "version": "5.1.3",
"fullVersion": "5.1-rc5", "fullVersion": "5.1-rc2",
"androidVersion": "501600", "androidVersion": "501003",
"_extraCSS": "", "_extraCSS": "",
"_enabledExtensions": { "_enabledExtensions": {
"coinbase": false, "coinbase": false,

View file

@ -77,7 +77,7 @@
</plugin> </plugin>
<!-- Supported Platforms --> <!-- Supported Platforms -->
<engine name="ios" spec="~4.5.3" /> <engine name="ios" spec="~4.5.3" />
<engine name="android" spec="~6.4.0" /> <engine name="android" spec="~6.3.0" />
<engine name="windows" spec="~5.0.0" /> <engine name="windows" spec="~5.0.0" />
<!-- Platform Specific Settings --> <!-- Platform Specific Settings -->

View file

@ -1,6 +1,5 @@
ext { ext {
ANDROID_SUPPORT_V4_VERSION = '26.1.0' ANDROID_SUPPORT_V4_VERSION = '26.1.0'
ANDROID_SUPPORT_ANNOTATIONS_VERSION = '26.1.0'
} }
configurations.all { configurations.all {

View file

@ -9,6 +9,30 @@ angular
function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, popupService, profileService, walletService, $window) { function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, popupService, profileService, walletService, $window) {
var vm = this; var vm = this;
// Variables
vm.allowSend = false;
vm.altCurrencyList = [];
vm.alternativeAmount = '';
vm.alternativeUnit = '';
vm.amount = '0';
vm.availableFunds = '';
vm.canSendAllAvailableFunds = true;
vm.errorMessage = '';
// Use insufficient for logic, as when the amount is invalid, funds being
// either sufficent or insufficient doesn't make sense.
vm.fundsAreInsufficient = false;
vm.globalResult = '';
vm.isRequestingSpecificAmount = false;
vm.listComplete = false;
vm.lastUsedPopularList = [];
vm.maxAmount = 0;
vm.minAmount = 0;
vm.sendableFunds = '';
vm.showSendMaxButton = false;
vm.showSendLimitMaxButton = false;
vm.thirdParty = false;
vm.unit = '';
// Functions // Functions
vm.changeUnit = changeUnit; vm.changeUnit = changeUnit;
vm.close = close; vm.close = close;
@ -23,6 +47,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
vm.save = save; vm.save = save;
vm.sendMax = sendMax; vm.sendMax = sendMax;
$scope.$on('$ionicView.beforeEnter', onBeforeEnter); $scope.$on('$ionicView.beforeEnter', onBeforeEnter);
$scope.$on('$ionicView.leave', onLeave); $scope.$on('$ionicView.leave', onLeave);
@ -54,55 +79,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
satoshis: null satoshis: null
}; };
function initVariables() { function onLeave() {
// Private variables angular.element($window).off('keydown');
altCurrencyModal = null;
altUnitIndex = 0;
availableUnits = [];
canSendMax = true;
fiatCode;
isNW = platformInfo.isNW;
isAndroid = platformInfo.isAndroid;
isIos = platformInfo.isIOS;
lastUsedAltCurrencyList = [];
passthroughParams = {};
satToUnit;
transactionSendableAmount = {
crypto: '',
satoshis: null
};
unitDecimals;
unitIndex = 0;
unitToSatoshi;
useSendMax = false;
walletSpendableAmount = {
crypto: '',
satoshis: null
};
// Public variables
vm.allowSend = false;
vm.altCurrencyList = [];
vm.alternativeAmount = '';
vm.alternativeUnit = '';
vm.amount = '0';
vm.availableFunds = '';
vm.canSendAllAvailableFunds = true;
vm.errorMessage = '';
// Use insufficient for logic, as when the amount is invalid, funds being
// either sufficent or insufficient doesn't make sense.
vm.fundsAreInsufficient = false;
vm.globalResult = '';
vm.isRequestingSpecificAmount = false;
vm.listComplete = false;
vm.lastUsedPopularList = [];
vm.maxAmount = 0;
vm.minAmount = 0;
vm.sendableFunds = '';
vm.showSendMaxButton = false;
vm.showSendLimitMaxButton = false;
vm.thirdParty = null;
vm.unit = '';
} }
function onBeforeEnter(event, data) { function onBeforeEnter(event, data) {
@ -110,19 +88,11 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
sendFlowService.state.pop(); sendFlowService.state.pop();
} }
// Init before entering on this screen
initVariables();
initCurrencies(); initCurrencies();
// Then start
passthroughParams = sendFlowService.state.getClone(); passthroughParams = sendFlowService.state.getClone();
console.log('amount onBeforeEnter after back sendflow ', passthroughParams); console.log('amount onBeforeEnter after back sendflow ', passthroughParams);
// Init thirdParty, should be done for all the variable
vm.thirdParty = null;
vm.fromWalletId = passthroughParams.fromWalletId; vm.fromWalletId = passthroughParams.fromWalletId;
vm.toWalletId = passthroughParams.toWalletId; vm.toWalletId = passthroughParams.toWalletId;
vm.minAmount = parseFloat(passthroughParams.minAmount); vm.minAmount = parseFloat(passthroughParams.minAmount);
@ -248,10 +218,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
} }
} }
function onLeave() {
angular.element($window).off('keydown');
}
function goBack() { function goBack() {
sendFlowService.router.goBack(); sendFlowService.router.goBack();
} }

View file

@ -517,7 +517,8 @@ angular.module('copayApp.controllers').controller('confirmController', function(
if (!lodash.isEmpty(warningMsg)) if (!lodash.isEmpty(warningMsg))
msg += '\n' + warningMsg; msg += '\n' + warningMsg;
popupService.showAlert(null, msg, function() {}); popupService.showAlert(null, msg, function() {});
}; };
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {

View file

@ -6,9 +6,58 @@ angular
.module('copayApp.controllers') .module('copayApp.controllers')
.controller('reviewController', reviewController); .controller('reviewController', reviewController);
function reviewController(addressbookService, externalLinkService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) {
var vm = this; var vm = this;
vm.buttonText = '';
vm.destination = {
address: '',
balanceAmount: '',
balanceCurrency: '',
coin: '',
color: '',
currency: '',
currencyColor: '',
kind: '', // 'address', 'contact', 'wallet'
name: ''
};
vm.displayAddress = '';
vm.feeCrypto = '';
vm.feeFiat = '';
vm.fiatCurrency = '';
vm.feeIsHigh = false;
vm.feeLessThanACent = false;
vm.isCordova = platformInfo.isCordova;
vm.memo = '';
vm.notReadyMessage = '';
vm.origin = {
balanceAmount: '',
balanceCurrency: '',
currency: '',
currencyColor: '',
};
vm.originWallet = null;
vm.paymentExpired = false;
vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here');
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.goBack = goBack;
vm.onSuccessConfirm = onSuccessConfirm;
vm.onShareTransaction = onShareTransaction;
var sendFlowData; var sendFlowData;
var config = null; var config = null;
var coin = ''; var coin = '';
@ -28,89 +77,22 @@ angular
var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15;
// Functions
vm.goBack = goBack;
vm.onSuccessConfirm = onSuccessConfirm;
vm.onShareTransaction = onShareTransaction;
function initVariables() {
// Private variables
sendFlowData;
config = null;
coin = '';
countDown = null;
defaults = {};
usingCustomFee = false;
usingMerchantFee = false;
destinationWalletId = '';
lastTxId = '';
originWalletId = '';
priceDisplayIsFiat = true;
satoshis = null;
toAddress = '';
tx = {};
txPayproData = null;
unitFromSat = 0;
// Public variables
vm.buttonText = '';
vm.destination = {
address: '',
balanceAmount: '',
balanceCurrency: '',
coin: '',
color: '',
currency: '',
currencyColor: '',
kind: '', // 'address', 'contact', 'wallet'
name: ''
};
vm.displayAddress = '';
vm.feeCrypto = '';
vm.feeFiat = '';
vm.fiatCurrency = '';
vm.feeIsHigh = false;
vm.feeLessThanACent = false;
vm.isCordova = platformInfo.isCordova;
vm.memo = '';
vm.notReadyMessage = '';
vm.origin = {
balanceAmount: '',
balanceCurrency: '',
currency: '',
currencyColor: '',
};
vm.originWallet = null;
vm.paymentExpired = false;
vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here');
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 = null;
vm.wallet = null;
vm.memoExpanded = false;
}
$scope.$on("$ionicView.beforeEnter", onBeforeEnter); $scope.$on("$ionicView.beforeEnter", onBeforeEnter);
function onBeforeEnter(event, data) {
$log.debug('reviewController onBeforeEnter sendflow ', sendFlowService.state);
// Init before entering on this screen function onBeforeEnter(event, data) {
initVariables(); console.log('review onBeforeEnter sendflow ', sendFlowService.state);
// Then start // Reset from last time
vm.memo = '';
defaults = configService.getDefaults(); defaults = configService.getDefaults();
sendFlowData = sendFlowService.state.getClone(); sendFlowData = sendFlowService.state.getClone();
originWalletId = sendFlowData.fromWalletId; originWalletId = sendFlowData.fromWalletId;
satoshis = parseInt(sendFlowData.amount, 10); if (typeof sendFlowData.amount === 'string') {
satoshis = parseInt(sendFlowData.amount, 10);
} else {
satoshis = sendFlowData.amount;
}
toAddress = sendFlowData.toAddress; toAddress = sendFlowData.toAddress;
destinationWalletId = sendFlowData.toWalletId; destinationWalletId = sendFlowData.toWalletId;
@ -123,14 +105,11 @@ angular
vm.thirdParty = sendFlowData.thirdParty; vm.thirdParty = sendFlowData.thirdParty;
switch (vm.thirdParty.id) { switch (vm.thirdParty.id) {
case 'shapeshift': case 'shapeshift':
initShapeshift(function onInitShapeshift(err) { initShapeshift(function (err) {
if (err) { if (err) {
// Error stop here // Error stop here
ongoingProcess.set('connectingShapeshift', false); ongoingProcess.set('connectingShapeshift', false);
popupService.showConfirm(gettextCatalog.getString('Shapeshift Error'), err.toString(), gettextCatalog.getString('Open') + " Shapeshift", gettextCatalog.getString('Go Back'), function onConfirm(hasConfirm) { popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () {
if (hasConfirm) {
externalLinkService.open("https://shapeshift.io");
}
$ionicHistory.goBack(); $ionicHistory.goBack();
}); });
} else { } else {
@ -156,6 +135,7 @@ angular
config = configCache; config = configCache;
priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat';
vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); 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; unitFromSat = 1 / config.wallet.settings.unitToSatoshi;
} }
updateSendAmounts(); updateSendAmounts();
@ -172,18 +152,18 @@ angular
if (!tx || !vm.originWallet) return; if (!tx || !vm.originWallet) return;
if (vm.paymentExpired) { if (vm.paymentExpired) {
popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function onAlert() { popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () {
$ionicHistory.goBack(); $ionicHistory.goBack();
})); }));
vm.sendStatus = ''; vm.sendStatus = '';
$timeout(function onTimeout() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
return; return;
} }
ongoingProcess.set('creatingTx', true, statusChangeHandler); ongoingProcess.set('creatingTx', true, statusChangeHandler);
getTxp(lodash.clone(tx), vm.originWallet, false, function onGetTxp(err, txp) { getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false, statusChangeHandler); ongoingProcess.set('creatingTx', false, statusChangeHandler);
if (err) return; if (err) return;
@ -200,12 +180,12 @@ angular
if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key'); $log.info('No signing proposal: No private key');
return walletService.onlyPublish(vm.originWallet, txp, function onOnlyPublish(err) { return walletService.onlyPublish(vm.originWallet, txp, function(err) {
if (err) setSendError(err); if (err) setSendError(err);
}, statusChangeHandler); }, statusChangeHandler);
} }
walletService.publishAndSign(vm.originWallet, txp, function onPublishAndSign(err, txp) { walletService.publishAndSign(vm.originWallet, txp, function(err, txp) {
if (err) return setSendError(err); if (err) return setSendError(err);
if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) {
txConfirmNotification.subscribe(vm.originWallet, { txConfirmNotification.subscribe(vm.originWallet, {
@ -219,7 +199,7 @@ angular
confirmTx(function(nok) { confirmTx(function(nok) {
if (nok) { if (nok) {
vm.sendStatus = ''; vm.sendStatus = '';
$timeout(function onTimeout() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
return; return;
@ -276,6 +256,7 @@ angular
}; };
function createVanityTransaction(data) { function createVanityTransaction(data) {
console.log('createVanityTransaction()');
var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal';
// Grab stateParams // Grab stateParams
@ -299,9 +280,6 @@ angular
txp: {}, txp: {},
}; };
if (vm.thirdParty && vm.thirdParty.id === "shapeshift") {
tx.toAddress = vm.thirdParty.toAddress;
}
if (data.stateParams.requiredFeeRate) { if (data.stateParams.requiredFeeRate) {
vm.usingMerchantFee = true; vm.usingMerchantFee = true;
@ -315,35 +293,29 @@ angular
var B = tx.coin === 'bch' ? bitcoreCash : bitcore; var B = tx.coin === 'bch' ? bitcoreCash : bitcore;
var networkName; var networkName;
try { try {
// Final destination is a wallet, but this transaction must go to an address for the first stage of the exchange. if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer
if (sendFlowData.thirdParty && sendFlowData.thirdParty.id === 'shapeshift') {
networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName;
setupTx(tx);
} else if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer
ongoingProcess.set('generatingNewAddress', true); ongoingProcess.set('generatingNewAddress', true);
var toWallet = profileService.getWallet(destinationWalletId); 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. // 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) { walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) {
if (err) { console.log('getAddress cb called', err);
$log.error('Error getting address for wallet.', err);
throw new Error(err.message);
}
ongoingProcess.set('generatingNewAddress', false); ongoingProcess.set('generatingNewAddress', false);
tx.toAddress = addr; tx.toAddress = addr;
networkName = (new B.Address(tx.toAddress)).network.name; networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName; tx.network = networkName;
console.log('calling setupTx() for wallet.');
setupTx(tx); setupTx(tx);
}); });
} else { // This is a Wallet-to-address transfer } else { // This is a Wallet-to-address transfer
networkName = (new B.Address(tx.toAddress)).network.name; networkName = (new B.Address(tx.toAddress)).network.name;
tx.network = networkName; tx.network = networkName;
console.log('calling setupTx() for address.');
setupTx(tx); setupTx(tx);
} }
} catch (e) { } catch (e) {
$log.error('Error setting up tx', e); console.error('Error setting up tx', e);
var message = gettextCatalog.getString('Invalid address'); var message = gettextCatalog.getString('Invalid address');
popupService.showAlert(null, message, function () { popupService.showAlert(null, message, function () {
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
@ -413,7 +385,7 @@ angular
} }
txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed;
txp.dryRun = dryRun; txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function onCreateTx(err, ctxp) { walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) { if (err) {
setSendError(err); setSendError(err);
return cb(err); return cb(err);
@ -433,7 +405,7 @@ angular
var walletStatus = null; var walletStatus = null;
if (wallet.status && wallet.status.isValid) { if (wallet.status && wallet.status.isValid) {
walletStatus = wallet.status; walletStatus = wallet.status;
} else if (wallet.cachedStatus && wallet.cachedStatus.isValid) { } else if (wallet.cachedStatus.isValid) {
walletStatus = wallet.cachedStatus; walletStatus = wallet.cachedStatus;
} }
@ -472,7 +444,7 @@ angular
} }
// Check if the recipient is a contact // Check if the recipient is a contact
addressbookService.get(originCoin + address, function onGetContact(err, contact) { addressbookService.get(originCoin + address, function(err, contact) {
if (!err && contact) { if (!err && contact) {
handleDestinationAsAddressOfContact(contact); handleDestinationAsAddressOfContact(contact);
} else { } else {
@ -545,6 +517,7 @@ angular
vm.destination.color = toWallet.color; vm.destination.color = toWallet.color;
vm.destination.currency = toWallet.coin.toUpperCase(); vm.destination.currency = toWallet.coin.toUpperCase();
ongoingProcess.set('connectingShapeshift', true); ongoingProcess.set('connectingShapeshift', true);
walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) {
if (err) { if (err) {
@ -562,8 +535,9 @@ angular
if (err) { if (err) {
return cb(err); return cb(err);
} else { } else {
// Want it to appear like a wallet-to-wallet transfer, so don't set the main toAddress. vm.destination.kind = 'shapeshift';
vm.thirdParty.toAddress = shapeshiftData.toAddress; vm.destination.address = toAddress;
tx.toAddress = shapeshiftData.toAddress;
vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId;
vm.memoExpanded = !!vm.memo; vm.memoExpanded = !!vm.memo;
ongoingProcess.set('connectingShapeshift', false); ongoingProcess.set('connectingShapeshift', false);
@ -598,6 +572,7 @@ angular
}, 1000); }, 1000);
function setExpirationTime() { function setExpirationTime() {
console.log('setExpirationTime()');
var now = Math.floor(Date.now() / 1000); var now = Math.floor(Date.now() / 1000);
if (now > expirationTime) { if (now > expirationTime) {
@ -715,7 +690,7 @@ angular
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function onAlert() { popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () {
$ionicHistory.goBack(); $ionicHistory.goBack();
}); });
}; };
@ -727,7 +702,7 @@ angular
tx.displayAddress = tx.toAddress; tx.displayAddress = tx.toAddress;
} }
addressbookService.get(tx.coin+tx.toAddress, function onGetContact(err, addr) { // Check if the recipient is a contact addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact
if (!err && addr) { if (!err && addr) {
tx.toName = addr.name; tx.toName = addr.name;
tx.toEmail = addr.email; tx.toEmail = addr.email;
@ -743,13 +718,35 @@ angular
if (tx.paypro) if (tx.paypro)
startExpirationTimer(tx.paypro.expires); startExpirationTimer(tx.paypro.expires);
updateTx(tx, vm.originWallet, { popupService.showConfirm(null, 'Do you want this transaction to be sent without a fee?', 'Yes', 'No', function(ok) {
dryRun: true if(ok){
}, function(err) { tx.feeRate = 0;
$timeout(function onTimeout() { // tx.feeLevel = 'free';
$scope.$apply(); usingCustomFee = true;
}, 10); }
}); 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) { function showSendMaxWarning(wallet, sendMaxInfo) {
@ -790,6 +787,7 @@ angular
msg += '\n' + warningMsg; msg += '\n' + warningMsg;
popupService.showAlert(null, msg, function() {}); popupService.showAlert(null, msg, function() {});
//popupService.showConfirm(null, msg, null, null, function() {});
}; };
function statusChangeHandler(processName, showName, isOn) { function statusChangeHandler(processName, showName, isOn) {
@ -860,14 +858,18 @@ angular
// refresh(); // refresh();
var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel;
feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function onGetFeeRate(err, feeRate) { feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) {
if (err) { if (err) {
ongoingProcess.set('calculatingFee', false); ongoingProcess.set('calculatingFee', false);
return cb(err); return cb(err);
} }
var msg; var msg;
if (usingCustomFee) { // if (tx.feeLevel == 'free'){
// tx.feeRate = 0;
// }
// else
if (usingCustomFee) {
msg = gettextCatalog.getString('Custom'); msg = gettextCatalog.getString('Custom');
tx.feeLevelName = msg; tx.feeLevelName = msg;
} else if (usingMerchantFee) { } else if (usingMerchantFee) {
@ -877,9 +879,11 @@ angular
} else { } else {
tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; tx.feeLevelName = feeService.feeOpts[tx.feeLevel];
tx.feeRate = feeRate; tx.feeRate = feeRate;
} }
getSendMaxInfo(lodash.clone(tx), wallet, function onGetSendmaxInfo(err, sendMaxInfo) {
getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) {
if (err) { if (err) {
ongoingProcess.set('calculatingFee', false); ongoingProcess.set('calculatingFee', false);
var msg = gettextCatalog.getString('Error getting SendMax information'); var msg = gettextCatalog.getString('Error getting SendMax information');
@ -893,7 +897,7 @@ angular
if (tx.sendMax && sendMaxInfo.amount == 0) { if (tx.sendMax && sendMaxInfo.amount == 0) {
ongoingProcess.set('calculatingFee', false); ongoingProcess.set('calculatingFee', false);
setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); setNotReady(gettextCatalog.getString('Insufficient confirmed funds'));
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function onAlert() { popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () {
$ionicHistory.goBack(); $ionicHistory.goBack();
}); });
return cb('no_funds'); return cb('no_funds');
@ -918,7 +922,8 @@ angular
return cb(); return cb();
} }
getTxp(lodash.clone(tx), wallet, opts.dryRun, function onGetTxp(err, txp) { console.log('calling getTxp() from getSendMaxInfo cb.');
getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) {
ongoingProcess.set('calculatingFee', false); ongoingProcess.set('calculatingFee', false);
if (err) { if (err) {
if (err.message == 'Insufficient funds') { if (err.message == 'Insufficient funds') {
@ -930,7 +935,7 @@ angular
} }
txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee);
txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function onFormatAlternativeStr(v) { txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) {
// txp.alternativeFeeStr = v; // txp.alternativeFeeStr = v;
// if (txp.alternativeFeeStr.substring(0, 4) == '0.00') // if (txp.alternativeFeeStr.substring(0, 4) == '0.00')
// txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr;
@ -940,6 +945,8 @@ angular
vm.feeLessThanACent = true; vm.feeLessThanACent = true;
} }
console.log("fiat", vm.feeFiat);
}); });
var per = (txp.fee / (txp.amount + txp.fee) * 100); var per = (txp.fee / (txp.amount + txp.fee) * 100);
@ -948,11 +955,14 @@ angular
txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE;
vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8);
vm.feeIsHigh = txp.feeToHigh; vm.feeIsHigh = txp.feeToHigh;
console.log("crypto", vm.feeCrypto);
tx.txp[wallet.id] = txp; tx.txp[wallet.id] = txp;
$log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx);
vm.readyToSend = true; vm.readyToSend = true;
updateSendAmounts(); updateSendAmounts();
console.log('readyToSend:', vm.readyToSend);
$scope.$apply(); $scope.$apply();
return cb(); return cb();

View file

@ -12,17 +12,6 @@ angular
var unitDecimals = 0; var unitDecimals = 0;
var unitsFromSatoshis = 0; var unitsFromSatoshis = 0;
//
// Needs to migrate $scope to vm.
//
function initVariables() {
// Private variables
fromWalletId = '';
priceDisplayAsFiat = false;
unitDecimals = 0;
unitsFromSatoshis = 0;
}
$scope.$on("$ionicView.beforeEnter", onBeforeEnter); $scope.$on("$ionicView.beforeEnter", onBeforeEnter);
$scope.$on("$ionicView.enter", onEnter); $scope.$on("$ionicView.enter", onEnter);
@ -31,10 +20,6 @@ angular
sendFlowService.state.pop(); sendFlowService.state.pop();
} }
// Init before entering on this screen
initVariables();
// Then start
$scope.params = sendFlowService.state.getClone(); $scope.params = sendFlowService.state.getClone();
console.log('walletSelector onBeforeEnter after back sendflow', $scope.params); console.log('walletSelector onBeforeEnter after back sendflow', $scope.params);
@ -67,6 +52,9 @@ angular
$scope.specificAmount = $scope.specificAlternativeAmount = ''; $scope.specificAmount = $scope.specificAlternativeAmount = '';
$scope.isPaymentRequest = true; $scope.isPaymentRequest = true;
} }
if ($scope.params.thirdParty) {
$scope.thirdParty = $scope.params.thirdParty;
}
}; };
function onEnter (event, data) { function onEnter (event, data) {
@ -74,7 +62,7 @@ angular
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
}); });
if ($scope.params.thirdParty) { if ($scope.thirdParty) {
// Third party services specific logic // Third party services specific logic
handleThirdPartyIfShapeshift(); handleThirdPartyIfShapeshift();
} }
@ -114,8 +102,8 @@ angular
} }
function handleThirdPartyIfShapeshift() { function handleThirdPartyIfShapeshift() {
console.log($scope.params.thirdParty, $scope.coin); console.log($scope.thirdParty, $scope.coin);
if ($scope.params.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the
$scope.coin = profileService.getWallet(fromWalletId).coin; $scope.coin = profileService.getWallet(fromWalletId).coin;
if ($scope.coin === 'bch') { if ($scope.coin === 'bch') {
$scope.coin = 'btc'; $scope.coin = 'btc';
@ -129,8 +117,6 @@ angular
var walletsAll = []; var walletsAll = [];
var walletsSufficientFunds = []; var walletsSufficientFunds = [];
$scope.walletsInsufficientFunds = []; // For origin screen $scope.walletsInsufficientFunds = []; // For origin screen
$scope.walletsBtc = [];
$scope.walletsBch = [];
if ($scope.type === 'origin') { if ($scope.type === 'origin') {
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from');

View file

@ -16,8 +16,8 @@ angular.module('copayApp.directives')
scope.type = "url"; scope.type = "url";
} else if (scope.data.parsed.publicAddress) { } else if (scope.data.parsed.publicAddress) {
scope.type = "bitcoinAddress"; scope.type = "bitcoinAddress";
var prefix = scope.data.coin === 'bch' ? (scope.data.parsed.isTestnet ? 'bchtest:' : 'bitcoincash:') : ''; var prefix = scope.data.parsed.isTestnet ? 'bchtest:' : 'bitcoincash:';
scope.data.toAddress = (scope.data.parsed.publicAddress.cashAddr ? prefix + scope.data.parsed.publicAddress.cashAddr : false) || scope.data.parsed.publicAddress.legacy || scope.data.parsed.publicAddress.bitpay; scope.data.toAddress = (prefix + scope.data.parsed.publicAddress.cashAddr) || scope.data.parsed.publicAddress.legacy || scope.data.parsed.publicAddress.bitpay;
} else { } else {
scope.type = "text"; scope.type = "text";
} }

View file

@ -151,7 +151,6 @@
hasPassphrase: false, hasPassphrase: false,
type: 1, type: 1,
}, },
isTestnet: false,
isValid: false, isValid: false,
label: '', label: '',
message: '', message: '',
@ -171,6 +170,7 @@
"req-param0": '', "req-param0": '',
"req-param1": '' "req-param1": ''
}, },
testnet: false,
url: '' // For BIP70 url: '' // For BIP70
} }
@ -204,13 +204,7 @@
} else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) { } else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) {
parsed.coin = 'bch'; parsed.coin = 'bch';
parsed.isTestnet = false; parsed.test = false;
addressAndParams = colonSplit[2].trim();
console.log('Is bch');
} else if (/^(?:bch)$/.test(preColonLower)) {
parsed.coin = 'bch';
parsed.isTestnet = false;
addressAndParams = colonSplit[2].trim(); addressAndParams = colonSplit[2].trim();
console.log('Is bch'); console.log('Is bch');

View file

@ -30,7 +30,7 @@ describe('bitcoinUriService', function() {
expect(parsed.isValid).toBe(true); expect(parsed.isValid).toBe(true);
expect(parsed.coin).toBe('bch'); expect(parsed.coin).toBe('bch');
expect(parsed.publicAddress).toBeUndefined(); expect(parsed.publicAddress).toBeUndefined();
expect(parsed.isTestnet).toBe(false); expect(parsed.isTestnet).toBeUndefined();
expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu'); expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
}); });
@ -171,16 +171,6 @@ describe('bitcoinUriService', function() {
expect(parsed.isTestnet).toBe(false); expect(parsed.isTestnet).toBe(false);
}); });
it('legacy address with bch prefix', function() {
var parsed = bitcoinUriService.parse('bch:19yUdM2H7sADrabR6Afu9zTpmwqr6WYprX');
expect(parsed.isValid).toBe(true);
expect(parsed.coin).toBe('bch');
expect(parsed.publicAddress.legacy).toBe('19yUdM2H7sADrabR6Afu9zTpmwqr6WYprX');
expect(parsed.isTestnet).toBe(false);
});
it('cashAddr testnet with prefix', function() { it('cashAddr testnet with prefix', function() {
var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt'); var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
@ -201,16 +191,6 @@ describe('bitcoinUriService', function() {
expect(parsed.isTestnet).toBe(false); expect(parsed.isTestnet).toBe(false);
}); });
it('cashAddr with bch prefix', function() {
var parsed = bitcoinUriService.parse('bch:qpqzqtjqqc00nsxj0e3kevz65ujg4yt5z5w99jap5f');
expect(parsed.isValid).toBe(true);
expect(parsed.coin).toBe('bch');
expect(parsed.publicAddress.cashAddr).toBe('qpqzqtjqqc00nsxj0e3kevz65ujg4yt5z5w99jap5f');
expect(parsed.isTestnet).toBe(false);
});
it('cashAddr with dash', function() { it('cashAddr with dash', function() {
var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0'); var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');

View file

@ -18,7 +18,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer
}, },
bwscash: { bwscash: {
url: 'https://bwscash.bitcoin.com/bws/api' url: 'https://bws.freepages.dk/bws/api'
}, },
download: { download: {

View file

@ -12,6 +12,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
normal: gettext('Normal'), normal: gettext('Normal'),
economy: gettext('Economy'), economy: gettext('Economy'),
superEconomy: gettext('Super Economy'), superEconomy: gettext('Super Economy'),
// free: gettext('No fee (works only for BCH)'),
custom: gettext('Custom') custom: gettext('Custom')
}; };
@ -31,6 +32,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
root.getFeeRate = function(coin, network, feeLevel, cb) { root.getFeeRate = function(coin, network, feeLevel, cb) {
if (feeLevel == 'custom') return cb(); if (feeLevel == 'custom') return cb();
// if (feeLevel == 'free') return cb(null, 0);
network = network || 'livenet'; network = network || 'livenet';
@ -48,9 +50,9 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
}) })
}); });
} }
var feeRate = feeLevelRate.feePerKb; var feeRate = feeLevelRate.feePerKb;
if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKb / 1000).toFixed() + ' SAT/B'); if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKb / 1000).toFixed() + ' SAT/B');
return cb(null, feeRate); return cb(null, feeRate);

View file

@ -52,7 +52,7 @@ angular
// Detect some merchant that we know // Detect some merchant that we know
if (payProData.memo.indexOf('eGifter') > -1) { if (payProData.memo.indexOf('eGifter') > -1) {
name = 'eGifter' name = 'eGifter'
} else if (payProData.url.indexOf('https://bitpay.com') > -1) { } else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
name = 'BitPay'; name = 'BitPay';
} }

View file

@ -5,7 +5,7 @@ angular.module('copayApp.services').factory('servicesService', function(configSe
name: 'shapeshift', name: 'shapeshift',
title: 'Shapeshift', title: 'Shapeshift',
icon: 'icon-shapeshift', icon: 'icon-shapeshift',
sref: 'tabs.shapeshift' href: 'https://shapeshift.io/'
}]; }];
root.register = function(serviceInfo) { root.register = function(serviceInfo) {

View file

@ -24,7 +24,7 @@ var ShapeShift = (function() {
var parsedResponse = JP(xmlhttp.responseText); var parsedResponse = JP(xmlhttp.responseText);
cb.apply(null, [parsedResponse]); cb.apply(null, [parsedResponse]);
} else { } else {
cb.apply(null, [new Error('Request Failed')]); cb.apply(null, [new Error('Request Failed')])
} }
} }
}; };

View file

@ -63,7 +63,6 @@ angular
} else if (amount > service.marketData.maxLimit) { } else if (amount > service.marketData.maxLimit) {
cb(new Error(gettextCatalog.getString('Amount is above the limit'))); cb(new Error(gettextCatalog.getString('Amount is above the limit')));
} else { } else {
// Init service data // Init service data
service.withdrawalAddress = withdrawalAddress; service.withdrawalAddress = withdrawalAddress;
service.returnAddress = returnAddress; service.returnAddress = returnAddress;
@ -73,7 +72,7 @@ angular
// Check the address // Check the address
shapeshiftApiService shapeshiftApiService
.ValidateAddress(returnAddress, coinOut) .ValidateAddress(withdrawalAddress, coinOut)
.then(function onSuccess(response) { .then(function onSuccess(response) {
if (response && response.isvalid) { if (response && response.isvalid) {
// Prepare the transaction shapeshift side // Prepare the transaction shapeshift side

View file

@ -7,8 +7,8 @@
.factory('walletHistoryService', walletHistoryService); .factory('walletHistoryService', walletHistoryService);
function walletHistoryService(configService, storageService, lodash, $log, txFormatService) { function walletHistoryService(configService, storageService, lodash, $log, txFormatService) {
var PAGE_SIZE = 50; //var PAGE_SIZE = 50;
//var PAGE_SIZE = 20; // For dev only var PAGE_SIZE = 20; // For dev only
// How much to overlap on each end of the page, for mitigating inconsistent sort order. // How much to overlap on each end of the page, for mitigating inconsistent sort order.
var PAGE_OVERLAP_FRACTION = 0.2; var PAGE_OVERLAP_FRACTION = 0.2;
var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION); var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION);
@ -28,8 +28,8 @@
function addEarlyTransactions(walletId, cachedTxs, newTxs) { function addEarlyTransactions(walletId, cachedTxs, newTxs) {
var cachedTxIndexFromId = {}; var cachedTxIndexFromId = {};
cachedTxs.forEach(function forCachedTx(tx, txIndex){ cachedTxs.forEach(function forCachedTx(tx){
cachedTxIndexFromId[tx.txid] = txIndex; cachedTxIndexFromId[tx.txid] = true;
}); });
var confirmationsUpdated = false; var confirmationsUpdated = false;
@ -49,9 +49,8 @@
var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP); var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP);
console.log('overlappingTxFraction:', overlappingTxFraction); console.log('overlappingTxFraction:', overlappingTxFraction);
console.log('overlappingTxsCount:', overlappingTxsCount);
if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION || (someTransactionsWereNew && overlappingTxsCount === 0)) { // We are good if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good
if (someTransactionsWereNew) { if (someTransactionsWereNew) {
saveTxHistory(walletId, cachedTxs); saveTxHistory(walletId, cachedTxs);
} else if (confirmationsUpdated) { } else if (confirmationsUpdated) {
@ -94,7 +93,7 @@
var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP); var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP);
if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION || (someTransactionsWereNew && overlappingTxsCount === 0)) { // We are good if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good
if (someTransactionsWereNew) { if (someTransactionsWereNew) {
var allTxs = uniqueNewTxs.concat(cachedTxs); var allTxs = uniqueNewTxs.concat(cachedTxs);
saveTxHistory(walletId, allTxs); saveTxHistory(walletId, allTxs);
@ -148,6 +147,7 @@
* @param {function(error, txs)} cb - txs is always an array, may be empty * @param {function(error, txs)} cb - txs is always an array, may be empty
*/ */
function getCachedTxHistory(walletId, cb) { function getCachedTxHistory(walletId, cb) {
console.log('txhistory updateLocalTxHistoryByPage()');
storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){ storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){
if (err) { if (err) {
return cb(err, []); return cb(err, []);
@ -230,6 +230,7 @@
} }
function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) {
console.log('txhistory updaetLocalTxHistoryByPage()');
if (flushCacheOnNew) { if (flushCacheOnNew) {
fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){
if (err) { if (err) {

View file

@ -10083,7 +10083,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm
.onboarding .button.button-white.button-standard, .onboarding .button.button-white.button-standard,
.onboarding .button.button-green.button-standard, .onboarding .button.button-green.button-standard,
.onboarding .button.button-assertive.button-standard, #shapeshift .button-shapeshift { .onboarding .button.button-assertive.button-standard, #shapeshift .button-shapeshift {
width: 85%; width: 90%;
max-width: 300px; max-width: 300px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -10195,6 +10195,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm
.button { .button {
border-radius: 6px; } border-radius: 6px; }
.button.button-full { .button.button-full {
border-radius: 0;
display: block; } display: block; }
.button-green { .button-green {
border-color: #FFF; border-color: #FFF;
@ -10263,8 +10264,8 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm
color: #FFFFFF; color: #FFFFFF;
text-decoration: none; } text-decoration: none; }
.button-white-outline.active, .button-white-outline.activated { .button-white-outline.active, .button-white-outline.activated {
border-color: #FFF; border-color: #FFFFFF;
background-color: #FAFAFA; } background-color: #FFFFFF; }
.button-white-outline.button-outline { .button-white-outline.button-outline {
border-color: #FFFFFF; border-color: #FFFFFF;
background: transparent; background: transparent;
@ -10632,7 +10633,7 @@ qrcode {
#view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display {
font-size: 2em; } } font-size: 2em; } }
#view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display {
font-size: 0.9em; } font-size: 1.2em; }
@media (min-width: 375px) { @media (min-width: 375px) {
#view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display {
font-size: 1.3em; } } font-size: 1.3em; } }
@ -10659,7 +10660,8 @@ qrcode {
line-height: 1em; } line-height: 1em; }
#view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display {
margin-right: 5px; margin-right: 5px;
word-break: break-all; } word-break: break-all;
width: 100%; }
#view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .alternative-amount { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .alternative-amount {
color: #6F6F70; } color: #6F6F70; }
#view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies {
@ -10680,30 +10682,33 @@ qrcode {
justify-content: space-between; justify-content: space-between;
margin: 0 14px; } margin: 0 14px; }
#view-amount .scroll-content .send-amount-extras .available-funds { #view-amount .scroll-content .send-amount-extras .available-funds {
color: #6F6F70; } color: #6F6F70;
text-align: left; }
#view-amount .scroll-content .send-amount-extras .change-currency {
text-align: right; }
#view-amount .scroll-content .send-amount-extras .warning { #view-amount .scroll-content .send-amount-extras .warning {
color: #b7664d; } color: #b7664d; }
#view-amount .scroll-content .send-amount-extras .extra, #view-amount .scroll-content .send-amount-extras .extra {
#view-amount .scroll-content .send-amount-extras button.extra { flex: 1;
/*display: flex;*/
flex: 0 1 auto; }
#view-amount .scroll-content .send-amount-extras button.extra {
background: none;
border: none;
color: #000;
font-family: 'ProximaNova';
font-size: 14px;
line-height: normal;
min-height: auto;
min-width: auto;
padding: 0; }
#view-amount .scroll-content .send-amount-extras .button .icon:before {
font-size: 14px;
line-height: normal; } line-height: normal; }
#view-amount .scroll-content .send-amount-extras .button span { #view-amount .scroll-content .send-amount-extras .extra .button {
display: flex; background: none;
align-items: center; border: none;
justify-content: center; } border-radius: 0;
color: #000;
font-family: 'ProximaNova';
font-size: 14px;
line-height: normal;
min-height: auto;
min-width: auto;
padding: 0; }
#view-amount .scroll-content .send-amount-extras .extra .button .icon:before {
font-size: 14px;
line-height: normal; }
#view-amount .scroll-content .send-amount-extras .extra .button span {
display: flex;
align-items: center;
justify-content: center; }
#view-amount .scroll-content .button.no-margin { #view-amount .scroll-content .button.no-margin {
margin: 0; } margin: 0; }
#view-amount .scroll-content .notification-warning { #view-amount .scroll-content .notification-warning {
@ -10939,14 +10944,14 @@ qrcode {
#tab-home .buttons .button { #tab-home .buttons .button {
border: 2px solid; border: 2px solid;
border-radius: 47px; border-radius: 47px;
padding: 0 15px 0 15px; padding: 8px 2px 8px 2px;
text-align: center; text-align: center;
width: 100%; width: 100%;
max-width: 300px; max-width: 300px;
font-size: 19px; font-size: 19px;
font-weight: bolder; font-weight: bolder;
min-height: auto; min-height: 0;
line-height: 36px; } line-height: 19px; }
#tab-home .wallet-coin-logo { #tab-home .wallet-coin-logo {
vertical-align: middle; vertical-align: middle;
@ -11013,6 +11018,10 @@ qrcode {
#tab-home .release .title { #tab-home .release .title {
font-weight: 700; font-weight: 700;
color: #444; } color: #444; }
#tab-home .release .release-notes {
white-space: pre;
white-space: pre-line;
text-align: left; }
#tab-home .release .button { #tab-home .release .button {
width: 100%; width: 100%;
border: none; } border: none; }
@ -11025,6 +11034,14 @@ qrcode {
#tab-home .badge { #tab-home .badge {
top: 11px; } top: 11px; }
.popup-update .popup-buttons {
display: block; }
.popup-update .popup-buttons .button {
display: block;
min-width: 100% !important;
margin-top: 4px; }
#tab-receive .button-share { #tab-receive .button-share {
color: #fff; color: #fff;
box-shadow: none; box-shadow: none;
@ -11101,6 +11118,8 @@ qrcode {
#tab-receive .payment-received-container { #tab-receive .payment-received-container {
margin: 0 20px; } margin: 0 20px; }
#tab-receive .payment-received-container svg {
max-height: 400px; }
#tab-receive .payment-received-container .payment-received-amount { #tab-receive .payment-received-container .payment-received-amount {
font-size: 1.8em; font-size: 1.8em;
display: block; display: block;
@ -11207,7 +11226,7 @@ qrcode {
margin: auto; margin: auto;
margin-top: 18px; } margin-top: 18px; }
#tab-send .send-wrapper .buttons .button { #tab-send .send-wrapper .buttons .button {
height: 60px; min-height: 65px;
line-height: 16px; line-height: 16px;
margin-right: 0px; margin-right: 0px;
width: 95%; width: 95%;
@ -11284,7 +11303,9 @@ qrcode {
margin-top: 18px; } margin-top: 18px; }
#tab-send .sendTip .buttons .button { #tab-send .sendTip .buttons .button {
font-weight: bold; font-weight: bold;
font-size: 19px; } font-size: 19px;
line-height: 26px;
padding: 8px 6px; }
#tab-send .sendTip .button-first-contact img { #tab-send .sendTip .button-first-contact img {
height: 19px; height: 19px;
width: 19px; width: 19px;
@ -11920,6 +11941,13 @@ qrcode {
#walletDetails .bp-content.status-bar { #walletDetails .bp-content.status-bar {
margin-top: 20px; margin-top: 20px;
margin-top: env(safe-area-inset-top); } margin-top: env(safe-area-inset-top); }
#walletDetails .bp-content.collapse ion-content {
margin-top: 40px; }
#walletDetails .bp-content.collapse .amount__scale, #walletDetails .bp-content.collapse .amount__error {
-webkit-transform: scale3d(0.5, 0.5, 0.5) translateY(0px);
transform: scale3d(0.5, 0.5, 0.5) translateY(0px); }
#walletDetails .bp-content.collapse .amount-alternative, #walletDetails .bp-content.collapse .send-receive-buttons, #walletDetails .bp-content.collapse .wallet-details-wallet-info {
opacity: 0; }
#walletDetails .bar-header { #walletDetails .bar-header {
border: 0; border: 0;
background: #eeb640; } background: #eeb640; }
@ -11932,9 +11960,12 @@ qrcode {
#walletDetails ion-content { #walletDetails ion-content {
padding-top: 0; padding-top: 0;
top: 0; top: 0;
transition: all 0.25s ease-in-out;
margin-top: 185px;
margin-bottom: 16px; } margin-bottom: 16px; }
#walletDetails ion-content.collapsible { @media only screen and (max-height: 500px) {
margin-top: 230px; } #walletDetails ion-content {
margin-top: 165px; } }
#walletDetails ion-content .scroll { #walletDetails ion-content .scroll {
background: #f8f8f9; background: #f8f8f9;
min-height: 300px; } min-height: 300px; }
@ -11965,38 +11996,45 @@ qrcode {
justify-content: space-evenly; justify-content: space-evenly;
width: 100%; width: 100%;
position: absolute; position: absolute;
bottom: 20px; } bottom: 20px;
transition: all 0.25s ease-in-out; }
#walletDetails .amount-wrapper .send-receive-buttons > .col { #walletDetails .amount-wrapper .send-receive-buttons > .col {
padding: 5px 10px; padding: 5px 10px;
margin-bottom: 0; } margin-bottom: 0; }
#walletDetails .amount-wrapper .send-receive-buttons .button { #walletDetails .amount-wrapper .send-receive-buttons .button {
border: 2px solid; border: 2px solid;
border-radius: 47px; border-radius: 47px;
padding: 0 15px 0 15px; padding: 6px 2px 6px 2px;
text-align: center; text-align: center;
width: 100%; width: 100%;
max-width: 300px; max-width: 300px;
font-size: 19px; font-size: 19px;
font-weight: bolder; font-weight: bolder;
min-height: auto; min-height: 0;
line-height: 36px; } line-height: 19px; }
#walletDetails .amount { #walletDetails .amount {
width: 100%;
text-align: center;
color: #fff;
height: 230px;
padding-top: 40px;
display: block;
align-items: center; align-items: center;
justify-content: center; } color: #fff;
display: block;
height: 230px;
justify-content: center;
padding-top: 40px;
text-align: center;
transition: all 0.25s ease-in-out;
width: 100%; }
@media only screen and (max-height: 500px) {
#walletDetails .amount {
height: 210px; } }
#walletDetails .amount__balance { #walletDetails .amount__balance {
-webkit-transform: scale3d(1, 1, 1) translateY(45px); -webkit-transform: scale3d(1, 1, 1) translateY(45px);
transform: scale3d(1, 1, 1) translateY(45px); } transform: scale3d(1, 1, 1) translateY(45px);
transition: all 0.25s ease-in-out; }
#walletDetails .amount__updating { #walletDetails .amount__updating {
z-index: 999; z-index: 999;
margin-top: -2.1rem; } margin-top: -2.1rem; }
#walletDetails .amount-alternative { #walletDetails .amount-alternative {
line-height: 36px; } line-height: 36px;
transition: all 0.25s ease-in-out; }
#walletDetails .amount__button-balance { #walletDetails .amount__button-balance {
background-color: transparent; background-color: transparent;
border: 1px solid rgba(255, 255, 255, 0.25); border: 1px solid rgba(255, 255, 255, 0.25);
@ -12006,7 +12044,8 @@ qrcode {
vertical-align: middle; } vertical-align: middle; }
#walletDetails .amount__error { #walletDetails .amount__error {
font-size: 14px; font-size: 14px;
padding: 35px 20px; } padding: 35px 20px;
opacity: 1; }
#walletDetails .no-alternative { #walletDetails .no-alternative {
padding-top: 45px; } padding-top: 45px; }
#walletDetails .item.item-footer { #walletDetails .item.item-footer {
@ -12071,7 +12110,9 @@ a.item {
font-size: 0.9em; } font-size: 0.9em; }
.loading-wallet svg { .loading-wallet svg {
margin-top: 0; } margin-top: 0;
width: 16px;
height: 16px; }
#advanced-settings .list .item:before { #advanced-settings .list .item:before {
display: block; display: block;
@ -12125,7 +12166,9 @@ a.item {
margin-top: 18px; } margin-top: 18px; }
#shapeshift .empty-case .buttons .button { #shapeshift .empty-case .buttons .button {
font-weight: bold; font-weight: bold;
font-size: 19px; } font-size: 19px;
line-height: 26px;
padding: 8px 6px; }
#shapeshift .empty-case .button-first-contact img { #shapeshift .empty-case .button-first-contact img {
height: 19px; height: 19px;
width: 19px; width: 19px;
@ -13812,6 +13855,7 @@ click-to-accept {
height: 92px; height: 92px;
width: 100%; } width: 100%; }
click-to-accept .click-to-accept__button.button.button-primary.button-standard { click-to-accept .click-to-accept__button.button.button-primary.button-standard {
border-radius: 0;
height: 100%; height: 100%;
max-width: 9999px; max-width: 9999px;
width: 100%; } width: 100%; }
@ -13918,6 +13962,8 @@ slide-to-accept {
height: 92px; height: 92px;
width: 100%; width: 100%;
background: #494949; } background: #494949; }
slide-to-accept .slide .button {
border-radius: 0; }
slide-to-accept .slide__listener { slide-to-accept .slide__listener {
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -15349,20 +15395,25 @@ log-options #check-bar .checkbox-icon {
#cash-scan a { #cash-scan a {
cursor: pointer; } cursor: pointer; }
#view-review { #view-review slide-to-accept, #view-review slide-to-accept-success {
background-color: #494949; } margin-bottom: constant(safe-area-inset-bottom);
#view-review slide-to-accept, #view-review slide-to-accept-success { /* iOS 11.0 */
margin-bottom: constant(safe-area-inset-bottom); margin-bottom: env(safe-area-inset-bottom);
/* iOS 11.0 */ /* iOS 11.2 */ }
margin-bottom: env(safe-area-inset-bottom);
/* iOS 11.2 */ } #view-review .fee-summary {
#view-review .fee-summary { bottom: 92px;
position: absolute; bottom: calc(92px + constant(safe-area-inset-bottom));
bottom: 92px; } /* iOS 11.0 */
#view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { bottom: calc(92px + env(safe-area-inset-bottom));
box-shadow: none; } /* iOS 11.2 */
#view-review .warning { position: absolute; }
color: #b7664d; }
#view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner {
box-shadow: none; }
#view-review .warning {
color: #b7664d; }
.gravatar { .gravatar {
border-radius: 3px; border-radius: 3px;
@ -15395,6 +15446,7 @@ log-options #check-bar .checkbox-icon {
left: 13px; left: 13px;
top: 50%; top: 50%;
padding: 0; padding: 0;
-webkit-transform: translate(0, -50%);
transform: translate(0, -50%); } transform: translate(0, -50%); }
.item .item-content.item-content-compact { .item .item-content.item-content-compact {
@ -15463,8 +15515,8 @@ ion-content.padded-bottom-cta-with-summary {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; } text-overflow: ellipsis; }
.address-frame.expanded { .address-frame.expanded {
white-space: pre-wrap; white-space: normal;
word-break: break-all; } text-overflow: clip; }
.address-frame .prefix { .address-frame .prefix {
color: #000000; } color: #000000; }
.address-frame .mid { .address-frame .mid {
@ -15507,13 +15559,13 @@ ion-content.padded-bottom-cta-with-summary {
transform: scale(1, 1); } transform: scale(1, 1); }
.fee-summary { .fee-summary {
position: relative; background-color: #F2F2F2;
box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%;
padding: 5px 12px 15px; padding: 5px 12px 15px;
box-sizing: border-box; position: relative;
background-color: #F2F2F2; } width: 100%; }
.fee-summary:before { .fee-summary:before {
content: ''; content: '';
position: absolute; position: absolute;
@ -15523,16 +15575,16 @@ ion-content.padded-bottom-cta-with-summary {
height: 15px; height: 15px;
background: linear-gradient(to bottom, rgba(242, 242, 242, 0) 0%, #f2f2f2 100%); } background: linear-gradient(to bottom, rgba(242, 242, 242, 0) 0%, #f2f2f2 100%); }
.fee-summary .amount { .fee-summary .amount {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%; } width: 100%; }
.fee-summary .amount .fee-fiat.positive { .fee-summary .amount .fee-fiat {
color: #70955F; } display: inline; }
.fee-summary .amount .fee-fiat.negative { .fee-summary .amount .fee-fiat.positive {
color: #C24633; } color: #70955F; }
.fee-summary .amount .fee-fiat.negative {
color: #C24633; }
.fee-summary .amount .fee-crypto { .fee-summary .amount .fee-crypto {
color: #A7A7A7; } color: #A7A7A7;
float: right; }
.formatted-amount { .formatted-amount {
display: inline-block; } display: inline-block; }
@ -15557,6 +15609,9 @@ ion-content.padded-bottom-cta-with-summary {
margin-left: 5px; margin-left: 5px;
text-transform: uppercase; } text-transform: uppercase; }
.wallet-balance-directive {
display: inline-block; }
/* This is for rules that don't yet have a home. /* This is for rules that don't yet have a home.
* Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/
*/ */