This commit is contained in:
Kadir Sekha 2017-10-16 18:05:09 +09:00
commit a0261a6c9f
146 changed files with 16800 additions and 5578 deletions

View file

@ -1,13 +1,3 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
## Plugins
# Atom (https://github.com/sindresorhus/atom-editorconfig)
# Vim (https://github.com/editorconfig/editorconfig-vim)
# Sublime (https://github.com/sindresorhus/editorconfig-sublime)
;
root = true root = true
[*] [*]
@ -27,4 +17,3 @@ indent_style = tab
[**.html] [**.html]
max_char = 78 max_char = 78
brace_style = expand brace_style = expand

3
.gitignore vendored
View file

@ -3,6 +3,9 @@ i18n/po/*.mo
i18n/crowdin_api_key.txt i18n/crowdin_api_key.txt
src/js/translations.js src/js/translations.js
package-lock.json
# cordova # cordova
plugins plugins
platforms platforms

View file

@ -267,7 +267,7 @@ module.exports = function(grunt) {
grunt.registerTask('desktop', ['prod', 'nwjs', 'copy:linux', 'compress:linux']); grunt.registerTask('desktop', ['prod', 'nwjs', 'copy:linux', 'compress:linux']);
grunt.registerTask('osx', ['prod', 'nwjs', 'exec:macos', 'exec:osxsign']); grunt.registerTask('osx', ['prod', 'nwjs', 'exec:macos', 'exec:osxsign']);
grunt.registerTask('osx-debug', ['default', 'nwjs']); grunt.registerTask('osx-debug', ['default', 'nwjs']);
grunt.registerTask('chrome', ['exec:chrome']); grunt.registerTask('chrome', ['default','exec:chrome']);
grunt.registerTask('wp', ['prod', 'exec:wp']); grunt.registerTask('wp', ['prod', 'exec:wp']);
grunt.registerTask('wp-copy', ['default', 'exec:wpcopy']); grunt.registerTask('wp-copy', ['default', 'exec:wpcopy']);
grunt.registerTask('wp-init', ['default', 'exec:wpinit']); grunt.registerTask('wp-init', ['default', 'exec:wpinit']);

View file

@ -13,6 +13,10 @@ bwcModule.provider("bwcService", function() {
return Client.Bitcore; return Client.Bitcore;
}; };
service.getBitcoreCash = function() {
return Client.BitcoreCash;
};
service.getErrors = function() { service.getErrors = function() {
return Client.errors; return Client.errors;
}; };

View file

@ -23,8 +23,8 @@
"windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195", "windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195",
"pushSenderId": "1036948132229", "pushSenderId": "1036948132229",
"description": "Secure Bitcoin Wallet", "description": "Secure Bitcoin Wallet",
"version": "3.7.1", "version": "3.8.2",
"androidVersion": "371000", "androidVersion": "382001",
"_extraCSS": null, "_extraCSS": null,
"_enabledExtensions": { "_enabledExtensions": {
"coinbase": true, "coinbase": true,

View file

@ -66,6 +66,7 @@
<plugin name="cordova-plugin-customurlscheme" spec="https://github.com/cmgustavo/Custom-URL-scheme.git"> <plugin name="cordova-plugin-customurlscheme" spec="https://github.com/cmgustavo/Custom-URL-scheme.git">
<variable name="URL_SCHEME" value="bitcoin" /> <variable name="URL_SCHEME" value="bitcoin" />
<variable name="SECOND_URL_SCHEME" value="*APPURI*" /> <variable name="SECOND_URL_SCHEME" value="*APPURI*" />
<variable name="THIRD_URL_SCHEME" value="bitcoincash" />
</plugin> </plugin>
<plugin name="cordova-custom-config" spec="~3.0.5" /> <plugin name="cordova-custom-config" spec="~3.0.5" />
<plugin name="cordova-plugin-queries-schemes" spec="~0.1.5" /> <plugin name="cordova-plugin-queries-schemes" spec="~0.1.5" />

View file

@ -23,8 +23,8 @@
"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": "3.7.1", "version": "3.8.2",
"androidVersion": "371000", "androidVersion": "382001",
"_extraCSS": null, "_extraCSS": null,
"_enabledExtensions": { "_enabledExtensions": {
"coinbase": true, "coinbase": true,

View file

@ -1,7 +1,7 @@
{ {
"name": "*PACKAGENAME*", "name": "*PACKAGENAME*",
"description": "*DESCRIPTION*", "description": "*DESCRIPTION*",
"author": "Bitcoin.com", "author": "BitPay",
"version": "*VERSION*", "version": "*VERSION*",
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
@ -54,9 +54,9 @@
"angular": "1.4.6", "angular": "1.4.6",
"angular-mocks": "1.4.10", "angular-mocks": "1.4.10",
"bezier-easing": "^2.0.3", "bezier-easing": "^2.0.3",
"bhttp": "^1.2.1", "bhttp": "1.2.1",
"bitauth": "^0.2.1", "bitauth": "^0.2.1",
"bitcore-wallet-client": "5.3.0", "bitcore-wallet-client": "6.2.1",
"bower": "^1.7.9", "bower": "^1.7.9",
"cordova-android": "5.1.1", "cordova-android": "5.1.1",
"cordova-custom-config": "^3.0.5", "cordova-custom-config": "^3.0.5",
@ -107,10 +107,9 @@
"run:android": "cordova run android --device", "run:android": "cordova run android --device",
"run:android-release": "cordova run android --device --release", "run:android-release": "cordova run android --device --release",
"log:android": "adb logcat | grep chromium", "log:android": "adb logcat | grep chromium",
"sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../copay.keystore -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && ../android-sdk-macosx/build-tools/25.0.3/zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk", "sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../copay.keystore -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk copay_play && $ANDROID_HOME/build-tools/26.0.1/zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk",
"apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare", "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare",
"apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare", "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare",
"apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare",
"test": "echo \"no package tests configured\"", "test": "echo \"no package tests configured\"",
"clean": "trash platforms && trash plugins && cordova prepare", "clean": "trash platforms && trash plugins && cordova prepare",
"unstage-package": "git reset package.json", "unstage-package": "git reset package.json",
@ -119,9 +118,9 @@
"devDependencies": { "devDependencies": {
"cordova": "^6.3.1", "cordova": "^6.3.1",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"ionic": "^2.1.0", "ionic": "^3.6.0",
"trash-cli": "^1.4.0", "trash-cli": "^1.4.0",
"lodash": "^4.3.0", "lodash": "^4.17.4",
"pre-commit": "^1.1.3" "pre-commit": "^1.1.3"
}, },
"pre-commit": "unstage-package" "pre-commit": "unstage-package"

View file

@ -15,7 +15,7 @@
"moment": "2.10.3", "moment": "2.10.3",
"ng-lodash": "0.2.3", "ng-lodash": "0.2.3",
"qrcode-decoder-js": "*", "qrcode-decoder-js": "*",
"trezor-connect": "*", "trezor-connect": "https://github.com/trezor/connect.git#c1a00f9f0f4a13239ba13ddd589c6ca61f2e7876",
"ng-csv": "~0.3.6", "ng-csv": "~0.3.6",
"ionic-toast": "^0.4.1", "ionic-toast": "^0.4.1",
"angular-clipboard": "^1.4.2", "angular-clipboard": "^1.4.2",

23
i18n/docs/appstore_nl.txt Normal file
View file

@ -0,0 +1,23 @@
Beveilig bitcoin op je eigen voorwaarden met een open source, meervoudige ondertekening-portemonnee van BitPay.
Copay gebruikers kunnen individueel saldo beheren of veilig financiën delen met andere gebruikers door middel van portemonnees met meervoudige ondertekening, dit voorkomt ongeautoriseerde betalingen doordat meerdere goedkeuringen nodig zijn. Hier zijn een aantal manieren waarop Copay gebruikt kan worden met anderen:
Om te sparen voor vakanties of gezamenlijke aankopen met vrienden
Voor het bijhouden van de familieuitgaven en zakgeld
Voor het beheer van het saldo en de uitgaven van een bedrijf, club of organisatie
We hebben de volgende functies ingebouwd in deze versie van Copay voor een bitcoin portemonnee die niet inlevert op beveiliging of toegankelijkheid:
Het aanmaken en beheren van meerdere portemonnees binnen de app
Intuïtieve meervoudige-ondertekening beveiliging voor persoonlijke of gedeelde portemonnees
Eenvoudige bestedingsvoorstellen voor gedeelde portemonnees en groepsbetalingen
Hiërarchisch deterministische (HD) adres generatie en portemonnee backups
Apparaat gebaseerde beveiliging: alle privé sleutels worden lokaal opgeslagen, niet in de cloud
Ondersteuning voor Bitcoin testnet portemonnees
Gesynchroniseerde toegang vanaf alle grote mobiele en desktop platformen
Ondersteuning voor betalings protocol (BIP70-BIP73): gemakkelijk te herkennen betalingsverzoeken en verifieerbaar veilige bitcoin betalingen
Ondersteuning voor prijsweergave in 150+ valuta's en eenheid denominatie in BTC of bits
Email meldingen voor betalingen en overdrachten
Aanpasbare portemonnee namen en achtergrond kleuren
10 ondersteunde talen (EN, CZ, FR, DE, IT, ES, JA, PL, RU, NL)
Copay is gratis open source software draaiend op niet-merkgebonden servers, dus het is niet nodig te vertrouwen op enig bedrijf voor blijvende ondersteuning. Iedereen kan de broncode van Copay inzien of er aan bijdragen via GitHub (https://github.com/bitpay/copay).

View file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

3630
i18n/po/nl.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@ angular.module('copayApp.controllers').controller('addressbookAddController', fu
$timeout(function() { $timeout(function() {
var form = addressbookForm; var form = addressbookForm;
if (data && form) { if (data && form) {
data = data.replace('bitcoin:', ''); data = data.replace(/^bitcoin(cash)?:/, '');
form.address.$setViewValue(data); form.address.$setViewValue(data);
form.address.$isValid = true; form.address.$isValid = true;
form.address.$render(); form.address.$render();

View file

@ -1,11 +1,23 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, $state, $timeout, $stateParams, lodash, addressbookService, popupService, $ionicHistory, platformInfo, gettextCatalog) { angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, $state, $timeout, lodash, addressbookService, popupService, $ionicHistory, platformInfo, gettextCatalog, bitcoreCash) {
$scope.isChromeApp = platformInfo.isChromeApp; $scope.isChromeApp = platformInfo.isChromeApp;
$scope.addressbookEntry = {}; $scope.addressbookEntry = {};
$scope.addressbookEntry.name = $stateParams.name; var coin;
$scope.addressbookEntry.email = $stateParams.email;
$scope.addressbookEntry.address = $stateParams.address; $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.addressbookEntry = {};
$scope.addressbookEntry.name = data.stateParams.name;
$scope.addressbookEntry.email = data.stateParams.email;
$scope.addressbookEntry.address = data.stateParams.address;
var cashAddress = bitcoreCash.Address.isValid($scope.addressbookEntry.address, 'livenet');
if (cashAddress) {
coin = 'bch';
} else {
coin = 'btc';
}
});
$scope.sendTo = function() { $scope.sendTo = function() {
$ionicHistory.removeBackView(); $ionicHistory.removeBackView();
@ -14,7 +26,8 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
$state.transitionTo('tabs.send.amount', { $state.transitionTo('tabs.send.amount', {
toAddress: $scope.addressbookEntry.address, toAddress: $scope.addressbookEntry.address,
toName: $scope.addressbookEntry.name, toName: $scope.addressbookEntry.name,
toEmail: $scope.addressbookEntry.email toEmail: $scope.addressbookEntry.email,
coin: coin
}); });
}, 100); }, 100);
}; };
@ -31,7 +44,7 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
} }
$ionicHistory.goBack(); $ionicHistory.goBack();
}); });
}); });
}; };
}); });

View file

@ -1,13 +1,8 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('addressesController', function($scope, $log, $stateParams, $state, $timeout, $ionicHistory, $ionicScrollDelegate, configService, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, bwcError, platformInfo, appConfigService, txFormatService, feeService) { angular.module('copayApp.controllers').controller('addressesController', function($scope, $log, $stateParams, $state, $timeout, $ionicHistory, $ionicScrollDelegate, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, bwcError, platformInfo, appConfigService, txFormatService, feeService) {
var UNUSED_ADDRESS_LIMIT = 5; var UNUSED_ADDRESS_LIMIT = 5;
var BALANCE_ADDRESS_LIMIT = 5; var BALANCE_ADDRESS_LIMIT = 5;
var config = configService.getSync().wallet.settings;
var unitName = config.unitName;
var unitToSatoshi = config.unitToSatoshi;
var satToUnit = 1 / unitToSatoshi;
var unitDecimals = config.unitDecimals;
var withBalance, cachedWallet; var withBalance, cachedWallet;
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
@ -55,7 +50,7 @@ angular.module('copayApp.controllers').controller('addressesController', functio
$scope.latestWithBalance = lodash.slice(withBalance, 0, BALANCE_ADDRESS_LIMIT); $scope.latestWithBalance = lodash.slice(withBalance, 0, BALANCE_ADDRESS_LIMIT);
lodash.each(withBalance, function(a) { lodash.each(withBalance, function(a) {
a.balanceStr = txFormatService.formatAmount(a.amount); a.balanceStr = txFormatService.formatAmountStr($scope.wallet.coin, a.amount);
}); });
$scope.viewAll = { $scope.viewAll = {
@ -75,11 +70,11 @@ angular.module('copayApp.controllers').controller('addressesController', functio
feeService.getFeeLevels(function(err, levels){ feeService.getFeeLevels($scope.wallet.coin, function(err, levels){
walletService.getLowUtxos($scope.wallet, levels, function(err, resp) { walletService.getLowUtxos($scope.wallet, levels, function(err, resp) {
if (err) return; if (err) return;
if (resp.allUtxos && resp.allUtxos.length) { if (resp && resp.allUtxos && resp.allUtxos.length) {
var allSum = lodash.sum(resp.allUtxos || 0, 'satoshis'); var allSum = lodash.sum(resp.allUtxos || 0, 'satoshis');
@ -88,9 +83,9 @@ angular.module('copayApp.controllers').controller('addressesController', functio
$scope.lowWarning = resp.warning; $scope.lowWarning = resp.warning;
$scope.lowUtxosNb = resp.lowUtxos.length; $scope.lowUtxosNb = resp.lowUtxos.length;
$scope.allUtxosNb = resp.allUtxos.length; $scope.allUtxosNb = resp.allUtxos.length;
$scope.lowUtxosSum = txFormatService.formatAmountStr(lodash.sum(resp.lowUtxos || 0, 'satoshis')); $scope.lowUtxosSum = txFormatService.formatAmountStr($scope.wallet.coin, lodash.sum(resp.lowUtxos || 0, 'satoshis'));
$scope.allUtxosSum = txFormatService.formatAmountStr(allSum); $scope.allUtxosSum = txFormatService.formatAmountStr($scope.wallet.coin, allSum);
$scope.minFee = txFormatService.formatAmountStr(resp.minFee || 0); $scope.minFee = txFormatService.formatAmountStr($scope.wallet.coin, resp.minFee || 0);
$scope.minFeePer = per.toFixed(2) + '%'; $scope.minFeePer = per.toFixed(2) + '%';

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService, platformInfo) { angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService, platformInfo, externalLinkService, gettextCatalog) {
var updateConfig = function() { var updateConfig = function() {
var config = configService.getSync(); var config = configService.getSync();
@ -14,6 +14,7 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.hideNextSteps = { $scope.hideNextSteps = {
value: config.hideNextSteps.enabled value: config.hideNextSteps.enabled
}; };
}; };
$scope.spendUnconfirmedChange = function() { $scope.spendUnconfirmedChange = function() {

View file

@ -9,6 +9,14 @@ angular.module('copayApp.controllers').controller('amountController', function($
var SMALL_FONT_SIZE_LIMIT = 10; var SMALL_FONT_SIZE_LIMIT = 10;
var LENGTH_EXPRESSION_LIMIT = 19; var LENGTH_EXPRESSION_LIMIT = 19;
var isNW = platformInfo.isNW; var isNW = platformInfo.isNW;
var unitIndex = 0;
var altUnitIndex = 0;
var availableUnits = [];
var fiatCode;
var fixedUnit;
$scope.isChromeApp = platformInfo.isChromeApp; $scope.isChromeApp = platformInfo.isChromeApp;
$scope.$on('$ionicView.leave', function() { $scope.$on('$ionicView.leave', function() {
@ -16,19 +24,100 @@ angular.module('copayApp.controllers').controller('amountController', function($
}); });
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
var config = configService.getSync().wallet.settings;
function setAvailableUnits() {
availableUnits = [];
var hasBTCWallets = profileService.getWallets({
coin: 'btc'
}).length;
if (hasBTCWallets) {
availableUnits.push({
name: 'Bitcoin',
id: 'btc',
shortName: 'BTC',
});
}
var hasBCHWallets = profileService.getWallets({
coin: 'bch'
}).length;
if (hasBCHWallets) {
availableUnits.push({
name: 'Bitcoin Cash',
id: 'bch',
shortName: 'BCH',
});
};
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
var fiatName;
if (data.stateParams.currency) {
fiatCode = data.stateParams.currency;
altUnitIndex = unitIndex
unitIndex = availableUnits.length;
} else {
fiatCode = config.alternativeIsoCode || 'USD';
fiatName = config.alternanativeName || fiatCode;
altUnitIndex = availableUnits.length;
}
availableUnits.push({
name: fiatName || fiatCode,
// TODO
id: fiatCode,
shortName: fiatCode,
isFiat: true,
});
if (data.stateParams.fixedUnit) {
fixedUnit = true;
}
};
// Go to... // Go to...
_id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID) _id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID)
$scope.nextStep = data.stateParams.nextStep; $scope.nextStep = data.stateParams.nextStep;
$scope.currency = data.stateParams.currency;
$scope.forceCurrency = data.stateParams.forceCurrency;
$scope.showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' ||
$ionicHistory.backView().stateName == 'tabs.bitpayCard'); setAvailableUnits();
updateUnitUI();
$scope.showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard');
$scope.recipientType = data.stateParams.recipientType || null; $scope.recipientType = data.stateParams.recipientType || null;
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail; $scope.toEmail = data.stateParams.toEmail;
$scope.showAlternativeAmount = !!$scope.nextStep;
$scope.toColor = data.stateParams.toColor; $scope.toColor = data.stateParams.toColor;
$scope.showSendMax = false; $scope.showSendMax = false;
@ -52,23 +141,13 @@ angular.module('copayApp.controllers').controller('amountController', function($
} else if (e.key.match(reOp)) { } else if (e.key.match(reOp)) {
$scope.pushOperator(e.key); $scope.pushOperator(e.key);
} else if (e.keyCode === 86) { } else if (e.keyCode === 86) {
if (e.ctrlKey || e.metaKey) if (e.ctrlKey || e.metaKey) processClipboard();
processClipboard(); } else if (e.keyCode === 13) $scope.finish();
} else if (e.keyCode === 13)
$scope.finish();
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
}); });
var config = configService.getSync().wallet.settings;
$scope.unitName = config.unitName;
if (data.stateParams.currency) {
$scope.alternativeIsoCode = data.stateParams.currency;
} else {
$scope.alternativeIsoCode = config.alternativeIsoCode || 'USD';
}
$scope.specificAmount = $scope.specificAlternativeAmount = ''; $scope.specificAmount = $scope.specificAlternativeAmount = '';
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
unitToSatoshi = config.unitToSatoshi; unitToSatoshi = config.unitToSatoshi;
@ -114,16 +193,59 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.finish(); $scope.finish();
}; };
$scope.toggleAlternative = function() {
if ($scope.forceCurrency) return;
$scope.showAlternativeAmount = !$scope.showAlternativeAmount;
$scope.toggleAlternative = function() {
if ($scope.amount && isExpression($scope.amount)) { if ($scope.amount && isExpression($scope.amount)) {
var amount = evaluate(format($scope.amount)); var amount = evaluate(format($scope.amount));
$scope.globalResult = '= ' + processResult(amount); $scope.globalResult = '= ' + processResult(amount);
} }
}; };
function updateUnitUI() {
$scope.unit = availableUnits[unitIndex].shortName;
$scope.alternativeUnit = availableUnits[altUnitIndex].shortName;
processAmount();
$log.debug('Update unit coin @amount unit:' + $scope.unit + " alternativeUnit:" + $scope.alternativeUnit);
};
$scope.changeUnit = function() {
if (fixedUnit) return;
unitIndex++;
if (unitIndex >= availableUnits.length) unitIndex = 0;
if (availableUnits[unitIndex].isFiat) {
// Always return to BTC... TODO?
altUnitIndex = 0;
} else {
altUnitIndex = lodash.findIndex(availableUnits, {
isFiat: true
});
}
updateUnitUI();
};
$scope.changeAlternativeUnit = function() {
// Do nothing is fiat is not main unit
if (!availableUnits[unitIndex].isFiat) return;
var nextCoin = lodash.findIndex(availableUnits, function(x) {
if (x.isFiat) return false;
if (x.id == availableUnits[altUnitIndex].id) return false;
return true;
});
if (nextCoin >= 0) {
altUnitIndex = nextCoin;
updateUnitUI();
}
};
function checkFontSize() { function checkFontSize() {
if ($scope.amount && $scope.amount.length >= SMALL_FONT_SIZE_LIMIT) $scope.smallFont = true; if ($scope.amount && $scope.amount.length >= SMALL_FONT_SIZE_LIMIT) $scope.smallFont = true;
else $scope.smallFont = false; else $scope.smallFont = false;
@ -132,7 +254,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.pushDigit = function(digit) { $scope.pushDigit = function(digit) {
if ($scope.amount && $scope.amount.length >= LENGTH_EXPRESSION_LIMIT) return; if ($scope.amount && $scope.amount.length >= LENGTH_EXPRESSION_LIMIT) return;
if ($scope.amount.indexOf('.') > -1 && digit == '.') return; if ($scope.amount.indexOf('.') > -1 && digit == '.') return;
if ($scope.showAlternativeAmount && $scope.amount.indexOf('.') > -1 && $scope.amount[$scope.amount.indexOf('.') + 2]) return; if (availableUnits[unitIndex].isFiat && $scope.amount.indexOf('.') > -1 && $scope.amount[$scope.amount.indexOf('.') + 2]) return;
$scope.amount = ($scope.amount + digit).replace('..', '.'); $scope.amount = ($scope.amount + digit).replace('..', '.');
checkFontSize(); checkFontSize();
@ -169,7 +291,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
}; };
$scope.resetAmount = function() { $scope.resetAmount = function() {
$scope.amount = $scope.alternativeResult = $scope.amountResult = $scope.globalResult = ''; $scope.amount = $scope.alternativeAmount = $scope.globalResult = '';
$scope.allowSend = false; $scope.allowSend = false;
checkFontSize(); checkFontSize();
}; };
@ -180,24 +302,39 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.allowSend = lodash.isNumber(result) && +result > 0; $scope.allowSend = lodash.isNumber(result) && +result > 0;
if (lodash.isNumber(result)) { if (lodash.isNumber(result)) {
$scope.globalResult = isExpression($scope.amount) ? '= ' + processResult(result) : ''; $scope.globalResult = isExpression($scope.amount) ? '= ' + processResult(result) : '';
$scope.amountResult = $filter('formatFiatAmount')(toFiat(result));
$scope.alternativeResult = txFormatService.formatAmount(fromFiat(result) * unitToSatoshi, true); if (availableUnits[unitIndex].isFiat) {
var a = fromFiat(result);
if (a) {
$scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true);
} else {
if (result) {
$scope.alternativeAmount = 'N/A';
} else {
$scope.alternativeAmount = null;
}
$scope.allowSend = false;
}
} else {
$scope.alternativeAmount = $filter('formatFiatAmount')(toFiat(result));
}
} }
}; };
function processResult(val) { function processResult(val) {
if ($scope.showAlternativeAmount) if (availableUnits[unitIndex].isFiat) return $filter('formatFiatAmount')(val);
return $filter('formatFiatAmount')(val); else return txFormatService.formatAmount(val.toFixed(unitDecimals) * unitToSatoshi, true);
else
return txFormatService.formatAmount(val.toFixed(unitDecimals) * unitToSatoshi, true);
}; };
function fromFiat(val) { function fromFiat(val) {
return parseFloat((rateService.fromFiat(val, $scope.alternativeIsoCode) * satToUnit).toFixed(unitDecimals)); return parseFloat((rateService.fromFiat(val, fiatCode, availableUnits[altUnitIndex].id) * satToUnit).toFixed(unitDecimals));
}; };
function toFiat(val) { function toFiat(val) {
return parseFloat((rateService.toFiat(val * unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2)); if (!rateService.getRate(fiatCode)) return;
return parseFloat((rateService.toFiat(val * unitToSatoshi, fiatCode, availableUnits[unitIndex].id)).toFixed(2));
}; };
function evaluate(val) { function evaluate(val) {
@ -212,33 +349,50 @@ angular.module('copayApp.controllers').controller('amountController', function($
}; };
function format(val) { function format(val) {
if (!val) return;
var result = val.toString(); var result = val.toString();
if (isOperator(lodash.last(val))) if (isOperator(lodash.last(val))) result = result.slice(0, -1);
result = result.slice(0, -1);
return result.replace('x', '*'); return result.replace('x', '*');
}; };
$scope.finish = function() { $scope.finish = function() {
var unit = availableUnits[unitIndex];
var _amount = evaluate(format($scope.amount)); var _amount = evaluate(format($scope.amount));
var coin = unit.id;
if (unit.isFiat) {
coin = availableUnits[altUnitIndex].id;
}
if ($scope.nextStep) { if ($scope.nextStep) {
$state.transitionTo($scope.nextStep, { $state.transitionTo($scope.nextStep, {
id: _id, id: _id,
amount: $scope.useSendMax ? null : _amount, amount: $scope.useSendMax ? null : _amount,
currency: $scope.showAlternativeAmount ? $scope.alternativeIsoCode : $scope.unitName, currency: unit.id.toUpperCase(),
coin: coin,
useSendMax: $scope.useSendMax useSendMax: $scope.useSendMax
}); });
} else { } else {
var amount = $scope.showAlternativeAmount ? fromFiat(_amount) : _amount; var amount = _amount;
if (unit.isFiat) {
amount = (fromFiat(amount) * unitToSatoshi).toFixed(0);
} else {
amount = (amount * unitToSatoshi).toFixed(0);
}
$state.transitionTo('tabs.send.confirm', { $state.transitionTo('tabs.send.confirm', {
recipientType: $scope.recipientType, recipientType: $scope.recipientType,
toAmount: $scope.useSendMax ? null : (amount * unitToSatoshi).toFixed(0), toAmount: amount,
toAddress: $scope.toAddress, toAddress: $scope.toAddress,
toName: $scope.toName, toName: $scope.toName,
toEmail: $scope.toEmail, toEmail: $scope.toEmail,
toColor: $scope.toColor, toColor: $scope.toColor,
coin: coin,
useSendMax: $scope.useSendMax useSendMax: $scope.useSendMax
}); });
} }

View file

@ -2,6 +2,7 @@
angular.module('copayApp.controllers').controller('buyAmazonController', function($scope, $log, $state, $timeout, $filter, $ionicHistory, $ionicConfig, lodash, amazonService, popupService, profileService, ongoingProcess, configService, walletService, payproService, bwcError, externalLinkService, platformInfo, gettextCatalog, txFormatService) { angular.module('copayApp.controllers').controller('buyAmazonController', function($scope, $log, $state, $timeout, $filter, $ionicHistory, $ionicConfig, lodash, amazonService, popupService, profileService, ongoingProcess, configService, walletService, payproService, bwcError, externalLinkService, platformInfo, gettextCatalog, txFormatService) {
var coin = 'btc';
var amount; var amount;
var currency; var currency;
var createdTx; var createdTx;
@ -64,7 +65,7 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio
}; };
var satToFiat = function(sat, cb) { var satToFiat = function(sat, cb) {
txFormatService.toFiat(sat, $scope.currencyIsoCode, function(value) { txFormatService.toFiat(coin, sat, $scope.currencyIsoCode, function(value) {
return cb(value); return cb(value);
}); });
}; };
@ -216,8 +217,8 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio
}); });
var initialize = function(wallet) { var initialize = function(wallet) {
var parsedAmount = txFormatService.parseAmount(amount, currency); var parsedAmount = txFormatService.parseAmount(coin, amount, currency);
$scope.currencyIsoCode = parsedAmount.alternativeIsoCode; $scope.currencyIsoCode = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr; $scope.amountUnitStr = parsedAmount.amountUnitStr;
var dataSrc = { var dataSrc = {
amount: parsedAmount.amount, amount: parsedAmount.amount,
@ -260,7 +261,7 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio
invoiceUrl: invoice.url, invoiceUrl: invoice.url,
invoiceTime: invoice.invoiceTime invoiceTime: invoice.invoiceTime
}; };
$scope.totalAmountStr = txFormatService.formatAmountStr(ctxp.amount); $scope.totalAmountStr = txFormatService.formatAmountStr(coin, ctxp.amount);
setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee); setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee);
}); });
}); });
@ -292,7 +293,8 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio
$scope.wallets = profileService.getWallets({ $scope.wallets = profileService.getWallets({
onlyComplete: true, onlyComplete: true,
network: $scope.network, network: $scope.network,
hasFunds: true hasFunds: true,
coin: coin
}); });
if (lodash.isEmpty($scope.wallets)) { if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack(null, gettextCatalog.getString('No wallets available')); showErrorAndBack(null, gettextCatalog.getString('No wallets available'));

View file

@ -2,6 +2,7 @@
angular.module('copayApp.controllers').controller('buyCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, $ionicConfig, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, txFormatService) { angular.module('copayApp.controllers').controller('buyCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, $ionicConfig, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, txFormatService) {
var coin = 'btc';
var amount; var amount;
var currency; var currency;
@ -33,6 +34,52 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
} }
}; };
var processPaymentInfo = function() {
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
$scope.paymentMethods.push(pm);
}
if (pm.allow_buy && pm.primary_buy) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
}
}
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
});
});
};
$scope.$on("$ionicView.beforeLeave", function(event, data) { $scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true); $ionicConfig.views.swipeBackEnabled(true);
}); });
@ -42,81 +89,22 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
}); });
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false; $scope.isFiat = data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount( amount = data.stateParams.amount;
data.stateParams.amount, currency = data.stateParams.currency;
data.stateParams.currency);
// Buy always in BTC $scope.network = coinbaseService.getNetwork();
amount = (parsedAmount.amountSat / 100000000).toFixed(8); $scope.wallets = profileService.getWallets({
currency = 'BTC'; onlyComplete: true,
network: $scope.network,
$scope.amountUnitStr = parsedAmount.amountUnitStr; coin: coin
ongoingProcess.set('calculatingFee', true);
coinbaseService.checkEnoughFundsForFee(amount, function(err) {
ongoingProcess.set('calculatingFee', false);
if (err) {
showErrorAndBack(err);
return;
}
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
$scope.paymentMethods.push(pm);
}
if (pm.allow_buy && pm.primary_buy) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
}
}
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
});
});
}); });
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
}); });
$scope.buyRequest = function() { $scope.buyRequest = function() {
@ -209,21 +197,31 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
}); });
}; };
var _processBuyOrder = function() {
coinbaseService.getBuyOrder(accessToken, accountId, b.data.id, function (err, buyResp) {
if (err) {
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
var tx = buyResp.data ? buyResp.data.transaction : null;
if (tx && tx.id) {
processBuyTx(tx);
} else {
$timeout(function() {
_processBuyOrder();
}, 5000);
}
});
}
$timeout(function() { $timeout(function() {
var tx = b.data ? b.data.transaction : null; var tx = b.data ? b.data.transaction : null;
if (tx) { if (tx && tx.id) {
processBuyTx(tx); processBuyTx(tx);
} }
else { else {
coinbaseService.getBuyOrder(accessToken, accountId, b.data.id, function (err, buyResp) { _processBuyOrder();
if (err) {
ongoingProcess.set('buyingBitcoin', false, statusChangeHandler);
showError(err);
return;
}
var tx = buyResp.data ? buyResp.data.transaction : null;
processBuyTx(tx);
});
} }
}, 8000); }, 8000);
}); });
@ -238,6 +236,25 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet; $scope.wallet = wallet;
var parsedAmount = txFormatService.parseAmount(
coin,
amount,
currency);
// Buy always in BTC
amount = (parsedAmount.amountSat / 100000000).toFixed(8);
currency = 'BTC';
$scope.amountUnitStr = parsedAmount.amountUnitStr;
ongoingProcess.set('calculatingFee', true);
coinbaseService.checkEnoughFundsForFee(amount, function(err) {
ongoingProcess.set('calculatingFee', false);
if (err) {
showErrorAndBack(err);
return;
}
processPaymentInfo();
});
}; };
$scope.goBackHome = function() { $scope.goBackHome = function() {

View file

@ -2,6 +2,7 @@
angular.module('copayApp.controllers').controller('buyGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, platformInfo, txFormatService) { angular.module('copayApp.controllers').controller('buyGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, platformInfo, txFormatService) {
var coin = 'btc';
var amount; var amount;
var currency; var currency;
@ -35,36 +36,7 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
} }
}; };
$scope.$on("$ionicView.beforeLeave", function(event, data) { var processPaymentInfo = function() {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$scope.network = glideraService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingGlidera', true); ongoingProcess.set('connectingGlidera', true);
glideraService.init(function(err, data) { glideraService.init(function(err, data) {
if (err) { if (err) {
@ -88,6 +60,33 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
$scope.buyInfo = buy; $scope.buyInfo = buy;
}); });
}); });
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'BTC' ? true : false;
amount = data.stateParams.amount;
currency = data.stateParams.currency;
$scope.network = glideraService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network,
coin: coin
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
}); });
var ask2FaCode = function(mode, cb) { var ask2FaCode = function(mode, cb) {
@ -105,7 +104,7 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
popupService.showPrompt(title, message, null, function(twoFaCode) { popupService.showPrompt(title, message, null, function(twoFaCode) {
if (typeof twoFaCode == 'undefined') return cb(); if (typeof twoFaCode == 'undefined') return cb();
return cb(twoFaCode); return cb(twoFaCode);
}); });
} else { } else {
return cb(); return cb();
} }
@ -116,7 +115,7 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
var okText = 'Confirm'; var okText = 'Confirm';
var cancelText = 'Cancel'; var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) { popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return; if (!ok) return;
ongoingProcess.set('buyingBitcoin', true, statusChangeHandler); ongoingProcess.set('buyingBitcoin', true, statusChangeHandler);
glideraService.get2faCode($scope.token, function(err, tfa) { glideraService.get2faCode($scope.token, function(err, tfa) {
if (err) { if (err) {
@ -162,6 +161,15 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet; $scope.wallet = wallet;
var parsedAmount = txFormatService.parseAmount(
coin,
amount,
currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
processPaymentInfo();
}; };
$scope.goBackHome = function() { $scope.goBackHome = function() {

View file

@ -0,0 +1,354 @@
'use strict';
angular.module('copayApp.controllers').controller('buyMercadoLibreController', function($scope, $log, $state, $timeout, $filter, $ionicHistory, $ionicConfig, lodash, mercadoLibreService, popupService, profileService, ongoingProcess, configService, walletService, payproService, bwcError, externalLinkService, platformInfo, txFormatService, gettextCatalog) {
var coin = 'btc';
var amount;
var currency;
var createdTx;
var message;
var invoiceId;
var configWallet = configService.getSync().wallet;
$scope.isCordova = platformInfo.isCordova;
$scope.openExternalLink = function(url) {
externalLinkService.open(url);
};
var _resetValues = function() {
$scope.totalAmountStr = $scope.amount = $scope.invoiceFee = $scope.networkFee = $scope.totalAmount = $scope.wallet = null;
createdTx = message = invoiceId = null;
};
var showErrorAndBack = function(title, msg) {
title = title || gettextCatalog.getString('Error');
$scope.sendStatus = '';
$log.error(msg);
msg = (msg && msg.errors) ? msg.errors[0].message : msg;
popupService.showAlert(title, msg, function() {
$ionicHistory.goBack();
});
};
var showError = function(title, msg, cb) {
cb = cb || function() {};
title = title || gettextCatalog.getString('Error');
$scope.sendStatus = '';
$log.error(msg);
msg = (msg && msg.errors) ? msg.errors[0].message : msg;
popupService.showAlert(title, msg, cb);
};
var publishAndSign = function(wallet, txp, onSendStatusChange, cb) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
var err = 'No signing proposal: No private key';
$log.info(err);
return cb(err);
}
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return cb(err);
return cb(null, txp);
}, onSendStatusChange);
};
var statusChangeHandler = function(processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if (processName == 'Comprando Vale-Presente' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
$scope.sendStatus = showName;
}
};
var satToFiat = function(sat, cb) {
txFormatService.toFiat(coin, sat, $scope.currencyIsoCode, function(value) {
return cb(value);
});
};
var setTotalAmount = function(amountSat, invoiceFeeSat, networkFeeSat) {
satToFiat(amountSat, function(a) {
$scope.amount = Number(a);
satToFiat(invoiceFeeSat, function(i) {
$scope.invoiceFee = Number(i);
satToFiat(networkFeeSat, function(n) {
$scope.networkFee = Number(n);
$scope.totalAmount = $scope.amount + $scope.invoiceFee + $scope.networkFee;
$timeout(function() {
$scope.$digest();
});
});
});
});
};
var createInvoice = function(data, cb) {
mercadoLibreService.createBitPayInvoice(data, function(err, dataInvoice) {
if (err) {
var err_title = gettextCatalog.getString('Error creating the invoice');
var err_msg;
if (err && err.message && err.message.match(/suspended/i)) {
err_title = gettextCatalog.getString('Service not available');
err_msg = gettextCatalog.getString('Mercadolibre Gift Card Service is not available at this moment. Please try back later.');
} else if (err && err.message) {
err_msg = err.message;
} else {
err_msg = gettextCatalog.getString('Could not access Gift Card Service');
};
return cb({
title: err_title,
message: err_msg
});
}
var accessKey = dataInvoice ? dataInvoice.accessKey : null;
if (!accessKey) {
return cb({
message: gettextCatalog.getString('No access key defined')
});
}
mercadoLibreService.getBitPayInvoice(dataInvoice.invoiceId, function(err, invoice) {
if (err) {
return cb({
message: gettextCatalog.getString('Could not get the invoice')
});
}
return cb(null, invoice, accessKey);
});
});
};
var createTx = function(wallet, invoice, message, cb) {
var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null;
if (!payProUrl) {
return cb({
title: gettextCatalog.getString('Error in Payment Protocol'),
message: gettextCatalog.getString('Invalid URL')
});
}
var outputs = [];
var toAddress = invoice.bitcoinAddress;
var amountSat = parseInt((invoice.btcDue * 100000000).toFixed(0)); // BTC to Satoshi
outputs.push({
'toAddress': toAddress,
'amount': amountSat,
'message': message
});
var txp = {
toAddress: toAddress,
amount: amountSat,
outputs: outputs,
message: message,
payProUrl: payProUrl,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: configWallet.settings.feeLevel || 'normal'
};
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not create transaction'),
message: bwcError.msg(err)
});
}
return cb(null, ctxp);
});
};
var checkTransaction = lodash.throttle(function(count, dataSrc) {
mercadoLibreService.createGiftCard(dataSrc, function(err, giftCard) {
$log.debug("creating gift card " + count);
if (err) {
$scope.sendStatus = '';
ongoingProcess.set('Comprando Vale-Presente', false, statusChangeHandler);
giftCard = {};
giftCard.status = 'FAILURE';
}
if (giftCard && giftCard.cardStatus && (giftCard.cardStatus != 'active' && giftCard.cardStatus != 'inactive' && giftCard.cardStatus != 'expired')) {
$scope.sendStatus = '';
ongoingProcess.set('Comprando Vale-Presente', false, statusChangeHandler);
giftCard = {};
giftCard.status = 'FAILURE';
}
if (giftCard.status == 'PENDING' && count < 3) {
$log.debug("Waiting for payment confirmation");
checkTransaction(count + 1, dataSrc);
return;
}
var now = moment().unix() * 1000;
var newData = giftCard;
newData['invoiceId'] = dataSrc.invoiceId;
newData['accessKey'] = dataSrc.accessKey;
newData['invoiceUrl'] = dataSrc.invoiceUrl;
newData['amount'] = dataSrc.amount;
newData['currency'] = dataSrc.currency;
newData['date'] = dataSrc.invoiceTime || now;
newData['uuid'] = dataSrc.uuid;
mercadoLibreService.savePendingGiftCard(newData, null, function(err) {
ongoingProcess.set('Comprando Vale-Presente', false, statusChangeHandler);
$log.debug("Saving new gift card with status: " + newData.status);
$scope.mlGiftCard = newData;
});
});
}, 8000, {
'leading': true
});
var initialize = function(wallet) {
var parsedAmount = txFormatService.parseAmount(coin, amount, currency);
$scope.currencyIsoCode = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
var dataSrc = {
amount: parsedAmount.amount,
currency: parsedAmount.currency,
uuid: wallet.id
};
ongoingProcess.set('loadingTxInfo', true);
createInvoice(dataSrc, function(err, invoice, accessKey) {
if (err) {
ongoingProcess.set('loadingTxInfo', false);
showErrorAndBack(err.title, err.message);
return;
}
// Sometimes API does not return this element;
invoice['buyerPaidBtcMinerFee'] = invoice.buyerPaidBtcMinerFee || 0;
var invoiceFeeSat = (invoice.buyerPaidBtcMinerFee * 100000000).toFixed();
message = gettextCatalog.getString("{{amountStr}} for Mercado Livre Brazil Gift Card", {
amountStr: $scope.amountUnitStr
});
createTx(wallet, invoice, message, function(err, ctxp) {
ongoingProcess.set('loadingTxInfo', false);
if (err) {
_resetValues();
showError(err.title, err.message);
return;
}
// Save in memory
createdTx = ctxp;
invoiceId = invoice.id;
createdTx['giftData'] = {
currency: dataSrc.currency,
amount: dataSrc.amount,
uuid: dataSrc.uuid,
accessKey: accessKey,
invoiceId: invoice.id,
invoiceUrl: invoice.url,
invoiceTime: invoice.invoiceTime
};
$scope.totalAmountStr = txFormatService.formatAmountStr(coin, ctxp.amount);
setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee);
});
});
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
amount = data.stateParams.amount;
currency = data.stateParams.currency;
if (amount > 2000 || amount < 50) {
showErrorAndBack(null, gettextCatalog.getString('Purchase amount must be a value between 50 and 2000'));
return;
}
$scope.network = mercadoLibreService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network,
coin: coin
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack(null, gettextCatalog.getString('No wallets available'));
return;
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
});
$scope.buyConfirm = function() {
if (!createdTx) {
showError(null, gettextCatalog.getString('Transaction has not been created'));
return;
}
var title = gettextCatalog.getString('Confirm');
var okText = gettextCatalog.getString('Ok');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(title, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
return;
}
ongoingProcess.set('Comprando Vale-Presente', true, statusChangeHandler);
publishAndSign($scope.wallet, createdTx, function() {}, function(err, txSent) {
if (err) {
ongoingProcess.set('Comprando Vale-Presente', false, statusChangeHandler);
showError(gettextCatalog.getString('Could not send transaction'), err);
return;
}
checkTransaction(1, createdTx.giftData);
});
});
};
$scope.showWalletSelector = function() {
$scope.walletSelectorTitle = 'Buy from';
$scope.showWallets = true;
};
$scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet;
initialize(wallet);
};
$scope.goBackHome = function() {
$scope.sendStatus = '';
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.home').then(function() {
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.transitionTo('tabs.giftcards.mercadoLibre').then(function() {
$state.transitionTo('tabs.giftcards.mercadoLibre.cards', {
invoiceId: invoiceId
});
});
});
};
});

View file

@ -0,0 +1,192 @@
'use strict';
angular.module('copayApp.controllers').controller('cashScanController',
function($rootScope, $timeout, $scope, $state, $ionicHistory, gettextCatalog, lodash, ongoingProcess, profileService, walletService, $log, txFormatService, bwcError, pushNotificationsService, bwcService, externalLinkService) {
var wallet;
var errors = bwcService.getErrors();
$scope.error = null;
$scope.walletDisabled = '#667';
$scope.$on("$ionicView.beforeEnter", function(event, data) {
updateAllWallets();
});
$scope.openRecoveryToolLink = function() {
var url = 'https://bitpay.github.io/copay-recovery/';
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Open the recovery tool.');
var okText = gettextCatalog.getString('Open');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
var goHome = function() {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.settings').then(function() {
$state.transitionTo('tabs.home');
});
}
var updateAllWallets = function() {
var walletsBTC = profileService.getWallets({
coin: 'btc',
onlyComplete: true,
network: 'livenet'
});
// Filter out already duplicated wallets
var walletsBCH = profileService.getWallets({
coin: 'bch',
network: 'livenet'
});
var xPubKeyIndex = lodash.indexBy(walletsBCH, "credentials.xPubKey");
walletsBTC = lodash.filter(walletsBTC, function(w) {
return !xPubKeyIndex[w.credentials.xPubKey];
});
var availableWallets = [];
var nonEligibleWallets = [];
lodash.each(walletsBTC, function(w) {
if (w.credentials.derivationStrategy != 'BIP44') {
w.excludeReason = gettextCatalog.getString('Non BIP44 wallet');
nonEligibleWallets.push(w);
} else if (!w.canSign()) {
w.excludeReason = gettextCatalog.getString('Read only wallet');
nonEligibleWallets.push(w);
} else if (w.needsBackup) {
w.excludeReason = gettextCatalog.getString('Backup needed');
nonEligibleWallets.push(w);
} else {
availableWallets.push(w);
}
});
$scope.availableWallets = availableWallets;
$scope.nonEligibleWallets = nonEligibleWallets;
var i = availableWallets.length;
var j = 0;
lodash.each(availableWallets, function(wallet) {
walletService.getBalance(wallet, {
coin: 'bch'
}, function(err, balance) {
if (err) {
wallet.error = (err === 'WALLET_NOT_REGISTERED') ? gettextCatalog.getString('Wallet not registered') : bwcError.msg(err);
$log.error(err);
return;
}
wallet.error = null;
wallet.bchBalance = txFormatService.formatAmountStr('bch', balance.availableAmount);
if (++j == i) {
//Done
$timeout(function() {
$rootScope.$apply();
}, 10);
}
});
});
};
$scope.duplicate = function(wallet) {
$scope.error = null;
$log.debug('Duplicating wallet for BCH:' + wallet.id + ':' + wallet.name);
var opts = {};
opts.name = wallet.name + '[BCH]';
opts.m = wallet.m;
opts.n = wallet.n;
opts.myName = wallet.credentials.copayerName;
opts.networkName = wallet.network;
opts.coin = 'bch';
opts.walletPrivKey = wallet.credentials.walletPrivKey;
opts.compliantDerivation = wallet.credentials.compliantDerivation;
function setErr(err, cb) {
if (!cb) cb = function() {};
$scope.error = bwcError.cb(err, gettextCatalog.getString('Could not duplicate'), function() {
return cb(err);
});
$timeout(function() {
$rootScope.$apply();
}, 10);
}
function importOrCreate(cb) {
walletService.getStatus(wallet, {}, function(err, status) {
if (err) return cb(err);
opts.singleAddress = status.wallet.singleAddress;
// first try to import
profileService.importExtendedPrivateKey(opts.extendedPrivateKey, opts, function(err, newWallet) {
if (err && !(err instanceof errors.NOT_AUTHORIZED)) {
return setErr(err, cb);
}
if (err) {
// create and store a wallet
return profileService.createWallet(opts, function(err, newWallet) {
if (err) return setErr(err, cb);
return cb(null, newWallet, true);
});
}
return cb(null, newWallet);
});
});
};
// Multisig wallets? add Copayers
function addCopayers(newWallet, isNew, cb) {
if (!isNew) return cb();
if (wallet.n == 1) return cb();
$log.info('Adding copayers for BCH wallet config:' + wallet.m + '-' + wallet.n);
walletService.copyCopayers(wallet, newWallet, function(err) {
if (err) return setErr(err, cb);
return cb();
});
};
walletService.getKeys(wallet, function(err, keys) {
if (err) {
$scope.error = err;
return $timeout(function() {
$rootScope.$apply();
}, 10);
}
opts.extendedPrivateKey = keys.xPrivKey;
ongoingProcess.set('duplicatingWallet', true);
importOrCreate(function(err, newWallet, isNew) {
if (err) {
ongoingProcess.set('duplicatingWallet', false);
return;
}
walletService.updateRemotePreferences(newWallet);
pushNotificationsService.updateSubscription(newWallet);
addCopayers(newWallet, isNew, function(err) {
ongoingProcess.set('duplicatingWallet', false);
if (err)
return setErr(err);
if (isNew)
walletService.startScan(newWallet, function() {});
goHome();
});
});
});
}
});

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification) { angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification, externalLinkService) {
var countDown = null; var countDown = null;
var CONFIRM_LIMIT_USD = 20; var CONFIRM_LIMIT_USD = 20;
@ -28,7 +28,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
function refresh() { function refresh() {
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 1); }, 10);
} }
@ -58,9 +58,10 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}); });
}; };
function setNoWallet(msg) { function setNoWallet(msg, criticalError) {
$scope.wallet = null; $scope.wallet = null;
$scope.noWalletMessage = msg; $scope.noWalletMessage = msg;
$scope.criticalError = criticalError;
$log.warn('Not ready to make the payment:' + msg); $log.warn('Not ready to make the payment:' + msg);
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
@ -69,18 +70,19 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
function setWalletSelector(network, minAmount, cb) { function setWalletSelector(coin, network, minAmount, cb) {
// no min amount? (sendMax) => look for no empty wallets // no min amount? (sendMax) => look for no empty wallets
minAmount = minAmount || 1; minAmount = minAmount || 1;
$scope.wallets = profileService.getWallets({ $scope.wallets = profileService.getWallets({
onlyComplete: true, onlyComplete: true,
network: network network: network,
coin: coin
}); });
if (!$scope.wallets || !$scope.wallets.length) { if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet(gettextCatalog.getString('No wallets available')); setNoWallet(gettextCatalog.getString('No wallets available'), true);
return cb(); return cb();
} }
@ -109,7 +111,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
return cb('Could not update any wallet'); return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) { if (lodash.isEmpty(filteredWallets)) {
setNoWallet(gettextCatalog.getString('Insufficient funds')); setNoWallet(gettextCatalog.getString('Insufficient funds'), true);
} }
$scope.wallets = lodash.clone(filteredWallets); $scope.wallets = lodash.clone(filteredWallets);
return cb(); return cb();
@ -120,6 +122,30 @@ angular.module('copayApp.controllers').controller('confirmController', function(
// Setup $scope // 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('Copay only supports Bitcoin Cash using new version numbers addresses');
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;
}
// Grab stateParams // Grab stateParams
tx = { tx = {
toAmount: parseInt(data.stateParams.toAmount), toAmount: parseInt(data.stateParams.toAmount),
@ -136,33 +162,32 @@ angular.module('copayApp.controllers').controller('confirmController', function(
toName: data.stateParams.toName, toName: data.stateParams.toName,
toEmail: data.stateParams.toEmail, toEmail: data.stateParams.toEmail,
toColor: data.stateParams.toColor, toColor: data.stateParams.toColor,
network: (new bitcore.Address(data.stateParams.toAddress)).network.name, network: networkName,
coin: data.stateParams.coin,
txp: {}, txp: {},
}; };
if (tx.coin && tx.coin == 'bch') tx.feeLevel = 'normal';
// Other Scope vars // Other Scope vars
$scope.isCordova = isCordova; $scope.isCordova = isCordova;
$scope.isWindowsPhoneApp = isWindowsPhoneApp; $scope.isWindowsPhoneApp = isWindowsPhoneApp;
$scope.showAddress = false; $scope.showAddress = false;
updateTx(tx, null, {}, function() { $scope.walletSelectorTitle = gettextCatalog.getString('Send from');
$scope.walletSelectorTitle = gettextCatalog.getString('Send from'); setWalletSelector(tx.coin, tx.network, tx.toAmount, function(err) {
if (err) {
setWalletSelector(tx.network, tx.toAmount, function(err) { return exitWithError('Could not update wallets');
if (err) { }
return exitWithError('Could not update wallets');
}
if ($scope.wallets.length > 1) {
$scope.showWalletSelector();
} else if ($scope.wallets.length) {
setWallet($scope.wallets[0], tx);
}
});
if ($scope.wallets.length > 1) {
$scope.showWalletSelector();
} else if ($scope.wallets.length) {
setWallet($scope.wallets[0], tx);
}
}); });
}); });
@ -227,6 +252,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}; };
function updateTx(tx, wallet, opts, cb) { function updateTx(tx, wallet, opts, cb) {
ongoingProcess.set('calculatingFee', true);
if (opts.clearCache) { if (opts.clearCache) {
tx.txp = {}; tx.txp = {};
@ -238,10 +264,10 @@ angular.module('copayApp.controllers').controller('confirmController', function(
if (!tx.toAmount) return; if (!tx.toAmount) return;
// Amount // Amount
tx.amountStr = txFormatService.formatAmountStr(tx.toAmount); tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.toAmount);
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(tx.toAmount, function(v) { txFormatService.formatAlternativeStr(wallet.coin, tx.toAmount, function(v) {
tx.alternativeAmountStr = v; tx.alternativeAmountStr = v;
}); });
} }
@ -250,19 +276,23 @@ angular.module('copayApp.controllers').controller('confirmController', function(
refresh(); refresh();
// End of quick refresh, before wallet is selected. // End of quick refresh, before wallet is selected.
if (!wallet) return cb(); if (!wallet) {
ongoingProcess.set('calculatingFee', false);
return cb();
}
feeService.getFeeRate(tx.network, tx.feeLevel, function(err, feeRate) { feeService.getFeeRate(wallet.coin, tx.network, tx.feeLevel, function(err, feeRate) {
if (err) return cb(err); if (err) {
ongoingProcess.set('calculatingFee', false);
return cb(err);
}
if (!usingCustomFee) tx.feeRate = feeRate; if (!usingCustomFee) tx.feeRate = feeRate;
tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; tx.feeLevelName = feeService.feeOpts[tx.feeLevel];
if (!wallet)
return cb();
getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) {
if (err) { if (err) {
ongoingProcess.set('calculatingFee', false);
var msg = gettextCatalog.getString('Error getting SendMax information'); var msg = gettextCatalog.getString('Error getting SendMax information');
return setSendError(msg); return setSendError(msg);
} }
@ -272,6 +302,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$log.debug('Send max info', sendMaxInfo); $log.debug('Send max info', sendMaxInfo);
if (tx.sendMax && sendMaxInfo.amount == 0) { if (tx.sendMax && sendMaxInfo.amount == 0) {
ongoingProcess.set('calculatingFee', false);
setNoWallet(gettextCatalog.getString('Insufficient funds')); setNoWallet(gettextCatalog.getString('Insufficient funds'));
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return cb('no_funds'); return cb('no_funds');
@ -280,20 +311,27 @@ angular.module('copayApp.controllers').controller('confirmController', function(
tx.sendMaxInfo = sendMaxInfo; tx.sendMaxInfo = sendMaxInfo;
tx.toAmount = tx.sendMaxInfo.amount; tx.toAmount = tx.sendMaxInfo.amount;
updateAmount(); updateAmount();
showSendMaxWarning(sendMaxInfo); ongoingProcess.set('calculatingFee', false);
$timeout(function() {
showSendMaxWarning(wallet, sendMaxInfo);
}, 200);
} }
// txp already generated for this wallet? // txp already generated for this wallet?
if (tx.txp[wallet.id]) { if (tx.txp[wallet.id]) {
ongoingProcess.set('calculatingFee', false);
refresh(); refresh();
return cb(); return cb();
} }
getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) {
if (err) return cb(err); ongoingProcess.set('calculatingFee', false);
if (err) {
return cb(err);
}
txp.feeStr = txFormatService.formatAmountStr(txp.fee); txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee);
txFormatService.formatAlternativeStr(txp.fee, function(v) { txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) {
txp.alternativeFeeStr = v; txp.alternativeFeeStr = v;
}); });
@ -321,14 +359,26 @@ angular.module('copayApp.controllers').controller('confirmController', function(
} }
function setButtonText(isMultisig, isPayPro) { function setButtonText(isMultisig, isPayPro) {
$scope.buttonText = gettextCatalog.getString(isCordova && !isWindowsPhoneApp ? 'Slide' : 'Click') + ' ';
if (isPayPro) { if (isPayPro) {
$scope.buttonText += gettextCatalog.getString('to pay'); if (isCordova && !isWindowsPhoneApp) {
$scope.buttonText = gettextCatalog.getString('Slide to pay');
} else {
$scope.buttonText = gettextCatalog.getString('Click to pay');
}
} else if (isMultisig) { } else if (isMultisig) {
$scope.buttonText += gettextCatalog.getString('to accept'); if (isCordova && !isWindowsPhoneApp) {
} else $scope.buttonText = gettextCatalog.getString('Slide to accept');
$scope.buttonText += gettextCatalog.getString('to send'); } else {
$scope.buttonText = gettextCatalog.getString('Click to accept');
}
} else {
if (isCordova && !isWindowsPhoneApp) {
$scope.buttonText = gettextCatalog.getString('Slide to send');
} else {
$scope.buttonText = gettextCatalog.getString('Click to send');
}
}
}; };
@ -337,26 +387,26 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}; };
function showSendMaxWarning(sendMaxInfo) { function showSendMaxWarning(wallet, sendMaxInfo) {
function verifyExcludedUtxos() { function verifyExcludedUtxos() {
var warningMsg = []; var warningMsg = [];
if (sendMaxInfo.utxosBelowFee > 0) { 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.", { warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", {
amountBelowFeeStr: txFormatService.formatAmountStr(sendMaxInfo.amountBelowFee) amountBelowFeeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountBelowFee)
})); }));
} }
if (sendMaxInfo.utxosAboveMaxSize > 0) { if (sendMaxInfo.utxosAboveMaxSize > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", { warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", {
amountAboveMaxSizeStr: txFormatService.formatAmountStr(sendMaxInfo.amountAboveMaxSize) amountAboveMaxSizeStr: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.amountAboveMaxSize)
})); }));
} }
return warningMsg.join('\n'); return warningMsg.join('\n');
}; };
var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", { var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", {
fee: txFormatService.formatAmountStr(sendMaxInfo.fee) fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee)
}); });
var warningMsg = verifyExcludedUtxos(); var warningMsg = verifyExcludedUtxos();
@ -422,6 +472,11 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.wallet = wallet; $scope.wallet = wallet;
// If select another wallet
tx.coin = wallet.coin;
tx.feeLevel = wallet.coin == 'bch' ? 'normal' : configFeeLevel;
usingCustomFee = null;
setButtonText(wallet.credentials.m > 1, !!tx.paypro); setButtonText(wallet.credentials.m > 1, !!tx.paypro);
if (tx.paypro) if (tx.paypro)
@ -483,7 +538,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
if (walletService.isEncrypted(wallet)) if (walletService.isEncrypted(wallet))
return cb(); return cb();
var amountUsd = parseFloat(txFormatService.formatToUSD(txp.amount)); var amountUsd = parseFloat(txFormatService.formatToUSD(wallet.coin, txp.amount));
if (amountUsd <= CONFIRM_LIMIT_USD) if (amountUsd <= CONFIRM_LIMIT_USD)
return cb(); return cb();
@ -563,10 +618,13 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.chooseFeeLevel = function(tx, wallet) { $scope.chooseFeeLevel = function(tx, wallet) {
if (wallet.coin == 'bch') return;
var scope = $rootScope.$new(true); var scope = $rootScope.$new(true);
scope.network = tx.network; scope.network = tx.network;
scope.feeLevel = tx.feeLevel; scope.feeLevel = tx.feeLevel;
scope.noSave = true; scope.noSave = true;
scope.coin = wallet.coin;
if (usingCustomFee) { if (usingCustomFee) {
scope.customFeePerKB = tx.feeRate; scope.customFeePerKB = tx.feeRate;

View file

@ -22,11 +22,16 @@ angular.module('copayApp.controllers').controller('createController',
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.formData = {}; $scope.formData = {};
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
var config = configService.getSync();
var tc = $state.current.name == 'tabs.add.create-personal' ? 1 : defaults.wallet.totalCopayers; var tc = $state.current.name == 'tabs.add.create-personal' ? 1 : defaults.wallet.totalCopayers;
$scope.formData.account = 1; $scope.formData.account = 1;
$scope.formData.bwsurl = defaults.bws.url; $scope.formData.bwsurl = defaults.bws.url;
$scope.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1); $scope.TCValues = lodash.range(2, defaults.limits.totalCopayers + 1);
$scope.formData.derivationPath = derivationPathHelper.default; $scope.formData.derivationPath = derivationPathHelper.default;
$scope.formData.coin = data.stateParams.coin;
if (config.cashSupport) $scope.enableCash = true;
$scope.setTotalCopayers(tc); $scope.setTotalCopayers(tc);
updateRCSelect(tc); updateRCSelect(tc);
resetPasswordFields(); resetPasswordFields();
@ -133,10 +138,11 @@ angular.module('copayApp.controllers').controller('createController',
m: $scope.formData.requiredCopayers, m: $scope.formData.requiredCopayers,
n: $scope.formData.totalCopayers, n: $scope.formData.totalCopayers,
myName: $scope.formData.totalCopayers > 1 ? $scope.formData.myName : null, myName: $scope.formData.totalCopayers > 1 ? $scope.formData.myName : null,
networkName: $scope.formData.testnetEnabled ? 'testnet' : 'livenet', networkName: $scope.formData.testnetEnabled && $scope.formData.coin != 'bch' ? 'testnet' : 'livenet',
bwsurl: $scope.formData.bwsurl, bwsurl: $scope.formData.bwsurl,
singleAddress: $scope.formData.singleAddressEnabled, singleAddress: $scope.formData.singleAddressEnabled,
walletPrivKey: $scope.formData._walletPrivKey, // Only for testing walletPrivKey: $scope.formData._walletPrivKey, // Only for testing
coin: $scope.formData.coin
}; };
var setSeed = $scope.formData.seedSource.id == 'set'; var setSeed = $scope.formData.seedSource.id == 'set';
@ -170,6 +176,11 @@ angular.module('copayApp.controllers').controller('createController',
} }
if ($scope.formData.seedSource.id == walletService.externalSource.ledger.id || $scope.formData.seedSource.id == walletService.externalSource.trezor.id || $scope.formData.seedSource.id == walletService.externalSource.intelTEE.id) { if ($scope.formData.seedSource.id == walletService.externalSource.ledger.id || $scope.formData.seedSource.id == walletService.externalSource.trezor.id || $scope.formData.seedSource.id == walletService.externalSource.intelTEE.id) {
if ($scope.formData.coin == 'bch') {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Hardware wallets are not yet supported with Bitcoin Cash'));
return;
}
var account = $scope.formData.account; var account = $scope.formData.account;
if (!account || account < 1) { if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));

View file

@ -8,6 +8,10 @@ angular.module('copayApp.controllers').controller('customAmountController', func
}); });
}; };
var setProtocolHandler = function() {
$scope.protocolHandler = walletService.getProtocolHandler($scope.wallet);
}
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
var walletId = data.stateParams.id; var walletId = data.stateParams.id;
@ -15,21 +19,25 @@ angular.module('copayApp.controllers').controller('customAmountController', func
showErrorAndBack('Error', 'No wallet selected'); showErrorAndBack('Error', 'No wallet selected');
return; return;
} }
$scope.showShareButton = platformInfo.isCordova ? (platformInfo.isIOS ? 'iOS' : 'Android') : null; $scope.showShareButton = platformInfo.isCordova ? (platformInfo.isIOS ? 'iOS' : 'Android') : null;
$scope.wallet = profileService.getWallet(walletId); $scope.wallet = profileService.getWallet(walletId);
setProtocolHandler();
walletService.getAddress($scope.wallet, false, function(err, addr) { walletService.getAddress($scope.wallet, false, function(err, addr) {
if (!addr) { if (!addr) {
showErrorAndBack('Error', 'Could not get the address'); showErrorAndBack('Error', 'Could not get the address');
return; return;
} }
$scope.address = addr; $scope.address = addr;
$scope.coin = data.stateParams.coin;
var parsedAmount = txFormatService.parseAmount( var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount, $scope.wallet.coin,
data.stateParams.amount,
data.stateParams.currency); data.stateParams.currency);
// Amount in USD or BTC // Amount in USD or BTC
@ -37,17 +45,17 @@ angular.module('copayApp.controllers').controller('customAmountController', func
var currency = parsedAmount.currency; var currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr; $scope.amountUnitStr = parsedAmount.amountUnitStr;
if (currency != 'BTC') { if (currency != 'BTC' && currency != 'BCH') {
// Convert to BTC // Convert to BTC or BCH
var config = configService.getSync().wallet.settings; var config = configService.getSync().wallet.settings;
var amountUnit = txFormatService.satToUnit(parsedAmount.amountSat); var amountUnit = txFormatService.satToUnit(parsedAmount.amountSat);
var btcParsedAmount = txFormatService.parseAmount(amountUnit, config.unitName); var btcParsedAmount = txFormatService.parseAmount($scope.wallet.coin, amountUnit, $scope.wallet.coin);
$scope.amountBtc = btcParsedAmount.amount; $scope.amountBtc = btcParsedAmount.amount;
$scope.altAmountStr = btcParsedAmount.amountUnitStr; $scope.altAmountStr = btcParsedAmount.amountUnitStr;
} else { } else {
$scope.amountBtc = amount; // BTC $scope.amountBtc = amount; // BTC or BCH
$scope.altAmountStr = txFormatService.formatAlternativeStr(parsedAmount.amountSat); $scope.altAmountStr = txFormatService.formatAlternativeStr($scope.wallet.coin, parsedAmount.amountSat);
} }
}); });
}); });
@ -61,12 +69,16 @@ angular.module('copayApp.controllers').controller('customAmountController', func
$scope.shareAddress = function() { $scope.shareAddress = function() {
if (!platformInfo.isCordova) return; if (!platformInfo.isCordova) return;
var data = 'bitcoin:' + $scope.address + '?amount=' + $scope.amountBtc; var protocol = 'bitcoin';
if ($scope.wallet.coin == 'bch') protocol += 'cash';
var data = protocol + ':' + $scope.address + '?amount=' + $scope.amountBtc;
window.plugins.socialsharing.share(data, null, null, null); window.plugins.socialsharing.share(data, null, null, null);
} }
$scope.copyToClipboard = function() { $scope.copyToClipboard = function() {
return 'bitcoin:' + $scope.address + '?amount=' + $scope.amountBtc; var protocol = 'bitcoin';
if ($scope.wallet.coin == 'bch') protocol += 'cash';
return protocol + ':' + $scope.address + '?amount=' + $scope.amountBtc;
}; };
}); });

View file

@ -5,6 +5,7 @@ angular.module('copayApp.controllers').controller('importController',
var reader = new FileReader(); var reader = new FileReader();
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
var config = configService.getSync();
var errors = bwcService.getErrors(); var errors = bwcService.getErrors();
$scope.init = function() { $scope.init = function() {
@ -15,9 +16,14 @@ angular.module('copayApp.controllers').controller('importController',
$scope.formData.bwsurl = defaults.bws.url; $scope.formData.bwsurl = defaults.bws.url;
$scope.formData.derivationPath = derivationPathHelper.default; $scope.formData.derivationPath = derivationPathHelper.default;
$scope.formData.account = 1; $scope.formData.account = 1;
$scope.formData.coin = $stateParams.coin;
$scope.importErr = false; $scope.importErr = false;
$scope.isCopay = appConfigService.name == 'copay'; $scope.isCopay = appConfigService.name == 'copay';
$scope.fromHardwareWallet = { value: false }; $scope.fromHardwareWallet = {
value: false
};
if (config.cashSupport) $scope.enableCash = true;
if ($stateParams.code) if ($stateParams.code)
$scope.processWalletInfo($stateParams.code); $scope.processWalletInfo($stateParams.code);
@ -59,6 +65,15 @@ angular.module('copayApp.controllers').controller('importController',
}); });
}; };
$scope.switchTestnetOff = function() {
$scope.formData.testnetEnabled = false;
$scope.setDerivationPath();
$scope.resizeView();
$timeout(function() {
$scope.$apply();
});
};
$scope.processWalletInfo = function(code) { $scope.processWalletInfo = function(code) {
if (!code) return; if (!code) return;
@ -203,6 +218,7 @@ angular.module('copayApp.controllers').controller('importController',
if (evt.target.readyState == FileReader.DONE) { // DONE == 2 if (evt.target.readyState == FileReader.DONE) { // DONE == 2
var opts = {}; var opts = {};
opts.bwsurl = $scope.formData.bwsurl; opts.bwsurl = $scope.formData.bwsurl;
opts.coin = $scope.formData.coin;
_importBlob(evt.target.result, opts); _importBlob(evt.target.result, opts);
} }
} }
@ -228,6 +244,7 @@ angular.module('copayApp.controllers').controller('importController',
} else { } else {
var opts = {}; var opts = {};
opts.bwsurl = $scope.formData.bwsurl; opts.bwsurl = $scope.formData.bwsurl;
opts.coin = $scope.formData.coin;
_importBlob(backupText, opts); _importBlob(backupText, opts);
} }
}; };
@ -253,6 +270,7 @@ angular.module('copayApp.controllers').controller('importController',
opts.account = pathData.account; opts.account = pathData.account;
opts.networkName = pathData.networkName; opts.networkName = pathData.networkName;
opts.derivationStrategy = pathData.derivationStrategy; opts.derivationStrategy = pathData.derivationStrategy;
opts.coin = $scope.formData.coin;
var words = $scope.formData.words || null; var words = $scope.formData.words || null;
@ -279,7 +297,7 @@ angular.module('copayApp.controllers').controller('importController',
$log.warn('This wont work for Intel TEE wallets'); $log.warn('This wont work for Intel TEE wallets');
var id = $scope.formData.seedSourceAll.id; var id = $scope.formData.seedSourceAll.id;
var isMultisig = opts.derivationStrategy =='BIP48'; var isMultisig = opts.derivationStrategy == 'BIP48';
var account = opts.account; var account = opts.account;
opts.entropySourcePath = 'm/' + hwWallet.getEntropyPath(id, isMultisig, account); opts.entropySourcePath = 'm/' + hwWallet.getEntropyPath(id, isMultisig, account);
} }

View file

@ -5,11 +5,14 @@ angular.module('copayApp.controllers').controller('joinController',
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
var config = configService.getSync();
$scope.formData = {}; $scope.formData = {};
$scope.formData.bwsurl = defaults.bws.url; $scope.formData.bwsurl = defaults.bws.url;
$scope.formData.derivationPath = derivationPathHelper.default; $scope.formData.derivationPath = derivationPathHelper.default;
$scope.formData.account = 1; $scope.formData.account = 1;
$scope.formData.secret = null; $scope.formData.secret = null;
$scope.formData.coin = data.stateParams.coin;
if (config.cashSupport) $scope.enableCash = true;
resetPasswordFields(); resetPasswordFields();
updateSeedSourceSelect(); updateSeedSourceSelect();
}); });
@ -103,7 +106,8 @@ angular.module('copayApp.controllers').controller('joinController',
var opts = { var opts = {
secret: $scope.formData.secret, secret: $scope.formData.secret,
myName: $scope.formData.myName, myName: $scope.formData.myName,
bwsurl: $scope.formData.bwsurl bwsurl: $scope.formData.bwsurl,
coin: $scope.formData.coin
} }
var setSeed = $scope.formData.seedSource.id == 'set'; var setSeed = $scope.formData.seedSource.id == 'set';
@ -137,6 +141,11 @@ angular.module('copayApp.controllers').controller('joinController',
} }
if ($scope.formData.seedSource.id == walletService.externalSource.ledger.id || $scope.formData.seedSource.id == walletService.externalSource.trezor.id || $scope.formData.seedSource.id == walletService.externalSource.intelTEE.id) { if ($scope.formData.seedSource.id == walletService.externalSource.ledger.id || $scope.formData.seedSource.id == walletService.externalSource.trezor.id || $scope.formData.seedSource.id == walletService.externalSource.intelTEE.id) {
if ($scope.formData.coin == 'bch') {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Hardware wallets are not yet supported with Bitcoin Cash'));
return;
}
var account = $scope.formData.account; var account = $scope.formData.account;
if (!account || account < 1) { if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));

View file

@ -0,0 +1,24 @@
'use strict';
angular.module('copayApp.controllers').controller('mercadoLibreController',
function($scope, $timeout, $log, mercadoLibreService, externalLinkService, popupService) {
$scope.openExternalLink = function(url) {
externalLinkService.open(url);
};
var init = function() {
mercadoLibreService.getPendingGiftCards(function(err, gcds) {
if (err) $log.error(err);
$scope.giftCards = gcds;
$timeout(function() {
$scope.$digest();
});
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.network = mercadoLibreService.getNetwork();
init();
});
});

View file

@ -0,0 +1,100 @@
'use strict';
angular.module('copayApp.controllers').controller('mercadoLibreCardsController',
function($scope, $timeout, $ionicModal, $log, $ionicScrollDelegate, lodash, mercadoLibreService, platformInfo, externalLinkService, popupService, ongoingProcess) {
$scope.openExternalLink = function(url) {
externalLinkService.open(url);
};
var updateGiftCards = function(cb) {
mercadoLibreService.getPendingGiftCards(function(err, gcds) {
if (err) {
popupService.showAlert('Could not get gift cards', err);
if (cb) return cb();
else return;
}
$scope.giftCards = gcds;
$timeout(function() {
$scope.$digest();
$ionicScrollDelegate.resize();
if (cb) return cb();
}, 100);
});
};
$scope.updatePendingGiftCards = lodash.debounce(function() {
updateGiftCards(function() {
var index = 0;
var gcds = $scope.giftCards;
lodash.forEach(gcds, function(dataFromStorage) {
if (dataFromStorage.status == 'PENDING') {
$log.debug("Creating / Updating gift card");
mercadoLibreService.createGiftCard(dataFromStorage, function(err, giftCard) {
if (err) {
popupService.showAlert('Error creating gift card', err);
return;
}
if (giftCard.status != 'PENDING') {
var newData = {};
if (!giftCard.status) dataFromStorage.status = null; // Fix error from server
var cardStatus = giftCard.cardStatus;
if (cardStatus && (cardStatus != 'active' && cardStatus != 'inactive' && cardStatus != 'expired'))
giftCard.status = 'FAILURE';
lodash.merge(newData, dataFromStorage, giftCard);
mercadoLibreService.savePendingGiftCard(newData, null, function(err) {
$log.debug("Saving new gift card");
updateGiftCards();
});
}
});
}
});
});
}, 1000, {
'leading': true
});
$scope.openCardModal = function(card) {
$scope.card = card;
$ionicModal.fromTemplateUrl('views/modals/mercadolibre-card-details.html', {
scope: $scope
}).then(function(modal) {
$scope.mercadoLibreCardDetailsModal = modal;
$scope.mercadoLibreCardDetailsModal.show();
});
$scope.$on('modal.hidden', function() {
$scope.updatePendingGiftCards();
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.invoiceId = data.stateParams.invoiceId;
updateGiftCards(function() {
if ($scope.invoiceId) {
var card = lodash.find($scope.giftCards, {
invoiceId: $scope.invoiceId
});
if (lodash.isEmpty(card)) {
popupService.showAlert(null, 'Card not found');
return;
}
$scope.openCardModal(card);
}
});
});
$scope.$on("$ionicView.afterEnter", function(event, data) {
$scope.updatePendingGiftCards();
});
});

View file

@ -18,14 +18,14 @@ angular.module('copayApp.controllers').controller('feeLevelsController', functio
var value = lodash.find($scope.feeLevels[$scope.network], { var value = lodash.find($scope.feeLevels[$scope.network], {
level: 'superEconomy' level: 'superEconomy'
}); });
return parseInt((value.feePerKB / 1000).toFixed()); return parseInt((value.feePerKb / 1000).toFixed());
}; };
var getMaxRecommended = function() { var getMaxRecommended = function() {
var value = lodash.find($scope.feeLevels[$scope.network], { var value = lodash.find($scope.feeLevels[$scope.network], {
level: 'urgent' level: 'urgent'
}); });
return parseInt((value.feePerKB / 1000).toFixed()); return parseInt((value.feePerKb / 1000).toFixed());
}; };
$scope.ok = function() { $scope.ok = function() {
@ -61,7 +61,7 @@ angular.module('copayApp.controllers').controller('feeLevelsController', functio
// If no custom fee // If no custom fee
if (value) { if (value) {
$scope.customFeePerKB = null; $scope.customFeePerKB = null;
$scope.feePerSatByte = (value.feePerKB / 1000).toFixed(); $scope.feePerSatByte = (value.feePerKb / 1000).toFixed();
$scope.avgConfirmationTime = value.nbBlocks * 10; $scope.avgConfirmationTime = value.nbBlocks * 10;
} else { } else {
$scope.avgConfirmationTime = null; $scope.avgConfirmationTime = null;
@ -102,7 +102,7 @@ angular.module('copayApp.controllers').controller('feeLevelsController', functio
$scope.feeOpts = feeService.feeOpts; $scope.feeOpts = feeService.feeOpts;
$scope.loadingFee = true; $scope.loadingFee = true;
feeService.getFeeLevels(function(err, levels) { feeService.getFeeLevels($scope.coin, function(err, levels) {
$scope.loadingFee = false; $scope.loadingFee = false;
if (err || lodash.isEmpty(levels)) { if (err || lodash.isEmpty(levels)) {
showErrorAndClose(null, err); showErrorAndClose(null, err);

View file

@ -0,0 +1,21 @@
'use strict';
angular.module('copayApp.controllers').controller('mercadoLibreCardDetailsController', function($scope, mercadoLibreService, externalLinkService) {
$scope.remove = function() {
mercadoLibreService.savePendingGiftCard($scope.card, {
remove: true
}, function(err) {
$scope.close();
});
};
$scope.close = function() {
$scope.mercadoLibreCardDetailsModal.hide();
};
$scope.openExternalLink = function(url) {
externalLinkService.open(url);
};
});

View file

@ -23,7 +23,7 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
}; };
function displayFeeValues() { function displayFeeValues() {
txFormatService.formatAlternativeStr($scope.tx.fee, function(v) { txFormatService.formatAlternativeStr($scope.wallet.coin, $scope.tx.fee, function(v) {
$scope.tx.feeFiatStr = v; $scope.tx.feeFiatStr = v;
}); });
$scope.tx.feeRateStr = ($scope.tx.fee / ($scope.tx.amount + $scope.tx.fee) * 100).toFixed(2) + '%'; $scope.tx.feeRateStr = ($scope.tx.fee / ($scope.tx.amount + $scope.tx.fee) * 100).toFixed(2) + '%';
@ -31,17 +31,23 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
}; };
function applyButtonText() { function applyButtonText() {
$scope.buttonText = $scope.isCordova && !$scope.isWindowsPhoneApp ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
var lastSigner = lodash.filter($scope.tx.actions, { var lastSigner = lodash.filter($scope.tx.actions, {
type: 'accept' type: 'accept'
}).length == $scope.tx.requiredSignatures - 1; }).length == $scope.tx.requiredSignatures - 1;
if (lastSigner) { if (lastSigner) {
$scope.buttonText += gettextCatalog.getString('to send'); if ($scope.isCordova && !$scope.isWindowsPhoneApp) {
$scope.buttonText = gettextCatalog.getString('Slide to send');
} else {
$scope.buttonText = gettextCatalog.getString('Click to send');
}
$scope.successText = gettextCatalog.getString('Payment Sent'); $scope.successText = gettextCatalog.getString('Payment Sent');
} else { } else {
$scope.buttonText += gettextCatalog.getString('to accept'); if ($scope.isCordova && !$scope.isWindowsPhoneApp) {
$scope.buttonText = gettextCatalog.getString('Slide to accept');
} else {
$scope.buttonText = gettextCatalog.getString('Click to accept');
}
$scope.successText = gettextCatalog.getString('Payment Accepted'); $scope.successText = gettextCatalog.getString('Payment Accepted');
} }
}; };
@ -219,7 +225,7 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
copayerId: $scope.wallet.credentials.copayerId copayerId: $scope.wallet.credentials.copayerId
}); });
$scope.tx = txFormatService.processTx(tx); $scope.tx = txFormatService.processTx($scope.wallet.coin, tx);
if (!action && tx.status == 'pending') if (!action && tx.status == 'pending')
$scope.tx.pendingForUs = true; $scope.tx.pendingForUs = true;

View file

@ -27,7 +27,7 @@ angular.module('copayApp.controllers').controller('tourController',
rateService.whenAvailable(function() { rateService.whenAvailable(function() {
var localCurrency = 'USD'; var localCurrency = 'USD';
var btcAmount = 1; var btcAmount = 1;
var rate = rateService.toFiat(btcAmount * 1e8, localCurrency); var rate = rateService.toFiat(btcAmount * 1e8, localCurrency, 'btc');
$scope.localCurrencySymbol = '$'; $scope.localCurrencySymbol = '$';
$scope.localCurrencyPerBtc = $filter('formatFiatAmount')(parseFloat(rate.toFixed(2), 10)); $scope.localCurrencyPerBtc = $filter('formatFiatAmount')(parseFloat(rate.toFixed(2), 10));
$timeout(function() { $timeout(function() {

View file

@ -45,8 +45,7 @@ angular.module('copayApp.controllers').controller('paperWalletController',
$scope.balanceSat = balance; $scope.balanceSat = balance;
if ($scope.balanceSat <= 0) if ($scope.balanceSat <= 0)
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not funds found')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not funds found'));
var config = configService.getSync().wallet.settings; $scope.balance = txFormatService.formatAmountStr($scope.wallet.coin, balance);
$scope.balance = txFormatService.formatAmount(balance) + ' ' + config.unitName;
} }
$scope.$apply(); $scope.$apply();
}); });
@ -60,9 +59,9 @@ angular.module('copayApp.controllers').controller('paperWalletController',
$scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, null, function(err, testTx) { $scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, null, function(err, testTx) {
if (err) return cb(err); if (err) return cb(err);
var rawTxLength = testTx.serialize().length; var rawTxLength = testTx.serialize().length;
feeService.getCurrentFeeRate('livenet', function(err, feePerKB) { feeService.getCurrentFeeRate('btc', 'livenet', function(err, feePerKb) {
var opts = {}; var opts = {};
opts.fee = Math.round((feePerKB * rawTxLength) / 2000); opts.fee = Math.round((feePerKb * rawTxLength) / 2000);
$scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, opts, function(err, tx) { $scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, opts, function(err, tx) {
if (err) return cb(err); if (err) return cb(err);
$scope.wallet.broadcastRawTx({ $scope.wallet.broadcastRawTx({

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesController', angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $timeout, $log, $ionicHistory, configService, profileService, fingerprintService, walletService, platformInfo) { function($scope, $rootScope, $timeout, $log, $ionicHistory, configService, profileService, fingerprintService, walletService, platformInfo, externalLinkService, gettextCatalog) {
var wallet; var wallet;
var walletId; var walletId;
@ -58,6 +58,16 @@ angular.module('copayApp.controllers').controller('preferencesController',
} }
}; };
$scope.openWikiSpendingPassword = function() {
var url = 'https://github.com/bitpay/copay/wiki/COPAY---FAQ#what-the-spending-password-does';
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Read more in our Wiki');
var okText = gettextCatalog.getString('Open');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
$scope.touchIdChange = function() { $scope.touchIdChange = function() {
var newStatus = $scope.touchIdEnabled.value; var newStatus = $scope.touchIdEnabled.value;
walletService.setTouchId(wallet, !!newStatus, function(err) { walletService.setTouchId(wallet, !!newStatus, function(err) {

View file

@ -43,8 +43,9 @@ angular.module('copayApp.controllers').controller('preferencesAltCurrencyControl
$scope.findCurrency = function(search) { $scope.findCurrency = function(search) {
if (!search) init(); if (!search) init();
$scope.altCurrencyList = lodash.filter(completeAlternativeList, function(item) { $scope.altCurrencyList = lodash.filter(completeAlternativeList, function(item) {
var val = item.name; var val = item.name
return lodash.includes(val.toLowerCase(), search.toLowerCase()); var val2 = item.isoCode;
return lodash.includes(val.toLowerCase(), search.toLowerCase()) || lodash.includes(val2.toLowerCase(), search.toLowerCase());
}); });
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();

View file

@ -0,0 +1,42 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesCashController', function($scope, $log, $timeout, appConfigService, configService, gettextCatalog, externalLinkService) {
var updateConfig = function() {
var config = configService.getSync();
$scope.appName = appConfigService.nameCase;
$scope.cashSupport = {
value: config.cashSupport
};
$timeout(function() {
$scope.$apply();
});
};
$scope.cashSupportChange = function() {
var opts = {
cashSupport: $scope.cashSupport.value
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
$scope.openBitcoinCashWeb = function() {
var url = 'https://www.bitcoincash.org/';
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Open bitcoincash.org?');
var okText = gettextCatalog.getString('Open');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
updateConfig();
});
});

View file

@ -2,7 +2,7 @@
angular.module('copayApp.controllers').controller('preferencesDeleteWalletController', angular.module('copayApp.controllers').controller('preferencesDeleteWalletController',
function($scope, $ionicHistory, gettextCatalog, lodash, profileService, $state, ongoingProcess, popupService, pushNotificationsService) { function($scope, $ionicHistory, gettextCatalog, lodash, profileService, $state, ongoingProcess, popupService, pushNotificationsService) {
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
if (!data.stateParams || !data.stateParams.walletId) { if (!data.stateParams || !data.stateParams.walletId) {
popupService.showAlert(null, gettextCatalog.getString('No wallet selected'), function() { popupService.showAlert(null, gettextCatalog.getString('No wallet selected'), function() {
@ -17,8 +17,7 @@ angular.module('copayApp.controllers').controller('preferencesDeleteWalletContro
}); });
return; return;
} }
$scope.alias = lodash.isEqual($scope.wallet.name, $scope.wallet.credentials.walletName) ? null : $scope.wallet.name + ' '; $scope.walletName = $scope.wallet.name;
$scope.walletName = $scope.wallet.credentials.walletName;
}); });
$scope.showDeletePopup = function() { $scope.showDeletePopup = function() {

View file

@ -32,11 +32,12 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
}); });
$scope.init = function() { $scope.init = function() {
var coin = 'btc'; // TODO: only BTC in preferences
$scope.network = $scope.network || 'livenet'; $scope.network = $scope.network || 'livenet';
$scope.feeOpts = feeService.feeOpts; $scope.feeOpts = feeService.feeOpts;
$scope.currentFeeLevel = $scope.feeLevel || feeService.getCurrentFeeLevel(); $scope.currentFeeLevel = $scope.feeLevel || feeService.getCurrentFeeLevel();
$scope.loadingFee = true; $scope.loadingFee = true;
feeService.getFeeLevels(function(err, levels) { feeService.getFeeLevels(coin, function(err, levels) {
$scope.loadingFee = false; $scope.loadingFee = false;
if (err) { if (err) {
//Error is already formatted //Error is already formatted
@ -66,7 +67,7 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
return; return;
} }
$scope.feePerSatByte = (value.feePerKB / 1000).toFixed(); $scope.feePerSatByte = (value.feePerKb / 1000).toFixed();
$scope.avgConfirmationTime = value.nbBlocks * 10; $scope.avgConfirmationTime = value.nbBlocks * 10;
$scope.invalidCustomFeeEntered = false; $scope.invalidCustomFeeEntered = false;
setMinWarning(); setMinWarning();
@ -97,7 +98,7 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
var value = lodash.find($scope.feeLevels[$scope.network], { var value = lodash.find($scope.feeLevels[$scope.network], {
level: 'superEconomy' level: 'superEconomy'
}); });
return parseInt((value.feePerKB / 1000).toFixed()); return parseInt((value.feePerKb / 1000).toFixed());
}; };
var setMinWarning = function() { var setMinWarning = function() {

View file

@ -1,43 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesUnitController', function($scope, $log, configService, $ionicHistory, gettextCatalog, walletService, profileService) {
var config = configService.getSync();
$scope.unitList = [{
name: 'bits (1,000,000 bits = 1BTC)',
shortName: 'bits',
value: 100,
decimals: 2,
code: 'bit',
}, {
name: 'BTC',
shortName: 'BTC',
value: 100000000,
decimals: 8,
code: 'btc',
}];
$scope.save = function(newUnit) {
var opts = {
wallet: {
settings: {
unitName: newUnit.shortName,
unitToSatoshi: newUnit.value,
unitDecimals: newUnit.decimals,
unitCode: newUnit.code,
}
}
};
configService.set(opts, function(err) {
if (err) $log.warn(err);
$ionicHistory.goBack();
walletService.updateRemotePreferences(profileService.getWallets())
});
};
$scope.$on("$ionicView.enter", function(event, data){
$scope.currentUnit = config.wallet.settings.unitCode;
});
});

View file

@ -2,6 +2,7 @@
angular.module('copayApp.controllers').controller('sellCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, $ionicConfig, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, appConfigService, configService, txFormatService) { angular.module('copayApp.controllers').controller('sellCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, $ionicConfig, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, appConfigService, configService, txFormatService) {
var coin = 'btc';
var amount; var amount;
var currency; var currency;
@ -34,124 +35,7 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
}, onSendStatusChange); }, onSendStatusChange);
}; };
var checkTransaction = lodash.throttle(function(count, txp) { var processPaymentInfo = function() {
$log.warn('Check if transaction has been received by Coinbase. Try ' + count + '/5');
// TX amount in BTC
var satToBtc = 1 / 100000000;
var amountBTC = (txp.amount * satToBtc).toFixed(8);
coinbaseService.init(function(err, res) {
if (err) {
$log.error(err);
checkTransaction(count, txp);
return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var sellPrice = null;
coinbaseService.sellPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, sell) {
if (err) {
$log.debug(err);
checkTransaction(count, txp);
return;
}
sellPrice = sell.data;
coinbaseService.getTransactions(accessToken, accountId, function(err, ctxs) {
if (err) {
$log.debug(err);
checkTransaction(count, txp);
return;
}
var coinbaseTransactions = ctxs.data;
var txFound = false;
var ctx;
for(var i = 0; i < coinbaseTransactions.length; i++) {
ctx = coinbaseTransactions[i];
if (ctx.type == 'send' && ctx.from && ctx.amount.amount == amountBTC ) {
$log.warn('Transaction found!', ctx);
txFound = true;
$log.debug('Saving transaction to process later...');
ctx['payment_method'] = $scope.selectedPaymentMethodId.value;
ctx['status'] = 'pending'; // Forcing "pending" status to process later
ctx['price_sensitivity'] = $scope.selectedPriceSensitivity.data;
ctx['sell_price_amount'] = sellPrice ? sellPrice.amount : '';
ctx['sell_price_currency'] = sellPrice ? sellPrice.currency : 'USD';
ctx['description'] = appConfigService.nameCase + ' Wallet: ' + $scope.wallet.name;
coinbaseService.savePendingTransaction(ctx, null, function(err) {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
if (err) $log.debug(err);
});
return;
}
}
if (!txFound) {
// Transaction sent, but could not be verified by Coinbase.com
$log.warn('Transaction not found in Coinbase.');
if (count < 5) {
checkTransaction(count + 1, txp);
} else {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError('No transaction found');
return;
}
}
});
});
});
}, 8000, {
'leading': true
});
var statusChangeHandler = function (processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( processName == 'sellingBitcoin' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
$scope.sendStatus = showName;
}
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$scope.priceSensitivity = coinbaseService.priceSensitivity;
$scope.selectedPriceSensitivity = { data: coinbaseService.selectedPriceSensitivity };
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network,
hasFunds: true,
minAmount: parsedAmount.amountSat
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('Insufficient funds');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true); ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) { coinbaseService.init(function(err, res) {
if (err) { if (err) {
@ -193,7 +77,121 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id; if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.sellRequest(); $scope.sellRequest();
}); });
}); });
};
var checkTransaction = lodash.throttle(function(count, txp) {
$log.warn('Check if transaction has been received by Coinbase. Try ' + count + '/5');
// TX amount in BTC
var satToBtc = 1 / 100000000;
var amountBTC = (txp.amount * satToBtc).toFixed(8);
coinbaseService.init(function(err, res) {
if (err) {
$log.error(err);
checkTransaction(count, txp);
return;
}
var accessToken = res.accessToken;
var accountId = res.accountId;
var sellPrice = null;
coinbaseService.sellPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, sell) {
if (err) {
$log.debug(err);
checkTransaction(count, txp);
return;
}
sellPrice = sell.data;
coinbaseService.getTransactions(accessToken, accountId, function(err, ctxs) {
if (err) {
$log.debug(err);
checkTransaction(count, txp);
return;
}
var coinbaseTransactions = ctxs.data;
var txFound = false;
var ctx;
for(var i = 0; i < coinbaseTransactions.length; i++) {
ctx = coinbaseTransactions[i];
if (ctx.type == 'send' && ctx.from && ctx.amount.amount == amountBTC ) {
$log.warn('Transaction found!', ctx);
txFound = true;
$log.debug('Saving transaction to process later...');
ctx['payment_method'] = $scope.selectedPaymentMethodId.value;
ctx['status'] = 'pending'; // Forcing "pending" status to process later
ctx['price_sensitivity'] = $scope.selectedPriceSensitivity.data;
ctx['sell_price_amount'] = sellPrice ? sellPrice.amount : '';
ctx['sell_price_currency'] = sellPrice ? sellPrice.currency : 'USD';
ctx['description'] = appConfigService.nameCase + ' Wallet: ' + $scope.wallet.name;
coinbaseService.savePendingTransaction(ctx, null, function(err) {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
if (err) $log.debug(err);
});
return;
}
}
if (!txFound) {
// Transaction sent, but could not be verified by Coinbase.com
$log.warn('Transaction not found in Coinbase.');
if (count < 5) {
checkTransaction(count + 1, txp);
} else {
ongoingProcess.set('sellingBitcoin', false, statusChangeHandler);
showError('No transaction found');
return;
}
}
});
});
});
}, 8000, {
'leading': true
});
var statusChangeHandler = function (processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( processName == 'sellingBitcoin' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
}, 100);
} else if (showName) {
$scope.sendStatus = showName;
}
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'BTC' ? true : false;
amount = data.stateParams.amount;
currency = data.stateParams.currency;
$scope.priceSensitivity = coinbaseService.priceSensitivity;
$scope.selectedPriceSensitivity = { data: coinbaseService.selectedPriceSensitivity };
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network,
hasFunds: true,
coin: coin
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('Insufficient funds');
return;
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
}); });
$scope.sellRequest = function() { $scope.sellRequest = function() {
@ -236,7 +234,7 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
var cancelText = 'Cancel'; var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) { popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return; if (!ok) return;
ongoingProcess.set('sellingBitcoin', true, statusChangeHandler); ongoingProcess.set('sellingBitcoin', true, statusChangeHandler);
coinbaseService.init(function(err, res) { coinbaseService.init(function(err, res) {
if (err) { if (err) {
@ -294,8 +292,8 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
checkTransaction(1, txSent); checkTransaction(1, txSent);
}); });
}); });
}); });
}); });
}); });
}; };
@ -306,6 +304,15 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet; $scope.wallet = wallet;
var parsedAmount = txFormatService.parseAmount(
coin,
amount,
currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
processPaymentInfo();
}; };
$scope.goBackHome = function() { $scope.goBackHome = function() {

View file

@ -2,6 +2,7 @@
angular.module('copayApp.controllers').controller('sellGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, txFormatService) { angular.module('copayApp.controllers').controller('sellGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, txFormatService) {
var coin = 'btc';
var amount; var amount;
var currency; var currency;
@ -35,39 +36,7 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
} }
}; };
$scope.$on("$ionicView.beforeLeave", function(event, data) { var processPaymentInfo = function() {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$scope.network = glideraService.getNetwork();
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network,
hasFunds: true,
minAmount: parsedAmount.amountSat
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('Insufficient funds');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingGlidera', true); ongoingProcess.set('connectingGlidera', true);
glideraService.init(function(err, data) { glideraService.init(function(err, data) {
if (err) { if (err) {
@ -91,6 +60,35 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
$scope.sellInfo = sell; $scope.sellInfo = sell;
}); });
}); });
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'BTC' ? true : false;
amount = data.stateParams.amount;
currency = data.stateParams.currency;
$scope.network = glideraService.getNetwork();
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network,
hasFunds: true,
coin: coin
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('Insufficient funds');
return;
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
}); });
var ask2FaCode = function(mode, cb) { var ask2FaCode = function(mode, cb) {
@ -108,7 +106,7 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
popupService.showPrompt(title, message, null, function(twoFaCode) { popupService.showPrompt(title, message, null, function(twoFaCode) {
if (typeof twoFaCode == 'undefined') return cb(); if (typeof twoFaCode == 'undefined') return cb();
return cb(twoFaCode); return cb(twoFaCode);
}); });
} else { } else {
return cb(); return cb();
} }
@ -119,7 +117,7 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
var okText = 'Confirm'; var okText = 'Confirm';
var cancelText = 'Cancel'; var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) { popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return; if (!ok) return;
ongoingProcess.set('sellingBitcoin', true, statusChangeHandler); ongoingProcess.set('sellingBitcoin', true, statusChangeHandler);
glideraService.get2faCode($scope.token, function(err, tfa) { glideraService.get2faCode($scope.token, function(err, tfa) {
if (err) { if (err) {
@ -231,6 +229,15 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet; $scope.wallet = wallet;
var parsedAmount = txFormatService.parseAmount(
coin,
amount,
currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
processPaymentInfo();
}; };
$scope.goBackHome = function() { $scope.goBackHome = function() {

View file

@ -206,14 +206,24 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}; };
var updateAllWallets = function() { var updateAllWallets = function() {
$scope.wallets = profileService.getWallets(); var wallets = [];
if (lodash.isEmpty($scope.wallets)) return; $scope.walletsBtc = profileService.getWallets({coin: 'btc'});
$scope.walletsBch = profileService.getWallets({coin: 'bch'});
var i = $scope.wallets.length; lodash.each($scope.walletsBtc, function(wBtc) {
wallets.push(wBtc);
});
lodash.each($scope.walletsBch, function(wBch) {
wallets.push(wBch);
});
if (lodash.isEmpty(wallets)) return;
var i = wallets.length;
var j = 0; var j = 0;
var timeSpan = 60 * 60 * 24 * 7;
lodash.each($scope.wallets, function(wallet) { lodash.each(wallets, function(wallet) {
walletService.getStatus(wallet, {}, function(err, status) { walletService.getStatus(wallet, {}, function(err, status) {
if (err) { if (err) {

View file

@ -8,7 +8,8 @@ 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 id: $scope.wallet.credentials.walletId,
coin: $scope.wallet.coin
}); });
}; };
@ -123,8 +124,13 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
return wallet; return wallet;
} }
var setProtocolHandler = function() {
$scope.protocolHandler = walletService.getProtocolHandler($scope.wallet);
}
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet; $scope.wallet = wallet;
setProtocolHandler();
$scope.setAddress(); $scope.setAddress();
}; };
@ -136,6 +142,8 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
$scope.shareAddress = function() { $scope.shareAddress = function() {
if (!$scope.isCordova) return; if (!$scope.isCordova) return;
window.plugins.socialsharing.share('bitcoin:' + $scope.addr, null, null, null); var protocol = 'bitcoin';
if ($scope.wallet.coin == 'bch') protocol += 'cash';
window.plugins.socialsharing.share(protocol + ':' + $scope.addr, null, null, null);
} }
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService) { angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService, bitcoreCash) {
var originalList; var originalList;
var CONTACTS_SHOW_LIMIT; var CONTACTS_SHOW_LIMIT;
@ -76,6 +76,8 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
color: v.color, color: v.color,
name: v.name, name: v.name,
recipientType: 'wallet', recipientType: 'wallet',
coin: v.coin,
network: v.network,
getAddress: function(cb) { getAddress: function(cb) {
walletService.getAddress(v, false, cb); walletService.getAddress(v, false, cb);
}, },
@ -85,6 +87,14 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
} }
} }
var getCoin = function(address) {
var cashAddress = bitcoreCash.Address.isValid(address, 'livenet');
if (cashAddress) {
return 'bch';
}
return 'btc';
};
var updateContactsList = function(cb) { var updateContactsList = function(cb) {
addressbookService.list(function(err, ab) { addressbookService.list(function(err, ab) {
if (err) $log.error(err); if (err) $log.error(err);
@ -99,6 +109,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
address: k, address: k,
email: lodash.isObject(v) ? v.email : null, email: lodash.isObject(v) ? v.email : null,
recipientType: 'contact', recipientType: 'contact',
coin: getCoin(k),
getAddress: function(cb) { getAddress: function(cb) {
return cb(null, k); return cb(null, k);
}, },
@ -186,7 +197,8 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
toAddress: addr, toAddress: addr,
toName: item.name, toName: item.name,
toEmail: item.email, toEmail: item.email,
toColor: item.color toColor: item.color,
coin: item.coin
}) })
}); });
}); });

View file

@ -6,11 +6,11 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.currentLanguageName = uxLanguage.getCurrentLanguageName(); $scope.currentLanguageName = uxLanguage.getCurrentLanguageName();
$scope.feeOpts = feeService.feeOpts; $scope.feeOpts = feeService.feeOpts;
$scope.currentFeeLevel = feeService.getCurrentFeeLevel(); $scope.currentFeeLevel = feeService.getCurrentFeeLevel();
$scope.wallets = profileService.getWallets(); $scope.walletsBtc = profileService.getWallets({ coin: 'btc' });
$scope.walletsBch = profileService.getWallets({ coin: 'bch' });
$scope.buyAndSellServices = buyAndSellService.getLinked(); $scope.buyAndSellServices = buyAndSellService.getLinked();
configService.whenAvailable(function(config) { configService.whenAvailable(function(config) {
$scope.unitName = config.wallet.settings.unitName;
$scope.selectedAlternative = { $scope.selectedAlternative = {
name: config.wallet.settings.alternativeName, name: config.wallet.settings.alternativeName,
isoCode: config.wallet.settings.alternativeIsoCode isoCode: config.wallet.settings.alternativeIsoCode
@ -26,6 +26,11 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
}, 10); }, 10);
}); });
$scope.cashSupport = {
value: config.cashSupport
};
// TODO move this to a generic service // TODO move this to a generic service
bitpayCardService.getCards(function(err, cards) { bitpayCardService.getCards(function(err, cards) {
if (err) $log.error(err); if (err) $log.error(err);
@ -63,6 +68,8 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
}); });
}); });
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
updateConfig(); updateConfig();
}); });

View file

@ -3,6 +3,7 @@
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService, gettextCatalog) { angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService, gettextCatalog) {
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
var coin = 'btc';
var cardId; var cardId;
var useSendMax; var useSendMax;
var amount; var amount;
@ -36,7 +37,7 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
}; };
var satToFiat = function(sat, cb) { var satToFiat = function(sat, cb) {
txFormatService.toFiat(sat, $scope.currencyIsoCode, function(value) { txFormatService.toFiat(coin, sat, $scope.currencyIsoCode, function(value) {
return cb(value); return cb(value);
}); });
}; };
@ -218,7 +219,7 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
// Save TX in memory // Save TX in memory
createdTx = ctxp; createdTx = ctxp;
$scope.totalAmountStr = txFormatService.formatAmountStr(ctxp.amount); $scope.totalAmountStr = txFormatService.formatAmountStr(coin, ctxp.amount);
setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee); setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee);
@ -256,7 +257,8 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
$scope.wallets = profileService.getWallets({ $scope.wallets = profileService.getWallets({
onlyComplete: true, onlyComplete: true,
network: bitpayService.getEnvironment().network, network: bitpayService.getEnvironment().network,
hasFunds: true hasFunds: true,
coin: coin
}); });
if (lodash.isEmpty($scope.wallets)) { if (lodash.isEmpty($scope.wallets)) {
@ -319,7 +321,7 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
}); });
return; return;
} }
var parsedAmount = txFormatService.parseAmount(a, c); var parsedAmount = txFormatService.parseAmount(coin, a, c);
initializeTopUp(wallet, parsedAmount); initializeTopUp(wallet, parsedAmount);
}); });
}; };

View file

@ -5,6 +5,7 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
var txId; var txId;
var listeners = []; var listeners = [];
var config = configService.getSync(); var config = configService.getSync();
var blockexplorerUrl;
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
txId = data.stateParams.txid; txId = data.stateParams.txid;
@ -15,6 +16,12 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.isShared = $scope.wallet.credentials.n > 1; $scope.isShared = $scope.wallet.credentials.n > 1;
$scope.txsUnsubscribedForNotifications = config.confirmedTxsNotifications ? !config.confirmedTxsNotifications.enabled : true; $scope.txsUnsubscribedForNotifications = config.confirmedTxsNotifications ? !config.confirmedTxsNotifications.enabled : true;
if ($scope.wallet.coin == 'bch') {
blockexplorerUrl = 'bch-insight.bitpay.com';
} else {
blockexplorerUrl = 'insight.bitpay.com';
}
txConfirmNotification.checkIfEnabled(txId, function(res) { txConfirmNotification.checkIfEnabled(txId, function(res) {
$scope.txNotification = { $scope.txNotification = {
value: res value: res
@ -40,6 +47,16 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
}); });
}); });
$scope.readMore = function() {
var url = 'https://github.com/bitpay/copay/wiki/COPAY---FAQ#amount-too-low-to-spend';
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Read more in our Wiki');
var okText = gettextCatalog.getString('Open');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
function updateMemo() { function updateMemo() {
walletService.getTxNote($scope.wallet, $scope.btx.txid, function(err, note) { walletService.getTxNote($scope.wallet, $scope.btx.txid, function(err, note) {
if (err) { if (err) {
@ -102,8 +119,8 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Transaction not available at this time')); return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Transaction not available at this time'));
} }
$scope.btx = txFormatService.processTx(tx); $scope.btx = txFormatService.processTx($scope.wallet.coin, tx);
txFormatService.formatAlternativeStr(tx.fees, function(v) { txFormatService.formatAlternativeStr($scope.wallet.coin, tx.fees, function(v) {
$scope.btx.feeFiatStr = v; $scope.btx.feeFiatStr = v;
$scope.btx.feeRateStr = ($scope.btx.fees / ($scope.btx.amount + $scope.btx.fees) * 100).toFixed(2) + '%'; $scope.btx.feeRateStr = ($scope.btx.fees / ($scope.btx.amount + $scope.btx.fees) * 100).toFixed(2) + '%';
}); });
@ -121,7 +138,7 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.$digest(); $scope.$digest();
}); });
feeService.getFeeLevels(function(err, levels) { feeService.getFeeLevels($scope.wallet.coin, function(err, levels) {
if (err) return; if (err) return;
walletService.getLowAmount($scope.wallet, levels, function(err, amount) { walletService.getLowAmount($scope.wallet, levels, function(err, amount) {
if (err) return; if (err) return;
@ -168,10 +185,7 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.viewOnBlockchain = function() { $scope.viewOnBlockchain = function() {
var btx = $scope.btx; var btx = $scope.btx;
var url = 'https://blockchain.info/tx/' + btx.txid; var url = 'https://' + ($scope.getShortNetworkName() == 'test' ? 'test-' : '') + blockexplorerUrl + '/tx/' + btx.txid;
if ($scope.getShortNetworkName() == 'test') {
url = "https://test-insight.bitpay.com/tx/" + btx.txid;
}
var optIn = true; var optIn = true;
var title = null; var title = null;
var message = gettextCatalog.getString('View Transaction on Insight'); var message = gettextCatalog.getString('View Transaction on Insight');

View file

@ -52,7 +52,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
var analyzeUtxos = function() { var analyzeUtxos = function() {
if (analyzeUtxosDone) return; if (analyzeUtxosDone) return;
feeService.getFeeLevels(function(err, levels) { feeService.getFeeLevels($scope.wallet.coin, function(err, levels) {
if (err) return; if (err) return;
walletService.getLowUtxos($scope.wallet, levels, function(err, resp) { walletService.getLowUtxos($scope.wallet, levels, function(err, resp) {
if (err || !resp) return; if (err || !resp) return;
@ -156,9 +156,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
var updateTxHistory = function(cb) { var updateTxHistory = function(cb) {
if (!cb) cb = function() {}; if (!cb) cb = function() {};
if ($scope.updatingTxHistory) return;
$scope.updatingTxHistory = true;
$scope.updateTxHistoryError = false; $scope.updateTxHistoryError = false;
$scope.updatingTxHistoryProgress = 0; $scope.updatingTxHistoryProgress = 0;
@ -171,7 +169,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
}); });
}; };
feeService.getFeeLevels(function(err, levels) { feeService.getFeeLevels($scope.wallet.coin, function(err, levels) {
walletService.getTxHistory($scope.wallet, { walletService.getTxHistory($scope.wallet, {
progressFn: progressFn, progressFn: progressFn,
feeLevels: levels, feeLevels: levels,
@ -184,7 +182,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
} }
$scope.completeTxHistory = txHistory; $scope.completeTxHistory = txHistory;
$scope.showHistory(); $scope.showHistory();
$scope.$apply(); $timeout(function() {
$scope.$apply();
});
return cb(); return cb();
}); });
}); });
@ -356,6 +356,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
if (!$scope.wallet) return; if (!$scope.wallet) return;
$scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1; $scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1;
$scope.updatingTxHistory = true;
addressbookService.list(function(err, ab) { addressbookService.list(function(err, ab) {
if (err) $log.error(err); if (err) $log.error(err);
$scope.addressbook = ab || {}; $scope.addressbook = ab || {};

View file

@ -1,12 +1,18 @@
'use strict'; 'use strict';
angular.module('copayApp.directives') angular.module('copayApp.directives')
.directive('validAddress', ['$rootScope', 'bitcore', .directive('validAddress', ['$rootScope', 'bitcore', 'bitcoreCash',
function($rootScope, bitcore) { function($rootScope, bitcore, bitcoreCash) {
return { return {
require: 'ngModel', require: 'ngModel',
link: function(scope, elem, attrs, ctrl) { link: function(scope, elem, attrs, ctrl) {
// Bitcoin address
var URI = bitcore.URI; var URI = bitcore.URI;
var Address = bitcore.Address var Address = bitcore.Address
// Bitcoin Cash address
var URICash = bitcoreCash.URI;
var AddressCash = bitcoreCash.Address
var validator = function(value) { var validator = function(value) {
// Regular url // Regular url
@ -16,8 +22,8 @@ angular.module('copayApp.directives')
} }
// Bip21 uri // Bip21 uri
var uri, isAddressValidLivenet, isAddressValidTestnet;
if (/^bitcoin:/.test(value)) { if (/^bitcoin:/.test(value)) {
var uri, isAddressValidLivenet, isAddressValidTestnet;
var isUriValid = URI.isValid(value); var isUriValid = URI.isValid(value);
if (isUriValid) { if (isUriValid) {
uri = new URI(value); uri = new URI(value);
@ -26,6 +32,14 @@ angular.module('copayApp.directives')
} }
ctrl.$setValidity('validAddress', isUriValid && (isAddressValidLivenet || isAddressValidTestnet)); ctrl.$setValidity('validAddress', isUriValid && (isAddressValidLivenet || isAddressValidTestnet));
return value; return value;
} else if (/^bitcoincash:/.test(value)) {
var isUriValid = URICash.isValid(value);
if (isUriValid) {
uri = new URICash(value);
isAddressValidLivenet = AddressCash.isValid(uri.address.toString(), 'livenet')
}
ctrl.$setValidity('validAddress', isUriValid && (isAddressValidLivenet));
return value;
} }
if (typeof value == 'undefined') { if (typeof value == 'undefined') {
@ -33,10 +47,11 @@ angular.module('copayApp.directives')
return; return;
} }
// Regular Address // Regular Address: try Bitcoin and Bitcoin Cash
var regularAddressLivenet = Address.isValid(value, 'livenet'); var regularAddressLivenet = Address.isValid(value, 'livenet');
var regularAddressTestnet = Address.isValid(value, 'testnet'); var regularAddressTestnet = Address.isValid(value, 'testnet');
ctrl.$setValidity('validAddress', (regularAddressLivenet || regularAddressTestnet)); var regularAddressCashLivenet = AddressCash.isValid(value, 'livenet');
ctrl.$setValidity('validAddress', (regularAddressLivenet || regularAddressTestnet || regularAddressCashLivenet));
return value; return value;
}; };

View file

@ -27,12 +27,10 @@ angular.module('copayApp.filters', [])
} }
}) })
.filter('formatFiatAmount', ['$filter', '$locale', 'configService', .filter('formatFiatAmount', ['$filter', '$locale', 'configService',
function(filter, locale, configService) { function(filter, locale) {
var numberFilter = filter('number'); var numberFilter = filter('number');
var formats = locale.NUMBER_FORMATS; var formats = locale.NUMBER_FORMATS;
var config = configService.getSync().wallet.settings;
return function(amount) { return function(amount) {
if (!config) return amount;
var fractionSize = 2; var fractionSize = 2;
var value = numberFilter(amount, fractionSize); var value = numberFilter(amount, fractionSize);

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', url: '/amount/:recipientType/:toAddress/:toName/:toEmail/:toColor/:coin/:fixedUnit',
views: { views: {
'tab-send@tabs': { 'tab-send@tabs': {
controller: 'amountController', controller: 'amountController',
@ -296,7 +296,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.send.confirm', { .state('tabs.send.confirm', {
url: '/confirm/:recipientType/:toAddress/:toName/:toAmount/:toEmail/:toColor/:description/:useSendMax', url: '/confirm/:recipientType/:toAddress/:toName/:toAmount/:toEmail/:toColor/:description/:coin/:useSendMax',
views: { views: {
'tab-send@tabs': { 'tab-send@tabs': {
controller: 'confirmController', controller: 'confirmController',
@ -329,6 +329,9 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
'tab-home@tabs': { 'tab-home@tabs': {
templateUrl: 'views/add.html' templateUrl: 'views/add.html'
} }
},
params: {
coin: 'btc'
} }
}) })
.state('tabs.add.join', { .state('tabs.add.join', {
@ -374,6 +377,16 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
* *
*/ */
.state('tabs.preferencesCash', {
url: '/preferencesCash',
views: {
'tab-settings@tabs': {
controller: 'preferencesCashController',
templateUrl: 'views/preferencesCash.html'
}
}
})
.state('tabs.notifications', { .state('tabs.notifications', {
url: '/notifications', url: '/notifications',
views: { views: {
@ -392,15 +405,6 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
} }
}) })
.state('tabs.unit', {
url: '/unit',
views: {
'tab-settings@tabs': {
controller: 'preferencesUnitController',
templateUrl: 'views/preferencesUnit.html'
}
}
})
.state('tabs.fee', { .state('tabs.fee', {
url: '/fee', url: '/fee',
views: { views: {
@ -474,6 +478,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
/* /*
* *
* Wallet preferences * Wallet preferences
@ -589,6 +594,16 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.preferencesCash.scan', {
url: '/cashScan',
views: {
'tab-settings@tabs': {
controller: 'cashScanController',
templateUrl: 'views/cashScan.html'
}
}
})
/* /*
* *
* Addressbook * Addressbook
@ -676,12 +691,12 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
abstract: true, abstract: true,
params: { params: {
id: null, id: null,
nextStep: 'tabs.paymentRequest.confirm' nextStep: 'tabs.paymentRequest.confirm',
} }
}) })
.state('tabs.paymentRequest.amount', { .state('tabs.paymentRequest.amount', {
url: '/amount', url: '/amount/:coin',
views: { views: {
'tab-receive@tabs': { 'tab-receive@tabs': {
controller: 'amountController', controller: 'amountController',
@ -690,7 +705,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.paymentRequest.confirm', { .state('tabs.paymentRequest.confirm', {
url: '/confirm/:amount/:currency', url: '/confirm/:amount/:currency/:coin',
views: { views: {
'tab-receive@tabs': { 'tab-receive@tabs': {
controller: 'customAmountController', controller: 'customAmountController',
@ -923,6 +938,9 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
controllerAs: 'glidera', controllerAs: 'glidera',
templateUrl: 'views/glidera.html' templateUrl: 'views/glidera.html'
} }
},
params: {
coin: 'btc',
} }
}) })
.state('tabs.buyandsell.glidera.amount', { .state('tabs.buyandsell.glidera.amount', {
@ -976,6 +994,9 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
controllerAs: 'coinbase', controllerAs: 'coinbase',
templateUrl: 'views/coinbase.html' templateUrl: 'views/coinbase.html'
} }
},
params: {
coin: 'btc',
} }
}) })
.state('tabs.preferences.coinbase', { .state('tabs.preferences.coinbase', {
@ -1026,36 +1047,54 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
abstract: true abstract: true
}) })
/*
*
* Mercado Libre Gift Card
*
*/
/* Explore Bitcoin.com */ .state('tabs.giftcards.mercadoLibre', {
.state('tabs.bitcoin-com', { url: '/mercadoLibre',
url: '/bitcoincom',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'bitcoincomController', controller: 'mercadoLibreController',
templateUrl: 'views/bitcoincom.html' templateUrl: 'views/mercadoLibre.html'
} }
} }
}) })
.state('tabs.giftcards.mercadoLibre.cards', {
/* buy.Bitcoin.com */ url: '/cards',
.state('tabs.buyandsell.bitcoindotcom', {
url: '/buyBitcoindotcom',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'buyBitcoindotcomController', controller: 'mercadoLibreCardsController',
templateUrl: 'views/buyBitcoindotcom.html' templateUrl: 'views/mercadoLibreCards.html'
} }
},
params: {
invoiceId: null
} }
}) })
.state('tabs.giftcards.mercadoLibre.amount', {
/* Price Chart */ url: '/amount',
.state('tabs.pricechart', {
url: '/pricechart',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'pricechartController', controller: 'amountController',
templateUrl: 'views/pricechart.html' templateUrl: 'views/amount.html'
}
},
params: {
nextStep: 'tabs.giftcards.mercadoLibre.buy',
currency: 'BRL',
coin: 'btc',
fixedUnit: 1,
}
})
.state('tabs.giftcards.mercadoLibre.buy', {
url: '/buy/:amount/:currency',
views: {
'tab-home@tabs': {
controller: 'buyMercadoLibreController',
templateUrl: 'views/buyMercadoLibre.html'
} }
} }
}) })
@ -1098,7 +1137,8 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
params: { params: {
nextStep: 'tabs.giftcards.amazon.buy', nextStep: 'tabs.giftcards.amazon.buy',
currency: 'USD', currency: 'USD',
forceCurrency: true coin: 'btc',
fixedUnit: true,
} }
}) })
.state('tabs.giftcards.amazon.buy', { .state('tabs.giftcards.amazon.buy', {
@ -1138,6 +1178,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
params: { params: {
id: null, id: null,
currency: 'USD', currency: 'USD',
coin: 'btc',
useSendMax: null useSendMax: null
} }
}) })
@ -1169,7 +1210,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}); });
}) })
.run(function($rootScope, $state, $location, $log, $timeout, startupService, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, emailService, /* plugins START HERE => */ glideraService, buydotbitcoindotcomService, amazonService, bitpayCardService, applicationService) { .run(function($rootScope, $state, $location, $log, $timeout, startupService, ionicToast, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, emailService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService, applicationService, mercadoLibreService) {
uxLanguage.init(); uxLanguage.init();

View file

@ -1,8 +1,19 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('addressbookService', function(bitcore, storageService, lodash) { angular.module('copayApp.services').factory('addressbookService', function($log, bitcore, bitcoreCash, storageService, lodash) {
var root = {}; var root = {};
var getNetwork = function(address) {
var network;
try {
network = (new bitcore.Address(address)).network.name;
} catch(e) {
$log.warn('No valid bitcoin address. Trying bitcoin cash...');
network = (new bitcoreCash.Address(address)).network.name;
}
return network;
};
root.get = function(addr, cb) { root.get = function(addr, cb) {
storageService.getAddressbook('testnet', function(err, ab) { storageService.getAddressbook('testnet', function(err, ab) {
if (err) return cb(err); if (err) return cb(err);
@ -35,7 +46,8 @@ angular.module('copayApp.services').factory('addressbookService', function(bitco
}; };
root.add = function(entry, cb) { root.add = function(entry, cb) {
var network = (new bitcore.Address(entry.address)).network.name; var network = getNetwork(entry.address);
if (lodash.isEmpty(network)) return cb('Not valid bitcoin address');
storageService.getAddressbook(network, function(err, ab) { storageService.getAddressbook(network, function(err, ab) {
if (err) return cb(err); if (err) return cb(err);
if (ab) ab = JSON.parse(ab); if (ab) ab = JSON.parse(ab);
@ -53,7 +65,8 @@ angular.module('copayApp.services').factory('addressbookService', function(bitco
}; };
root.remove = function(addr, cb) { root.remove = function(addr, cb) {
var network = (new bitcore.Address(addr)).network.name; var network = getNetwork(addr);
if (lodash.isEmpty(network)) return cb('Not valid bitcoin address');
storageService.getAddressbook(network, function(err, ab) { storageService.getAddressbook(network, function(err, ab) {
if (err) return cb(err); if (err) return cb(err);
if (ab) ab = JSON.parse(ab); if (ab) ab = JSON.parse(ab);

View file

@ -0,0 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('bitcoreCash', function bitcoreFactory(bwcService) {
var bitcoreCash = bwcService.getBitcoreCash();
return bitcoreCash;
});

View file

@ -53,6 +53,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
'wallet:sells:create,' + 'wallet:sells:create,' +
'wallet:transactions:read,' + 'wallet:transactions:read,' +
'wallet:transactions:send,' + 'wallet:transactions:send,' +
'wallet:transactions:send:bypass-2fa,' +
'wallet:payment-methods:read'; 'wallet:payment-methods:read';
// NW has a bug with Window Object // NW has a bug with Window Object
@ -169,9 +170,9 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
var _getNetAmount = function(amount, cb) { var _getNetAmount = function(amount, cb) {
// Fee Normal for a single transaction (450 bytes) // Fee Normal for a single transaction (450 bytes)
var txNormalFeeKB = 450 / 1000; var txNormalFeeKB = 450 / 1000;
feeService.getFeeRate(null, 'normal', function(err, feePerKB) { feeService.getFeeRate('btc', 'livenet', 'normal', function(err, feePerKb) {
if (err) return cb(err); if (err) return cb(err);
var feeBTC = (feePerKB * txNormalFeeKB / 100000000).toFixed(8); var feeBTC = (feePerKb * txNormalFeeKB / 100000000).toFixed(8);
return cb(null, amount - feeBTC, feeBTC); return cb(null, amount - feeBTC, feeBTC);
}); });

View file

@ -69,7 +69,8 @@ angular.module('copayApp.services').factory('configService', function(storageSer
bannedUntil: null, bannedUntil: null,
}, },
// External services cashSupport: false,
recentTransactions: { recentTransactions: {
enabled: true, enabled: true,
}, },
@ -141,6 +142,11 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.hideNextSteps = defaultConfig.hideNextSteps; configCache.hideNextSteps = defaultConfig.hideNextSteps;
} }
if (!configCache.cashSupport) {
configCache.cashSupport = defaultConfig.cashSupport;
}
if (!configCache.recentTransactions) { if (!configCache.recentTransactions) {
configCache.recentTransactions = defaultConfig.recentTransactions; configCache.recentTransactions = defaultConfig.recentTransactions;
} }
@ -151,6 +157,14 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.bitpayAccount = defaultConfig.bitpayAccount; configCache.bitpayAccount = defaultConfig.bitpayAccount;
} }
if (configCache.wallet.settings.unitCode == 'bit') {
// Convert to BTC. Bits will be disabled
configCache.wallet.settings.unitName = defaultConfig.wallet.settings.unitName;
configCache.wallet.settings.unitToSatoshi = defaultConfig.wallet.settings.unitToSatoshi;
configCache.wallet.settings.unitDecimals = defaultConfig.wallet.settings.unitDecimals;
configCache.wallet.settings.unitCode = defaultConfig.wallet.settings.unitCode;
}
} else { } else {
configCache = lodash.clone(defaultConfig); configCache = lodash.clone(defaultConfig);
}; };

View file

@ -17,6 +17,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
var cache = { var cache = {
updateTs: 0, updateTs: 0,
coin: ''
}; };
root.getCurrentFeeLevel = function() { root.getCurrentFeeLevel = function() {
@ -24,20 +25,20 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
}; };
root.getFeeRate = function(network, feeLevel, cb) { root.getFeeRate = function(coin, network, feeLevel, cb) {
if (feeLevel == 'custom') return cb(); if (feeLevel == 'custom') return cb();
network = network || 'livenet'; network = network || 'livenet';
root.getFeeLevels(function(err, levels, fromCache) { root.getFeeLevels(coin, function(err, levels, fromCache) {
if (err) return cb(err); if (err) return cb(err);
var feeLevelRate = lodash.find(levels[network], { var feeLevelRate = lodash.find(levels[network], {
level: feeLevel level: feeLevel
}); });
if (!feeLevelRate || !feeLevelRate.feePerKB) { if (!feeLevelRate || !feeLevelRate.feePerKb) {
return cb({ return cb({
message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", { message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", {
feeLevel: feeLevel feeLevel: feeLevel
@ -45,34 +46,35 @@ 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);
}); });
}; };
root.getCurrentFeeRate = function(network, cb) { root.getCurrentFeeRate = function(coin, network, cb) {
return root.getFeeRate(network, root.getCurrentFeeLevel(), cb); return root.getFeeRate(coin, network, root.getCurrentFeeLevel(), cb);
}; };
root.getFeeLevels = function(cb) { root.getFeeLevels = function(coin, cb) {
coin = coin || 'btc';
if (cache.updateTs > Date.now() - CACHE_TIME_TS * 1000) { if (cache.coin == coin && cache.updateTs > Date.now() - CACHE_TIME_TS * 1000) {
return cb(null, cache.data, true); return cb(null, cache.data, true);
} }
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
var unitName = configService.getSync().wallet.settings.unitName;
walletClient.getFeeLevels('livenet', function(errLivenet, levelsLivenet) { walletClient.getFeeLevels(coin, 'livenet', function(errLivenet, levelsLivenet) {
walletClient.getFeeLevels('testnet', function(errTestnet, levelsTestnet) { walletClient.getFeeLevels('btc', 'testnet', function(errTestnet, levelsTestnet) {
if (errLivenet || errTestnet) { if (errLivenet || errTestnet) {
return cb(gettextCatalog.getString('Could not get dynamic fee')); return cb(gettextCatalog.getString('Could not get dynamic fee'));
} }
cache.updateTs = Date.now(); cache.updateTs = Date.now();
cache.coin = coin;
cache.data = { cache.data = {
'livenet': levelsLivenet, 'livenet': levelsLivenet,
'testnet': levelsTestnet 'testnet': levelsTestnet

View file

@ -34,6 +34,7 @@ angular.module('copayApp.services')
}; };
root.add = function(level, msg) { root.add = function(level, msg) {
msg = msg.replace('/xpriv.*/', 'xpriv[Hidden]');
logs.push({ logs.push({
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
level: level, level: level,

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) { angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) {
var root = {}; var root = {};
@ -46,7 +46,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
return true; return true;
} }
function goSend(addr, amount, message) { function goSend(addr, amount, message, coin) {
$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
@ -57,18 +57,20 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
$state.transitionTo('tabs.send.confirm', { $state.transitionTo('tabs.send.confirm', {
toAmount: amount, toAmount: amount,
toAddress: addr, toAddress: addr,
description: message description: message,
coin: coin
}); });
} else { } else {
$state.transitionTo('tabs.send.amount', { $state.transitionTo('tabs.send.amount', {
toAddress: addr toAddress: addr,
coin: coin
}); });
} }
}, 100); }, 100);
} }
// data extensions for Payment Protocol with non-backwards-compatible request // data extensions for Payment Protocol with non-backwards-compatible request
if ((/^bitcoin:\?r=[\w+]/).exec(data)) { if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace('bitcoin:?r=', '')); data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
$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
@ -82,27 +84,97 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
data = sanitizeUri(data); data = sanitizeUri(data);
// BIP21 // Bitcoin URL
if (bitcore.URI.isValid(data)) { if (bitcore.URI.isValid(data)) {
var parsed = new bitcore.URI(data); var coin = 'btc';
var parsed = new bitcore.URI(data);
var addr = parsed.address ? parsed.address.toString() : ''; var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message; var message = parsed.message;
var amount = parsed.amount ? parsed.amount : ''; var amount = parsed.amount ? parsed.amount : '';
if (parsed.r) { if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) { payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) { if (err) {
if (addr && amount) goSend(addr, amount, message); if (addr && amount) goSend(addr, amount, message, coin);
else popupService.showAlert(gettextCatalog.getString('Error'), err); else popupService.showAlert(gettextCatalog.getString('Error'), err);
} else handlePayPro(details); } else handlePayPro(details);
}); });
} else { } else {
goSend(addr, amount, message); goSend(addr, amount, message, coin);
} }
return true;
// Cash URI
} else if (bitcoreCash.URI.isValid(data)) {
var coin = 'bch';
var parsed = new bitcoreCash.URI(data);
var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
// paypro not yet supported on cash
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount)
goSend(addr, amount, message, coin);
else
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
handlePayPro(details, coin);
});
} else {
goSend(addr, amount, message, coin);
}
return true;
// Cash URI with bitcoin core address version number?
} else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
$log.debug('Handling bitcoincash URI with legacy address');
var coin = 'bch';
var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));
var oldAddr = parsed.address ? parsed.address.toString() : '';
if (!oldAddr) return false;
var addr = '';
var a = bitcore.Address(oldAddr).toObject();
addr = bitcoreCash.Address.fromObject(a).toString();
// Translate address
$log.debug('address transalated to:' + addr);
popupService.showConfirm(
gettextCatalog.getString('Bitcoin cash Payment'),
gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
gettextCatalog.getString('OK'),
gettextCatalog.getString('Cancel'),
function(ret) {
if (!ret) return false;
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
// paypro not yet supported on cash
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount)
goSend(addr, amount, message, coin);
else
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
handlePayPro(details, coin);
});
} else {
goSend(addr, amount, message, coin);
}
}
);
return true; return true;
// Plain URL // Plain URL
} else if (/^https?:\/\//.test(data)) { } else if (/^https?:\/\//.test(data)) {
@ -127,6 +199,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
} else { } else {
goToAmountPage(data); goToAmountPage(data);
} }
} else if (bitcoreCash.Address.isValid(data, 'livenet')) {
if ($state.includes('tabs.scan')) {
root.showMenu({
data: data,
type: 'bitcoinAddress',
coin: 'bch',
});
} else {
goToAmountPage(data, 'bch');
}
} else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) { } else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
var code = getParameterByName('code', data); var code = getParameterByName('code', data);
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
@ -236,29 +318,29 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
}); });
} }
} }
return false; return false;
}; };
function goToAmountPage(toAddress) { function goToAmountPage(toAddress, coin) {
$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(function() { $timeout(function() {
$state.transitionTo('tabs.send.amount', { $state.transitionTo('tabs.send.amount', {
toAddress: toAddress toAddress: toAddress,
coin: coin,
}); });
}, 100); }, 100);
} }
function handlePayPro(payProDetails) { function handlePayPro(payProDetails, coin) {
var stateParams = { var stateParams = {
toAmount: payProDetails.amount, toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress, toAddress: payProDetails.toAddress,
description: payProDetails.memo, description: payProDetails.memo,
paypro: payProDetails paypro: payProDetails,
coin: coin,
}; };
scannerService.pausePreview(); scannerService.pausePreview();
$state.go('tabs.send', {}, { $state.go('tabs.send', {}, {

View file

@ -0,0 +1,183 @@
'use strict';
angular.module('copayApp.services').factory('mercadoLibreService', function($http, $log, lodash, moment, storageService, configService, platformInfo, nextStepsService, homeIntegrationsService) {
var root = {};
var credentials = {};
// Not used yet
var availableCountries = [{
'country': 'Brazil',
'currency': 'BRL',
'name': 'Mercado Livre',
'url': 'https://www.mercadolivre.com.br'
}];
/*
* Development: 'testnet'
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
//credentials.NETWORK = 'testnet';
if (credentials.NETWORK == 'testnet') {
credentials.BITPAY_API_URL = "https://test.bitpay.com";
} else {
credentials.BITPAY_API_URL = "https://bitpay.com";
};
var homeItem = {
name: 'mercadoLibre',
title: 'Vales-Presente do Mercado Livre Brasil',
icon: 'icon-ml',
sref: 'tabs.giftcards.mercadoLibre',
};
var nextStepItem = {
name: 'mercadoLibre',
title: 'Comprar um Vale-Presente Mercado Livre',
icon: 'icon-ml',
sref: 'tabs.giftcards.mercadoLibre',
};
var _getBitPay = function(endpoint) {
return {
method: 'GET',
url: credentials.BITPAY_API_URL + endpoint,
headers: {
'content-type': 'application/json'
}
};
};
var _postBitPay = function(endpoint, data) {
return {
method: 'POST',
url: credentials.BITPAY_API_URL + endpoint,
headers: {
'content-type': 'application/json'
},
data: data
};
};
root.getNetwork = function() {
return credentials.NETWORK;
};
root.savePendingGiftCard = function(gc, opts, cb) {
var network = root.getNetwork();
storageService.getMercadoLibreGiftCards(network, function(err, oldGiftCards) {
if (lodash.isString(oldGiftCards)) {
oldGiftCards = JSON.parse(oldGiftCards);
}
if (lodash.isString(gc)) {
gc = JSON.parse(gc);
}
var inv = oldGiftCards || {};
inv[gc.invoiceId] = gc;
if (opts && (opts.error || opts.status)) {
inv[gc.invoiceId] = lodash.assign(inv[gc.invoiceId], opts);
}
if (opts && opts.remove) {
delete(inv[gc.invoiceId]);
}
inv = JSON.stringify(inv);
storageService.setMercadoLibreGiftCards(network, inv, function(err) {
homeIntegrationsService.register(homeItem);
nextStepsService.unregister(nextStepItem.name);
return cb(err);
});
});
};
root.getPendingGiftCards = function(cb) {
var network = root.getNetwork();
storageService.getMercadoLibreGiftCards(network, function(err, giftCards) {
var _gcds = giftCards ? JSON.parse(giftCards) : null;
return cb(err, _gcds);
});
};
root.createBitPayInvoice = function(data, cb) {
var dataSrc = {
currency: data.currency,
amount: data.amount,
clientId: data.uuid
};
$http(_postBitPay('/mercado-libre-gift/pay', dataSrc)).then(function(data) {
$log.info('BitPay Create Invoice: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('BitPay Create Invoice: ERROR', JSON.stringify(data.data));
return cb(data.data);
});
};
root.getBitPayInvoice = function(id, cb) {
$http(_getBitPay('/invoices/' + id)).then(function(data) {
$log.info('BitPay Get Invoice: SUCCESS');
return cb(null, data.data.data);
}, function(data) {
$log.error('BitPay Get Invoice: ERROR', JSON.stringify(data.data));
return cb(data.data);
});
};
root.createGiftCard = function(data, cb) {
var dataSrc = {
"clientId": data.uuid,
"invoiceId": data.invoiceId,
"accessKey": data.accessKey
};
$http(_postBitPay('/mercado-libre-gift/redeem', dataSrc)).then(function(data) {
var status = data.data.status == 'new' ? 'PENDING' : (data.data.status == 'paid') ? 'PENDING' : data.data.status;
data.data.status = status;
$log.info('Mercado Libre Gift Card Create/Update: ' + status);
return cb(null, data.data);
}, function(data) {
$log.error('Mercado Libre Gift Card Create/Update: ERROR', JSON.stringify(data.data));
return cb(data.data);
});
};
/*
* Disabled for now *
*/
/*
root.cancelGiftCard = function(data, cb) {
var dataSrc = {
"clientId": data.uuid,
"invoiceId": data.invoiceId,
"accessKey": data.accessKey
};
$http(_postBitPay('/mercado-libre-gift/cancel', dataSrc)).then(function(data) {
$log.info('Mercado Libre Gift Card Cancel: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('Mercado Libre Gift Card Cancel: ' + data.data.message);
return cb(data.data);
});
};
*/
var register = function() {
storageService.getMercadoLibreGiftCards(root.getNetwork(), function(err, giftCards) {
if (giftCards) {
homeIntegrationsService.register(homeItem);
} else {
nextStepsService.register(nextStepItem);
}
});
};
// Hide Mercado Libre
// register();
return root;
});

View file

@ -45,7 +45,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'cancelingGiftCard': 'Canceling Gift Card...', 'cancelingGiftCard': 'Canceling Gift Card...',
'creatingGiftCard': 'Creating Gift Card...', 'creatingGiftCard': 'Creating Gift Card...',
'buyingGiftCard': 'Buying Gift Card...', 'buyingGiftCard': 'Buying Gift Card...',
'topup': gettext('Top up in progress...') 'topup': gettext('Top up in progress...'),
'duplicatingWallet': gettext('Duplicating wallet...'),
}; };
root.clear = function() { root.clear = function() {

View file

@ -57,10 +57,10 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
// This event is sent to an existent instance of Copay (only for standalone apps) // This event is sent to an existent instance of Copay (only for standalone apps)
gui.App.on('open', function(pathData) { gui.App.on('open', function(pathData) {
if (pathData.indexOf('bitcoin:') != -1) { if (pathData.indexOf(/^bitcoin(cash)?:/) != -1) {
$log.debug('Bitcoin URL found'); $log.debug('Bitcoin URL found');
handleOpenURL({ handleOpenURL({
url: pathData.substring(pathData.indexOf('bitcoin:')) url: pathData.substring(pathData.indexOf(/^bitcoin(cash)?:/))
}); });
} else if (pathData.indexOf(appConfigService.name + '://') != -1) { } else if (pathData.indexOf(appConfigService.name + '://') != -1) {
$log.debug(appConfigService.name + ' URL found'); $log.debug(appConfigService.name + ' URL found');
@ -84,6 +84,7 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
if (navigator.registerProtocolHandler) { if (navigator.registerProtocolHandler) {
$log.debug('Registering Browser handlers base:' + base); $log.debug('Registering Browser handlers base:' + base);
navigator.registerProtocolHandler('bitcoin', url, 'Copay Bitcoin Handler'); navigator.registerProtocolHandler('bitcoin', url, 'Copay Bitcoin Handler');
navigator.registerProtocolHandler('web+bitcoincash', url, 'Copay Bitcoin Cash Handler');
navigator.registerProtocolHandler('web+copay', url, 'Copay Wallet Handler'); navigator.registerProtocolHandler('web+copay', url, 'Copay Wallet Handler');
navigator.registerProtocolHandler('web+bitpay', url, 'BitPay Wallet Handler'); navigator.registerProtocolHandler('web+bitpay', url, 'BitPay Wallet Handler');
} }

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, $state) { .factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, $state, sjcl, lodash, storageService, bwcService, configService, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, appConfigService) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
@ -89,6 +89,7 @@ angular.module('copayApp.services')
wallet.copayerId = wallet.credentials.copayerId; wallet.copayerId = wallet.credentials.copayerId;
wallet.m = wallet.credentials.m; wallet.m = wallet.credentials.m;
wallet.n = wallet.credentials.n; wallet.n = wallet.credentials.n;
wallet.coin = wallet.credentials.coin;
root.updateWalletSettings(wallet); root.updateWalletSettings(wallet);
root.wallet[walletId] = wallet; root.wallet[walletId] = wallet;
@ -222,11 +223,12 @@ angular.module('copayApp.services')
return ((config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url); return ((config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url);
}; };
var client = bwcService.getClient(JSON.stringify(credentials), { var client = bwcService.getClient(JSON.stringify(credentials), {
bwsurl: getBWSURL(credentials.walletId), bwsurl: getBWSURL(credentials.walletId),
}); });
var skipKeyValidation = shouldSkipValidation(credentials.walletId); var skipKeyValidation = shouldSkipValidation(credentials.walletId);
if (!skipKeyValidation) if (!skipKeyValidation)
root.runValidation(client, 500); root.runValidation(client, 500);
@ -328,6 +330,7 @@ angular.module('copayApp.services')
passphrase: opts.passphrase, passphrase: opts.passphrase,
account: opts.account || 0, account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44', derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin
}); });
} catch (ex) { } catch (ex) {
@ -336,7 +339,12 @@ angular.module('copayApp.services')
} }
} else if (opts.extendedPrivateKey) { } else if (opts.extendedPrivateKey) {
try { try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey); walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey, {
network: network,
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin,
});
} catch (ex) { } catch (ex) {
$log.warn(ex); $log.warn(ex);
return cb(gettextCatalog.getString('Could not create using the specified extended private key')); return cb(gettextCatalog.getString('Could not create using the specified extended private key'));
@ -346,6 +354,7 @@ angular.module('copayApp.services')
walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, { walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: opts.account || 0, account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44', derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin
}); });
walletClient.credentials.hwInfo = opts.hwInfo; walletClient.credentials.hwInfo = opts.hwInfo;
} catch (ex) { } catch (ex) {
@ -360,6 +369,7 @@ angular.module('copayApp.services')
passphrase: opts.passphrase, passphrase: opts.passphrase,
language: lang, language: lang,
account: 0, account: 0,
coin: opts.coin
}); });
} catch (e) { } catch (e) {
$log.info('Error creating recovery phrase: ' + e.message); $log.info('Error creating recovery phrase: ' + e.message);
@ -369,6 +379,7 @@ angular.module('copayApp.services')
network: network, network: network,
passphrase: opts.passphrase, passphrase: opts.passphrase,
account: 0, account: 0,
coin: opts.coin
}); });
} else { } else {
return cb(e); return cb(e);
@ -380,7 +391,11 @@ angular.module('copayApp.services')
// Creates a wallet on BWC/BWS // Creates a wallet on BWC/BWS
var doCreateWallet = function(opts, cb) { var doCreateWallet = function(opts, cb) {
$log.debug('Creating Wallet:', opts); var showOpts = lodash.clone(opts);
if (showOpts.extendedPrivateKey) showOpts.extendedPrivateKey='[hidden]';
if (showOpts.mnemonic) showOpts.mnemonic='[hidden]';
$log.debug('Creating Wallet:', showOpts);
$timeout(function() { $timeout(function() {
seedWallet(opts, function(err, walletClient) { seedWallet(opts, function(err, walletClient) {
if (err) return cb(err); if (err) return cb(err);
@ -392,6 +407,7 @@ angular.module('copayApp.services')
network: opts.networkName, network: opts.networkName,
singleAddress: opts.singleAddress, singleAddress: opts.singleAddress,
walletPrivKey: opts.walletPrivKey, walletPrivKey: opts.walletPrivKey,
coin: opts.coin
}, function(err, secret) { }, function(err, secret) {
if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb); if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb);
return cb(null, walletClient, secret); return cb(null, walletClient, secret);
@ -435,7 +451,9 @@ angular.module('copayApp.services')
seedWallet(opts, function(err, walletClient) { seedWallet(opts, function(err, walletClient) {
if (err) return cb(err); if (err) return cb(err);
walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) { walletClient.joinWallet(opts.secret, opts.myName || 'me', {
coin: opts.coin
}, function(err) {
if (err) return bwcError.cb(err, gettextCatalog.getString('Could not join wallet'), cb); if (err) return bwcError.cb(err, gettextCatalog.getString('Could not join wallet'), cb);
addAndBindWalletClient(walletClient, { addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl bwsurl: opts.bwsurl
@ -495,7 +513,9 @@ angular.module('copayApp.services')
var walletId = client.credentials.walletId var walletId = client.credentials.walletId
if (!root.profile.addWallet(JSON.parse(client.export()))) if (!root.profile.addWallet(JSON.parse(client.export())))
return cb(gettextCatalog.getString('Wallet already in Copay')); return cb(gettextCatalog.getString("Wallet already in {{appName}}", {
appName: appConfigService.nameCase
}));
var skipKeyValidation = shouldSkipValidation(walletId); var skipKeyValidation = shouldSkipValidation(walletId);
@ -621,6 +641,7 @@ angular.module('copayApp.services')
entropySourcePath: opts.entropySourcePath, entropySourcePath: opts.entropySourcePath,
derivationStrategy: opts.derivationStrategy || 'BIP44', derivationStrategy: opts.derivationStrategy || 'BIP44',
account: opts.account || 0, account: opts.account || 0,
coin: opts.coin
}, function(err) { }, function(err) {
if (err) { if (err) {
if (err instanceof errors.NOT_AUTHORIZED) if (err instanceof errors.NOT_AUTHORIZED)
@ -642,6 +663,7 @@ angular.module('copayApp.services')
walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, { walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: opts.account || 0, account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44', derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin
}, function(err) { }, function(err) {
if (err) { if (err) {
@ -682,6 +704,7 @@ angular.module('copayApp.services')
opts.m = 1; opts.m = 1;
opts.n = 1; opts.n = 1;
opts.networkName = 'livenet'; opts.networkName = 'livenet';
opts.coin = 'btc';
root.createWallet(opts, cb); root.createWallet(opts, cb);
}; };
@ -747,6 +770,12 @@ angular.module('copayApp.services')
var ret = lodash.values(root.wallet); var ret = lodash.values(root.wallet);
if (opts.coin) {
ret = lodash.filter(ret, function(x) {
return (x.credentials.coin == opts.coin);
});
}
if (opts.network) { if (opts.network) {
ret = lodash.filter(ret, function(x) { ret = lodash.filter(ret, function(x) {
return (x.credentials.network == opts.network); return (x.credentials.network == opts.network);
@ -767,12 +796,14 @@ angular.module('copayApp.services')
if (opts.hasFunds) { if (opts.hasFunds) {
ret = lodash.filter(ret, function(w) { ret = lodash.filter(ret, function(w) {
if (!w.status) return;
return (w.status.availableBalanceSat > 0); 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;
return (w.status.availableBalanceSat > opts.minAmount); return (w.status.availableBalanceSat > opts.minAmount);
}); });
} }
@ -857,7 +888,7 @@ angular.module('copayApp.services')
x.types = [x.type]; x.types = [x.type];
if (x.data && x.data.amount) if (x.data && x.data.amount)
x.amountStr = txFormatService.formatAmountStr(x.data.amount); x.amountStr = txFormatService.formatAmountStr(x.wallet.coin, x.data.amount);
x.action = function() { x.action = function() {
// TODO? // TODO?

View file

@ -25,9 +25,10 @@ var RateService = function(opts) {
self._isAvailable = false; self._isAvailable = false;
self._rates = {}; self._rates = {};
self._alternatives = []; self._alternatives = [];
self._ratesBCH = {};
self._queued = []; self._queued = [];
self._fetchCurrencies(); self.updateRates();
}; };
@ -39,14 +40,20 @@ RateService.singleton = function(opts) {
return _instance; return _instance;
}; };
RateService.prototype._fetchCurrencies = function() { RateService.prototype.updateRates = function() {
var self = this; var self = this;
var backoffSeconds = 5; var backoffSeconds = 5;
var updateFrequencySeconds = 5 * 60; var updateFrequencySeconds = 5 * 60;
var rateServiceUrl = 'https://bitpay.com/api/rates'; var rateServiceUrl = 'https://bitpay.com/api/rates';
var bchRateServiceUrl = 'https://api.kraken.com/0/public/Ticker?pair=BCHUSD,BCHEUR';
function getBTC(cb, tries) {
tries = tries || 0;
if (!self.httprequest) return;
if (tries > 5) return cb('could not get BTC rates');
var retrieve = function() {
//log.info('Fetching exchange rates'); //log.info('Fetching exchange rates');
self.httprequest.get(rateServiceUrl).success(function(res) { self.httprequest.get(rateServiceUrl).success(function(res) {
self.lodash.each(res, function(currency) { self.lodash.each(res, function(currency) {
@ -57,27 +64,64 @@ RateService.prototype._fetchCurrencies = function() {
rate: currency.rate rate: currency.rate
}); });
}); });
return cb();
}).error(function() {
//log.debug('Error fetching exchange rates', err);
setTimeout(function() {
backoffSeconds *= 1.5;
getBTC(cb, tries++);
}, backoffSeconds * 1000);
return;
})
}
function getBCH(cb, tries) {
tries = tries || 0;
if (!self.httprequest) return;
if (tries > 5) return cb('could not get BCH rates');
function retry(tries) {
//log.debug('Error fetching exchange rates', err);
setTimeout(function() {
backoffSeconds *= 1.5;
getBTC(cb, tries++);
}, backoffSeconds * 1000);
return;
}
self.httprequest.get(bchRateServiceUrl).success(function(res) {
self.lodash.each(res.result, function(data, paircode) {
var code = paircode.substr(3,3);
var rate =data.c[0];
self._ratesBCH[code] = rate;
})
return cb();
}).error(function() {
return retry(tries);
})
}
getBTC(function(err) {
if (err) return;
getBCH(function(err) {
if (err) return;
self._isAvailable = true; self._isAvailable = true;
self.lodash.each(self._queued, function(callback) { self.lodash.each(self._queued, function(callback) {
setTimeout(callback, 1); setTimeout(callback, 1);
}); });
setTimeout(retrieve, updateFrequencySeconds * 1000); setTimeout( self.updateRates , updateFrequencySeconds * 1000);
}).error(function(err) { })
//log.debug('Error fetching exchange rates', err); })
setTimeout(function() {
backoffSeconds *= 1.5;
retrieve();
}, backoffSeconds * 1000);
return;
});
};
retrieve();
}; };
RateService.prototype.getRate = function(code) { RateService.prototype.getRate = function(code, chain) {
return this._rates[code]; if (chain == 'bch')
return this._ratesBCH[code];
else
return this._rates[code];
}; };
RateService.prototype.getAlternatives = function() { RateService.prototype.getAlternatives = function() {
@ -90,25 +134,25 @@ RateService.prototype.isAvailable = function() {
RateService.prototype.whenAvailable = function(callback) { RateService.prototype.whenAvailable = function(callback) {
if (this.isAvailable()) { if (this.isAvailable()) {
setTimeout(callback, 1); setTimeout(callback, 10);
} else { } else {
this._queued.push(callback); this._queued.push(callback);
} }
}; };
RateService.prototype.toFiat = function(satoshis, code) { RateService.prototype.toFiat = function(satoshis, code, chain) {
if (!this.isAvailable()) { if (!this.isAvailable()) {
return null; return null;
} }
return satoshis * this.SAT_TO_BTC * this.getRate(code); return satoshis * this.SAT_TO_BTC * this.getRate(code, chain);
}; };
RateService.prototype.fromFiat = function(amount, code) { RateService.prototype.fromFiat = function(amount, code, chain) {
if (!this.isAvailable()) { if (!this.isAvailable()) {
return null; return null;
} }
return amount / this.getRate(code) * this.BTC_TO_SAT; return amount / this.getRate(code, chain) * this.BTC_TO_SAT;
}; };
RateService.prototype.listAlternatives = function(sort) { RateService.prototype.listAlternatives = function(sort) {

View file

@ -10,7 +10,7 @@ angular.module('copayApp.services').service('sendMaxService', function(feeServic
* *
*/ */
this.getInfo = function(wallet, cb) { this.getInfo = function(wallet, cb) {
feeService.getCurrentFeeRate(wallet.credentials.network, function(err, feePerKb) { feeService.getCurrentFeeRate(wallet.coin, wallet.credentials.network, function(err, feePerKb) {
if (err) return cb(err); if (err) return cb(err);
var config = configService.getSync().wallet; var config = configService.getSync().wallet;

View file

@ -610,5 +610,17 @@ angular.module('copayApp.services')
storage.remove('txConfirmNotif-' + txid, cb); storage.remove('txConfirmNotif-' + txid, cb);
}; };
root.setMercadoLibreGiftCards = function(network, gcs, cb) {
storage.set('mercadoLibreGiftCards-' + network, gcs, cb);
};
root.getMercadoLibreGiftCards = function(network, cb) {
storage.get('mercadoLibreGiftCards-' + network, cb);
};
root.removeMercadoLibreGiftCards = function(network, cb) {
storage.remove('MercadoLibreGiftCards-' + network, cb);
};
return root; return root;
}); });

View file

@ -7,7 +7,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
root.formatAmount = function(satoshis, fullPrecision) { root.formatAmount = function(satoshis, fullPrecision) {
var config = configService.getSync().wallet.settings; var config = configService.getDefaults().wallet.settings;
if (config.unitCode == 'sat') return satoshis; if (config.unitCode == 'sat') return satoshis;
//TODO : now only works for english, specify opts to change thousand separator and decimal separator //TODO : now only works for english, specify opts to change thousand separator and decimal separator
@ -17,16 +17,15 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return this.Utils.formatAmount(satoshis, config.unitCode, opts); return this.Utils.formatAmount(satoshis, config.unitCode, opts);
}; };
root.formatAmountStr = function(satoshis) { root.formatAmountStr = function(coin, satoshis) {
if (isNaN(satoshis)) return; if (isNaN(satoshis)) return;
var config = configService.getSync().wallet.settings; return root.formatAmount(satoshis) + ' ' + (coin).toUpperCase();
return root.formatAmount(satoshis) + ' ' + config.unitName;
}; };
root.toFiat = function(satoshis, code, cb) { root.toFiat = function(coin, satoshis, code, cb) {
if (isNaN(satoshis)) return; if (isNaN(satoshis)) return;
var val = function() { var val = function() {
var v1 = rateService.toFiat(satoshis, code); var v1 = rateService.toFiat(satoshis, code, coin);
if (!v1) return null; if (!v1) return null;
return v1.toFixed(2); return v1.toFixed(2);
@ -43,10 +42,10 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
}; };
}; };
root.formatToUSD = function(satoshis, cb) { root.formatToUSD = function(coin, satoshis, cb) {
if (isNaN(satoshis)) return; if (isNaN(satoshis)) return;
var val = function() { var val = function() {
var v1 = rateService.toFiat(satoshis, 'USD'); var v1 = rateService.toFiat(satoshis, 'USD', coin);
if (!v1) return null; if (!v1) return null;
return v1.toFixed(2); return v1.toFixed(2);
@ -63,12 +62,12 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
}; };
}; };
root.formatAlternativeStr = function(satoshis, cb) { root.formatAlternativeStr = function(coin, satoshis, cb) {
if (isNaN(satoshis)) return; if (isNaN(satoshis)) return;
var config = configService.getSync().wallet.settings; var config = configService.getSync().wallet.settings;
var val = function() { var val = function() {
var v1 = parseFloat((rateService.toFiat(satoshis, config.alternativeIsoCode)).toFixed(2)); var v1 = parseFloat((rateService.toFiat(satoshis, config.alternativeIsoCode, coin)).toFixed(2));
v1 = $filter('formatFiatAmount')(v1); v1 = $filter('formatFiatAmount')(v1);
if (!v1) return null; if (!v1) return null;
@ -86,7 +85,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
}; };
}; };
root.processTx = function(tx) { root.processTx = function(coin, tx) {
if (!tx || tx.action == 'invalid') if (!tx || tx.action == 'invalid')
return tx; return tx;
@ -101,17 +100,17 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
tx.hasMultiplesOutputs = true; tx.hasMultiplesOutputs = true;
} }
tx.amount = lodash.reduce(tx.outputs, function(total, o) { tx.amount = lodash.reduce(tx.outputs, function(total, o) {
o.amountStr = root.formatAmountStr(o.amount); o.amountStr = root.formatAmountStr(coin, o.amount);
o.alternativeAmountStr = root.formatAlternativeStr(o.amount); o.alternativeAmountStr = root.formatAlternativeStr(coin, o.amount);
return total + o.amount; return total + o.amount;
}, 0); }, 0);
} }
tx.toAddress = tx.outputs[0].toAddress; tx.toAddress = tx.outputs[0].toAddress;
} }
tx.amountStr = root.formatAmountStr(tx.amount); tx.amountStr = root.formatAmountStr(coin, tx.amount);
tx.alternativeAmountStr = root.formatAlternativeStr(tx.amount); tx.alternativeAmountStr = root.formatAlternativeStr(coin, tx.amount);
tx.feeStr = root.formatAmountStr(tx.fee || tx.fees); tx.feeStr = root.formatAmountStr(coin, tx.fee || tx.fees);
if (tx.amountStr) { if (tx.amountStr) {
tx.amountValueStr = tx.amountStr.split(' ')[0]; tx.amountValueStr = tx.amountStr.split(' ')[0];
@ -145,8 +144,6 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
lodash.each(txps, function(tx) { lodash.each(txps, function(tx) {
tx = txFormatService.processTx(tx);
// no future transactions... // no future transactions...
if (tx.createdOn > now) if (tx.createdOn > now)
tx.createdOn = now; tx.createdOn = now;
@ -157,6 +154,8 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return; return;
} }
tx = txFormatService.processTx(tx.wallet.coin, tx);
var action = lodash.find(tx.actions, { var action = lodash.find(tx.actions, {
copayerId: tx.wallet.copayerId copayerId: tx.wallet.copayerId
}); });
@ -180,7 +179,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return txps; return txps;
}; };
root.parseAmount = function(amount, currency) { root.parseAmount = function(coin, amount, currency) {
var config = configService.getSync().wallet.settings; var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000; var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi; var unitToSatoshi = config.unitToSatoshi;
@ -189,21 +188,21 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
var alternativeIsoCode = config.alternativeIsoCode; var alternativeIsoCode = config.alternativeIsoCode;
// If fiat currency // If fiat currency
if (currency != 'bits' && currency != 'BTC' && currency != 'sat') { if (currency != 'BCH' && currency != 'BTC' && currency != 'sat') {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency; amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
amountSat = rateService.fromFiat(amount, currency).toFixed(0); amountSat = rateService.fromFiat(amount, currency, coin).toFixed(0);
} else if (currency == 'sat') { } else if (currency == 'sat') {
amountSat = amount; amountSat = amount;
amountUnitStr = root.formatAmountStr(amountSat); amountUnitStr = root.formatAmountStr(coin, amountSat);
// convert sat to BTC // convert sat to BTC or BCH
amount = (amountSat * satToBtc).toFixed(8); amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC'; currency = (coin).toUpperCase();
} else { } else {
amountSat = parseInt((amount * unitToSatoshi).toFixed(0)); amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = root.formatAmountStr(amountSat); amountUnitStr = root.formatAmountStr(coin, amountSat);
// convert unit to BTC // convert unit to BTC or BCH
amount = (amountSat * satToBtc).toFixed(8); amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC'; currency = (coin).toUpperCase();
} }
return { return {

View file

@ -17,6 +17,9 @@ angular.module('copayApp.services')
}, { }, {
name: 'Italiano', name: 'Italiano',
isoCode: 'it', isoCode: 'it',
}, {
name: 'Nederlands',
isoCode: 'nl',
}, { }, {
name: 'Polski', name: 'Polski',
isoCode: 'pl', isoCode: 'pl',

View file

@ -2,8 +2,8 @@
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, intelTEE, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) { angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, intelTEE, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
// Ratio low amount warning (fee/amount) in incoming TX // Ratio low amount warning (fee/amount) in incoming TX
var LOW_AMOUNT_RATIO = 0.15; var LOW_AMOUNT_RATIO = 0.15;
// Ratio of "many utxos" warning in total balance (fee/amount) // Ratio of "many utxos" warning in total balance (fee/amount)
var TOTAL_LOW_WARNING_RATIO = .3; var TOTAL_LOW_WARNING_RATIO = .3;
@ -104,7 +104,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.getStatus = function(wallet, opts, cb) { root.getStatus = function(wallet, opts, cb) {
opts = opts || {}; opts = opts || {};
var walletId = wallet.id;
function processPendingTxps(status) { function processPendingTxps(status) {
var txps = status.pendingTxps; var txps = status.pendingTxps;
@ -130,7 +130,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
lodash.each(txps, function(tx) { lodash.each(txps, function(tx) {
tx = txFormatService.processTx(tx); tx = txFormatService.processTx(wallet.coin, tx);
// no future transactions... // no future transactions...
if (tx.createdOn > now) if (tx.createdOn > now)
@ -213,14 +213,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// Selected unit // Selected unit
cache.unitToSatoshi = config.settings.unitToSatoshi; cache.unitToSatoshi = config.settings.unitToSatoshi;
cache.satToUnit = 1 / cache.unitToSatoshi; cache.satToUnit = 1 / cache.unitToSatoshi;
cache.unitName = config.settings.unitName;
//STR //STR
cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName; cache.totalBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.totalBalanceSat);
cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName; cache.lockedBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.lockedBalanceSat);
cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName; cache.availableBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.availableBalanceSat);
cache.spendableBalanceStr = txFormatService.formatAmount(cache.spendableAmount) + ' ' + cache.unitName; cache.spendableBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.spendableAmount);
cache.pendingBalanceStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName; cache.pendingBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.pendingAmount);
cache.alternativeName = config.settings.alternativeName; cache.alternativeName = config.settings.alternativeName;
cache.alternativeIsoCode = config.settings.alternativeIsoCode; cache.alternativeIsoCode = config.settings.alternativeIsoCode;
@ -238,11 +237,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
rateService.whenAvailable(function() { rateService.whenAvailable(function() {
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode); var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode, wallet.coin);
var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode); var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode, wallet.coin);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode); var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode, wallet.coin);
var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode); var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode, wallet.coin);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode); var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode, wallet.coin);
cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative);
cache.pendingBalanceAlternative = $filter('formatFiatAmount')(pendingBalanceAlternative); cache.pendingBalanceAlternative = $filter('formatFiatAmount')(pendingBalanceAlternative);
@ -260,6 +259,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}; };
function cacheStatus(status) { function cacheStatus(status) {
if (status.wallet && status.wallet.scanStatus == 'running') return;
wallet.cachedStatus = status ||  {}; wallet.cachedStatus = status ||  {};
var cache = wallet.cachedStatus; var cache = wallet.cachedStatus;
cache.statusUpdatedOn = Date.now(); cache.statusUpdatedOn = Date.now();
@ -304,6 +305,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cacheStatus(status); cacheStatus(status);
wallet.scanning = status.wallet && status.wallet.scanStatus == 'running';
return cb(null, status); return cb(null, status);
}); });
}; };
@ -366,7 +369,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
wallet.hasUnsafeConfirmed = false; wallet.hasUnsafeConfirmed = false;
lodash.each(txs, function(tx) { lodash.each(txs, function(tx) {
tx = txFormatService.processTx(tx); tx = txFormatService.processTx(wallet.coin, tx);
// no future transactions... // no future transactions...
if (tx.time > now) if (tx.time > now)
@ -400,7 +403,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var LIMIT = 50; var LIMIT = 50;
var requestLimit = FIRST_LIMIT; var requestLimit = FIRST_LIMIT;
var walletId = wallet.credentials.walletId; var walletId = wallet.credentials.walletId;
var config = configService.getSync().wallet.settings;
var opts = opts || {}; var opts = opts || {};
var progressFn = opts.progressFn || function() {}; var progressFn = opts.progressFn || function() {};
@ -414,18 +416,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var fixTxsUnit = function(txs) { var fixTxsUnit = function(txs) {
if (!txs || !txs[0] || !txs[0].amountStr) return; if (!txs || !txs[0] || !txs[0].amountStr) return;
var cacheUnit = txs[0].amountStr.split(' ')[1]; var cacheCoin = txs[0].amountStr.split(' ')[1];
if (cacheUnit == config.unitName) if (cacheCoin == 'bits') {
return;
var name = ' ' + config.unitName; $log.debug('Fixing Tx Cache Unit to: ' + wallet.coin)
lodash.each(txs, function(tx) {
$log.debug('Fixing Tx Cache Unit to:' + name) tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount);
lodash.each(txs, function(tx) { tx.feeStr = txFormatService.formatAmountStr(wallet.coin, tx.fees);
tx.amountStr = txFormatService.formatAmount(tx.amount) + name; });
tx.feeStr = txFormatService.formatAmount(tx.fees) + name; }
});
}; };
getSavedTxs(walletId, function(err, txsFromLocal) { getSavedTxs(walletId, function(err, txsFromLocal) {
@ -788,7 +788,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
//prefs.email (may come from arguments) //prefs.email (may come from arguments)
prefs.email = config.emailNotifications.email; prefs.email = config.emailNotifications.email;
prefs.language = uxLanguage.getCurrentLanguage(); prefs.language = uxLanguage.getCurrentLanguage();
prefs.unit = walletSettings.unitCode; // prefs.unit = walletSettings.unitCode; // TODO: remove, not used
updateRemotePreferencesFor(lodash.clone(clients), prefs, function(err) { updateRemotePreferencesFor(lodash.clone(clients), prefs, function(err) {
if (err) return cb(err); if (err) return cb(err);
@ -820,13 +820,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
$log.debug('Scanning wallet ' + wallet.id); $log.debug('Scanning wallet ' + wallet.id);
if (!wallet.isComplete()) return; if (!wallet.isComplete()) return;
wallet.updating = true; wallet.scanning = true;
ongoingProcess.set('scanning', true);
wallet.startScan({ wallet.startScan({
includeCopayerBranches: true, includeCopayerBranches: true,
}, function(err) { }, function(err) {
wallet.updating = false;
ongoingProcess.set('scanning', false);
return cb(err); return cb(err);
}); });
}; };
@ -922,28 +919,30 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}; };
// Approx utxo amount, from which the uxto is economically redeemable // Approx utxo amount, from which the uxto is economically redeemable
root.getMinFee = function(wallet, feeLevels, nbOutputs) { root.getMinFee = function(wallet, feeLevels, nbOutputs) {
var lowLevelRate = (lodash.find(feeLevels[wallet.network], { var lowLevelRate = (lodash.find(feeLevels[wallet.network], {
level: 'normal', level: 'normal',
}).feePerKB / 1000).toFixed(0); }).feePerKb / 1000).toFixed(0);
var size = root.getEstimatedTxSize(wallet, nbOutputs); var size = root.getEstimatedTxSize(wallet, nbOutputs);
return size * lowLevelRate; return size * lowLevelRate;
}; };
// Approx utxo amount, from which the uxto is economically redeemable // Approx utxo amount, from which the uxto is economically redeemable
root.getLowAmount = function(wallet, feeLevels, nbOutputs) { root.getLowAmount = function(wallet, feeLevels, nbOutputs) {
var minFee = root.getMinFee(wallet,feeLevels, nbOutputs); var minFee = root.getMinFee(wallet, feeLevels, nbOutputs);
return parseInt( minFee / LOW_AMOUNT_RATIO); return parseInt(minFee / LOW_AMOUNT_RATIO);
}; };
root.getLowUtxos = function(wallet, levels, cb) { root.getLowUtxos = function(wallet, levels, cb) {
wallet.getUtxos({}, function(err, resp) { wallet.getUtxos({
coin: wallet.coin
}, function(err, resp) {
if (err || !resp || !resp.length) return cb(); if (err || !resp || !resp.length) return cb();
var minFee = root.getMinFee(wallet, levels, resp.length); var minFee = root.getMinFee(wallet, levels, resp.length);
@ -959,7 +958,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var totalLow = lodash.sum(lowUtxos, 'satoshis'); var totalLow = lodash.sum(lowUtxos, 'satoshis');
return cb(err, { return cb(err, {
allUtxos: resp || [], allUtxos: resp || [],
lowUtxos: lowUtxos || [], lowUtxos: lowUtxos || [],
warning: minFee / balance > TOTAL_LOW_WARNING_RATIO, warning: minFee / balance > TOTAL_LOW_WARNING_RATIO,
minFee: minFee, minFee: minFee,
@ -1236,5 +1235,38 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
root.getProtocolHandler = function(wallet) {
if (wallet.coin== 'bch') return 'bitcoincash';
else return 'bitcoin';
}
root.copyCopayers = function(wallet, newWallet, cb) {
var c = wallet.credentials;
var walletPrivKey = bitcore.PrivateKey.fromString(c.walletPrivKey);
var copayer = 1,
i = 0,
l = c.publicKeyRing.length;
var mainErr = null;
lodash.each(c.publicKeyRing, function(item) {
var name = item.copayerName || ('copayer ' + copayer++);
newWallet._doJoinWallet(newWallet.credentials.walletId, walletPrivKey, item.xPubKey, item.requestPubKey, name, {
coin: newWallet.credentials.coin,
}, function(err) {
//Ignore error is copayer already in wallet
if (err && !(err instanceof errors.COPAYER_IN_WALLET)) {
mainErr = err;
}
if (++i == l) {
return cb(mainErr);
}
});
});
};
return root; return root;
}); });

View file

@ -139,6 +139,19 @@
} }
} }
.amount { .amount {
.icon-toggle {
font-size: 1.2em;
width: auto;
margin: 0.8em auto;
border: 1px solid $v-subtle-gray;
color: $v-dark-gray;
border-radius: 3px;
padding: 0 10px;
cursor: pointer;
@media(max-height: 280px) {
margin: 0.1em auto;
}
}
&__editable { &__editable {
&--minimize { &--minimize {
font-size: 22px; font-size: 22px;
@ -187,7 +200,7 @@
&__result { &__result {
color: $v-light-gray; color: $v-light-gray;
font-size: .9em; font-size: .9em;
margin-bottom: -.9em; //margin-bottom: -.9em; TODO matias
line-height: 1; line-height: 1;
@media(max-height: 480px) { @media(max-height: 480px) {
margin-bottom: 0; margin-bottom: 0;
@ -196,7 +209,6 @@
&__result-equiv { &__result-equiv {
color: $v-mid-gray; color: $v-mid-gray;
font-size: 1.2em; font-size: 1.2em;
margin-top: 2rem;
@media(max-height: 480px) { @media(max-height: 480px) {
margin-top: 0; margin-top: 0;
font-size: 16px; font-size: 16px;

View file

@ -0,0 +1,43 @@
#cash-scan {
.comment {
color: #667;
font-size: 0.9em;
}
.item {
color: $v-dark-gray;
padding-top: 1.3rem;
padding-bottom: 1.3rem;
}
.heading {
font-size: 17px;
color: $v-dark-gray;
margin: 1rem 0;
padding-top: 5px;
padding-bottom: 5px;
border: none;
}
.text-disabled {
color: $v-light-gray;
}
.supported {
display: flex;
.wallet-content {
padding-left: 7px;
}
}
.duplicate-button {
position: absolute;
right: 15px;
padding-top: .5rem;
}
a {
cursor: pointer;
}
}

View file

@ -99,6 +99,9 @@
i { i {
padding-right: 20px; padding-right: 20px;
} }
span {
display: inline;
}
} }
} }
.toggle-unconfirmed { .toggle-unconfirmed {

View file

@ -7,6 +7,17 @@ wallet-selector {
padding-right: .75rem; padding-right: .75rem;
} }
.subheader {
margin: 20px 0 10px 0;
font-weight: bold;
padding-bottom: 10px;
border-bottom: 1px solid #EFEFEF;
.wallet-coin-logo {
vertical-align: middle;
margin-right: 5px;
}
}
.wallet-selector { .wallet-selector {
.wallet { .wallet {
border: 0; border: 0;

View file

@ -1,6 +1,7 @@
@import "coinbase"; @import "coinbase";
@import "glidera"; @import "glidera";
@import "amazon"; @import "amazon";
@import "mercadolibre";
#coinbase, #glidera { #coinbase, #glidera {
.button-small { .button-small {

View file

@ -0,0 +1,202 @@
#mercadolibre {
$item-lateral-padding: 20px;
$item-vertical-padding: 10px;
$item-border-color: #EFEFEF;
$item-label-color: #6C6C6E;
@extend .deflash-blue;
.icon-amazon {
background-image: url("../img/mercado-libre/icon-ml.svg");
}
.spinner svg {
stroke: black;
fill: black;
}
.add-bottom-for-cta {
bottom: 92px;
}
.head {
padding: 30px $item-lateral-padding 4rem;
border-top: 0;
.sending-label {
display: flex;
font-size: 18px;
align-items: center;
margin-bottom: 1.8rem;
img {
margin-right: 1rem;
height: 35px;
width: 35px;
}
span {
text-transform: capitalize;
}
.big-icon-svg {
padding: 0 7px 0 0;
margin-right: 0.6rem;
}
.big-icon-svg > .bg {
height: 28px;
box-shadow: none;
}
}
.amount-label{
line-height: 30px;
.amount{
font-size: 38px;
margin-bottom: .5rem;
> .unit {
font-family: "Roboto-Light";
}
}
.alternative {
font-size: 12px;
font-family: "Roboto-Light";
color: #9B9B9B;
}
}
}
.item {
border-color: $item-border-color;
}
.info {
.badge {
border-radius: 0;
padding: .5rem;
}
.item {
color: #4A4A4A;
padding-top: $item-vertical-padding;
padding-bottom: $item-vertical-padding;
padding-left: $item-lateral-padding;
&:not(.item-icon-right) {
padding-right: $item-lateral-padding;
}
.label {
font-size: 14px;
color: $item-label-color;
margin-bottom: 8px;
}
.capitalized {
text-transform: capitalize;
}
.wallet .big-icon-svg > .bg {
height: 24px;
width: 24px;
padding: 2px;
box-shadow: none;
vertical-align: middle;
}
.total-amount {
font-weight: bold;
}
&.single-line {
display: flex;
align-items: center;
padding-top: 17px;
padding-bottom: 17px;
.label {
margin: 0;
flex-grow: 1;
}
}
}
.item-divider {
padding-top: 1.2rem;
color: $item-label-color;
font-size: 15px;
}
.wallet {
display: flex;
align-items: center;
padding: .2rem 0;
margin-bottom: 5px;
~ .bp-arrow-right {
top: 14px;
}
> i {
padding: 0;
position: static;
> img {
height: 24px;
width: 24px;
padding: 2px;
margin-right: .7rem;
box-shadow: none;
}
}
}
}
}
#meli-list-cards {
img.item-logo {
width: auto;
height: auto;
border-radius: 0;
}
}
#meli-card {
.card-head {
margin: 20px 0;
text-align: center;
.date {
font-size: 12px;
margin: 10px 0;
}
.amount {
font-size: 16px;
font-weight: bold;
}
}
.card-status {
text-align: center;
margin-bottom: 25px;
.card-status-desc {
margin-top: 5px;
font-size: 12px;
color: $v-text-secondary-color;
}
.redeem-pin {
font-weight: bold;
font-size: 22px;
}
.button-redeem {
margin-top: 10px;
background: transparent;
border: none;
font-size: 12px;
color: $v-text-accent-color;
}
}
.card-remove {
text-align: center;
margin-top: 30px;
.button-remove {
margin-top: 10px;
background: transparent;
border: none;
font-size: 12px;
color: red;
}
}
}

View file

@ -17,6 +17,11 @@
.icon-amazon { .icon-amazon {
background-image: url("../img/icon-amazon.svg"); background-image: url("../img/icon-amazon.svg");
} }
.icon-ml {
background-image: url("../img/mercado-libre/icon-ml.svg");
background-position: center;
background-size: 85%;
}
.bg { .bg {
&.wallet { &.wallet {
padding: .25rem padding: .25rem
@ -54,6 +59,10 @@
} }
} }
} }
.wallet-coin-logo {
vertical-align: middle;
margin-right: 5px;
}
.wallet-details__item.item { .wallet-details__item.item {
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;

View file

@ -20,6 +20,12 @@
.has-comment { .has-comment {
border-bottom: 0 none; border-bottom: 0 none;
} }
.scan-label {
cursor: pointer;
cursor: hand;
color: $link-color;
font-weight: bold;
}
.comment { .comment {
padding: 15px; padding: 15px;
background-color: #fff; background-color: #fff;
@ -34,6 +40,9 @@
width: 20px; width: 20px;
} }
} }
a {
cursor: pointer;
}
} }
&-explanation, &-button-group { &-explanation, &-button-group {
padding: 0 1rem; padding: 0 1rem;
@ -137,6 +146,14 @@
.log-level { .log-level {
font-weight: bold; font-weight: bold;
} }
.alt-currency-radio {
.item-content {
padding-right: 16px;
}
.radio-icon {
display: none;
}
}
} }
#tab-settings { #tab-settings {
@ -149,6 +166,13 @@
border-radius: 0; border-radius: 0;
box-shadow: none; box-shadow: none;
} }
& > .bch {
background-color: #9b9bab;
border-radius: 1rem;
}
& > .bch-enabled {
background-color: #ff9900 !important;
}
&.circle{ &.circle{
left:8px; left:8px;
.bg { .bg {

View file

@ -50,3 +50,4 @@
@import "includes/pin"; @import "includes/pin";
@import "includes/logOptions"; @import "includes/logOptions";
@import "includes/checkBar"; @import "includes/checkBar";
@import "cashScan";

View file

@ -1,4 +1,22 @@
#wallet-backup-phrase { #wallet-backup-phrase {
.comment {
color: #667;
font-size: 0.9em;
}
.item {
color: $v-dark-gray;
padding-top: 1.3rem;
padding-bottom: 1.3rem;
border: none;
}
.heading {
font-size: 17px;
color: $v-dark-gray;
margin: 1rem 0;
padding-top: 5px;
padding-bottom: 5px;
border: none;
}
h3 { h3 {
padding: 15px; padding: 15px;
} }

View file

@ -88,6 +88,27 @@
padding: 1rem; padding: 1rem;
background: #f8f8f9; background: #f8f8f9;
} }
&__no-transaction {
color: $v-mid-gray;
font-size: 12.5px;
text-align: center;
padding-top: 2rem;
}
&__no-update-history {
color: $v-error-color;
font-size: 12.5px;
text-align: center;
padding-top: 2rem;
}
&__updating-history {
color: $v-mid-gray;
font-size: 12.5px;
text-align: center;
padding-top: 1rem;
}
} }
#walletDetails { #walletDetails {
@ -177,10 +198,6 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&.collapsible {
margin-bottom: 10px;
}
&__balance { &__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);
@ -198,8 +215,21 @@
&__button-balance { &__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);
margin-top: 10px;
i.icon {
margin-right: 7px;
vertical-align: middle;
}
} }
&__error {
font-size: 14px;
padding: 35px 20px;
}
}
.no-alternative {
padding-top: 45px;
} }
.item.item-footer { .item.item-footer {
font-weight: lighter; font-weight: lighter;
@ -237,6 +267,17 @@
font-size: 20px; font-size: 20px;
color: #fff; color: #fff;
width:95%; width:95%;
.actions{
float: right;
a {
color: white;
font-size: 14px;
cursor: pointer;
padding: 5px;
line-height: 16px;
}
}
} }
.wallet-not-backed-up-warning { .wallet-not-backed-up-warning {

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="49px" height="33px" viewBox="0 0 49 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 4</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Home/Overview" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-4" transform="translate(0.000000, -6.000000)" fill-rule="nonzero">
<g id="element-icon-bitcoin" transform="translate(24.500000, 22.000000) rotate(-26.000000) translate(-24.500000, -22.000000) translate(8.000000, 6.000000)">
<g id="bitcoin" transform="translate(0.000000, -0.000000)">
<path d="M32.1867932,19.8608311 C30.0167764,28.4289206 21.2022608,33.6584288 12.4778868,31.5222983 C3.77643011,29.3856664 -1.5352984,20.7067588 0.634718392,12.1386692 C2.80422591,3.57108109 11.6192507,-1.65842708 20.3207074,0.477703406 C29.0450815,2.59177057 34.3563007,11.2932429 32.1867932,19.8608311" id="Shape" fill="#09C286"></path>
<path d="M20.8680125,13.6901518 C20.3834958,15.7120375 17.1993056,14.7348781 16.1611297,14.4850856 L17.0381466,10.8958545 C18.0529285,11.1456469 21.3754035,11.5776651 20.8680125,13.6901518 L20.8680125,13.6901518 Z M20.3601017,19.4829825 C19.8298366,21.7091043 15.9994508,20.5507429 14.7767218,20.255394 L15.7228811,16.3027353 C16.9684843,16.5980842 20.9137609,17.1432255 20.3601017,19.4829825 Z M24.4670572,13.6901518 C24.7670209,11.5090745 23.0826493,10.3732354 20.7754761,9.60082384 L21.4908141,6.64784689 L19.6681577,6.21634063 L18.9762137,9.1012389 C18.4916969,8.98760381 18.0071801,8.87396871 17.5226634,8.78336775 L18.2146074,5.89795761 L16.3690768,5.46645136 L15.6537387,8.41994018 C15.2612385,8.32882735 14.8692582,8.26074867 14.4767581,8.16963584 L11.9621576,7.57944995 L11.500515,9.48770061 C11.500515,9.48770061 12.8620487,9.78253763 12.8157805,9.80557177 C13.5539927,9.98728555 13.6922776,10.4643482 13.6694034,10.8277758 L12.8620487,14.1897367 C12.9077971,14.2127709 12.9774594,14.2127709 13.0466018,14.2578154 C12.9774594,14.2352931 12.9311911,14.2352931 12.8620487,14.2127709 L11.7542105,18.914807 C11.6621939,19.1420772 11.4313726,19.4599483 10.9239817,19.3468251 C10.9468558,19.3693474 9.58584199,19.0284421 9.58584199,19.0284421 L8.70882504,21.0958842 L11.0851407,21.6410256 C11.5239091,21.7546607 11.9621576,21.8452617 12.377532,21.9588968 L11.6621939,24.9349079 L13.4848503,25.3664141 L14.2001884,22.4134371 C14.7075794,22.5495945 15.1920961,22.6632296 15.6537387,22.7768647 L14.9384006,25.7073194 L16.7610571,26.1388257 L17.4763951,23.1633264 C20.590923,23.7084678 22.9209704,23.4581634 23.8671298,20.664378 C24.6287361,18.4377443 23.7979873,17.1657478 22.1370098,16.3252576 C23.3597388,16.052431 24.2362359,15.2805313 24.4670572,13.6901518 L24.4670572,13.6901518 Z" id="Shape" fill="#FFFFFF"></path>
</g>
</g>
<path d="M15.921528,5.99999821 L0,5.99999821 L0,37.9999982 L15.913948,37.9999982 C8.54220902,34.2663545 4.45416261,25.9154102 6.54600075,17.6560011 C7.87168946,12.4207324 11.4030305,8.29337503 15.921528,5.99999821 Z M32.6737493,5.99999821 L49,5.99999821 L49,37.9999982 L32.6621291,37.9999982 C37.1847176,35.7065678 40.716075,31.5790477 42.0420849,26.3434332 C44.1319349,18.09042 40.0515857,9.72697744 32.6737493,5.99999821 Z" id="Combined-Shape" fill="#09C286"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

12
www/img/icon-bch.svg Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Fondo" transform="matrix(0.0814451,-0.0296277,0.0296277,0.0814451,-44.594,-27.3805)">
<g transform="matrix(10.8433,3.94452,-3.94452,10.8433,375.543,472.796)">
<use xlink:href="#_Image1" x="7.552" y="5.557" width="23.603px" height="28.885px" transform="matrix(0.983467,0,0,0.996049,0,0)"/>
</g>
</g>
<defs>
<image id="_Image1" width="24px" height="29px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAdCAYAAACwuqxLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACgUlEQVRIia2WzYuPURTHP88Y7zNEaWJKppnEUKRkM5shdlIipSSDSLazQFYkK/EXeMnChsWUkvK2mCJvWXgpZczGa6EmJn7Mx+K50zzu3OeZ35Tv6t5zzj3fc8859wUmAbVJbZjMmnqcZuo69ZT6XN31v5wuV+eE+XXH8FptrNfXP9tVZ6n7gD7gLrA6qO4BhnEHME1dpk6bbORz1duFaC8E0hXq24L8g/pJfa9eVddMhuSk/+KY+shqDKqb6nE+Q92hfpvAYQrP1QWxz0ydAiwAFgMHgJ6KGPqBH8AMoAvIIn17lmVv4qh71C8VkT1Rj6ob1Jawplk9H9kNq5tTaTlU4fyPurcknV3mxR7FV7UptmsErgNbw3YXAcsK+gZgeUm6BoH5hfltoJa0DHXAvMAxHqvtkf0ctS/scETtj23KiFrUOwmS3UG/VD2nDiXqdDOQ7hkNuIzkeILggflZeFdRr1HU1MtVBN2OPwcjCUfPzHt/MKH/re4vI5hufvxTeKGeUDeqbcF+ieNbVvWjOrOM5HBiwX21s8R+ZmIXI2pz2ePRD7yJZJ1Aa4l9E3nbFjEEtCYJsix7CtyKxM3k10MKs4G5kWwgy7JXVc/fvYSsW11YFKjzgHPAvKIYKO+ksLBNfZioxc6gX6+eVV8m8n9Jba4kCE5OJwgG1BuhS2L8VI9Y75OqbjHv6Sr8iebTiz4m+oJ8B4ZLdDXgM3AGGCjIe9X4nRgX+VTzCyzObS2KvCPY3yzIfxRTVNamNfLrNwN+AV+Ba0Av8KWwdlUYXwHeAxeBbYz9QCp3sTYUc3tB1mB+Y46iz/DbU1snTE09UHvN2/eguvK/OI0IZsedUoW/zudIHkrmZMIAAAAASUVORK5CYII="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,93 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
xmlns:dc="http://purl.org/dc/elements/1.1/" <svg width="100%" height="100%" viewBox="0 0 51 51" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
xmlns:cc="http://creativecommons.org/ns#" <path id="coin" d="M25.5,51C39.6,51 51,39.6 51,25.5C51,11.4 39.5,0 25.5,0C11.5,0 0,11.4 0,25.5C0,39.6 11.4,51 25.5,51Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" <g id="BitPay.-Bitcoin_Symbol" transform="matrix(0.939973,0.341249,-0.341249,0.939973,10.2185,-7.19166)">
xmlns:svg="http://www.w3.org/2000/svg" <path d="M33.5,19.1C32.9,16.1 30.2,15.3 27,15.2L26.5,11L24,11.2L24.5,15.4C23.8,15.5 23.2,15.5 22.5,15.6L22,11.4L19.5,11.6L20,15.9C19.5,15.9 18.9,16 18.4,16.1L15,16.4L15.3,19.2C15.3,19.2 17.1,19 17.1,19.1C18.1,19 18.5,19.6 18.6,20.1L19.1,25C19.2,25 19.3,25 19.4,25C19.3,25 19.2,25 19.1,25L19.9,31.8C19.9,32.1 19.7,32.7 19.1,32.7L17.3,32.9L17.2,36L20.4,35.7C21,35.7 21.6,35.6 22.2,35.6L22.7,39.9L25.2,39.7L24.7,35.4C25.4,35.4 26.1,35.3 26.7,35.3L27.2,39.5L29.7,39.3L29.2,35C33.4,34.4 36.2,33 36.1,28.9C36,25.6 34.4,24.2 31.9,23.8C33.2,22.8 34,21.4 33.5,19.1ZM31,28.7C31.4,31.9 26,32 24.3,32.2L23.7,26.5C25.5,26.3 30.7,25.3 31,28.7ZM23.5,23.8L23,18.6C24.4,18.5 28.8,17.6 29.1,20.7C29.3,23.6 24.9,23.7 23.5,23.8Z" style="fill:white;fill-rule:nonzero;"/>
xmlns="http://www.w3.org/2000/svg" </g>
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" <defs>
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" <linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-17.5917,-30.4347,30.4347,-17.5917,30.8557,34.8191)"><stop offset="0" style="stop-color:rgb(255,162,75);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(247,137,28);stop-opacity:1"/></linearGradient>
version="1.1" </defs>
id="Layer_1" </svg>
x="0px"
y="0px"
viewBox="0 0 51 51"
style="enable-background:new 0 0 51 51;"
xml:space="preserve"
sodipodi:docname="icon-bitcoin.svg"
inkscape:version="0.92.1 r15371"><metadata
id="metadata23"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview21"
showgrid="false"
inkscape:zoom="15.54902"
inkscape:cx="25.5"
inkscape:cy="25.5"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style2">
.st0{fill:url(#coin_1_);}
.st1{filter:url(#Adobe_OpacityMaskFilter);}
.st2{fill:#FFFFFF;}
.st3{mask:url(#mask-3);fill:#FFFFFF;}
</style><linearGradient
id="coin_1_"
gradientUnits="userSpaceOnUse"
x1="-273.8224"
y1="413.966"
x2="-274.172"
y2="414.57"
gradientTransform="matrix(50.3194 0 0 -50.3886 13809.4346 20893.9863)"><stop
offset="0"
style="stop-color:#FFA24B"
id="stop4" /><stop
offset="1"
style="stop-color:#F7891C"
id="stop6" /></linearGradient><path
id="coin"
class="st0"
d="M25.5,51c14.1,0,25.5-11.4,25.5-25.5S39.5,0,25.5,0S0,11.4,0,25.5S11.4,51,25.5,51z"
style="fill:#fab915;fill-opacity:1" /><defs
id="defs13"><filter
id="Adobe_OpacityMaskFilter"
filterUnits="userSpaceOnUse"
x="15"
y="11"
width="21"
height="28.7"><feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
id="feColorMatrix10" /></filter></defs><mask
maskUnits="userSpaceOnUse"
x="15"
y="11"
width="21"
height="28.7"
id="mask-3"><g
class="st1"
id="g16"><path
id="path-2"
class="st2"
d="M26.2,50.4c13.9,0,25.2-11.3,25.2-25.2S40.1,0,26.2,0S1,11.3,1,25.2S12.3,50.4,26.2,50.4z" /></g></mask><g
transform="matrix(2.7667404,0,0,2.7667404,-215.6998,-317.27681)"
aria-label=""
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:icomoon;-inkscape-font-specification:icomoon;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text4487"><path
inkscape:connector-curvature="0"
d="m 89.710383,123.60521 v -0.0207 q 0.506429,-0.24804 0.785482,-0.7338 0.289388,-0.48576 0.289388,-1.11621 0,-0.69247 -0.330729,-1.1989 -0.33073,-0.50642 -0.847494,-0.74414 -0.320394,-0.14469 -0.682129,-0.19637 -0.3514,-0.0517 -0.816487,-0.0517 H 87.78802 v -0.7028 q 0,-0.10336 -0.07235,-0.1757 -0.07235,-0.0724 -0.1757,-0.0724 H 86.8165 q -0.103353,0 -0.1757,0.0724 -0.07235,0.0723 -0.07235,0.1757 v 0.7028 h -0.806152 v -0.7028 q 0,-0.10336 -0.07235,-0.1757 -0.07235,-0.0724 -0.1757,-0.0724 h -0.7028 q -0.113688,0 -0.186035,0.0724 -0.06201,0.0723 -0.06201,0.1757 v 0.7028 h -0.74414 q -0.06201,0 -0.113688,0.0517 -0.04134,0.0413 -0.04134,0.11369 v 0.68213 q 0,0.0724 0.04134,0.12402 0.05168,0.0413 0.113688,0.0413 h 0.74414 v 6.57324 h -0.692464 q -0.08268,0 -0.144694,0.062 -0.05168,0.0517 -0.05168,0.13436 v 0.59944 q 0,0.0827 0.05168,0.1447 0.06201,0.0517 0.144694,0.0517 h 0.692464 v 0.68213 q 0,0.11369 0.06201,0.1757 0.07235,0.0724 0.186035,0.0724 h 0.72347 q 0.103353,0 0.165365,-0.0724 0.07235,-0.062 0.07235,-0.1757 v -0.68213 h 0.816487 v 0.68213 q 0,0.11369 0.07235,0.1757 0.07235,0.0724 0.1757,0.0724 h 0.72347 q 0.103353,0 0.1757,-0.0724 0.07235,-0.062 0.07235,-0.1757 v -0.68213 h 0.475423 q 0.3514,0 0.661459,-0.0207 0.320394,-0.031 0.620117,-0.14469 0.661458,-0.21704 1.126546,-0.81649 0.465088,-0.60978 0.465088,-1.47794 -0.01034,-0.71314 -0.382406,-1.28158 -0.361735,-0.57877 -1.064534,-0.77515 z m -1.601969,-3.0489 q 0.279052,0 0.485758,0.0413 0.217041,0.0413 0.392741,0.13436 0.279053,0.15502 0.423747,0.45475 0.144694,0.29972 0.144694,0.67179 0,0.57878 -0.3514,0.94051 -0.341065,0.36174 -0.919841,0.36174 h -2.511474 v -2.60449 h 2.335775 z m 0.971516,6.44921 q -0.175699,0.0827 -0.382405,0.10336 -0.206706,0.0207 -0.382406,0.0207 h -2.54248 v -2.97657 h 2.645833 q 0.682129,0 1.085205,0.40308 0.403076,0.39274 0.403076,1.0542 0,0.49609 -0.227376,0.86816 -0.217041,0.37207 -0.599447,0.5271 z"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.26458332px"
id="path4527" /></g></svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

12
www/img/icon-btc.svg Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Fondo" transform="matrix(0.0814603,0.0295859,-0.0295859,0.0814603,0.874237,-57.7095)">
<g transform="matrix(10.8453,-3.93895,3.93895,10.8453,217.833,629.321)">
<use xlink:href="#_Image1" x="7.557" y="5.56" width="22.579px" height="28.881px" transform="matrix(0.981703,0,0,0.995881,0,0)"/>
</g>
</g>
<defs>
<image id="_Image1" width="23px" height="29px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAdCAYAAABBsffGAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACiElEQVRIia3VPYhfRRQF8PM2myya1SxBcLUQP+JXYQpBkJUlGgu1EoRgIyxkUUQUEU0hKQ1io6K9YBRBC0Fj4UdhghYWURHBQtFI0oioWUVFs5vkZ/Hers/J/N9/F7zdO+/MuWfu3JmbbDAwg/PXw50YIzRVgfcmOYqnMY9mowaDObyE23AptmAT3vFvvIFpTOKadSfCgZ7IKbyI+7HUw+/puIs40SV+AJOD5cBr+NXoOI0bO/4HPfyPQfFekgX8PZDgczzZuV6N/esqD+7uHG4k1ppgsFuSzCXZVGBHkrw/gr+c5AVsH+f6ShytONuDi3ArnsJXFc4hbBsSv6+y6CNcUvB24kiFuzBUll0V7HDTND/0gaZpvkzySJIzBXe+Kq5tsdsL+M8kH48wMpvk9wK7YZTzuSRXFNjmJM/jGVzbmZjCTOd8puC/W3M9hTfHtNvPOIm38CPOFP//wo6a+G7n3sxf8MWYhP3Ei2hq1/SWJGUbvZ1kf5Lr057FniRX9/4vJTmU5HiSl5Mcb5pG6fpiHK64WSh4O/Eqzvbc7sOWitkEE7i3IvwZrqrwp/Fhj7eCJ0rS5TiG1/F1RfzZqpt27aMF91tc0Cc8NHA4Z8uS9NZN4r2Cv4z5Vc5EkukkJ5OcGmHwYW1v7+5EV5/ThSR3FNzNaQ81SbL27uKuJI8luSnJhTn3xfwpybHOhCQ3Jyln7HKS2aZplqo2tTPzt95WywsyKk7j8RG7XxM/UCx6TnsLVwaEV3BwnPA2/22vg9iqnf7f9/AT2sP+Bp9gL4YHj/ZduROvaKf8rg7fV5RnB7ZjdlBwRJIGl2Fr993fzXfWM903kOg6PIhPsfi/CFcSnTe2tr34BwJIUvoJiuf+AAAAAElFTkSuQmCC"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="57px" height="16px" viewBox="0 0 57 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>Group 2 Copy</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-2-Copy">
<rect id="Rectangle" stroke="#FBFBFB" x="0.5" y="0.5" width="56" height="15" rx="2"></rect>
<path d="M9.75390625,4.55761719 L7.41601562,4.55761719 L7.41601562,13 L5.48144531,13 L5.48144531,4.55761719 L3.20507812,4.55761719 L3.20507812,3.046875 L9.75390625,3.046875 L9.75390625,4.55761719 Z M15.6669922,8.60449219 L12.625,8.60449219 L12.625,11.4960938 L16.2685547,11.4960938 L16.2685547,13 L10.6972656,13 L10.6972656,3.046875 L16.2548828,3.046875 L16.2548828,4.55761719 L12.625,4.55761719 L12.625,7.10058594 L15.6669922,7.10058594 L15.6669922,8.60449219 Z M21.8808594,10.4160156 C21.8808594,10.0195293 21.7794606,9.7062186 21.5766602,9.47607422 C21.3738597,9.24592984 21.0104193,9.02148547 20.4863281,8.80273438 C19.4654897,8.44270653 18.6975937,8.03483301 18.1826172,7.57910156 C17.6676407,7.12337012 17.4101562,6.48535566 17.4101562,5.66503906 C17.4101562,4.84927978 17.7040986,4.18506116 18.2919922,3.67236328 C18.8798858,3.15966541 19.6295527,2.90332031 20.5410156,2.90332031 C21.5071663,2.90332031 22.2841767,3.17675508 22.8720703,3.72363281 C23.4599639,4.27051055 23.7447918,5.00422717 23.7265625,5.92480469 L23.7128906,5.96582031 L21.8398438,5.96582031 C21.8398438,5.45084378 21.7247733,5.06005993 21.4946289,4.79345703 C21.2644845,4.52685414 20.9352235,4.39355469 20.5068359,4.39355469 C20.1376935,4.39355469 19.850587,4.51546102 19.6455078,4.75927734 C19.4404287,5.00309367 19.3378906,5.30728984 19.3378906,5.671875 C19.3378906,6.00911627 19.4541004,6.28710828 19.6865234,6.50585938 C19.9189465,6.72461047 20.3177055,6.96386589 20.8828125,7.22363281 C21.8444058,7.54264482 22.5724259,7.9459611 23.0668945,8.43359375 C23.5613631,8.9212264 23.8085938,9.57746983 23.8085938,10.4023438 C23.8085938,11.2545616 23.5192086,11.9244767 22.9404297,12.4121094 C22.3616508,12.899742 21.5937548,13.1435547 20.6367188,13.1435547 C19.6933547,13.1435547 18.8867221,12.8803737 18.2167969,12.3540039 C17.5468717,11.8276341 17.2233072,11.0266981 17.2460938,9.95117188 L17.2597656,9.91015625 L19.1396484,9.91015625 C19.1396484,10.5345083 19.266112,10.9833971 19.519043,11.2568359 C19.7719739,11.5302748 20.1445288,11.6669922 20.6367188,11.6669922 C21.0514344,11.6669922 21.3624664,11.553061 21.5698242,11.3251953 C21.777182,11.0973296 21.8808594,10.7942727 21.8808594,10.4160156 Z M31.0478516,4.55761719 L28.7099609,4.55761719 L28.7099609,13 L26.7753906,13 L26.7753906,4.55761719 L24.4990234,4.55761719 L24.4990234,3.046875 L31.0478516,3.046875 L31.0478516,4.55761719 Z M39.0458984,13 L37.1181641,13 L33.9599609,6.64941406 L33.9189453,6.65625 L33.9189453,13 L31.9912109,13 L31.9912109,3.046875 L33.9189453,3.046875 L37.0771484,9.40429688 L37.1181641,9.39746094 L37.1181641,3.046875 L39.0458984,3.046875 L39.0458984,13 Z M45.546875,8.60449219 L42.5048828,8.60449219 L42.5048828,11.4960938 L46.1484375,11.4960938 L46.1484375,13 L40.5771484,13 L40.5771484,3.046875 L46.1347656,3.046875 L46.1347656,4.55761719 L42.5048828,4.55761719 L42.5048828,7.10058594 L45.546875,7.10058594 L45.546875,8.60449219 Z M53.3603516,4.55761719 L51.0224609,4.55761719 L51.0224609,13 L49.0878906,13 L49.0878906,4.55761719 L46.8115234,4.55761719 L46.8115234,3.046875 L53.3603516,3.046875 L53.3603516,4.55761719 Z" id="TESTNET" fill="#FFFFFF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="flask" transform="matrix(1.27778,0,0,1.27778,7.86111,8)">
<path d="M13.358,7.26C13.563,7.465 13.78,7.69 14.009,7.937C14.237,8.183 14.571,8.552 15.011,9.044C15.45,9.536 15.854,10.034 16.224,10.538C16.593,11.042 16.959,11.58 17.322,12.151C17.686,12.722 17.973,13.295 18.184,13.869C18.395,14.443 18.5,14.962 18.5,15.425C18.5,16.134 18.248,16.74 17.744,17.244C17.24,17.748 16.634,18 15.925,18L3.075,18C2.366,18 1.76,17.748 1.256,17.244C0.752,16.74 0.5,16.134 0.5,15.425C0.5,14.962 0.605,14.443 0.816,13.869C1.027,13.295 1.314,12.722 1.678,12.151C2.041,11.58 2.407,11.042 2.776,10.538C3.146,10.034 3.55,9.536 3.989,9.044C4.429,8.552 4.763,8.183 4.991,7.937C5.22,7.69 5.437,7.465 5.642,7.26L5.642,1.925L4.675,1.925C4.587,1.925 4.512,1.894 4.451,1.833C4.389,1.771 4.358,1.696 4.358,1.608L4.358,0.316C4.358,0.229 4.389,0.154 4.451,0.092C4.512,0.031 4.587,0 4.675,0L14.325,0C14.413,0 14.488,0.031 14.549,0.092C14.611,0.154 14.642,0.229 14.642,0.316L14.642,1.608C14.642,1.696 14.611,1.771 14.549,1.833C14.488,1.894 14.413,1.925 14.325,1.925L13.358,1.925L13.358,7.26ZM5,1.283L14,1.283L14,0.642L5,0.642L5,1.283ZM12.717,1.925L6.283,1.925L6.283,4.14C6.705,4.011 7.105,3.926 7.483,3.885C7.861,3.844 8.168,3.838 8.406,3.867C8.643,3.896 8.863,3.948 9.065,4.021C9.267,4.094 9.408,4.156 9.487,4.206C9.566,4.255 9.632,4.304 9.685,4.351C9.737,4.392 9.806,4.437 9.891,4.487C9.976,4.537 10.124,4.601 10.335,4.68C10.546,4.759 10.763,4.811 10.985,4.834C11.208,4.857 11.478,4.835 11.794,4.768C12.11,4.701 12.418,4.576 12.717,4.395L12.717,1.925ZM15.925,17.358C16.458,17.358 16.914,17.169 17.292,16.792C17.669,16.414 17.858,15.958 17.858,15.425C17.858,14.897 17.688,14.273 17.349,13.553C17.009,12.832 16.615,12.161 16.167,11.54C15.718,10.919 15.228,10.295 14.694,9.668C14.161,9.041 13.747,8.574 13.451,8.266C13.155,7.958 12.942,7.746 12.813,7.629C12.749,7.564 12.717,7.485 12.717,7.392L12.717,5.124C12.242,5.353 11.753,5.467 11.249,5.467C10.903,5.467 10.566,5.413 10.238,5.304C9.91,5.196 9.679,5.101 9.544,5.019C9.409,4.937 9.321,4.875 9.28,4.834C9.233,4.799 9.192,4.77 9.157,4.746C9.122,4.723 9.012,4.679 8.828,4.614C8.643,4.55 8.45,4.51 8.248,4.496C8.045,4.481 7.767,4.496 7.413,4.54C7.058,4.583 6.682,4.673 6.283,4.808L6.283,7.392C6.283,7.485 6.251,7.564 6.187,7.629C6.058,7.746 5.845,7.958 5.549,8.266C5.253,8.574 4.839,9.041 4.306,9.668C3.772,10.295 3.282,10.919 2.833,11.54C2.385,12.161 1.991,12.832 1.651,13.553C1.312,14.273 1.142,14.897 1.142,15.425C1.142,15.958 1.331,16.414 1.708,16.792C2.086,17.169 2.542,17.358 3.075,17.358L15.925,17.358ZM9.825,6.425C10.358,6.425 10.812,6.614 11.187,6.992C11.563,7.37 11.75,7.825 11.75,8.358C11.75,8.892 11.563,9.346 11.187,9.721C10.812,10.096 10.358,10.283 9.825,10.283C9.292,10.283 8.836,10.096 8.458,9.721C8.081,9.346 7.892,8.892 7.892,8.358C7.892,7.825 8.081,7.37 8.458,6.992C8.836,6.614 9.292,6.425 9.825,6.425ZM9.825,9.642C10.177,9.642 10.479,9.517 10.73,9.268C10.982,9.019 11.108,8.716 11.108,8.358C11.108,8.001 10.982,7.696 10.73,7.444C10.479,7.192 10.177,7.066 9.825,7.066C9.468,7.066 9.163,7.192 8.911,7.444C8.659,7.696 8.533,8.001 8.533,8.358C8.533,8.716 8.659,9.019 8.911,9.268C9.163,9.517 9.468,9.642 9.825,9.642ZM7.892,10.925C8.249,10.925 8.552,11.051 8.801,11.303C9.05,11.555 9.175,11.859 9.175,12.217C9.175,12.568 9.05,12.87 8.801,13.122C8.552,13.374 8.249,13.5 7.892,13.5C7.534,13.5 7.231,13.374 6.982,13.122C6.733,12.87 6.608,12.568 6.608,12.217C6.608,11.859 6.733,11.555 6.982,11.303C7.231,11.051 7.534,10.925 7.892,10.925ZM7.892,12.858C8.073,12.858 8.226,12.795 8.349,12.669C8.472,12.543 8.533,12.393 8.533,12.217C8.533,12.035 8.472,11.881 8.349,11.755C8.226,11.629 8.073,11.566 7.892,11.566C7.716,11.566 7.565,11.629 7.439,11.755C7.313,11.881 7.25,12.035 7.25,12.217C7.25,12.393 7.313,12.543 7.439,12.669C7.565,12.795 7.716,12.858 7.892,12.858ZM10.783,13.5C11.053,13.5 11.281,13.594 11.469,13.781C11.656,13.969 11.75,14.197 11.75,14.467C11.75,14.73 11.656,14.956 11.469,15.144C11.281,15.331 11.053,15.425 10.783,15.425C10.52,15.425 10.294,15.331 10.106,15.144C9.919,14.956 9.825,14.73 9.825,14.467C9.825,14.197 9.919,13.969 10.106,13.781C10.294,13.594 10.52,13.5 10.783,13.5ZM10.783,14.783C10.871,14.783 10.947,14.752 11.012,14.691C11.076,14.629 11.108,14.555 11.108,14.467C11.108,14.373 11.076,14.295 11.012,14.234C10.947,14.172 10.871,14.142 10.783,14.142C10.695,14.142 10.621,14.172 10.559,14.234C10.498,14.295 10.467,14.373 10.467,14.467C10.467,14.555 10.498,14.629 10.559,14.691C10.621,14.752 10.695,14.783 10.783,14.783Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show more