├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── gulpfile.js
├── lib
├── .gitignore
├── README.md
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── alphawallet
│ │ ├── ethereum
│ │ ├── EthereumNetworkBase.java
│ │ └── NetworkInfo.java
│ │ └── token
│ │ ├── entity
│ │ ├── As.java
│ │ ├── AttributeInterface.java
│ │ ├── AttributeType.java
│ │ ├── BadContract.java
│ │ ├── ChainSpec.java
│ │ ├── ContractAddress.java
│ │ ├── ContractInfo.java
│ │ ├── CryptoFunctionsInterface.java
│ │ ├── EthereumReadBuffer.java
│ │ ├── EthereumTransaction.java
│ │ ├── EthereumWriteBuffer.java
│ │ ├── FunctionDefinition.java
│ │ ├── MagicLinkData.java
│ │ ├── MagicLinkInfo.java
│ │ ├── MessageData.java
│ │ ├── MethodArg.java
│ │ ├── NonFungibleToken.java
│ │ ├── ParseResult.java
│ │ ├── SalesOrderMalformed.java
│ │ ├── SigReturnType.java
│ │ ├── TSAction.java
│ │ ├── TicketRange.java
│ │ ├── TokenScriptParseType.java
│ │ ├── TokenScriptResult.java
│ │ ├── TokenscriptContext.java
│ │ ├── TokenscriptElement.java
│ │ ├── TransactionResult.java
│ │ ├── UnsignedLong.java
│ │ ├── XMLDsigDescriptor.java
│ │ └── XMLDsigVerificationResult.java
│ │ ├── tools
│ │ ├── Convert.java
│ │ ├── Numeric.java
│ │ ├── ParseMagicLink.java
│ │ ├── TokenDefinition.java
│ │ ├── TrustAddressGenerator.java
│ │ ├── VerifyXMLDSig.java
│ │ └── XMLDSigVerifier.java
│ │ └── util
│ │ ├── DateTime.java
│ │ ├── DateTimeFactory.java
│ │ ├── GeneralDateTime.java
│ │ └── ZonedDateTime.java
│ └── test
│ ├── java
│ └── com
│ │ └── alphawallet
│ │ └── token
│ │ ├── tools
│ │ ├── TokenDefinitionTest.java
│ │ ├── TrustAddressGeneratorTest.java
│ │ └── XMLDsigVerifierTest.java
│ │ └── util
│ │ └── ZonedDateTimeTest.java
│ └── ts
│ ├── DAI-wrong-chain-order.tsml
│ ├── DAI.tsml
│ ├── EntryToken-duplicate-Values.tsml
│ ├── EntryToken-future-cert-self-signed.tsml
│ ├── EntryToken-valid-RSA.tsml
│ ├── EntryToken.tsml
│ ├── entrytoken.xml
│ └── fifa.tsml
├── package-lock.json
├── package.json
├── settings.gradle
├── snapshot.png
├── src
├── file.tcl
├── lmap.tcl
└── tsviewer.tcl
├── tokenscriptviewer
├── .gitignore
├── README.md
├── build.gradle
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── alphawallet
│ │ └── token
│ │ └── web
│ │ ├── AppSiteController.java
│ │ ├── Ethereum
│ │ ├── TokenscriptFunction.java
│ │ └── TransactionHandler.java
│ │ └── Service
│ │ ├── CryptoFunctions.java
│ │ └── EthRPCNodes.java
│ └── resources
│ ├── application.properties
│ ├── sass
│ ├── _error.scss
│ ├── _global.scss
│ ├── _main.scss
│ ├── _steps.scss
│ ├── _ticket.scss
│ ├── _variables.scss
│ └── style.scss
│ ├── static
│ ├── apple-app-site-association
│ ├── css
│ │ ├── bootstrap.min.css
│ │ ├── error.css
│ │ ├── logo.png
│ │ ├── main.css
│ │ └── style.css
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ └── images
│ │ ├── alogobg.png
│ │ ├── app_preview.png
│ │ ├── appstore.png
│ │ ├── calendar.png
│ │ ├── category.png
│ │ ├── googleplay.png
│ │ └── ticket.png
│ └── templates
│ ├── error.html
│ ├── token_inject.js.tokenscript
│ └── tokenscriptTemplate.html
└── windows-instruction.webm
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | /captures
4 | .externalNativeBuild
5 |
6 | ### Android ###
7 | # Built application files
8 | *.apk
9 | *.ap_
10 | *.iml
11 | *.swp
12 | *.swo
13 |
14 | app/mapping.txt
15 | app/seeds.txt
16 | app/unused.txt
17 | app/full-r8-config.txt
18 | app/awallet
19 |
20 | # Files for the Dalvik VM
21 | *.dex
22 |
23 | # Java class files
24 | *.class
25 |
26 | # Generated files
27 | bin/
28 | gen/
29 |
30 | # Gradle files
31 | .gradle/
32 | /build
33 | app/release
34 | app/debug
35 | app/debug/output.json
36 |
37 | # Fastlane
38 | fastlane/report.xml
39 |
40 | # Local configuration file (sdk path, etc)
41 | local.properties
42 |
43 | gradle.properties
44 |
45 | # Proguard folder generated by Eclipse
46 | proguard/
47 |
48 | # Log Files
49 | *.log
50 |
51 | # Android Studio Navigation editor temp files
52 | .navigation/
53 |
54 | ### Android Patch ###
55 | gen-external-apklibs
56 | *.*#
57 |
58 | # Emacs saves
59 | *~
60 |
61 | # Java profiling
62 | *.hprof
63 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | sudo: false
3 | android:
4 | components:
5 | # Uncomment the lines below if you want to
6 | # use the latest revision of Android SDK Tools
7 | - tools
8 | - platform-tools
9 |
10 | # The BuildTools version used by your project
11 | - build-tools-26.0.2
12 |
13 | # The SDK version used to compile your project
14 | - android-26
15 |
16 | - extra-android-m2repository
17 | - extra-google-android-support
18 |
19 | # Specify at least one system image,
20 | # if you need to run emulator(s) during your tests
21 | - sys-img-x86-android-26
22 |
23 | licenses:
24 | - 'android-sdk-preview-license-.+'
25 | - 'android-sdk-license-.+'
26 | - 'google-gdk-license-.+'
27 |
28 | before_install:
29 | - yes | sdkmanager "platforms;android-27"
30 |
31 | install:
32 | - true
33 |
34 | script:
35 | - ./gradlew bootJar
36 |
37 | after_success:
38 | - bash <(curl -s https://codecov.io/bash)
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # TokenScript Visualiser
3 |
4 | You can use the TokenScript Viewer to see the major parts in a TokenScript file.
5 |
6 | Here is the instruction:
7 |
8 | 1. Check your dependency. On OS X everything needed is shipped. On Ubuntu, you need to install 3 packages: tcl, tk, tdom at the outset.
9 |
10 | 2. Clone it to get the source code:
11 |
12 | $ git clone https://github.com/AlphaWallet/TokenScript-Viewer
13 |
14 | 3. Go to src directory and run ./tsviewer.tcl
15 |
16 | $ cd src
17 | src$ ./tsviewer.tcl
18 |
19 | You should see a new window, use "File -> Open" to open a tokenscript file (signed or unsigned doesn't matter). You will get something like this:
20 |
21 | 
22 |
23 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | // don't add anything here until you read to the bottom of this bracket
6 | google()
7 | jcenter()
8 | mavenCentral()
9 | maven { url 'https://jitpack.io' }
10 | google()
11 | // WARNING WARNING WARNING
12 | // you are about to add here a repository which provides some library for the Android app
13 | // don't do that. add that repository to app/build.gradle
14 | }
15 | dependencies {
16 | classpath 'com.android.tools.build:gradle:3.5.3'
17 | classpath "io.realm:realm-gradle-plugin:5.13.0"
18 | // WARNING WARNING WARNING
19 | // you are about to add here a dependency to be used in the Android app
20 | // don't do that. add that dependency to app/build.gradle
21 | classpath 'com.google.gms:google-services:4.3.3'
22 | }
23 | }
24 |
25 | allprojects {
26 | repositories {
27 | google()
28 | jcenter()
29 | mavenCentral()
30 | maven { url 'https://jitpack.io' }
31 | // WARNING WARNING WARNING
32 | // you are about to add here a repository which provides some library for the Android app
33 | // don't do that. add that repository to app/build.gradle
34 | }
35 | }
36 |
37 | task clean(type: Delete) {
38 | delete rootProject.buildDir
39 | }
40 |
41 | // WARNING WARNING WARNING
42 | // you are about to add here a plugin that is only related to the Android app
43 | // don't do that. add that repository to app/build.gradle
44 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Aug 22 13:43:08 AEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -viewType d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const sass = require('gulp-sass');
3 |
4 | gulp.task('sass', function(){
5 | //compile and copy our sass file
6 | gulp.src('dmz/src/main/resources/sass/**/*.scss')
7 | .pipe(sass())
8 | .pipe(gulp.dest('dmz/src/main/resources/static/css/'));
9 | });
10 |
11 | //watch task
12 | gulp.task('default',function() {
13 | gulp.watch('dmz/src/main/sass/**/*.scss',['styles']);
14 | });
15 |
16 | //these get run by gradle when building the app
17 | gulp.task('build', ['styles']);
--------------------------------------------------------------------------------
/lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 | The shared libraries currently handle Asset Definition File as well as Univeral Link.
2 |
3 | ## The Asset Definition file ##
4 |
5 | The asset definition file (XML) controls how a client interacts with a
6 | smart-contract. Typically, how the contract name should be localised,
7 | how to display tokens differently by category, colour and shape, what
8 | operations the users can do with the token - if exchangable, which
9 | exchange service to use. It is very important for user experience and
10 | a lot finer than the contract. Naturally, such important document
11 | needs to be signed, but, by what key?
12 |
13 | There are 2 candidates:
14 |
15 | 1. The owner's key of the smart-contract.
16 | 2. The private key of a web certificate of the company or organisation starting the smart-contract.
17 |
18 | There are a few advantages of using the contract owner's key:
19 |
20 | - It doesn't force any dependencies - many smart contracts authors are reasonably anonymous; to force them to leave a trace of their identity by applying a web certificate is not our design goal.
21 |
22 | - The admin key of a smart-contract is intended to last longer - for most contracts, being valid as long as the contract is at work. The web certificate, although can be renewed without changing the private key, is in practice regenerated yearly.
23 |
24 | - The web certificates have a designated purpose (to secure the website). It may come as a surprise to some administrator users that its use is needed to control the way the world interacts with a smart contract.
25 |
26 | - When it comes down to trust, we trust the contract owner about their smart contract almost as much as about their definition of how apps should interact with that smart-contract. A webmaster is an external guest invited to the party from the point of view of the smart-contract owner.
27 |
28 | - Only one signature is needed. Many organisations own more than one websites, and it is not clear which site's key should be used.
29 |
30 | However, there are also advantages from the second option, of using the website SSL key.
31 |
32 | True
33 | - Most people get to know a smart-contract from a website. There should be a way to certify that "this smart contract is recommended by this website". The trust is passed from website to smart-contracts.
34 |
35 | - Most SSL certificates are kept in a format that can be easily used for signing stuff. The smart-contract owner's key could be kept in Trezor, which has difficulty displaying a long XML file (it may be an advantage if the user took some strenuous effort to scroll down Trezor 1000 times to verify the XML file being signed is correct).
36 |
37 | - SSL certificates can be used to sign XML file while Ethereum key might be kept by a security device which can only sign strings starting with "Ethereum Signed Message..." which breaks XML Signature standards.
38 |
39 | This document propose a combined way: signing the XML file from the website's key and 'acknowledge' it from the smart-contract.
40 |
41 | First, let's define a new interface:
42 |
43 | `contract.getDefinition()`
44 |
45 | It either returns a full XML file or returns a hash that must match that of the XML file. In either case, no matter if the XML file is signed or not, it's considered acceptable. As long as the XML file hashes to the hash value returned by the smart-contract, we call the ADF "true".
46 |
47 | Then, this very XML file may optionally be signed by a website SSL key. If the signature is correct, the website's certificate is verifiable and has not updated, and we call the ADF "trusted".
48 |
49 | Then, there are cases when an XML definition is outdated, contains errors or for some reason; people are still using it after the smart-contract owner refuses to provide an update to that XML file - typically because the key is already lost. In such a case, a webmaster can provide an XML patch, which updates the True ADF, and that update has to be signed by the webmaster. If the True ADF was a signed one, and the update is signed by a key certified by a certificate of the same website, it's called "amended".
50 |
51 | Finally, if the True ADF was not a signed one, or that a contract does not provide getDefinition(), an ADF could be signed by αWallet, in which case it is called "rectified".
52 |
53 | The priority of selecting ADF in the event that there are conflicting versions is the following.
54 |
55 | - Category 1. Amended (imply trusted)
56 | - Category 2. True and Trusted.
57 | - Category 3. True.
58 | - Category 4. Rectified.
59 |
60 | Let's examine this ordering method by scenarios.
61 |
62 | ### Scenario 1. The thorne builder who, if revealed, may be buried alive ###
63 |
64 | Let's say that there is a King of Ether smart-contract: anyone can deposit money and get rewarded by the next deposit, with the condition that the next deposit has to be higher in amount.
65 |
66 | For example, Alice deposits 100 Ethers in it. She will get rewarded when another day Bob deposits 101 Ethers in it. Alice gets exactly 101Ether in reward; Bob will be rewarded when, or if, another person comes and deposits more than 101 Ethers.
67 |
68 | The smart contract is created by someone called James Brown. However, James didn't believe that his contract can stand the test of time. He lives in a communism dictatorship country where he is punishable in the event that his contract is hacked.
69 |
70 | Therefore, he produced an ADF file to allow wallets to interact with his smart-contract, yet he did not sign the ADF file. The smart-contract spits out the hash of the ADF file as a way to validate the ADF file. Since there is no signature on the ADF file, the ADF file can't be "Trusted", but it still can be "True". By our ladder of priority, the only ADF file that can be accepted by the wallet is the True one, category 3.
71 |
72 | The user who accesses this smart-contract from the wallet is given the security status message "No trust assumed".
73 |
74 | ### Scenario 2. The smart-contract admin key is lost. ###
75 |
76 | Alice started a smart-contract and a website www.alice.com about it. She wrote an ADF file and signed it with her SSL key, together with the SSL certificate. The smart-contract is configured to spit out the hash of that very ADF file on `getDefinition()` call.
77 |
78 | The user who accesses this smart-contract from the wallet is given the security status message "as trustworthy as www.alice.com".
79 |
80 | For a year it worked fine - the ADF file is category 2 on the priority list. Then, like the most smart-contract authors, she lost her owner's key. She still owns the website though, and the contract is still operational.
81 |
82 | She obtained a new SSL certificate and a new SSL key. She signed another ADF file. Although she couldn't update the smart-contract with its hash, the wallet recognises that it comes from the same website, and prioritises the ADF file over the previous one because it is a category 1 ADF file.
83 |
84 | As time goes buy she has to move to a new smart-contract because she couldn't update the old one, and business changed since. There is no way to destroy the old contract, but she created a new one and updated a new XML file, preventing the users to interact with the old contract. The new XML file is also a category 1 ADF file, with a newer timestamp signed by the same SSL key. Therefore it replaces the old category 1 ADF file. The user was introduced to use the new contract from a prompt message defined in the new ADF file.
85 |
86 | ### Scenario 3: The smart-contract owner and the website owner isn't the same person. ###
87 |
88 | TTM coin (short for To The Moon!) is a new ICO in town started by CEO John and his brother, CTO Joey. John hired a webmaster to build the ICO website, and Joey wrote the smart-contract. Joey also did some prototype for a new technology he called Sigmund, of which the ICO is about. Both John and Joey are led by their ICO coin buyers to believe that Sigmund is a technology with great potential, giving users the potential to be whatever they want to be.
89 |
90 | ADF file was released as True and Trusted category 2. Having raised 10 million USD in ICO, John and Joey fought over a Youtube video on who owns the project. John believes that he got the crowd and Joey thinks he owns the contract and therefore the money. Joey decided not to give the money to John, and John, in turn, released a new ADF file blocking user's access to the smart-contract. Most simply, John can publish a really messed-up XML file to confuse the users unless Joey coughs up the money.
91 |
92 | In this case, John's ADF file is in category 1 - Amended. Unlike Alice's case, Joey did not lose the smart-contract key. He simply updated the smart-contract to invalidate the previous True and Trusted ADF, therefore invalidating the Amended as well, moving it out of the list.
93 |
94 | Consider that the action of buying ICO token is done to the contract, despite its reputation is assumed from the website, the fact that the contract holds users tokens and funds makes it the trusted party. Coherence between the contract and user's means to access it (ADF) is prioritized.
95 |
96 | Furthermore, it's more sensible that the wallet should behave as intended by the contract, assuming trust from a website, not that wallet should behave as intended by a website to access a contract. Therefore, a design allowing Joey to have an upper hand wins in the lines of common sense, without judging who is the rightful actor.
97 |
98 | ### Scenario 5: The smart-contract doesn't support `getDefinition()` ###
99 |
100 | In this case, a Rectified ADF is used which is signed by αWallet team.
101 |
--------------------------------------------------------------------------------
/lib/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
7 | }
8 | }
9 |
10 | apply plugin: 'com.github.johnrengelman.shadow'
11 | apply plugin: 'java-library'
12 |
13 | dependencies {
14 | implementation('com.amazonaws:aws-lambda-java-core:1.2.0')
15 | implementation('com.amazonaws:aws-lambda-java-events:2.2.6')
16 | testImplementation 'junit:junit:4.12'
17 | implementation 'org.bouncycastle:bcprov-jdk15on:1.62'
18 | // https://mvnrepository.com/artifact/com.github.cliftonlabs/json-simple
19 | implementation group: 'com.github.cliftonlabs', name: 'json-simple', version: '3.1.0'
20 | // https://mvnrepository.com/artifact/com.google.code.gson/gson
21 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
22 | }
23 |
24 | sourceCompatibility = "1.8"
25 | targetCompatibility = "1.8"
26 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/ethereum/EthereumNetworkBase.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.ethereum;
2 |
3 | /* Weiwu 12 Jan 2020: This class eventually will replace the EthereumNetworkBase class in :app
4 | * one all inteface methods are implemented.
5 | */
6 |
7 | import java.util.LinkedHashMap;
8 | import java.util.Map;
9 |
10 | public abstract class EthereumNetworkBase { // implements EthereumNetworkRepositoryType
11 | public static final int MAINNET_ID = 1;
12 | public static final int CLASSIC_ID = 61;
13 | public static final int POA_ID = 99;
14 | public static final int KOVAN_ID = 42;
15 | public static final int ROPSTEN_ID = 3;
16 | public static final int SOKOL_ID = 77;
17 | public static final int RINKEBY_ID = 4;
18 | public static final int XDAI_ID = 100;
19 | public static final int GOERLI_ID = 5;
20 | public static final int ARTIS_SIGMA1_ID = 246529;
21 | public static final int ARTIS_TAU1_ID = 246785;
22 |
23 | public static final String MAINNET_RPC_URL = "https://mainnet.infura.io/v3/da3717f25f824cc1baa32d812386d93f";
24 | public static final String CLASSIC_RPC_URL = "https://ethereumclassic.network";
25 | public static final String XDAI_RPC_URL = "https://dai.poa.network";
26 | public static final String POA_RPC_URL = "https://core.poa.network/";
27 | public static final String ROPSTEN_RPC_URL = "https://ropsten.infura.io/v3/da3717f25f824cc1baa32d812386d93f";
28 | public static final String RINKEBY_RPC_URL = "https://rinkeby.infura.io/v3/da3717f25f824cc1baa32d812386d93f";
29 | public static final String KOVAN_RPC_URL = "https://kovan.infura.io/v3/da3717f25f824cc1baa32d812386d93f";
30 | public static final String SOKOL_RPC_URL = "https://sokol.poa.network";
31 | public static final String GOERLI_RPC_URL = "https://goerli.infura.io/v3/da3717f25f824cc1baa32d812386d93f";
32 | public static final String ARTIS_SIGMA1_RPC_URL = "https://rpc.sigma1.artis.network";
33 | public static final String ARTIS_TAU1_RPC_URL = "https://rpc.tau1.artis.network";
34 |
35 | public static final String MAINNET_BLOCKSCOUT = "eth/mainnet";
36 | public static final String CLASSIC_BLOCKSCOUT = "etc/mainnet";
37 | public static final String XDAI_BLOCKSCOUT = "poa/dai";
38 | public static final String POA_BLOCKSCOUT = "poa/core";
39 | public static final String ROPSTEN_BLOCKSCOUT = "eth/ropsten";
40 | public static final String RINKEBY_BLOCKSCOUT = "eth/rinkeby";
41 | public static final String SOKOL_BLOCKSCOUT = "poa/sokol";
42 | public static final String KOVAN_BLOCKSCOUT = "eth/kovan";
43 | public static final String GOERLI_BLOCKSCOUT = "eth/goerli";
44 |
45 | static Map networkMap = new LinkedHashMap() {
46 | {
47 | put(MAINNET_ID, new NetworkInfo("Ethereum", "ETH", MAINNET_RPC_URL, "https://etherscan.io/tx/",
48 | MAINNET_ID, true, "ethereum", MAINNET_BLOCKSCOUT));
49 | put(CLASSIC_ID, new NetworkInfo("Ethereum Classic", "ETC", CLASSIC_RPC_URL, "https://gastracker.io/tx/",
50 | CLASSIC_ID, true, "ethereum-classic", CLASSIC_BLOCKSCOUT));
51 | put(XDAI_ID, new NetworkInfo("xDAI", "xDAI", XDAI_RPC_URL, "https://blockscout.com/poa/dai/tx/",
52 | XDAI_ID, false, "dai", XDAI_BLOCKSCOUT));
53 | put(POA_ID, new NetworkInfo("POA", "POA", POA_RPC_URL, "https://poaexplorer.com/txid/search/",
54 | POA_ID, false, "ethereum", POA_BLOCKSCOUT));
55 | put(ARTIS_SIGMA1_ID, new NetworkInfo("ARTIS sigma1", "ATS", ARTIS_SIGMA1_RPC_URL, "https://explorer.sigma1.artis.network/tx/",
56 | ARTIS_SIGMA1_ID, false, "artis", ""));
57 | put(KOVAN_ID, new NetworkInfo("Kovan (Test)", "ETH", KOVAN_RPC_URL, "https://kovan.etherscan.io/tx/",
58 | KOVAN_ID, false, "ethereum", KOVAN_BLOCKSCOUT));
59 | put(ROPSTEN_ID, new NetworkInfo("Ropsten (Test)", "ETH", ROPSTEN_RPC_URL, "https://ropsten.etherscan.io/tx/",
60 | ROPSTEN_ID, false, "ethereum", ROPSTEN_BLOCKSCOUT));
61 | put(SOKOL_ID, new NetworkInfo("Sokol (Test)", "POA", SOKOL_RPC_URL, "https://sokol-explorer.poa.network/account/",
62 | SOKOL_ID, false, "ethereum", SOKOL_BLOCKSCOUT));
63 | put(RINKEBY_ID, new NetworkInfo("Rinkeby (Test)", "ETH", RINKEBY_RPC_URL, "https://rinkeby.etherscan.io/tx/",
64 | RINKEBY_ID, false, "ethereum", RINKEBY_BLOCKSCOUT));
65 | put(GOERLI_ID, new NetworkInfo("Görli (Test)", "GÖETH", GOERLI_RPC_URL, "https://goerli.etherscan.io/tx/",
66 | GOERLI_ID, false, "ethereum", GOERLI_BLOCKSCOUT));
67 | put(ARTIS_TAU1_ID, new NetworkInfo("ARTIS tau1 (Test)", "ATS", ARTIS_TAU1_RPC_URL, "https://explorer.tau1.artis.network/tx/",
68 | ARTIS_TAU1_ID, false, "artis", ""));
69 | }
70 | };
71 |
72 | public static NetworkInfo getNetworkByChain(int chainId) {
73 | return networkMap.get(chainId);
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/ethereum/NetworkInfo.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.ethereum;
2 |
3 | /* it's some kind of Trust Ethereum Wallet naming convention that a
4 | * class that has no behaviour (equivalent of C's struct) is called
5 | * SomethingInfo. I didn't agree with this naming convention but I'll
6 | * keep it here */
7 |
8 | public class NetworkInfo {
9 | public final String name;
10 | public final String symbol;
11 | public final String rpcServerUrl;
12 | public final String etherscanUrl;
13 | public final int chainId;
14 | public final boolean isMainNetwork;
15 | public final String tickerId;
16 | public final String blockscoutAPI;
17 |
18 | public NetworkInfo(
19 | String name,
20 | String symbol,
21 | String rpcServerUrl,
22 | String etherscanUrl,
23 | int chainId,
24 | boolean isMainNetwork,
25 | String tickerId,
26 | String blockscoutAPI) {
27 | this.name = name;
28 | this.symbol = symbol;
29 | this.rpcServerUrl = rpcServerUrl;
30 | this.etherscanUrl = etherscanUrl;
31 | this.chainId = chainId;
32 | this.isMainNetwork = isMainNetwork;
33 | this.tickerId = tickerId;
34 | this.blockscoutAPI = blockscoutAPI;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/As.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 30/07/2019.
5 | * Stormbird in Sydney
6 | */
7 | public enum As { // always assume big endian
8 | UTF8, Unsigned, Signed, Mapping, Boolean, UnsignedInput, TokenId, Bytes, e18, e8, e4, e2
9 | }
10 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/AttributeInterface.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 |
5 | /**
6 | * Created by James on 22/05/2019.
7 | * Stormbird in Sydney
8 | */
9 | public interface AttributeInterface
10 | {
11 | TransactionResult getFunctionResult(ContractAddress contract, AttributeType attr, BigInteger tokenId);
12 | TransactionResult storeAuxData(TransactionResult tResult);
13 | boolean resolveOptimisedAttr(ContractAddress contract, AttributeType attr, TransactionResult transactionResult);
14 |
15 | String getWalletAddr();
16 | }
17 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/AttributeType.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import com.alphawallet.token.tools.TokenDefinition;
4 | import com.alphawallet.token.util.DateTime;
5 | import com.alphawallet.token.util.DateTimeFactory;
6 | import org.w3c.dom.Element;
7 | import org.w3c.dom.Node;
8 | import org.w3c.dom.NodeList;
9 |
10 | import java.io.UnsupportedEncodingException;
11 | import java.math.BigInteger;
12 | import java.text.ParseException;
13 | import java.text.SimpleDateFormat;
14 | import java.util.Date;
15 | import java.util.HashMap;
16 | import java.util.Locale;
17 | import java.util.Map;
18 |
19 | import static org.w3c.dom.Node.ELEMENT_NODE;
20 |
21 | /**
22 | * Created by James on 9/05/2019.
23 | * Stormbird in Sydney
24 | */
25 |
26 | public class AttributeType {
27 | //default the bitmask to 32 bytes represented
28 | public BigInteger bitmask = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); // TODO: BigInteger !== BitInt. Test edge conditions.
29 | public String name; // TODO: should be polyglot because user change change language in the run
30 | public String id;
31 | public int bitshift = 0;
32 | public TokenDefinition.Syntax syntax;
33 | public As as;
34 | public Map members;
35 | private TokenDefinition definition;
36 | public FunctionDefinition function = null;
37 | public boolean userInput = false;
38 |
39 | public AttributeType(Element attr, TokenDefinition def)
40 | {
41 | definition = def;
42 | id = attr.getAttribute("id");
43 | as = As.Unsigned; //default value
44 | try {
45 | switch (attr.getAttribute("syntax")) { // We don't validate syntax here; schema does it.
46 | case "1.3.6.1.4.1.1466.115.121.1.6":
47 | syntax = TokenDefinition.Syntax.BitString;
48 | break;
49 | case "1.3.6.1.4.1.1466.115.121.1.7":
50 | syntax = TokenDefinition.Syntax.Boolean;
51 | break;
52 | case "1.3.6.1.4.1.1466.115.121.1.11":
53 | syntax = TokenDefinition.Syntax.CountryString;
54 | break;
55 | case "1.3.6.1.4.1.1466.115.121.1.28":
56 | syntax = TokenDefinition.Syntax.JPEG;
57 | break;
58 | case "1.3.6.1.4.1.1466.115.121.1.36":
59 | syntax = TokenDefinition.Syntax.NumericString;
60 | break;
61 | case "1.3.6.1.4.1.1466.115.121.1.24":
62 | syntax = TokenDefinition.Syntax.GeneralizedTime;
63 | break;
64 | case "1.3.6.1.4.1.1466.115.121.1.26":
65 | syntax = TokenDefinition.Syntax.IA5String;
66 | break;
67 | case "1.3.6.1.4.1.1466.115.121.1.27":
68 | syntax = TokenDefinition.Syntax.Integer;
69 | break;
70 | default: // unknown syntax treat as Directory String
71 | syntax = TokenDefinition.Syntax.DirectoryString;
72 | }
73 | } catch (NullPointerException e) { // missing
74 | syntax = TokenDefinition.Syntax.DirectoryString; // 1.3.6.1.4.1.1466.115.121.1.15
75 | }
76 | for(Node node = attr.getFirstChild();
77 | node!=null; node=node.getNextSibling()){
78 | if (node.getNodeType() == Node.ELEMENT_NODE) {
79 | Element origin = (Element) node;
80 | String label = node.getLocalName();
81 | switch (label)
82 | {
83 | case "origins":
84 | handleOrigins(origin);
85 | break;
86 | case "name":
87 | name = definition.getLocalisedString(origin, "string");
88 | break;
89 | case "mapping":
90 | populate(origin);
91 | break;
92 | }
93 | switch(origin.getAttribute("contract").toLowerCase()) {
94 | case "holding-contract":
95 | setAs(As.Mapping);
96 | // TODO: Syntax is not checked
97 | //getFunctions(origin);
98 | break;
99 | default:
100 | break;
101 | }
102 | }
103 | }
104 | if (bitmask != null ) {
105 | while (bitmask.mod(BigInteger.ONE.shiftLeft(++bitshift)).equals(BigInteger.ZERO)) ; // !!
106 | bitshift--;
107 | }
108 | }
109 |
110 | private void handleOrigins(Element origin)
111 | {
112 | for(Node node = origin.getFirstChild();
113 | node!=null; node=node.getNextSibling())
114 | {
115 | if (node.getNodeType() == Node.ELEMENT_NODE)
116 | {
117 | Element resolve = (Element) node;
118 | setAs(definition.parseAs(resolve));
119 | switch (node.getLocalName())
120 | {
121 | case "ethereum":
122 | function = definition.parseFunction(resolve, syntax);
123 | //drop through (no break)
124 | case "token-id":
125 | //this value is obtained from the token id
126 | setAs(definition.parseAs(resolve));
127 | populate(resolve); //check for mappings
128 | if (function != null) function.as = definition.parseAs(resolve);
129 | if (resolve.hasAttribute("bitmask")) {
130 | bitmask = new BigInteger(resolve.getAttribute("bitmask"), 16);
131 | }
132 | break;
133 | case "user-entry":
134 | userInput = true;
135 | setAs(definition.parseAs(resolve));
136 | if (resolve.hasAttribute("bitmask")) {
137 | bitmask = new BigInteger(resolve.getAttribute("bitmask"), 16);
138 | }
139 | break;
140 | }
141 | }
142 | }
143 | }
144 |
145 | private void populate(Element origin) {
146 | Element option;
147 | for (Node n = origin.getFirstChild(); n != null; n = n.getNextSibling())
148 | {
149 | if (n.getNodeType() == ELEMENT_NODE)
150 | {
151 | Element element = (Element) n;
152 | if (element.getLocalName().equals("mapping"))
153 | {
154 | members = new HashMap<>();
155 | setAs(As.Mapping);
156 |
157 | NodeList nList = origin.getElementsByTagNameNS(definition.nameSpace, "option");
158 | for (int i = 0; i < nList.getLength(); i++) {
159 | option = (Element) nList.item(i);
160 | members.put(new BigInteger(option.getAttribute("key")), definition.getLocalisedString(option, "value"));
161 | }
162 | }
163 | }
164 | }
165 | }
166 |
167 | public String getSyntaxVal(String data)
168 | {
169 | if (data == null) return null;
170 | switch (syntax)
171 | {
172 | case DirectoryString:
173 | return data;
174 | case IA5String:
175 | return data;
176 | case Integer:
177 | //convert to integer
178 | if (data.length() == 0)
179 | {
180 | return "0";
181 | }
182 | else if (Character.isDigit(data.charAt(0)))
183 | {
184 | return data;
185 | }
186 | else
187 | {
188 | //convert from byte value
189 | return new BigInteger(data.getBytes()).toString();
190 | }
191 | case GeneralizedTime:
192 | try
193 | {
194 | //ensure data is alphanum
195 | data = checkAlphaNum(data);
196 |
197 | DateTime dt = DateTimeFactory.getDateTime(data);
198 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
199 | SimpleDateFormat simpleTimeFormat = new SimpleDateFormat("hh:mm:ssZ");
200 | String generalizedTime = dt.format(simpleDateFormat) + "T" + dt.format(simpleTimeFormat);
201 | return "{ generalizedTime: \"" + data + "\", date: new Date(\"" + generalizedTime + "\") }";
202 | }
203 | catch (ParseException e)
204 | {
205 | return data;
206 | }
207 | case Boolean:
208 | if (data.length() == 0) return "FALSE";
209 | if (Character.isDigit(data.charAt(0)))
210 | {
211 | return (data.charAt(0) == '0') ? "FALSE" : "TRUE";
212 | }
213 | else if (data.charAt(0) == 0) return "FALSE";
214 | else if (data.charAt(0) == 1) return "TRUE";
215 | else return data;
216 | case BitString:
217 | return data;
218 | case CountryString:
219 | return data;
220 | case JPEG:
221 | return data;
222 | case NumericString:
223 | if (data == null)
224 | {
225 | return "0";
226 | }
227 | else if (data.startsWith("0x"))
228 | {
229 | data = data.substring(2);
230 | }
231 | return data;
232 | default:
233 | return data;
234 | }
235 | }
236 |
237 | private String checkAlphaNum(String data)
238 | {
239 | for (char ch : data.toCharArray())
240 | {
241 | if (!(Character.isAlphabetic(ch) || Character.isDigit(ch) || ch == '+' || ch == '-' || Character.isWhitespace(ch)))
242 | {
243 | //set to current time
244 | SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssZ", Locale.ENGLISH);
245 | data = format.format(new Date(System.currentTimeMillis()));
246 | break;
247 | }
248 | }
249 |
250 | return data;
251 | }
252 |
253 | /**
254 | * Converts bitshifted/masked token numeric data into corresponding string.
255 | * eg. Attr is 'venue'; choices are "1" -> "Kaliningrad Stadium", "2" -> "Volgograd Arena" etc.
256 | * NB 'time' is Unix EPOCH, which is also a mapping.
257 | * Since the value may not have a corresponding mapping, but is a valid time we should still return the time value
258 | * and interpret it as a local time
259 | *
260 | * Also - some NF tokens which share a contract with others (eg World Cup, Meetup invites) will have mappings
261 | * which intentionally have zero value - eg 'Match' has no lookup value for a meeting. Returning null is a guide for the
262 | * token layout not to show the value.
263 | *
264 | * This will become less relevant once the IFrame system is in place - each token appearance will be defined explicitly.
265 | * However it may be necessary for a default display of token attributes for ease of use while potential
266 | * users become acquainted with the system.
267 | *
268 | * @param data
269 | * @return
270 | * @throws UnsupportedEncodingException
271 | */
272 | public String toString(BigInteger data) throws UnsupportedEncodingException
273 | {
274 | // TODO: in all cases other than UTF8, syntax should be checked
275 | switch (getAs())
276 | {
277 | case UTF8:
278 | return new String(data.toByteArray(), "UTF8");
279 |
280 | case Unsigned:
281 | return data.toString();
282 |
283 | case Mapping:
284 | // members might be null, but it is better to throw up ( NullPointerException )
285 | // than silently ignore
286 | // JB: Existing contracts and tokens throw this error. The wallet 'crashes' each time existing tokens are opened
287 | // due to assumptions made with extra tickets (ie null member is assumed to return null and not display that element).
288 | if (members != null && members.containsKey(data))
289 | {
290 | return members.get(data);
291 | }
292 | else if (syntax == TokenDefinition.Syntax.GeneralizedTime)
293 | {
294 | //This is a time entry but without a localised mapped entry. Return the EPOCH time.
295 | Date date = new Date(data.multiply(BigInteger.valueOf(1000)).longValue());
296 | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssZ");
297 | return sdf.format(date);
298 | }
299 | else
300 | {
301 | return null; // have to revert to this behaviour due to values being zero when tokens are created
302 | //refer to 'AlphaWallet meetup indices' where 'Match' mapping is null but for FIFA is not.
303 | //throw new NullPointerException("Key " + data.toString() + " can't be mapped.");
304 | }
305 | default:
306 | throw new NullPointerException("Missing valid 'as' attribute");
307 | }
308 | }
309 |
310 | public As getAs()
311 | {
312 | return as;
313 | }
314 |
315 | public void setAs(As as)
316 | {
317 | this.as = as;
318 | }
319 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/BadContract.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | public class BadContract extends Exception
4 | {
5 | public BadContract()
6 | {
7 |
8 | }
9 |
10 | public BadContract(String message)
11 | {
12 | super(message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/ChainSpec.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | public class ChainSpec
4 | {
5 | public String name;
6 | public int chainId;
7 | public String urlPrefix;
8 | }
9 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/ContractAddress.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * Created by James on 15/05/2019.
7 | * Stormbird in Sydney
8 | */
9 | public class ContractAddress
10 | {
11 | public final int chainId;
12 | public final String address;
13 |
14 | public ContractAddress(int chainId, String address)
15 | {
16 | this.chainId = chainId;
17 | this.address = address;
18 | }
19 |
20 | /**
21 | * Select contract to use from list of allowed contracts.
22 | * Selection rule:
23 | * 1 - Is the token itself a viable choice? If so use the token contract
24 | * 2 - Token contract not available - choose first contract on same chain
25 | * 3 - Token contract not available and no contract on same chainId as token - must be a cross-chain call, use first contract in list.
26 | * @param fd
27 | * @param chainId
28 | * @param address
29 | */
30 | public ContractAddress(FunctionDefinition fd, int chainId, String address)
31 | {
32 | List contracts = fd.contract.addresses.get(chainId);
33 | if (contracts != null && contracts.contains(address))
34 | {
35 | this.address = address;
36 | this.chainId = chainId;
37 | }
38 | else if (contracts != null)
39 | {
40 | this.chainId = chainId;
41 | this.address = fd.contract.addresses.get(chainId).iterator().next();
42 | }
43 | else
44 | {
45 | this.chainId = fd.contract.addresses.keySet().iterator().next();
46 | this.address = fd.contract.addresses.get(chainId).iterator().next();
47 | }
48 | }
49 |
50 | public ContractAddress(FunctionDefinition fd)
51 | {
52 | this.chainId = fd.contract.addresses.keySet().iterator().next();
53 | this.address = fd.contract.addresses.get(chainId).iterator().next();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/ContractInfo.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.util.HashMap;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | /**
8 | * Created by James on 2/05/2019.
9 | * Stormbird in Sydney
10 | */
11 | public class ContractInfo
12 | {
13 | public String contractInterface = null;
14 | public Map> addresses = new HashMap<>();
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/CryptoFunctionsInterface.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 | import java.security.SignatureException;
5 |
6 | public interface CryptoFunctionsInterface
7 | {
8 | byte[] Base64Decode(String message);
9 | byte[] Base64Encode(byte[] data);
10 | BigInteger signedMessageToKey(byte[] data, byte[] signature) throws SignatureException;
11 | String getAddressFromKey(BigInteger recoveredKey);
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/EthereumReadBuffer.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.io.DataInputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.math.BigInteger;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import com.alphawallet.token.tools.Numeric;
11 |
12 | /**
13 | * Created by James on 24/02/2018.
14 | */
15 |
16 | public class EthereumReadBuffer extends DataInputStream
17 | {
18 | private final byte[] readBuffer;
19 |
20 | public EthereumReadBuffer(InputStream in)
21 | {
22 | super(in);
23 | readBuffer = new byte[32];
24 | }
25 |
26 | public BigInteger readBI() throws IOException
27 | {
28 | BigInteger retVal;
29 |
30 | read(readBuffer);
31 | retVal = new BigInteger(readBuffer);
32 |
33 | return retVal;
34 | }
35 |
36 | /**
37 | * Custom BigInteger which is formed from a byte array of sz size.
38 | * @param sz size of bytes to read for the BigInteger
39 | * @return
40 | * @throws IOException
41 | */
42 | public BigInteger readBI(int sz) throws IOException
43 | {
44 | BigInteger retVal;
45 | byte[] buffer = new byte[sz];
46 |
47 | read(buffer);
48 | retVal = new BigInteger(buffer);
49 |
50 | return retVal;
51 | }
52 |
53 | public String readAddress() throws IOException {
54 | byte[] buffer20 = new byte[20];
55 | read(buffer20);
56 | return Numeric.toHexString(buffer20);
57 | }
58 |
59 | @Override
60 | public int available() throws IOException
61 | {
62 | int remains = 0;
63 | remains = super.available();
64 |
65 | return remains;
66 | }
67 |
68 |
69 | public void readSignature(byte[] signature) throws IOException
70 | {
71 | if (signature.length == 65) {
72 | read(signature); // would it throw already, if the data is too short? - Weiwu
73 | } else {
74 | throw new IOException("Data isn't a signature"); // Is this even necessary? - Weiwu
75 | }
76 | }
77 |
78 | /*
79 | * The java 8 recommended way is to read an unsigned Short as Short, and use it as
80 | * unsigned Short. Here we still use the old method, reading unsigned shorts into int[].
81 | */
82 | public void readUnsignedShort(int[] ints) throws IOException
83 | {
84 | for (int i = 0; i < ints.length; i++)
85 | {
86 | int value = toUnsignedInt(readShort());
87 | ints[i] = value;
88 | }
89 | }
90 |
91 | /*
92 | * equivalent of Short.toUnsignedInt
93 | */
94 | private int toUnsignedInt(short s) {
95 | return s & 0x0000FFFF;
96 | }
97 |
98 | /*
99 | * equivalent of Byte.toUnsignedInt
100 | */
101 | private int toUnsignedInt(byte b)
102 | {
103 | return b & 0x000000FF;
104 | } // Int is 32 bits
105 |
106 | /*
107 | * equivalent of Integer.readUnsignedLong
108 | */
109 | public long toUnsignedLong(int i) {
110 | return i & 0x00000000ffffffffL; // long is always 64 bits
111 | }
112 |
113 | public List readTokenIdsFromSpawnableLink(int length) throws IOException
114 | {
115 | List tokenIds = new ArrayList<>();
116 | byte[] tokenIdBuffer = new byte[32];
117 | while (length > 0)
118 | {
119 | length -= read(tokenIdBuffer);
120 | BigInteger tokenId = new BigInteger(tokenIdBuffer);
121 | tokenIds.add(tokenId);
122 | }
123 |
124 | return tokenIds;
125 | }
126 |
127 | public int[] readCompressedIndices(int indiciesLength) throws IOException
128 | {
129 | byte[] readBuffer = new byte[indiciesLength];
130 | int bufferLength = read(readBuffer);
131 | int index = 0;
132 | int state = 0;
133 |
134 | List indexList = new ArrayList<>();
135 | Integer rValue = 0;
136 |
137 | while (index < indiciesLength)
138 | {
139 | Integer p = toUnsignedInt(readBuffer[index]); // equivalent of Byte.toUnsignedInt()
140 | switch (state)
141 | {
142 | case 0:
143 | //check if we require an extension byte read
144 | rValue = (p & ~(1 << 7)); //remove top bit.
145 | if (((1 << 7) & p) == (1 << 7)) //check if top bit is there
146 | {
147 | state = 1;
148 | }
149 | else
150 | {
151 | indexList.add(rValue);
152 | }
153 | break;
154 | case 1:
155 | rValue = (rValue << 8) + (p & 0xFF); //Low byte + High byte without top bit (which is the extension designation bit)
156 | indexList.add(rValue);
157 | state = 0;
158 | break;
159 | default:
160 | throw new IOException("Illegal state in readCompressedIndicies");
161 | }
162 |
163 | index++;
164 | }
165 |
166 | int[] indexArray = new int[indexList.size()];
167 | for (int i = 0; i < indexList.size(); i++) indexArray[i] = indexList.get(i);
168 |
169 | return indexArray;
170 | }
171 |
172 | public byte[] readBytes(int i) throws IOException
173 | {
174 | byte[] buffer = new byte[i];
175 | read(buffer);
176 | return buffer;
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/EthereumTransaction.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * Created by James on 28/05/2019.
8 | * Stormbird in Sydney
9 | */
10 | public class EthereumTransaction
11 | {
12 | public Map args = new HashMap<>();
13 | }
14 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/EthereumWriteBuffer.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 |
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.io.OutputStream;
7 | import java.math.BigDecimal;
8 | import java.math.BigInteger;
9 | import java.util.List;
10 |
11 | import com.alphawallet.token.tools.Convert;
12 | import com.alphawallet.token.tools.Numeric;
13 |
14 | /**
15 | * Created by James on 26/03/2018.
16 | */
17 |
18 | public class EthereumWriteBuffer extends DataOutputStream
19 | {
20 | public EthereumWriteBuffer(OutputStream in)
21 | {
22 | super(in);
23 | }
24 |
25 | public void write32(BigInteger bi) throws IOException
26 | {
27 | write(Numeric.toBytesPadded(bi, 32));
28 | }
29 |
30 | public void writeAddress(BigInteger addr) throws IOException {
31 | write(Numeric.toBytesPadded(addr, 20));
32 | }
33 |
34 | public void writeAddress(String addr) throws IOException {
35 | BigInteger addrBI = new BigInteger(Numeric.cleanHexPrefix(addr), 16);
36 | writeAddress(addrBI);
37 | }
38 |
39 | public void writeUnsigned4(BigInteger value) throws IOException {
40 | write(Numeric.toBytesPadded(UnsignedLong.create(value), 4));
41 | }
42 |
43 | public void writeUnsigned4(long value) throws IOException {
44 | write(Numeric.toBytesPadded(UnsignedLong.create(value), 4));
45 | }
46 |
47 |
48 | public void writeCompressedIndices(int[] indices) throws IOException
49 | {
50 | byte[] uint16 = new byte[2];
51 | byte[] uint8 = new byte[1];
52 | final int indexMax = 1<<16;
53 | for (int i : indices)
54 | {
55 | if (i >= indexMax)
56 | {
57 | throw new IOException("Index out of representation range: " + i);
58 | }
59 | if (i < (1 << 7))
60 | {
61 | uint8[0] = (byte) (i & ~(1 << 7));
62 | write(uint8);
63 | }
64 | else
65 | {
66 | uint16[0] = (byte) ((i >> 8) | (1<<7));
67 | uint16[1] = (byte) (i & 0xFF);
68 | write(uint16);
69 | }
70 | }
71 | }
72 |
73 | public void writeTokenIds(List tokenIds) throws IOException
74 | {
75 | for (BigInteger tokenId : tokenIds)
76 | {
77 | write(Numeric.toBytesPadded(tokenId, 32));
78 | }
79 | }
80 |
81 | public void writeSignature(byte[] sig) throws IOException
82 | {
83 | //assertEquals(sig.length, 65);
84 | write(sig);
85 | }
86 |
87 | public void write4ByteMicroEth(BigInteger weiValue) throws IOException
88 | {
89 | byte[] max = Numeric.hexStringToByteArray("FFFFFFFF");
90 | BigInteger maxValue = new BigInteger(1, max);
91 | //this is value in microeth/szabo
92 | //convert to wei
93 | BigInteger microEth = Convert.fromWei(new BigDecimal(weiValue), Convert.Unit.SZABO).abs().toBigInteger();
94 | if (microEth.compareTo(maxValue) > 0)
95 | {
96 | microEth = maxValue; //should we signal an overflow error here, or just silently round?
97 | //possibly irrelevant, this is a huge amount of eth.
98 | }
99 |
100 | byte[] uValBytes = UnsignedLong.createBytes(microEth.longValue());
101 | write(uValBytes);
102 | }
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/FunctionDefinition.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import com.alphawallet.token.tools.TokenDefinition;
4 |
5 | import java.math.BigInteger;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * Created by James on 10/11/2018.
11 | * Stormbird in Singapore
12 | */
13 |
14 | public class FunctionDefinition
15 | {
16 | public ContractInfo contract;
17 | public String method;
18 | public TokenDefinition.Syntax syntax;
19 | public As as;
20 | public List parameters = new ArrayList<>();
21 |
22 | public String result;
23 | public long resultTime = 0;
24 | public BigInteger tokenId;
25 | public EthereumTransaction tx;
26 |
27 | public int getTokenRequirement()
28 | {
29 | int count = 0;
30 | for (MethodArg arg : parameters)
31 | {
32 | if (arg.isTokenId()) count++;
33 | }
34 |
35 | return count;
36 | }
37 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/MagicLinkData.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | public class MagicLinkData
8 | {
9 | public long expiry;
10 | public byte[] prefix;
11 | public BigInteger nonce;
12 | public double price;
13 | public BigInteger priceWei;
14 | public List tokenIds;
15 | public int[] indices;
16 | public BigInteger amount;
17 | public int ticketStart;
18 | public int ticketCount;
19 | public String contractAddress;
20 | public byte[] signature = new byte[65];
21 | public byte[] message;
22 | public String ownerAddress;
23 | public String contractName;
24 | public byte contractType;
25 | public int chainId;
26 |
27 | public List balanceInfo = null;
28 |
29 | public boolean isValidOrder()
30 | {
31 | //check this order is not corrupt
32 | //first check the owner address - we should already have called getOwnerKey
33 | boolean isValid = true;
34 |
35 | if (this.ownerAddress == null || this.ownerAddress.length() < 20) isValid = false;
36 | if (this.contractAddress == null || this.contractAddress.length() < 20) isValid = false;
37 | if (this.message == null) isValid = false;
38 |
39 | return isValid;
40 | }
41 |
42 | public boolean balanceChange(List balance)
43 | {
44 | //compare two balances
45 | //quick return, if sizes are different there's a change
46 | if (balanceInfo == null)
47 | {
48 | balanceInfo = new ArrayList<>(); //initialise the balance list
49 | return true;
50 | }
51 | if (balance.size() != balanceInfo.size()) return true;
52 |
53 | List oldBalance = new ArrayList<>(balanceInfo);
54 | List newBalance = new ArrayList<>(balance);
55 |
56 | oldBalance.removeAll(balanceInfo);
57 | newBalance.removeAll(balance);
58 |
59 | return (oldBalance.size() != 0 || newBalance.size() != 0);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/MagicLinkInfo.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 2/03/2019.
5 | * Stormbird in Singapore
6 | */
7 | public class MagicLinkInfo
8 | {
9 | //domains for DMZ
10 | public static final String mainnetMagicLinkDomain = "aw.app";
11 | private static final String legacyMagicLinkDomain = "app.awallet.io";
12 | private static final String classicMagicLinkDomain = "classic.aw.app";
13 | private static final String callistoMagicLinkDomain = "callisto.aw.app";
14 | private static final String kovanMagicLinkDomain = "kovan.aw.app";
15 | private static final String ropstenMagicLinkDomain = "ropsten.aw.app";
16 | private static final String rinkebyMagicLinkDomain = "rinkeby.aw.app";
17 | private static final String poaMagicLinkDomain = "poa.aw.app";
18 | private static final String sokolMagicLinkDomain = "sokol.aw.app";
19 | private static final String xDaiMagicLinkDomain = "xdai.aw.app";
20 | private static final String goerliMagicLinkDomain = "goerli.aw.app";
21 | private static final String artisSigma1MagicLinkDomain = "artissigma1.aw.app";
22 | private static final String artisTau1MagicLinkDomain = "artistau1.aw.app";
23 | private static final String customMagicLinkDomain = "custom.aw.app";
24 |
25 | //Etherscan domains
26 | private static final String mainNetEtherscan = "https://etherscan.io/";
27 | private static final String classicEtherscan = "https://gastracker.io/";
28 | private static final String callistoEtherscan = "https://etherscan.io/"; //TODO: determine callisto etherscan
29 | private static final String kovanEtherscan = "https://kovan.etherscan.io/";
30 | private static final String ropstenEtherscan = "https://ropsten.etherscan.io/";
31 | private static final String rinkebyEtherscan = "https://rinkeby.etherscan.io/";
32 | private static final String poaEtherscan = "https://poaexplorer.com/";
33 | private static final String sokolEtherscan = "https://sokol-explorer.poa.network/account/";
34 | private static final String xDaiEtherscan = "https://blockscout.com/poa/dai/";
35 | private static final String goerliEtherscan = "https://goerli.etherscan.io/";
36 | private static final String artisSigma1Etherscan = "https://explorer.sigma1.artis.network/";
37 | private static final String artisTau1Etherscan = "https://explorer.tau1.artis.network/";
38 |
39 | //network ids
40 | public static final int LEGACY_VALUE = 0;
41 | public static final int MAINNET_NETWORK_ID = 1;
42 | public static final int CLASSIC_NETWORK_ID = 61;
43 | public static final int KOVAN_NETWORK_ID = 42;
44 | public static final int ROPSTEN_NETWORK_ID = 3;
45 | public static final int RINKEBY_NETWORK_ID = 4;
46 | public static final int POA_NETWORK_ID = 99;
47 | public static final int SOKOL_NETWORK_ID = 77;
48 | public static final int XDAI_NETWORK_ID = 100;
49 | public static final int GOERLI_NETWORK_ID = 5;
50 | public static final int ARTIS_SIGMA1_NETWORK_ID = 246529;
51 | public static final int ARTIS_TAU1_NETWORK_ID = 246785;
52 |
53 | //network names
54 | private static final String ETHEREUM_NETWORK = "Ethereum";
55 | private static final String CLASSIC_NETWORK = "Ethereum Classic";
56 | private static final String KOVAN_NETWORK = "Kovan";
57 | private static final String ROPSTEN_NETWORK = "Ropsten";
58 | private static final String RINKEBY_NETWORK = "Rinkeby";
59 | private static final String POA_NETWORK = "POA";
60 | private static final String SOKOL_NETWORK = "Sokol";
61 | private static final String XDAI_NETWORK = "xDAI";
62 | private static final String GOERLI_NETWORK = "Görli";
63 | private static final String ARTIS_SIGMA1_NETWORK = "ARTIS sigma1";
64 | private static final String ARTIS_TAU1_NETWORK = "ARTIS tau1";
65 |
66 | public static String getNetworkNameById(int networkId) {
67 | switch (networkId) {
68 | case MAINNET_NETWORK_ID:
69 | return ETHEREUM_NETWORK;
70 | case KOVAN_NETWORK_ID:
71 | return KOVAN_NETWORK;
72 | case ROPSTEN_NETWORK_ID:
73 | return ROPSTEN_NETWORK;
74 | case RINKEBY_NETWORK_ID:
75 | return RINKEBY_NETWORK;
76 | case POA_NETWORK_ID:
77 | return POA_NETWORK;
78 | case SOKOL_NETWORK_ID:
79 | return SOKOL_NETWORK;
80 | case CLASSIC_NETWORK_ID:
81 | return CLASSIC_NETWORK;
82 | case XDAI_NETWORK_ID:
83 | return XDAI_NETWORK;
84 | case GOERLI_NETWORK_ID:
85 | return GOERLI_NETWORK;
86 | case ARTIS_SIGMA1_NETWORK_ID:
87 | return ARTIS_SIGMA1_NETWORK;
88 | case ARTIS_TAU1_NETWORK_ID:
89 | return ARTIS_TAU1_NETWORK;
90 | default:
91 | return ETHEREUM_NETWORK;
92 | }
93 | }
94 |
95 | public static String getMagicLinkDomainFromNetworkId(int networkId) {
96 | switch (networkId) {
97 | case LEGACY_VALUE:
98 | return legacyMagicLinkDomain;
99 | case MAINNET_NETWORK_ID:
100 | default:
101 | return mainnetMagicLinkDomain;
102 | case KOVAN_NETWORK_ID:
103 | return kovanMagicLinkDomain;
104 | case ROPSTEN_NETWORK_ID:
105 | return ropstenMagicLinkDomain;
106 | case RINKEBY_NETWORK_ID:
107 | return rinkebyMagicLinkDomain;
108 | case POA_NETWORK_ID:
109 | return poaMagicLinkDomain;
110 | case SOKOL_NETWORK_ID:
111 | return sokolMagicLinkDomain;
112 | case CLASSIC_NETWORK_ID:
113 | return classicMagicLinkDomain;
114 | case XDAI_NETWORK_ID:
115 | return xDaiMagicLinkDomain;
116 | case GOERLI_NETWORK_ID:
117 | return goerliMagicLinkDomain;
118 | case ARTIS_SIGMA1_NETWORK_ID:
119 | return artisSigma1MagicLinkDomain;
120 | case ARTIS_TAU1_NETWORK_ID:
121 | return artisTau1MagicLinkDomain;
122 | }
123 | }
124 |
125 | //For testing you will not have the correct domain (localhost)
126 | //To test, alter the else statement to return the network you wish to test
127 | public static int getNetworkIdFromDomain(String domain) {
128 | switch(domain) {
129 | case mainnetMagicLinkDomain:
130 | default:
131 | return MAINNET_NETWORK_ID;
132 | case legacyMagicLinkDomain:
133 | return MAINNET_NETWORK_ID;
134 | case classicMagicLinkDomain:
135 | return CLASSIC_NETWORK_ID;
136 | case kovanMagicLinkDomain:
137 | return KOVAN_NETWORK_ID;
138 | case ropstenMagicLinkDomain:
139 | return ROPSTEN_NETWORK_ID;
140 | case rinkebyMagicLinkDomain:
141 | return RINKEBY_NETWORK_ID;
142 | case poaMagicLinkDomain:
143 | return POA_NETWORK_ID;
144 | case sokolMagicLinkDomain:
145 | return SOKOL_NETWORK_ID;
146 | case xDaiMagicLinkDomain:
147 | return XDAI_NETWORK_ID;
148 | case goerliMagicLinkDomain:
149 | return GOERLI_NETWORK_ID;
150 | case artisSigma1MagicLinkDomain:
151 | return ARTIS_SIGMA1_NETWORK_ID;
152 | case artisTau1MagicLinkDomain:
153 | return ARTIS_TAU1_NETWORK_ID;
154 | }
155 | }
156 |
157 | public static String getEtherscanURLbyNetwork(int networkId) {
158 | switch (networkId) {
159 | case MAINNET_NETWORK_ID:
160 | default:
161 | return mainNetEtherscan;
162 | case KOVAN_NETWORK_ID:
163 | return kovanEtherscan;
164 | case ROPSTEN_NETWORK_ID:
165 | return ropstenEtherscan;
166 | case RINKEBY_NETWORK_ID:
167 | return rinkebyEtherscan;
168 | case POA_NETWORK_ID:
169 | return poaEtherscan;
170 | case SOKOL_NETWORK_ID:
171 | return sokolEtherscan;
172 | case CLASSIC_NETWORK_ID:
173 | return classicEtherscan;
174 | case XDAI_NETWORK_ID:
175 | return xDaiEtherscan;
176 | case GOERLI_NETWORK_ID:
177 | return goerliEtherscan;
178 | case ARTIS_SIGMA1_NETWORK_ID:
179 | return artisSigma1Etherscan;
180 | case ARTIS_TAU1_NETWORK_ID:
181 | return artisTau1Etherscan;
182 | }
183 | }
184 |
185 | public static int identifyChainId(String link)
186 | {
187 | int chainId = 0;
188 | //split out the chainId from the magiclink
189 | int index = link.indexOf(mainnetMagicLinkDomain);
190 | int dSlash = link.indexOf("://");
191 | int legacy = link.indexOf(legacyMagicLinkDomain);
192 | //try new style link
193 | if (index > 0 && dSlash > 0)
194 | {
195 | String domain = link.substring(dSlash+3, index + mainnetMagicLinkDomain.length());
196 | chainId = getNetworkIdFromDomain(domain);
197 | }
198 | else if (legacy > 0)
199 | {
200 | chainId = 0;
201 | }
202 |
203 | return chainId;
204 | }
205 |
206 | public static String generatePrefix(int chainId)
207 | {
208 | return "https://" + getMagicLinkDomainFromNetworkId(chainId) + "/";
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/MessageData.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 |
5 | /**
6 | * Created by James on 21/03/2018.
7 | */
8 |
9 | public class MessageData
10 | {
11 | public BigInteger priceWei;
12 | public int[] tickets;
13 | public byte[] signature = new byte[65];
14 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/MethodArg.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 2/05/2019.
5 | * Stormbird in Sydney
6 | */
7 |
8 | // A param to pass into a smart contract function call
9 | public class MethodArg
10 | {
11 | public String parameterType; //type of param eg uint256, address etc
12 | public TokenscriptElement element; // contains either the value or reference to the value
13 |
14 | public boolean isTokenId()
15 | {
16 | return element.isToken();
17 | }
18 |
19 | public int getTokenIndex()
20 | {
21 | return element.getTokenIndex();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/NonFungibleToken.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import com.alphawallet.token.tools.TokenDefinition;
4 |
5 | import java.math.BigInteger;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | /**
10 | * Created by weiwu on 1/3/18. Each NonFungibleToken is a
11 | * non-fungible token identified by a byte32 tokenID (other forms of
12 | * IDs may be added if tests proves that they can be more efficient).
13 | */
14 |
15 | public class NonFungibleToken
16 | {
17 | public BigInteger id;
18 |
19 | public static final class Attribute {
20 | public final String id;
21 | public String name;
22 | public String text;
23 | public final BigInteger value;
24 | public Attribute(String attributeId, String name, BigInteger value, String text) {
25 | this.id = attributeId;
26 | this.name = name;
27 | this.text = text;
28 | this.value = value;
29 | }
30 | }
31 |
32 | protected HashMap attributes;
33 |
34 | public HashMap getAttributes()
35 | {
36 | return attributes;
37 | }
38 |
39 | public Attribute getAttribute(String attributeId) {
40 | if (attributes != null)
41 | {
42 | return attributes.get(attributeId);
43 | }
44 | else
45 | {
46 | return null;
47 | }
48 | }
49 |
50 | public void setAttribute(String attributeId, Attribute attribute) {
51 | attributes.put(attributeId, attribute);
52 | }
53 |
54 | public NonFungibleToken(BigInteger tokenId, TokenDefinition ad, Map functionMappings) {
55 | this(tokenId);
56 | ad.parseField(tokenId, this, functionMappings);
57 | }
58 |
59 | public NonFungibleToken(BigInteger tokenId, TokenScriptResult tsr) {
60 | this(tokenId);
61 | for (TokenScriptResult.Attribute attr : tsr.getAttributes().values())
62 | {
63 | attributes.put(attr.id, new Attribute(attr.id, attr.name, attr.value, attr.text));
64 | }
65 | }
66 |
67 | public NonFungibleToken(BigInteger tokenId, TokenDefinition ad) {
68 | this(tokenId);
69 | ad.parseField(tokenId, this);
70 | }
71 |
72 | public NonFungibleToken(BigInteger tokenId) {
73 | id = tokenId;
74 | attributes = new HashMap<>();
75 | }
76 |
77 | public String getRangeStr(TicketRange data)
78 | {
79 | int ticketStart = getAttribute("category").value.intValue();
80 | String ticketRange = String.valueOf(ticketStart);
81 | if (data.tokenIds != null)
82 | {
83 | int lastValue = ticketStart + (data.tokenIds.size() - 1);
84 | if (data.tokenIds.size() > 1)
85 | {
86 | ticketRange = ticketRange + "-" + lastValue;
87 | }
88 | }
89 |
90 | return ticketRange;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/ParseResult.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 11/04/2019.
5 | * Stormbird in Singapore
6 | */
7 | public interface ParseResult
8 | {
9 | enum ParseResultId
10 | {
11 | OK,
12 | XML_OUT_OF_DATE,
13 | PARSER_OUT_OF_DATE,
14 | PARSE_FAILED
15 | };
16 |
17 | void parseMessage(ParseResultId parseResult);
18 | }
19 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/SalesOrderMalformed.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by weiwu on 12/3/18.
5 | */
6 |
7 | public class SalesOrderMalformed extends Exception
8 | {
9 | // Parameterless Constructor
10 | public SalesOrderMalformed() {}
11 |
12 | // Constructor that accepts a message
13 | public SalesOrderMalformed(String message)
14 | {
15 | super(message);
16 | }
17 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/SigReturnType.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | public enum SigReturnType
4 | {
5 | NO_TOKENSCRIPT,
6 | DEBUG_NO_SIGNATURE,
7 | DEBUG_SIGNATURE_INVALID,
8 | DEBUG_SIGNATURE_PASS,
9 | NO_SIGNATURE,
10 | SIGNATURE_INVALID,
11 | SIGNATURE_PASS
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TSAction.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Created by James on 2/04/2019.
7 | * Stormbird in Singapore
8 | */
9 | public class TSAction
10 | {
11 | public String type;
12 | public String exclude;
13 | public String view;
14 | public String style;
15 |
16 | public Map attributeTypes;
17 | public FunctionDefinition function;
18 | }
19 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TicketRange.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | /**
8 | * Created by James on 10/02/2018.
9 | */
10 |
11 | /**
12 | * This should purely be a container class of NonFungibleToken
13 | *
14 | */
15 | public class TicketRange
16 | {
17 | //public final int seatStart;
18 | //public int seatCount;
19 | public boolean isChecked;
20 | public boolean exposeRadio;
21 | public String contractAddress; // Should this be address or actual token?
22 |
23 | public List tokenIds;
24 |
25 | public TicketRange(BigInteger tokenId, String contractAddress)
26 | {
27 | this.contractAddress = contractAddress;
28 | tokenIds = new ArrayList<>();
29 | tokenIds.add(tokenId);
30 | this.isChecked = false;
31 | this.exposeRadio = false;
32 | }
33 |
34 | public TicketRange(List tokenIds, String contractAddress, boolean isChecked)
35 | {
36 | this.contractAddress = contractAddress;
37 | this.tokenIds = tokenIds;
38 | this.isChecked = isChecked;
39 | this.exposeRadio = false;
40 | }
41 |
42 | public void selectSubRange(int count)
43 | {
44 | if (count < tokenIds.size())
45 | {
46 | tokenIds = tokenIds.subList(0, count);
47 | }
48 | }
49 |
50 | public boolean equals(TicketRange compare)
51 | {
52 | if (compare == null || compare.tokenIds.size() != tokenIds.size()) return false;
53 | for (int i = 0; i < tokenIds.size(); i++)
54 | {
55 | BigInteger id = tokenIds.get(i);
56 | if (!id.equals(compare.tokenIds.get(i))) return false;
57 | }
58 |
59 | return true;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TokenScriptParseType.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 29/04/2019.
5 | * Stormbird in Sydney
6 | */
7 | public enum TokenScriptParseType
8 | {
9 | tsName, tsContract, tsOrigins, tsCards, tsTokenCard, tsAction, tsViewIconified, tsView, tsAttributeType,
10 | tsTransaction, tsAttributeTypes, tsToken, ts
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TokenScriptResult.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import java.math.BigInteger;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | /**
11 | * Created by James on 14/05/2019.
12 | * Stormbird in Sydney
13 | */
14 | public class TokenScriptResult
15 | {
16 | public static final class Attribute {
17 | public final String id;
18 | public String name;
19 | public String text;
20 | public final BigInteger value;
21 | public Map tokenIdText;
22 | public Map tokenIdValue;
23 | public Attribute(String attributeId, String name, BigInteger value, String text) {
24 | this.id = attributeId;
25 | this.name = name;
26 | this.text = text;
27 | this.value = value;
28 | }
29 | }
30 |
31 | private Map attrs = new HashMap<>();
32 |
33 | public void setAttribute(String key, Attribute attr)
34 | {
35 | attrs.put(key, attr);
36 | }
37 |
38 | public Map getAttributes()
39 | {
40 | return attrs;
41 | }
42 |
43 | public Attribute getAttribute(String attributeId) {
44 | if (attrs != null)
45 | {
46 | return attrs.get(attributeId);
47 | }
48 | else
49 | {
50 | return null;
51 | }
52 | }
53 |
54 | public static void addPair(StringBuilder attrs, String attrId, T attrValue)
55 | {
56 | attrs.append(attrId);
57 | attrs.append(": ");
58 |
59 | if (attrValue == null)
60 | {
61 | attrs.append("\"\"");
62 | }
63 | else if (attrValue instanceof BigInteger)
64 | {
65 | attrs.append("\"");
66 | attrs.append(((BigInteger)attrValue).toString(10));
67 | attrs.append("\"");
68 | }
69 | else if (attrValue instanceof List)
70 | {
71 | attrs.append("\'");
72 | attrs.append(new Gson().toJson(attrValue));
73 | attrs.append("\'");
74 | }
75 | else
76 | {
77 | String attrValueStr = (String) attrValue;
78 | if (attrValueStr.length() == 0 || (attrValueStr.charAt(0) != '{')) attrs.append("\"");
79 | attrs.append(attrValueStr);
80 | if (attrValueStr.length() == 0 || (attrValueStr.charAt(0) != '{')) attrs.append("\"");
81 | }
82 |
83 | attrs.append(",\n");
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TokenscriptContext.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 23/05/2019.
5 | * Stormbird in Sydney
6 | */
7 | public class TokenscriptContext
8 | {
9 | public ContractAddress cAddr; // holding contract of primary token
10 | public AttributeInterface attrInterface;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TokenscriptElement.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 28/05/2019.
5 | * Stormbird in Sydney
6 | */
7 | public class TokenscriptElement
8 | {
9 | public String ref;
10 | public String value;
11 |
12 | public boolean isToken()
13 | {
14 | return ref != null && ref.contains("tokenId");
15 | }
16 |
17 | public int getTokenIndex()
18 | {
19 | int index = -1;
20 | if (isToken())
21 | {
22 | try
23 | {
24 | String[] split = ref.split("[\\[\\]]");
25 | if (split.length == 2)
26 | {
27 | String indexStr = split[1];
28 | index = Integer.parseInt(indexStr);
29 | }
30 | }
31 | catch (NumberFormatException e)
32 | {
33 | e.printStackTrace();
34 | }
35 | }
36 |
37 | return index;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/TransactionResult.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 |
5 | /**
6 | * Created by James on 7/05/2019.
7 | * Stormbird in Sydney
8 | */
9 | public class TransactionResult
10 | {
11 | public final BigInteger tokenId;
12 | public final String method;
13 | public final String contractAddress;
14 | public final int contractChainId;
15 | public String result;
16 | public long resultTime;
17 | public final String attrId;
18 |
19 | public TransactionResult(int chainId, String address, BigInteger tokenId, AttributeType attr)
20 | {
21 | this.contractAddress = address;
22 | this.contractChainId = chainId;
23 | this.tokenId = tokenId;
24 | this.method = attr.function.method;
25 | this.attrId = attr.id;
26 | result = null;
27 | resultTime = 0;
28 | }
29 |
30 | public boolean needsUpdating(long lastTxTime)
31 | {
32 | //if contract had new transactions then update, or if last tx was -1 (always check)
33 | return (resultTime == 0 || lastTxTime <= 0 || lastTxTime > resultTime);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/UnsignedLong.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | import java.math.BigInteger;
4 |
5 | /**
6 | * Created by James on 26/03/2018.
7 | */
8 |
9 | public class UnsignedLong extends BigInteger
10 | {
11 | public UnsignedLong(byte[] byteValue)
12 | {
13 | super(1, byteValue);
14 | }
15 |
16 | static public UnsignedLong create(long value)
17 | {
18 | byte[] byteVal = new byte[4];
19 |
20 | for (int i = 0; i < 4; i++) {
21 | byteVal[i] = (byte) getByteVal(value, 3 - i);
22 | }
23 | return new UnsignedLong(byteVal);
24 | }
25 |
26 | static public UnsignedLong create(BigInteger value)
27 | {
28 | byte[] byteVal = new byte[4];
29 |
30 | for (int i = 0; i < 4; i++) {
31 | byteVal[i] = value.divide(BigInteger.valueOf( 1 << (3-i)*8 )).byteValue();// (value, 3 - i);
32 | }
33 | return new UnsignedLong(byteVal);
34 | }
35 |
36 | static public byte[] createBytes(long value)
37 | {
38 | byte[] byteVal = new byte[4];
39 |
40 | for (int i = 0; i < 4; i++) {
41 | byteVal[i] = (byte) getByteVal(value, 3 - i);
42 | }
43 |
44 | return byteVal;
45 | }
46 |
47 | //select the value 0-255 from each byte of the long
48 | private static int getByteVal(long value, int p)
49 | {
50 | return ((int) ((byte) ((value >> (p*8)) & 0xFF) ));
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/XMLDsigDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | public class XMLDsigDescriptor
4 | {
5 | public String result;
6 | public String subject;
7 | public String keyName;
8 | public String keyType;
9 | public String issuer;
10 | public SigReturnType type;
11 | public String certificateName = null;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/entity/XMLDsigVerificationResult.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.entity;
2 |
3 | /**
4 | * Created by James on 19/04/2019.
5 | * Stormbird in Sydney
6 | */
7 | public class XMLDsigVerificationResult
8 | {
9 | public boolean isValid;
10 | public String keyName;
11 | public String issuerPrincipal;
12 | public String subjectPrincipal;
13 | public String keyType;
14 | public String failureReason;
15 |
16 | public XMLDsigVerificationResult()
17 | {
18 | isValid = false;
19 | keyName = "";
20 | issuerPrincipal = "";
21 | subjectPrincipal = "";
22 | keyType = "";
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/tools/Convert.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import java.math.BigDecimal;
4 | import java.math.BigInteger;
5 | import java.math.RoundingMode;
6 | import java.text.DecimalFormat;
7 |
8 | /**
9 | * Ethereum unit conversion functions.
10 | */
11 | public final class Convert {
12 | private Convert() { }
13 |
14 | public static BigDecimal fromWei(String number, Unit unit) {
15 | return fromWei(new BigDecimal(number), unit);
16 | }
17 |
18 | public static BigDecimal fromWei(BigDecimal number, Unit unit) {
19 | return number.divide(unit.getWeiFactor());
20 | }
21 |
22 | public static BigDecimal toWei(String number, Unit unit) {
23 | return toWei(new BigDecimal(number), unit);
24 | }
25 |
26 | public static BigDecimal toWei(BigDecimal number, Unit unit) {
27 | return number.multiply(unit.getWeiFactor());
28 | }
29 |
30 | public enum Unit {
31 | WEI("wei", 0),
32 | KWEI("kwei", 3),
33 | MWEI("mwei", 6),
34 | GWEI("gwei", 9),
35 | SZABO("szabo", 12),
36 | FINNEY("finney", 15),
37 | ETHER("ether", 18),
38 | KETHER("kether", 21),
39 | METHER("mether", 24),
40 | GETHER("gether", 27);
41 |
42 | private String name;
43 | private BigDecimal weiFactor;
44 |
45 | Unit(String name, int factor) {
46 | this.name = name;
47 | this.weiFactor = BigDecimal.TEN.pow(factor);
48 | }
49 |
50 | public BigDecimal getWeiFactor() {
51 | return weiFactor;
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return name;
57 | }
58 |
59 | public static Unit fromString(String name) {
60 | if (name != null) {
61 | for (Unit unit : Unit.values()) {
62 | if (name.equalsIgnoreCase(unit.name)) {
63 | return unit;
64 | }
65 | }
66 | }
67 | return Unit.valueOf(name);
68 | }
69 | }
70 |
71 | public static String getEthString(double ethPrice)
72 | {
73 | DecimalFormat df = new DecimalFormat("0.#####");
74 | df.setRoundingMode(RoundingMode.CEILING);
75 | return df.format(ethPrice);
76 | }
77 |
78 | public static String getEthString(double ethFiatValue, int decimals)
79 | {
80 | DecimalFormat df = new DecimalFormat("0.#####");
81 | df.setRoundingMode(RoundingMode.CEILING);
82 | df.setMaximumFractionDigits(decimals);
83 | return df.format(ethFiatValue);
84 | }
85 |
86 | public static String getEthStringSzabo(BigInteger szabo)
87 | {
88 | BigDecimal ethPrice = fromWei(toWei(new BigDecimal(szabo), Unit.SZABO), Unit.ETHER);
89 | DecimalFormat df = new DecimalFormat("0.#####");
90 | df.setRoundingMode(RoundingMode.CEILING);
91 | return df.format(ethPrice);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/tools/Numeric.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import java.math.BigDecimal;
4 | import java.math.BigInteger;
5 | import java.util.Arrays;
6 |
7 | /**
8 | * Message codec functions.
9 | *
10 | * Implementation as per https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding
11 | */
12 | public final class Numeric {
13 |
14 | private static final String HEX_PREFIX = "0x";
15 |
16 | private Numeric() {
17 | }
18 |
19 | private static boolean isValidHexQuantity(String value) {
20 | if (value == null) {
21 | return false;
22 | }
23 |
24 | if (value.length() < 3) {
25 | return false;
26 | }
27 |
28 | return value.startsWith(HEX_PREFIX);
29 | }
30 |
31 | public static String cleanHexPrefix(String input) {
32 | if (containsHexPrefix(input)) {
33 | return input.substring(2);
34 | } else {
35 | return input;
36 | }
37 | }
38 |
39 | public static String prependHexPrefix(String input) {
40 | if (!containsHexPrefix(input)) {
41 | return HEX_PREFIX + input;
42 | } else {
43 | return input;
44 | }
45 | }
46 |
47 | public static boolean containsHexPrefix(String input) {
48 | return input.length() > 1 && input.charAt(0) == '0' && input.charAt(1) == 'x';
49 | }
50 |
51 | public static BigInteger toBigInt(byte[] value, int offset, int length) {
52 | return toBigInt((Arrays.copyOfRange(value, offset, offset + length)));
53 | }
54 |
55 | public static BigInteger toBigInt(byte[] value) {
56 | return new BigInteger(1, value);
57 | }
58 |
59 | public static BigInteger toBigInt(String hexValue) {
60 | String cleanValue = cleanHexPrefix(hexValue);
61 | return toBigIntNoPrefix(cleanValue);
62 | }
63 |
64 | public static BigInteger toBigIntNoPrefix(String hexValue) {
65 | return new BigInteger(hexValue, 16);
66 | }
67 |
68 | public static String toHexStringWithPrefix(BigInteger value) {
69 | return HEX_PREFIX + value.toString(16);
70 | }
71 |
72 | public static String toHexStringNoPrefix(BigInteger value) {
73 | return value.toString(16);
74 | }
75 |
76 | public static String toHexStringNoPrefix(byte[] input) {
77 | return toHexString(input, 0, input.length, false);
78 | }
79 |
80 | public static String toHexStringWithPrefixZeroPadded(BigInteger value, int size) {
81 | return toHexStringZeroPadded(value, size, true);
82 | }
83 |
84 | public static String toHexStringNoPrefixZeroPadded(BigInteger value, int size) {
85 | return toHexStringZeroPadded(value, size, false);
86 | }
87 |
88 | private static String toHexStringZeroPadded(BigInteger value, int size, boolean withPrefix) {
89 | String result = toHexStringNoPrefix(value);
90 |
91 | int length = result.length();
92 | if (length > size) {
93 | throw new UnsupportedOperationException(
94 | "Value " + result + "is larger then length " + size);
95 | } else if (value.signum() < 0) {
96 | throw new UnsupportedOperationException("Value cannot be negative");
97 | }
98 |
99 | StringBuilder sb = new StringBuilder();
100 | for (int i = 0; i < (size - length); i++) sb.append("0");
101 |
102 | if (length < size) {
103 | result = sb.toString() + result;
104 | }
105 |
106 | if (withPrefix) {
107 | return HEX_PREFIX + result;
108 | } else {
109 | return result;
110 | }
111 | }
112 |
113 | public static byte[] toBytesPadded(BigInteger value, int length) {
114 | byte[] result = new byte[length];
115 | byte[] bytes = value.toByteArray();
116 |
117 | int bytesLength;
118 | int srcOffset;
119 | if (bytes[0] == 0) {
120 | bytesLength = bytes.length - 1;
121 | srcOffset = 1;
122 | } else {
123 | bytesLength = bytes.length;
124 | srcOffset = 0;
125 | }
126 |
127 | if (bytesLength > length) {
128 | throw new RuntimeException("Input is too large to put in byte array of size " + length);
129 | }
130 |
131 | int destOffset = length - bytesLength;
132 | System.arraycopy(bytes, srcOffset, result, destOffset, bytesLength);
133 | return result;
134 | }
135 |
136 | public static byte[] hexStringToByteArray(String input) {
137 | String cleanInput = cleanHexPrefix(input);
138 |
139 | int len = cleanInput.length();
140 |
141 | if (len == 0) {
142 | return new byte[] {};
143 | }
144 |
145 | byte[] data;
146 | int startIdx;
147 | if (len % 2 != 0) {
148 | data = new byte[(len / 2) + 1];
149 | data[0] = (byte) Character.digit(cleanInput.charAt(0), 16);
150 | startIdx = 1;
151 | } else {
152 | data = new byte[len / 2];
153 | startIdx = 0;
154 | }
155 |
156 | for (int i = startIdx; i < len; i += 2) {
157 | data[(i + 1) / 2] = (byte) ((Character.digit(cleanInput.charAt(i), 16) << 4)
158 | + Character.digit(cleanInput.charAt(i + 1), 16));
159 | }
160 | return data;
161 | }
162 |
163 | public static String toHexString(byte[] input, int offset, int length, boolean withPrefix) {
164 | StringBuilder stringBuilder = new StringBuilder();
165 | if (withPrefix) {
166 | stringBuilder.append("0x");
167 | }
168 | for (int i = offset; i < offset + length; i++) {
169 | stringBuilder.append(String.format("%02x", input[i] & 0xFF));
170 | }
171 |
172 | return stringBuilder.toString();
173 | }
174 |
175 | public static String toHexString(byte[] input) {
176 | return toHexString(input, 0, input.length, true);
177 | }
178 |
179 | public static byte asByte(int m, int n) {
180 | return (byte) ((m << 4) | n);
181 | }
182 |
183 | public static boolean isIntegerValue(BigDecimal value) {
184 | return value.signum() == 0
185 | || value.scale() <= 0
186 | || value.stripTrailingZeros().scale() <= 0;
187 | }
188 | }
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/tools/TrustAddressGenerator.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import org.bouncycastle.asn1.x9.X9ECParameters;
4 | import org.bouncycastle.crypto.ec.CustomNamedCurves;
5 | import org.bouncycastle.crypto.params.ECDomainParameters;
6 | import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
7 | import org.bouncycastle.jce.ECNamedCurveTable;
8 | import org.bouncycastle.jce.ECPointUtil;
9 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
10 | import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
11 | import org.bouncycastle.math.ec.ECCurve;
12 | import org.bouncycastle.math.ec.ECPoint;
13 | import org.bouncycastle.jcajce.provider.digest.Keccak;
14 | import org.bouncycastle.util.encoders.Hex;
15 |
16 | import java.math.BigInteger;
17 | import java.security.*;
18 | import java.security.interfaces.ECPublicKey;
19 | import java.security.spec.InvalidKeySpecException;
20 | import java.util.Arrays;
21 |
22 | /***** WARNING *****
23 | *
24 | * TrustAddress can be generated without the TokenScript being
25 | * signed. It's digest is produced in the way "as if tokenscript is
26 | * signed", therefore please do not add logic like extracting
27 | * from the TokenScript assuming it's signed.
28 | * - Weiwu
29 | */
30 | public class TrustAddressGenerator {
31 | private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
32 | private static final ECDomainParameters CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(),
33 | CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
34 |
35 | public static final byte[] masterPubKey = Hex.decode("04f0985bd9dbb6f461adc994a0c12595716a7f4fb2879bfc5155dffec3770096201c13f8314b46db8d8177887f8d95af1f2dd217291ce6ffe9183681186696bbe5");
36 |
37 | public static String getTrustAddress(String contractAddress, String digest) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
38 | return preimageToAddress((contractAddress + "TRUST" + digest).getBytes());
39 | }
40 |
41 | public static String getRevokeAddress(String contractAddress, String digest) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
42 | return preimageToAddress((contractAddress + "REVOKE" + digest).getBytes());
43 | }
44 |
45 | // this won't make sense at all if you didn't read security.md
46 | // https://github.com/AlphaWallet/TokenScript/blob/master/doc/security.md
47 | public static String preimageToAddress(byte[] preimage) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
48 | Security.addProvider(new BouncyCastleProvider());
49 |
50 | // get the hash of the preimage text
51 | Keccak.Digest256 digest = new Keccak.Digest256();
52 | digest.update(preimage);
53 | byte[] hash = digest.digest();
54 |
55 | // use the hash to derive a new address
56 | BigInteger keyDerivationFactor = new BigInteger(Numeric.toHexStringNoPrefix(hash), 16);
57 | ECPoint donatePKPoint = extractPublicKey(decodeKey(masterPubKey));
58 | ECPoint digestPKPoint = donatePKPoint.multiply(keyDerivationFactor);
59 | return getAddress(digestPKPoint);
60 | }
61 |
62 | private static ECPoint extractPublicKey(ECPublicKey ecPublicKey) {
63 | java.security.spec.ECPoint publicPointW = ecPublicKey.getW();
64 | BigInteger xCoord = publicPointW.getAffineX();
65 | BigInteger yCoord = publicPointW.getAffineY();
66 | return CURVE.getCurve().createPoint(xCoord, yCoord);
67 | }
68 |
69 | private static ECPublicKey decodeKey(byte[] encoded)
70 | throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
71 | ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1");
72 | KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
73 | ECCurve curve = params.getCurve();
74 | java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params.getSeed());
75 | java.security.spec.ECPoint point = ECPointUtil.decodePoint(ellipticCurve, encoded);
76 | java.security.spec.ECParameterSpec params2 = EC5Util.convertSpec(ellipticCurve, params);
77 | java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(point, params2);
78 | return (ECPublicKey) fact.generatePublic(keySpec);
79 | }
80 |
81 | private static String getAddress(ECPoint pub) {
82 | byte[] pubKeyHash = computeAddress(pub);
83 | return Numeric.toHexString(pubKeyHash);
84 | }
85 |
86 | private static byte[] computeAddress(byte[] pubBytes) {
87 | Keccak.Digest256 digest = new Keccak.Digest256();
88 | digest.update(Arrays.copyOfRange(pubBytes, 1, pubBytes.length));
89 | byte[] addressBytes = digest.digest();
90 | return Arrays.copyOfRange(addressBytes, 0, 20);
91 | }
92 |
93 | private static byte[] computeAddress(ECPoint pubPoint) {
94 | return computeAddress(pubPoint.getEncoded(false ));
95 | }
96 |
97 | /**********************************************************************************
98 | For use in Command Console
99 | **********************************************************************************/
100 |
101 | public static void main(String args[]) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
102 | if (args.length == 2) {
103 | System.out.println("Express of Trust Address derived using the following:");
104 | System.out.println("");
105 | System.out.println("\tContract Address: " + args[0]);
106 | System.out.println("\tXML Digest for Signature: " + args[1]);
107 | System.out.println("");
108 | System.out.println("Are:");
109 | System.out.println("");
110 | System.out.println("\tTrust Address:\t" + getTrustAddress(args[0], args[1]));
111 | System.out.println("\tRevoke Address:\t" + getRevokeAddress(args[0], args[1]));
112 | } else {
113 | System.out.println("This utility generates express-of-trust address and its revocation address\n for a given pair of token contract and TokenScript");
114 | System.out.println("");
115 | System.out.println("Expecting two arguments: contract address and XML digest.");
116 | System.out.println("");
117 | System.out.println("\tExample:");
118 | System.out.println("\tAssuming classpath is set properly,:");
119 | System.out.println("\te.g. if you built the lib project with `gradle shadowJar` and you've set");
120 | System.out.println("\tCLASSPATH=build/libs/lib-all.jar");
121 | System.out.println("\tRun the following:");
122 | System.out.println("");
123 | System.out.println("$ java " + TrustAddressGenerator.class.getCanonicalName() +
124 | "0x63cCEF733a093E5Bd773b41C96D3eCE361464942 z+I6NxdALVtlc3TuUo2QEeV9rwyAmKB4UtQWkTLQhpE=");
125 | }
126 | }
127 |
128 | /**********************************************************************************
129 | For use in Amazon Lambda
130 | **********************************************************************************/
131 |
132 | public Response DeriveTrustAddress(Request req) throws Exception {
133 | String trust = getTrustAddress(req.contract, req.getDigest());
134 | String revoke = getRevokeAddress(req.contract, req.getDigest());
135 | return new Response(trust, revoke);
136 | }
137 |
138 | public static class Request {
139 | String contract;
140 | String digest;
141 |
142 | public String getContractAddress() {
143 | return contract;
144 | }
145 |
146 | public void setContractAddress(String contractAddress) {
147 | this.contract = contractAddress;
148 | }
149 |
150 | public String getDigest() {
151 | return digest;
152 | }
153 |
154 | public void setDigest(String digest) {
155 | this.digest = digest;
156 | }
157 |
158 | public Request(String contractAddress, String digest) {
159 | this.contract = contractAddress;
160 | this.digest = digest;
161 | }
162 |
163 | public Request() {
164 | }
165 | }
166 |
167 | public static class Response {
168 | String trustAddress;
169 | String revokeAddress;
170 |
171 | public String getTrustAddress() { return trustAddress; }
172 |
173 | public void setTrustAddress(String trustAddress) { this.trustAddress = trustAddress; }
174 |
175 | public String getRevokeAddress() { return revokeAddress; }
176 |
177 | public void setRevokeAddress(String revokeAddress) { this.revokeAddress = revokeAddress; }
178 |
179 | public Response(String trustAddress, String revokeAddress) {
180 | this.trustAddress = trustAddress;
181 | this.revokeAddress = revokeAddress;
182 | }
183 |
184 | public Response() {
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/tools/VerifyXMLDSig.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.InputStream;
5 | import java.io.UnsupportedEncodingException;
6 | import com.alphawallet.token.entity.XMLDsigVerificationResult;
7 | import com.github.cliftonlabs.json_simple.JsonObject;
8 |
9 | public class VerifyXMLDSig {
10 |
11 | //Invoke with Lambda via VerifyXMLDSig interface
12 | public Response VerifyTSMLFile(Request req) throws Exception {
13 | JsonObject result = validateSSLCertificate(req.file);
14 | return new Response(result);
15 | }
16 |
17 | public JsonObject validateSSLCertificate(String file) throws UnsupportedEncodingException {
18 | JsonObject result = new JsonObject();
19 | InputStream stream = new ByteArrayInputStream(file.getBytes("UTF-8"));
20 | XMLDsigVerificationResult XMLDsigVerificationResult = new XMLDSigVerifier().VerifyXMLDSig(stream);
21 | if (XMLDsigVerificationResult.isValid)
22 | {
23 | result.put("result", "pass");
24 | result.put("issuer", XMLDsigVerificationResult.issuerPrincipal);
25 | result.put("subject", XMLDsigVerificationResult.subjectPrincipal);
26 | result.put("keyName", XMLDsigVerificationResult.keyName);
27 | result.put("keyType", XMLDsigVerificationResult.keyType);
28 | }
29 | else
30 | {
31 | result.put("result", "fail");
32 | result.put("failureReason", XMLDsigVerificationResult.failureReason);
33 | }
34 | return result;
35 | }
36 |
37 | public static class Request {
38 | String file;
39 |
40 | public String getFile() {
41 | return file;
42 | }
43 |
44 | public void setFile(String file) {
45 | this.file = file;
46 | }
47 |
48 | public Request(String file) {
49 | this.file = file;
50 | }
51 |
52 | public Request() {
53 | }
54 | }
55 |
56 | public static class Response {
57 | JsonObject result;
58 |
59 | public JsonObject getResult() { return result; }
60 |
61 | public void setResult(JsonObject result) { this.result = result; }
62 |
63 | public Response(JsonObject result) {
64 | this.result = result;
65 | }
66 |
67 | public Response() {
68 | }
69 | }
70 |
71 | }
72 |
73 |
74 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import org.w3c.dom.DOMException;
4 | import org.w3c.dom.Document;
5 | import org.w3c.dom.NodeList;
6 | import org.xml.sax.SAXException;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.security.InvalidAlgorithmParameterException;
10 | import java.security.Key;
11 | import java.security.KeyException;
12 | import java.security.KeyStore;
13 | import java.security.KeyStoreException;
14 | import java.security.NoSuchAlgorithmException;
15 | import java.security.Principal;
16 | import java.security.PublicKey;
17 | import java.security.Security;
18 | import java.security.cert.CertPathValidator;
19 | import java.security.cert.CertPathValidatorException;
20 | import java.security.cert.CertificateException;
21 | import java.security.cert.CertificateExpiredException;
22 | import java.security.cert.CertificateFactory;
23 | import java.security.cert.CertificateNotYetValidException;
24 | import java.security.cert.PKIXParameters;
25 | import java.security.cert.TrustAnchor;
26 | import java.security.cert.X509Certificate;
27 | import java.util.Arrays;
28 | import java.util.HashSet;
29 | import java.util.List;
30 | import java.util.Set;
31 | import javax.net.ssl.TrustManagerFactory;
32 | import javax.net.ssl.X509TrustManager;
33 | import javax.xml.crypto.AlgorithmMethod;
34 | import javax.xml.crypto.KeySelector;
35 | import javax.xml.crypto.KeySelectorException;
36 | import javax.xml.crypto.KeySelectorResult;
37 | import javax.xml.crypto.MarshalException;
38 | import javax.xml.crypto.XMLCryptoContext;
39 | import javax.xml.crypto.XMLStructure;
40 | import javax.xml.crypto.dsig.XMLSignature;
41 | import javax.xml.crypto.dsig.XMLSignatureException;
42 | import javax.xml.crypto.dsig.XMLSignatureFactory;
43 | import javax.xml.crypto.dsig.dom.DOMValidateContext;
44 | import javax.xml.crypto.dsig.keyinfo.KeyInfo;
45 | import javax.xml.crypto.dsig.keyinfo.KeyName;
46 | import javax.xml.crypto.dsig.keyinfo.KeyValue;
47 | import javax.xml.crypto.dsig.keyinfo.X509Data;
48 | import javax.xml.parsers.DocumentBuilder;
49 | import javax.xml.parsers.DocumentBuilderFactory;
50 | import javax.xml.parsers.ParserConfigurationException;
51 |
52 | import com.alphawallet.token.entity.XMLDsigVerificationResult;
53 |
54 | /**
55 | * James Sangalli mans this project since July 2019.
56 | * Stormbird Pte Ltd, in Sydney
57 | */
58 |
59 | /**
60 | * This verifiers an XML Signature using the JSR 105 API. It assumes
61 | * the key needed to verify the signature is certified by one of the
62 | * X.509 certificates in the KeyInfo, and that X.509 certificate,
63 | * together with any other found in KeyInfo, form a chain of
64 | * certificate to a top level certified by one of the trusted
65 | * authorities of the installed JRE
66 | *
67 | * Out of scope:
68 | * - Multi-signature XML file
69 | * - Ignores any public key provided in KeyInfo
70 | *
71 | * See the test case for usage examples.
72 | */
73 | public class XMLDSigVerifier {
74 |
75 | public XMLDsigVerificationResult VerifyXMLDSig(InputStream fileStream)
76 | {
77 | XMLDsigVerificationResult result = new XMLDsigVerificationResult();
78 | try
79 | {
80 | //Signature will also be validated in this call, if it fails an exception is thrown
81 | //No point to validate the certificate is this signature is invalid to begin with
82 | //And TrustAddressGenerator needs to get an XMLSignature too.
83 | XMLSignature signature = getValidXMLSignature(fileStream);
84 | result.isValid = true; //would go to catch if this was not the case
85 | //check that the tsml file is signed by a valid certificate
86 | return validateCertificateIssuer(signature, result);
87 | }
88 | catch(Exception e)
89 | {
90 | result.isValid = false;
91 | result.failureReason = e.getMessage();
92 | return result;
93 | }
94 | }
95 |
96 | XMLSignature getValidXMLSignature(InputStream fileStream)
97 | throws ParserConfigurationException,
98 | IOException,
99 | SAXException,
100 | MarshalException,
101 | XMLSignatureException,
102 | DOMException
103 | {
104 | DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
105 | dbFactory.setNamespaceAware(true);
106 | DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
107 | Document xml = dBuilder.parse(fileStream);
108 | xml.getDocumentElement().normalize();
109 |
110 | // Find Signature element
111 | NodeList nl = xml.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
112 | if (nl.getLength() == 0)
113 | {
114 | throw new DOMException(DOMException.INDEX_SIZE_ERR, "Missing elements");
115 | }
116 |
117 | // Create a DOM XMLSignatureFactory that will be used to unmarshal the
118 | // document containing the XMLSignature
119 | XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
120 |
121 | // Create a DOMValidateContext and specify a KeyValue KeySelector
122 | // and document context
123 | DOMValidateContext valContext = new DOMValidateContext(new SigningCertSelector(), nl.item(0));
124 |
125 | // unmarshal the XMLSignature
126 | XMLSignature signature = fac.unmarshalXMLSignature(valContext);
127 |
128 | boolean validSig = signature.validate(valContext);
129 | if(!validSig)
130 | {
131 | throw new XMLSignatureException("Invalid XML signature");
132 | }
133 | return signature;
134 | }
135 |
136 | private void validateCertificateChain(List certList)
137 | throws NoSuchAlgorithmException,
138 | KeyStoreException,
139 | InvalidAlgorithmParameterException,
140 | CertificateException,
141 | CertPathValidatorException
142 | {
143 | // By default on Oracle JRE, algorithm is PKIX
144 | TrustManagerFactory tmf = TrustManagerFactory
145 | .getInstance(TrustManagerFactory.getDefaultAlgorithm());
146 | // 'null' will initialise the tmf with the default CA certs installed
147 | // with the JRE.
148 | tmf.init((KeyStore) null);
149 |
150 | X509TrustManager tm = (X509TrustManager) tmf.getTrustManagers()[0];
151 | CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
152 | Set anch = new HashSet<>();
153 | for (X509Certificate cert : tm.getAcceptedIssuers())
154 | {
155 | anch.add(new TrustAnchor(cert, null));
156 | }
157 | PKIXParameters params = new PKIXParameters(anch);
158 | Security.setProperty("ocsp.enable", "true");
159 | params.setRevocationEnabled(true);
160 | CertificateFactory factory = CertificateFactory.getInstance("X.509");
161 | try
162 | {
163 | cpv.validate(factory.generateCertPath(certList), params);
164 | }
165 | catch (CertPathValidatorException e)
166 | {
167 | System.out.println(e.getIndex());
168 | //if the timestamp check fails because the cert is expired
169 | //we allow this to continue (code 0)
170 | if(e.getIndex() != 0)
171 | {
172 | throw e;
173 | }
174 | }
175 | }
176 |
177 | private X509Certificate findRootCert(List certificates) {
178 | X509Certificate rootCert = null;
179 | for (X509Certificate cert : certificates) {
180 | X509Certificate signer = this.findSignerCertificate(cert, certificates);
181 | if (signer == null || signer.equals(cert)) {
182 | rootCert = cert;
183 | break;
184 | }
185 | }
186 | return rootCert;
187 | }
188 |
189 | private List reorderCertificateChain(List chain)
190 | {
191 | X509Certificate[] reorderedChain = new X509Certificate[chain.size()];
192 | int position = chain.size() - 1;
193 | X509Certificate rootCert = this.findRootCert(chain);
194 | reorderedChain[position] = rootCert;
195 | for (X509Certificate cert = rootCert;
196 | (cert = this.findSignedCert(cert, chain)) != null && position > 0;
197 | reorderedChain[position] = cert
198 | ) {
199 | --position;
200 | }
201 | return Arrays.asList(reorderedChain);
202 | }
203 |
204 | private X509Certificate findSignedCert(X509Certificate signingCert, List certificates)
205 | {
206 | X509Certificate signed = null;
207 | for (X509Certificate cert : certificates)
208 | {
209 | Principal signingCertSubjectDN = signingCert.getSubjectDN();
210 | Principal certIssuerDN = cert.getIssuerDN();
211 | if (certIssuerDN.equals(signingCertSubjectDN) && !cert.equals(signingCert))
212 | {
213 | signed = cert;
214 | break;
215 | }
216 | }
217 | return signed;
218 | }
219 |
220 |
221 | private X509Certificate findSignerCertificate(X509Certificate signedCert, List certificates) {
222 | X509Certificate signer = null;
223 | for (X509Certificate cert : certificates) {
224 | Principal certSubjectDN = cert.getSubjectDN();
225 | Principal issuerDN = signedCert.getIssuerDN();
226 | if (certSubjectDN.equals(issuerDN)) {
227 | signer = cert;
228 | break;
229 | }
230 | }
231 | return signer;
232 | }
233 |
234 | private XMLDsigVerificationResult validateCertificateIssuer(XMLSignature signature, XMLDsigVerificationResult result) {
235 | try
236 | {
237 | KeyInfo xmlKeyInfo = signature.getKeyInfo();
238 | List certList = getCertificateChainFromXML(xmlKeyInfo.getContent());
239 | List orderedCerts = reorderCertificateChain(certList);
240 | X509Certificate signingCert = selectSigningKeyFromXML(xmlKeyInfo.getContent());
241 | //Throws if invalid
242 | validateCertificateChain(orderedCerts);
243 | result.issuerPrincipal = signingCert.getIssuerX500Principal().getName();
244 | result.subjectPrincipal = signingCert.getSubjectX500Principal().getName();
245 | result.keyType = signingCert.getSigAlgName();
246 | for (Object o : xmlKeyInfo.getContent())
247 | {
248 | XMLStructure xmlStructure = (XMLStructure) o;
249 | if (xmlStructure instanceof KeyName)
250 | {
251 | result.keyName = ((KeyName) xmlStructure).getName();
252 | }
253 | }
254 | }
255 | catch(Exception e)
256 | {
257 | result.isValid = false;
258 | result.failureReason = e.getMessage();
259 | }
260 | return result;
261 | }
262 |
263 | private List getCertificateChainFromXML(List xmlElements) throws KeyStoreException {
264 | boolean found = false;
265 | List certs = null;
266 | for (int i = 0; i < xmlElements.size(); i++)
267 | {
268 | XMLStructure xmlStructure = (XMLStructure) xmlElements.get(i);
269 | if (xmlStructure instanceof X509Data)
270 | {
271 | if(found) throw new KeyStoreException("Duplicate X509Data element");
272 | found = true;
273 | certs = (List) ((X509Data) xmlStructure).getContent();
274 | }
275 | }
276 | return certs;
277 | }
278 |
279 | private PublicKey recoverPublicKeyFromXML(List xmlElements) throws KeyStoreException {
280 | boolean found = false;
281 | PublicKey keyVal = null;
282 | for (int i = 0; i < xmlElements.size(); i++)
283 | {
284 | XMLStructure xmlStructure = (XMLStructure) xmlElements.get(i);
285 | if (xmlStructure instanceof KeyValue)
286 | {
287 | //should only be one KeyValue
288 | if(found) throw new KeyStoreException("Duplicate Key found");
289 | found = true;
290 | KeyValue kv = (KeyValue) xmlStructure;
291 | try
292 | {
293 | keyVal = kv.getPublicKey();
294 | }
295 | catch (KeyException e)
296 | {
297 | e.printStackTrace();
298 | }
299 | }
300 | }
301 | return keyVal;
302 | }
303 |
304 | private X509Certificate selectSigningKeyFromXML(List xmlElements) throws KeyStoreException, CertificateNotYetValidException {
305 | PublicKey recovered = recoverPublicKeyFromXML(xmlElements);
306 | //Certificates from the XML might be in the wrong order
307 | List certList = reorderCertificateChain(getCertificateChainFromXML(xmlElements));
308 | for (X509Certificate crt : certList)
309 | {
310 | try
311 | {
312 | crt.checkValidity();
313 | }
314 | catch (CertificateExpiredException e)
315 | {
316 | //allow this
317 | System.out.println("Allowing expired cert: " + e.getMessage());
318 | continue;
319 | }
320 | if (recovered != null)
321 | {
322 | PublicKey certKey = crt.getPublicKey();
323 | if (Arrays.equals(recovered.getEncoded(), certKey.getEncoded()))
324 | {
325 | return crt;
326 | }
327 | }
328 | else if (crt.getSigAlgName().equals("SHA256withECDSA"))
329 | {
330 | return crt;
331 | }
332 | }
333 | //if non recovered, simply return the first certificate?
334 | return certList.get(0);
335 |
336 | }
337 |
338 | private class SigningCertSelector extends KeySelector
339 | {
340 | public KeySelectorResult select(
341 | KeyInfo keyInfo,
342 | KeySelector.Purpose purpose,
343 | AlgorithmMethod method,
344 | XMLCryptoContext context
345 | ) throws KeySelectorException
346 | {
347 | if (keyInfo == null) throw new KeySelectorException("Null KeyInfo object!");
348 | PublicKey signer = null;
349 | List list = keyInfo.getContent();
350 | boolean found = false;
351 | for (Object o : list)
352 | {
353 | XMLStructure xmlStructure = (XMLStructure) o;
354 | if (xmlStructure instanceof KeyValue)
355 | {
356 | if(found) throw new KeySelectorException("Duplicate KeyValue");
357 | found = true;
358 | KeyValue kv = (KeyValue) xmlStructure;
359 | try
360 | {
361 | signer = kv.getPublicKey();
362 | }
363 | catch (KeyException e)
364 | {
365 | e.printStackTrace();
366 | }
367 | }
368 | }
369 | if(signer != null) return new SimpleKeySelectorResult(signer);
370 | X509Certificate signingCert = null;
371 | try
372 | {
373 | signingCert = selectSigningKeyFromXML(list);
374 | }
375 | catch (Exception e)
376 | {
377 | throw new KeySelectorException(e.getMessage());
378 | }
379 | ;
380 | if (signingCert != null)
381 | {
382 | return new SimpleKeySelectorResult(signingCert.getPublicKey());
383 | }
384 | else
385 | {
386 | throw new KeySelectorException("No KeyValue element found!");
387 | }
388 | }
389 | }
390 |
391 | private class SimpleKeySelectorResult implements KeySelectorResult
392 | {
393 | private PublicKey pk;
394 | SimpleKeySelectorResult(PublicKey pk) {
395 | this.pk = pk;
396 | }
397 | public Key getKey() { return pk; }
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/util/DateTime.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.util;
2 |
3 | import java.text.DateFormat;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 | import java.util.TimeZone;
7 |
8 | /**
9 | * Created by James on 11/02/2019.
10 | * Stormbird in Singapore
11 | */
12 | public abstract class DateTime
13 | {
14 | protected long time;
15 | protected TimeZone timezone;
16 | protected boolean isZoned = false;
17 |
18 | public int getHour() {
19 | /* you can't just do this:
20 | return new Date(time + offset).getHours() - 1;
21 | because Date applies the local (the JRE's) timezone
22 | */
23 | SimpleDateFormat format = new SimpleDateFormat("H");
24 | return Integer.valueOf(format(format));
25 | }
26 |
27 | public int getMinute() {
28 | SimpleDateFormat format = new SimpleDateFormat("m");
29 | return Integer.valueOf(format(format));
30 | }
31 |
32 | public String format(DateFormat format) {
33 | format.setTimeZone(timezone);
34 | return format.format(new Date(time));
35 | }
36 |
37 | public boolean isZoned()
38 | {
39 | return isZoned;
40 | }
41 |
42 | /* EVERY FUNCTION BELOW ARE SET OUT IN JAVA8 */
43 |
44 | public long toEpochSecond() {
45 | return time/1000L;
46 | }
47 |
48 | public long toEpoch()
49 | {
50 | return time;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/util/DateTimeFactory.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.util;
2 |
3 | import com.alphawallet.token.entity.NonFungibleToken;
4 |
5 | import java.text.ParseException;
6 | import java.util.TimeZone;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | /**
11 | * Created by James on 11/02/2019.
12 | * Stormbird in Singapore
13 | */
14 | public abstract class DateTimeFactory
15 | {
16 | public static DateTime getDateTime(long unixTime, TimeZone timezone)
17 | {
18 | return new ZonedDateTime(unixTime, timezone);
19 | }
20 |
21 | public static DateTime getDateTime(NonFungibleToken.Attribute timeAttr) throws ParseException, IllegalArgumentException
22 | {
23 | String eventTimeText = timeAttr.text;
24 | if (eventTimeText != null)
25 | {
26 | //there was a specific timezone set in the XML definition file, use this
27 | return initZonedTime(eventTimeText);
28 | }
29 | else
30 | {
31 | //No timezone specified, assume time in GMT
32 | return new GeneralDateTime(timeAttr);
33 | }
34 | }
35 |
36 | public static DateTime getDateTime(String time) throws ParseException, IllegalArgumentException
37 | {
38 | return initZonedTime(time);
39 | }
40 |
41 | public static DateTime getCurrentTime()
42 | {
43 | return new GeneralDateTime(String.valueOf(System.currentTimeMillis()));
44 | }
45 |
46 | private static DateTime initZonedTime(String time) throws ParseException, IllegalArgumentException
47 | {
48 | Pattern p = Pattern.compile("(\\+\\d{4}|\\-\\d{4})");
49 | Matcher m = p.matcher(time);
50 | if (m.find())
51 | {
52 | return new ZonedDateTime(time, m);
53 | }
54 | else if (isNumeric(time))
55 | {
56 | return new GeneralDateTime(time);
57 | }
58 | else
59 | {
60 | //drop through, use current time
61 | return new GeneralDateTime(String.valueOf(System.currentTimeMillis()));
62 | }
63 | }
64 |
65 | private static boolean isNumeric(String testStr)
66 | {
67 | boolean result = false;
68 | if (testStr != null && testStr.length() > 0)
69 | {
70 | result = true;
71 | for (int i = 0; i < testStr.length(); i++)
72 | {
73 | char c = testStr.charAt(i);
74 | if (!Character.isDigit(c))
75 | {
76 | result = false;
77 | break;
78 | }
79 | }
80 | }
81 |
82 | return result;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/util/GeneralDateTime.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.util;
2 |
3 | import com.alphawallet.token.entity.NonFungibleToken;
4 |
5 | import java.util.TimeZone;
6 |
7 | /**
8 | * Created by James on 11/02/2019.
9 | * Stormbird in Singapore
10 | */
11 | class GeneralDateTime extends DateTime
12 | {
13 | GeneralDateTime(NonFungibleToken.Attribute timeAttr)
14 | {
15 | this.timezone = TimeZone.getTimeZone("GMT");
16 | time = timeAttr.value.longValue()*1000;
17 | }
18 |
19 | GeneralDateTime(String time)
20 | {
21 | this.timezone = TimeZone.getTimeZone("GMT");
22 | Long timeConv = Long.valueOf(time);
23 | this.time = timeConv*1000;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/alphawallet/token/util/ZonedDateTime.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.util;
2 |
3 | import java.text.ParseException;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 | import java.util.TimeZone;
7 | import java.util.regex.Matcher;
8 | /*
9 | * by Weiwu, 2018. Modeled after Java8's ZonedDateTime, intended to be
10 | * replaced by Java8's ZonedDateTime as soon as Android 8.0 gets popular
11 | */
12 | public class ZonedDateTime extends DateTime
13 | {
14 | //private final SimpleDateFormat ISO8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmXXX");
15 |
16 |
17 | /* For anyone deleting this class to use Java8 ZonedDateTime:
18 | *
19 | * A LITTLE DEVIL LIES IN THE DETAIL HERE
20 | *
21 | * In Java8, ZonedDateTime.of(LocalDateTime time, ZoneID id) works
22 | * by taking the year, month, day, hour, minute, second tuple from
23 | * LocalDateTime, stripping off the timezone information, then
24 | * treat it as if the tuple is in ZoneID. i.e. no timezone offset applied
25 | * unless later toEpochSecond() is used.
26 | *
27 | * In this ZonedDateTime which uses a constructor instead of a
28 | * static of() method, unixTime always represent Unix Time, that
29 | * is, the number of seconds since Epoch as the Epoch happens at UTC.
30 | * Apparently timezone offset is applied in format()
31 | */
32 | ZonedDateTime(long unixTime, TimeZone timezone) {
33 | this.time = unixTime * 1000L;
34 | this.timezone = timezone;
35 | isZoned = true;
36 | }
37 |
38 | ZonedDateTime(String time, Matcher m) throws ParseException, IllegalArgumentException
39 | {
40 | SimpleDateFormat isoFormat = new SimpleDateFormat("yyyyMMddHHmmssZZZZ");
41 | this.timezone = TimeZone.getTimeZone("GMT"+m.group(1));
42 | isoFormat.setTimeZone(this.timezone);
43 | Date date = isoFormat.parse(time);
44 | this.time = date.getTime();
45 | isZoned = true;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/src/test/java/com/alphawallet/token/tools/TokenDefinitionTest.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import com.alphawallet.token.entity.ContractInfo;
4 | import com.alphawallet.token.entity.ParseResult;
5 | import org.junit.Test;
6 | import org.xml.sax.SAXException;
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.IOException;
10 | import java.util.Locale;
11 | import static junit.framework.TestCase.assertNotNull;
12 | import static org.junit.Assert.assertEquals;
13 | import static org.junit.Assert.assertFalse;
14 | import static org.junit.Assert.assertNotEquals;
15 | import static org.junit.Assert.assertTrue;
16 | import static org.junit.Assert.fail;
17 |
18 |
19 | public class TokenDefinitionTest implements ParseResult
20 | {
21 | private File entryTokenTestFile = new File("src/test/ts/entrytoken.xml");
22 |
23 | @Test
24 | public void TokenInformationCanBeExtracted() throws IOException, SAXException {
25 | assertTrue(entryTokenTestFile.exists());
26 | TokenDefinition entryToken = new TokenDefinition(new FileInputStream(entryTokenTestFile), new Locale("en"), this);
27 | assertFalse(entryToken.attributeTypes.isEmpty());
28 | for (String contractName : entryToken.contracts.keySet())
29 | {
30 | assertNotEquals(0, contractName.length());
31 | }
32 |
33 | // test contract address extraction
34 | String holdingContract = entryToken.holdingToken;
35 | ContractInfo ci = entryToken.contracts.get(holdingContract);
36 |
37 | assertTrue(entryToken.contracts.size() > 0); //we have at least one address
38 |
39 | for (int networkId : ci.addresses.keySet())
40 | {
41 | for (String address : ci.addresses.get(networkId))
42 | {
43 | assertEquals(40 + 2, address.length());
44 | }
45 | }
46 | }
47 |
48 | @Test(expected = SAXException.class)
49 | public void BadLocaleShouldThrowException() throws IOException, SAXException {
50 | TokenDefinition ticketAsset = new TokenDefinition(new FileInputStream(entryTokenTestFile), new Locale("asdf"), this);
51 | assertNotNull(ticketAsset);
52 | }
53 |
54 | @Override
55 | public void parseMessage(ParseResultId parseResult)
56 | {
57 | switch (parseResult)
58 | {
59 | case OK:
60 | System.out.println("Schema date is correct.");
61 | break;
62 | case XML_OUT_OF_DATE:
63 | System.out.println("Parsing outdated schema. It's an older schema but it checks out.");
64 | break;
65 | case PARSER_OUT_OF_DATE:
66 | System.out.println("Parser attempting to parse future schema. Code base needs to be updated.");
67 | fail();
68 | break;
69 | case PARSE_FAILED:
70 | System.out.println("Parser Error.");
71 | fail();
72 | break;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/src/test/java/com/alphawallet/token/tools/TrustAddressGeneratorTest.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import org.junit.Test;
4 | import org.xml.sax.SAXException;
5 |
6 | import javax.xml.crypto.MarshalException;
7 | import javax.xml.crypto.dsig.XMLSignature;
8 | import javax.xml.crypto.dsig.XMLSignatureException;
9 | import javax.xml.parsers.ParserConfigurationException;
10 | import java.io.*;
11 | import org.bouncycastle.util.encoders.Base64;
12 |
13 | public class TrustAddressGeneratorTest {
14 | String digest;
15 |
16 | public TrustAddressGeneratorTest() throws IOException, MarshalException, ParserConfigurationException, SAXException, XMLSignatureException {
17 | InputStream input = new FileInputStream("src/test/ts/EntryToken.tsml");
18 | XMLDSigVerifier sigVerifier = new XMLDSigVerifier();
19 | XMLSignature signature = sigVerifier.getValidXMLSignature(input);
20 | InputStream digest = signature.getSignedInfo().getCanonicalizedData();
21 | this.digest = convertHexToBase64String(Numeric.toHexString(getBytesFromInputStream(digest)));
22 | }
23 |
24 | @Test
25 | public void generateTrustAddress() throws Exception {
26 | System.out.println("digest:" + digest);
27 | String trustAddress = TrustAddressGenerator.getTrustAddress("0x63cCEF733a093E5Bd773b41C96D3eCE361464942", digest);
28 | assert(trustAddress.equals("0x2e02934b4ed1bee0defa7a58061dd8ee9440094c"));
29 | }
30 |
31 | @Test
32 | public void generateRevokeAddress() throws Exception {
33 | String revokeAddress = TrustAddressGenerator.getRevokeAddress("0x63cCEF733a093E5Bd773b41C96D3eCE361464942", digest);
34 | assert(revokeAddress.equals("0x6b4c50938caef365fa3e04bfe5a25da518dba447"));
35 | }
36 |
37 | /*
38 | * the following utility functions are moved from
39 | * TrustAddressGenerator because it doesn't belong
40 | * there. TrustAddressGenerator generates addresses from a
41 | * contract address and a digest. Itself shouldn't do the work of
42 | * parsing XML and calculating the digest.
43 | */
44 |
45 | byte[] getBytesFromInputStream(InputStream is) throws IOException {
46 | ByteArrayOutputStream os = new ByteArrayOutputStream();
47 | byte[] buffer = new byte[0xFFFF];
48 | for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
49 | os.write(buffer, 0, len);
50 | }
51 | return os.toByteArray();
52 | }
53 |
54 | String convertHexToBase64String(String input) throws IOException {
55 | byte barr[] = new byte[16];
56 | int bcnt = 0;
57 | for (int i = 0; i < 32; i += 2) {
58 | char c1 = input.charAt(i);
59 | char c2 = input.charAt(i + 1);
60 | int i1 = convertCharToInt(c1);
61 | int i2 = convertCharToInt(c2);
62 | barr[bcnt] = 0;
63 | barr[bcnt] |= (byte) ((i1 & 0x0F) << 4);
64 | barr[bcnt] |= (byte) (i2 & 0x0F);
65 | bcnt++;
66 | }
67 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
68 | Base64.encode(barr, outputStream);
69 | return outputStream.toString();
70 | }
71 |
72 | int convertCharToInt(char c) {
73 | char[] carr = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
74 | char clower = Character.toLowerCase(c);
75 | for (int i = 0; i < carr.length; i++) {
76 | if (clower == carr[i]) {
77 | return i;
78 | }
79 | }
80 | return 0;
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/lib/src/test/java/com/alphawallet/token/tools/XMLDsigVerifierTest.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.tools;
2 |
3 | import com.alphawallet.token.entity.XMLDsigVerificationResult;
4 | import org.junit.Test;
5 |
6 | import java.io.FileInputStream;
7 | import java.io.InputStream;
8 |
9 | public class XMLDsigVerifierTest {
10 |
11 | @Test
12 | public void testRSACertSignedByInvalidAuthority() throws Exception {
13 | InputStream EntryToken = new FileInputStream("src/test/ts/EntryToken.tsml");
14 | XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(EntryToken);
15 | assert(!result.isValid);
16 | assert(result.failureReason.equals("Path does not chain with any of the trust anchors"));
17 | }
18 |
19 | // Always fails on travis, comment out for now to preserve Android test integrity
20 | // @Test
21 | // public void verifyECDSAxmldsig() throws Exception {
22 | // InputStream DAIToken = new FileInputStream("src/test/ts/DAI.tsml");
23 | // XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(DAIToken);
24 | // assert(result.isValid);
25 | // assert(result.subjectPrincipal.equals("CN=*.aw.app"));
26 | // }
27 |
28 | @Test
29 | public void verifyRSAxmldsig() throws Exception {
30 | InputStream DAIToken = new FileInputStream("src/test/ts/EntryToken-valid-RSA.tsml");
31 | XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(DAIToken);
32 | assert(result.isValid);
33 | assert(result.subjectPrincipal.equals("CN=aw.app"));
34 | }
35 |
36 | @Test
37 | public void testFifaTSMLECDSA() throws Exception {
38 | InputStream EntryToken = new FileInputStream("src/test/ts/fifa.tsml");
39 | XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(EntryToken);
40 | //cert is expired but we still allow this so long as the signature is valid and is approved by ca
41 | assert(result.isValid);
42 | }
43 |
44 | @Test
45 | public void testDuplicateKeyInfo() throws Exception {
46 | InputStream EntryToken = new FileInputStream("src/test/ts/EntryToken-duplicate-Values.tsml");
47 | XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(EntryToken);
48 | assert(!result.isValid);
49 | System.out.println(result.failureReason);
50 | assert(result.failureReason.contains("expected KeyInfo or Object"));
51 | }
52 |
53 | // Always fails on travis, comment out for now to preserve Android test integrity
54 | // @Test
55 | // public void testWrongOrderCertChain() throws Exception {
56 | // InputStream EntryToken = new FileInputStream("src/test/ts/DAI-wrong-chain-order.tsml");
57 | // XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(EntryToken);
58 | // assert(result.isValid);
59 | // }
60 |
61 | @Test
62 | public void testNotYetValidCertificate() throws Exception {
63 | InputStream EntryToken = new FileInputStream("src/test/ts/EntryToken-future-cert-self-signed.tsml");
64 | XMLDsigVerificationResult result = new XMLDSigVerifier().VerifyXMLDSig(EntryToken);
65 | assert(!result.isValid);
66 | assert(result.failureReason.contains("NotBefore")); // save travis from misreporting thanks to timezone
67 | }
68 | }
--------------------------------------------------------------------------------
/lib/src/test/java/com/alphawallet/token/util/ZonedDateTimeTest.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.util;
2 |
3 | import org.junit.Test;
4 |
5 | import java.text.ParseException;
6 | import java.text.SimpleDateFormat;
7 | import java.util.Date;
8 | import java.util.Locale;
9 | import java.util.TimeZone;
10 |
11 | import static org.junit.Assert.assertEquals;
12 |
13 | public class ZonedDateTimeTest {
14 | final long unixTime = 0;
15 | final String ISO8601 = "1970-01-01T03:00+03:00";
16 | final String GeneralizedTime = "19700101030000+0300";
17 | @Test
18 | public void DemonstrateBehaviourOfJave8ZonedDateTime() {
19 | java.time.LocalDateTime time = java.time.LocalDateTime.ofEpochSecond(unixTime, 0, java.time.ZoneOffset.of("+3"));
20 | java.time.ZoneId moscow = java.time.ZoneId.of("Europe/Moscow");
21 | java.time.ZonedDateTime timeInMoscow = java.time.ZonedDateTime.of(time, moscow);
22 | assertEquals(ISO8601, timeInMoscow.toString().substring(0, ISO8601.length()));
23 | // timeInMoscow is at the Epoch
24 | assertEquals(unixTime, timeInMoscow.toEpochSecond());
25 | // yet the hour value read from it is based on Moscow time.
26 | assertEquals(3, timeInMoscow.getHour());
27 | }
28 |
29 | @Test
30 | public void OurZonedDatedTimeShouldBehaveAlike() {
31 | // what time was it in Moscow at Epoch?
32 | Date epoch = new Date(0);
33 | SimpleDateFormat format = new SimpleDateFormat("hh:mm", Locale.ENGLISH);
34 | format.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));
35 | assertEquals("03:00", format.format(epoch));
36 |
37 | // okay let's verify this with the ZonedDateTime
38 | ZonedDateTime timeInMoscow = new ZonedDateTime(unixTime, TimeZone.getTimeZone("Europe/Moscow"));
39 | //assertEquals(ISO8601, timeInMoscow.toString()); //TODO: ZonedDatTime.toString isn't implemented - needs to explicitly overloaded for this test to work
40 | assertEquals(unixTime, timeInMoscow.toEpochSecond());
41 | assertEquals(3, timeInMoscow.getHour());
42 | assertEquals(0, timeInMoscow.getMinute());
43 | }
44 |
45 | @Test
46 | public void ZonedDateTimeCanBeCreatedFromGeneralizedTime() throws ParseException {
47 | DateTime timeInMoscow = DateTimeFactory.getDateTime(GeneralizedTime);
48 | //assertEquals(ISO8601, timeInMoscow.toString()); //TODO: ZonedDatTime.toString isn't implemented - needs to explicitly overloaded for this test to work
49 | assertEquals(unixTime, timeInMoscow.toEpochSecond());
50 | assertEquals(3, timeInMoscow.getHour());
51 | assertEquals(0, timeInMoscow.getMinute());
52 |
53 | DateTime timeInMoscow2 = DateTimeFactory.getDateTime("19700101030101+0300");
54 | //assertEquals("1970-01-01T03:01+03:00", timeInMoscow2.toString()); //TODO: ZonedDatTime.toString isn't implemented - needs to explicitly overloaded for this test to work
55 | assertEquals(61, timeInMoscow2.toEpochSecond());
56 | assertEquals(3, timeInMoscow2.getHour());
57 | assertEquals(1, timeInMoscow2.getMinute());
58 |
59 | DateTime timeInAzores = DateTimeFactory.getDateTime("19700101030000-0100");
60 | //assertEquals("1970-01-01T03:00-01:00", timeInAzores.toString()); //TODO: ZonedDatTime.toString isn't implemented - needs to explicitly overloaded for this test to work
61 | assertEquals(14400, timeInAzores.toEpochSecond()); //this time is relatively 4 hrs from Moscow
62 | assertEquals(3, timeInAzores.getHour());
63 | assertEquals(0, timeInAzores.getMinute());
64 |
65 | // ADDING MORE TESTS: READ VERY CAREFULLY
66 | //
67 | // IF YOU ADD MORE TESTS HERE, AND YOU USE 'assertEquals("1970 ...", timeInTimbuktu.toString());'
68 | // THEN THE TEST _WILL FAIL_. IF YOU WANT TO USE THIS NOTATION THEN PLEASE ADD A toString() OVERRIDE.
69 | // NEXT PERSON WHO DOES THIS WITHOUT WRITING THE OVERRIDE WILL GET USED FOR BOXING PRACTICE IN LIU OF BOB
70 | // PHILLIP WILL REVOKE YOUR LOVE TOKENS AND NOT ISSUE YOU ANY MORE. AND HE'LL TURN HIS BACK ON YOU.
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/src/test/ts/DAI-wrong-chain-order.tsml:
--------------------------------------------------------------------------------
1 |
2 |
3 | DAI
4 |
5 |
6 | 0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Convert to xDAI
21 |
22 |
23 |
24 | Amount in DAI
25 | 代幣金額
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016
38 |
39 |
40 |
41 |
42 |
133 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | DAI-Balance
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | X0FONQ5A2nKYEK9Kh7nZhkXCOwKiUFc2CX7PzRTRZIg=
190 |
191 |
192 |
193 | T3QNYxDZGUi3S3hLZQW5gd4xOdLK05lqIiFKS+G4BQXc19dtMKw2ZcUAzkpKk9rHlcwaDe71mXNP
194 | 09+un7VGGg==
195 |
196 |
197 |
198 |
199 | MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQK
200 | ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
201 | DTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxl
202 | dCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkq
203 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4
204 | S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13
205 | EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKH
206 | y9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2P
207 | MTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQAB
208 | o4IBfTCCAXkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEE
209 | czBxMDIGCCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNvbTA7
210 | BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9kc3Ryb290Y2F4My5w
211 | N2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwVAYDVR0gBE0wSzAIBgZngQwBAgEw
212 | PwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcCARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNy
213 | eXB0Lm9yZzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9P
214 | VENBWDNDUkwuY3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
215 | AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJouM2VcGfl96S8
216 | TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/wApIvJSwtmVi4MFU5aMqrSDE
217 | 6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwuX4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPM
218 | TZ+sOPAveyxindmjkW8lGy+QsRlGPfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M
219 | +X+Q7UNKEkROb3N6KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
220 |
221 |
222 | MIIEnTCCA4WgAwIBAgISA4eHru2g9b47E+GGv5IhZBdxMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNV
223 | BAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1
224 | dGhvcml0eSBYMzAeFw0xOTA1MTAwNzQwMzNaFw0xOTA4MDgwNzQwMzNaMBMxETAPBgNVBAMMCCou
225 | YXcuYXBwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENkPeytqd1S4FxsPfCRCHNmI/7uDcPVL4
226 | TvjF6s+zHfB1evO76ssakyy0BXg/uJWsGrtGzGYhvjd3444H4gf8XaOCAn0wggJ5MA4GA1UdDwEB
227 | /wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNV
228 | HQ4EFgQUyjoV3pdAeichjdQFBT8kA05t0ScwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo
229 | 7KEwbwYIKwYBBQUHAQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2Vu
230 | Y3J5cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQub3Jn
231 | LzAzBgNVHREELDAqgggqLmF3LmFwcIIGYXcuYXBwghZwYXltYXN0ZXIuc3Rvcm1iaXJkLnNnMEwG
232 | A1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9j
233 | cHMubGV0c2VuY3J5cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAdH7agzGtMxCRIZzO
234 | JU9CcMK//V5CIAjGNzV55hB7zFYAAAFqoOauPQAABAMARzBFAiApHLwIHu5CA7kwyQAGQrK8PwwJ
235 | sVoRZxeuQCJdMB+UUAIhAP6W/YOl2/5N5bQooLHGa4rGa3+JOoCFTmiRqnVvFgq0AHYAY/Lbzeg7
236 | zCzPC3KEJ1drM6SNYXePvXWmOLHHaFRL2I0AAAFqoOauLQAABAMARzBFAiA6FEMR75e3R38smygH
237 | jfixJI9qSBRuxThdnsgyWz2nWAIhAKLUQC2pZkdCJLTiy8dk8WUeUdiLjgzejYWuI0V0phZkMA0G
238 | CSqGSIb3DQEBCwUAA4IBAQAmrLYQDsnzwzXfyuZ/Ig6JyDeBk8Yxz+htyS6Ez7LpBEJy9N1e0BjD
239 | +krmI8+7fllr/yNcGh6ulBG6yMQvgXYoRGmDDipDJh8Sf7zqY3dVByri0IxHxS0JeXiYCsxZSJ3Q
240 | 4RkTD+Wqu1p92lv/F0oLeBcgdL/gfeCTaCcLw+/V/yav8wF6Ro3zBkJ/3Gx/qqn57g7+r5eF/4mK
241 | Ed9k40t1QsbxZD6S0LQ1G0ubtJXClQXfxvq4OZWhBJFX+GlSz3/bDwRD4txq0wbDp3Vx2L05ck1D
242 | qRN41nXqnHsy+MTEVwr8uPceit1UgIZQmWkeHvWJPHEafPluNR+y00DegDZx
243 |
244 |
245 |
246 |
--------------------------------------------------------------------------------
/lib/src/test/ts/DAI.tsml:
--------------------------------------------------------------------------------
1 |
2 |
3 | DAI
4 |
5 |
6 | 0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Convert to xDAI
21 |
22 |
23 |
24 | Amount in DAI
25 | 代幣金額
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016
38 |
39 |
40 |
41 |
42 |
133 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | DAI-Balance
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | X0FONQ5A2nKYEK9Kh7nZhkXCOwKiUFc2CX7PzRTRZIg=
190 |
191 |
192 |
193 | T3QNYxDZGUi3S3hLZQW5gd4xOdLK05lqIiFKS+G4BQXc19dtMKw2ZcUAzkpKk9rHlcwaDe71mXNP
194 | 09+un7VGGg==
195 |
196 |
197 |
198 |
199 | MIIEnTCCA4WgAwIBAgISA4eHru2g9b47E+GGv5IhZBdxMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNV
200 | BAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1
201 | dGhvcml0eSBYMzAeFw0xOTA1MTAwNzQwMzNaFw0xOTA4MDgwNzQwMzNaMBMxETAPBgNVBAMMCCou
202 | YXcuYXBwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENkPeytqd1S4FxsPfCRCHNmI/7uDcPVL4
203 | TvjF6s+zHfB1evO76ssakyy0BXg/uJWsGrtGzGYhvjd3444H4gf8XaOCAn0wggJ5MA4GA1UdDwEB
204 | /wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNV
205 | HQ4EFgQUyjoV3pdAeichjdQFBT8kA05t0ScwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo
206 | 7KEwbwYIKwYBBQUHAQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2Vu
207 | Y3J5cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQub3Jn
208 | LzAzBgNVHREELDAqgggqLmF3LmFwcIIGYXcuYXBwghZwYXltYXN0ZXIuc3Rvcm1iaXJkLnNnMEwG
209 | A1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9j
210 | cHMubGV0c2VuY3J5cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAdH7agzGtMxCRIZzO
211 | JU9CcMK//V5CIAjGNzV55hB7zFYAAAFqoOauPQAABAMARzBFAiApHLwIHu5CA7kwyQAGQrK8PwwJ
212 | sVoRZxeuQCJdMB+UUAIhAP6W/YOl2/5N5bQooLHGa4rGa3+JOoCFTmiRqnVvFgq0AHYAY/Lbzeg7
213 | zCzPC3KEJ1drM6SNYXePvXWmOLHHaFRL2I0AAAFqoOauLQAABAMARzBFAiA6FEMR75e3R38smygH
214 | jfixJI9qSBRuxThdnsgyWz2nWAIhAKLUQC2pZkdCJLTiy8dk8WUeUdiLjgzejYWuI0V0phZkMA0G
215 | CSqGSIb3DQEBCwUAA4IBAQAmrLYQDsnzwzXfyuZ/Ig6JyDeBk8Yxz+htyS6Ez7LpBEJy9N1e0BjD
216 | +krmI8+7fllr/yNcGh6ulBG6yMQvgXYoRGmDDipDJh8Sf7zqY3dVByri0IxHxS0JeXiYCsxZSJ3Q
217 | 4RkTD+Wqu1p92lv/F0oLeBcgdL/gfeCTaCcLw+/V/yav8wF6Ro3zBkJ/3Gx/qqn57g7+r5eF/4mK
218 | Ed9k40t1QsbxZD6S0LQ1G0ubtJXClQXfxvq4OZWhBJFX+GlSz3/bDwRD4txq0wbDp3Vx2L05ck1D
219 | qRN41nXqnHsy+MTEVwr8uPceit1UgIZQmWkeHvWJPHEafPluNR+y00DegDZx
220 |
221 |
222 | MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/MSQwIgYDVQQK
223 | ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
224 | DTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0NlowSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxl
225 | dCdzIEVuY3J5cHQxIzAhBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkq
226 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4
227 | S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13
228 | EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKH
229 | y9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2P
230 | MTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQAB
231 | o4IBfTCCAXkwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEE
232 | czBxMDIGCCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNvbTA7
233 | BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9kc3Ryb290Y2F4My5w
234 | N2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwVAYDVR0gBE0wSzAIBgZngQwBAgEw
235 | PwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcCARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNy
236 | eXB0Lm9yZzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9P
237 | VENBWDNDUkwuY3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
238 | AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJouM2VcGfl96S8
239 | TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/wApIvJSwtmVi4MFU5aMqrSDE
240 | 6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwuX4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPM
241 | TZ+sOPAveyxindmjkW8lGy+QsRlGPfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M
242 | +X+Q7UNKEkROb3N6KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
243 |
244 |
245 |
246 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "alpha-wallet-android",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "normalize.css": {
8 | "version": "8.0.0",
9 | "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.0.tgz",
10 | "integrity": "sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A=="
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "alpha-wallet-android",
3 | "version": "1.0.0",
4 | "description": "Each module has its own README. Please click the name of the module to go to the corrisponding README file.",
5 | "main": "gulpfile.js",
6 | "directories": {
7 | "lib": "lib"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/AlphaWallet/alpha-wallet-android.git"
15 | },
16 | "author": "",
17 | "license": "GPL-3.0",
18 | "bugs": {
19 | "url": "https://github.com/AlphaWallet/alpha-wallet-android/issues"
20 | },
21 | "homepage": "https://github.com/AlphaWallet/alpha-wallet-android#readme",
22 | "dependencies": {
23 | "normalize.css": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':lib', ':tokenscriptviewer'
2 |
--------------------------------------------------------------------------------
/snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/snapshot.png
--------------------------------------------------------------------------------
/src/file.tcl:
--------------------------------------------------------------------------------
1 | # take a list of n nodes, create a table of n rows for each node,
2 | # extract columns of data by the columns defined in $args.
3 |
4 | # $args: each column is defined as a 3-element list of
5 | # {id, table-heading, xpath for value extraction}
6 |
7 | # note that variable $name can be used in the xpath which, for each
8 | # row, will be replaced by the @name value of the node of that row.
9 |
10 | proc table {pathname nodes args} {
11 |
12 | foreach col $args {
13 | if {[llength $col] != 3} {
14 | puts "Column description should have exactly 3 items: $col"
15 | exit 255
16 | }
17 | }
18 |
19 | if [winfo exists $pathname] { destroy $pathname }
20 | ttk::treeview $pathname -columns [lmap col $args {lindex $col 0}]
21 |
22 | foreach col $args {
23 | $pathname heading [lindex $col 0] -text [lindex $col 1]
24 | }
25 |
26 | return [lmap node $nodes {
27 | set name [expr {[$node hasAttribute name]?[$node getAttribute name]:""}]
28 | set values [lmap col $args {$node selectNodes [lindex $col 2]}]
29 | $pathname insert {} end -text $name -values $values
30 | }]
31 | }
32 |
33 |
34 | proc filemenu_open {} {
35 | global filename
36 | set file_types {
37 | { {TokenScript XML Files} { .xml .XML} }
38 | { {Signed TokenScrpt (TSML) Files} { .tsml TSML } }
39 | { {All Files} * }
40 | }
41 |
42 | if [info exist filename] {
43 | set new_filename [tk_getOpenFile -filetypes $file_types \
44 | -initialdir [file dirname $filename] \
45 | -initialfile [file tail $filename]]
46 | } else {
47 | set new_filename [tk_getOpenFile -filetypes $file_types]
48 | }
49 | if {$new_filename != ""} {
50 | # The user didn't press "cancel"
51 | if [catch {open $new_filename r} fileid] {
52 | # Error opening file -- with "catch", fileid holds error message
53 | tk_messageBox -icon error -message \
54 | "Couldn't open \"$filename\":\n$fileid"
55 | } else {
56 | # OK, didn't cancel, and filename is valid -- save it
57 | set filename $new_filename
58 | doc_load $fileid
59 | close $fileid
60 | wm title . $filename
61 | }
62 | }
63 | }
64 |
65 | # this procedure is called when a file is opened or supplied by commandline
66 | proc doc_load {fileid} {
67 | global NS xp-contract xp-attribute xp-card xp-dataObject xp-localisation xp-selection
68 | set doc [dom parse [read $fileid]]
69 |
70 | $doc selectNodesNamespaces $NS
71 | set root [$doc documentElement]
72 |
73 | set nodes [$root selectNodes {//ts:contract}]
74 | set rows [table .nb.f0.table $nodes {*}${xp-contract}]
75 | .nb tab .nb.f0 -text "Smart Contracts ([llength $rows])"
76 |
77 | set nodes [$root selectNodes {//ts:attribute}]
78 | set rows [table .nb.f1.table $nodes {*}${xp-attribute}]
79 | .nb tab .nb.f1 -text "Token Attributes ([llength $rows])"
80 |
81 | set nodes [$root selectNodes {/ts:token/ts:cards/ts:card}]
82 | set rows [table .nb.f2.table $nodes {*}${xp-card}]
83 | .nb tab .nb.f2 -text "Cards ([llength $rows])"
84 |
85 | set nodes [$root selectNodes {//asnx:module}]
86 | set rows [table .nb.f3.table $nodes {*}${xp-dataObject}]
87 | .nb tab .nb.f3 -text "Data Objects ([llength $rows])"
88 |
89 | set nodes [$root selectNodes {//ts:label}]
90 | set rows [table .nb.f4.table $nodes {*}${xp-localisation}]
91 | .nb tab .nb.f4 -text "Strings ([llength $rows])"
92 |
93 | set nodes [$root selectNodes {//ts:selection}]
94 | set rows [table .nb.f5.table $nodes {*}${xp-selection}]
95 | .nb tab .nb.f5 -text "Selections ([llength $rows])"
96 |
97 | pack .nb.f0.table
98 | pack .nb.f1.table
99 | pack .nb.f2.table
100 | pack .nb.f3.table
101 | pack .nb.f4.table
102 | pack .nb.f5.table
103 | }
104 |
--------------------------------------------------------------------------------
/src/lmap.tcl:
--------------------------------------------------------------------------------
1 | proc lmap args {
2 | set body [lindex $args end]
3 | set args [lrange $args 0 end-1]
4 | set n 0
5 | set pairs [list]
6 | foreach {varname listval} $args {
7 | upvar 1 $varname var$n
8 | lappend pairs var$n $listval
9 | incr n
10 | }
11 | set temp [list]
12 | foreach {*}$pairs {
13 | lappend temp [uplevel 1 $body]
14 | }
15 | set temp
16 | }
17 |
--------------------------------------------------------------------------------
/src/tsviewer.tcl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/tclsh
2 | # this is a -*-Tcl-*- file
3 | package require tdom
4 | package require Tk
5 |
6 | # gives us [table] procedure
7 | source file.tcl
8 |
9 | if {[info commands lmap] != "lmap"} {
10 | source lmap.tcl
11 | }
12 |
13 | set NS {
14 | ts http://tokenscript.org/2020/06/tokenscript
15 | asnx urn:ietf:params:xml:ns:asnx
16 | ethereum urn:ethereum:constantinople
17 | }
18 |
19 | #--------- XPaths applied under each contrainers to get the columns
20 |
21 | set xp-contract {
22 | {interface Interface @interface}
23 | {addresses Addresses string(ts:address)}
24 | }
25 |
26 | set xp-attribute {
27 | {label Label string(ts:label/ts:string/text())}
28 | {syntax Syntax string(ts:type/ts:syntax/text())}
29 | {origins Origins name(ts:origins/*)}
30 | }
31 |
32 | set xp-card {
33 | {type Type @type}
34 | {view-lang View-Languages ts:view/@xml:lang}
35 | }
36 |
37 | set xp-dataObject {
38 | {elements Elements //element/@name}
39 | {references References count(//ethereum:event[@module=$name])}
40 | }
41 |
42 | set xp-localisation {
43 | {parent Of name(..)}
44 | {name Name ../@name}
45 | {l10n Localistaion string(.//ts:string)}
46 | }
47 |
48 | set xp-selection {
49 | {label Label string(ts:label/ts:string/text())}
50 | {filter Filter @filter}
51 | }
52 |
53 | #--------- UI elements that only need to be spawned once ---------
54 |
55 | menu .mbar
56 | . configure -menu .mbar
57 |
58 | menu .mbar.file
59 | .mbar.file add command -label "New" -underline 0
60 | .mbar.file add command -label "Open..." -underline 0 \
61 | -command { filemenu_open }
62 |
63 | .mbar add cascade -menu .mbar.file -label File \
64 | -underline 0
65 |
66 | .mbar.file add command -label Exit -command { exit }
67 |
68 |
69 | pack [ttk::notebook .nb]
70 | .nb add [frame .nb.f0] -text "Contracts"
71 | .nb add [frame .nb.f1] -text "Token Attributes"
72 | .nb add [frame .nb.f2] -text "Cards"
73 | .nb add [frame .nb.f3] -text "Data Objects"
74 | .nb add [frame .nb.f4] -text "Strings"
75 | .nb add [frame .nb.f5] -text "Selection"
76 |
77 | ttk::frame .status
78 | ttk::label .status.filename -textvariable filename
79 |
80 | pack .status.filename
81 | pack .status
82 |
83 | # $doc delete
84 |
85 | #--------- process commandline parameters ---------
86 |
87 |
88 | if { $argc != 1 } {
89 | puts "If you supply a parameter, it will be taken as a TokenScript filename"
90 | puts "For example, try $argv0 examples/ENS/ENS.xml"
91 | } else {
92 | set filename [lindex $argv 0]
93 | set fileid [open $filename r]
94 | doc_load $fileid
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/tokenscriptviewer/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | # We shouldn't even need this, but some sysop just love to include this
3 | nohup.out
4 | # Yes. Surprisingly, this was also commited into git before.
5 | *.p12
6 |
--------------------------------------------------------------------------------
/tokenscriptviewer/README.md:
--------------------------------------------------------------------------------
1 | # TokenscriptViewer
2 | Standalone TokenScript web viewer for reference
3 |
4 | To Run:
5 |
6 | From Intellij GUI:
7 | click 'Gradle'->'tokenscriptviewer'->Tasks->application->bootRun
8 |
9 | From commandline:
10 | > gradlew bootRun
11 |
12 | For default tokenscript demo just go to localhost:8080
13 |
14 | To view different tokens use:
15 |
16 | ```localhost:8080?token=&tokenId=&chainId=```
17 |
18 | EG:
19 |
20 | ```localhost:8080?token=0xec78db1c7244854420a2d8d8d8349c646ac60e06&tokenId=32303234303730363231303030302b30333030010200434e00554b0101000300&chainId=100```
21 |
--------------------------------------------------------------------------------
/tokenscriptviewer/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | dependencies {
6 | classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE")
7 | }
8 | }
9 |
10 | apply plugin: 'java'
11 | apply plugin: 'eclipse'
12 | apply plugin: 'idea'
13 | apply plugin: 'org.springframework.boot'
14 | apply plugin: 'io.spring.dependency-management'
15 |
16 | bootJar {
17 | launchScript()
18 | }
19 |
20 | repositories {
21 | mavenCentral()
22 | }
23 |
24 | sourceCompatibility = 1.8
25 | targetCompatibility = 1.8
26 |
27 | dependencies {
28 | implementation("org.springframework.boot:spring-boot-starter-web")
29 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
30 | implementation('org.web3j:core:4.2.0')
31 |
32 |
33 | // https://mvnrepository.com/artifact/com.github.cliftonlabs/json-simple
34 | implementation group: 'com.github.cliftonlabs', name: 'json-simple', version: '3.1.0'
35 | implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.1.12'
36 | implementation group: 'org.apache.commons', name: 'commons-io', version: '1.3.2'
37 |
38 | testCompile("junit:junit")
39 | implementation project(path: ':lib')
40 | }
41 |
42 | bootRun {
43 | main = 'com.alphawallet.token.web.AppSiteController'
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/java/com/alphawallet/token/web/Ethereum/TransactionHandler.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.web.Ethereum;
2 |
3 | import org.web3j.abi.FunctionEncoder;
4 | import org.web3j.abi.FunctionReturnDecoder;
5 | import org.web3j.abi.TypeReference;
6 | import org.web3j.abi.datatypes.Address;
7 | import org.web3j.abi.datatypes.DynamicArray;
8 | import org.web3j.abi.datatypes.Function;
9 | import org.web3j.abi.datatypes.Type;
10 | import org.web3j.abi.datatypes.Utf8String;
11 | import org.web3j.abi.datatypes.generated.Uint256;
12 | import org.web3j.protocol.Web3j;
13 | import org.web3j.protocol.core.DefaultBlockParameterName;
14 | import org.web3j.protocol.core.methods.request.Transaction;
15 | import org.web3j.protocol.core.methods.response.Web3ClientVersion;
16 | import org.web3j.protocol.http.HttpService;
17 |
18 | import java.math.BigInteger;
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.List;
22 | import java.util.concurrent.ExecutionException;
23 | import java.util.concurrent.TimeUnit;
24 |
25 | import com.alphawallet.token.entity.BadContract;
26 | import com.alphawallet.token.tools.Numeric;
27 | import com.alphawallet.token.web.Service.EthRPCNodes;
28 |
29 | import okhttp3.OkHttpClient;
30 |
31 | public class TransactionHandler
32 | {
33 | private static Web3j mWeb3;
34 |
35 | public TransactionHandler(int networkId)
36 | {
37 | String nodeURL = EthRPCNodes.getNodeURLByNetworkId(networkId);
38 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
39 | builder.connectTimeout(20, TimeUnit.SECONDS);
40 | builder.readTimeout(20, TimeUnit.SECONDS);
41 | HttpService service = new HttpService(nodeURL, builder.build(), false);
42 | mWeb3 = Web3j.build(service);
43 | try
44 | {
45 | Web3ClientVersion web3ClientVersion = mWeb3.web3ClientVersion().sendAsync().get();
46 | System.out.println(web3ClientVersion.getWeb3ClientVersion());
47 | }
48 | catch (Exception e)
49 | {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | public List getBalanceArray(String address, String contractAddress) throws Exception
55 | {
56 | List result = new ArrayList<>();
57 | org.web3j.abi.datatypes.Function function = balanceOfArray(address);
58 | List indices = callSmartContractFunctionArray(function, contractAddress, address);
59 | if (indices == null) throw new BadContract();
60 | for (Uint256 val : indices)
61 | {
62 | result.add(val.getValue());
63 | }
64 | return result;
65 | }
66 |
67 | public String getNameOnly(String address)
68 | {
69 | String name = "";
70 | try
71 | {
72 | name = callSmartContractAndGetResult(address, stringParam("name"));
73 | }
74 | catch (Exception e)
75 | {
76 | e.printStackTrace();
77 | }
78 |
79 | return name;
80 | }
81 |
82 | public String getSymbolOnly(String address)
83 | {
84 | String symbol = "";
85 | try
86 | {
87 | symbol = callSmartContractAndGetResult(address, stringParam("symbol"));
88 | }
89 | catch (Exception e)
90 | {
91 | e.printStackTrace();
92 | }
93 |
94 | return symbol;
95 | }
96 |
97 | public String getName(String address)
98 | {
99 | String name = "";
100 | try
101 | {
102 | name = callSmartContractAndGetResult(address, stringParam("name"));
103 | }
104 | catch (Exception e)
105 | {
106 | e.printStackTrace();
107 | }
108 |
109 | return name;
110 | }
111 |
112 | public String getOwnerOf721(String address, BigInteger tokenId) {
113 | String owner = "";
114 | try
115 | {
116 | owner = Numeric.toHexStringWithPrefix(callSmartContractAndGetResult(address, ownerOf721(tokenId)));
117 | }
118 | catch (Exception e)
119 | {
120 | e.printStackTrace();
121 | }
122 | return owner;
123 | }
124 |
125 | public List getBalanceArray721Tickets(String owner, String contractAddress) {
126 | List castBalances = new ArrayList<>();
127 | try
128 | {
129 | List balances = callSmartContractAndGetResult(contractAddress, getBalances721TicketToken(owner));
130 | for(Uint256 token: balances) {
131 | castBalances.add(token.getValue());
132 | }
133 | }
134 | catch (Exception e)
135 | {
136 | e.printStackTrace();
137 | }
138 | return castBalances;
139 | }
140 |
141 | private T callSmartContractAndGetResult(String address, org.web3j.abi.datatypes.Function function) throws Exception
142 | {
143 | String responseValue = callSmartContractFunction(function, address);
144 |
145 | List response = FunctionReturnDecoder.decode(
146 | responseValue, function.getOutputParameters());
147 | if (response.size() == 1)
148 | {
149 | return (T) response.get(0).getValue();
150 | }
151 | else
152 | {
153 | return null;
154 | }
155 | }
156 |
157 | private String callSmartContractFunction(
158 | Function function, String contractAddress) throws Exception {
159 | String encodedFunction = FunctionEncoder.encode(function);
160 | return makeEthCall(
161 | org.web3j.protocol.core.methods.request.Transaction
162 | .createEthCallTransaction(null, contractAddress, encodedFunction));
163 | }
164 |
165 | private String makeEthCall(Transaction transaction) throws ExecutionException, InterruptedException
166 | {
167 | org.web3j.protocol.core.methods.response.EthCall ethCall = mWeb3.ethCall(transaction,
168 | DefaultBlockParameterName.LATEST)
169 | .sendAsync().get();
170 | return ethCall.getValue();
171 | }
172 |
173 | private List callSmartContractFunctionArray(
174 | org.web3j.abi.datatypes.Function function, String contractAddress, String address) throws Exception
175 | {
176 | String encodedFunction = FunctionEncoder.encode(function);
177 | String value = makeEthCall(
178 | org.web3j.protocol.core.methods.request.Transaction
179 | .createEthCallTransaction(address, contractAddress, encodedFunction));
180 |
181 | List values = FunctionReturnDecoder.decode(value, function.getOutputParameters());
182 | if (values.isEmpty()) return null;
183 |
184 | Type T = values.get(0);
185 | Object o = T.getValue();
186 | return (List) o;
187 | }
188 |
189 | private static org.web3j.abi.datatypes.Function stringParam(String param) {
190 | return new Function(param,
191 | Collections.emptyList(),
192 | Collections.singletonList(new TypeReference() {
193 | }));
194 | }
195 |
196 | private static org.web3j.abi.datatypes.Function balanceOfArray(String owner) {
197 | return new org.web3j.abi.datatypes.Function(
198 | "balanceOf",
199 | Collections.singletonList(new Address(owner)),
200 | Collections.singletonList(new TypeReference>() {}));
201 | }
202 |
203 | private static org.web3j.abi.datatypes.Function getBalances721TicketToken(String owner) {
204 | return new org.web3j.abi.datatypes.Function(
205 | "getBalances",
206 | Collections.singletonList(new Address(owner)),
207 | Collections.singletonList(new TypeReference>() {}));
208 | }
209 |
210 | private static org.web3j.abi.datatypes.Function ownerOf721(BigInteger token) {
211 | return new org.web3j.abi.datatypes.Function(
212 | "ownerOf",
213 | Collections.singletonList(new Uint256(token)),
214 | Collections.singletonList(new TypeReference() {}));
215 | }
216 |
217 | }
218 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/java/com/alphawallet/token/web/Service/CryptoFunctions.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.web.Service;
2 |
3 | import java.math.BigInteger;
4 | import java.security.SignatureException;
5 | import java.util.Arrays;
6 |
7 | import com.alphawallet.token.entity.CryptoFunctionsInterface;
8 | import java.util.Base64;
9 |
10 | import org.web3j.crypto.Keys;
11 | import org.web3j.crypto.Sign;
12 |
13 | public class CryptoFunctions implements CryptoFunctionsInterface
14 | {
15 | @Override
16 | public byte[] Base64Decode(String message)
17 | {
18 | return Base64.getUrlDecoder().decode(message);
19 | }
20 |
21 | @Override
22 | public byte[] Base64Encode(byte[] data)
23 | {
24 | return Base64.getUrlEncoder().encode(data);
25 | }
26 |
27 | @Override
28 | public BigInteger signedMessageToKey(byte[] data, byte[] signature) throws SignatureException
29 | {
30 | Sign.SignatureData sigData = sigFromByteArray(signature);
31 | if (sigData == null) return BigInteger.ZERO;
32 | else return Sign.signedMessageToKey(data, sigData);
33 | }
34 |
35 | @Override
36 | public String getAddressFromKey(BigInteger recoveredKey)
37 | {
38 | return Keys.getAddress(recoveredKey);
39 | }
40 |
41 | public static Sign.SignatureData sigFromByteArray(byte[] sig)
42 | {
43 | if (sig.length < 64 || sig.length > 65) return null;
44 |
45 | byte subv = sig[64];
46 | if (subv < 27) subv += 27;
47 |
48 | byte[] subrRev = Arrays.copyOfRange(sig, 0, 32);
49 | byte[] subsRev = Arrays.copyOfRange(sig, 32, 64);
50 |
51 | BigInteger r = new BigInteger(1, subrRev);
52 | BigInteger s = new BigInteger(1, subsRev);
53 |
54 | return new Sign.SignatureData(subv, subrRev, subsRev);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/java/com/alphawallet/token/web/Service/EthRPCNodes.java:
--------------------------------------------------------------------------------
1 | package com.alphawallet.token.web.Service;
2 |
3 | import static com.alphawallet.token.entity.MagicLinkInfo.ARTIS_SIGMA1_NETWORK_ID;
4 | import static com.alphawallet.token.entity.MagicLinkInfo.ARTIS_TAU1_NETWORK_ID;
5 | import static com.alphawallet.token.entity.MagicLinkInfo.CLASSIC_NETWORK_ID;
6 | import static com.alphawallet.token.entity.MagicLinkInfo.GOERLI_NETWORK_ID;
7 | import static com.alphawallet.token.entity.MagicLinkInfo.KOVAN_NETWORK_ID;
8 | import static com.alphawallet.token.entity.MagicLinkInfo.MAINNET_NETWORK_ID;
9 | import static com.alphawallet.token.entity.MagicLinkInfo.POA_NETWORK_ID;
10 | import static com.alphawallet.token.entity.MagicLinkInfo.RINKEBY_NETWORK_ID;
11 | import static com.alphawallet.token.entity.MagicLinkInfo.ROPSTEN_NETWORK_ID;
12 | import static com.alphawallet.token.entity.MagicLinkInfo.SOKOL_NETWORK_ID;
13 | import static com.alphawallet.token.entity.MagicLinkInfo.XDAI_NETWORK_ID;
14 | import static com.alphawallet.token.web.AppSiteController.getInfuraKey;
15 |
16 | public class EthRPCNodes
17 | {
18 | private static final String MAINNET_RPC_URL = "https://mainnet.infura.io/v3/" + getInfuraKey();
19 | private static final String CLASSIC_RPC_URL = "https://web3.gastracker.io";
20 | private static final String XDAI_RPC_URL = "https://dai.poa.network";
21 | private static final String POA_RPC_URL = "https://core.poa.network/";
22 | private static final String ROPSTEN_RPC_URL = "https://ropsten.infura.io/v3/" + getInfuraKey();
23 | private static final String RINKEBY_RPC_URL = "https://rinkeby.infura.io/v3/" + getInfuraKey();
24 | private static final String KOVAN_RPC_URL = "https://kovan.infura.io/v3/" + getInfuraKey();
25 | private static final String SOKOL_RPC_URL = "https://sokol.poa.network";
26 | private static final String GOERLI_RPC_URL = "https://goerli.infura.io/v3/" + getInfuraKey();
27 | private static final String ARTIS_SIGMA1_RPC_URL = "https://rpc.sigma1.artis.network";
28 | private static final String ARTIS_TAU1_RPC_URL = "https://rpc.tau1.artis.network";
29 |
30 |
31 | public static String getNodeURLByNetworkId(int networkId) {
32 | switch (networkId) {
33 | case MAINNET_NETWORK_ID:
34 | return MAINNET_RPC_URL;
35 | case KOVAN_NETWORK_ID:
36 | return KOVAN_RPC_URL;
37 | case ROPSTEN_NETWORK_ID:
38 | return ROPSTEN_RPC_URL;
39 | case RINKEBY_NETWORK_ID:
40 | return RINKEBY_RPC_URL;
41 | case POA_NETWORK_ID:
42 | return POA_RPC_URL;
43 | case SOKOL_NETWORK_ID:
44 | return SOKOL_RPC_URL;
45 | case CLASSIC_NETWORK_ID:
46 | return CLASSIC_RPC_URL;
47 | case XDAI_NETWORK_ID:
48 | return XDAI_RPC_URL;
49 | case GOERLI_NETWORK_ID:
50 | return GOERLI_RPC_URL;
51 | case ARTIS_SIGMA1_NETWORK_ID:
52 | return ARTIS_SIGMA1_RPC_URL;
53 | case ARTIS_TAU1_NETWORK_ID:
54 | return ARTIS_TAU1_RPC_URL;
55 | default:
56 | return MAINNET_RPC_URL;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | repository.dir=../../TokenScript-Repo
2 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/_error.scss:
--------------------------------------------------------------------------------
1 | // Error page
2 |
3 | .bg-error {
4 | width: 100%;
5 | height: 100%;
6 | background-color: #56C9E9;
7 | background-image: url('');
8 | background-size: cover;
9 | background-position: 50% 50%;
10 | background-repeat: no-repeat;
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/_global.scss:
--------------------------------------------------------------------------------
1 | // Variables
2 |
3 | // Blue and Brand Colors for use across Alpha Wallet theme
4 |
5 | $theme-primary: #54c1e3;
6 |
7 | // Main Typography
8 | $body-font: 'Source Sans Pro', sans-serif;
9 | $body-color: #2f2f2f;
10 |
11 | // Button
12 | $cta-btn: #f9d021;
13 | $green-btn: #75b943;
14 | $red-btn: #E70000;
15 |
16 | html,
17 | body {
18 | width: 100%;
19 | height: 100%;
20 | }
21 |
22 | body {
23 | padding-top: 150px;
24 | font-family: $body-font;
25 | color: $body-color;
26 | }
27 |
28 | h1,
29 | h2,
30 | h3,
31 | h4,
32 | h5,
33 | h6 {
34 | font-family: $body-font;
35 | color: $body-font;
36 | margin-bottom: 1rem;
37 | }
38 |
39 | p {
40 | font-size: 18px;
41 | line-height: 1.5;
42 | margin-bottom: 20px;
43 | }
44 |
45 | section {
46 | padding: 100px 0;
47 | h2 {
48 | font-size: 50px;
49 | }
50 | }
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/_main.scss:
--------------------------------------------------------------------------------
1 | //Styling for main
2 |
3 |
4 | header.main {
5 | position: relative;
6 | width: 100%;
7 | padding-top: 150px;
8 | padding-bottom: 100px;
9 | color: $body-color;
10 | .header-content {
11 | max-width: 300px;
12 | margin-bottom: 100px;
13 | h1 {
14 | font-size: 18px;
15 | }
16 | }
17 | .device-container {
18 | max-width: 325px;
19 | margin-right: auto;
20 | margin-left: auto;
21 | .screen img {
22 | border-radius: 3px;
23 | }
24 | }
25 | @media (min-width: 992px) {
26 | height: 100vh;
27 | min-height: 775px;
28 | padding-top: 0;
29 | padding-bottom: 0;
30 | .header-content {
31 | margin-bottom: 0;
32 |
33 | text-align: left;
34 | h1 {
35 | font-size: 30px;
36 | }
37 | }
38 | .device-container {
39 | max-width: 325px;
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/_steps.scss:
--------------------------------------------------------------------------------
1 | // Steps
2 |
3 | #view_listing a {
4 | background-color: goldenrod;
5 | font-weight: bold;
6 | border-radius: 0.5em;
7 | padding: 1ex 3em;
8 | }
9 |
10 | #view_listing :link {
11 | text-decoration: none;
12 | }
13 | #view_listing :visited {
14 | text-decoration: none;
15 | }
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/_ticket.scss:
--------------------------------------------------------------------------------
1 | // Styling for ticket box
2 |
3 | .ticket-table {
4 | width: 346px;
5 | height: 250px;
6 | border-radius: 20px;
7 | box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.07);
8 | background-color: #ffffff;
9 | border: solid 1px #f3f3f3;
10 | padding: 1.5em; }
11 |
12 | .box-img {
13 | height: 25px;
14 | width: 25px; }
15 |
16 | #status {
17 | font-family: "Source Sans Pro", sans-serif;
18 | font-size: 15px;
19 | font-variant: small-caps;
20 | border-radius: 8px;
21 | padding-left: 1ex;
22 | padding-right: 1ex;
23 | }
24 | .available #status {
25 | background: #75b943;
26 | color: white;
27 | }
28 | .unavailable #status {
29 | background-color: #E70000;
30 | color: white;
31 | }
32 | .expired #status {
33 | background-color: #800000;
34 | color: silver;
35 | }
36 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/_variables.scss:
--------------------------------------------------------------------------------
1 | // Variables
2 |
3 | // Blue and Brand Colors for use across Alpha Wallet theme
4 |
5 | $theme-primary: #54c1e3;
6 |
7 | // Main Typography
8 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700);
9 | $body-font: 'Source Sans Pro', sans-serif;
10 | $body-color: #2f2f2f;
11 |
12 | // Button
13 | $cta-btn: #f9d021;
14 | $green-btn: #75b943;
15 | $red-btn: #E70000;
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/sass/style.scss:
--------------------------------------------------------------------------------
1 | @import "global.scss";
2 | @import "main.scss";
3 | @import "steps.scss";
4 | @import "ticket.scss";
5 | @import "variables.scss";
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/apple-app-site-association:
--------------------------------------------------------------------------------
1 | {
2 | "applinks": {
3 | "apps": [],
4 | "details": [
5 | {
6 | "appID": "LRAW5PL536.com.stormbird.alphawallet",
7 | "paths": [ "*" ]
8 | }
9 | ]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/css/error.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700);
2 | html,
3 | body {
4 | min-height: 100%;}
5 |
6 |
7 | body {
8 | background-image: url('/images/alogobg.png');
9 | background-repeat: no-repeat;
10 | background-position: center;
11 | background-size: 280px;
12 | background-attachment: fixed;
13 | background-color: #56C9E9;
14 | font-family: "Source Sans Pro", sans-serif; }
15 | @media (min-width: 992px) {
16 | body {
17 | background-image: url('/images/alogobg.png');
18 | background-repeat: no-repeat;
19 | background-position: 630px 200px;
20 | background-attachment: fixed;
21 | background-size: 50%;
22 | background-color: #56C9E9; } }
23 |
24 |
25 | header.main {
26 | width: 100%;
27 | color: white; }
28 | header.main .header-content {
29 | margin-top: 50px;
30 | max-width: 100%;
31 | text-align: center; }
32 | header.main .header-content h1 {
33 | font-size: 68px; }
34 | header.main .header-content p {
35 | font-size: 22px; }
36 | @media (min-width: 992px) {
37 | header.main {
38 | width: 100%;
39 | color: white; }
40 | header.main .header-content {
41 | margin-top: 150px;
42 | max-width: 100%;
43 | text-align: center; }
44 | header.main .header-content h1 {
45 | font-size: 72px; }
46 | header.main .header-content p {
47 | font-size: 24px; }
48 | }
49 |
50 |
51 | .footer {
52 | position: absolute;
53 | bottom: 0;
54 | width: 100%;
55 | height: 120px; /* Set the fixed height of the footer here */
56 | }
57 |
58 | .footer p {
59 | font-size: 12px;
60 | margin-left: 2em;
61 | color: white;
62 | }
63 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/css/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/css/logo.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/css/main.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700);
2 | html,
3 | body {
4 | width: 100%;
5 | height: 100%; }
6 |
7 | body {
8 | background-image: url('logo.png');
9 | background-size: 40px 28px;
10 | background-position: 28px 30px;
11 | background-repeat: no-repeat;
12 | padding-top: 100px;
13 | padding-bottom: 0;
14 | font-family: "Source Sans Pro", sans-serif;
15 | color: #2f2f2f; }
16 | @media (min-width: 992px) {
17 | body {
18 | background-image: url('logo.png');
19 | padding-top: 150px;
20 | background-size: 40px 28px;
21 | background-position: 110px 60px;
22 | background-repeat: no-repeat;
23 | padding-bottom: 50px; } }
24 |
25 | h1,
26 | h2,
27 | h3,
28 | h4,
29 | h5,
30 | h6,
31 | p {
32 | font-family: "Source Sans Pro", sans-serif;
33 | color: "Source Sans Pro", sans-serif;
34 | margin-bottom: 1rem; }
35 |
36 |
37 | header.main {
38 | position: relative;
39 | width: 100%;
40 | padding-top: 0px;
41 | padding-bottom: 80px;
42 | color: #2f2f2f; }
43 | header.main .header-content {
44 | max-width: 800px;
45 | margin-bottom: 100px; }
46 | header.main .header-content h1 {
47 | font-size: 18px;
48 | margin: 15px; }
49 | header.main .header-content h2, h3, p {
50 | margin: 15px; }
51 | header.main .header-content h5 {
52 | margin-left: 15px;
53 | font-size: 16px; }
54 | header.main .device-container {
55 | max-width: 325px;
56 | margin-right: auto;
57 | margin-left: 1.8em; }
58 | @media (min-width: 992px) {
59 | header.main {
60 | height: 100vh;
61 | min-height: 775px;
62 | padding-top: 0;
63 | padding-bottom: 0; }
64 | header.main .header-content {
65 | width: 500px;
66 | margin-bottom: 50px;
67 | margin-left: 30px; }
68 | header.main .header-content h1 {
69 | font-size: 30px;
70 | margin: 0; }
71 | header.main .header-content h2, h3, h5, p {
72 | margin: 0; }
73 | header.main .header-content h5 {
74 | margin: 0; }
75 | header.main .device-container {
76 | max-width: 325px;
77 | margin-left: 4em; } }
78 |
79 |
80 | #view_listing a {
81 | background-color: goldenrod;
82 | font-weight: bold;
83 | border-radius: 0.5em;
84 | padding: 1ex 3em; }
85 |
86 | #view_listing :link {
87 | text-decoration: none; }
88 |
89 | #view_listing :visited {
90 | text-decoration: none; }
91 |
92 | .ticket-table {
93 | width: 100%;
94 | border-radius: 20px;
95 | box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.07);
96 | background-color: #ffffff;
97 | border: solid 1px #f3f3f3;
98 | padding: 1em; }
99 | @media (min-width: 992px) {
100 | .ticket-table {
101 | width: 346px;
102 | border-radius: 20px;
103 | box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.07);
104 | background-color: #ffffff;
105 | border: solid 1px #f3f3f3;
106 | padding: 1.5em; } }
107 |
108 | .inline-box {
109 | margin-top: 0;
110 | }
111 | @media (min-width: 992px) {
112 | .inline-box {
113 | margin-top: 1em;
114 | position: relative;
115 | display: inline-block;
116 | } }
117 |
118 |
119 | .box-img {
120 | margin-left: 1em;
121 | height: 16px;
122 | width: 16px; }
123 | @media (min-width: 992px) {
124 | .box-img {
125 | margin-left: 0;
126 | height: 20px;
127 | width: 20px; } }
128 |
129 |
130 |
131 | .device-img {
132 | width: 90%; }
133 | @media (min-width: 992px) {
134 | .device-img {
135 | width: 73%; } }
136 |
137 | #status {
138 | font-family: "Source Sans Pro", sans-serif;
139 | font-size: 15px;
140 | font-variant: small-caps;
141 | border-radius: 8px;
142 | padding-left: 1ex;
143 | padding-right: 1ex; }
144 |
145 | .available #status {
146 | background: #75b943;
147 | color: white; }
148 |
149 | .unavailable #status {
150 | background-color: #E70000;
151 | color: white; }
152 |
153 | .expired #status {
154 | background-color: #800000;
155 | color: silver; }
156 |
157 | .btn-y {
158 | display: inline-block;
159 | background-color: #f9d021;
160 | font-family: "Source Sans Pro", sans-serif;
161 | border-radius: 18.7px;
162 | border: 0;
163 | color: white;
164 | margin-top: none;
165 | margin-right: auto;
166 | margin-left: 15px; }
167 | @media (min-width: 992px) {
168 | .btn-y {
169 | background-color: #f9d021;
170 | font-family: "Source Sans Pro", sans-serif;
171 | border-radius: 18.7px;
172 | border: 0;
173 | color: white;
174 | margin-top: 1em;
175 | margin-right: auto;
176 | margin-left: auto;
177 | } }
178 |
179 |
180 | .btn-xl {
181 | font-size: 16px;
182 | padding: 8px 70px;
183 | font-weight: 300; }
184 | @media (min-width: 992px) {
185 | .btn-xl {
186 | font-size: 16px;
187 | padding: 10px 100px;
188 | font-weight: 300;
189 | } }
190 |
191 | dt, dd {
192 | display: inline;
193 | }
194 | dd {
195 | margin-left: 0em;
196 | padding-left: -1ex;
197 | padding-right: 1em;
198 | }
199 | @media (min-width: 992px) {
200 | dt, dd {
201 | display: inline-block;
202 | }
203 | dd {
204 | margin-left: 0em;
205 | padding-left: 1ex;
206 | padding-right: 1em;
207 | } }
208 |
209 |
210 | .purchase {
211 | font-size: 30px;
212 | font-weight: 300;
213 | font-style: normal;
214 | font-stretch: normal;
215 | line-height: normal;
216 | letter-spacing: normal;
217 | color: #2f2f2f; }
218 | @media (min-width: 992px) {
219 | .purchase {
220 | font-size: 38px;
221 | font-weight: 300;
222 | font-style: normal;
223 | font-stretch: normal;
224 | line-height: normal;
225 | letter-spacing: normal;
226 | color: #2f2f2f;
227 | } }
228 |
229 | .form-control-tx {
230 | display: block;
231 | width: 90%;
232 | padding: .375rem .75rem;
233 | margin: 15px;
234 | font-size: 1rem;
235 | line-height: 1.5;
236 | color: #495057;
237 | background-color: #f0f0f0;
238 | background-clip: padding-box;
239 | border: 1px solid #ced4da;
240 | border-radius: .25rem;
241 | transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; }
242 | @media (min-width: 992px) {
243 | .form-control-tx {
244 | display: block;
245 | margin: 0;
246 | width: 100%;
247 | padding: .375rem .75rem;
248 | font-size: 1rem;
249 | line-height: 1.5;
250 | color: #495057;
251 | background-color: #f0f0f0;
252 | background-clip: padding-box;
253 | border: 1px solid #ced4da;
254 | border-radius: .25rem;
255 | transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
256 | } }
257 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/css/style.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700);
2 | html,
3 | body {
4 | width: 100%;
5 | height: 100%; }
6 |
7 | body {
8 | padding-top: 150px;
9 | font-family: "Source Sans Pro", sans-serif;
10 | color: #2f2f2f; }
11 |
12 | h1,
13 | h2,
14 | h3,
15 | h4,
16 | h5,
17 | h6 {
18 | font-family: "Source Sans Pro", sans-serif;
19 | color: "Source Sans Pro", sans-serif;
20 | margin-bottom: 1rem; }
21 |
22 | p {
23 | font-size: 18px;
24 | line-height: 1.5;
25 | margin-bottom: 20px; }
26 |
27 | section {
28 | padding: 100px 0; }
29 | section h2 {
30 | font-size: 50px; }
31 |
32 | header.main {
33 | position: relative;
34 | width: 100%;
35 | padding-top: 150px;
36 | padding-bottom: 100px;
37 | color: #2f2f2f; }
38 | header.main .header-content {
39 | max-width: 300px;
40 | margin-bottom: 100px; }
41 | header.main .header-content h1 {
42 | font-size: 18px; }
43 | header.main .device-container {
44 | max-width: 325px;
45 | margin-right: auto;
46 | margin-left: auto; }
47 | header.main .device-container .screen img {
48 | border-radius: 3px; }
49 | @media (min-width: 992px) {
50 | header.main {
51 | height: 100vh;
52 | min-height: 775px;
53 | padding-top: 0;
54 | padding-bottom: 0; }
55 | header.main .header-content {
56 | margin-bottom: 0;
57 | text-align: left; }
58 | header.main .header-content h1 {
59 | font-size: 30px; }
60 | header.main .device-container {
61 | max-width: 325px; } }
62 |
63 | #view_listing a {
64 | background-color: goldenrod;
65 | font-weight: bold;
66 | border-radius: 0.5em;
67 | padding: 1ex 3em; }
68 |
69 | #view_listing :link {
70 | text-decoration: none; }
71 |
72 | #view_listing :visited {
73 | text-decoration: none; }
74 |
75 | .ticket-table {
76 | width: 346px;
77 | height: 250px;
78 | border-radius: 20px;
79 | box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.07);
80 | background-color: #ffffff;
81 | border: solid 1px #f3f3f3;
82 | padding: 1.5em; }
83 |
84 | .box-img {
85 | height: 25px;
86 | width: 25px; }
87 |
88 | #status {
89 | font-family: "Source Sans Pro", sans-serif;
90 | font-size: 15px;
91 | font-variant: small-caps;
92 | border-radius: 8px;
93 | padding-left: 1ex;
94 | padding-right: 1ex; }
95 |
96 | .available #status {
97 | background: #75b943;
98 | color: white; }
99 |
100 | .unavailable #status {
101 | background-color: #E70000;
102 | color: white; }
103 |
104 | .expired #status {
105 | background-color: #800000;
106 | color: silver; }
107 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/favicon-16x16.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/favicon-32x32.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/favicon.ico
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/alogobg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/alogobg.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/app_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/app_preview.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/appstore.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/calendar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/calendar.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/category.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/category.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/googleplay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/googleplay.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/static/images/ticket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/tokenscriptviewer/src/main/resources/static/images/ticket.png
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | Error
8 |
9 |
10 |
11 |
12 |
25 |
27 | Path:
28 | Message:
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/templates/token_inject.js.tokenscript:
--------------------------------------------------------------------------------
1 |
25 | %2$s
--------------------------------------------------------------------------------
/tokenscriptviewer/src/main/resources/templates/tokenscriptTemplate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | %3$s
9 |
10 |
--------------------------------------------------------------------------------
/windows-instruction.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlphaWallet/TokenScript-Visualiser/675dcf190d56cf23d94360d54efb574bdcf2462b/windows-instruction.webm
--------------------------------------------------------------------------------