Merge branch 'wallet/sprint/21' into wallet/task/562
# Conflicts: # src/js/controllers/tab-home.controller.js
|
|
@ -9,7 +9,7 @@ module.exports = function(grunt) {
|
|||
pkg: grunt.file.readJSON('package.json'),
|
||||
exec: {
|
||||
get_nwjs_for_pkg: {
|
||||
command: 'if [ ! -d ./cache/0.19.5-pkg/osx64/nwjs.app ]; then cd ./cache; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output nwjs.zip; unzip nwjs.zip; mkdir -p ./0.19.5-pkg/osx64; cp -R ./nwjs-mas-v0.19.5-osx-x64/nwjs.app ./0.19.5-pkg/osx64/; fi'
|
||||
command: 'if [ ! -d ./cache/0.19.4/osx64/nwjs.app ]; then mkdir -p ./cache/0.19.4/osx64; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output ./cache/nwjs.zip; unzip ./cache/nwjs.zip -d ./cache; cp -R ./cache/nwjs-mas-v0.19.5-osx-x64/nwjs.app ./cache/0.19.4/osx64/; fi'
|
||||
},
|
||||
create_others_dist: {
|
||||
command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
|
||||
|
|
@ -296,10 +296,10 @@ module.exports = function(grunt) {
|
|||
},
|
||||
pkg: {
|
||||
options: {
|
||||
appName: '<%= pkg.nameCaseNoSpace %>',
|
||||
appName: '<%= pkg.title %>',
|
||||
platforms: ['osx64'],
|
||||
buildDir: './webkitbuilds/pkg',
|
||||
version: '0.19.5',
|
||||
version: '0.19.4',
|
||||
macIcns: './resources/<%= pkg.name %>/mac/pkg/app.icns',
|
||||
exeIco: './www/img/app/logo.ico',
|
||||
macPlist: {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,12 @@
|
|||
<config-file platform="ios" target="*-Info.plist" parent="UIStatusBarHidden"><true/></config-file>
|
||||
<config-file platform="ios" target="*-Info.plist" parent="UIViewControllerBasedStatusBarAppearance"><false/></config-file>
|
||||
<config-file target="*-Info.plist" parent="ITSAppUsesNonExemptEncryption"><false/></config-file>
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon24x24@2x.png" width="48" height="48" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon27.5x27.5@2x.png" width="55" height="55" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon44x44@2x.png" width="88" height="88" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon86x86@2x.png" width="172" height="172" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon98x98@2x.png" width="196" height="196" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-20.png" width="20" height="20" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-60@3x.png" width="180" height="180" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-60.png" width="60" height="60" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-60@2x.png" width="120" height="120" />
|
||||
|
|
@ -102,6 +108,7 @@
|
|||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-small@3x.png" width="87" height="87" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-50.png" width="50" height="50" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-50@2x.png" width="100" height="100" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-1024.png" width="1024" height="1024" />
|
||||
|
||||
<splash src="resources/*PACKAGENAME*/ios/splash/Default~iphone.png" width="320" height="480"/>
|
||||
<splash src="resources/*PACKAGENAME*/ios/splash/Default@2x~iphone.png" width="640" height="960"/>
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ ln -s ../resources/bitcoin.com/mac/pkg/build.cfg build.cfg
|
|||
rm build_mas.py
|
||||
ln -s ../resources/bitcoin.com/mac/pkg/build_mas.py build_mas.py
|
||||
|
||||
echo "Signing ${APP_NAME}"
|
||||
export APP_PATH="pkg/${APP_NAME}/osx64/${APP_NAME}"
|
||||
echo "Signing ${APP_FULLNAME}"
|
||||
export CURRENT_PATH=`pwd`
|
||||
export APP_PATH="pkg/${APP_FULLNAME}/osx64/${APP_FULLNAME}"
|
||||
export TMP_PATH="tmp"
|
||||
export DIST_PATH="dist"
|
||||
|
||||
|
|
@ -36,10 +37,16 @@ if [ ! -d $DIST_PATH ]; then
|
|||
mkdir $DIST_PATH
|
||||
fi
|
||||
|
||||
python build_mas.py -C build.cfg -O "${TMP_PATH}/${APP_NAME}.app" -I "${APP_PATH}.app" -P "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg"
|
||||
cd "${APP_PATH}.app/Contents/Versions"
|
||||
ln -s "55.0.2883.87" "Current"
|
||||
|
||||
cd $CURRENT_PATH
|
||||
chmod -vR 777 "${APP_PATH}.app/Contents"
|
||||
|
||||
python build_mas.py -C build.cfg -O "${TMP_PATH}/${APP_FULLNAME}.app" -I "${APP_PATH}.app" -P "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg"
|
||||
|
||||
echo "Signing Done"
|
||||
|
||||
echo "Done."
|
||||
|
||||
exit
|
||||
exit
|
||||
|
|
@ -511,7 +511,7 @@ msgid "Cannot Create Wallet"
|
|||
msgstr ""
|
||||
|
||||
#: src/js/services/profileService.js:442
|
||||
msgid "Cannot join the same wallet more that once"
|
||||
msgid "Cannot join the same wallet more than once"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/includes/bitpayCardsCard.html:2
|
||||
|
|
@ -2916,10 +2916,15 @@ msgid "Sweep"
|
|||
msgstr ""
|
||||
|
||||
#: www/views/includes/incomingDataMenu.html:89
|
||||
#: www/views/paperWallet.html:3
|
||||
msgctxt "List item"
|
||||
msgid "Sweep paper wallet"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/paperWallet.html:3
|
||||
msgctxt "Page title"
|
||||
msgid "Sweep Paper Wallet"
|
||||
msgstr ""
|
||||
|
||||
#: src/js/services/onGoingProcess.js:33
|
||||
msgid "Sweeping Wallet..."
|
||||
msgstr ""
|
||||
|
|
@ -3783,6 +3788,10 @@ msgstr ""
|
|||
msgid "Bitcoin Cash Games"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/includes/community.html:29
|
||||
msgid "Share the Wallet App"
|
||||
msgstr ""
|
||||
|
||||
#: src/js/services/bitcoincomService.js:28
|
||||
msgid "News"
|
||||
msgstr ""
|
||||
|
|
@ -3849,4 +3858,47 @@ msgstr ""
|
|||
|
||||
#: src/js/services/incomingData.js:129
|
||||
msgid "This invoice is no longer accepting payments"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/paperWallet.html:48
|
||||
msgid "No Bitcoin Cash wallet to transfer funds to found."
|
||||
msgstr ""
|
||||
|
||||
#: www/views/paperWallet.html:54
|
||||
msgid "No Bitcoin Cash found."
|
||||
msgstr ""
|
||||
|
||||
#: www/views/paperWallet.html:60
|
||||
msgid "Bitcoin Core found:"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/paperWallet.html:98
|
||||
msgid "No Bitcoin Core wallet to transfer funds to found."
|
||||
msgstr ""
|
||||
|
||||
#: www/views/paperWallet.html:104
|
||||
msgid "No Bitcoin Core found."
|
||||
|
||||
#: src/js/controllers/tab-scan.js:120
|
||||
msgid "Scan Failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/js/controllers/tab-scan.js:121
|
||||
msgid "Data not recognised."
|
||||
msgstr ""
|
||||
|
||||
#: src/js/controllers/tab-scan.js:121
|
||||
msgid "Unsupported"
|
||||
msgstr ""
|
||||
|
||||
#: src/js/controllers/tab-scan.js:121
|
||||
msgid "Testnet is not supported."
|
||||
msgstr ""
|
||||
|
||||
#: www/views/includes/incomingDataMenu.html:81
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/includes/incomingDataMenu.html:90
|
||||
msgid "Open in web browser"
|
||||
msgstr ""
|
||||
BIN
resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon27.5x27.5@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon86x86@2x.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon98x98@2x.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
resources/bitcoin.com/ios/icon/icon-1024.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
resources/bitcoin.com/ios/icon/icon-20.png
Normal file
|
After Width: | Height: | Size: 425 B |
|
|
@ -178,11 +178,17 @@ def codesign_app(config, args):
|
|||
|
||||
plistlib.writePlist(child_entitlements, tmp_child_entitlements)
|
||||
info('Child entitlements: %s' % tmp_child_entitlements)
|
||||
framework = glob(args.output, 'nwjs Framework.framework', returnOnFound=True)
|
||||
system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, framework))
|
||||
helperApp = glob(args.output, 'nwjs Helper.app', returnOnFound=True)
|
||||
system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, helperApp))
|
||||
|
||||
libffmpeg = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'libffmpeg.dylib', returnOnFound=True)
|
||||
system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, libffmpeg))
|
||||
libnode = glob(os.path.join(args.output, 'Contents/Versions/55.0.2883.87/nwjs Framework.framework/Versions/A'), 'libnode.dylib', returnOnFound=True)
|
||||
system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, libnode))
|
||||
|
||||
helperApp = glob(args.output, 'nwjs Helper.app', returnOnFound=True)
|
||||
system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, helperApp))
|
||||
framework = glob(args.output, 'nwjs Framework.framework', returnOnFound=True)
|
||||
system('codesign --deep --force --verbose --verify --sign "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, framework))
|
||||
|
||||
## sign parent app
|
||||
(_, tmp_parent_entitlements) = tempfile.mkstemp()
|
||||
if config.has_option('Sign', 'ParentEntitlements'):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<string>$GROUPID</string>
|
||||
<array>
|
||||
<string>299HJ3G3BP.com.bitcoin.mwallet.mac</string>
|
||||
</array>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ var modules = [
|
|||
'copayApp.controllers',
|
||||
'copayApp.directives',
|
||||
'copayApp.addons',
|
||||
'bitcoincom.directives'
|
||||
'bitcoincom.directives',
|
||||
'bitcoincom.services'
|
||||
];
|
||||
|
||||
var copayApp = window.copayApp = angular.module('copayApp', modules);
|
||||
|
|
@ -30,3 +31,4 @@ angular.module('copayApp.controllers', []);
|
|||
angular.module('copayApp.directives', []);
|
||||
angular.module('copayApp.addons', []);
|
||||
angular.module('bitcoincom.directives', []);
|
||||
angular.module('bitcoincom.services', []);
|
||||
|
|
|
|||
|
|
@ -21,28 +21,14 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
|
|||
});
|
||||
|
||||
$scope.sendTo = function() {
|
||||
$ionicHistory.removeBackView();
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
$timeout(function() {
|
||||
var to = '';
|
||||
if ($scope.addressbookEntry.coin == 'bch') {
|
||||
var a = 'bitcoincash:' + $scope.addressbookEntry.address;
|
||||
to = bitcoinCashJsService.readAddress(a).legacy;
|
||||
} else {
|
||||
to = $scope.addressbookEntry.address;
|
||||
}
|
||||
var stateParams = {
|
||||
data: $scope.addressbookEntry.address,
|
||||
toName: $scope.addressbookEntry.name,
|
||||
toEmail: $scope.addressbookEntry.email,
|
||||
coin: $scope.addressbookEntry.coin
|
||||
};
|
||||
|
||||
var stateParams = {
|
||||
toAddress: to,
|
||||
toName: $scope.addressbookEntry.name,
|
||||
toEmail: $scope.addressbookEntry.email,
|
||||
coin: $scope.addressbookEntry.coin
|
||||
};
|
||||
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 100);
|
||||
sendFlowService.start(stateParams);
|
||||
};
|
||||
|
||||
$scope.remove = function(addressbookEntry) {
|
||||
|
|
|
|||
|
|
@ -68,13 +68,14 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
function onBeforeEnter(event, data) {
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.popState();
|
||||
sendFlowService.state.pop();
|
||||
}
|
||||
console.log('amount onBeforeEnter after back sendflow ', sendFlowService.state);
|
||||
|
||||
initCurrencies();
|
||||
|
||||
passthroughParams = sendFlowService.getStateClone();
|
||||
passthroughParams = sendFlowService.state.getClone();
|
||||
|
||||
console.log('amount onBeforeEnter after back sendflow ', passthroughParams);
|
||||
|
||||
vm.fromWalletId = passthroughParams.fromWalletId;
|
||||
vm.toWalletId = passthroughParams.toWalletId;
|
||||
|
|
@ -214,7 +215,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
}
|
||||
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
function paste(value) {
|
||||
|
|
@ -467,11 +468,10 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
confirmData.thirdParty = vm.thirdParty;
|
||||
}
|
||||
|
||||
sendFlowService.pushState(confirmData);
|
||||
if (!confirmData.fromWalletId) {
|
||||
$state.transitionTo('tabs.paymentRequest.confirm', confirmData);
|
||||
} else {
|
||||
$state.transitionTo('tabs.send.review', confirmData);
|
||||
sendFlowService.goNext(confirmData);
|
||||
$scope.useSendMax = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ describe('amountController', function(){
|
|||
platformInfo,
|
||||
profileService,
|
||||
rateService,
|
||||
sendFlowService,
|
||||
shapeshiftService,
|
||||
$stateParams;
|
||||
|
||||
|
||||
|
|
@ -39,9 +41,11 @@ describe('amountController', function(){
|
|||
isIos: true
|
||||
};
|
||||
|
||||
profileService = jasmine.createSpyObj(['getWallets']);
|
||||
profileService = jasmine.createSpyObj(['getWallet', 'getWallets']);
|
||||
|
||||
rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']);
|
||||
sendFlowService = jasmine.createSpyObj(['getStateClone']);
|
||||
shapeshiftService = jasmine.createSpyObj(['shiftIt']);
|
||||
|
||||
$stateParams = {};
|
||||
|
||||
|
|
@ -61,6 +65,11 @@ describe('amountController', function(){
|
|||
stateName: 'ignoreme'
|
||||
};
|
||||
$ionicHistory.backView.and.returnValue(backView);
|
||||
|
||||
var wallet = {
|
||||
|
||||
};
|
||||
profileService.getWallet.and.returnValue(wallet);
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
||||
|
||||
|
|
@ -80,22 +89,25 @@ describe('amountController', function(){
|
|||
popupService: {},
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: {},
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: {},
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var data = {
|
||||
stateParams: {
|
||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||
}
|
||||
var sendFlowState = {
|
||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||
};
|
||||
$scope.$emit('$ionicView.beforeEnter', data);
|
||||
|
||||
expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||
expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
//expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||
//expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.controllers').controller('tourController',
|
||||
function($scope, $state, $log, $timeout, $filter, ongoingProcess, profileService, rateService, popupService, gettextCatalog, startupService, storageService, walletService, $q) {
|
||||
function ($scope, $state, $log, $timeout, $filter, ongoingProcess, configService, profileService, rateService, popupService, gettextCatalog, lodash, startupService, storageService, uxLanguage, walletService, $q) {
|
||||
|
||||
$scope.data = {
|
||||
index: 0
|
||||
|
|
@ -46,62 +46,90 @@ angular.module('copayApp.controllers').controller('tourController',
|
|||
creatingWallet = true;
|
||||
ongoingProcess.set('creatingWallet', true);
|
||||
$timeout(function() {
|
||||
profileService.createDefaultWallet(function(err, walletClients) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
uxLanguage.init(function(lang) {
|
||||
var rateCode = uxLanguage.getRateCode(lang);
|
||||
console.log("When Available: rateService");
|
||||
rateService.whenAvailable(function() {
|
||||
var alternatives = rateService.listAlternatives(true);
|
||||
|
||||
return $timeout(function() {
|
||||
$log.warn('Retrying to create default wallet.....:' + ++retryCount);
|
||||
if (retryCount > 3) {
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
popupService.showAlert(
|
||||
gettextCatalog.getString('Cannot Create Wallet'), err,
|
||||
function() {
|
||||
retryCount = 0;
|
||||
return $scope.createDefaultWallet();
|
||||
}, gettextCatalog.getString('Retry'));
|
||||
} else {
|
||||
return $scope.createDefaultWallet();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
var bchWallet = walletClients[0];
|
||||
var btcWallet = walletClients[1];
|
||||
var bchWalletId = bchWallet.credentials.walletId;
|
||||
var btcWalletId = btcWallet.credentials.walletId;
|
||||
|
||||
function createAddressPromise(wallet) {
|
||||
return $q(function(resolve, reject) {
|
||||
walletService.getAddress(wallet, true, function(e, addr) {
|
||||
if (e) reject(e);
|
||||
resolve(addr);
|
||||
var newAltCurrency = lodash.find(alternatives, {
|
||||
'isoCode': rateCode
|
||||
});
|
||||
|
||||
configService.whenAvailable(function(config) {
|
||||
var opts = {
|
||||
wallet: {
|
||||
settings: {
|
||||
alternativeName: newAltCurrency.name,
|
||||
alternativeIsoCode: newAltCurrency.isoCode,
|
||||
}
|
||||
}
|
||||
};
|
||||
configService.set(opts, function(err) {
|
||||
if (err) $log.warn(err);
|
||||
|
||||
profileService.createDefaultWallet(function(err, walletClients) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
|
||||
return $timeout(function() {
|
||||
$log.warn('Retrying to create default wallet.....:' + ++retryCount);
|
||||
if (retryCount > 3) {
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
popupService.showAlert(
|
||||
gettextCatalog.getString('Cannot Create Wallet'), err,
|
||||
function() {
|
||||
retryCount = 0;
|
||||
return $scope.createDefaultWallet();
|
||||
}, gettextCatalog.getString('Retry'));
|
||||
} else {
|
||||
return $scope.createDefaultWallet();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
;
|
||||
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
var bchWallet = walletClients[0];
|
||||
var btcWallet = walletClients[1];
|
||||
var bchWalletId = bchWallet.credentials.walletId;
|
||||
var btcWalletId = btcWallet.credentials.walletId;
|
||||
|
||||
function createAddressPromise(wallet) {
|
||||
return $q(function (resolve, reject) {
|
||||
walletService.getAddress(wallet, true, function (e, addr) {
|
||||
if (e) reject(e);
|
||||
resolve(addr);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function goToCollectEmail() {
|
||||
$state.go('onboarding.collectEmail', {
|
||||
bchWalletId: bchWalletId,
|
||||
btcWalletId: btcWalletId
|
||||
});
|
||||
}
|
||||
|
||||
var bchAddressPromise = createAddressPromise(bchWallet);
|
||||
var btcAddressPromise = createAddressPromise(btcWallet);
|
||||
ongoingProcess.set('generatingNewAddress', true);
|
||||
|
||||
$q.all([bchAddressPromise, btcAddressPromise]).then(function (addresses) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$state.go('tabs.home');
|
||||
}, function (e) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$log.warn(e);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), e);
|
||||
$state.go('tabs.home');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
$log.debug('Setting default currency : ' + newAltCurrency);
|
||||
});
|
||||
}
|
||||
|
||||
function goToCollectEmail() {
|
||||
$state.go('onboarding.collectEmail', {
|
||||
bchWalletId: bchWalletId,
|
||||
btcWalletId: btcWalletId
|
||||
});
|
||||
}
|
||||
|
||||
var bchAddressPromise = createAddressPromise(bchWallet);
|
||||
var btcAddressPromise = createAddressPromise(btcWallet);
|
||||
ongoingProcess.set('generatingNewAddress', true);
|
||||
|
||||
$q.all([bchAddressPromise, btcAddressPromise]).then(function(addresses) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$state.go('tabs.home');
|
||||
}, function(e) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$log.warn(e);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), e);
|
||||
$state.go('tabs.home');
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
});
|
||||
})
|
||||
}, 300);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
function onBeforeEnter(event, data) {
|
||||
console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.state);
|
||||
defaults = configService.getDefaults();
|
||||
sendFlowData = sendFlowService.getStateClone();
|
||||
sendFlowData = sendFlowService.state.getClone();
|
||||
originWalletId = sendFlowData.fromWalletId;
|
||||
satoshis = parseInt(sendFlowData.amount, 10);
|
||||
toAddress = sendFlowData.toAddress;
|
||||
|
|
@ -403,7 +403,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
}
|
||||
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
function handleDestinationAsAddress(address, originCoin) {
|
||||
|
|
@ -766,8 +766,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
((processName === 'signingTx') && vm.originWallet.m > 1) ||
|
||||
(processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal())
|
||||
) && !isOn) {
|
||||
// Show the popup
|
||||
vm.sendStatus = 'success';
|
||||
|
||||
// Clear the send flow service state
|
||||
sendFlowService.state.clear();
|
||||
|
||||
if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device.
|
||||
soundService.play('misc/payment_sent.mp3');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,22 +6,6 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
|
|||
|
||||
$scope.showMyAddress = showMyAddress;
|
||||
|
||||
function generateAddress(wallet, cb) {
|
||||
if (!wallet) return;
|
||||
walletService.getAddress(wallet, false, function(err, addr) {
|
||||
if (err) {
|
||||
popupService.showAlert(err);
|
||||
}
|
||||
return cb(addr);
|
||||
});
|
||||
}
|
||||
|
||||
function showToWallets() {
|
||||
$scope.toWallets = $scope.fromWallet.coin === 'btc' ? walletsBch : walletsBtc;
|
||||
$scope.onToWalletSelect($scope.toWallets[0]);
|
||||
$scope.singleToWallet = $scope.toWallets.length === 1;
|
||||
}
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
walletsBtc = profileService.getWallets({coin: 'btc'});
|
||||
walletsBch = profileService.getWallets({coin: 'bch'});
|
||||
|
|
@ -62,18 +46,7 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
|
|||
id: 'shapeshift'
|
||||
}
|
||||
};
|
||||
|
||||
// Starting new send flow, so ensure everything is reset
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.home').then(function() {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send').then(function() {
|
||||
$timeout(function () {
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 60);
|
||||
});
|
||||
});
|
||||
sendFlowService.start(stateParams);
|
||||
}
|
||||
|
||||
function showMyAddress() {
|
||||
|
|
|
|||
|
|
@ -121,9 +121,8 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
|||
};
|
||||
|
||||
$scope.startFreshSend = function() {
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
};
|
||||
sendFlowService.start();
|
||||
}
|
||||
|
||||
$scope.showUpdatePopup = function() {
|
||||
latestReleaseService.showUpdatePopup();
|
||||
|
|
@ -18,10 +18,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
$scope.displayBalanceAsFiat = true;
|
||||
|
||||
$scope.requestSpecificAmount = function() {
|
||||
sendFlowService.pushState({
|
||||
toWalletId: $scope.wallet.credentials.walletId
|
||||
sendFlowService.start({
|
||||
toWalletId: $scope.wallet.credentials.walletId,
|
||||
isRequestAmount: true
|
||||
});
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
};
|
||||
|
||||
$scope.setAddress = function(newAddr, copyAddress) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function($scope, $log, $timeout, scannerService, incomingData, $state, $ionicHistory, $rootScope, $ionicNavBarDelegate) {
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function(gettextCatalog, popupService, $scope, $log, $timeout, scannerService, incomingDataService, $state, $ionicHistory, $rootScope, $ionicNavBarDelegate) {
|
||||
|
||||
var scannerStates = {
|
||||
unauthorized: 'unauthorized',
|
||||
|
|
@ -111,7 +111,18 @@ angular.module('copayApp.controllers').controller('tabScanController', function(
|
|||
// Sometimes (testing in Chrome, when reading QR Code) data is an object
|
||||
// that has a string data.result.
|
||||
contents = contents.result || contents;
|
||||
incomingData.redir(contents);
|
||||
incomingDataService.redir(contents, function onError(err) {
|
||||
if (err) {
|
||||
var title = gettextCatalog.getString('Scan Failed');
|
||||
popupService.showAlert(title, err.message, function onAlertShown() {
|
||||
// Enable another scan since we won't receive incomingDataMenu.menuHidden
|
||||
activate();
|
||||
});
|
||||
} else {
|
||||
scannerService.resumePreview();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.$on('incomingDataMenu.menuHidden', function() {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, sendFlowService, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) {
|
||||
angular.module('copayApp.controllers').controller('tabSendController', function(bitcoinUriService, $scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, platformInfo, sendFlowService, gettextCatalog, configService, $ionicPopup, $ionicNavBarDelegate, clipboardService, incomingDataService) {
|
||||
var clipboardHasAddress = false;
|
||||
var clipboardHasContent = false;
|
||||
var originalList;
|
||||
|
|
@ -29,7 +29,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$scope.$on("$ionicView.enter", function(event, data) {
|
||||
|
||||
var stateParams = sendFlowService.getStateClone();
|
||||
var stateParams = sendFlowService.state.getClone();
|
||||
$scope.fromWallet = profileService.getWallet(stateParams.fromWalletId);
|
||||
|
||||
clipboardService.readFromClipboard(function(text) {
|
||||
|
|
@ -39,7 +39,9 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$scope.clipboardHasAddress = false;
|
||||
$scope.clipboardHasContent = false;
|
||||
if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr
|
||||
var parsed = bitcoinUriService.parse(text);
|
||||
console.log('parsed', parsed);
|
||||
if (parsed.isValid && parsed.publicAddress && parsed.coin === 'bch' && !parsed.testnet) { // CashAddr
|
||||
$scope.clipboardHasAddress = true;
|
||||
} else if ((text[0] === "1" || text[0] === "3" || text.substring(0, 3) === "bc1") && text.length >= 26 && text.length <= 35) { // Legacy Addresses
|
||||
$scope.clipboardHasAddress = true;
|
||||
|
|
@ -60,11 +62,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
});
|
||||
|
||||
$scope.findContact = function(search) {
|
||||
|
||||
if (incomingData.redir(search)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!search || search.length < 1) {
|
||||
$scope.list = originalList;
|
||||
$timeout(function() {
|
||||
|
|
@ -73,12 +70,16 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
return;
|
||||
}
|
||||
|
||||
var result = lodash.filter(originalList, function(item) {
|
||||
var val = item.name;
|
||||
return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
|
||||
var params = sendFlowService.state.getClone();
|
||||
params.data = search;
|
||||
sendFlowService.start(params, function onError() {
|
||||
var result = lodash.filter(originalList, function(item) {
|
||||
var val = item.name;
|
||||
return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
|
||||
});
|
||||
|
||||
$scope.list = result;
|
||||
});
|
||||
|
||||
$scope.list = result;
|
||||
};
|
||||
|
||||
var hasWallets = function() {
|
||||
|
|
@ -184,27 +185,18 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$log.debug('Got toAddress:' + toAddress + ' | ' + item.name);
|
||||
|
||||
var stateParams = sendFlowService.getStateClone();
|
||||
stateParams.toAddress = toAddress,
|
||||
var stateParams = sendFlowService.state.getClone();
|
||||
stateParams.toAddress = toAddress;
|
||||
stateParams.coin = item.coin;
|
||||
sendFlowService.pushState(stateParams);
|
||||
|
||||
if (!stateParams.fromWalletId) { // If we have no toAddress or fromWallet
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
} else {
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
}
|
||||
|
||||
sendFlowService.start(stateParams);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.startWalletToWalletTransfer = function() {
|
||||
console.log('startWalletToWalletTransfer()');
|
||||
var params = sendFlowService.getStateClone();
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.wallet-to-wallet', {
|
||||
fromWalletId: sendFlowService.fromWalletId
|
||||
});
|
||||
var params = sendFlowService.state.getClone();
|
||||
params.isWalletTransfer = true;
|
||||
sendFlowService.start(params);
|
||||
}
|
||||
|
||||
// This could probably be enhanced refactoring the routes abstract states
|
||||
|
|
@ -238,7 +230,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
});
|
||||
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.clear();
|
||||
sendFlowService.state.clear();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
|
||||
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingDataService, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
|
||||
|
||||
$scope.onScan = function(data) {
|
||||
if (!incomingData.redir(data)) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid data'));
|
||||
}
|
||||
incomingDataService.redir(data, function onError(err) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setScanFn = function(scanFn) {
|
||||
|
|
@ -16,8 +18,7 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
};
|
||||
|
||||
$scope.startFreshSend = function() {
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
sendFlowService.start();
|
||||
};
|
||||
|
||||
$scope.importInit = function() {
|
||||
|
|
@ -28,7 +29,6 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
};
|
||||
|
||||
$scope.chooseScanner = function() {
|
||||
sendFlowService.clear();
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
if (!isWindowsPhoneApp) {
|
||||
|
|
@ -38,10 +38,14 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
|
||||
scannerService.useOldScanner(function(err, contents) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
return;
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
} else {
|
||||
incomingDataService.redir(contents, function onError(err) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
incomingData.redir(contents);
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,27 +26,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
};
|
||||
|
||||
var setPendingTxps = function(txps) {
|
||||
|
||||
/* Uncomment to test multiple outputs */
|
||||
|
||||
// var txp = {
|
||||
// message: 'test multi-output',
|
||||
// fee: 1000,
|
||||
// createdOn: new Date() / 1000,
|
||||
// outputs: [],
|
||||
// wallet: $scope.wallet
|
||||
// };
|
||||
//
|
||||
// function addOutput(n) {
|
||||
// txp.outputs.push({
|
||||
// amount: 600,
|
||||
// toAddress: '2N8bhEwbKtMvR2jqMRcTCQqzHP6zXGToXcK',
|
||||
// message: 'output #' + (Number(n) + 1)
|
||||
// });
|
||||
// };
|
||||
// lodash.times(15, addOutput);
|
||||
// txps.push(txp);
|
||||
|
||||
if (!txps) {
|
||||
$scope.txps = [];
|
||||
return;
|
||||
|
|
@ -378,8 +357,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
});
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
sendFlowService.clear();
|
||||
|
||||
configService.whenAvailable(function (config) {
|
||||
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
|
||||
|
||||
|
|
@ -477,16 +454,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
}
|
||||
|
||||
$scope.goToSend = function() {
|
||||
sendFlowService.startSend({
|
||||
sendFlowService.start({
|
||||
fromWalletId: $scope.wallet.id
|
||||
});
|
||||
|
||||
// Go home first so that the Home tab works properly
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send');
|
||||
});
|
||||
|
||||
};
|
||||
$scope.goToReceive = function() {
|
||||
$state.go('tabs.home', {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, sendFlowService, configService, gettextCatalog, profileService, txFormatService) {
|
||||
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $state, sendFlowService, configService, gettextCatalog, profileService, txFormatService) {
|
||||
|
||||
var fromWalletId = '';
|
||||
var priceDisplayAsFiat = false;
|
||||
|
|
@ -12,31 +12,22 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu
|
|||
|
||||
function onBeforeEnter(event, data) {
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.popState();
|
||||
sendFlowService.state.pop();
|
||||
}
|
||||
console.log('walletSelector onBeforeEnter after back sendflow', sendFlowService.state);
|
||||
|
||||
$scope.params = sendFlowService.getStateClone();
|
||||
$scope.params = sendFlowService.state.getClone();
|
||||
|
||||
console.log('walletSelector onBeforeEnter after back sendflow', $scope.params);
|
||||
|
||||
var config = configService.getSync().wallet.settings;
|
||||
priceDisplayAsFiat = config.priceDisplay === 'fiat';
|
||||
unitDecimals = config.unitDecimals;
|
||||
unitsFromSatoshis = 1 / config.unitToSatoshi;
|
||||
|
||||
switch($state.current.name) {
|
||||
case 'tabs.send.wallet-to-wallet':
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets');
|
||||
break;
|
||||
case 'tabs.send.destination':
|
||||
if ($scope.params.fromWalletId && !$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Send');
|
||||
}
|
||||
// nop
|
||||
if ($scope.params.isWalletTransfer) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets');
|
||||
} else if (!$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Send');
|
||||
}
|
||||
|
||||
$scope.coin = false; // Wallets to show (for destination screen or contacts)
|
||||
|
|
@ -99,21 +90,12 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu
|
|||
$scope.requestAmountSecondary = fiatAmount;
|
||||
$scope.requestCurrencySecondary = fiatCurrrency;
|
||||
}
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getNextStep(params) {
|
||||
if (!params.toWalletId && !params.toAddress) { // If we have no toAddress or fromWallet
|
||||
return 'tabs.send.destination';
|
||||
} else if (!params.amount) { // If we have no amount
|
||||
return 'tabs.send.amount';
|
||||
} else { // If we do have them
|
||||
return 'tabs.send.review';
|
||||
}
|
||||
}
|
||||
|
||||
function handleThirdPartyIfShapeshift() {
|
||||
console.log($scope.thirdParty, $scope.coin);
|
||||
if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the
|
||||
|
|
@ -191,20 +173,17 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu
|
|||
|
||||
|
||||
$scope.useWallet = function(wallet) {
|
||||
var params = sendFlowService.getStateClone();
|
||||
var params = sendFlowService.state.getClone();
|
||||
if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from
|
||||
params.fromWalletId = wallet.id;
|
||||
} else { // we're on the destination screen, set wallet to send to
|
||||
params.toWalletId = wallet.id;
|
||||
}
|
||||
sendFlowService.pushState(params);
|
||||
var nextStep = getNextStep(params);
|
||||
console.log('walletSelector nextStep', nextStep);
|
||||
$state.transitionTo(nextStep, $scope.params);
|
||||
sendFlowService.goNext(params);
|
||||
};
|
||||
|
||||
$scope.goBack = function() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives')
|
||||
.directive('incomingDataMenu', function($timeout, $rootScope, $state, externalLinkService) {
|
||||
.directive('incomingDataMenu', function($timeout, $rootScope, $state, externalLinkService, sendFlowService, bitcoinCashJsService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'views/includes/incomingDataMenu.html',
|
||||
link: function(scope, element, attrs) {
|
||||
$rootScope.$on('incomingDataMenu.showMenu', function(event, data) {
|
||||
$timeout(function() {
|
||||
scope.data = data.data;
|
||||
scope.type = data.type;
|
||||
scope.showMenu = true;
|
||||
scope.https = false;
|
||||
scope.data = data;
|
||||
|
||||
if (scope.type === 'url') {
|
||||
if (scope.data.indexOf('https://') === 0) {
|
||||
scope.https = true;
|
||||
}
|
||||
if (scope.data.parsed.privateKey) {
|
||||
scope.type = "privateKey";
|
||||
} else if (scope.data.parsed.url) {
|
||||
scope.type = "url";
|
||||
} else if (scope.data.parsed.publicAddress) {
|
||||
scope.type = "bitcoinAddress";
|
||||
var prefix = scope.data.parsed.isTestnet ? 'bchtest:' : 'bitcoincash:';
|
||||
scope.data.toAddress = (prefix + scope.data.parsed.publicAddress.cashAddr) || scope.data.parsed.publicAddress.legacy || scope.data.parsed.publicAddress.bitpay;
|
||||
} else {
|
||||
scope.type = "text";
|
||||
}
|
||||
|
||||
scope.showMenu = true;
|
||||
});
|
||||
});
|
||||
scope.hide = function() {
|
||||
|
|
@ -28,18 +33,9 @@ angular.module('copayApp.directives')
|
|||
externalLinkService.open(url);
|
||||
};
|
||||
scope.sendPaymentToAddress = function(bitcoinAddress) {
|
||||
var noPrefixInAddress = 0;
|
||||
if (bitcoinAddress.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
scope.showMenu = false;
|
||||
$state.go('tabs.send').then(function() {
|
||||
$timeout(function() {
|
||||
$state.transitionTo('tabs.send.amount', {
|
||||
toAddress: bitcoinAddress,
|
||||
noPrefix: noPrefixInAddress
|
||||
});
|
||||
}, 50);
|
||||
sendFlowService.start({
|
||||
data: bitcoinAddress
|
||||
});
|
||||
};
|
||||
scope.addToAddressBook = function(bitcoinAddress) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function($interval, shapeshiftApiService, profileService, incomingData, ongoingProcess) {
|
||||
angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function($interval, shapeshiftApiService, profileService, incomingDataService, ongoingProcess) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
|
|
@ -111,7 +111,8 @@ angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function
|
|||
orderId: $scope.depositInfo.orderId
|
||||
};
|
||||
|
||||
if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
// How to handle this
|
||||
if (incomingDataService.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
416
src/js/services/bitcoin-uri.service.js
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
'use strict';
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('bitcoinUriService', bitcoinUriService);
|
||||
|
||||
function bitcoinUriService(bitcoinCashJsService, bwcService, $log) {
|
||||
var bch = bitcoinCashJsService.getBitcoinCashJs();
|
||||
var bitcore = bwcService.getBitcore();
|
||||
|
||||
var service = {
|
||||
parse: parse
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function bitpayAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var BitpayFormat = Address.BitpayFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var result = null;
|
||||
if (address[0] == 'C') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'pubkeyhash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'H') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'scripthash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var prefixed = 'bitcoincash:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnTestnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var testnet = bch.Networks.testnet;
|
||||
|
||||
var prefixed = 'bchtest:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function infoFromWalletImportText(data) {
|
||||
var split = data.split('|');
|
||||
// Copay seems to use extra parameter for coin.
|
||||
if (split.length < 5 || split.length > 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = parseInt(split[0], 10);
|
||||
if (isNaN(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = split[1];
|
||||
var network = split[2];
|
||||
if (!(network === 'livenet' || network === 'testnet')) {
|
||||
return null;
|
||||
}
|
||||
var isTestnet = network === 'testnet';
|
||||
|
||||
var derivationPath = split[3];
|
||||
if (!/^m\/\d+'\/\d+'\/\d+'$/.test(derivationPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hasPassphraseText = split[4];
|
||||
if (!(hasPassphraseText === 'true' || hasPassphraseText === 'false')) {
|
||||
return null;
|
||||
}
|
||||
var hasPassphrase = hasPassphraseText === 'true';
|
||||
|
||||
var coin; // Intentionally undefined as may not be present
|
||||
if (split.length > 5) {
|
||||
var coinText = split[5];
|
||||
if (!(coinText === 'bch' || coinText === 'btc')) {
|
||||
return null;
|
||||
}
|
||||
coin = coinText;
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
data: data,
|
||||
isTestnet: isTestnet,
|
||||
derivationPath: derivationPath,
|
||||
hasPassphrase: hasPassphrase,
|
||||
coin: coin
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
For parsing:
|
||||
BIP21
|
||||
BIP72
|
||||
|
||||
returns:
|
||||
{
|
||||
amount: '',
|
||||
amountInSatoshis: 0,
|
||||
bareUrl: '',
|
||||
coin: '',
|
||||
copayInvitation: '',
|
||||
import: { // testnet info in root, coin info in root if available
|
||||
data: '',
|
||||
derivationPath: '',
|
||||
hasPassphrase: false,
|
||||
type: 1,
|
||||
},
|
||||
isValid: false,
|
||||
label: '',
|
||||
message: '',
|
||||
other: {
|
||||
somethingIDontUnderstand: 'Its value'
|
||||
},
|
||||
privateKey: {
|
||||
encrypted: '',
|
||||
wif: ''
|
||||
}'',
|
||||
publicAddress: {
|
||||
bitpay: '',
|
||||
cashAddr: '',
|
||||
legacy: '',
|
||||
},
|
||||
req: {
|
||||
"req-param0": '',
|
||||
"req-param1": ''
|
||||
},
|
||||
testnet: false,
|
||||
url: '' // For BIP70
|
||||
}
|
||||
|
||||
Only fields that are present in the data are defined in the returned object. Both privateKey and publicAddress only have 1 field defined, if they exist at all.
|
||||
The exception to this is the coin property, which is determined from other data, such as the prefix or address type.
|
||||
|
||||
*/
|
||||
|
||||
function parse(data) {
|
||||
var parsed = {
|
||||
isValid: false
|
||||
};
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Identify prefix
|
||||
var trimmed = data.trim();
|
||||
var colonSplit = /^([\w-]*):?(.*)$/.exec(trimmed);
|
||||
if (!colonSplit) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var addressAndParams = '';
|
||||
var preColonLower = colonSplit[1].toLowerCase();
|
||||
if (preColonLower === 'bitcoin') {
|
||||
parsed.coin = 'btc';
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is btc');
|
||||
|
||||
} else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.test = false;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (/^(?:bchtest)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.isTestnet = true;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (colonSplit[2] === '') {
|
||||
// No colon and no coin specifier.
|
||||
addressAndParams = colonSplit[1].trim();
|
||||
console.log('No prefix.');
|
||||
|
||||
} else if (/^https?$/.test(colonSplit[1])) { // Plain URL
|
||||
addressAndParams = trimmed;
|
||||
|
||||
} else if (colonSplit[2].indexOf('|') == 0) { // Import
|
||||
addressAndParams = trimmed
|
||||
} else {
|
||||
// Something we don't recognise
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Remove erroneous leading slashes
|
||||
//var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams);
|
||||
var leadingSlashes = /^\/*(.*)$/.exec(addressAndParams);
|
||||
if (!leadingSlashes) {
|
||||
return parsed;
|
||||
}
|
||||
addressAndParams = leadingSlashes[1];
|
||||
|
||||
var questionMarkSplit = /^([^\?]*)\??([^\?]*)$/.exec(addressAndParams);
|
||||
if (!questionMarkSplit) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var address = questionMarkSplit[1];
|
||||
var params = questionMarkSplit[2];
|
||||
|
||||
if (params.length > 0) {
|
||||
var paramsSplit = params.split('&');
|
||||
var others;
|
||||
var req;
|
||||
var paramCount = paramsSplit.length;
|
||||
for(var i = 0; i < paramCount; i++) {
|
||||
var param = paramsSplit[i];
|
||||
var valueSplit = param.split('=');
|
||||
if (valueSplit.length !== 2) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var key = valueSplit[0];
|
||||
var value = valueSplit[1];
|
||||
var decodedValue = decodeURIComponent(value);
|
||||
switch(key) {
|
||||
case 'amount':
|
||||
var amount = parseFloat(decodedValue);
|
||||
if (amount) { // Checking for NaN, or no numbers at all etc. & convert to satoshi
|
||||
parsed.amount = decodedValue; // Need to check if a currency is precised
|
||||
parsed.amountInSatoshis = amount * 100000000
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'label':
|
||||
parsed.label = decodedValue;
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
parsed.message = decodedValue;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
// Could use a more comprehesive regex to test URL validity, but then how would we know
|
||||
// which part of the validation it failed?
|
||||
if (decodedValue.startsWith('https://')) {
|
||||
parsed.url = decodedValue;
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (key.startsWith('req-')) {
|
||||
req = req || {};
|
||||
req[key] = decodedValue;
|
||||
} else {
|
||||
others = others || {};
|
||||
others[key] = decodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
parsed.others = others;
|
||||
parsed.req = req;
|
||||
|
||||
|
||||
if (address) {
|
||||
var addressLowerCase = address.toLowerCase();
|
||||
var copayInvitationRe = /^[0-9A-HJ-NP-Za-km-z]{70,80}$/;
|
||||
//var legacyRe = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
//var legacyTestnetRe = /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
var privateKeyEncryptedRe = /^6P[1-9A-HJ-NP-Za-km-z]{56}$/;
|
||||
var privateKeyForUncompressedPublicKeyRe = /^5[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForUncompressedPublicKeyTestnetRe = /^9[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForCompressedPublicKeyRe = /^[KL][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var privateKeyForCompressedPublicKeyTestnetRe = /^[c][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var urlRe = /^https?:\/\/.+/;
|
||||
|
||||
var bitpayAddrMainnet = bitpayAddrOnMainnet(address);
|
||||
var cashAddrTestnet = cashAddrOnTestnet(addressLowerCase);
|
||||
var cashAddrMainnet = cashAddrOnMainnet(addressLowerCase);
|
||||
var importInfo = infoFromWalletImportText(address);
|
||||
var privateKey = '';
|
||||
|
||||
if (parsed.isTestnet && cashAddrTestnet) {
|
||||
parsed.address = addressLowerCase;
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (cashAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'livenet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'testnet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitpayAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
bitpay: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (copayInvitationRe.test(address) ) {
|
||||
parsed.copayInvitation = address;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyRe.test(address) || privateKeyForCompressedPublicKeyRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyTestnetRe.test(address) || privateKeyForCompressedPublicKeyTestnetRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'testnet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyEncryptedRe.test(address)) {
|
||||
parsed.privateKey = { encrypted: address };
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (urlRe.test(address)) {
|
||||
parsed.bareUrl = trimmed;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (importInfo) {
|
||||
parsed.import = {
|
||||
type: importInfo.type,
|
||||
data: importInfo.data,
|
||||
derivationPath: importInfo.derivationPath,
|
||||
hasPassphrase: importInfo.hasPassphrase
|
||||
};
|
||||
parsed.coin = importInfo.coin;
|
||||
parsed.isTestnet = importInfo.isTestnet;
|
||||
parsed.isValid = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
parsed.isValid = !!parsed.url; // BIP72
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
429
src/js/services/bitcoin-uri.service.spec.js
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
describe('bitcoinUriService', function() {
|
||||
var bitcoinUriService;
|
||||
|
||||
beforeEach(function() {
|
||||
module('bitcoinCashJsModule');
|
||||
module('bitcoincom.services');
|
||||
module('bwcModule');
|
||||
|
||||
inject(function($injector){
|
||||
bitcoinUriService = $injector.get('bitcoinUriService');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:?r=https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:?r=https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with extended params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc?unknown=something&mystery=Melton%20probang&req-one=ichi&req-beta=Ni%20san');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.others.mystery).toBe('Melton probang');
|
||||
expect(parsed.others.unknown).toBe('something');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc');
|
||||
expect(parsed.req['req-beta']).toBe('Ni san');
|
||||
expect(parsed.req['req-one']).toBe('ichi');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with invalid amount', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qq0knhwj4d5zy3kdph24w6etq58vwzua6sm7lhcmuk?amount=three');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin testnet address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin uri', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with encoded label', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT?label=Mr.%20Smith');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Mr. Smith');
|
||||
expect(parsed.publicAddress.legacy).toBe('1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu?amount=20.3&label=Luke-Jr&message=Donation%20for%20project%20xyz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.amount).toBe('20.3');
|
||||
expect(parsed.amountInSatoshis).toBe(2030000000);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Luke-Jr');
|
||||
expect(parsed.publicAddress.legacy).toBe('12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu');
|
||||
expect(parsed.message).toBe('Donation for project xyz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:/1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin://18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin: 19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitpay without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.bitpay).toBe('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr testnet with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr uppercase', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('BITCOINCASH:QZZG9NMC5VX8GAP6XFATX3TWNSDN2YRMCSSULSMY44');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzzg9nmc5vx8gap6xfatx3twnsdn2yrmcssulsmy44');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with dash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:/qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash://qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash: qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('cashAddr with space on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest: qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('copay invitation', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.copayInvitation).toBe('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
});
|
||||
|
||||
|
||||
it ('import BCH wallet no password', function() {
|
||||
var parsed = bitcoinUriService.parse("1|suggest route obvious broccoli good position hidden tone history around final lobster|livenet|m/44'/0'/0'|false");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('suggest route obvious broccoli good position hidden tone history around final lobster');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
it ('import BCH wallet with passphrase', function() {
|
||||
var parsed = bitcoinUriService.parse("1|fringe hazard all hobby trap myth fire stand sock empty soon east|livenet|m/44'/0'/0'|true");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('fringe hazard all hobby trap myth fire stand sock empty soon east');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(true);
|
||||
});
|
||||
|
||||
it ('import BTC wallet testnet', function() {
|
||||
// From copay
|
||||
var parsed = bitcoinUriService.parse("1|cat wealth column firm wet sauce tornado era feature monster click eyebrow|testnet|m/44'/1'/0'|false|btc");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('cat wealth column firm wet sauce tornado era feature monster click eyebrow');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/1'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
// Invalid addresses from https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md
|
||||
it('invalid cashAddr style 1', function() {
|
||||
var parsed = bitcoinUriService.parse('prefix:x64nx6hz');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 2', function() {
|
||||
var parsed = bitcoinUriService.parse('p:gpf8m4h7');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 3', function() {
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 4', function() {
|
||||
var parsed = bitcoinUriService.parse('bchtest:testnetaddress4d6njnut');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 5', function() {
|
||||
var parsed = bitcoinUriService.parse('bchreg:555555555555555555555555555555555555555555555udxmlmrz');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('non-string', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse([1, 2, 3, 4]);
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key encrypted with BIP38', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.encrypted).toBe('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTu');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMM');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTTwx');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcC');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('URL only, http', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('http://paperwallet.bitcoin.com');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('http://paperwallet.bitcoin.com');
|
||||
});
|
||||
|
||||
it('URL only, https with query', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('https://purse.io/?one=two&three=four');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('https://purse.io/?one=two&three=four');
|
||||
});
|
||||
|
||||
});
|
||||
79
src/js/services/incoming-data.service.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* incomingDataService is an intermediate to redirect either to the sendFlow
|
||||
* or to import/join a wallet.
|
||||
*/
|
||||
angular.module('copayApp.services').factory('incomingDataService', function(bitcoinUriService, $log, $state, $rootScope, scannerService, sendFlowService, gettextCatalog) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, cbError) {
|
||||
var parsed = bitcoinUriService.parse(data);
|
||||
|
||||
console.log(parsed);
|
||||
$log.debug(parsed);
|
||||
|
||||
|
||||
if (parsed.isValid) {
|
||||
if (parsed.isTestnet) {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Testnet is not supported.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
} else {
|
||||
scannerService.pausePreview();
|
||||
|
||||
/**
|
||||
* Strategy for the action
|
||||
*/
|
||||
if (parsed.copayInvitation) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
} else if (parsed.import) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
!parsed.isValid
|
||||
|| parsed.privateKey
|
||||
|| (sendFlowService.state.isEmpty() && !parsed.url && !parsed.amount)
|
||||
) {
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
} else {
|
||||
var state = sendFlowService.state.getClone();
|
||||
state.data = data;
|
||||
|
||||
sendFlowService.start(state, function onError(err) {
|
||||
/**
|
||||
* OnError, open the menu (link not validated)
|
||||
*/
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Data not recognised.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, serviceId, serviceData) {
|
||||
var originalAddress = null;
|
||||
var noPrefixInAddress = 0;
|
||||
|
||||
if (data.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
|
||||
if (typeof(data) == 'string' && !(/^bitcoin(cash)?:\?r=[\w+]/).exec(data) && (data.toLowerCase().indexOf('bitcoincash:') >= 0 || data[0] == 'q' || data[0] == 'p' || data[0] == 'C' || data[0] == 'H')) {
|
||||
try {
|
||||
noPrefixInAddress = 0;
|
||||
|
||||
if (data[0] == 'p' || data[0] == 'q') {
|
||||
data = 'bitcoincash:' + data;
|
||||
}
|
||||
var paramString = '';
|
||||
if (data.indexOf('?') >= 0) {
|
||||
paramString = data.substring(data.indexOf('?'));
|
||||
data = data.substring(0, data.indexOf('?'));
|
||||
}
|
||||
|
||||
if (data.indexOf('BITCOINCASH:') >= 0) {
|
||||
data = data.toLowerCase();
|
||||
}
|
||||
originalAddress = data.replace('bitcoincash:', '');
|
||||
var legacyAddress = bitcoinCashJsService.readAddress(data).legacy;
|
||||
data = 'bitcoincash:' + legacyAddress + paramString;
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
$log.debug('Processing incoming data: ' + data);
|
||||
|
||||
function sanitizeUri(data) {
|
||||
// Fixes when a region uses comma to separate decimals
|
||||
var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
|
||||
var match = regex.exec(data);
|
||||
if (!match || match.length === 0) {
|
||||
return data;
|
||||
}
|
||||
var value = match[0].replace(',', '.');
|
||||
var newUri = data.replace(regex, value);
|
||||
|
||||
// mobile devices, uris like copay://glidera
|
||||
newUri.replace('://', ':');
|
||||
|
||||
return newUri;
|
||||
}
|
||||
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) return;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
function checkPrivateKey(privateKey) {
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function goSend(addr, amount, message, coin, serviceId, serviceData) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
// Timeout is required to enable the "Back" button
|
||||
$timeout(function() {
|
||||
var params = sendFlowService.getStateClone();
|
||||
|
||||
if (amount) {
|
||||
params.amount = amount;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
params.toAddress = addr;
|
||||
params.displayAddress = originalAddress ? originalAddress : addr;
|
||||
}
|
||||
|
||||
if (coin) {
|
||||
params.coin = coin;
|
||||
}
|
||||
|
||||
if (noPrefixInAddress) {
|
||||
params.noPrefixInAddress = noPrefixInAddress;
|
||||
}
|
||||
|
||||
if (serviceId) {
|
||||
params.thirdParty = [];
|
||||
params.thirdParty.id = serviceId;
|
||||
params.thirdParty.data = serviceData;
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
} else {
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
// data extensions for Payment Protocol with non-backwards-compatible request
|
||||
if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
|
||||
var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
|
||||
data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
|
||||
if (coin == 'bch') {
|
||||
payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
|
||||
if (err) {
|
||||
var message = err.toString();
|
||||
if (typeof err.data === 'string') {
|
||||
// i.e. 'This invoice is no longer accepting payments'
|
||||
message = gettextCatalog.getString(err.data);
|
||||
}
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), message)
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
data = sanitizeUri(data);
|
||||
|
||||
// Bitcoin URL
|
||||
if (bitcore.URI.isValid(data)) {
|
||||
var coin = 'btc';
|
||||
var parsed = new bitcore.URI(data);
|
||||
|
||||
var addr = parsed.address ? parsed.address.toString() : '';
|
||||
var message = parsed.message;
|
||||
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
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, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
return true;
|
||||
|
||||
// Cash URI with bitcoin (btc) 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, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
}
|
||||
);
|
||||
return true;
|
||||
// Plain URL
|
||||
} else if (/^https?:\/\//.test(data)) {
|
||||
payproService.getPayProDetails(data, coin, function(err, details) {
|
||||
if (err) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'url'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
handlePayPro(details);
|
||||
return true;
|
||||
});
|
||||
// Plain Address
|
||||
} else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'bitcoinAddress'
|
||||
});
|
||||
} else {
|
||||
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) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.glidera', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.coinbase', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// BitPayCard Authentication
|
||||
} else if (data && data.indexOf(appConfigService.name + '://') === 0) {
|
||||
|
||||
// Disable BitPay Card
|
||||
if (!appConfigService._enabledExtensions.debitcard) return false;
|
||||
|
||||
var secret = getParameterByName('secret', data);
|
||||
var email = getParameterByName('email', data);
|
||||
var otp = getParameterByName('otp', data);
|
||||
var reason = getParameterByName('r', data);
|
||||
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
switch (reason) {
|
||||
default:
|
||||
case '0':
|
||||
/* For BitPay card binding */
|
||||
$state.transitionTo('tabs.bitpayCardIntro', {
|
||||
secret: secret,
|
||||
email: email,
|
||||
otp: otp
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
// Join
|
||||
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// Old join
|
||||
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
} else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'privateKey'
|
||||
});
|
||||
} else if (data && ((data.substring(0, 2) == '1|') || (data.substring(0, 2) == '2|') || (data.substring(0, 2) == '3|'))) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'text'
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function goToAmountPage(toAddress, coin) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
$timeout(function() {
|
||||
var stateParams = {
|
||||
toAddress: toAddress,
|
||||
displayAddress: toAddress,
|
||||
coin: coin,
|
||||
noPrefix: 1
|
||||
};
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function handlePayPro(payProData, coin) {
|
||||
|
||||
console.log(payProData);
|
||||
|
||||
var toAddr = payProData.toAddress;
|
||||
var amount = payProData.amount;
|
||||
var paymentUrl = payProData.url;
|
||||
var expires = payProData.expires;
|
||||
var time = payProData.time;
|
||||
|
||||
if (coin === 'bch') {
|
||||
var displayAddr = payProData.outputs[0].address;
|
||||
toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
|
||||
amount = payProData.outputs[0].amount;
|
||||
paymentUrl = payProData.paymentUrl;
|
||||
expires = Math.floor(new Date(expires).getTime() / 1000)
|
||||
time = Math.ceil(new Date(time).getTime() / 1000)
|
||||
}
|
||||
|
||||
var name = payProData.domain;
|
||||
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
amount: amount,
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: time,
|
||||
displayAddress: displayAddr,
|
||||
toAddress: toAddr,
|
||||
url: paymentUrl,
|
||||
verified: true
|
||||
};
|
||||
|
||||
var stateParams = {
|
||||
amount: thirdPartyData.amount,
|
||||
toAddress: thirdPartyData.toAddress,
|
||||
coin: coin,
|
||||
thirdParty: thirdPartyData
|
||||
};
|
||||
|
||||
// fee
|
||||
if (thirdPartyData.requiredFeeRate) {
|
||||
stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
|
||||
}
|
||||
|
||||
// This does not make sense, thirdPartyData gets added by stateParams below
|
||||
//sendFlowService.pushState(thirdPartyData);
|
||||
|
||||
scannerService.pausePreview();
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
}).then(function() {
|
||||
$timeout(function() {
|
||||
sendFlowService.pushState(stateParams); // Need to do more here
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingData, appConfigService) {
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingDataService, appConfigService) {
|
||||
var root = {};
|
||||
|
||||
var handleOpenURL = function(args) {
|
||||
|
|
@ -23,9 +23,12 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
|
|||
|
||||
document.addEventListener('handleopenurl', handleOpenURL, false);
|
||||
|
||||
if (!incomingData.redir(url)) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
}
|
||||
incomingDataService.redir(url, function onError(err) {
|
||||
if (err) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var handleResume = function() {
|
||||
|
|
|
|||
85
src/js/services/send-flow-router.service.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowRouterService', sendFlowRouterService);
|
||||
|
||||
function sendFlowRouterService(
|
||||
sendFlowStateService
|
||||
, $state, $ionicHistory, $timeout
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack,
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start new send flow
|
||||
*/
|
||||
function start() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
if (state.isRequestAmount) {
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
} else {
|
||||
if ($state.current.name != 'tabs.send') {
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send').then(function () {
|
||||
$timeout(function () {
|
||||
goNext();
|
||||
}, 60);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
goNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next page
|
||||
* Routing strategy : https://bitcoindotcom.atlassian.net/wiki/x/BQDWKQ
|
||||
*/
|
||||
function goNext() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
var needsDestination = !state.toWalletId && !state.toAddress;
|
||||
var needsOrigin = !state.fromWalletId;
|
||||
var needsAmount = !state.amount && !state.sendMax;
|
||||
|
||||
if (needsDestination) {
|
||||
if (!state.isWalletTransfer && !state.thirdParty) {
|
||||
$state.go('tabs.send');
|
||||
return;
|
||||
} else if (!needsOrigin) {
|
||||
$state.go('tabs.send.destination');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsOrigin) {
|
||||
$state.go('tabs.send.origin');
|
||||
} else if (needsAmount) {
|
||||
$state.go('tabs.send.amount');
|
||||
} else {
|
||||
$state.go('tabs.send.review');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous page
|
||||
*/
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -3,14 +3,13 @@
|
|||
(function(){
|
||||
|
||||
angular
|
||||
.module('copayApp.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowStateService', sendFlowStateService);
|
||||
|
||||
function sendFlowService($log) {
|
||||
function sendFlowStateService($log) {
|
||||
|
||||
var service = {
|
||||
// A separate state variable so we can ensure it is cleared of everything,
|
||||
// even other properties added that this service does not know about. (such as "coin")
|
||||
// Variables
|
||||
state: {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
|
|
@ -18,29 +17,55 @@ angular
|
|||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
init: init,
|
||||
clear: clear,
|
||||
getStateClone: getStateClone,
|
||||
getClone: getClone,
|
||||
map: map,
|
||||
popState: popState,
|
||||
pushState: pushState,
|
||||
startSend: startSend
|
||||
pop: pop,
|
||||
push: push,
|
||||
isEmpty: isEmpty
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Init state & stack
|
||||
* @param {Object} params
|
||||
*/
|
||||
function init(params) {
|
||||
$log.debug("send-flow-state init()");
|
||||
|
||||
clear();
|
||||
|
||||
if (params) {
|
||||
push(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a state & stack
|
||||
*/
|
||||
function clear() {
|
||||
console.log("sendFlow clear()");
|
||||
$log.debug("send-flow-state clear()");
|
||||
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current state only
|
||||
*/
|
||||
function clearCurrent() {
|
||||
console.log("sendFlow clearCurrent()");
|
||||
$log.debug("send-flow-state clearCurrent()");
|
||||
|
||||
service.state = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
|
|
@ -48,14 +73,17 @@ angular
|
|||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handy for debugging
|
||||
* Get a clone of the current state
|
||||
*/
|
||||
function getStateClone() {
|
||||
function getClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
|
|
@ -66,22 +94,21 @@ angular
|
|||
}
|
||||
|
||||
/**
|
||||
* Clears all previous state
|
||||
* Fill in the current state from the params
|
||||
* @param {Object} params
|
||||
*/
|
||||
function startSend(params) {
|
||||
console.log('startSend()');
|
||||
clear();
|
||||
map(params);
|
||||
}
|
||||
|
||||
function map(params) {
|
||||
Object.keys(params).forEach(function forNewParam(key) {
|
||||
service.state[key] = params[key];
|
||||
});
|
||||
};
|
||||
|
||||
function popState() {
|
||||
console.log('sendFlow pop');
|
||||
/**
|
||||
* Pop state
|
||||
*/
|
||||
function pop() {
|
||||
$log.debug('send-flow-state pop');
|
||||
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
|
|
@ -91,13 +118,25 @@ angular
|
|||
}
|
||||
};
|
||||
|
||||
function pushState(params) {
|
||||
console.log('sendFlow push');
|
||||
var currentParams = getStateClone();
|
||||
/**
|
||||
* Push state
|
||||
* @param {Object} params
|
||||
*/
|
||||
function push(params) {
|
||||
$log.debug('send-flow-state push');
|
||||
|
||||
var currentParams = getClone();
|
||||
service.previousStates.push(currentParams);
|
||||
clearCurrent();
|
||||
map(params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is empty stack
|
||||
*/
|
||||
function isEmpty() {
|
||||
return service.previousStates.length == 0;
|
||||
};
|
||||
};
|
||||
|
||||
})();
|
||||
149
src/js/services/send-flow.service.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
|
||||
function sendFlowService(
|
||||
sendFlowStateService, sendFlowRouterService
|
||||
, bitcoinUriService, payproService, bitcoinCashJsService
|
||||
, popupService, gettextCatalog
|
||||
, $state, $log
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
state: sendFlowStateService,
|
||||
router: sendFlowRouterService,
|
||||
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start a new send flow
|
||||
* @param {Object} params
|
||||
* @param {Function} onError
|
||||
*/
|
||||
function start(params, onError) {
|
||||
$log.debug('send-flow start()');
|
||||
|
||||
if (params && params.data) {
|
||||
var res = bitcoinUriService.parse(params.data);
|
||||
|
||||
if (res.isValid) {
|
||||
|
||||
// If BIP70 (url)
|
||||
if (res.url) {
|
||||
var url = res.url;
|
||||
var coin = res.coin || '';
|
||||
payproService.getPayProDetails(url, coin, function onGetPayProDetails(err, payProData) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
var name = payProData.domain;
|
||||
|
||||
// Detect some merchant that we know
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
// Init thirdParty
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: payProData.expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: payProData.time,
|
||||
url: payProData.url,
|
||||
verified: true
|
||||
};
|
||||
|
||||
// Fill in params
|
||||
params.amount = payProData.amount,
|
||||
params.toAddress = payProData.toAddress,
|
||||
params.coin = coin,
|
||||
params.thirdParty = thirdPartyData
|
||||
}
|
||||
|
||||
// Resolve
|
||||
_next();
|
||||
});
|
||||
} else {
|
||||
if (res.coin) {
|
||||
params.coin = res.coin;
|
||||
}
|
||||
|
||||
if (res.amountInSatoshis) {
|
||||
params.amount = res.amountInSatoshis;
|
||||
}
|
||||
|
||||
if (res.publicAddress) {
|
||||
var prefix = res.isTestnet ? 'bchtest:' : 'bitcoincash:';
|
||||
params.displayAddress = res.publicAddress.cashAddr || res.publicAddress.legacy || res.publicAddress.bitpay;
|
||||
var formatAddress = res.publicAddress.cashAddr ? prefix + params.displayAddress : params.displayAddress;
|
||||
params.toAddress = bitcoinCashJsService.readAddress(formatAddress).legacy;
|
||||
}
|
||||
|
||||
_next();
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_next();
|
||||
}
|
||||
|
||||
|
||||
// Next used for sync the async task
|
||||
function _next() {
|
||||
sendFlowStateService.init(params);
|
||||
|
||||
// Routing strategy to -> send-flow-router.service
|
||||
sendFlowRouterService.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next step
|
||||
* @param {Object} state
|
||||
*/
|
||||
function goNext(state) {
|
||||
$log.debug('send-flow goNext()');
|
||||
|
||||
// Save the current route before leaving
|
||||
state.route = $state.current.name;
|
||||
|
||||
// Save the state and redirect the user
|
||||
sendFlowStateService.push(state);
|
||||
sendFlowRouterService.goNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous step
|
||||
*/
|
||||
function goBack() {
|
||||
$log.debug('send-flow goBack()');
|
||||
|
||||
// Remove the state on top and redirect the user
|
||||
sendFlowStateService.pop();
|
||||
sendFlowRouterService.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) {
|
||||
angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingDataService, platformInfo, servicesService) {
|
||||
var root = {};
|
||||
root.ShiftState = 'Shift';
|
||||
root.coinIn = '';
|
||||
|
|
@ -111,7 +111,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http
|
|||
toAddress: txData.deposit
|
||||
};
|
||||
//
|
||||
// if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
// if (incomingDataService.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
// return;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
%button-standard {
|
||||
width: 85%;
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
.fee-summary {
|
||||
position: relative;
|
||||
background-color: #F2F2F2;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 5px 12px 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: #F2F2F2;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@
|
|||
.button {
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
line-height: 26px;
|
||||
padding: 8px 6px;
|
||||
}
|
||||
}
|
||||
.button-first-contact img {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
}
|
||||
|
||||
.fee-summary {
|
||||
position: absolute;
|
||||
bottom: 92px;
|
||||
bottom: calc(92px + constant(safe-area-inset-bottom)); /* iOS 11.0 */
|
||||
bottom: calc(92px + env(safe-area-inset-bottom)); /* iOS 11.2 */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.shapeshift-banner, .bitpay-banner, .egifter-banner {
|
||||
|
|
|
|||
|
|
@ -83,14 +83,14 @@
|
|||
.button {
|
||||
border: 2px solid;
|
||||
border-radius: 47px;
|
||||
padding: 0 15px 0 15px;
|
||||
padding: 8px 2px 8px 2px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
font-size: 19px;
|
||||
font-weight: bolder;
|
||||
min-height: auto;
|
||||
line-height: 36px;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
.wallet-coin-logo {
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
width: auto;
|
||||
margin: 2px 0 4px;
|
||||
}
|
||||
height: 60px;
|
||||
min-height: 65px;
|
||||
line-height: 16px;
|
||||
margin-right: 0px;
|
||||
width: 95%;
|
||||
|
|
|
|||
|
|
@ -207,14 +207,14 @@
|
|||
.button {
|
||||
border: 2px solid;
|
||||
border-radius: 47px;
|
||||
padding: 0 15px 0 15px;
|
||||
padding: 6px 2px 6px 2px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
font-size: 19px;
|
||||
font-weight: bolder;
|
||||
min-height: auto;
|
||||
line-height: 36px;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module.exports = function(config) {
|
|||
files: [
|
||||
'node_modules/angular/angular.js',
|
||||
|
||||
'bitanalytics/bitanalytics-0.1.0.js',
|
||||
'bitanalytics/bitanalytics.js',
|
||||
|
||||
// From Gruntfile.js
|
||||
'bower_components/qrcode-generator/js/qrcode.js',
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<i class="icon big-icon-svg theme-circle theme-circle-community">
|
||||
<div class="bg icon-share"></div>
|
||||
</i>
|
||||
<span>Share the Wallet App</span>
|
||||
<span translate>Share the Wallet App</span>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,21 +9,21 @@
|
|||
<img src="img/icon-bitcoin-small.svg">
|
||||
</div>
|
||||
<div class="incoming-data-menu__url__text">
|
||||
{{data}}
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="addToAddressBook(data)">
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="addToAddressBook(data.toAddress)">
|
||||
<img src="img/icon-contacts.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Add as a contact</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="sendPaymentToAddress(data)">
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="sendPaymentToAddress(data.toAddress)">
|
||||
<img src="img/icon-send-alt.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Send payment to this address</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data">
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
|
|
@ -38,11 +38,11 @@
|
|||
<div class="incoming-data-menu__header" translate>Text</div>
|
||||
<div class="incoming-data-menu__url">
|
||||
<div class="incoming-data-menu__url__text" style="border: 0;">
|
||||
{{data}}
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data">
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
|
|
@ -57,16 +57,40 @@
|
|||
<div class="incoming-data-menu__header" translate>Private Key</div>
|
||||
<div class="incoming-data-menu__url">
|
||||
<div class="incoming-data-menu__url__text" style="border: 0;">
|
||||
{{data}}
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="scanPaperWallet(data)">
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="scanPaperWallet(data.original)">
|
||||
<img src="img/icon-import.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Sweep paper wallet</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data">
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__cancel item" ng-click="hide()" translate>
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div ng-if="type === 'url'">
|
||||
<div class="incoming-data-menu__item head">
|
||||
<div class="incoming-data-menu__header" translate>URL</div>
|
||||
<div class="incoming-data-menu__url">
|
||||
<div class="incoming-data-menu__url__text" style="border: 0;">
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="goToUrl(data.original)">
|
||||
<img src="img/icon-link-external.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Open in web browser</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<ion-view hide-tabs>
|
||||
<ion-nav-bar class="bar-royal">
|
||||
<ion-nav-title>{{'Sweep paper wallet' | translate}}</ion-nav-title>
|
||||
<ion-nav-title>{{'Sweep Paper Wallet' | translate}}</ion-nav-title>
|
||||
<ion-nav-back-button>
|
||||
</ion-nav-back-button>
|
||||
</ion-nav-bar>
|
||||
|
|
@ -45,19 +45,19 @@
|
|||
</div>
|
||||
<div ng-class="ng-hide" ng-show="noMatchingBchWallet">
|
||||
<div class="text-center size-12 text-gray">
|
||||
<span>No Bitcoin Cash wallet to transfer funds to found.</span>
|
||||
<span translate>No Bitcoin Cash wallet to transfer funds to found.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-class="ng-hide" ng-show="!bchBalance && readyToShow">
|
||||
<div class="text-center">
|
||||
<h4 class="text-bold" translate>No Bitcoin Cash found</h4>
|
||||
<h4 class="text-bold" translate>No Bitcoin Cash found.</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-class="ng-hide" ng-show="btcBalance">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<h4 class="text-bold" translate>Bitcoin found:</h4>
|
||||
<h4 class="text-bold" translate>Bitcoin Core found:</h4>
|
||||
<div class="size-24">
|
||||
<span>{{btcBalanceText}}</span>
|
||||
<div class="size-14 amount-alternative">
|
||||
|
|
@ -95,13 +95,13 @@
|
|||
</div>
|
||||
<div ng-class="ng-hide" ng-show="noMatchingBtcWallet">
|
||||
<div class="text-center size-12 text-gray">
|
||||
<span>No Bitcoin wallet to transfer funds to found.</span>
|
||||
<span translate>No Bitcoin Core wallet to transfer funds to found.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-class="ng-hide" ng-show="!btcBalance && readyToShow">
|
||||
<div class="text-center">
|
||||
<h4 class="text-bold" translate>No Bitcoin found</h4>
|
||||
<h4 class="text-bold" translate>No Bitcoin Core found.</h4>
|
||||
</div>
|
||||
</div>
|
||||
<slide-to-accept-success
|
||||
|
|
|
|||