diff --git a/Gruntfile.js b/Gruntfile.js
index eb4bb2eb0..160db85d5 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -8,26 +8,32 @@ module.exports = function(grunt) {
grunt.initConfig({
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'
- },
- create_others_dist: {
- command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
- },
- create_dmg_dist: {
- command: 'sh webkitbuilds/create-dmg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
- },
- create_pkg_dist: {
- command: 'sh webkitbuilds/create-pkg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
- },
- sign_desktop_dist: {
- command: 'sh webkitbuilds/sign-desktop-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>"'
- },
appConfig: {
command: 'node ./util/buildAppConfig.js'
},
- externalServices: {
- command: 'node ./util/buildExternalServices.js'
+ android_studio: {
+ command: ' open -a open -a /Applications/Android\\ Studio.app platforms/android',
+ },
+ build_android_debug: {
+ command: 'cordova prepare android && cordova build android --debug',
+ },
+ build_android_release: {
+ command: 'cordova prepare android && cordova build android --release',
+ },
+ build_ios_debug: {
+ command: 'cordova prepare ios && cordova build ios --debug',
+ options: {
+ maxBuffer: 3200 * 1024
+ }
+ },
+ build_ios_release: {
+ command: 'cordova prepare ios && cordova build ios --release',
+ options: {
+ maxBuffer: 1600 * 1024
+ }
+ },
+ chrome: {
+ command: 'make -C chrome-app '
},
clean: {
command: 'rm -Rf bower_components node_modules'
@@ -38,8 +44,35 @@ module.exports = function(grunt) {
coveralls: {
command: 'cat coverage/report-lcov/lcov.info |./node_modules/coveralls/bin/coveralls.js'
},
- chrome: {
- command: 'make -C chrome-app '
+ create_dmg_dist: {
+ command: 'sh webkitbuilds/create-dmg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
+ },
+ create_others_dist: {
+ command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
+ },
+ create_pkg_dist: {
+ command: 'sh webkitbuilds/create-pkg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"'
+ },
+ externalServices: {
+ command: 'node ./util/buildExternalServices.js'
+ },
+ 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'
+ },
+ log_android: {
+ command: 'adb logcat | grep chromium',
+ },
+ run_android: {
+ command: 'cordova run android --device',
+ },
+ sign_android: {
+ // When the build log outputs "Built the following apk(s):", it seems to need the filename to start with "android-release".
+ // It looks like it simply lists all apk files starting with "android-release"
+ command: 'rm -f platforms/android/build/outputs/apk/android-release-signed-*.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/bitcoin-com-wallet-<%= pkg.fullVersion %>-android-signed-aligned.apk',
+ stdin: true,
+ },
+ sign_desktop_dist: {
+ command: 'sh webkitbuilds/sign-desktop-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>"'
},
wpinit: {
command: 'make -C cordova wp-init',
@@ -47,40 +80,9 @@ module.exports = function(grunt) {
wpcopy: {
command: 'make -C cordova wp-copy',
},
- iosdebug: {
- command: 'npm run build:ios',
- },
- ios: {
- command: 'npm run build:ios-release',
- },
xcode: {
- command: 'npm run open:ios',
- },
- androiddebug: {
- command: 'npm run build:android',
- },
- android: {
- command: 'npm run build:android-release',
- },
- androidrun: {
- command: 'npm run run:android && npm run log:android',
- },
- androidbuild: {
- command: 'cd cordova/project && cordova build android --release',
- },
- androidsign: {
- command: 'rm -f cordova/project/platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar cordova/project/platforms/android/build/outputs/apk/android-release-signed.apk cordova/project/platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && ../android-sdk-macosx/build-tools/27.0.1/zipalign -v 4 cordova/project/platforms/android/build/outputs/apk/android-release-signed.apk cordova/project/platforms/android/build/outputs/apk/android-release-signed-aligned.apk ',
- stdin: true,
- },
- desktopsign: {
- cmd: 'gpg -u E0AE67E7 --output webkitbuilds/others/<%= pkg.title %>-linux.zip.sig --detach-sig webkitbuilds/others/<%= pkg.title %>-linux.zip ; gpg -u E0AE67E7 --output webkitbuilds/others/<%= pkg.title %>.exe.sig --detach-sig webkitbuilds/others/<%= pkg.title %>.exe'
- },
- desktopverify: {
- cmd: 'gpg --verify webkitbuilds/<%= pkg.title %>-linux.zip.sig webkitbuilds/<%= pkg.title %>-linux.zip; gpg --verify webkitbuilds/<%= pkg.title %>.exe.sig webkitbuilds/<%= pkg.title %>.exe'
- },
- osxsign: {
- cmd: 'gpg -u E0AE67E7 --output webkitbuilds/<%= pkg.title %>.dmg.sig --detach-sig webkitbuilds/<%= pkg.title %>.dmg'
- },
+ command: 'open platforms/ios/*.xcodeproj',
+ }
},
watch: {
options: {
@@ -296,10 +298,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: {
@@ -348,30 +350,48 @@ module.exports = function(grunt) {
grunt.registerTask('wp', ['prod', 'exec:wp']);
grunt.registerTask('wp-copy', ['default', 'exec:wpcopy']);
grunt.registerTask('wp-init', ['default', 'exec:wpinit']);
- grunt.registerTask('ios', ['exec:ios']);
- grunt.registerTask('ios-debug', ['exec:iosdebug']);
- grunt.registerTask('ios-run', ['exec:xcode']);
grunt.registerTask('cordovaclean', ['exec:cordovaclean']);
- grunt.registerTask('android-debug', ['exec:androiddebug', 'exec:androidrun']);
- grunt.registerTask('android', ['exec:android']);
- grunt.registerTask('android-release', ['prod', 'exec:android', 'exec:androidsign']);
- grunt.registerTask('desktopsign', ['exec:desktopsign', 'exec:desktopverify']);
+
+ // Build all
+ grunt.registerTask('build-app-release', ['build-mobile-release', 'build-desktop-release']);
+
+ /**
+ * Mobile app
+ */
+
+ // Build mobile app
+ grunt.registerTask('build-mobile-release', ['build-ios-release', 'build-android-release']);
+
+ // Build ios
+ grunt.registerTask('start-ios', ['exec:build_ios_debug', 'exec:xcode']);
+ grunt.registerTask('build-ios-debug', ['exec:build_ios_debug']);
+ grunt.registerTask('build-ios-release', ['prod', 'exec:build_ios_release']);
+
+ // Build android
+ grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android']);
+ grunt.registerTask('build-android-debug', ['exec:build_android_debug']);
+ grunt.registerTask('build-android-release', ['prod', 'exec:build_android_release', 'sign-android']);
+ grunt.registerTask('sign-android', ['exec:sign_android']);
+
+ /**
+ * Desktop app
+ */
// Build desktop
- grunt.registerTask('desktop-build', ['desktop-others', 'desktop-osx-dmg', 'desktop-osx-pkg']);
+ grunt.registerTask('build-desktop', ['build-desktop-others', 'build-desktop-osx-dmg', 'build-desktop-osx-pkg']);
// Build desktop win64 & linux64
- grunt.registerTask('desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']);
+ grunt.registerTask('build-desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']);
// Build desktop osx pkg
- grunt.registerTask('desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']);
+ grunt.registerTask('build-desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']);
// Build desktop osx dmg
- grunt.registerTask('desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']);
+ grunt.registerTask('build-desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']);
// Sign desktop
- grunt.registerTask('desktop-sign', ['exec:sign_desktop_dist']);
+ grunt.registerTask('sign-desktop', ['exec:sign_desktop_dist']);
// Release desktop
- grunt.registerTask('desktop-release', ['desktop-build', 'desktop-sign']);
+ grunt.registerTask('build-desktop-release', ['build-desktop', 'sign-desktop']);
};
diff --git a/README.md b/README.md
index 56ae84eab..75ced5808 100644
--- a/README.md
+++ b/README.md
@@ -113,14 +113,14 @@ npm run start:desktop
Before building the release version for a platform, run the `clean-all` command to delete any untracked files in your current working directory. (Be sure to stash any uncommited changes you've made.) This guarantees consistency across builds for the current state of this repository.
-The `final` commands build the production version of the app, and bundle it with the release version of the platform being built.
+The `build:*-release` commands build the production version of the app, and bundle it with the release version of the platform being built.
### Android
```sh
npm run clean-all
npm run apply:bitcoincom
-npm run final:android
+npm run build:android-release
```
### iOS
@@ -128,7 +128,7 @@ npm run final:android
```sh
npm run clean-all
npm run apply:bitcoincom
-npm run final:ios
+npm run build:ios-release
```
### Desktop (Linux, macOS, and Windows)
@@ -136,7 +136,7 @@ npm run final:ios
```sh
npm run clean-all
npm run apply:bitcoincom
-npm run final:desktop
+npm run build:desktop-release
```
## About The Bitcoin.com Wallet
diff --git a/app-template/config-template.xml b/app-template/config-template.xml
index 2f8e3db04..8686b7b36 100644
--- a/app-template/config-template.xml
+++ b/app-template/config-template.xml
@@ -85,6 +85,12 @@
+
+
+
+
+
+
@@ -102,6 +108,7 @@
+
diff --git a/app-template/create-pkg-dist.sh b/app-template/create-pkg-dist.sh
index c0b4d266d..66fe589d0 100644
--- a/app-template/create-pkg-dist.sh
+++ b/app-template/create-pkg-dist.sh
@@ -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
\ No newline at end of file
diff --git a/app-template/package-template.json b/app-template/package-template.json
index c42ef81b4..200c269f6 100644
--- a/app-template/package-template.json
+++ b/app-template/package-template.json
@@ -87,46 +87,52 @@
"bitcoincashjs-fork": "^1.0.3"
},
"scripts": {
- "postinstall": "bower install",
- "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0",
- "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"",
- "start:ios": "npm run build:www && npm run build:ios && npm run open:ios",
- "start:android": "npm run build:www && npm run build:android && npm run run:android",
- "start:windows": "npm run build:www && npm run build:windows",
- "start:desktop": "npm start",
- "watch": "grunt watch",
+ "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh",
+
+ "build:app-release": "grunt build-app-release",
+
+ "build:mobile-release": "grunt build-mobile-release",
+ "build:desktop-release": "grunt build-desktop-release",
+
+ "build:android-debug": "grunt build-android-debug",
+ "build:android-release": "grunt build-android-release",
+
+ "build:ios-debug": "grunt build-ios-debug",
+ "build:ios-release": "grunt build-ios-release",
+
+ "build:desktop": "grunt build-desktop",
+ "build:osx-pkg": "grunt build-desktop-osx-pkg",
+ "build:osx-dmg": "grunt build-desktop-osx-dmg",
+ "build:others": "grunt build-desktop-others",
+
+ "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"",
+ "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"",
"build:www": "grunt",
"build:www-release": "grunt prod",
- "build:ios": "cordova prepare ios && cordova build ios --debug",
- "build:android": "cordova prepare android && cordova build android --debug",
- "build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"",
- "build:ios-release": "cordova prepare ios && cordova build ios --release",
- "build:android-release": "cordova prepare android && cordova build android --release",
- "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"",
- "build:desktop-release": "grunt desktop-release",
- "build:desktop": "grunt desktop-build",
- "build:osx-pkg": "grunt desktop-osx-pkg",
- "build:osx-dmg": "grunt desktop-osx-dmg",
- "build:others": "grunt desktop-others",
- "sign:desktop": "grunt desktop-sign",
- "open:ios": "open platforms/ios/*.xcodeproj",
- "open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android",
- "final:www": "npm run build:www-release",
- "final:ios": "npm run final:www && npm run build:ios-release && npm run open:ios",
- "final:android": "npm run final:www && npm run build:android-release && npm run sign:android && npm run run:android-release",
- "final:windows": "npm run final:www && npm run build:windows-release",
- "final:desktop": "npm run final:www && npm run build:desktop-release",
- "run:android": "cordova run android --device",
- "run:android-release": "cordova run android --device --release",
- "log:android": "adb logcat | grep chromium",
- "sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk",
- "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare",
- "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare",
- "apply:bitcoincom": "npm i fs-extra && cd app-template && node apply.js bitcoincom && npm i && cordova prepare && cd ../ && ./fix-asn1.sh",
- "test": "karma start test/karma.conf.js --single-run",
+
"clean": "trash platforms && trash plugins && cordova prepare",
+ "clean-all": "git clean -dfx",
+
+ "log:android": "adb logcat | grep chromium",
+
+ "open:android": "grunt exec:android_studio",
+ "open:ios": "grunt exec:xcode",
+
+ "postinstall": "bower install",
+
+ "sign:android": "grunt sign-android",
+ "sign:desktop": "grunt sign-desktop",
+
+ "start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0",
+ "start:chrome": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0 --browser \"google chrome\"",
+ "start:android": "grunt start-android",
+ "start:android-log": "grunt start-android && npm run log:android",
+ "start:ios": "grunt start-ios",
+ "start:windows": "npm run build:www && npm run build:windows",
+
+ "test": "karma start test/karma.conf.js --single-run",
"unstage-package": "git reset package.json",
- "clean-all": "git clean -dfx"
+ "watch": "grunt watch"
},
"devDependencies": {
"cordova": "^6.3.1",
diff --git a/i18n/po/template.pot b/i18n/po/template.pot
index 10b3de712..2b1068ad4 100644
--- a/i18n/po/template.pot
+++ b/i18n/po/template.pot
@@ -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 ""
@@ -3857,4 +3866,68 @@ msgstr ""
#: src/js/controllers/amount.controller.js:239
msgid "Unknown error."
+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."
+msgstr ""
+
+#: 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 ""
+
+#: src/js/services/shapeshift.service.js.html:90
+msgid "Invalid address"
+msgstr ""
+
+#: src/js/services/shapeshift.service.js.html:90
+msgid "Amount is not defined"
+msgstr ""
+
+#: src/js/services/shapeshift.service.js.html:90
+msgid "Amount is below the minimun"
+msgstr ""
+
+#: src/js/services/shapeshift.service.js.html:90
+msgid "Amount is above the limit"
+msgstr ""
+
+#: src/js/services/shapeshift.service.js.html:90
+msgid "Invalid response from Shapeshift"
msgstr ""
\ No newline at end of file
diff --git a/resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png b/resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png
new file mode 100644
index 000000000..67dddbb02
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png differ
diff --git a/resources/bitcoin.com/ios/icon/AppIcon27.5x27.5@2x.png b/resources/bitcoin.com/ios/icon/AppIcon27.5x27.5@2x.png
new file mode 100644
index 000000000..0033e910b
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/AppIcon27.5x27.5@2x.png differ
diff --git a/resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png b/resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png
new file mode 100644
index 000000000..ac78920fc
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png differ
diff --git a/resources/bitcoin.com/ios/icon/AppIcon86x86@2x.png b/resources/bitcoin.com/ios/icon/AppIcon86x86@2x.png
new file mode 100644
index 000000000..5a0e05b71
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/AppIcon86x86@2x.png differ
diff --git a/resources/bitcoin.com/ios/icon/AppIcon98x98@2x.png b/resources/bitcoin.com/ios/icon/AppIcon98x98@2x.png
new file mode 100644
index 000000000..a0aaf7bd7
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/AppIcon98x98@2x.png differ
diff --git a/resources/bitcoin.com/ios/icon/icon-1024.png b/resources/bitcoin.com/ios/icon/icon-1024.png
new file mode 100644
index 000000000..2950ff8de
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/icon-1024.png differ
diff --git a/resources/bitcoin.com/ios/icon/icon-20.png b/resources/bitcoin.com/ios/icon/icon-20.png
new file mode 100644
index 000000000..323da6465
Binary files /dev/null and b/resources/bitcoin.com/ios/icon/icon-20.png differ
diff --git a/resources/bitcoin.com/mac/pkg/build_mas.py b/resources/bitcoin.com/mac/pkg/build_mas.py
index d067abacd..90f7ad5f0 100755
--- a/resources/bitcoin.com/mac/pkg/build_mas.py
+++ b/resources/bitcoin.com/mac/pkg/build_mas.py
@@ -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'):
diff --git a/resources/bitcoin.com/mac/pkg/entitlements-parent.plist b/resources/bitcoin.com/mac/pkg/entitlements-parent.plist
index 12d6997e3..b39edb569 100644
--- a/resources/bitcoin.com/mac/pkg/entitlements-parent.plist
+++ b/resources/bitcoin.com/mac/pkg/entitlements-parent.plist
@@ -5,7 +5,9 @@
com.apple.security.app-sandbox
com.apple.security.application-groups
- $GROUPID
+
+ 299HJ3G3BP.com.bitcoin.mwallet.mac
+
com.apple.security.files.user-selected.read-only
com.apple.security.network.client
diff --git a/src/js/app.js b/src/js/app.js
index 6bef6f615..62ff2e6f4 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -20,7 +20,8 @@ var modules = [
'copayApp.directives',
'copayApp.addons',
'bitcoincom.controllers',
- 'bitcoincom.directives'
+ 'bitcoincom.directives',
+ 'bitcoincom.services'
];
var copayApp = window.copayApp = angular.module('copayApp', modules);
@@ -32,3 +33,4 @@ angular.module('copayApp.directives', []);
angular.module('copayApp.addons', []);
angular.module('bitcoincom.controllers', []);
angular.module('bitcoincom.directives', []);
+angular.module('bitcoincom.services', []);
diff --git a/src/js/controllers/addressbookView.js b/src/js/controllers/addressbookView.js
index 89c1cd924..16df9e559 100644
--- a/src/js/controllers/addressbookView.js
+++ b/src/js/controllers/addressbookView.js
@@ -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) {
diff --git a/src/js/controllers/amount.controller.js b/src/js/controllers/amount.controller.js
index 1e6af76b1..0c3be6bd3 100644
--- a/src/js/controllers/amount.controller.js
+++ b/src/js/controllers/amount.controller.js
@@ -85,14 +85,13 @@ 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();
- console.log('sendflow Amount on BeforeEnter after back', passthroughParams);
+ passthroughParams = sendFlowService.state.getClone();
+ console.log('amount onBeforeEnter after back sendflow ', passthroughParams);
vm.fromWalletId = passthroughParams.fromWalletId;
vm.toWalletId = passthroughParams.toWalletId;
@@ -113,7 +112,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
if (passthroughParams.thirdParty) {
vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object
if (vm.thirdParty) {
- initForShapeshift();
+ initShapeshift();
}
}
@@ -122,7 +121,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
var reOp = /^[\*\+\-\/]$/;
if (!isAndroid && !isIos) {
- var disableKeys = angular.element($window).on('keydown', function(e) {
+ angular.element($window).on('keydown', function(e) {
if (!e.key) return;
if (e.which === 8) { // you can add others here inside brackets.
if (!altCurrencyModal) {
@@ -221,10 +220,10 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
}
function goBack() {
- $ionicHistory.goBack();
+ sendFlowService.router.goBack();
}
- function initForShapeshift() {
+ function initShapeshift() {
if (vm.thirdParty.id === 'shapeshift') {
vm.thirdParty.data = vm.thirdParty.data || {};
@@ -236,28 +235,19 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
vm.canSendAllAvailableFunds = false;
ongoingProcess.set('connectingShapeshift', true);
- shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(data) {
+ shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(err, data) {
ongoingProcess.set('connectingShapeshift', false);
-
- if (data.error) {
- var defaultErrorMessage = gettextCatalog.getString('Unknown error.');
- popupService.showAlert(
- gettextCatalog.getString('Shapeshift Error'),
- typeof data.error === 'string' ? data.error : (data.error.message ? data.error.message : defaultErrorMessage),
- function () {
- goBack();
- }
- );
+ if (err) {
+ // Error stop here
+ popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.message, function () {
+ goBack();
+ });
} else {
-
vm.thirdParty.data.minAmount = vm.minAmount = parseFloat(data.minimum);
vm.thirdParty.data.maxAmount = vm.maxAmount = parseFloat(data.maxLimit);
-
setMaximumButtonFromWallet(vm.fromWallet);
}
});
-
-
}
}
@@ -532,11 +522,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;
}
}
diff --git a/src/js/controllers/onboarding/tour.js b/src/js/controllers/onboarding/tour.js
index 47261006b..f6cf0afe2 100644
--- a/src/js/controllers/onboarding/tour.js
+++ b/src/js/controllers/onboarding/tour.js
@@ -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);
+ };
+ });
diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js
index 554774252..15f982f2f 100644
--- a/src/js/controllers/review.controller.js
+++ b/src/js/controllers/review.controller.js
@@ -80,7 +80,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
function onBeforeEnter(event, data) {
console.log('review onBeforeEnter sendflow ', sendFlowService.state);
defaults = configService.getDefaults();
- sendFlowData = sendFlowService.getStateClone();
+ sendFlowData = sendFlowService.state.getClone();
originWalletId = sendFlowData.fromWalletId;
if (typeof sendFlowData.amount === 'string') {
satoshis = parseInt(sendFlowData.amount, 10);
@@ -97,26 +97,48 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
if (sendFlowData.thirdParty) {
vm.thirdParty = sendFlowData.thirdParty;
- handleThirdPartyInitIfBip70();
- handleThirdPartyInitIfShapeshift();
+ switch (vm.thirdParty.id) {
+ case 'shapeshift':
+ initShapeshift(function (err) {
+ if (err) {
+ // Error stop here
+ ongoingProcess.set('connectingShapeshift', false);
+ popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () {
+ $ionicHistory.goBack();
+ });
+ } else {
+ _next(data);
+ }
+ });
+ break;
+ case 'bip70':
+ initBip70();
+ default:
+ _next(data);
+ break;
+ }
+ } else {
+ _next(data);
}
- configService.get(function onConfig(err, configCache) {
- if (err) {
- $log.err('Error getting config.', err);
- } else {
- config = configCache;
- priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat';
- vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor);
- console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc');
- unitFromSat = 1 / config.wallet.settings.unitToSatoshi;
- }
- updateSendAmounts();
- getOriginWalletBalance(vm.originWallet);
- handleDestinationAsAddress(toAddress, coin);
- handleDestinationAsWallet(sendFlowData.toWalletId);
- createVanityTransaction(data);
- });
+ function _next() {
+ configService.get(function onConfig(err, configCache) {
+ if (err) {
+ $log.err('Error getting config.', err);
+ } else {
+ config = configCache;
+ priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat';
+ vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor);
+ console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc');
+ unitFromSat = 1 / config.wallet.settings.unitToSatoshi;
+ }
+ updateSendAmounts();
+ getOriginWalletBalance(vm.originWallet);
+ handleDestinationAsAddress(toAddress, coin);
+ handleDestinationAsWallet(sendFlowData.toWalletId);
+ createVanityTransaction(data);
+ });
+ }
}
vm.approve = function() {
@@ -407,7 +429,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
}
function goBack() {
- $ionicHistory.goBack();
+ sendFlowService.router.goBack();
}
function handleDestinationAsAddress(address, originCoin) {
@@ -462,72 +484,62 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
vm.destination.balanceCurrency = balanceText.currency;
}
- function handleThirdPartyInitIfBip70() {
- if (vm.thirdParty.id === 'bip70') {
- vm.sendingTitle = gettextCatalog.getString('You are paying');
- vm.memo = vm.thirdParty.memo;
- vm.memoExpanded = !!vm.memo;
- vm.destination.name = vm.thirdParty.name;
+ function initBip70() {
+ vm.sendingTitle = gettextCatalog.getString('You are paying');
+ vm.memo = vm.thirdParty.memo;
+ vm.memoExpanded = !!vm.memo;
+ vm.destination.name = vm.thirdParty.name;
- txPayproData = {
- caTrusted: vm.thirdParty.caTrusted,
- domain: vm.thirdParty.domain,
- expires: vm.thirdParty.expires,
- toAddress: toAddress,
- url: vm.thirdParty.url,
- verified: vm.thirdParty.verified,
- };
- }
+ txPayproData = {
+ caTrusted: vm.thirdParty.caTrusted,
+ domain: vm.thirdParty.domain,
+ expires: vm.thirdParty.expires,
+ toAddress: toAddress,
+ url: vm.thirdParty.url,
+ verified: vm.thirdParty.verified,
+ };
}
- function handleThirdPartyInitIfShapeshift() {
- if (vm.thirdParty.id === 'shapeshift') {
- vm.sendingTitle = gettextCatalog.getString('You are shifting');
- if (!vm.thirdParty.data) {
- vm.thirdParty.data = {};
- }
+ function initShapeshift(cb) {
+ vm.sendingTitle = gettextCatalog.getString('You are shifting');
+ if (!vm.thirdParty.data) {
+ vm.thirdParty.data = {};
+ }
- var toWallet = profileService.getWallet(destinationWalletId);
- vm.destination.name = toWallet.name;
- vm.destination.color = toWallet.color;
- vm.destination.currency = toWallet.coin.toUpperCase();
+ var toWallet = profileService.getWallet(destinationWalletId);
+ vm.destination.name = toWallet.name;
+ vm.destination.color = toWallet.color;
+ vm.destination.currency = toWallet.coin.toUpperCase();
- ongoingProcess.set('connectingShapeshift', true);
- walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) {
+ ongoingProcess.set('connectingShapeshift', true);
+ walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) {
+ if (err) {
+ return cb(err);
+ }
+ walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) {
if (err) {
- ongoingProcess.set('connectingShapeshift', false);
- popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () {
- $ionicHistory.goBack();
- });
- return;
+ return cb(err);
}
- walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) {
- if (err) {
- ongoingProcess.set('connectingShapeshift', false);
- popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () {
- $ionicHistory.goBack();
- });
- return;
- }
- shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) {
- if (err && err != null) {
- ongoingProcess.set('connectingShapeshift', false);
- popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () {
- $ionicHistory.goBack();
- });
- } else {
- vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId;
- vm.memoExpanded = !!vm.memo;
- tx.toAddress = shapeshiftData.toAddress;
- vm.destination.address = toAddress;
- vm.destination.kind = 'shapeshift';
- }
- });
+ // Need to use the correct service to do it.
+ var amount = parseFloat(satoshis / 100000000);
+
+ shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) {
+ if (err) {
+ return cb(err);
+ } else {
+ vm.destination.kind = 'shapeshift';
+ vm.destination.address = toAddress;
+ tx.toAddress = shapeshiftData.toAddress;
+ vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId;
+ vm.memoExpanded = !!vm.memo;
+ ongoingProcess.set('connectingShapeshift', false);
+ cb();
+ }
});
});
- }
+ });
}
function onShareTransaction() {
@@ -770,8 +782,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');
}
diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js
index 43e0790d1..0dac21a11 100644
--- a/src/js/controllers/shapeshift.js
+++ b/src/js/controllers/shapeshift.js
@@ -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() {
diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.controller.js
similarity index 99%
rename from src/js/controllers/tab-home.js
rename to src/js/controllers/tab-home.controller.js
index 318fcece2..229848df8 100644
--- a/src/js/controllers/tab-home.js
+++ b/src/js/controllers/tab-home.controller.js
@@ -122,8 +122,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
};
$scope.startFreshSend = function() {
- sendFlowService.clear();
- $state.go('tabs.send');
+ sendFlowService.start();
}
$scope.openExternalLink = function() {
diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js
index 66d1799f8..320afe320 100644
--- a/src/js/controllers/tab-receive.js
+++ b/src/js/controllers/tab-receive.js
@@ -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) {
diff --git a/src/js/controllers/tab-scan.js b/src/js/controllers/tab-scan.controller.js
similarity index 89%
rename from src/js/controllers/tab-scan.js
rename to src/js/controllers/tab-scan.controller.js
index 4a654d91d..14368ee1c 100644
--- a/src/js/controllers/tab-scan.js
+++ b/src/js/controllers/tab-scan.controller.js
@@ -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() {
diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.controller.js
similarity index 80%
rename from src/js/controllers/tab-send.js
rename to src/js/controllers/tab-send.controller.js
index 9ac6c35cb..03a9562e8 100644
--- a/src/js/controllers/tab-send.js
+++ b/src/js/controllers/tab-send.controller.js
@@ -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();
}
});
diff --git a/src/js/controllers/tabsController.js b/src/js/controllers/tabsController.js
index b3de6c70f..b78274ecb 100644
--- a/src/js/controllers/tabsController.js
+++ b/src/js/controllers/tabsController.js
@@ -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);
});
};
diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/wallet-details.controller.js
similarity index 79%
rename from src/js/controllers/walletDetails.js
rename to src/js/controllers/wallet-details.controller.js
index ec787a5f4..7a06d4fb6 100644
--- a/src/js/controllers/walletDetails.js
+++ b/src/js/controllers/wallet-details.controller.js
@@ -19,34 +19,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
var log = new window.BitAnalytics.LogEvent("wallet_details_open", [], [channel]);
window.BitAnalytics.LogEventHandlers.postEvent(log);
- $scope.amountIsCollapsible = !$scope.isAndroid;
-
$scope.openExternalLink = function(url, target) {
externalLinkService.open(url, target);
};
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;
@@ -283,103 +260,36 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
};
var prevPos;
-
+ $scope.txHistoryPaddingBottom = 0;
function getScrollPosition() {
var scrollPosition = $ionicScrollDelegate.getScrollPosition();
+
+ $timeout(function() {
+ getScrollPosition();
+ }, 200);
+
if (!scrollPosition) {
- $window.requestAnimationFrame(function() {
- getScrollPosition();
- });
return;
}
var pos = scrollPosition.top;
+ if (pos > 0) {
+ $scope.txHistoryPaddingBottom = "200px";
+ }
if (pos === prevPos) {
- $window.requestAnimationFrame(function() {
- getScrollPosition();
- });
return;
}
prevPos = pos;
- refreshAmountSection(pos);
- };
-
- function refreshAmountSection(scrollPos) {
- var AMOUNT_HEIGHT_BASE = 210;
- $scope.showBalanceButton = false;
- if ($scope.status) {
- $scope.showBalanceButton = ($scope.status.totalBalanceSat != $scope.status.spendableAmount);
- if ($scope.showBalanceButton) {
- AMOUNT_HEIGHT_BASE = 270;
- }
- }
- if (!$scope.amountIsCollapsible) {
- var t = ($scope.showBalanceButton ? 15 : 45);
- $scope.amountScale = 'translateY(' + t + 'px)';
- return;
- }
-
- scrollPos = scrollPos || 0;
- var amountHeight = AMOUNT_HEIGHT_BASE - scrollPos;
- if (amountHeight < 80) {
- amountHeight = 80;
- }
- var contentMargin = amountHeight;
- if (contentMargin > AMOUNT_HEIGHT_BASE) {
- contentMargin = AMOUNT_HEIGHT_BASE;
- }
-
- var amountScale = (amountHeight / AMOUNT_HEIGHT_BASE);
- if (amountScale < 0.5) {
- amountScale = 0.5;
- }
- if (amountScale > 1.1) {
- amountScale = 1.1;
- }
-
- var s = amountScale;
-
- // Make space for the balance button when it needs to display.
- var TOP_NO_BALANCE_BUTTON = 115;
- var TOP_BALANCE_BUTTON = 30;
- var top = TOP_NO_BALANCE_BUTTON;
- if ($scope.showBalanceButton) {
- top = TOP_BALANCE_BUTTON;
- }
-
- var amountTop = ((amountScale - 0.80) / 0.80) * top;
- if (amountTop < -2) {
- amountTop = -2;
- }
- if (amountTop > top) {
- amountTop = top;
- }
-
- var t = amountTop;
-
- $scope.altAmountOpacity = (amountHeight - 100) / 80;
- $scope.buttonsOpacity = (amountHeight - 140) / 70;
- $window.requestAnimationFrame(function() {
- $scope.amountHeight = amountHeight + 'px';
- $scope.contentMargin = contentMargin + 'px';
- $scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)';
- $scope.$digest();
- getScrollPosition();
- });
+ $scope.scrollPosition = pos;
}
var scrollWatcherInitialized;
$scope.$on("$ionicView.enter", function(event, data) {
if ($scope.isCordova && $scope.isAndroid) setAndroidStatusBarColor();
- if (scrollWatcherInitialized || !$scope.amountIsCollapsible) {
- return;
- }
scrollWatcherInitialized = true;
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
- sendFlowService.clear();
-
configService.whenAvailable(function (config) {
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
@@ -416,8 +326,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.$on("$ionicView.afterEnter", function(event, data) {
$scope.updateAll();
- refreshAmountSection();
+ // refreshAmountSection();
refreshInterval = $interval($scope.onRefresh, 10 * 1000);
+ $timeout(function() {
+ getScrollPosition();
+ }, 1000);
});
$scope.$on("$ionicView.afterLeave", function(event, data) {
@@ -477,16 +390,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', {
diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/wallet-selector.controller.js
similarity index 81%
rename from src/js/controllers/walletSelectorController.js
rename to src/js/controllers/wallet-selector.controller.js
index 777871e44..06e6179da 100644
--- a/src/js/controllers/walletSelectorController.js
+++ b/src/js/controllers/wallet-selector.controller.js
@@ -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();
}
});
\ No newline at end of file
diff --git a/src/js/directives/incomingDataMenu.js b/src/js/directives/incomingDataMenu.js
index 21478102b..78856e62f 100644
--- a/src/js/directives/incomingDataMenu.js
+++ b/src/js/directives/incomingDataMenu.js
@@ -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) {
diff --git a/src/js/directives/shapeshiftCoinTrader.js b/src/js/directives/shapeshiftCoinTrader.js
index 60cc66bdf..793f380fb 100644
--- a/src/js/directives/shapeshiftCoinTrader.js
+++ b/src/js/directives/shapeshiftCoinTrader.js
@@ -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;
}
diff --git a/src/js/services/bitcoin-uri.service.js b/src/js/services/bitcoin-uri.service.js
new file mode 100644
index 000000000..3da84f3da
--- /dev/null
+++ b/src/js/services/bitcoin-uri.service.js
@@ -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;
+ }
+
+ }
+
+})();
diff --git a/src/js/services/bitcoin-uri.service.spec.js b/src/js/services/bitcoin-uri.service.spec.js
new file mode 100644
index 000000000..2ddbd0d2e
--- /dev/null
+++ b/src/js/services/bitcoin-uri.service.spec.js
@@ -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');
+ });
+
+});
\ No newline at end of file
diff --git a/src/js/services/incoming-data.service.js b/src/js/services/incoming-data.service.js
new file mode 100644
index 000000000..eece6d17c
--- /dev/null
+++ b/src/js/services/incoming-data.service.js
@@ -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;
+});
diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js
deleted file mode 100644
index 0bf708d8a..000000000
--- a/src/js/services/incomingData.js
+++ /dev/null
@@ -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;
-});
diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js
index 147985dbb..9c25c3c26 100644
--- a/src/js/services/onGoingProcess.js
+++ b/src/js/services/onGoingProcess.js
@@ -52,11 +52,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
root.clear = function() {
ongoingProcess = {};
- if (isCordova && !isWindowsPhoneApp) {
- window.plugins.spinnerDialog.hide();
- } else {
- $ionicLoading.hide();
- }
+ $ionicLoading.hide();
};
root.get = function(processName) {
@@ -82,23 +78,14 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
if (customHandler) {
customHandler(processName, showName, isOn);
} else if (root.onGoingProcessName) {
- if (isCordova && !isWindowsPhoneApp) {
- window.plugins.spinnerDialog.show(null, showName, root.clear);
- } else {
-
- var tmpl;
- if (isWindowsPhoneApp) tmpl = '
' + showName + '
';
- else tmpl = '' + showName + '
';
- $ionicLoading.show({
- template: tmpl
- });
- }
+ var tmpl;
+ if (isWindowsPhoneApp) tmpl = '' + showName + '
';
+ else tmpl = '' + showName + '
';
+ $ionicLoading.show({
+ template: tmpl,
+ });
} else {
- if (isCordova && !isWindowsPhoneApp) {
- window.plugins.spinnerDialog.hide();
- } else {
- $ionicLoading.hide();
- }
+ $ionicLoading.hide();
}
};
diff --git a/src/js/services/openURL.js b/src/js/services/openURL.js
index 0f4d6c666..2cf8d95a5 100644
--- a/src/js/services/openURL.js
+++ b/src/js/services/openURL.js
@@ -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() {
diff --git a/src/js/services/send-flow-router.service.js b/src/js/services/send-flow-router.service.js
new file mode 100644
index 000000000..32aa8420b
--- /dev/null
+++ b/src/js/services/send-flow-router.service.js
@@ -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();
+ }
+ };
+
+})();
\ No newline at end of file
diff --git a/src/js/services/sendFlowService.js b/src/js/services/send-flow-state.service.js
similarity index 50%
rename from src/js/services/sendFlowService.js
rename to src/js/services/send-flow-state.service.js
index 9981fdbcb..960e6f306 100644
--- a/src/js/services/sendFlowService.js
+++ b/src/js/services/send-flow-state.service.js
@@ -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: 0,
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: 0,
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];
});
- };
+ }
+
+ /**
+ * Pop state
+ */
+ function pop() {
+ $log.debug('send-flow-state pop');
- function popState() {
- console.log('sendFlow pop');
if (service.previousStates.length) {
var params = service.previousStates.pop();
clearCurrent();
@@ -89,15 +116,27 @@ angular
} else {
clear();
}
- };
+ }
- 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;
+ }
};
})();
\ No newline at end of file
diff --git a/src/js/services/send-flow.service.js b/src/js/services/send-flow.service.js
new file mode 100644
index 000000000..e8be2e487
--- /dev/null
+++ b/src/js/services/send-flow.service.js
@@ -0,0 +1,148 @@
+'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();
+ }
+ }
+})();
\ No newline at end of file
diff --git a/src/js/services/shapeShiftApiService.js b/src/js/services/shapeShiftApiService.js
index 210ae0fbd..cc5fb0792 100644
--- a/src/js/services/shapeShiftApiService.js
+++ b/src/js/services/shapeShiftApiService.js
@@ -328,18 +328,23 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
$scope.amount, $scope.withdrawalAddress,
$scope.coinIn, $scope.coinOut
);
+ console.log('shapeshiftApiService.FixedAmountTx()');
console.log(fixedTx);
SSA.FixedAmountTx(fixedTx, function (data) {
- console.log(data)
- return promise.resolve({ fixedTxData : data.success });
+ console.log(data);
+ promise.resolve(data);
});
return promise.promise;
},
NormalTx : function($scope){
var promise = $q.defer();
var normalTx = SSA.CreateNormalTx($scope.withdrawalAddress, $scope.coinIn, $scope.coinOut);
+
+ console.log('shapeshiftApiService.NormalTx()');
+ console.log(normalTx);
SSA.NormalTx(normalTx, function (data) {
- promise.resolve({ normalTxData : data });
+ console.log(data);
+ promise.resolve(data);
});
return promise.promise;
},
@@ -360,11 +365,12 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
return promise.promise;
},
ValidateAddress : function(address, coin) {
- var promise = $q.defer();
- SSA.ValidateAdddress(address, coin, function(data){
- promise.resolve(data);
- });
- return promise.promise;
+ var promise = $q.defer();
+ SSA.ValidateAdddress(address, coin, function onRequest(data){
+ console.log(data);
+ promise.resolve(data);
+ });
+ return promise.promise;
}
};
});
diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js
new file mode 100644
index 000000000..77f0de297
--- /dev/null
+++ b/src/js/services/shapeshift.service.js
@@ -0,0 +1,112 @@
+'use strict';
+
+(function(){
+
+angular
+ .module('bitcoincom.services')
+ .factory('shapeshiftService', shapeshiftService);
+
+ function shapeshiftService(shapeshiftApiService, gettextCatalog) {
+
+ var service = {
+ // Variables
+ coinIn: '',
+ coinOut: '',
+ withdrawalAddress: '',
+ returnAddress: '',
+ amount: '',
+ marketData: {},
+ coins: {
+ 'BTC': {name: 'Bitcoin', symbol: 'BTC'},
+ 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
+ },
+
+ // Functions
+ getMarketData: getMarketData,
+ shiftIt: shiftIt
+ };
+
+ return service;
+
+ function handleError(response, defaultMessage, cb) {
+ if (response && typeof response.error === "string") {
+ cb(new Error(response.error));
+ } else if (response && response.error && response.error.message) {
+ cb(new Error(response.error.message));
+ } else {
+ cb(new Error(defaultMessage));
+ }
+ }
+
+ function getMarketData(coinIn, coinOut, cb) {
+ service.coinIn = coinIn;
+ service.coinOut = coinOut;
+ shapeshiftApiService
+ .marketInfo(service.coinIn, service.coinOut)
+ .then(function (response) {
+ if (!response || response.error) {
+ handleError(response, 'Invalid response from Shapeshift', cb);
+ } else {
+ service.marketData = response;
+ service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
+ cb(null, response);
+ }
+ });
+ }
+
+ function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) {
+ // Test if the amount is correct depending on the min and max
+ if (!amount || typeof amount !== 'number') {
+ cb(new Error(gettextCatalog.getString('Amount is not defined'))));
+ } else if (amount < service.marketData.minimum) {
+ cb(new Error(gettextCatalog.getString('Amount is below the minimun')));
+ } else if (amount > service.marketData.maxLimit) {
+ cb(new Error(gettextCatalog.getString('Amount is above the limit')));
+ } else {
+ // Init service data
+ service.withdrawalAddress = withdrawalAddress;
+ service.returnAddress = returnAddress;
+ service.coinIn = coinIn;
+ service.coinOut = coinOut;
+ service.amount = amount;
+
+ // Check the address
+ shapeshiftApiService
+ .ValidateAddress(withdrawalAddress, coinOut)
+ .then(function onSuccess(response) {
+ if (response && response.isvalid) {
+ // Prepare the transaction shapeshift side
+ shapeshiftApiService.NormalTx(service).then(function onResponse(response) {
+ // If error, return it
+ if (!response || response.error) {
+ handleError(response, gettextCatalog.getString('Invalid response from Shapeshift'), cb);
+ } else {
+ var txData = response;
+
+ // If the content is not that it was expected, get back an error
+ if (!txData || !txData.orderId || !txData.deposit) {
+ cb(new Error(gettextCatalog.getString('Invalid response from Shapeshift')));
+ } else {
+ // Get back the data
+ service.depositInfo = txData;
+ var shapeshiftData = {
+ coinIn: coinIn,
+ coinOut: coinOut,
+ toWalletId: service.toWalletId,
+ minAmount: service.marketData.minimum,
+ maxAmount: service.marketData.maxLimit,
+ orderId: txData.orderId,
+ toAddress: txData.deposit
+ };
+ cb(null, shapeshiftData);
+ }
+ }
+ });
+ } else {
+ cb(new Error(gettextCatalog.getString('Invalid address')));
+ }
+ });
+ }
+ }
+ }
+})();
\ No newline at end of file
diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js
deleted file mode 100644
index 1ce9672ce..000000000
--- a/src/js/services/shapeshiftService.js
+++ /dev/null
@@ -1,141 +0,0 @@
-'use strict';
-
-angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) {
- var root = {};
- root.ShiftState = 'Shift';
- root.coinIn = '';
- root.coinOut = '';
- root.withdrawalAddress = '';
- root.returnAddress = '';
- root.amount = '';
- root.marketData = {};
-
- root.getMarketDataIn = function (coin) {
- if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn);
- return root.getMarketData(coin, root.coinOut);
- };
- root.getMarketDataOut = function (coin) {
- if (coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn);
- return root.getMarketData(root.coinIn, coin);
- };
- root.getMarketData = function (coinIn, coinOut, cb) {
- root.coinIn = coinIn;
- root.coinOut = coinOut;
- if (root.coinIn === undefined || root.coinOut === undefined) return;
- shapeshiftApiService
- .marketInfo(root.coinIn, root.coinOut)
- .then(function (marketData) {
- root.marketData = marketData;
- root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
- if (cb) {
- cb(marketData);
- }
- });
- };
-
- /*shapeshiftApiService.coins().then(function(coins){
- root.coins = coins;
- root.coinIn = coins['BTC'].symbol;
- root.coinOut = coins['BCH'].symbol;
- root.getMarketData(root.coinIn, root.coinOut);
- });*/
-
- root.coins = {
- 'BTC': {name: 'Bitcoin', symbol: 'BTC'},
- 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
- };
-
- function checkForError(data) {
- if (data.err) return true;
- return false;
- }
-
- root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) {
- ongoingProcess.set('connectingShapeshift', true);
- root.withdrawalAddress = withdrawalAddress;
- root.returnAddress = returnAddress;
- root.coinIn = coinIn;
- root.coinOut = coinOut;
- shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) {
- var tx = ShapeShift();
- var coin;
- console.log("Starting");
- tx.then(function (txData) {
- console.log("Got txData", txData);
- if (txData['fixedTxData']) {
- txData = txData.fixedTxData;
- if (checkForError(txData)) return cb(txData.err);
- //console.log(txData)
- var coinPair = txData.pair.split('_');
- txData.depositType = coinPair[0].toUpperCase();
- txData.withdrawalType = coinPair[1].toUpperCase();
- coin = root.coins[txData.depositType].name.toLowerCase();
-
- txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount;
-
- root.txFixedPending = true;
-
- } else if (txData['normalTxData']) {
- txData = txData.normalTxData;
- if (checkForError(txData)) return cb(txData.err);
- coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase();
- txData.depositQR = coin + ":" + txData.deposit;
- } else if (txData['cancelTxData']) {
- txData = txData.cancelTxData;
- if (checkForError(txData)) return cb(txData.err);
- if (root.txFixedPending) {
- root.txFixedPending = false;
- }
- root.ShiftState = 'Shift';
- }
- root.depositInfo = txData;
- //console.log(root.marketData);
- //console.log(root.depositInfo);
- var sendAddress = txData.depositQR;
- if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0)
- sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash');
-
- ongoingProcess.set('connectingShapeshift', false);
-
- root.ShiftState = 'Cancel';
- //root.GetStatus();
- //root.txInterval=$interval(root.GetStatus, 8000);
-
- var shapeshiftData = {
- coinIn: coinIn,
- coinOut: coinOut,
- toWalletId: root.toWalletId,
- minAmount: root.marketData.minimum,
- maxAmount: root.marketData.maxLimit,
- orderId: root.depositInfo.orderId,
- toAddress: txData.deposit
- };
- //
- // if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
- ongoingProcess.set('connectingShapeshift', false);
- // return;
- // }
- cb(null, shapeshiftData);
- });
- })
- };
-
- function ShapeShift() {
- if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root);
- return shapeshiftApiService.NormalTx(root);
- }
-
- root.GetStatus = function () {
- var address = root.depositInfo.deposit
- shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function (data) {
- root.DepositStatus = data;
- if (root.DepositStatus.status === 'complete') {
- $interval.cancel(root.txInterval);
- root.depositInfo = null;
- root.ShiftState = 'Shift'
- }
- });
- };
-
- return root;
-});
diff --git a/src/sass/buttons.scss b/src/sass/buttons.scss
index df0ce8945..886d730c7 100644
--- a/src/sass/buttons.scss
+++ b/src/sass/buttons.scss
@@ -1,5 +1,5 @@
%button-standard {
- width: 85%;
+ width: 90%;
max-width: 300px;
margin-left: auto;
margin-right: auto;
diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss
index e09af4be3..5c9488b60 100644
--- a/src/sass/components/fee-summary.scss
+++ b/src/sass/components/fee-summary.scss
@@ -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: '';
diff --git a/src/sass/mixins/layout.scss b/src/sass/mixins/layout.scss
index 269a50320..e7a1af9da 100644
--- a/src/sass/mixins/layout.scss
+++ b/src/sass/mixins/layout.scss
@@ -59,6 +59,8 @@
.button {
font-weight: bold;
font-size: 19px;
+ line-height: 26px;
+ padding: 8px 6px;
}
}
.button-first-contact img {
diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss
index c530a1cef..474e1ea3f 100644
--- a/src/sass/views/review.scss
+++ b/src/sass/views/review.scss
@@ -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 {
diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss
index 55080ab7b..f9ace4655 100644
--- a/src/sass/views/tab-home.scss
+++ b/src/sass/views/tab-home.scss
@@ -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 {
diff --git a/src/sass/views/tab-send.scss b/src/sass/views/tab-send.scss
index bf77984fd..564e90d66 100644
--- a/src/sass/views/tab-send.scss
+++ b/src/sass/views/tab-send.scss
@@ -105,7 +105,7 @@
width: auto;
margin: 2px 0 4px;
}
- height: 60px;
+ min-height: 65px;
line-height: 16px;
margin-right: 0px;
width: 95%;
diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss
index 858229d85..6126c6b8b 100644
--- a/src/sass/views/walletDetails.scss
+++ b/src/sass/views/walletDetails.scss
@@ -1,3 +1,4 @@
+$wallet-details-collapse-transition: all 0.25s ease-in-out;
.wallet-details {
&__tx-amount {
font-size: 16px;
@@ -137,6 +138,20 @@
margin-top: 20px;
margin-top: env(safe-area-inset-top);
}
+ &.collapse {
+ ion-content {
+ margin-top: 40px;
+ }
+ .amount {
+ &__scale, &__error {
+ -webkit-transform: scale3d(0.5, 0.5, 0.5) translateY(0px);
+ transform: scale3d(0.5, 0.5, 0.5) translateY(0px);
+ }
+ }
+ .amount-alternative, .send-receive-buttons, .wallet-details-wallet-info {
+ opacity: 0;
+ }
+ }
}
.bar-header {
border: 0;
@@ -152,13 +167,14 @@
background-color: inherit !important;
}
ion-content {
-
- &.collapsible {
- margin-top: 230px;
- }
-
padding-top: 0;
top: 0;
+ transition: $wallet-details-collapse-transition;
+
+ margin-top: 185px;
+ @media only screen and (max-height:500px) {
+ margin-top: 165px;
+ }
margin-bottom: 16px;
.scroll {
@@ -199,7 +215,7 @@
width: 100%;
position: absolute;
bottom: 20px;
-
+ transition: $wallet-details-collapse-transition;
>.col {
padding: 5px 10px;
margin-bottom: 0;
@@ -207,30 +223,37 @@
.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;
}
}
}
.amount {
- width: 100%;
- text-align: center;
- color: #fff;
- height: 230px;
- padding-top: 40px;
- display: block;
align-items: center;
+ color: #fff;
+ display: block;
+
+ height: 230px;
+ @media only screen and (max-height:500px) {
+ height: 210px;
+ }
+
justify-content: center;
+ padding-top: 40px;
+ text-align: center;
+ transition: $wallet-details-collapse-transition;
+ width: 100%;
&__balance {
-webkit-transform: scale3d(1, 1, 1) translateY(45px);
transform: scale3d(1, 1, 1) translateY(45px);
+ transition: $wallet-details-collapse-transition;
}
&__updating {
@@ -240,6 +263,7 @@
&-alternative {
line-height: 36px;
+ transition: $wallet-details-collapse-transition;
}
&__button-balance {
@@ -255,6 +279,7 @@
&__error {
font-size: 14px;
padding: 35px 20px;
+ opacity: 1;
}
}
diff --git a/www/views/includes/community.html b/www/views/includes/community.html
index 86841a77c..ad7dcb169 100644
--- a/www/views/includes/community.html
+++ b/www/views/includes/community.html
@@ -26,7 +26,7 @@
- Share the Wallet App
+ Share the Wallet App
diff --git a/www/views/includes/incomingDataMenu.html b/www/views/includes/incomingDataMenu.html
index ca4b78dfc..e60d7e956 100644
--- a/www/views/includes/incomingDataMenu.html
+++ b/www/views/includes/incomingDataMenu.html
@@ -9,21 +9,21 @@
-
-
-
-
+
+
+
+
+
+
+
- No Bitcoin Cash wallet to transfer funds to found.
+ No Bitcoin Cash wallet to transfer funds to found.
-
No Bitcoin Cash found
+ No Bitcoin Cash found.
-
Bitcoin found:
+
Bitcoin Core found:
{{btcBalanceText}}
@@ -95,13 +95,13 @@
- No Bitcoin wallet to transfer funds to found.
+ No Bitcoin Core wallet to transfer funds to found.
-
No Bitcoin found
+ No Bitcoin Core found.
-
+
-
+
-
+
{{updateStatusError}}
-
+
This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.
+ class="amount__balance amount__scale">
+ ng-if="status.totalBalanceStr && wallet.network == 'livenet'">
+ ng-if="status.totalBalanceStr && selectedPriceDisplay=='crypto' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
+ class="amount__balance amount__scale">
+ ng-if="status.totalBalanceAlternative && wallet.network == 'livenet'">
-