Merge pull request #331 from Bitcoin-com/wallet/sprint/21
Wallet/sprint/21
154
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']);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -85,6 +85,12 @@
|
|||
<config-file platform="ios" target="*-Info.plist" parent="UIStatusBarHidden"><true/></config-file>
|
||||
<config-file platform="ios" target="*-Info.plist" parent="UIViewControllerBasedStatusBarAppearance"><false/></config-file>
|
||||
<config-file target="*-Info.plist" parent="ITSAppUsesNonExemptEncryption"><false/></config-file>
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon24x24@2x.png" width="48" height="48" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon27.5x27.5@2x.png" width="55" height="55" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon44x44@2x.png" width="88" height="88" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon86x86@2x.png" width="172" height="172" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/AppIcon98x98@2x.png" width="196" height="196" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-20.png" width="20" height="20" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-60@3x.png" width="180" height="180" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-60.png" width="60" height="60" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-60@2x.png" width="120" height="120" />
|
||||
|
|
@ -102,6 +108,7 @@
|
|||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-small@3x.png" width="87" height="87" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-50.png" width="50" height="50" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-50@2x.png" width="100" height="100" />
|
||||
<icon src="resources/*PACKAGENAME*/ios/icon/icon-1024.png" width="1024" height="1024" />
|
||||
|
||||
<splash src="resources/*PACKAGENAME*/ios/splash/Default~iphone.png" width="320" height="480"/>
|
||||
<splash src="resources/*PACKAGENAME*/ios/splash/Default@2x~iphone.png" width="640" height="960"/>
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ ln -s ../resources/bitcoin.com/mac/pkg/build.cfg build.cfg
|
|||
rm build_mas.py
|
||||
ln -s ../resources/bitcoin.com/mac/pkg/build_mas.py build_mas.py
|
||||
|
||||
echo "Signing ${APP_NAME}"
|
||||
export APP_PATH="pkg/${APP_NAME}/osx64/${APP_NAME}"
|
||||
echo "Signing ${APP_FULLNAME}"
|
||||
export CURRENT_PATH=`pwd`
|
||||
export APP_PATH="pkg/${APP_FULLNAME}/osx64/${APP_FULLNAME}"
|
||||
export TMP_PATH="tmp"
|
||||
export DIST_PATH="dist"
|
||||
|
||||
|
|
@ -36,7 +37,13 @@ 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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
@ -3850,3 +3859,75 @@ msgstr ""
|
|||
#: src/js/services/incomingData.js:129
|
||||
msgid "This invoice is no longer accepting payments"
|
||||
msgstr ""
|
||||
|
||||
#: www/views/amount.html.js:60
|
||||
msgid "Send Maximum Amount"
|
||||
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 ""
|
||||
BIN
resources/bitcoin.com/ios/icon/AppIcon24x24@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon27.5x27.5@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon44x44@2x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon86x86@2x.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
resources/bitcoin.com/ios/icon/AppIcon98x98@2x.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
resources/bitcoin.com/ios/icon/icon-1024.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
resources/bitcoin.com/ios/icon/icon-20.png
Normal file
|
After Width: | Height: | Size: 425 B |
|
|
@ -178,10 +178,16 @@ 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))
|
||||
|
||||
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 -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, helperApp))
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<string>$GROUPID</string>
|
||||
<array>
|
||||
<string>299HJ3G3BP.com.bitcoin.mwallet.mac</string>
|
||||
</array>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ var modules = [
|
|||
'copayApp.controllers',
|
||||
'copayApp.directives',
|
||||
'copayApp.addons',
|
||||
'bitcoincom.directives'
|
||||
'bitcoincom.controllers',
|
||||
'bitcoincom.directives',
|
||||
'bitcoincom.services'
|
||||
];
|
||||
|
||||
var copayApp = window.copayApp = angular.module('copayApp', modules);
|
||||
|
|
@ -29,4 +31,6 @@ angular.module('copayApp.services', []);
|
|||
angular.module('copayApp.controllers', []);
|
||||
angular.module('copayApp.directives', []);
|
||||
angular.module('copayApp.addons', []);
|
||||
angular.module('bitcoincom.controllers', []);
|
||||
angular.module('bitcoincom.directives', []);
|
||||
angular.module('bitcoincom.services', []);
|
||||
|
|
|
|||
|
|
@ -21,28 +21,14 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
|
|||
});
|
||||
|
||||
$scope.sendTo = function() {
|
||||
$ionicHistory.removeBackView();
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
$timeout(function() {
|
||||
var to = '';
|
||||
if ($scope.addressbookEntry.coin == 'bch') {
|
||||
var a = 'bitcoincash:' + $scope.addressbookEntry.address;
|
||||
to = bitcoinCashJsService.readAddress(a).legacy;
|
||||
} else {
|
||||
to = $scope.addressbookEntry.address;
|
||||
}
|
||||
|
||||
var stateParams = {
|
||||
toAddress: to,
|
||||
data: $scope.addressbookEntry.address,
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('amountController', amountController);
|
||||
(function(){
|
||||
|
||||
function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) {
|
||||
angular
|
||||
.module('bitcoincom.controllers')
|
||||
.controller('amountController', amountController);
|
||||
|
||||
function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, popupService, profileService, walletService, $window) {
|
||||
var vm = this;
|
||||
|
||||
// Variables
|
||||
vm.allowSend = false;
|
||||
vm.altCurrencyList = [];
|
||||
vm.alternativeAmount = '';
|
||||
vm.alternativeUnit = '';
|
||||
vm.amount = '0';
|
||||
vm.availableFunds = '';
|
||||
vm.canSendAllAvailableFunds = true;
|
||||
vm.errorMessage = '';
|
||||
// Use insufficient for logic, as when the amount is invalid, funds being
|
||||
// either sufficent or insufficient doesn't make sense.
|
||||
vm.fundsAreInsufficient = false;
|
||||
|
|
@ -20,9 +27,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
vm.lastUsedPopularList = [];
|
||||
vm.maxAmount = 0;
|
||||
vm.minAmount = 0;
|
||||
vm.sendableFunds = '';
|
||||
vm.showSendMaxButton = false;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
vm.thirdParty = false;
|
||||
vm.unit = '';
|
||||
|
||||
// Functions
|
||||
vm.changeUnit = changeUnit;
|
||||
vm.close = close;
|
||||
vm.findCurrency = findCurrency;
|
||||
|
|
@ -35,7 +46,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
vm.removeDigit = removeDigit;
|
||||
vm.save = save;
|
||||
vm.sendMax = sendMax;
|
||||
vm.errorMessage = '';
|
||||
|
||||
|
||||
$scope.$on('$ionicView.beforeEnter', onBeforeEnter);
|
||||
$scope.$on('$ionicView.leave', onLeave);
|
||||
|
|
@ -46,10 +57,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
var altCurrencyModal = null;
|
||||
var altUnitIndex = 0;
|
||||
var availableFundsInCrypto = '';
|
||||
var availableFundsInFiat = '';
|
||||
var availableSatoshis = null;
|
||||
var availableUnits = [];
|
||||
var canSendMax = true;
|
||||
var fiatCode;
|
||||
var isNW = platformInfo.isNW;
|
||||
var isAndroid = platformInfo.isAndroid;
|
||||
|
|
@ -57,10 +66,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
var lastUsedAltCurrencyList = [];
|
||||
var passthroughParams = {};
|
||||
var satToUnit;
|
||||
var transactionSendableAmount = {
|
||||
crypto: '',
|
||||
satoshis: null
|
||||
};
|
||||
var unitDecimals;
|
||||
var unitIndex = 0;
|
||||
var unitToSatoshi;
|
||||
var useSendMax = false;
|
||||
var walletSpendableAmount = {
|
||||
crypto: '',
|
||||
satoshis: null
|
||||
};
|
||||
|
||||
function onLeave() {
|
||||
angular.element($window).off('keydown');
|
||||
|
|
@ -68,42 +85,26 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
function onBeforeEnter(event, data) {
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.popState();
|
||||
sendFlowService.state.pop();
|
||||
}
|
||||
console.log('amount onBeforeEnter after back sendflow ', sendFlowService.state);
|
||||
|
||||
initCurrencies();
|
||||
|
||||
passthroughParams = sendFlowService.getStateClone();
|
||||
passthroughParams = sendFlowService.state.getClone();
|
||||
console.log('amount onBeforeEnter after back sendflow ', passthroughParams);
|
||||
|
||||
vm.fromWalletId = passthroughParams.fromWalletId;
|
||||
vm.toWalletId = passthroughParams.toWalletId;
|
||||
vm.minAmount = parseFloat(passthroughParams.minAmount);
|
||||
vm.maxAmount = parseFloat(passthroughParams.maxAmount);
|
||||
|
||||
if (passthroughParams.thirdParty) {
|
||||
vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object
|
||||
if (vm.thirdParty) {
|
||||
if (vm.thirdParty.id === 'shapeshift') {
|
||||
if (!vm.thirdParty.data) {
|
||||
vm.thirdParty.data = {};
|
||||
}
|
||||
vm.thirdParty.data['fromWalletId'] = vm.fromWalletId;
|
||||
|
||||
vm.fromWallet = profileService.getWallet(vm.fromWalletId);
|
||||
vm.toWallet = profileService.getWallet(vm.toWalletId);
|
||||
|
||||
shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) {
|
||||
vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum);
|
||||
vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId;
|
||||
vm.showSendMaxButton = !vm.isRequestingSpecificAmount;
|
||||
|
||||
var config = configService.getSync().wallet.settings;
|
||||
unitToSatoshi = config.unitToSatoshi;
|
||||
satToUnit = 1 / unitToSatoshi;
|
||||
unitDecimals = config.unitDecimals;
|
||||
|
||||
setAvailableUnits();
|
||||
updateUnitUI();
|
||||
|
|
@ -112,7 +113,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) {
|
||||
|
|
@ -135,9 +136,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
});
|
||||
}
|
||||
|
||||
unitToSatoshi = config.unitToSatoshi;
|
||||
satToUnit = 1 / unitToSatoshi;
|
||||
unitDecimals = config.unitDecimals;
|
||||
|
||||
resetAmount();
|
||||
|
||||
|
|
@ -210,11 +208,46 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
var fromWallet = profileService.getWallet(passthroughParams.fromWalletId);
|
||||
updateAvailableFundsFromWallet(fromWallet);
|
||||
}
|
||||
|
||||
if (passthroughParams.thirdParty) {
|
||||
vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object
|
||||
if (vm.thirdParty) {
|
||||
initShapeshift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
function initShapeshift() {
|
||||
if (vm.thirdParty.id === 'shapeshift') {
|
||||
vm.thirdParty.data = vm.thirdParty.data || {};
|
||||
|
||||
vm.fromWallet = profileService.getWallet(vm.fromWalletId);
|
||||
vm.toWallet = profileService.getWallet(vm.toWalletId);
|
||||
|
||||
vm.showSendMaxButton = false;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
vm.canSendAllAvailableFunds = false;
|
||||
|
||||
ongoingProcess.set('connectingShapeshift', true);
|
||||
shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(err, data) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function paste(value) {
|
||||
|
|
@ -232,8 +265,28 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
}
|
||||
|
||||
function sendMax() {
|
||||
if (canSendMax) {
|
||||
useSendMax = true;
|
||||
finish();
|
||||
} else {
|
||||
var transactionSendableAmountInUnits = transactionSendableAmount.satoshis * satToUnit;
|
||||
if (vm.minAmount && transactionSendableAmountInUnits < vm.minAmount) {
|
||||
popupService.showAlert(
|
||||
gettextCatalog.getString('Insufficient funds'),
|
||||
gettextCatalog.getString('Amount below minimum allowed')
|
||||
);
|
||||
} else {
|
||||
// Need to be precise, so use crypto directly rather than fiat with exchange rate
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
var tempIndex = altUnitIndex;
|
||||
altUnitIndex = unitIndex;
|
||||
unitIndex = tempIndex;
|
||||
}
|
||||
vm.amount = transactionSendableAmountInUnits.toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT);
|
||||
useSendMax = true;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateUnitUI() {
|
||||
|
|
@ -353,8 +406,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
amountInCrypto = a;
|
||||
var amountInSatoshis = a * unitToSatoshi;
|
||||
vm.fundsAreInsufficient = !!passthroughParams.fromWalletId
|
||||
&& availableSatoshis !== null
|
||||
&& availableSatoshis < amountInSatoshis;
|
||||
&& walletSpendableAmount.satoshis !== null
|
||||
&& walletSpendableAmount.satoshis < amountInSatoshis;
|
||||
|
||||
vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true);
|
||||
vm.allowSend = lodash.isNumber(a)
|
||||
|
|
@ -374,8 +427,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
} else {
|
||||
amountInCrypto = result;
|
||||
vm.fundsAreInsufficient = passthroughParams.fromWalletId
|
||||
&& availableSatoshis !== null
|
||||
&& availableSatoshis < result * unitToSatoshi;
|
||||
&& walletSpendableAmount.satoshis !== null
|
||||
&& walletSpendableAmount.satoshis < result * unitToSatoshi;
|
||||
|
||||
vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result));
|
||||
vm.allowSend = lodash.isNumber(result)
|
||||
|
|
@ -449,13 +502,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
var satoshis = 0;
|
||||
if (unit.isFiat) {
|
||||
satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0);
|
||||
satoshis = Math.floor(fromFiat(uiAmount) * unitToSatoshi);
|
||||
} else {
|
||||
satoshis = (uiAmount * unitToSatoshi).toFixed(0);
|
||||
satoshis = Math.floor(uiAmount * unitToSatoshi);
|
||||
}
|
||||
|
||||
var confirmData = {
|
||||
amount: useSendMax ? undefined : satoshis,
|
||||
amount: (useSendMax && canSendMax) ? undefined : satoshis,
|
||||
displayAddress: passthroughParams.displayAddress,
|
||||
fromWalletId: passthroughParams.fromWalletId,
|
||||
sendMax: useSendMax,
|
||||
|
|
@ -467,12 +520,11 @@ 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);
|
||||
$scope.useSendMax = null;
|
||||
sendFlowService.goNext(confirmData);
|
||||
useSendMax = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -590,56 +642,133 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
}
|
||||
|
||||
function updateAvailableFundsStringIfNeeded() {
|
||||
if (passthroughParams.fromWalletId && availableSatoshis !== null) {
|
||||
availableFundsInFiat = '';
|
||||
vm.availableFunds = availableFundsInCrypto;
|
||||
if (passthroughParams.fromWalletId && walletSpendableAmount.satoshis !== null) {
|
||||
vm.availableFunds = walletSpendableAmount.crypto;
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
var coin = availableUnits[altUnitIndex].id;
|
||||
txFormatService.formatAlternativeStr(coin, availableSatoshis, function formatCallback(formatted){
|
||||
if (formatted) {
|
||||
availableFundsInFiat = formatted;
|
||||
txFormatService.formatAlternativeStr(coin, walletSpendableAmount.satoshis, function formatCallback(formatted){
|
||||
|
||||
if (formatted) {
|
||||
$scope.$apply(function() {
|
||||
vm.availableFunds = availableFundsInFiat;
|
||||
vm.availableFunds = formatted;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
updateMaximumButtonIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvailableFundsFromWallet(wallet) {
|
||||
console.log('amount updateAvailableFundsFromWallet()');
|
||||
var availableFundsInFiat = '';
|
||||
if (wallet.status && wallet.status.isValid) {
|
||||
availableFundsInCrypto = wallet.status.spendableBalanceStr;
|
||||
availableSatoshis = wallet.status.spendableAmount;
|
||||
walletSpendableAmount.crypto = wallet.status.spendableBalanceStr;
|
||||
walletSpendableAmount.satoshis = wallet.status.spendableAmount;
|
||||
if (wallet.status.alternativeBalanceAvailable) {
|
||||
availableFundsInFiat = wallet.status.spendableBalanceAlternative + ' ' + wallet.status.alternativeIsoCode;
|
||||
} else {
|
||||
availableFundsInFiat = '';
|
||||
}
|
||||
|
||||
} else if (wallet.cachedStatus && wallet.status.isValid) {
|
||||
} else if (wallet.cachedStatus && wallet.cachedStatus.isValid) {
|
||||
|
||||
if (wallet.cachedStatus.alternativeBalanceAvailable) {
|
||||
availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode;
|
||||
} else {
|
||||
availableFundsInFiat = '';
|
||||
}
|
||||
availableFundsInCrypto = wallet.cachedStatus.spendableBalanceStr;
|
||||
availableSatoshis = wallet.cachedStatus.spendableAmount;
|
||||
walletSpendableAmount.crypto = wallet.cachedStatus.spendableBalanceStr;
|
||||
walletSpendableAmount.satoshis = wallet.cachedStatus.spendableAmount;
|
||||
|
||||
} else {
|
||||
|
||||
availableFundsInFiat = '';
|
||||
availableFundsInCrypto = '';
|
||||
availableSatoshis = null;
|
||||
walletSpendableAmount.crypto = '';
|
||||
walletSpendableAmount.satoshis = null;
|
||||
}
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
vm.availableFunds = availableFundsInFiat || availableFundsInCrypto;
|
||||
vm.availableFunds = availableFundsInFiat || walletSpendableAmount.crypto;
|
||||
} else {
|
||||
vm.availableFunds = availableFundsInCrypto;
|
||||
vm.availableFunds = walletSpendableAmount.crypto;
|
||||
}
|
||||
|
||||
setMaximumButtonFromWallet(wallet);
|
||||
}
|
||||
|
||||
function updateMaximumButtonIfNeeded() {
|
||||
console.log('sendmax updateMaximumButtonIfNeeded()');
|
||||
if (vm.showSendMaxButton || vm.showSendLimitMaxButton) {
|
||||
transactionSendableAmount.fiat = '';
|
||||
vm.sendableFunds = transactionSendableAmount.crypto;
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
var coin = availableUnits[altUnitIndex].id;
|
||||
txFormatService.formatAlternativeStr(coin, transactionSendableAmount.satoshis, function formatCallback(formatted){
|
||||
if (formatted) {
|
||||
$scope.$apply(function onApply() {
|
||||
vm.sendableFunds = formatted;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setMaximumButtonFromWallet(wallet) {
|
||||
console.log('sendmax setMaximumButtonFromWallet()');
|
||||
var minSatoshis = vm.minAmount * unitToSatoshi;
|
||||
var maxSatoshis = vm.maxAmount * unitToSatoshi;
|
||||
|
||||
if (minSatoshis > walletSpendableAmount.satoshis) {
|
||||
console.log('sendmax Hiding max buttons as minimum is too high.');
|
||||
canSendMax = false;
|
||||
vm.showSendMaxButton = true;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
transactionSendableAmount.satoshis = walletSpendableAmount.satoshis;
|
||||
|
||||
} else if (maxSatoshis) {
|
||||
if (walletSpendableAmount.satoshis > maxSatoshis) {
|
||||
console.log('sendmax Showing max limit button as available is greater than max limit.');
|
||||
canSendMax = false;
|
||||
vm.showSendMaxButton = false;
|
||||
vm.showSendLimitMaxButton = true;
|
||||
transactionSendableAmount.satoshis = maxSatoshis;
|
||||
} else {
|
||||
console.log('sendmax Showing sendmax as all available as less than max limit.');
|
||||
// Enabling send max here is a little dangerous, if they receive funds between pressing
|
||||
// this and the calculation in the Review screen.
|
||||
canSendMax = false;
|
||||
vm.showSendMaxButton = true;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
transactionSendableAmount.satoshis = walletSpendableAmount.satoshis;
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('sendmax Showing sendmax as all available because no limits.');
|
||||
canSendMax = true;
|
||||
vm.showSendMaxButton = true;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
transactionSendableAmount.satoshis = walletSpendableAmount.satoshis;
|
||||
}
|
||||
|
||||
if (vm.showSendMaxButton || vm.showSendLimitMaxButton) {
|
||||
console.log('sendmax Setting max button text');
|
||||
transactionSendableAmount.crypto = txFormatService.formatAmountStr(wallet.coin, transactionSendableAmount.satoshis);
|
||||
vm.sendableFunds = transactionSendableAmount.crypto;
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
txFormatService.formatAlternativeStr(wallet.coin, transactionSendableAmount.satoshis, function onFormat(formatted){
|
||||
if (formatted) {
|
||||
$scope.$apply(function onApply() {
|
||||
vm.sendableFunds = formatted;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,12 +1,20 @@
|
|||
describe('amountController', function(){
|
||||
var configCache,
|
||||
configService,
|
||||
gettextCatalog,
|
||||
$controller,
|
||||
$ionicHistory,
|
||||
$rootScope,
|
||||
ongoingProcess,
|
||||
platformInfo,
|
||||
popupService,
|
||||
profileService,
|
||||
rateService,
|
||||
sendFlowService,
|
||||
shapeshiftService,
|
||||
txFormatService,
|
||||
$scope,
|
||||
$state,
|
||||
$stateParams;
|
||||
|
||||
|
||||
|
|
@ -18,7 +26,7 @@ describe('amountController', function(){
|
|||
configCache = {
|
||||
wallet: {
|
||||
settings: {
|
||||
|
||||
unitToSatoshi: 100000000
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -31,18 +39,42 @@ describe('amountController', function(){
|
|||
});
|
||||
configService.getSync.and.returnValue(configCache);
|
||||
|
||||
gettextCatalog = jasmine.createSpyObj(['getString']);
|
||||
gettextCatalog.getString.and.callFake(function(str){ return str; });
|
||||
$ionicHistory = jasmine.createSpyObj(['backView']);
|
||||
|
||||
ongoingProcess = jasmine.createSpyObj(['set']);
|
||||
|
||||
platformInfo = {
|
||||
isChromeApp: false,
|
||||
isAndroid: false,
|
||||
isIos: true
|
||||
};
|
||||
popupService = jasmine.createSpyObj(['showAlert']);
|
||||
profileService = jasmine.createSpyObj(['getWallet', 'getWallets']);
|
||||
|
||||
profileService = jasmine.createSpyObj(['getWallets']);
|
||||
rateService = jasmine.createSpyObj(['fromFiat', 'listAlternatives', 'updateRates', 'whenAvailable']);
|
||||
sendFlowService = jasmine.createSpyObj(['getStateClone', 'pushState']);
|
||||
shapeshiftService = jasmine.createSpyObj(['getMarketData']);
|
||||
txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']);
|
||||
|
||||
rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']);
|
||||
txFormatService.formatAlternativeStr.and.callFake(function(coin, satoshis, cb) {
|
||||
if (typeof satoshis !== "number") {
|
||||
throw "satoshis in formatAlternativeStr() is not a number."
|
||||
}
|
||||
var units = satoshis / 100000000;
|
||||
var formatted = (units * 10000).toFixed(2) + ' USD';
|
||||
cb(formatted);
|
||||
});
|
||||
|
||||
txFormatService.formatAmountStr.and.callFake(function(coin, satoshis) {
|
||||
if (typeof satoshis !== "number") {
|
||||
throw "satoshis in formatAmountStr() is not a number."
|
||||
}
|
||||
return (satoshis * 100000000).toFixed(8) + ' ' + (coin || 'bch').toUpperCase();
|
||||
});
|
||||
|
||||
$state = jasmine.createSpyObj(['transitionTo']);
|
||||
$stateParams = {};
|
||||
|
||||
inject(function(_$controller_, _$rootScope_){
|
||||
|
|
@ -61,12 +93,184 @@ describe('amountController', function(){
|
|||
stateName: 'ignoreme'
|
||||
};
|
||||
$ionicHistory.backView.and.returnValue(backView);
|
||||
|
||||
var wallet = {
|
||||
status: {
|
||||
isValid: true,
|
||||
spendableAmount: 123456
|
||||
}
|
||||
};
|
||||
profileService.getWallet.and.returnValue(wallet);
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: {},
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
//expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||
//expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Shapeshift', function() {
|
||||
var walletFrom;
|
||||
var walletTo;
|
||||
|
||||
beforeEach(function(){
|
||||
walletFrom = {};
|
||||
walletTo = {};
|
||||
|
||||
profileService.getWallet.and.callFake(function(walletId){
|
||||
if (walletId === '4cd7673e-7320-4dfa-86e5-d4edb51d460a') {
|
||||
return walletFrom;
|
||||
} else if (walletId === 'bf00af8f-0788-4b57-b30a-0390747407e9') {
|
||||
return walletTo;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
rateService.listAlternatives.and.returnValue([
|
||||
{name: "Australian Dollar", isoCode: "AUD"},
|
||||
{name: "United States Dollar", isoCode: "USD"}
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it ('with available balance below limit, shows sendMax for triggering alert', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 789
|
||||
};
|
||||
walletTo.coin = 'bch';
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
rateService.whenAvailable.and.callFake(function(cb){
|
||||
cb();
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
sendMax: false,
|
||||
thirdParty: {
|
||||
id: 'shapeshift',
|
||||
data: {},
|
||||
},
|
||||
toAddress: '',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
var reqCoinIn = '';
|
||||
var reqCoinOut = '';
|
||||
shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){
|
||||
reqCoinIn = coinIn;
|
||||
reqCoinOut = coinOut;
|
||||
cb({
|
||||
maxLimit: '0.6846239',
|
||||
minimum: '0.00013692'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(rateService.updateRates.calls.any()).toEqual(true);
|
||||
|
||||
expect(reqCoinIn).toBe('btc');
|
||||
expect(reqCoinOut).toBe('bch');
|
||||
|
||||
expect(amountController.maxAmount).toBe(0.68462390);
|
||||
expect(amountController.minAmount).toBe(0.00013692);
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
expect(amountController.sendableFunds).toEqual('0.08 USD');
|
||||
|
||||
// Now hit the Send Max button
|
||||
amountController.sendMax();
|
||||
|
||||
expect(popupService.showAlert.calls.argsFor(0)[0]).toEqual('Insufficient funds');
|
||||
expect(popupService.showAlert.calls.argsFor(0)[1]).toEqual('Amount below minimum allowed');
|
||||
expect(sendFlowService.pushState.calls.any()).toEqual(false);
|
||||
expect($state.transitionTo.calls.any()).toEqual(false);
|
||||
});
|
||||
|
||||
it ('with available balance between limits, uses sendMax', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 456789
|
||||
};
|
||||
walletTo.coin = 'bch';
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: {},
|
||||
|
|
@ -74,28 +278,327 @@ describe('amountController', function(){
|
|||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: {},
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
$state: {},
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: {},
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var data = {
|
||||
stateParams: {
|
||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||
}
|
||||
};
|
||||
$scope.$emit('$ionicView.beforeEnter', data);
|
||||
rateService.whenAvailable.and.callFake(function(cb){
|
||||
cb();
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
sendMax: false,
|
||||
thirdParty: {
|
||||
id: 'shapeshift',
|
||||
data: {},
|
||||
},
|
||||
toAddress: '',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
var reqCoinIn = '';
|
||||
var reqCoinOut = '';
|
||||
shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){
|
||||
reqCoinIn = coinIn;
|
||||
reqCoinOut = coinOut;
|
||||
cb({
|
||||
maxLimit: '0.6846239',
|
||||
minimum: '0.00013692'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(rateService.updateRates.calls.any()).toEqual(true);
|
||||
|
||||
expect(reqCoinIn).toBe('btc');
|
||||
expect(reqCoinOut).toBe('bch');
|
||||
|
||||
expect(amountController.maxAmount).toBe(0.68462390);
|
||||
expect(amountController.minAmount).toBe(0.00013692);
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
// Now hit the Send Max button
|
||||
var pushedState = null;
|
||||
sendFlowService.pushState.and.callFake(function (sendFlowState){
|
||||
pushedState = sendFlowState;
|
||||
});
|
||||
|
||||
amountController.sendMax();
|
||||
|
||||
expect(pushedState.amount).toBeUndefined();
|
||||
expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a');
|
||||
expect(pushedState.sendMax).toEqual(true);
|
||||
expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9');
|
||||
|
||||
expect(pushedState.thirdParty.id).toEqual('shapeshift');
|
||||
expect(pushedState.thirdParty.data.maxAmount).toEqual(0.6846239);
|
||||
expect(pushedState.thirdParty.data.minAmount).toEqual(0.00013692);
|
||||
|
||||
expect($state.transitionTo.calls.count()).toEqual(1);
|
||||
expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review');
|
||||
});
|
||||
|
||||
it ('with available balance higher than max, uses send limit max instead of sendMax', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 123456789
|
||||
};
|
||||
walletTo.coin = 'bch';
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: {},
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: {},
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
rateService.whenAvailable.and.callFake(function(cb){
|
||||
cb();
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
sendMax: false,
|
||||
thirdParty: {
|
||||
id: 'shapeshift',
|
||||
data: {},
|
||||
},
|
||||
toAddress: '',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
var reqCoinIn = '';
|
||||
var reqCoinOut = '';
|
||||
shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){
|
||||
reqCoinIn = coinIn;
|
||||
reqCoinOut = coinOut;
|
||||
cb({
|
||||
maxLimit: '0.6846239',
|
||||
minimum: '0.00013692'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(rateService.updateRates.calls.any()).toEqual(true);
|
||||
|
||||
expect(reqCoinIn).toBe('btc');
|
||||
expect(reqCoinOut).toBe('bch');
|
||||
|
||||
expect(amountController.maxAmount).toBe(0.6846239);
|
||||
expect(amountController.minAmount).toBe(0.00013692);
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(false);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(true);
|
||||
|
||||
// Now hit the Send Max button
|
||||
var pushedState = null;
|
||||
sendFlowService.pushState.and.callFake(function (sendFlowState){
|
||||
pushedState = sendFlowState;
|
||||
});
|
||||
|
||||
amountController.sendMax();
|
||||
|
||||
expect(pushedState.amount).toEqual(68462390);
|
||||
expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a');
|
||||
expect(pushedState.sendMax).toEqual(false);
|
||||
expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9');
|
||||
|
||||
expect(pushedState.thirdParty.id).toEqual('shapeshift');
|
||||
expect(pushedState.thirdParty.data.maxAmount).toEqual(0.6846239);
|
||||
expect(pushedState.thirdParty.data.minAmount).toEqual(0.00013692);
|
||||
|
||||
expect($state.transitionTo.calls.count()).toEqual(1);
|
||||
expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Wallet transfer', function() {
|
||||
var walletFrom;
|
||||
var walletTo;
|
||||
|
||||
beforeEach(function(){
|
||||
walletFrom = {};
|
||||
walletTo = {};
|
||||
|
||||
profileService.getWallet.and.callFake(function(walletId){
|
||||
if (walletId === '4cd7673e-7320-4dfa-86e5-d4edb51d460a') {
|
||||
return walletFrom;
|
||||
} else if (walletId === 'bf00af8f-0788-4b57-b30a-0390747407e9') {
|
||||
return walletTo;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
rateService.listAlternatives.and.returnValue([
|
||||
{name: "Australian Dollar", isoCode: "AUD"},
|
||||
{name: "United States Dollar", isoCode: "USD"}
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('wallet transfer send max.', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 123456789
|
||||
};
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
expect(amountController.sendableFunds).toEqual('12345.68 USD');
|
||||
|
||||
// Now hit the Send Max button
|
||||
var pushedState = null;
|
||||
sendFlowService.pushState.and.callFake(function (sendFlowState){
|
||||
pushedState = sendFlowState;
|
||||
});
|
||||
|
||||
amountController.sendMax();
|
||||
|
||||
expect(pushedState.amount).toBeUndefined();
|
||||
expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a');
|
||||
expect(pushedState.sendMax).toEqual(true);
|
||||
expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9');
|
||||
|
||||
expect($state.transitionTo.calls.count()).toEqual(1);
|
||||
expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review');
|
||||
});
|
||||
|
||||
|
||||
// This situation was seen in real life
|
||||
it('wallet transfer with valid cached status only.', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: false,
|
||||
};
|
||||
walletFrom.cachedStatus = {
|
||||
isValid: true,
|
||||
spendableAmount: 5678
|
||||
};
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
expect(amountController.sendableFunds).toEqual('0.57 USD');
|
||||
});
|
||||
|
||||
expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||
expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.controllers').controller('tourController',
|
||||
function($scope, $state, $log, $timeout, $filter, ongoingProcess, profileService, rateService, popupService, gettextCatalog, startupService, storageService, walletService, $q) {
|
||||
function ($scope, $state, $log, $timeout, $filter, ongoingProcess, configService, profileService, rateService, popupService, gettextCatalog, lodash, startupService, storageService, uxLanguage, walletService, $q) {
|
||||
|
||||
$scope.data = {
|
||||
index: 0
|
||||
|
|
@ -46,6 +46,28 @@ angular.module('copayApp.controllers').controller('tourController',
|
|||
creatingWallet = true;
|
||||
ongoingProcess.set('creatingWallet', true);
|
||||
$timeout(function() {
|
||||
uxLanguage.init(function(lang) {
|
||||
var rateCode = uxLanguage.getRateCode(lang);
|
||||
console.log("When Available: rateService");
|
||||
rateService.whenAvailable(function() {
|
||||
var alternatives = rateService.listAlternatives(true);
|
||||
|
||||
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);
|
||||
|
|
@ -64,7 +86,8 @@ angular.module('copayApp.controllers').controller('tourController',
|
|||
return $scope.createDefaultWallet();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
}
|
||||
;
|
||||
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
var bchWallet = walletClients[0];
|
||||
|
|
@ -73,8 +96,8 @@ angular.module('copayApp.controllers').controller('tourController',
|
|||
var btcWalletId = btcWallet.credentials.walletId;
|
||||
|
||||
function createAddressPromise(wallet) {
|
||||
return $q(function(resolve, reject) {
|
||||
walletService.getAddress(wallet, true, function(e, addr) {
|
||||
return $q(function (resolve, reject) {
|
||||
walletService.getAddress(wallet, true, function (e, addr) {
|
||||
if (e) reject(e);
|
||||
resolve(addr);
|
||||
});
|
||||
|
|
@ -92,16 +115,21 @@ angular.module('copayApp.controllers').controller('tourController',
|
|||
var btcAddressPromise = createAddressPromise(btcWallet);
|
||||
ongoingProcess.set('generatingNewAddress', true);
|
||||
|
||||
$q.all([bchAddressPromise, btcAddressPromise]).then(function(addresses) {
|
||||
$q.all([bchAddressPromise, btcAddressPromise]).then(function (addresses) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$state.go('tabs.home');
|
||||
}, function(e) {
|
||||
}, 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);
|
||||
});
|
||||
})
|
||||
}, 300);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,11 +78,15 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
|
||||
|
||||
function onBeforeEnter(event, data) {
|
||||
console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.state);
|
||||
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);
|
||||
} else {
|
||||
satoshis = sendFlowData.amount;
|
||||
}
|
||||
toAddress = sendFlowData.toAddress;
|
||||
destinationWalletId = sendFlowData.toWalletId;
|
||||
|
||||
|
|
@ -93,10 +97,31 @@ 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);
|
||||
}
|
||||
|
||||
function _next() {
|
||||
configService.get(function onConfig(err, configCache) {
|
||||
if (err) {
|
||||
$log.err('Error getting config.', err);
|
||||
|
|
@ -114,6 +139,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
createVanityTransaction(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
vm.approve = function() {
|
||||
|
||||
|
|
@ -403,7 +429,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
}
|
||||
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
function handleDestinationAsAddress(address, originCoin) {
|
||||
|
|
@ -458,8 +484,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
vm.destination.balanceCurrency = balanceText.currency;
|
||||
}
|
||||
|
||||
function handleThirdPartyInitIfBip70() {
|
||||
if (vm.thirdParty.id === 'bip70') {
|
||||
function initBip70() {
|
||||
vm.sendingTitle = gettextCatalog.getString('You are paying');
|
||||
vm.memo = vm.thirdParty.memo;
|
||||
vm.memoExpanded = !!vm.memo;
|
||||
|
|
@ -474,10 +499,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
verified: vm.thirdParty.verified,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function handleThirdPartyInitIfShapeshift() {
|
||||
if (vm.thirdParty.id === 'shapeshift') {
|
||||
function initShapeshift(cb) {
|
||||
vm.sendingTitle = gettextCatalog.getString('You are shifting');
|
||||
if (!vm.thirdParty.data) {
|
||||
vm.thirdParty.data = {};
|
||||
|
|
@ -492,39 +515,32 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit
|
|||
ongoingProcess.set('connectingShapeshift', true);
|
||||
walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) {
|
||||
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;
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
// 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;
|
||||
tx.toAddress = shapeshiftData.toAddress;
|
||||
vm.destination.address = toAddress;
|
||||
vm.destination.kind = 'shapeshift';
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
cb();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onShareTransaction() {
|
||||
var explorerTxUrl = 'https://explorer.bitcoin.com/' + tx.coin + '/tx/' + lastTxId;
|
||||
|
|
@ -766,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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -43,21 +43,20 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
|||
});
|
||||
}
|
||||
|
||||
if ($scope.isNW) {
|
||||
latestReleaseService.checkLatestRelease(function(err, newRelease) {
|
||||
latestReleaseService.checkLatestRelease(function(err, newReleaseData) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
return;
|
||||
}
|
||||
if (newRelease) {
|
||||
if (newReleaseData) {
|
||||
$scope.newRelease = true;
|
||||
$scope.updateText = gettextCatalog.getString('There is a new version of {{appName}} available', {
|
||||
$scope.newReleaseText = gettextCatalog.getString('There is a new version of {{appName}} available', {
|
||||
appName: $scope.name
|
||||
});
|
||||
$scope.newReleaseNotes = newReleaseData.releaseNotes;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function onEnter(event, data) {
|
||||
$ionicNavBarDelegate.showBar(true);
|
||||
|
|
@ -109,31 +108,24 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
|||
$scope.$apply();
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function onLeave (event, data) {
|
||||
lodash.each(listeners, function(x) {
|
||||
x();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
$scope.createdWithinPastDay = function(time) {
|
||||
return timeService.withinPastDay(time);
|
||||
};
|
||||
|
||||
$scope.startFreshSend = function() {
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
sendFlowService.start();
|
||||
}
|
||||
|
||||
$scope.openExternalLink = function() {
|
||||
var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest';
|
||||
var optIn = true;
|
||||
var title = gettextCatalog.getString('Update Available');
|
||||
var message = gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.');
|
||||
var okText = gettextCatalog.getString('View Update');
|
||||
var cancelText = gettextCatalog.getString('Go Back');
|
||||
externalLinkService.open(url, optIn, title, message, okText, cancelText);
|
||||
$scope.showUpdatePopup = function() {
|
||||
latestReleaseService.showUpdatePopup();
|
||||
};
|
||||
|
||||
$scope.openBannerUrl = function() {
|
||||
|
|
@ -18,10 +18,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
$scope.displayBalanceAsFiat = true;
|
||||
|
||||
$scope.requestSpecificAmount = function() {
|
||||
sendFlowService.pushState({
|
||||
toWalletId: $scope.wallet.credentials.walletId
|
||||
sendFlowService.start({
|
||||
toWalletId: $scope.wallet.credentials.walletId,
|
||||
isRequestAmount: true
|
||||
});
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
};
|
||||
|
||||
$scope.setAddress = function(newAddr, copyAddress) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function($scope, $log, $timeout, scannerService, incomingData, $state, $ionicHistory, $rootScope, $ionicNavBarDelegate) {
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function(gettextCatalog, popupService, $scope, $log, $timeout, scannerService, incomingDataService, $state, $ionicHistory, $rootScope, $ionicNavBarDelegate) {
|
||||
|
||||
var scannerStates = {
|
||||
unauthorized: 'unauthorized',
|
||||
|
|
@ -111,7 +111,18 @@ angular.module('copayApp.controllers').controller('tabScanController', function(
|
|||
// Sometimes (testing in Chrome, when reading QR Code) data is an object
|
||||
// that has a string data.result.
|
||||
contents = contents.result || contents;
|
||||
incomingData.redir(contents);
|
||||
incomingDataService.redir(contents, function onError(err) {
|
||||
if (err) {
|
||||
var title = gettextCatalog.getString('Scan Failed');
|
||||
popupService.showAlert(title, err.message, function onAlertShown() {
|
||||
// Enable another scan since we won't receive incomingDataMenu.menuHidden
|
||||
activate();
|
||||
});
|
||||
} else {
|
||||
scannerService.resumePreview();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.$on('incomingDataMenu.menuHidden', function() {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, sendFlowService, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) {
|
||||
angular.module('copayApp.controllers').controller('tabSendController', function(bitcoinUriService, $scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, platformInfo, sendFlowService, gettextCatalog, configService, $ionicPopup, $ionicNavBarDelegate, clipboardService, incomingDataService) {
|
||||
var clipboardHasAddress = false;
|
||||
var clipboardHasContent = false;
|
||||
var originalList;
|
||||
|
|
@ -29,7 +29,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$scope.$on("$ionicView.enter", function(event, data) {
|
||||
|
||||
var stateParams = sendFlowService.getStateClone();
|
||||
var stateParams = sendFlowService.state.getClone();
|
||||
$scope.fromWallet = profileService.getWallet(stateParams.fromWalletId);
|
||||
|
||||
clipboardService.readFromClipboard(function(text) {
|
||||
|
|
@ -39,7 +39,9 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$scope.clipboardHasAddress = false;
|
||||
$scope.clipboardHasContent = false;
|
||||
if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr
|
||||
var parsed = bitcoinUriService.parse(text);
|
||||
console.log('parsed', parsed);
|
||||
if (parsed.isValid && parsed.publicAddress && parsed.coin === 'bch' && !parsed.testnet) { // CashAddr
|
||||
$scope.clipboardHasAddress = true;
|
||||
} else if ((text[0] === "1" || text[0] === "3" || text.substring(0, 3) === "bc1") && text.length >= 26 && text.length <= 35) { // Legacy Addresses
|
||||
$scope.clipboardHasAddress = true;
|
||||
|
|
@ -60,11 +62,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
});
|
||||
|
||||
$scope.findContact = function(search) {
|
||||
|
||||
if (incomingData.redir(search)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!search || search.length < 1) {
|
||||
$scope.list = originalList;
|
||||
$timeout(function() {
|
||||
|
|
@ -73,12 +70,16 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
return;
|
||||
}
|
||||
|
||||
var 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;
|
||||
});
|
||||
};
|
||||
|
||||
var hasWallets = function() {
|
||||
|
|
@ -184,27 +185,18 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$log.debug('Got toAddress:' + toAddress + ' | ' + item.name);
|
||||
|
||||
var stateParams = sendFlowService.getStateClone();
|
||||
stateParams.toAddress = toAddress,
|
||||
var stateParams = sendFlowService.state.getClone();
|
||||
stateParams.toAddress = toAddress;
|
||||
stateParams.coin = item.coin;
|
||||
sendFlowService.pushState(stateParams);
|
||||
|
||||
if (!stateParams.fromWalletId) { // If we have no toAddress or fromWallet
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
} else {
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
}
|
||||
|
||||
sendFlowService.start(stateParams);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.startWalletToWalletTransfer = function() {
|
||||
console.log('startWalletToWalletTransfer()');
|
||||
var params = sendFlowService.getStateClone();
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.wallet-to-wallet', {
|
||||
fromWalletId: sendFlowService.fromWalletId
|
||||
});
|
||||
var params = sendFlowService.state.getClone();
|
||||
params.isWalletTransfer = true;
|
||||
sendFlowService.start(params);
|
||||
}
|
||||
|
||||
// This could probably be enhanced refactoring the routes abstract states
|
||||
|
|
@ -238,7 +230,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
});
|
||||
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.clear();
|
||||
sendFlowService.state.clear();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
|
||||
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingDataService, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
|
||||
|
||||
$scope.onScan = function(data) {
|
||||
if (!incomingData.redir(data)) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid data'));
|
||||
incomingDataService.redir(data, function onError(err) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setScanFn = function(scanFn) {
|
||||
|
|
@ -16,8 +18,7 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
};
|
||||
|
||||
$scope.startFreshSend = function() {
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
sendFlowService.start();
|
||||
};
|
||||
|
||||
$scope.importInit = function() {
|
||||
|
|
@ -28,7 +29,6 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
};
|
||||
|
||||
$scope.chooseScanner = function() {
|
||||
sendFlowService.clear();
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
if (!isWindowsPhoneApp) {
|
||||
|
|
@ -38,10 +38,14 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
|
||||
scannerService.useOldScanner(function(err, contents) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
return;
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
} else {
|
||||
incomingDataService.redir(contents, function onError(err) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
incomingData.redir(contents);
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,52 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) {
|
||||
|
||||
var HISTORY_SHOW_LIMIT = 10;
|
||||
var currentTxHistoryPage = 0;
|
||||
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService, walletHistoryService) {
|
||||
// Desktop can display 13 rows of transactions, bump it up to a nice round 15.
|
||||
var DISPLAY_PAGE_SIZE = 15;
|
||||
var currentTxHistoryDisplayPage = 0;
|
||||
var completeTxHistory = []
|
||||
var listeners = [];
|
||||
$scope.txps = [];
|
||||
$scope.completeTxHistory = [];
|
||||
$scope.openTxpModal = txpModalService.open;
|
||||
|
||||
// For gradual migration for doing it properly
|
||||
$scope.vm = {
|
||||
allowInfiniteScroll: false,
|
||||
gettingCachedHistory: true,
|
||||
gettingInitialHistory: true,
|
||||
updatingTxHistory: false,
|
||||
fetchedAllTxHistory: false,
|
||||
//updateTxHistoryError: false
|
||||
updateTxHistoryFailed: false
|
||||
};
|
||||
|
||||
// Need flag for when to allow infinite scroll at bottom
|
||||
// - ie not when loading initial data and there is no more cached data
|
||||
|
||||
$scope.amountIsCollapsible = false;
|
||||
$scope.color = '#888888';
|
||||
|
||||
$scope.filteredTxHistory = [];
|
||||
$scope.isCordova = platformInfo.isCordova;
|
||||
$scope.isAndroid = platformInfo.isAndroid;
|
||||
$scope.isIOS = platformInfo.isIOS;
|
||||
$scope.isSearching = false;
|
||||
$scope.openTxpModal = txpModalService.open;
|
||||
$scope.requiresMultipleSignatures = false;
|
||||
$scope.showBalanceButton = false;
|
||||
$scope.status = null;
|
||||
// Displaying 50 transactions when entering the screen takes a while, so only display a subset
|
||||
// of everything we have, not the complete history.
|
||||
$scope.txHistory = []; // This is what is displayed
|
||||
$scope.txHistorySearchResults = [];
|
||||
$scope.txps = [];
|
||||
$scope.updatingStatus = false;
|
||||
$scope.updateStatusError = null;
|
||||
$scope.updatingTxHistoryProgress = 0;
|
||||
$scope.wallet = null;
|
||||
$scope.walletId = '';
|
||||
$scope.walletNotRegistered = false;
|
||||
|
||||
|
||||
|
||||
|
||||
var channel = "ga";
|
||||
if (platformInfo.isCordova) {
|
||||
|
|
@ -19,34 +55,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;
|
||||
|
|
@ -73,6 +86,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
$scope.updatingStatus = true;
|
||||
$scope.updateStatusError = null;
|
||||
$scope.walletNotRegistered = false;
|
||||
$scope.vm.fetchedAllTxHistory = false;
|
||||
|
||||
walletService.getStatus($scope.wallet, {
|
||||
force: !!force,
|
||||
|
|
@ -156,68 +170,96 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
if (err) return;
|
||||
$timeout(function() {
|
||||
walletService.startScan($scope.wallet, function() {
|
||||
$scope.updateAll();
|
||||
$scope.updateAll(true, true);
|
||||
$scope.$apply();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var updateTxHistory = function(cb) {
|
||||
if (!cb) cb = function() {};
|
||||
$scope.updateTxHistoryError = false;
|
||||
$scope.updatingTxHistoryProgress = 0;
|
||||
|
||||
feeService.getFeeLevels($scope.wallet.coin, function(err, levels) {
|
||||
walletService.getTxHistory($scope.wallet, {
|
||||
feeLevels: levels
|
||||
}, function(err, txHistory) {
|
||||
$scope.updatingTxHistory = false;
|
||||
if (err) {
|
||||
$scope.txHistory = null;
|
||||
$scope.updateTxHistoryError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
applyCurrencyAliases(txHistory);
|
||||
|
||||
var config = configService.getSync();
|
||||
var fiatCode = config.wallet.settings.alternativeIsoCode;
|
||||
lodash.each(txHistory, function(t) {
|
||||
var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin);
|
||||
t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode;
|
||||
});
|
||||
|
||||
$scope.completeTxHistory = txHistory;
|
||||
|
||||
$scope.showHistory();
|
||||
$timeout(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function applyCurrencyAliases(txHistory) {
|
||||
var defaults = configService.getDefaults();
|
||||
var configCache = configService.getSync();
|
||||
|
||||
lodash.each(txHistory, function(t) {
|
||||
t.amountUnitStr = $scope.wallet.coin == 'btc'
|
||||
lodash.each(txHistory, function onTx(tx) {
|
||||
tx.amountUnitStr = $scope.wallet.coin == 'btc'
|
||||
? (configCache.bitcoinAlias || defaults.bitcoinAlias)
|
||||
: (configCache.bitcoinCashAlias || defaults.bitcoinCashAlias);
|
||||
|
||||
t.amountUnitStr = t.amountUnitStr.toUpperCase();
|
||||
tx.amountUnitStr = tx.amountUnitStr.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.showHistory = function() {
|
||||
if ($scope.completeTxHistory) {
|
||||
$scope.txHistory = $scope.completeTxHistory.slice(0, (currentTxHistoryPage + 1) * HISTORY_SHOW_LIMIT);
|
||||
$scope.txHistoryShowMore = $scope.completeTxHistory.length > $scope.txHistory.length;
|
||||
function formatTxHistoryForDisplay(txHistory) {
|
||||
applyCurrencyAliases(txHistory);
|
||||
|
||||
var config = configService.getSync();
|
||||
var fiatCode = config.wallet.settings.alternativeIsoCode;
|
||||
lodash.each(txHistory, function(t) {
|
||||
var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin);
|
||||
t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function updateTxHistoryFromCachedData() {
|
||||
walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){
|
||||
$scope.vm.gettingCachedHistory = false;
|
||||
if (err) {
|
||||
// Don't display an error because we are also requesting the history.
|
||||
$log.error('Error getting cached tx history.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!txHistory) {
|
||||
$log.debug('No cached tx history.');
|
||||
return;
|
||||
}
|
||||
|
||||
formatTxHistoryForDisplay(txHistory);
|
||||
|
||||
completeTxHistory = txHistory;
|
||||
showHistory(false);
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAndShowTxHistory(getLatest, flushCacheOnNew) {
|
||||
$scope.vm.updatingTxHistory = true;
|
||||
|
||||
walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory, fetchedAllTransactions) {
|
||||
$scope.vm.gettingInitialHistory = false;
|
||||
$scope.vm.updatingTxHistory = false;
|
||||
$scope.$broadcast('scroll.infiniteScrollComplete');
|
||||
|
||||
if (err) {
|
||||
console.error('pagination Failed to get history.', err);
|
||||
$scope.vm.updateTxHistoryFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fetchedAllTransactions) {
|
||||
$scope.vm.fetchedAllTxHistory = true;
|
||||
}
|
||||
|
||||
formatTxHistoryForDisplay(txHistory);
|
||||
|
||||
completeTxHistory = txHistory;
|
||||
showHistory(true);
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function showHistory(showAll) {
|
||||
if (completeTxHistory) {
|
||||
$scope.txHistory = showAll ? completeTxHistory : completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE);
|
||||
$scope.vm.allowInfiniteScroll = !$scope.vm.fetchedAllTxHistory && !(completeTxHistory.length === $scope.txHistory.length && $scope.vm.gettingInitialHistory);
|
||||
} else {
|
||||
$scope.vm.allowInfiniteScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$scope.getDate = function(txCreated) {
|
||||
var date = new Date(txCreated * 1000);
|
||||
|
|
@ -256,24 +298,35 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
return !tx.confirmations || tx.confirmations === 0;
|
||||
};
|
||||
|
||||
// on-infinite="showMore()"
|
||||
$scope.showMore = function() {
|
||||
$timeout(function() {
|
||||
currentTxHistoryPage++;
|
||||
$scope.showHistory();
|
||||
// Check if we have more than we are displaying
|
||||
if (completeTxHistory.length > $scope.txHistory.length) {
|
||||
currentTxHistoryDisplayPage++;
|
||||
showHistory(false);
|
||||
$scope.$broadcast('scroll.infiniteScrollComplete');
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.vm.updatingTxHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchAndShowTxHistory(false, false);
|
||||
};
|
||||
|
||||
// on-refresh="onRefresh()"
|
||||
$scope.onRefresh = function() {
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('scroll.refreshComplete');
|
||||
}, 300);
|
||||
$scope.updateAll(true);
|
||||
$scope.updateAll(true, false);
|
||||
};
|
||||
|
||||
$scope.updateAll = function(force, cb) {
|
||||
updateStatus(force);
|
||||
updateTxHistory(cb);
|
||||
$scope.updateAll = function(forceStatusUpdate, flushTxCacheOnNew) {
|
||||
updateStatus(forceStatusUpdate);
|
||||
//updateTxHistory(cb);
|
||||
fetchAndShowTxHistory(true, flushTxCacheOnNew);
|
||||
};
|
||||
|
||||
$scope.hideToggle = function() {
|
||||
|
|
@ -283,103 +336,36 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
};
|
||||
|
||||
var prevPos;
|
||||
|
||||
$scope.txHistoryPaddingBottom = 0;
|
||||
function getScrollPosition() {
|
||||
var scrollPosition = $ionicScrollDelegate.getScrollPosition();
|
||||
if (!scrollPosition) {
|
||||
$window.requestAnimationFrame(function() {
|
||||
|
||||
$timeout(function() {
|
||||
getScrollPosition();
|
||||
});
|
||||
}, 200);
|
||||
|
||||
if (!scrollPosition) {
|
||||
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;
|
||||
|
||||
|
|
@ -393,7 +379,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
if (!$scope.wallet) return;
|
||||
$scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1;
|
||||
|
||||
$scope.updatingTxHistory = true;
|
||||
$scope.vm.gettingInitialHistory = true;
|
||||
|
||||
addressbookService.list(function(err, ab) {
|
||||
if (err) $log.error(err);
|
||||
|
|
@ -403,11 +389,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
listeners = [
|
||||
$rootScope.$on('bwsEvent', function(e, walletId) {
|
||||
if (walletId == $scope.wallet.id && e.type != 'NewAddress')
|
||||
$scope.updateAll();
|
||||
$scope.updateAll(false, false);
|
||||
}),
|
||||
$rootScope.$on('Local/TxAction', function(e, walletId) {
|
||||
if (walletId == $scope.wallet.id)
|
||||
$scope.updateAll();
|
||||
$scope.updateAll(false, false);
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
|
@ -416,8 +402,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 +466,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
}
|
||||
|
||||
$scope.goToSend = function() {
|
||||
sendFlowService.startSend({
|
||||
sendFlowService.start({
|
||||
fromWalletId: $scope.wallet.id
|
||||
});
|
||||
|
||||
// Go home first so that the Home tab works properly
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send');
|
||||
});
|
||||
|
||||
};
|
||||
$scope.goToReceive = function() {
|
||||
$state.go('tabs.home', {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, sendFlowService, configService, gettextCatalog, profileService, txFormatService) {
|
||||
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $state, sendFlowService, configService, gettextCatalog, profileService, txFormatService) {
|
||||
|
||||
var fromWalletId = '';
|
||||
var priceDisplayAsFiat = false;
|
||||
|
|
@ -12,32 +12,23 @@ 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':
|
||||
if ($scope.params.isWalletTransfer) {
|
||||
$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) {
|
||||
} else if (!$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Send');
|
||||
}
|
||||
// nop
|
||||
}
|
||||
|
||||
$scope.coin = false; // Wallets to show (for destination screen or contacts)
|
||||
$scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination
|
||||
|
|
@ -99,21 +90,12 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu
|
|||
$scope.requestAmountSecondary = fiatAmount;
|
||||
$scope.requestCurrencySecondary = fiatCurrrency;
|
||||
}
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getNextStep(params) {
|
||||
if (!params.toWalletId && !params.toAddress) { // If we have no toAddress or fromWallet
|
||||
return 'tabs.send.destination';
|
||||
} else if (!params.amount) { // If we have no amount
|
||||
return 'tabs.send.amount';
|
||||
} else { // If we do have them
|
||||
return 'tabs.send.review';
|
||||
}
|
||||
}
|
||||
|
||||
function handleThirdPartyIfShapeshift() {
|
||||
console.log($scope.thirdParty, $scope.coin);
|
||||
if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the
|
||||
|
|
@ -191,20 +173,17 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu
|
|||
|
||||
|
||||
$scope.useWallet = function(wallet) {
|
||||
var params = sendFlowService.getStateClone();
|
||||
var params = sendFlowService.state.getClone();
|
||||
if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from
|
||||
params.fromWalletId = wallet.id;
|
||||
} else { // we're on the destination screen, set wallet to send to
|
||||
params.toWalletId = wallet.id;
|
||||
}
|
||||
sendFlowService.pushState(params);
|
||||
var nextStep = getNextStep(params);
|
||||
console.log('walletSelector nextStep', nextStep);
|
||||
$state.transitionTo(nextStep, $scope.params);
|
||||
sendFlowService.goNext(params);
|
||||
};
|
||||
|
||||
$scope.goBack = function() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives')
|
||||
.directive('incomingDataMenu', function($timeout, $rootScope, $state, externalLinkService) {
|
||||
.directive('incomingDataMenu', function($timeout, $rootScope, $state, externalLinkService, sendFlowService, bitcoinCashJsService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'views/includes/incomingDataMenu.html',
|
||||
link: function(scope, element, attrs) {
|
||||
$rootScope.$on('incomingDataMenu.showMenu', function(event, data) {
|
||||
$timeout(function() {
|
||||
scope.data = data.data;
|
||||
scope.type = data.type;
|
||||
scope.showMenu = true;
|
||||
scope.https = false;
|
||||
scope.data = data;
|
||||
|
||||
if (scope.type === 'url') {
|
||||
if (scope.data.indexOf('https://') === 0) {
|
||||
scope.https = true;
|
||||
}
|
||||
if (scope.data.parsed.privateKey) {
|
||||
scope.type = "privateKey";
|
||||
} else if (scope.data.parsed.url) {
|
||||
scope.type = "url";
|
||||
} else if (scope.data.parsed.publicAddress) {
|
||||
scope.type = "bitcoinAddress";
|
||||
var prefix = scope.data.parsed.isTestnet ? 'bchtest:' : 'bitcoincash:';
|
||||
scope.data.toAddress = (prefix + scope.data.parsed.publicAddress.cashAddr) || scope.data.parsed.publicAddress.legacy || scope.data.parsed.publicAddress.bitpay;
|
||||
} else {
|
||||
scope.type = "text";
|
||||
}
|
||||
|
||||
scope.showMenu = true;
|
||||
});
|
||||
});
|
||||
scope.hide = function() {
|
||||
|
|
@ -28,18 +33,9 @@ angular.module('copayApp.directives')
|
|||
externalLinkService.open(url);
|
||||
};
|
||||
scope.sendPaymentToAddress = function(bitcoinAddress) {
|
||||
var noPrefixInAddress = 0;
|
||||
if (bitcoinAddress.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
scope.showMenu = false;
|
||||
$state.go('tabs.send').then(function() {
|
||||
$timeout(function() {
|
||||
$state.transitionTo('tabs.send.amount', {
|
||||
toAddress: bitcoinAddress,
|
||||
noPrefix: noPrefixInAddress
|
||||
});
|
||||
}, 50);
|
||||
sendFlowService.start({
|
||||
data: bitcoinAddress
|
||||
});
|
||||
};
|
||||
scope.addToAddressBook = function(bitcoinAddress) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function($interval, shapeshiftApiService, profileService, incomingData, ongoingProcess) {
|
||||
angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function($interval, shapeshiftApiService, profileService, incomingDataService, ongoingProcess) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
|
|
@ -111,7 +111,8 @@ angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function
|
|||
orderId: $scope.depositInfo.orderId
|
||||
};
|
||||
|
||||
if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
// How to handle this
|
||||
if (incomingDataService.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ angular.module('copayApp.directives')
|
|||
|
||||
function getTransformStyle(translatePct) {
|
||||
return {
|
||||
'transform': 'translateX(' + translatePct + '%)'
|
||||
'transform': 'translateX(' + translatePct + '%)',
|
||||
'-webkit-transform': 'translateX(' + translatePct + '%)'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
416
src/js/services/bitcoin-uri.service.js
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
'use strict';
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('bitcoinUriService', bitcoinUriService);
|
||||
|
||||
function bitcoinUriService(bitcoinCashJsService, bwcService, $log) {
|
||||
var bch = bitcoinCashJsService.getBitcoinCashJs();
|
||||
var bitcore = bwcService.getBitcore();
|
||||
|
||||
var service = {
|
||||
parse: parse
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function bitpayAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var BitpayFormat = Address.BitpayFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var result = null;
|
||||
if (address[0] == 'C') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'pubkeyhash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'H') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'scripthash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var prefixed = 'bitcoincash:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnTestnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var testnet = bch.Networks.testnet;
|
||||
|
||||
var prefixed = 'bchtest:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function infoFromWalletImportText(data) {
|
||||
var split = data.split('|');
|
||||
// Copay seems to use extra parameter for coin.
|
||||
if (split.length < 5 || split.length > 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = parseInt(split[0], 10);
|
||||
if (isNaN(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = split[1];
|
||||
var network = split[2];
|
||||
if (!(network === 'livenet' || network === 'testnet')) {
|
||||
return null;
|
||||
}
|
||||
var isTestnet = network === 'testnet';
|
||||
|
||||
var derivationPath = split[3];
|
||||
if (!/^m\/\d+'\/\d+'\/\d+'$/.test(derivationPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hasPassphraseText = split[4];
|
||||
if (!(hasPassphraseText === 'true' || hasPassphraseText === 'false')) {
|
||||
return null;
|
||||
}
|
||||
var hasPassphrase = hasPassphraseText === 'true';
|
||||
|
||||
var coin; // Intentionally undefined as may not be present
|
||||
if (split.length > 5) {
|
||||
var coinText = split[5];
|
||||
if (!(coinText === 'bch' || coinText === 'btc')) {
|
||||
return null;
|
||||
}
|
||||
coin = coinText;
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
data: data,
|
||||
isTestnet: isTestnet,
|
||||
derivationPath: derivationPath,
|
||||
hasPassphrase: hasPassphrase,
|
||||
coin: coin
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
For parsing:
|
||||
BIP21
|
||||
BIP72
|
||||
|
||||
returns:
|
||||
{
|
||||
amount: '',
|
||||
amountInSatoshis: 0,
|
||||
bareUrl: '',
|
||||
coin: '',
|
||||
copayInvitation: '',
|
||||
import: { // testnet info in root, coin info in root if available
|
||||
data: '',
|
||||
derivationPath: '',
|
||||
hasPassphrase: false,
|
||||
type: 1,
|
||||
},
|
||||
isValid: false,
|
||||
label: '',
|
||||
message: '',
|
||||
other: {
|
||||
somethingIDontUnderstand: 'Its value'
|
||||
},
|
||||
privateKey: {
|
||||
encrypted: '',
|
||||
wif: ''
|
||||
}'',
|
||||
publicAddress: {
|
||||
bitpay: '',
|
||||
cashAddr: '',
|
||||
legacy: '',
|
||||
},
|
||||
req: {
|
||||
"req-param0": '',
|
||||
"req-param1": ''
|
||||
},
|
||||
testnet: false,
|
||||
url: '' // For BIP70
|
||||
}
|
||||
|
||||
Only fields that are present in the data are defined in the returned object. Both privateKey and publicAddress only have 1 field defined, if they exist at all.
|
||||
The exception to this is the coin property, which is determined from other data, such as the prefix or address type.
|
||||
|
||||
*/
|
||||
|
||||
function parse(data) {
|
||||
var parsed = {
|
||||
isValid: false
|
||||
};
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Identify prefix
|
||||
var trimmed = data.trim();
|
||||
var colonSplit = /^([\w-]*):?(.*)$/.exec(trimmed);
|
||||
if (!colonSplit) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var addressAndParams = '';
|
||||
var preColonLower = colonSplit[1].toLowerCase();
|
||||
if (preColonLower === 'bitcoin') {
|
||||
parsed.coin = 'btc';
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is btc');
|
||||
|
||||
} else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.test = false;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (/^(?:bchtest)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.isTestnet = true;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (colonSplit[2] === '') {
|
||||
// No colon and no coin specifier.
|
||||
addressAndParams = colonSplit[1].trim();
|
||||
console.log('No prefix.');
|
||||
|
||||
} else if (/^https?$/.test(colonSplit[1])) { // Plain URL
|
||||
addressAndParams = trimmed;
|
||||
|
||||
} else if (colonSplit[2].indexOf('|') == 0) { // Import
|
||||
addressAndParams = trimmed
|
||||
} else {
|
||||
// Something we don't recognise
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Remove erroneous leading slashes
|
||||
//var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams);
|
||||
var leadingSlashes = /^\/*(.*)$/.exec(addressAndParams);
|
||||
if (!leadingSlashes) {
|
||||
return parsed;
|
||||
}
|
||||
addressAndParams = leadingSlashes[1];
|
||||
|
||||
var questionMarkSplit = /^([^\?]*)\??([^\?]*)$/.exec(addressAndParams);
|
||||
if (!questionMarkSplit) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var address = questionMarkSplit[1];
|
||||
var params = questionMarkSplit[2];
|
||||
|
||||
if (params.length > 0) {
|
||||
var paramsSplit = params.split('&');
|
||||
var others;
|
||||
var req;
|
||||
var paramCount = paramsSplit.length;
|
||||
for(var i = 0; i < paramCount; i++) {
|
||||
var param = paramsSplit[i];
|
||||
var valueSplit = param.split('=');
|
||||
if (valueSplit.length !== 2) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var key = valueSplit[0];
|
||||
var value = valueSplit[1];
|
||||
var decodedValue = decodeURIComponent(value);
|
||||
switch(key) {
|
||||
case 'amount':
|
||||
var amount = parseFloat(decodedValue);
|
||||
if (amount) { // Checking for NaN, or no numbers at all etc. & convert to satoshi
|
||||
parsed.amount = decodedValue; // Need to check if a currency is precised
|
||||
parsed.amountInSatoshis = amount * 100000000
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'label':
|
||||
parsed.label = decodedValue;
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
parsed.message = decodedValue;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
// Could use a more comprehesive regex to test URL validity, but then how would we know
|
||||
// which part of the validation it failed?
|
||||
if (decodedValue.startsWith('https://')) {
|
||||
parsed.url = decodedValue;
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (key.startsWith('req-')) {
|
||||
req = req || {};
|
||||
req[key] = decodedValue;
|
||||
} else {
|
||||
others = others || {};
|
||||
others[key] = decodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
parsed.others = others;
|
||||
parsed.req = req;
|
||||
|
||||
|
||||
if (address) {
|
||||
var addressLowerCase = address.toLowerCase();
|
||||
var copayInvitationRe = /^[0-9A-HJ-NP-Za-km-z]{70,80}$/;
|
||||
//var legacyRe = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
//var legacyTestnetRe = /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
var privateKeyEncryptedRe = /^6P[1-9A-HJ-NP-Za-km-z]{56}$/;
|
||||
var privateKeyForUncompressedPublicKeyRe = /^5[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForUncompressedPublicKeyTestnetRe = /^9[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForCompressedPublicKeyRe = /^[KL][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var privateKeyForCompressedPublicKeyTestnetRe = /^[c][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var urlRe = /^https?:\/\/.+/;
|
||||
|
||||
var bitpayAddrMainnet = bitpayAddrOnMainnet(address);
|
||||
var cashAddrTestnet = cashAddrOnTestnet(addressLowerCase);
|
||||
var cashAddrMainnet = cashAddrOnMainnet(addressLowerCase);
|
||||
var importInfo = infoFromWalletImportText(address);
|
||||
var privateKey = '';
|
||||
|
||||
if (parsed.isTestnet && cashAddrTestnet) {
|
||||
parsed.address = addressLowerCase;
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (cashAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'livenet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'testnet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitpayAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
bitpay: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (copayInvitationRe.test(address) ) {
|
||||
parsed.copayInvitation = address;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyRe.test(address) || privateKeyForCompressedPublicKeyRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyTestnetRe.test(address) || privateKeyForCompressedPublicKeyTestnetRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'testnet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyEncryptedRe.test(address)) {
|
||||
parsed.privateKey = { encrypted: address };
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (urlRe.test(address)) {
|
||||
parsed.bareUrl = trimmed;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (importInfo) {
|
||||
parsed.import = {
|
||||
type: importInfo.type,
|
||||
data: importInfo.data,
|
||||
derivationPath: importInfo.derivationPath,
|
||||
hasPassphrase: importInfo.hasPassphrase
|
||||
};
|
||||
parsed.coin = importInfo.coin;
|
||||
parsed.isTestnet = importInfo.isTestnet;
|
||||
parsed.isValid = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
parsed.isValid = !!parsed.url; // BIP72
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
429
src/js/services/bitcoin-uri.service.spec.js
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
describe('bitcoinUriService', function() {
|
||||
var bitcoinUriService;
|
||||
|
||||
beforeEach(function() {
|
||||
module('bitcoinCashJsModule');
|
||||
module('bitcoincom.services');
|
||||
module('bwcModule');
|
||||
|
||||
inject(function($injector){
|
||||
bitcoinUriService = $injector.get('bitcoinUriService');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:?r=https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:?r=https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with extended params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc?unknown=something&mystery=Melton%20probang&req-one=ichi&req-beta=Ni%20san');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.others.mystery).toBe('Melton probang');
|
||||
expect(parsed.others.unknown).toBe('something');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc');
|
||||
expect(parsed.req['req-beta']).toBe('Ni san');
|
||||
expect(parsed.req['req-one']).toBe('ichi');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with invalid amount', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qq0knhwj4d5zy3kdph24w6etq58vwzua6sm7lhcmuk?amount=three');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin testnet address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin uri', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with encoded label', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT?label=Mr.%20Smith');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Mr. Smith');
|
||||
expect(parsed.publicAddress.legacy).toBe('1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu?amount=20.3&label=Luke-Jr&message=Donation%20for%20project%20xyz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.amount).toBe('20.3');
|
||||
expect(parsed.amountInSatoshis).toBe(2030000000);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Luke-Jr');
|
||||
expect(parsed.publicAddress.legacy).toBe('12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu');
|
||||
expect(parsed.message).toBe('Donation for project xyz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:/1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin://18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin: 19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitpay without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.bitpay).toBe('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr testnet with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr uppercase', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('BITCOINCASH:QZZG9NMC5VX8GAP6XFATX3TWNSDN2YRMCSSULSMY44');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzzg9nmc5vx8gap6xfatx3twnsdn2yrmcssulsmy44');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with dash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:/qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash://qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash: qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('cashAddr with space on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest: qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('copay invitation', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.copayInvitation).toBe('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
});
|
||||
|
||||
|
||||
it ('import BCH wallet no password', function() {
|
||||
var parsed = bitcoinUriService.parse("1|suggest route obvious broccoli good position hidden tone history around final lobster|livenet|m/44'/0'/0'|false");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('suggest route obvious broccoli good position hidden tone history around final lobster');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
it ('import BCH wallet with passphrase', function() {
|
||||
var parsed = bitcoinUriService.parse("1|fringe hazard all hobby trap myth fire stand sock empty soon east|livenet|m/44'/0'/0'|true");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('fringe hazard all hobby trap myth fire stand sock empty soon east');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(true);
|
||||
});
|
||||
|
||||
it ('import BTC wallet testnet', function() {
|
||||
// From copay
|
||||
var parsed = bitcoinUriService.parse("1|cat wealth column firm wet sauce tornado era feature monster click eyebrow|testnet|m/44'/1'/0'|false|btc");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('cat wealth column firm wet sauce tornado era feature monster click eyebrow');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/1'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
// Invalid addresses from https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md
|
||||
it('invalid cashAddr style 1', function() {
|
||||
var parsed = bitcoinUriService.parse('prefix:x64nx6hz');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 2', function() {
|
||||
var parsed = bitcoinUriService.parse('p:gpf8m4h7');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 3', function() {
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 4', function() {
|
||||
var parsed = bitcoinUriService.parse('bchtest:testnetaddress4d6njnut');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 5', function() {
|
||||
var parsed = bitcoinUriService.parse('bchreg:555555555555555555555555555555555555555555555udxmlmrz');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('non-string', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse([1, 2, 3, 4]);
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key encrypted with BIP38', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.encrypted).toBe('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTu');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMM');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTTwx');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcC');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('URL only, http', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('http://paperwallet.bitcoin.com');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('http://paperwallet.bitcoin.com');
|
||||
});
|
||||
|
||||
it('URL only, https with query', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('https://purse.io/?one=two&three=four');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('https://purse.io/?one=two&three=four');
|
||||
});
|
||||
|
||||
});
|
||||
79
src/js/services/incoming-data.service.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* incomingDataService is an intermediate to redirect either to the sendFlow
|
||||
* or to import/join a wallet.
|
||||
*/
|
||||
angular.module('copayApp.services').factory('incomingDataService', function(bitcoinUriService, $log, $state, $rootScope, scannerService, sendFlowService, gettextCatalog) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, cbError) {
|
||||
var parsed = bitcoinUriService.parse(data);
|
||||
|
||||
console.log(parsed);
|
||||
$log.debug(parsed);
|
||||
|
||||
|
||||
if (parsed.isValid) {
|
||||
if (parsed.isTestnet) {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Testnet is not supported.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
} else {
|
||||
scannerService.pausePreview();
|
||||
|
||||
/**
|
||||
* Strategy for the action
|
||||
*/
|
||||
if (parsed.copayInvitation) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
} else if (parsed.import) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
!parsed.isValid
|
||||
|| parsed.privateKey
|
||||
|| (sendFlowService.state.isEmpty() && !parsed.url && !parsed.amount)
|
||||
) {
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
} else {
|
||||
var state = sendFlowService.state.getClone();
|
||||
state.data = data;
|
||||
|
||||
sendFlowService.start(state, function onError(err) {
|
||||
/**
|
||||
* OnError, open the menu (link not validated)
|
||||
*/
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Data not recognised.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, serviceId, serviceData) {
|
||||
var originalAddress = null;
|
||||
var noPrefixInAddress = 0;
|
||||
|
||||
if (data.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
|
||||
if (typeof(data) == 'string' && !(/^bitcoin(cash)?:\?r=[\w+]/).exec(data) && (data.toLowerCase().indexOf('bitcoincash:') >= 0 || data[0] == 'q' || data[0] == 'p' || data[0] == 'C' || data[0] == 'H')) {
|
||||
try {
|
||||
noPrefixInAddress = 0;
|
||||
|
||||
if (data[0] == 'p' || data[0] == 'q') {
|
||||
data = 'bitcoincash:' + data;
|
||||
}
|
||||
var paramString = '';
|
||||
if (data.indexOf('?') >= 0) {
|
||||
paramString = data.substring(data.indexOf('?'));
|
||||
data = data.substring(0, data.indexOf('?'));
|
||||
}
|
||||
|
||||
if (data.indexOf('BITCOINCASH:') >= 0) {
|
||||
data = data.toLowerCase();
|
||||
}
|
||||
originalAddress = data.replace('bitcoincash:', '');
|
||||
var legacyAddress = bitcoinCashJsService.readAddress(data).legacy;
|
||||
data = 'bitcoincash:' + legacyAddress + paramString;
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
$log.debug('Processing incoming data: ' + data);
|
||||
|
||||
function sanitizeUri(data) {
|
||||
// Fixes when a region uses comma to separate decimals
|
||||
var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
|
||||
var match = regex.exec(data);
|
||||
if (!match || match.length === 0) {
|
||||
return data;
|
||||
}
|
||||
var value = match[0].replace(',', '.');
|
||||
var newUri = data.replace(regex, value);
|
||||
|
||||
// mobile devices, uris like copay://glidera
|
||||
newUri.replace('://', ':');
|
||||
|
||||
return newUri;
|
||||
}
|
||||
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) return;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
function checkPrivateKey(privateKey) {
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function goSend(addr, amount, message, coin, serviceId, serviceData) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
// Timeout is required to enable the "Back" button
|
||||
$timeout(function() {
|
||||
var params = sendFlowService.getStateClone();
|
||||
|
||||
if (amount) {
|
||||
params.amount = amount;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
params.toAddress = addr;
|
||||
params.displayAddress = originalAddress ? originalAddress : addr;
|
||||
}
|
||||
|
||||
if (coin) {
|
||||
params.coin = coin;
|
||||
}
|
||||
|
||||
if (noPrefixInAddress) {
|
||||
params.noPrefixInAddress = noPrefixInAddress;
|
||||
}
|
||||
|
||||
if (serviceId) {
|
||||
params.thirdParty = [];
|
||||
params.thirdParty.id = serviceId;
|
||||
params.thirdParty.data = serviceData;
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
} else {
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
// data extensions for Payment Protocol with non-backwards-compatible request
|
||||
if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
|
||||
var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
|
||||
data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
|
||||
if (coin == 'bch') {
|
||||
payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
|
||||
if (err) {
|
||||
var message = err.toString();
|
||||
if (typeof err.data === 'string') {
|
||||
// i.e. 'This invoice is no longer accepting payments'
|
||||
message = gettextCatalog.getString(err.data);
|
||||
}
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), message)
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
data = sanitizeUri(data);
|
||||
|
||||
// Bitcoin URL
|
||||
if (bitcore.URI.isValid(data)) {
|
||||
var coin = 'btc';
|
||||
var parsed = new bitcore.URI(data);
|
||||
|
||||
var addr = parsed.address ? parsed.address.toString() : '';
|
||||
var message = parsed.message;
|
||||
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
return true;
|
||||
// Cash URI
|
||||
} else if (bitcoreCash.URI.isValid(data)) {
|
||||
var coin = 'bch';
|
||||
var parsed = new bitcoreCash.URI(data);
|
||||
|
||||
var addr = parsed.address ? parsed.address.toString() : '';
|
||||
var message = parsed.message;
|
||||
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
// paypro not yet supported on cash
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
return true;
|
||||
|
||||
// Cash URI with bitcoin (btc) address version number?
|
||||
} else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
|
||||
$log.debug('Handling bitcoincash URI with legacy address');
|
||||
var coin = 'bch';
|
||||
var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));
|
||||
|
||||
var oldAddr = parsed.address ? parsed.address.toString() : '';
|
||||
if (!oldAddr) return false;
|
||||
|
||||
var addr = '';
|
||||
|
||||
var a = bitcore.Address(oldAddr).toObject();
|
||||
addr = bitcoreCash.Address.fromObject(a).toString();
|
||||
|
||||
// Translate address
|
||||
$log.debug('address transalated to:' + addr);
|
||||
popupService.showConfirm(
|
||||
gettextCatalog.getString('Bitcoin cash Payment'),
|
||||
gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
|
||||
gettextCatalog.getString('OK'),
|
||||
gettextCatalog.getString('Cancel'),
|
||||
function(ret) {
|
||||
if (!ret) return false;
|
||||
|
||||
var message = parsed.message;
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
// paypro not yet supported on cash
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
}
|
||||
);
|
||||
return true;
|
||||
// Plain URL
|
||||
} else if (/^https?:\/\//.test(data)) {
|
||||
payproService.getPayProDetails(data, coin, function(err, details) {
|
||||
if (err) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'url'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
handlePayPro(details);
|
||||
return true;
|
||||
});
|
||||
// Plain Address
|
||||
} else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'bitcoinAddress'
|
||||
});
|
||||
} else {
|
||||
goToAmountPage(data);
|
||||
}
|
||||
} else if (bitcoreCash.Address.isValid(data, 'livenet')) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'bitcoinAddress',
|
||||
coin: 'bch',
|
||||
});
|
||||
} else {
|
||||
goToAmountPage(data, 'bch');
|
||||
}
|
||||
} else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.glidera', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.coinbase', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// BitPayCard Authentication
|
||||
} else if (data && data.indexOf(appConfigService.name + '://') === 0) {
|
||||
|
||||
// Disable BitPay Card
|
||||
if (!appConfigService._enabledExtensions.debitcard) return false;
|
||||
|
||||
var secret = getParameterByName('secret', data);
|
||||
var email = getParameterByName('email', data);
|
||||
var otp = getParameterByName('otp', data);
|
||||
var reason = getParameterByName('r', data);
|
||||
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
switch (reason) {
|
||||
default:
|
||||
case '0':
|
||||
/* For BitPay card binding */
|
||||
$state.transitionTo('tabs.bitpayCardIntro', {
|
||||
secret: secret,
|
||||
email: email,
|
||||
otp: otp
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
// Join
|
||||
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// Old join
|
||||
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
} else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'privateKey'
|
||||
});
|
||||
} else if (data && ((data.substring(0, 2) == '1|') || (data.substring(0, 2) == '2|') || (data.substring(0, 2) == '3|'))) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'text'
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function goToAmountPage(toAddress, coin) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
$timeout(function() {
|
||||
var stateParams = {
|
||||
toAddress: toAddress,
|
||||
displayAddress: toAddress,
|
||||
coin: coin,
|
||||
noPrefix: 1
|
||||
};
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function handlePayPro(payProData, coin) {
|
||||
|
||||
console.log(payProData);
|
||||
|
||||
var toAddr = payProData.toAddress;
|
||||
var amount = payProData.amount;
|
||||
var paymentUrl = payProData.url;
|
||||
var expires = payProData.expires;
|
||||
var time = payProData.time;
|
||||
|
||||
if (coin === 'bch') {
|
||||
var displayAddr = payProData.outputs[0].address;
|
||||
toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
|
||||
amount = payProData.outputs[0].amount;
|
||||
paymentUrl = payProData.paymentUrl;
|
||||
expires = Math.floor(new Date(expires).getTime() / 1000)
|
||||
time = Math.ceil(new Date(time).getTime() / 1000)
|
||||
}
|
||||
|
||||
var name = payProData.domain;
|
||||
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
amount: amount,
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: time,
|
||||
displayAddress: displayAddr,
|
||||
toAddress: toAddr,
|
||||
url: paymentUrl,
|
||||
verified: true
|
||||
};
|
||||
|
||||
var stateParams = {
|
||||
amount: thirdPartyData.amount,
|
||||
toAddress: thirdPartyData.toAddress,
|
||||
coin: coin,
|
||||
thirdParty: thirdPartyData
|
||||
};
|
||||
|
||||
// fee
|
||||
if (thirdPartyData.requiredFeeRate) {
|
||||
stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
|
||||
}
|
||||
|
||||
// This does not make sense, thirdPartyData gets added by stateParams below
|
||||
//sendFlowService.pushState(thirdPartyData);
|
||||
|
||||
scannerService.pausePreview();
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
}).then(function() {
|
||||
$timeout(function() {
|
||||
sendFlowService.pushState(stateParams); // Need to do more here
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return root;
|
||||
});
|
||||
180
src/js/services/latest-release.service.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('latestReleaseService', latestReleaseService);
|
||||
|
||||
function latestReleaseService($log, $http, $ionicPopup, configService, externalLinkService, gettextCatalog, platformInfo) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
checkLatestRelease: checkLatestRelease,
|
||||
requestLatestRelease: requestLatestRelease,
|
||||
showUpdatePopup: showUpdatePopup
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function checkLatestRelease(cb) {
|
||||
var releaseURL = configService.getDefaults().release.url;
|
||||
|
||||
requestLatestRelease(releaseURL, function (err, releaseData) {
|
||||
if (err) return cb(err);
|
||||
var currentVersion = window.version;
|
||||
var latestVersion = releaseData.tag_name;
|
||||
|
||||
if (!verifyTagFormat(currentVersion))
|
||||
return cb('Cannot verify the format of version tag: ' + currentVersion);
|
||||
if (!verifyTagFormat(latestVersion))
|
||||
return cb('Cannot verify the format of latest release tag: ' + latestVersion);
|
||||
|
||||
var current = formatTagNumber(currentVersion);
|
||||
var latest = formatTagNumber(latestVersion);
|
||||
|
||||
if (latest.major < current.major || (latest.major === current.major && latest.minor <= current.minor)) {
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
var releaseSearchTerm = "";
|
||||
if (platformInfo.isNW) { // XX SP: DESKTOP: Check if the latest release is already available for current OS
|
||||
var platform = process.platform;
|
||||
if (platform === "darwin") {
|
||||
releaseSearchTerm = "osx";
|
||||
} else if (platform === "win32") {
|
||||
releaseSearchTerm = "win";
|
||||
} else if (platform === "linux") {
|
||||
releaseSearchTerm = "linux";
|
||||
}
|
||||
var foundNewVersion = false;
|
||||
for (var i in releaseData.assets) {
|
||||
if (releaseData.assets[i].name.indexOf(releaseSearchTerm) !== -1) {
|
||||
foundNewVersion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log.debug('A new version is available: ' + latestVersion);
|
||||
|
||||
var releaseNotes = false;
|
||||
if (releaseData.body) {
|
||||
var releaseLines = releaseData.body.split('\n');
|
||||
for (var lineNum in releaseLines) {
|
||||
if (releaseLines[lineNum].substring(0, 2) === "# ") {
|
||||
releaseLines[lineNum] = "<strong>" + releaseLines[lineNum].substring(2) + "</strong>";
|
||||
} else if (releaseLines[lineNum].substring(0, 2) === "- ") {
|
||||
releaseLines[lineNum] = "• " + releaseLines[lineNum].substring(2);
|
||||
}
|
||||
}
|
||||
releaseNotes = releaseLines.join('\n');
|
||||
}
|
||||
|
||||
return cb(null, {latestVersion: latestVersion, releaseNotes: releaseNotes});
|
||||
});
|
||||
|
||||
function verifyTagFormat(tag) {
|
||||
var regex = /^v?\d+\.\d+(\.\d+)?(-rc\d)?$/i;
|
||||
return regex.exec(tag);
|
||||
}
|
||||
|
||||
function formatTagNumber(tag) {
|
||||
var label = false;
|
||||
if (tag.split("-")[1]) { // Move postfixes like "-rc2" to a variable
|
||||
label = tag.split("-")[1];
|
||||
tag = tag.split("-")[0];
|
||||
}
|
||||
|
||||
var formattedNumber = tag.replace(/^v/i, '').split('.');
|
||||
return {
|
||||
major: +(formattedNumber[0] ? +formattedNumber[0] : 0),
|
||||
minor: +(formattedNumber[1] ? +formattedNumber[1] : 0),
|
||||
patch: +(formattedNumber[2] ? +formattedNumber[2] : 0),
|
||||
label: label /* XX SP: Maybe we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function requestLatestRelease(releaseURL, cb) {
|
||||
$log.debug('Retrieving latest release information...');
|
||||
|
||||
var request = {
|
||||
url: releaseURL,
|
||||
method: 'GET',
|
||||
json: true
|
||||
};
|
||||
|
||||
$http(request).then(function (release) {
|
||||
$log.debug('Latest release: ' + release.data.name);
|
||||
return cb(null, release.data);
|
||||
}, function (err) {
|
||||
return cb('Cannot get the release information: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdatePopup() {
|
||||
var buttons = [];
|
||||
|
||||
if (!platformInfo.isIOS) { // There is no GitHub-release for iPhone
|
||||
buttons.push({
|
||||
text: "GitHub",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (platformInfo.isAndroid) {
|
||||
buttons.unshift({
|
||||
text: "Google Play Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://play.google.com/store/apps/details?id=com.bitcoin.mwallet';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (platformInfo.isIOS) {
|
||||
buttons.unshift({
|
||||
text: "App Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://itunes.apple.com/app/id1252903728';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
} else if (platformInfo.isNW) {
|
||||
if (process.platform === 'darwin') {
|
||||
buttons.unshift({
|
||||
text: "Mac App Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://itunes.apple.com/app/bitcoin-com-wallet/id1383072453';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (buttons.length === 1) { // There is only one source to download (probably on desktop, so open GitHub release page..)
|
||||
buttons[0].onTap();
|
||||
} else {
|
||||
buttons.push({
|
||||
text: gettextCatalog.getString('Go Back'),
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$ionicPopup.show({
|
||||
title: gettextCatalog.getString('Update Available'),
|
||||
subTitle: gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.'),
|
||||
cssClass: 'popup-update',
|
||||
buttons: buttons
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.services')
|
||||
.factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, configService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.checkLatestRelease = function(cb) {
|
||||
var releaseURL = configService.getDefaults().release.url;
|
||||
|
||||
requestLatestRelease(releaseURL, function(err, release) {
|
||||
if (err) return cb(err);
|
||||
var currentVersion = window.version;
|
||||
var latestVersion = release.data.tag_name;
|
||||
|
||||
if (!verifyTagFormat(currentVersion))
|
||||
return cb('Cannot verify the format of version tag: ' + currentVersion);
|
||||
if (!verifyTagFormat(latestVersion))
|
||||
return cb('Cannot verify the format of latest release tag: ' + latestVersion);
|
||||
|
||||
var current = formatTagNumber(currentVersion);
|
||||
var latest = formatTagNumber(latestVersion);
|
||||
|
||||
if (latest.major < current.major || (latest.major == current.major && latest.minor <= current.minor))
|
||||
return cb(null, false);
|
||||
|
||||
$log.debug('A new version is available: ' + latestVersion);
|
||||
return cb(null, true);
|
||||
});
|
||||
|
||||
function verifyTagFormat(tag) {
|
||||
var regex = /^v?\d+\.\d+\.\d+$/i;
|
||||
return regex.exec(tag);
|
||||
};
|
||||
|
||||
function formatTagNumber(tag) {
|
||||
var formattedNumber = tag.replace(/^v/i, '').split('.');
|
||||
return {
|
||||
major: +formattedNumber[0],
|
||||
minor: +formattedNumber[1],
|
||||
patch: +formattedNumber[2]
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function requestLatestRelease(releaseURL, cb) {
|
||||
$log.debug('Retrieving latest relsease information...');
|
||||
|
||||
var request = {
|
||||
url: releaseURL,
|
||||
method: 'GET',
|
||||
json: true
|
||||
};
|
||||
|
||||
$http(request).then(function(release) {
|
||||
$log.debug('Latest release: ' + release.data.name);
|
||||
return cb(null, release);
|
||||
}, function(err) {
|
||||
return cb('Cannot get the release information: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
root.get = function(processName) {
|
||||
|
|
@ -82,24 +78,15 @@ 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 = '<div>' + showName + '</div>';
|
||||
else tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
|
||||
$ionicLoading.show({
|
||||
template: tmpl
|
||||
template: tmpl,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return root;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingData, appConfigService) {
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingDataService, appConfigService) {
|
||||
var root = {};
|
||||
|
||||
var handleOpenURL = function(args) {
|
||||
|
|
@ -23,9 +23,12 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
|
|||
|
||||
document.addEventListener('handleopenurl', handleOpenURL, false);
|
||||
|
||||
if (!incomingData.redir(url)) {
|
||||
incomingDataService.redir(url, function onError(err) {
|
||||
if (err) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var handleResume = function() {
|
||||
|
|
|
|||
85
src/js/services/send-flow-router.service.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowRouterService', sendFlowRouterService);
|
||||
|
||||
function sendFlowRouterService(
|
||||
sendFlowStateService
|
||||
, $state, $ionicHistory, $timeout
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack,
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start new send flow
|
||||
*/
|
||||
function start() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
if (state.isRequestAmount) {
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
} else {
|
||||
if ($state.current.name != 'tabs.send') {
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send').then(function () {
|
||||
$timeout(function () {
|
||||
goNext();
|
||||
}, 60);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
goNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next page
|
||||
* Routing strategy : https://bitcoindotcom.atlassian.net/wiki/x/BQDWKQ
|
||||
*/
|
||||
function goNext() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
var needsDestination = !state.toWalletId && !state.toAddress;
|
||||
var needsOrigin = !state.fromWalletId;
|
||||
var needsAmount = !state.amount && !state.sendMax;
|
||||
|
||||
if (needsDestination) {
|
||||
if (!state.isWalletTransfer && !state.thirdParty) {
|
||||
$state.go('tabs.send');
|
||||
return;
|
||||
} else if (!needsOrigin) {
|
||||
$state.go('tabs.send.destination');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsOrigin) {
|
||||
$state.go('tabs.send.origin');
|
||||
} else if (needsAmount) {
|
||||
$state.go('tabs.send.amount');
|
||||
} else {
|
||||
$state.go('tabs.send.review');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous page
|
||||
*/
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
142
src/js/services/send-flow-state.service.js
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowStateService', sendFlowStateService);
|
||||
|
||||
function sendFlowStateService($log) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
state: {
|
||||
amount: 0,
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
init: init,
|
||||
clear: clear,
|
||||
getClone: getClone,
|
||||
map: map,
|
||||
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() {
|
||||
$log.debug("send-flow-state clear()");
|
||||
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current state only
|
||||
*/
|
||||
function clearCurrent() {
|
||||
$log.debug("send-flow-state clearCurrent()");
|
||||
|
||||
service.state = {
|
||||
amount: 0,
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a clone of the current state
|
||||
*/
|
||||
function getClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
currentState[key] = service.state[key];
|
||||
}
|
||||
});
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in the current state from the params
|
||||
* @param {Object} 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');
|
||||
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
map(params);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
148
src/js/services/send-flow.service.js
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('copayApp.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
|
||||
function sendFlowService($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")
|
||||
state: {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
clear: clear,
|
||||
getStateClone: getStateClone,
|
||||
map: map,
|
||||
popState: popState,
|
||||
pushState: pushState,
|
||||
startSend: startSend
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function clear() {
|
||||
console.log("sendFlow clear()");
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
function clearCurrent() {
|
||||
console.log("sendFlow clearCurrent()");
|
||||
service.state = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handy for debugging
|
||||
*/
|
||||
function getStateClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
currentState[key] = service.state[key];
|
||||
}
|
||||
});
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all previous state
|
||||
*/
|
||||
function startSend(params) {
|
||||
console.log('startSend()');
|
||||
clear();
|
||||
map(params);
|
||||
}
|
||||
|
||||
function map(params) {
|
||||
Object.keys(params).forEach(function forNewParam(key) {
|
||||
service.state[key] = params[key];
|
||||
});
|
||||
};
|
||||
|
||||
function popState() {
|
||||
console.log('sendFlow pop');
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
map(params);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
function pushState(params) {
|
||||
console.log('sendFlow push');
|
||||
var currentParams = getStateClone();
|
||||
service.previousStates.push(currentParams);
|
||||
clearCurrent();
|
||||
map(params);
|
||||
};
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -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;
|
||||
},
|
||||
|
|
@ -361,7 +366,8 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
|
|||
},
|
||||
ValidateAddress : function(address, coin) {
|
||||
var promise = $q.defer();
|
||||
SSA.ValidateAdddress(address, coin, function(data){
|
||||
SSA.ValidateAdddress(address, coin, function onRequest(data){
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
|
|
|
|||
112
src/js/services/shapeshift.service.js
Normal file
|
|
@ -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')));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -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;
|
||||
});
|
||||
244
src/js/services/wallet-history.service.js
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('walletHistoryService', walletHistoryService);
|
||||
|
||||
function walletHistoryService(configService, storageService, lodash, $log, txFormatService) {
|
||||
//var PAGE_SIZE = 50;
|
||||
var PAGE_SIZE = 20; // For dev only
|
||||
// How much to overlap on each end of the page, for mitigating inconsistent sort order.
|
||||
var PAGE_OVERLAP_FRACTION = 0.2;
|
||||
var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION);
|
||||
// The amount of transactions in the new overlapping resultset that we already know about.
|
||||
// If we know about at least this many, then there are probably no gaps.
|
||||
var MIN_KNOWN_TX_OVERLAP = Math.floor(PAGE_OVERLAP * 0.5);
|
||||
|
||||
var SAFE_CONFIRMATIONS = 6;
|
||||
|
||||
var allTransactionsFetched = false;
|
||||
var service = {
|
||||
getCachedTxHistory: getCachedTxHistory,
|
||||
updateLocalTxHistoryByPage: updateLocalTxHistoryByPage,
|
||||
};
|
||||
return service;
|
||||
|
||||
function addEarlyTransactions(walletId, cachedTxs, newTxs) {
|
||||
|
||||
var cachedTxIds = {};
|
||||
cachedTxs.forEach(function forCachedTx(tx){
|
||||
cachedTxIds[tx.txid] = true;
|
||||
});
|
||||
|
||||
var someTransactionsWereNew = false;
|
||||
var overlappingTxsCount = 0;
|
||||
|
||||
newTxs.forEach(function forNewTx(tx){
|
||||
if (cachedTxIds[tx.txid]) {
|
||||
overlappingTxsCount++;
|
||||
} else {
|
||||
someTransactionsWereNew = true;
|
||||
cachedTxs.push(tx);
|
||||
}
|
||||
});
|
||||
|
||||
if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good
|
||||
if (someTransactionsWereNew) {
|
||||
saveTxHistory(walletId, cachedTxs);
|
||||
} else if (overlappingTxsCount === newTxs.length) {
|
||||
allTransactionsFetched = true;
|
||||
}
|
||||
return cachedTxs;
|
||||
} else {
|
||||
// We might be missing some txs.
|
||||
console.error('We might be missing some txs in the history.');
|
||||
// Our history is wrong, so remove it - we could instead, try to fetch data that was not so early.
|
||||
storageService.removeTxHistory(walletId, function onRemoveTxHistory(){});
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function addLatestTransactions(walletId, cachedTxs, newTxs) {
|
||||
var cachedTxIds = {};
|
||||
cachedTxs.forEach(function forCachedTx(tx){
|
||||
cachedTxIds[tx.txid] = true;
|
||||
});
|
||||
|
||||
var someTransactionsWereNew = false;
|
||||
var overlappingTxsCount = 0;
|
||||
var uniqueNewTxs = [];
|
||||
|
||||
newTxs.forEach(function forNewTx(tx){
|
||||
if (cachedTxIds[tx.txid]) {
|
||||
overlappingTxsCount++;
|
||||
} else {
|
||||
someTransactionsWereNew = true;
|
||||
uniqueNewTxs.push(tx);
|
||||
}
|
||||
});
|
||||
|
||||
if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good
|
||||
if (someTransactionsWereNew) {
|
||||
var allTxs = uniqueNewTxs.concat(cachedTxs);
|
||||
saveTxHistory(walletId, allTxs);
|
||||
return allTxs;
|
||||
} else {
|
||||
return cachedTxs;
|
||||
}
|
||||
} else {
|
||||
// We might be missing some txs.
|
||||
// Our history is wrong, so just include the latest ones
|
||||
saveTxHistory(walletId, newTxs);
|
||||
return newTxs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Only clear the cache once we have received new transactions from the server.
|
||||
/**
|
||||
* @param {function(err, txs)} cb - transactions is always an array, may be empty
|
||||
*/
|
||||
function fetchTxHistoryByPage(wallet, start, cb) {
|
||||
var skip = Math.max(0, start - PAGE_OVERLAP);
|
||||
var limit = PAGE_SIZE;
|
||||
|
||||
var opts = {
|
||||
skip: skip,
|
||||
limit: limit
|
||||
};
|
||||
wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) {
|
||||
if (err) {
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
if (txsFromServer.length === 0) {
|
||||
return cb(null, []);
|
||||
}
|
||||
|
||||
var processedTxs = processNewTxs(wallet, txsFromServer);
|
||||
|
||||
return cb(null, processedTxs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} walletId
|
||||
* @param {function(error, txs)} cb - txs is always an array, may be empty
|
||||
*/
|
||||
function getCachedTxHistory(walletId, cb) {
|
||||
storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){
|
||||
if (err) {
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
if (!txHistoryString) {
|
||||
return cb(null, []);
|
||||
}
|
||||
|
||||
try {
|
||||
var txHistory = JSON.parse(txHistoryString);
|
||||
return cb(null, txHistory);
|
||||
} catch (e) {
|
||||
$log.error('Failed to parse tx history.', e);
|
||||
return cb(e, []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processNewTxs(wallet, txs) {
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
var txHistoryUnique = {};
|
||||
var processedTxs = [];
|
||||
wallet.hasUnsafeConfirmed = false;
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx = txFormatService.processTx(wallet.coin, tx);
|
||||
|
||||
// no future transactions...
|
||||
if (tx.time > now)
|
||||
tx.time = now;
|
||||
|
||||
if (tx.confirmations >= SAFE_CONFIRMATIONS) {
|
||||
tx.safeConfirmed = SAFE_CONFIRMATIONS + '+';
|
||||
} else {
|
||||
tx.safeConfirmed = false;
|
||||
wallet.hasUnsafeConfirmed = true;
|
||||
}
|
||||
|
||||
if (tx.note) {
|
||||
delete tx.note.encryptedEditedByName;
|
||||
delete tx.note.encryptedBody;
|
||||
}
|
||||
|
||||
if (!txHistoryUnique[tx.txid]) {
|
||||
processedTxs.push(tx);
|
||||
txHistoryUnique[tx.txid] = true;
|
||||
} else {
|
||||
$log.debug('Ignoring duplicate TX in history: ' + tx.txid)
|
||||
}
|
||||
});
|
||||
|
||||
return processedTxs;
|
||||
};
|
||||
|
||||
function saveTxHistory(walletId, processedTxs) {
|
||||
storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){
|
||||
if (error) {
|
||||
$log.error('pagination Failed to save tx history.', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) {
|
||||
|
||||
if (flushCacheOnNew) {
|
||||
fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){
|
||||
if (err) {
|
||||
return cb(err, txs);
|
||||
}
|
||||
saveTxHistory(wallet.id, txs);
|
||||
return cb(null, txs);
|
||||
});
|
||||
} else {
|
||||
getCachedTxHistory(wallet.id, function onCachedHistory(err, cachedTxs){
|
||||
if (err) {
|
||||
$log.error('Failed to get cached tx history.', err);
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
var start = getLatest ? 0 : cachedTxs.length;
|
||||
fetchTxHistoryByPage(wallet, start, function onFetchHistory(err, fetchedTxs){
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (fetchedTxs.length === 0) {
|
||||
return cb(null, cachedTxs, true /*fetchedAllTransactions*/);
|
||||
}
|
||||
|
||||
var txs = [];
|
||||
if (getLatest) {
|
||||
txs = addLatestTransactions(wallet.id, cachedTxs, fetchedTxs);
|
||||
} else {
|
||||
allTransactionsFetched = false;
|
||||
txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs);
|
||||
return cb(null, txs, allTransactionsFetched);
|
||||
}
|
||||
return cb(null, txs);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
})();
|
||||
|
|
@ -396,22 +396,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
return ret;
|
||||
};
|
||||
|
||||
var updateLocalTxHistory = function(wallet, opts, cb) {
|
||||
var FIRST_LIMIT = 5;
|
||||
var LIMIT = 50;
|
||||
var requestLimit = FIRST_LIMIT;
|
||||
var walletId = wallet.credentials.walletId;
|
||||
var skipped = 0;
|
||||
|
||||
var opts = opts || {};
|
||||
var progressFn = opts.progressFn || function() {};
|
||||
var foundLimitTx = false;
|
||||
|
||||
|
||||
if (opts.feeLevels) {
|
||||
opts.lowAmount = root.getLowAmount(wallet, opts.feeLevels);
|
||||
}
|
||||
|
||||
var fixTxsUnit = function(txs) {
|
||||
function fixTxsUnit(txs) {
|
||||
if (!txs || !txs[0] || !txs[0].amountStr) return;
|
||||
|
||||
var cacheCoin = txs[0].amountStr.split(' ')[1];
|
||||
|
|
@ -426,6 +413,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
}
|
||||
};
|
||||
|
||||
var updateLocalTxHistory = function(wallet, opts, cb) {
|
||||
var FIRST_LIMIT = 5;
|
||||
var LIMIT = 50;
|
||||
var requestLimit = FIRST_LIMIT;
|
||||
var walletId = wallet.credentials.walletId;
|
||||
|
||||
var opts = opts || {};
|
||||
var progressFn = opts.progressFn || function() {};
|
||||
var foundLimitTx = false;
|
||||
|
||||
if (opts.feeLevels) {
|
||||
opts.lowAmount = root.getLowAmount(wallet, opts.feeLevels);
|
||||
}
|
||||
|
||||
|
||||
getSavedTxs(walletId, function(err, txsFromLocal) {
|
||||
if (err) return cb(err);
|
||||
|
||||
|
|
@ -435,13 +437,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null;
|
||||
var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null;
|
||||
|
||||
$log.debug('Confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length);
|
||||
console.log('pagination Hard confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length);
|
||||
|
||||
// First update
|
||||
progressFn(txsFromLocal, 0);
|
||||
wallet.completeHistory = txsFromLocal;
|
||||
|
||||
function getNewTxs(newTxs, skip, next) {
|
||||
console.log('pagination getNewTxs skip: ' + skip);
|
||||
getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res) {
|
||||
if (err) {
|
||||
$log.warn(bwcError.msg(err, 'Server Error')); //TODO
|
||||
|
|
@ -454,6 +457,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
return next(err);
|
||||
}
|
||||
|
||||
console.log('pagination Result count: ' + res.length);
|
||||
// Check if new txs are founds, if yes, lets investigate in the 50 next
|
||||
// To be sure we are not missing txs by sorting (maybe a new tx is after the "endingTxid"
|
||||
var newDiscoveredTxs = res.filter(function (x) {
|
||||
|
|
@ -462,7 +466,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
}).length == 0;
|
||||
});
|
||||
|
||||
$log.debug('Discovering TXs. Got:' + newDiscoveredTxs.length);
|
||||
console.log('pagination Discovering new TXs. Got:' + newDiscoveredTxs.length);
|
||||
|
||||
var shouldContinue = newDiscoveredTxs.length > 0;
|
||||
|
||||
|
|
@ -475,7 +479,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
|
||||
skip = skip + requestLimit;
|
||||
|
||||
$log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
|
||||
console.log('pagination Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
|
||||
|
||||
// TODO Dirty <HACK>
|
||||
// do not sync all history, just looking for a single TX.
|
||||
|
|
@ -492,14 +496,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
}
|
||||
// </HACK>
|
||||
|
||||
shouldContinue = false;
|
||||
|
||||
skipped = skip;
|
||||
|
||||
if (!shouldContinue) {
|
||||
$log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length);
|
||||
console.log('pagination Finished Sync: New / soft confirmed Txs: ' + newTxs.length);
|
||||
return next(null, newTxs);
|
||||
}
|
||||
|
||||
requestLimit = LIMIT;
|
||||
getNewTxs(newTxs, skip, next);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -535,6 +541,87 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
if (opts.getMoreTxs) {
|
||||
var requestLimit = LIMIT;
|
||||
getNewTxs([], skipped, function(err, txs) {
|
||||
if (err) return cb(err);
|
||||
|
||||
console.log();
|
||||
createReceivedEvents(txs);
|
||||
|
||||
var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) {
|
||||
return x.txid;
|
||||
});
|
||||
|
||||
|
||||
function updateNotes(cb2) {
|
||||
if (!endingTs) return cb2();
|
||||
|
||||
$log.debug('Syncing notes from: ' + endingTs);
|
||||
wallet.getTxNotes({
|
||||
minTs: endingTs
|
||||
}, function(err, notes) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
return cb2();
|
||||
};
|
||||
lodash.each(notes, function(note) {
|
||||
$log.debug('Note for ' + note.txid);
|
||||
lodash.each(newHistory, function(tx) {
|
||||
if (tx.txid == note.txid) {
|
||||
$log.debug('...updating note for ' + note.txid);
|
||||
tx.note = note;
|
||||
}
|
||||
});
|
||||
});
|
||||
return cb2();
|
||||
});
|
||||
}
|
||||
|
||||
function updateLowAmount(txs) {
|
||||
if (!opts.lowAmount) return;
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx.lowAmount = tx.amount < opts.lowAmount;
|
||||
});
|
||||
};
|
||||
|
||||
updateLowAmount(txs);
|
||||
|
||||
updateNotes(function() {
|
||||
|
||||
// <HACK>
|
||||
if (foundLimitTx) {
|
||||
$log.debug('Tx history read until limitTx: ' + opts.limitTx);
|
||||
return cb(null, newHistory);
|
||||
}
|
||||
// </HACK>
|
||||
|
||||
var historyToSave = JSON.stringify(newHistory);
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx.recent = true;
|
||||
})
|
||||
|
||||
$log.debug('Tx History synced. Total Txs: ' + newHistory.length);
|
||||
|
||||
// Final update
|
||||
if (walletId == wallet.credentials.walletId) {
|
||||
wallet.completeHistory = newHistory;
|
||||
}
|
||||
|
||||
return storageService.setTxHistory(historyToSave, walletId, function() {
|
||||
$log.debug('Tx History saved.');
|
||||
return cb(null, newHistory);
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
skipped = 0;
|
||||
|
||||
getNewTxs([], 0, function(err, txs) {
|
||||
if (err) return cb(err);
|
||||
|
||||
|
|
@ -603,12 +690,17 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
|
||||
return storageService.setTxHistory(historyToSave, walletId, function() {
|
||||
$log.debug('Tx History saved.');
|
||||
return cb(null, newHistory);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
root.getMoreTxs = function(wallet, cb) {
|
||||
var opts = {};
|
||||
opts.getMoreTxs = true;
|
||||
updateLocalTxHistory(wallet, opts, cb);
|
||||
};
|
||||
|
||||
root.getTxNote = function(wallet, txid, cb) {
|
||||
|
|
@ -634,7 +726,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
};
|
||||
|
||||
root.getTx = function(wallet, txid, cb) {
|
||||
|
||||
function finish(list) {
|
||||
var tx = lodash.find(list, {
|
||||
txid: txid
|
||||
|
|
@ -651,7 +742,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
limitTx: txid
|
||||
}, function(err, txHistory) {
|
||||
if (err) return cb(err);
|
||||
|
||||
finish(txHistory);
|
||||
});
|
||||
}
|
||||
|
|
@ -675,16 +765,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
root.getTxHistory = function(wallet, opts, cb) {
|
||||
opts = opts || {};
|
||||
|
||||
var walletId = wallet.credentials.walletId;
|
||||
|
||||
if (!wallet.isComplete()) return cb();
|
||||
|
||||
function isHistoryCached() {
|
||||
return wallet.completeHistory && wallet.completeHistory.isValid;
|
||||
};
|
||||
|
||||
// disable caching
|
||||
//if (isHistoryCached() && !opts.force) return cb(null, wallet.completeHistory);
|
||||
// var historyIsCached = wallet.completeHistory && wallet.completeHistory.isValid;
|
||||
// if (historyIsCached && !opts.force) return cb(null, wallet.completeHistory);
|
||||
|
||||
$log.debug('Updating Transaction History');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
%button-standard {
|
||||
width: 85%;
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
.fee-summary {
|
||||
position: relative;
|
||||
background-color: #F2F2F2;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 5px 12px 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: #F2F2F2;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
|
|
@ -18,12 +18,11 @@
|
|||
}
|
||||
|
||||
.amount {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.fee-fiat {
|
||||
display: inline;
|
||||
|
||||
&.positive {
|
||||
color: #70955F;
|
||||
}
|
||||
|
|
@ -35,6 +34,7 @@
|
|||
|
||||
.fee-crypto {
|
||||
color: #A7A7A7;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
left: 13px;
|
||||
top: 50%;
|
||||
padding: 0;
|
||||
-webkit-transform: translate(0,-50%);
|
||||
transform: translate(0,-50%);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@
|
|||
.button {
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
line-height: 26px;
|
||||
padding: 8px 6px;
|
||||
}
|
||||
}
|
||||
.button-first-contact img {
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
.primary-amount-display {
|
||||
margin-right: 5px;
|
||||
word-break: break-all;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -17,4 +19,5 @@
|
|||
.warning {
|
||||
color: $v-warning-color-2;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
min-height: 0;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
.wallet-coin-logo {
|
||||
|
|
@ -173,6 +173,11 @@
|
|||
font-weight: 700;
|
||||
color: #444;
|
||||
}
|
||||
.release-notes {
|
||||
white-space: pre;
|
||||
white-space: pre-line;
|
||||
text-align: left;
|
||||
}
|
||||
.button {
|
||||
width: 100%;
|
||||
border: none;
|
||||
|
|
@ -190,3 +195,13 @@
|
|||
top:11px;
|
||||
}
|
||||
}
|
||||
.popup-update {
|
||||
.popup-buttons {
|
||||
display: block;
|
||||
}
|
||||
.popup-buttons .button{
|
||||
display:block;
|
||||
min-width: 100% !important;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,9 @@
|
|||
width: 100%;
|
||||
}
|
||||
.payment-received-container {
|
||||
svg {
|
||||
max-height: 400px;
|
||||
}
|
||||
margin: 0 20px;
|
||||
.payment-received-amount {
|
||||
font-size: 1.8em;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
width: auto;
|
||||
margin: 2px 0 4px;
|
||||
}
|
||||
height: 60px;
|
||||
min-height: 65px;
|
||||
line-height: 16px;
|
||||
margin-right: 0px;
|
||||
width: 95%;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
min-height: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -341,4 +366,6 @@ a.item {
|
|||
|
||||
.loading-wallet svg {
|
||||
margin-top: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
|
@ -9,3 +9,55 @@ if (!ArrayBuffer['isView']) {
|
|||
return a !== null && typeof(a) === "object" && a['buffer'] instanceof ArrayBuffer;
|
||||
};
|
||||
}
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
|
||||
if (!Array.prototype.includes) {
|
||||
Object.defineProperty(Array.prototype, 'includes', {
|
||||
value: function(searchElement, fromIndex) {
|
||||
|
||||
if (this == null) {
|
||||
throw new TypeError('"this" is null or not defined');
|
||||
}
|
||||
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
var o = Object(this);
|
||||
|
||||
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||
var len = o.length >>> 0;
|
||||
|
||||
// 3. If len is 0, return false.
|
||||
if (len === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Let n be ? ToInteger(fromIndex).
|
||||
// (If fromIndex is undefined, this step produces the value 0.)
|
||||
var n = fromIndex | 0;
|
||||
|
||||
// 5. If n ≥ 0, then
|
||||
// a. Let k be n.
|
||||
// 6. Else n < 0,
|
||||
// a. Let k be len + n.
|
||||
// b. If k < 0, let k be 0.
|
||||
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
||||
|
||||
function sameValueZero(x, y) {
|
||||
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
|
||||
}
|
||||
|
||||
// 7. Repeat, while k < len
|
||||
while (k < len) {
|
||||
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
|
||||
// b. If SameValueZero(searchElement, elementK) is true, return true.
|
||||
if (sameValueZero(o[k], searchElement)) {
|
||||
return true;
|
||||
}
|
||||
// c. Increase k by 1.
|
||||
k++;
|
||||
}
|
||||
|
||||
// 8. Return false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ module.exports = function(config) {
|
|||
files: [
|
||||
'node_modules/angular/angular.js',
|
||||
|
||||
'bitanalytics/bitanalytics-0.1.0.js',
|
||||
'bitanalytics/bitanalytics.js',
|
||||
|
||||
// From Gruntfile.js
|
||||
'bower_components/qrcode-generator/js/qrcode.js',
|
||||
|
|
|
|||
|
|
@ -53,11 +53,12 @@
|
|||
</div>
|
||||
|
||||
<div class="keypad-container" style="background: #fff; position: absolute; bottom: 0; margin-bottom: 57px; width: 100%;">
|
||||
<div class="sendmax" ng-if="vm.availableFunds && !vm.isRequestingSpecificAmount">
|
||||
<div class="sendmax" ng-if="vm.showSendMaxButton || vm.showSendLimitMaxButton">
|
||||
<button class="button button-sendmax" ng-click="vm.sendMax()">
|
||||
<span>
|
||||
<span translate>Use All Available Funds</span> 
|
||||
<span class="available-funds-amount">(<formatted-amount value="{{vm.availableFunds}}"></formatted-amount>)</span>
|
||||
<span ng-if="vm.showSendMaxButton" translate>Use All Available Funds</span>
|
||||
<span ng-if="vm.showSendLimitMaxButton" translate>Send Maximum Amount</span> 
|
||||
<span class="available-funds-amount">(<formatted-amount value="{{vm.sendableFunds}}"></formatted-amount>)</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<i class="icon big-icon-svg theme-circle theme-circle-community">
|
||||
<div class="bg icon-share"></div>
|
||||
</i>
|
||||
<span>Share the Wallet App</span>
|
||||
<span translate>Share the Wallet App</span>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,21 +9,21 @@
|
|||
<img src="img/icon-bitcoin-small.svg">
|
||||
</div>
|
||||
<div class="incoming-data-menu__url__text">
|
||||
{{data}}
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="addToAddressBook(data)">
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="addToAddressBook(data.toAddress)">
|
||||
<img src="img/icon-contacts.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Add as a contact</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="sendPaymentToAddress(data)">
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="sendPaymentToAddress(data.toAddress)">
|
||||
<img src="img/icon-send-alt.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Send payment to this address</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data">
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
|
|
@ -38,11 +38,11 @@
|
|||
<div class="incoming-data-menu__header" translate>Text</div>
|
||||
<div class="incoming-data-menu__url">
|
||||
<div class="incoming-data-menu__url__text" style="border: 0;">
|
||||
{{data}}
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data">
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
|
|
@ -57,16 +57,40 @@
|
|||
<div class="incoming-data-menu__header" translate>Private Key</div>
|
||||
<div class="incoming-data-menu__url">
|
||||
<div class="incoming-data-menu__url__text" style="border: 0;">
|
||||
{{data}}
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="scanPaperWallet(data)">
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="scanPaperWallet(data.original)">
|
||||
<img src="img/icon-import.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Sweep paper wallet</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data">
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__cancel item" ng-click="hide()" translate>
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div ng-if="type === 'url'">
|
||||
<div class="incoming-data-menu__item head">
|
||||
<div class="incoming-data-menu__header" translate>URL</div>
|
||||
<div class="incoming-data-menu__url">
|
||||
<div class="incoming-data-menu__url__text" style="border: 0;">
|
||||
{{data.original}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="incoming-data-menu__item item item-icon-right" ng-click="goToUrl(data.original)">
|
||||
<img src="img/icon-link-external.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Open in web browser</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
</a>
|
||||
<a class="incoming-data-menu__item item item-icon-right" copy-to-clipboard="data.original">
|
||||
<img src="img/icon-paperclip.svg">
|
||||
<div class="incoming-data-menu__item__text" translate>Copy to clipboard</div>
|
||||
<i class="icon bp-arrow-right"></i>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
<a ng-show="walletNotRegistered" ng-click="recreate()" translate>Tap to recreate</a>
|
||||
<a ng-show="updateStatusError" ng-click="updateAll(true)" translate>Tap to retry</a>
|
||||
</div>
|
||||
<span ng-click="onRefresh()" class="right" ng-show="(!updatingStatus && !updatingTxHistory)">↻</span>
|
||||
<ion-spinner class="spinner-dark recent right loading-wallet" icon="crescent" ng-show="(updatingStatus || updatingTxHistory) &&
|
||||
!walletNotRegistered && !updateStatusError && !updateTxHistoryError"></ion-spinner>
|
||||
<span ng-click="onRefresh()" class="right" ng-show="(!updatingStatus && !vm.updatingTxHistory && !vm.gettingInitialHistory)">↻</span>
|
||||
<ion-spinner class="spinner-dark recent right loading-wallet" icon="crescent" ng-show="(updatingStatus || vm.updatingTxHistory || vm.gettingInitialHistory) &&
|
||||
!walletNotRegistered && !updateStatusError && !vm.updateTxHistoryFailed"></ion-spinner>
|
||||
|
||||
<div>
|
||||
<span ng-show="wallet.status.wallet.singleAddress" class="size-12"><span translate>Auditable</span></span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<ion-view hide-tabs>
|
||||
<ion-nav-bar class="bar-royal">
|
||||
<ion-nav-title>{{'Sweep paper wallet' | translate}}</ion-nav-title>
|
||||
<ion-nav-title>{{'Sweep Paper Wallet' | translate}}</ion-nav-title>
|
||||
<ion-nav-back-button>
|
||||
</ion-nav-back-button>
|
||||
</ion-nav-bar>
|
||||
|
|
@ -45,19 +45,19 @@
|
|||
</div>
|
||||
<div ng-class="ng-hide" ng-show="noMatchingBchWallet">
|
||||
<div class="text-center size-12 text-gray">
|
||||
<span>No Bitcoin Cash wallet to transfer funds to found.</span>
|
||||
<span translate>No Bitcoin Cash wallet to transfer funds to found.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-class="ng-hide" ng-show="!bchBalance && readyToShow">
|
||||
<div class="text-center">
|
||||
<h4 class="text-bold" translate>No Bitcoin Cash found</h4>
|
||||
<h4 class="text-bold" translate>No Bitcoin Cash found.</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-class="ng-hide" ng-show="btcBalance">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<h4 class="text-bold" translate>Bitcoin found:</h4>
|
||||
<h4 class="text-bold" translate>Bitcoin Core found:</h4>
|
||||
<div class="size-24">
|
||||
<span>{{btcBalanceText}}</span>
|
||||
<div class="size-14 amount-alternative">
|
||||
|
|
@ -95,13 +95,13 @@
|
|||
</div>
|
||||
<div ng-class="ng-hide" ng-show="noMatchingBtcWallet">
|
||||
<div class="text-center size-12 text-gray">
|
||||
<span>No Bitcoin wallet to transfer funds to found.</span>
|
||||
<span translate>No Bitcoin Core wallet to transfer funds to found.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-class="ng-hide" ng-show="!btcBalance && readyToShow">
|
||||
<div class="text-center">
|
||||
<h4 class="text-bold" translate>No Bitcoin found</h4>
|
||||
<h4 class="text-bold" translate>No Bitcoin Core found.</h4>
|
||||
</div>
|
||||
</div>
|
||||
<slide-to-accept-success
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@
|
|||
<img src="img/icon-update.svg" class="bg"/>
|
||||
</i>
|
||||
|
||||
<div class="item title">{{updateText}}</div>
|
||||
<div class="item title">{{newReleaseText}}</div>
|
||||
<div class="item release-notes" ng-if="newReleaseNotes"><span ng-bind-html="newReleaseNotes"></span></div>
|
||||
|
||||
<div class="button" ng-click="openExternalLink()">
|
||||
|
||||
<div class="button" ng-click="showUpdatePopup()">
|
||||
<span translate>Download</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,79 +16,74 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-content" ng-class="{'status-bar': isCordova}">
|
||||
<div class="bp-content" ng-class="{'status-bar': isCordova, 'collapse': scrollPosition > 50}">
|
||||
|
||||
<div class="amount-wrapper" ng-show="wallet && wallet.isComplete() && amountIsCollapsible" ng-class="{'wallet-background-color-default': !wallet.color}" ng-style="{'background-color':wallet.color}">
|
||||
<div class="amount-wrapper" ng-show="wallet && wallet.isComplete()" ng-class="{'wallet-background-color-default': !wallet.color}" ng-style="{'background-color':wallet.color}">
|
||||
|
||||
<div
|
||||
ng-style="{'background-color':wallet.color, 'height': amountHeight}"
|
||||
class="amount"
|
||||
ng-class="{collapsible: amountIsCollapsible, 'wallet-background-color-default': !wallet.color, 'no-alternative': wallet.network != 'livenet'}"
|
||||
ng-style="{'background-color':wallet.color}"
|
||||
class="amount collapsible"
|
||||
ng-class="{'wallet-background-color-default': !wallet.color, 'no-alternative': wallet.network != 'livenet'}"
|
||||
>
|
||||
|
||||
<div class="amount__error" ng-style="{opacity: altAmountOpacity}" ng-show="updateStatusError">
|
||||
<div class="amount__error" ng-show="updateStatusError">
|
||||
<span>{{updateStatusError}}</span>
|
||||
</div>
|
||||
|
||||
<div class="amount__error" ng-style="{opacity: altAmountOpacity}" ng-show="walletNotRegistered">
|
||||
<div class="amount__error" ng-show="walletNotRegistered">
|
||||
<span translate>This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-show="selectedPriceDisplay=='fiat' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
|
||||
ng-if="selectedPriceDisplay=='fiat' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
|
||||
on-hold="hideToggle()"
|
||||
ng-style="{'transform': amountScale}"
|
||||
ng-class="{amount__balance: amountIsCollapsible}">
|
||||
class="amount__balance amount__scale">
|
||||
<strong class="size-36" ng-show="status.totalBalanceAlternative">
|
||||
<formatted-amount value="{{status.totalBalanceAlternative}}" currency="{{status.alternativeIsoCode}}"></formatted-amount>
|
||||
</strong>
|
||||
<div
|
||||
class="size-14 amount-alternative"
|
||||
ng-if="status.totalBalanceStr && wallet.network == 'livenet'"
|
||||
ng-style="{opacity: altAmountOpacity}">
|
||||
ng-if="status.totalBalanceStr && wallet.network == 'livenet'">
|
||||
<formatted-amount value="{{status.totalBalanceStr}}" size-equal="true"></formatted-amount>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-show="selectedPriceDisplay=='crypto' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
|
||||
on-hold="hideToggle()"
|
||||
ng-style="{'transform': amountScale}"
|
||||
ng-if="status.totalBalanceStr"
|
||||
ng-class="{amount__balance: amountIsCollapsible}">
|
||||
ng-if="status.totalBalanceStr && selectedPriceDisplay=='crypto' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
|
||||
class="amount__balance amount__scale">
|
||||
<strong class="size-36">
|
||||
<formatted-amount value="{{status.totalBalanceStr}}"></formatted-amount>
|
||||
</strong>
|
||||
<div
|
||||
class="size-14 amount-alternative"
|
||||
ng-if="status.totalBalanceAlternative && wallet.network == 'livenet'"
|
||||
ng-style="{opacity: altAmountOpacity}">
|
||||
ng-if="status.totalBalanceAlternative && wallet.network == 'livenet'">
|
||||
<formatted-amount value="{{status.totalBalanceAlternative}}" currency="{{status.alternativeIsoCode}}"></formatted-amount>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-style="{'transform': amountScale}"
|
||||
class="amount__balance"
|
||||
ng-show="!updateStatusError && wallet.balanceHidden && !wallet.scanning"
|
||||
<div
|
||||
class="amount__balance amount__scale"
|
||||
ng-if="!updateStatusError && wallet.balanceHidden && !wallet.scanning"
|
||||
on-hold="hideToggle()">
|
||||
<strong class="size-24" translate>[Balance Hidden]</strong>
|
||||
<div ng-style="{opacity: altAmountOpacity}" class="size-14 amount-alternative" translate>
|
||||
<div class="size-14 amount-alternative" translate>
|
||||
Tap and hold to show
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-style="{'transform': amountScale}"
|
||||
class="amount__balance"
|
||||
ng-show="!updateStatusError && wallet.scanning">
|
||||
<div
|
||||
class="amount__balance amount__scale"
|
||||
ng-if="!updateStatusError && wallet.scanning">
|
||||
<strong class="size-24" translate>[Scanning Funds]</strong>
|
||||
<div ng-style="{opacity: altAmountOpacity}" class="size-14 amount-alternative" translate>
|
||||
<div class="size-14 amount-alternative" translate>
|
||||
Please wait
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="!wallet.balanceHidden && !wallet.scanning && showBalanceButton" ng-style="{'opacity': altAmountOpacity, 'transform': amountScale}">
|
||||
<div ng-if="!wallet.balanceHidden && !wallet.scanning && showBalanceButton" class="amount__scale">
|
||||
<button class="button button-standard button-primary amount__button-balance size-14" ng-click="openBalanceModal()">
|
||||
<i class="icon ion-ios-checkmark-outline"></i>
|
||||
<strong>
|
||||
|
|
@ -101,7 +96,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="send-receive-buttons row" ng-if="(status.availableBalanceSat || status.availableBalanceSat === 0) && (buttonsOpacity > 0 || isAndroid)" ng-style="{opacity: buttonsOpacity}">
|
||||
<div class="send-receive-buttons row" ng-if="(status.availableBalanceSat || status.availableBalanceSat === 0)">
|
||||
<div class="col">
|
||||
<div class="button button-outline button-white-outline" ng-click="goToReceive()">
|
||||
<span translate>Receive</span>
|
||||
|
|
@ -118,136 +113,18 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div class="wallet-details-wallet-info" ng-style="{opacity: altAmountOpacity}">
|
||||
<div class="wallet-details-wallet-info">
|
||||
<span ng-include="'views/includes/walletInfo.html'"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ion-content ng-style="{'margin-top': contentMargin}" ng-class="{collapsible: amountIsCollapsible}">
|
||||
<ion-content class="amount__balance">
|
||||
<ion-refresher
|
||||
pulling-icon="ion-ios-refresh"
|
||||
spinner="ios-small"
|
||||
on-refresh="onRefresh()">
|
||||
</ion-refresher>
|
||||
|
||||
<!-- Start Balance view duplicate (for Android compatibility) -->
|
||||
<div class="amount-wrapper" ng-show="wallet && wallet.isComplete() && !amountIsCollapsible" ng-class="{'wallet-background-color-default': !wallet.color}" ng-style="{'background-color':wallet.color}">
|
||||
|
||||
<div
|
||||
ng-style="{'background-color':wallet.color, 'height': amountHeight}"
|
||||
class="amount"
|
||||
ng-class="{collapsible: amountIsCollapsible, 'wallet-background-color-default': !wallet.color, 'no-alternative': wallet.network != 'livenet'}"
|
||||
>
|
||||
|
||||
<div class="amount__error" ng-style="{opacity: altAmountOpacity}" ng-show="updateStatusError">
|
||||
<span>{{updateStatusError}}</span>
|
||||
</div>
|
||||
|
||||
<div class="amount__error" ng-style="{opacity: altAmountOpacity}" ng-show="walletNotRegistered">
|
||||
<span translate>This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click='updateAll(true)'
|
||||
ng-show="selectedPriceDisplay=='crypto' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
|
||||
on-hold="hideToggle()"
|
||||
ng-style="{'transform': amountScale}"
|
||||
ng-class="{amount__balance: amountIsCollapsible}">
|
||||
<strong ng-if="status.totalBalanceStr" class="size-36"><formatted-amount value="{{status.totalBalanceStr}}"></formatted-amount></strong>
|
||||
<div
|
||||
class="size-14 amount-alternative"
|
||||
ng-if="status.totalBalanceAlternative && wallet.network == 'livenet'"
|
||||
ng-style="{opacity: altAmountOpacity}">
|
||||
<formatted-amount value="{{status.totalBalanceAlternative}}" currency="{{status.alternativeIsoCode}}"></formatted-amount>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click='updateAll(true)'
|
||||
ng-show="selectedPriceDisplay=='fiat' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning"
|
||||
on-hold="hideToggle()"
|
||||
ng-style="{'transform': amountScale}"
|
||||
ng-class="{amount__balance: amountIsCollapsible}">
|
||||
<strong class="size-36"><formatted-amount value="{{status.totalBalanceAlternative}}" currency="{{status.alternativeIsoCode}}"></formatted-amount></strong>
|
||||
<div
|
||||
class="size-14 amount-alternative"
|
||||
ng-if="status.totalBalanceStr && wallet.network == 'livenet'"
|
||||
ng-style="{opacity: altAmountOpacity}">
|
||||
<formatted-amount value="{{status.totalBalanceStr}}"></formatted-amount>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-style="{'transform': amountScale}"
|
||||
class="amount__balance"
|
||||
ng-show="!updateStatusError && wallet.balanceHidden && !wallet.scanning"
|
||||
on-hold="hideToggle()">
|
||||
<strong class="size-24" translate>[Balance Hidden]</strong>
|
||||
<div ng-style="{opacity: altAmountOpacity}" class="size-16 amount-alternative" translate>
|
||||
Tap and hold to show
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-style="{'transform': amountScale}"
|
||||
class="amount__balance"
|
||||
ng-show="!updateStatusError && wallet.scanning">
|
||||
<strong class="size-24" translate>[Scanning Funds]</strong>
|
||||
<div ng-style="{opacity: altAmountOpacity}" class="size-16 amount-alternative" translate>
|
||||
Please wait
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="selectedPriceDisplay=='crypto' && !wallet.balanceHidden && !wallet.scanning && showBalanceButton" ng-style="{'opacity': altAmountOpacity, 'transform': amountScale}">
|
||||
<button class="button button-standard button-primary amount__button-balance size-14" ng-click="openBalanceModal()">
|
||||
<i class="icon ion-ios-checkmark-outline"></i>
|
||||
<strong>
|
||||
{{status.spendableBalanceStr}}
|
||||
</strong>
|
||||
|
||||
<span>
|
||||
<formatted-amount value="{{status.spendableBalanceAlternative}}" currency="{{status.alternativeIsoCode}}"></formatted-amount>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-if="selectedPriceDisplay=='fiat' && !wallet.balanceHidden && !wallet.scanning && showBalanceButton" ng-style="{'opacity': altAmountOpacity, 'transform': amountScale}">
|
||||
<button class="button button-standard button-primary amount__button-balance size-14" ng-click="openBalanceModal()">
|
||||
<i class="icon ion-ios-checkmark-outline"></i>
|
||||
<strong>
|
||||
<formatted-amount value="{{status.spendableBalanceAlternative}}" currency="{{status.alternativeIsoCode}}"></formatted-amount>
|
||||
</strong>
|
||||
|
||||
<span>
|
||||
{{status.spendableBalanceStr}}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="send-receive-buttons row" ng-if="(status.availableBalanceSat || status.availableBalanceSat === 0) && (buttonsOpacity > 0 || isAndroid)" ng-style="{opacity: buttonsOpacity}">
|
||||
<div class="col">
|
||||
<div class="button button-outline button-white-outline" ng-click="goToReceive()">
|
||||
<span translate>Receive</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="button button-outline button-white-outline" ng-if="!status.availableBalanceSat" ng-click="goToBuy()">
|
||||
<span translate>Buy Bitcoin</span>
|
||||
</div>
|
||||
<div class="button button-outline button-white-outline" ng-if="status.availableBalanceSat>0" ng-click="goToSend()">
|
||||
<span translate>Send</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="wallet-details-wallet-info" ng-style="{opacity: altAmountOpacity}">
|
||||
<span ng-include="'views/includes/walletInfo.html'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Balance view duplicate (for Android compatibility) -->
|
||||
|
||||
|
||||
<a class="wallet-not-backed-up-warning" ng-if="wallet.needsBackup" ui-sref="tabs.wallet.backupWarning({from: 'tabs.wallet'})" translate>
|
||||
Wallet not backed up
|
||||
</a>
|
||||
|
|
@ -284,29 +161,29 @@
|
|||
<!-- Transactions -->
|
||||
|
||||
<div class="wallet-details__no-transaction"
|
||||
ng-show="!txHistory[0] && !updatingTxHistory && !updateTxHistoryError && !updateStatusError" translate>
|
||||
ng-show="!txHistory[0] && !vm.gettingInitialHistory && !vm.updateTxHistoryFailed && !updateStatusError" translate>
|
||||
No transactions yet
|
||||
</div>
|
||||
|
||||
|
||||
<div class="wallet-details__no-update-history"
|
||||
ng-show="!txHistory[0] && !updatingTxHistory && updateTxHistoryError" translate>
|
||||
ng-show="!txHistory[0] && !vm.gettingInitialHistory && vm.updateTxHistoryFailed" translate>
|
||||
Could not update transaction history
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-show="updatingTxHistory && updatingTxHistoryProgress>5" class="wallet-details__updating-history">
|
||||
<div ng-show="vm.gettingInitialHistory && updatingTxHistoryProgress>5" class="wallet-details__updating-history">
|
||||
<span translate>Updating transaction history. Please stand by.</span><br>
|
||||
<span translate>{{updatingTxHistoryProgress}} transactions downloaded</span>
|
||||
</div>
|
||||
|
||||
<div class="wallet-details__list" ng-show="txHistory[0] && !updatingTxHistory">
|
||||
<div ng-style="{'padding-bottom': txHistoryPaddingBottom}" class="wallet-details__list" ng-show="txHistory[0] && !updatingTxHistory">
|
||||
<div ng-repeat="btx in txHistory track by $index" ng-click="openTxModal(btx)">
|
||||
<span ng-include="'views/includes/walletHistory.html'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<ion-infinite-scroll
|
||||
ng-if="txHistory[0] && !updatingTxHistory && txHistoryShowMore"
|
||||
ng-if="txHistory[0] && vm.allowInfiniteScroll"
|
||||
on-infinite="showMore()"
|
||||
distance="1%">
|
||||
</ion-infinite-scroll>
|
||||
|
|
|
|||