Initial commit
This commit is contained in:
commit
52c2c9db5e
39 changed files with 2603 additions and 0 deletions
77
.gitignore
vendored
Normal file
77
.gitignore
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Mac OS X file
|
||||
.DS_Store
|
||||
|
||||
# Gradle
|
||||
# ------
|
||||
.gradle
|
||||
/build
|
||||
/buildSrc/build
|
||||
/subprojects/*/build
|
||||
/subprojects/docs/src/samples/*/*/build
|
||||
/subprojects/internal-android-performance-testing/build-android-libs
|
||||
|
||||
# IDEA
|
||||
# ----
|
||||
.idea
|
||||
.shelf
|
||||
/*.iml
|
||||
/*.ipr
|
||||
/*.iws
|
||||
/buildSrc/*.iml
|
||||
/buildSrc/*.ipr
|
||||
/buildSrc/*.iws
|
||||
/buildSrc/out
|
||||
/out
|
||||
/subprojects/*/*.iml
|
||||
/subprojects/*/out
|
||||
|
||||
# Eclipse
|
||||
# -------
|
||||
*.classpath
|
||||
*.project
|
||||
*.settings
|
||||
/bin
|
||||
/subprojects/*/bin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# NetBeans
|
||||
# --------
|
||||
.nb-gradle
|
||||
.nb-gradle-properties
|
||||
|
||||
# Vim
|
||||
# ---
|
||||
*.sw[op]
|
||||
|
||||
# Emacs
|
||||
# -----
|
||||
*~
|
||||
|
||||
# Textmate
|
||||
# --------
|
||||
.textmate
|
||||
|
||||
# Sublime Text
|
||||
# ------------
|
||||
*.sublime-*
|
||||
|
||||
# jEnv
|
||||
# ----
|
||||
.java-version
|
||||
|
||||
# OS X
|
||||
# ----
|
||||
.DS_Store
|
||||
|
||||
# HPROF
|
||||
# -----
|
||||
*.hprof
|
||||
|
||||
# Work dirs
|
||||
# ---------
|
||||
/incoming-distributions
|
||||
/intTestHomeDir
|
||||
|
||||
# Logs
|
||||
# ----
|
||||
/*.log
|
15
build.gradle
Normal file
15
build.gradle
Normal file
|
@ -0,0 +1,15 @@
|
|||
group 'com.luminiasoft'
|
||||
version '0.1-SNAPSHOT'
|
||||
|
||||
apply plugin: 'java'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||
compile 'com.neovisionaries:nv-websocket-client:1.30'
|
||||
compile 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
|
||||
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Mon Nov 21 11:33:11 PET 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip
|
164
gradlew
vendored
Executable file
164
gradlew
vendored
Executable file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
gradlew.bat
vendored
Normal file
90
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
3
settings.gradle
Normal file
3
settings.gradle
Normal file
|
@ -0,0 +1,3 @@
|
|||
rootProject.name = 'fullerene'
|
||||
include 'application'
|
||||
|
13
src/main/java/com/luminiasoft/bitshares/Asset.java
Normal file
13
src/main/java/com/luminiasoft/bitshares/Asset.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Asset extends GrapheneObject {
|
||||
|
||||
public Asset(String id) {
|
||||
super(id);
|
||||
}
|
||||
}
|
88
src/main/java/com/luminiasoft/bitshares/AssetAmount.java
Normal file
88
src/main/java/com/luminiasoft/bitshares/AssetAmount.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.*;
|
||||
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/7/16.
|
||||
*/
|
||||
public class AssetAmount implements ByteSerializable, JsonSerializable{
|
||||
/**
|
||||
* Constants used in the JSON serialization procedure.
|
||||
*/
|
||||
public static final String KEY_AMOUNT = "amount";
|
||||
public static final String KEY_ASSET_ID = "asset_id";
|
||||
|
||||
private UnsignedLong amount;
|
||||
private Asset asset;
|
||||
|
||||
public AssetAmount(UnsignedLong amount, Asset asset){
|
||||
this.amount = amount;
|
||||
this.asset = asset;
|
||||
}
|
||||
|
||||
public void setAmount(UnsignedLong amount){
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public UnsignedLong getAmount(){
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public Asset getAsset(){ return this.asset; }
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] serialized = new byte[8 + 1];
|
||||
byte[] amountBytes = this.amount.bigIntegerValue().toByteArray();
|
||||
serialized[serialized.length - 1] = (byte) asset.instance;
|
||||
|
||||
for(int i = 0; i < amountBytes.length; i++)
|
||||
serialized[i] = amountBytes[amountBytes.length - 1 - i];
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetSerializer());
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject toJsonObject() {
|
||||
JsonObject jsonAmount = new JsonObject();
|
||||
jsonAmount.addProperty(KEY_AMOUNT, amount);
|
||||
jsonAmount.addProperty(KEY_ASSET_ID, asset.getObjectId());
|
||||
return jsonAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction.
|
||||
*/
|
||||
public static class AssetSerializer implements JsonSerializer<AssetAmount> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(AssetAmount assetAmount, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty(KEY_AMOUNT, assetAmount.amount);
|
||||
obj.addProperty(KEY_ASSET_ID, assetAmount.asset.getObjectId());
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AssetDeserializer implements JsonDeserializer<AssetAmount> {
|
||||
|
||||
@Override
|
||||
public AssetAmount deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
|
||||
Long amount = json.getAsJsonObject().get(KEY_AMOUNT).getAsLong();
|
||||
String assetId = json.getAsJsonObject().get(KEY_ASSET_ID).getAsString();
|
||||
AssetAmount assetAmount = new AssetAmount(UnsignedLong.valueOf(amount), new Asset(assetId));
|
||||
return assetAmount;
|
||||
}
|
||||
}
|
||||
}
|
20
src/main/java/com/luminiasoft/bitshares/BaseOperation.java
Normal file
20
src/main/java/com/luminiasoft/bitshares/BaseOperation.java
Normal file
|
@ -0,0 +1,20 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/5/16.
|
||||
*/
|
||||
public abstract class BaseOperation implements ByteSerializable, JsonSerializable{
|
||||
|
||||
protected OperationType type;
|
||||
|
||||
public BaseOperation(OperationType type){
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public abstract byte getId();
|
||||
|
||||
public abstract byte[] toBytes();
|
||||
}
|
101
src/main/java/com/luminiasoft/bitshares/BlockData.java
Normal file
101
src/main/java/com/luminiasoft/bitshares/BlockData.java
Normal file
|
@ -0,0 +1,101 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* This class encapsulates all block-related information needed in order to build a valid transaction.
|
||||
*/
|
||||
public class BlockData implements ByteSerializable {
|
||||
private final int REF_BLOCK_NUM_BYTES = 2;
|
||||
private final int REF_BLOCK_PREFIX_BYTES = 4;
|
||||
private final int REF_BLOCK_EXPIRATION_BYTES = 4;
|
||||
|
||||
private int refBlockNum;
|
||||
private long refBlockPrefix;
|
||||
private long relativeExpiration;
|
||||
|
||||
/**
|
||||
* Block data constructor
|
||||
* @param ref_block_num: Least significant 16 bits from the reference block number.
|
||||
* If "relative_expiration" is zero, this field must be zero as well.
|
||||
* @param ref_block_prefix: The first non-block-number 32-bits of the reference block ID.
|
||||
* Recall that block IDs have 32 bits of block number followed by the
|
||||
* actual block hash, so this field should be set using the second 32 bits
|
||||
* in the block_id_type
|
||||
* @param relative_expiration: This field specifies the number of block intervals after the
|
||||
* reference block until this transaction becomes invalid. If this field is
|
||||
* set to zero, the "ref_block_prefix" is interpreted as an absolute timestamp
|
||||
* of the time the transaction becomes invalid.
|
||||
*/
|
||||
public BlockData(int ref_block_num, long ref_block_prefix, long relative_expiration){
|
||||
this.refBlockNum = ref_block_num;
|
||||
this.refBlockPrefix = ref_block_prefix;
|
||||
this.relativeExpiration = relative_expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block data constructor that takes in raw blockchain information.
|
||||
* @param head_block_number: The last block number.
|
||||
* @param head_block_id: The last block apiId.
|
||||
* @param relative_expiration: The relative expiration
|
||||
*/
|
||||
public BlockData(long head_block_number, String head_block_id, long relative_expiration){
|
||||
String hashData = head_block_id.substring(8, 16);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for(int i = 0; i < 8; i = i + 2){
|
||||
builder.append(hashData.substring(6 - i, 8 - i));
|
||||
}
|
||||
this.refBlockNum = ((int) head_block_number ) & 0xFFFF;
|
||||
this.refBlockPrefix = Long.parseLong(builder.toString(), 16);
|
||||
this.relativeExpiration = relative_expiration;
|
||||
}
|
||||
|
||||
public int getRefBlockNum() {
|
||||
return refBlockNum;
|
||||
}
|
||||
|
||||
public void setRefBlockNum(int refBlockNum) {
|
||||
this.refBlockNum = refBlockNum;
|
||||
}
|
||||
|
||||
public long getRefBlockPrefix() {
|
||||
return refBlockPrefix;
|
||||
}
|
||||
|
||||
public void setRefBlockPrefix(long refBlockPrefix) {
|
||||
this.refBlockPrefix = refBlockPrefix;
|
||||
}
|
||||
|
||||
public long getRelativeExpiration() {
|
||||
return relativeExpiration;
|
||||
}
|
||||
|
||||
public void setRelativeExpiration(long relativeExpiration) {
|
||||
this.relativeExpiration = relativeExpiration;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
// Allocating a fixed length byte array, since we will always need
|
||||
// 2 bytes for the ref_block_num value
|
||||
// 4 bytes for the ref_block_prefix value
|
||||
// 4 bytes for the relative_expiration
|
||||
|
||||
byte[] result = new byte[REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES + REF_BLOCK_EXPIRATION_BYTES];
|
||||
for(int i = 0; i < result.length; i++){
|
||||
if(i < REF_BLOCK_NUM_BYTES){
|
||||
result[i] = (byte) (this.refBlockNum >> 8 * i);
|
||||
}else if(i >= REF_BLOCK_NUM_BYTES && i < REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES){
|
||||
result[i] = (byte) (this.refBlockPrefix >> 8 * (i - REF_BLOCK_NUM_BYTES));
|
||||
}else{
|
||||
result[i] = (byte) (this.relativeExpiration >> 8 * (i - REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
31
src/main/java/com/luminiasoft/bitshares/BrainKey.java
Normal file
31
src/main/java/com/luminiasoft/bitshares/BrainKey.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/19/16.
|
||||
*/
|
||||
public class BrainKey {
|
||||
|
||||
private ECKey mPrivateKey;
|
||||
|
||||
public BrainKey(String words, int sequence){
|
||||
String encoded = String.format("%s %d", words, sequence);
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
byte[] bytes = md.digest(encoded.getBytes("UTF-8"));
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
byte[] result = sha256.digest(bytes);
|
||||
System.out.println("hash: "+Util.bytesToHex(result));
|
||||
//TODO: Transform this final result into a ECKey private key (mPrivateKey)
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgotithmException. Msg: "+e.getMessage());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
System.out.println("UnsupportedEncodingException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
16
src/main/java/com/luminiasoft/bitshares/Chains.java
Normal file
16
src/main/java/com/luminiasoft/bitshares/Chains.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/8/16.
|
||||
*/
|
||||
public class Chains {
|
||||
public static class BITSHARES {
|
||||
public static final String CHAIN_ID = "4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8";
|
||||
}
|
||||
public static class GRAPHENE {
|
||||
public static final String CHAIN_ID = "b8d1603965b3eb1acba27e62ff59f74efa3154d43a4188d381088ac7cdf35539";
|
||||
}
|
||||
public static class TEST {
|
||||
public static final String CHAIN_ID = "39f5e2ede1f8bc1a3a54a7914414e3779e33193f1f5693510e73cb7a87617447";
|
||||
}
|
||||
}
|
8
src/main/java/com/luminiasoft/bitshares/Extension.java
Normal file
8
src/main/java/com/luminiasoft/bitshares/Extension.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Extension {
|
||||
//TODO: Give this class a proper implementation
|
||||
}
|
33
src/main/java/com/luminiasoft/bitshares/GrapheneObject.java
Normal file
33
src/main/java/com/luminiasoft/bitshares/GrapheneObject.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Generic class used to represent a graphene object as defined in
|
||||
* <a href="http://docs.bitshares.org/development/blockchain/objects.html"></a>
|
||||
* </p>
|
||||
* Created by nelson on 11/8/16.
|
||||
*/
|
||||
public class GrapheneObject {
|
||||
protected int space;
|
||||
protected int type;
|
||||
protected long instance;
|
||||
|
||||
public GrapheneObject(String id){
|
||||
String[] parts = id.split("\\.");
|
||||
if(parts.length == 3){
|
||||
this.space = Integer.parseInt(parts[0]);
|
||||
this.type = Integer.parseInt(parts[1]);
|
||||
this.instance = Long.parseLong(parts[2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return: A String containing the full object apiId in the form {space}.{type}.{instance}
|
||||
*/
|
||||
public String getObjectId(){
|
||||
return String.format("%d.%d.%d", space, type, instance);
|
||||
}
|
||||
}
|
64
src/main/java/com/luminiasoft/bitshares/Main.java
Normal file
64
src/main/java/com/luminiasoft/bitshares/Main.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Main {
|
||||
// Brain key from Nelson's app referencing the bilthon-83 account
|
||||
public static final String BRAIN_KEY = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH";
|
||||
|
||||
// WIF from Nelson's app referencing the bilthon-83 account
|
||||
public static final String WIF = "5J96pne45qWM1WpektoeazN6k9Mt93jQ7LyueRxFfEMTiy6yxjM";
|
||||
|
||||
// WIF from the cli_wallet instance
|
||||
// public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5";
|
||||
public static final String EXTERNAL_SIGNATURE = "1f36c41acb774fcbc9c231b5895ec9701d6872729098d8ea56d78dda72a6b54252694db85d7591de5751b7aea06871da15d63a1028758421607ffc143e53ef3306";
|
||||
|
||||
// Static block information used for transaction serialization tests
|
||||
public static int REF_BLOCK_NUM = 56204;
|
||||
public static int REF_BLOCK_PREFIX = 1614747814;
|
||||
public static int RELATIVE_EXPIRATION = 1478385607;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Test test = new Test();
|
||||
// test.testTransactionSerialization();
|
||||
// ECKey.ECDSASignature signature = test.testSigning();
|
||||
|
||||
// try {
|
||||
// test.testWebSocketTransfer();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
// test.testCustomSerializer();
|
||||
|
||||
// test.testTransactionSerialization();
|
||||
|
||||
// test.testLoginSerialization();
|
||||
|
||||
// test.testNetworkBroadcastSerialization();
|
||||
|
||||
// test.testNetworkBroadcastDeserialization();
|
||||
|
||||
// test.testGetDynamicParams();
|
||||
|
||||
// test.testGetRequiredFeesSerialization();
|
||||
|
||||
// test.testRequiredFeesResponse();
|
||||
|
||||
// test.testTransactionBroadcastSequence();
|
||||
|
||||
// test.testAccountLookupDeserialization();
|
||||
|
||||
// test.testPrivateKeyManipulations();
|
||||
|
||||
// test.testGetAccountByName();
|
||||
|
||||
// test.testGetRequiredFees();
|
||||
|
||||
// test.testRandomNumberGeneration();
|
||||
|
||||
test.testBrainKeyOperations();
|
||||
}
|
||||
}
|
15
src/main/java/com/luminiasoft/bitshares/Memo.java
Normal file
15
src/main/java/com/luminiasoft/bitshares/Memo.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Memo implements ByteSerializable {
|
||||
//TODO: Give this class a proper implementation
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return new byte[1];
|
||||
}
|
||||
}
|
52
src/main/java/com/luminiasoft/bitshares/OperationType.java
Normal file
52
src/main/java/com/luminiasoft/bitshares/OperationType.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/6/16.
|
||||
*/
|
||||
public enum OperationType {
|
||||
transfer_operation,
|
||||
limit_order_create_operation,
|
||||
limit_order_cancel_operation,
|
||||
call_order_update_operation,
|
||||
fill_order_operation, // VIRTUAL
|
||||
account_create_operation,
|
||||
account_update_operation,
|
||||
account_whitelist_operation,
|
||||
account_upgrade_operation,
|
||||
account_transfer_operation,
|
||||
asset_create_operation,
|
||||
asset_update_operation,
|
||||
asset_update_bitasset_operation,
|
||||
asset_update_feed_producers_operation,
|
||||
asset_issue_operation,
|
||||
asset_reserve_operation,
|
||||
asset_fund_fee_pool_operation,
|
||||
asset_settle_operation,
|
||||
asset_global_settle_operation,
|
||||
asset_publish_feed_operation,
|
||||
witness_create_operation,
|
||||
witness_update_operation,
|
||||
proposal_create_operation,
|
||||
proposal_update_operation,
|
||||
proposal_delete_operation,
|
||||
withdraw_permission_create_operation,
|
||||
withdraw_permission_update_operation,
|
||||
withdraw_permission_claim_operation,
|
||||
withdraw_permission_delete_operation,
|
||||
committee_member_create_operation,
|
||||
committee_member_update_operation,
|
||||
committee_member_update_global_parameters_operation,
|
||||
vesting_balance_create_operation,
|
||||
vesting_balance_withdraw_operation,
|
||||
worker_create_operation,
|
||||
custom_operation,
|
||||
assert_operation,
|
||||
balance_claim_operation,
|
||||
override_transfer_operation,
|
||||
transfer_to_blind_operation,
|
||||
blind_transfer_operation,
|
||||
transfer_from_blind_operation,
|
||||
asset_settle_cancel_operation, // VIRTUAL
|
||||
asset_claim_fees_operation,
|
||||
fba_distribute_operation // VIRTUAL
|
||||
}
|
13
src/main/java/com/luminiasoft/bitshares/RPC.java
Normal file
13
src/main/java/com/luminiasoft/bitshares/RPC.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/16/16.
|
||||
*/
|
||||
public class RPC {
|
||||
public static final String CALL_LOGIN = "login";
|
||||
public static final String CALL_NETWORK_BROADCAST = "network_broadcast";
|
||||
public static final String CALL_GET_ACCOUNT_BY_NAME = "get_account_by_name";
|
||||
public static final String CALL_GET_DYNAMIC_GLOBAL_PROPERTIES = "get_dynamic_global_properties";
|
||||
public static final String CALL_BROADCAST_TRANSACTION = "broadcast_transaction";
|
||||
public static final String CALL_GET_REQUIRED_FEES = "get_required_fees";
|
||||
}
|
509
src/main/java/com/luminiasoft/bitshares/Test.java
Normal file
509
src/main/java/com/luminiasoft/bitshares/Test.java
Normal file
|
@ -0,0 +1,509 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
|
||||
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
||||
import com.luminiasoft.bitshares.models.*;
|
||||
import com.luminiasoft.bitshares.ws.GetAccountByName;
|
||||
import com.luminiasoft.bitshares.ws.GetRequiredFees;
|
||||
import com.luminiasoft.bitshares.ws.TransactionBroadcastSequence;
|
||||
import com.neovisionaries.ws.client.*;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.SHA512Digest;
|
||||
import org.spongycastle.crypto.prng.DigestRandomGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Test {
|
||||
public static final String WITNESS_URL = "ws://api.devling.xyz:8088";
|
||||
private Transaction transaction;
|
||||
|
||||
public Transaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
|
||||
private WitnessResponseListener mListener = new WitnessResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(WitnessResponse response) {
|
||||
if(response.result.getClass() == AccountProperties.class){
|
||||
AccountProperties accountProperties = (AccountProperties) response.result;
|
||||
System.out.println("Got account properties");
|
||||
System.out.println("id: "+accountProperties.id);
|
||||
}else if(response.result.getClass() == ArrayList.class){
|
||||
List l = (List) response.result;
|
||||
if(l.size() > 0){
|
||||
if(l.get(0).getClass() == AssetAmount.class){
|
||||
AssetAmount assetAmount = (AssetAmount) l.get(0);
|
||||
System.out.println("Got fee");
|
||||
System.out.println("amount: "+assetAmount.getAmount()+", asset id: "+assetAmount.getAsset().getObjectId());
|
||||
}
|
||||
}else{
|
||||
System.out.println("Got empty list!");
|
||||
}
|
||||
}else{
|
||||
System.out.println("Got other: "+response.result.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(BaseResponse.Error error) {
|
||||
System.out.println("onError. message: "+error.message);
|
||||
}
|
||||
};
|
||||
|
||||
public ECKey.ECDSASignature testSigning() {
|
||||
byte[] serializedTransaction = this.transaction.toBytes();
|
||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||
byte[] bytesDigest = hash.getBytes();
|
||||
ECKey sk = transaction.getPrivateKey();
|
||||
ECKey.ECDSASignature signature = sk.sign(hash);
|
||||
return signature;
|
||||
}
|
||||
|
||||
public String testSigningMessage() {
|
||||
byte[] serializedTransaction = this.transaction.toBytes();
|
||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||
ECKey sk = transaction.getPrivateKey();
|
||||
return sk.signMessage(hash.toString());
|
||||
}
|
||||
|
||||
public byte[] signMessage() {
|
||||
byte[] serializedTransaction = this.transaction.toBytes();
|
||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||
System.out.println(">> digest <<");
|
||||
System.out.println(Util.bytesToHex(hash.getBytes()));
|
||||
ECKey sk = transaction.getPrivateKey();
|
||||
System.out.println("Private key bytes");
|
||||
System.out.println(Util.bytesToHex(sk.getPrivKeyBytes()));
|
||||
boolean isCanonical = false;
|
||||
int recId = -1;
|
||||
ECKey.ECDSASignature sig = null;
|
||||
while (!isCanonical) {
|
||||
sig = sk.sign(hash);
|
||||
if (!sig.isCanonical()) {
|
||||
System.out.println("Signature was not canonical, retrying");
|
||||
continue;
|
||||
} else {
|
||||
System.out.println("Signature is canonical");
|
||||
isCanonical = true;
|
||||
}
|
||||
// Now we have to work backwards to figure out the recId needed to recover the signature.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ECKey k = ECKey.recoverFromSignature(i, sig, hash, sk.isCompressed());
|
||||
if (k != null && k.getPubKeyPoint().equals(sk.getPubKeyPoint())) {
|
||||
recId = i;
|
||||
break;
|
||||
} else {
|
||||
if (k == null) {
|
||||
System.out.println("Recovered key was null");
|
||||
}
|
||||
if (k.getPubKeyPoint().equals(sk.getPubKeyPoint())) {
|
||||
System.out.println("Recovered pub point is not equal to sk pub point");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (recId == -1)
|
||||
throw new RuntimeException("Could not construct a recoverable key. This should never happen.");
|
||||
}
|
||||
int headerByte = recId + 27 + (sk.isCompressed() ? 4 : 0);
|
||||
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
|
||||
sigData[0] = (byte) headerByte;
|
||||
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
|
||||
System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32);
|
||||
System.out.println("recId: " + recId);
|
||||
System.out.println("r: " + Util.bytesToHex(sig.r.toByteArray()));
|
||||
System.out.println("s: " + Util.bytesToHex(sig.s.toByteArray()));
|
||||
return sigData;
|
||||
// return new String(Base64.encode(sigData), Charset.forName("UTF-8"));
|
||||
}
|
||||
|
||||
public byte[] testTransactionSerialization(long head_block_number, String head_block_id, long relative_expiration) {
|
||||
BlockData blockData = new BlockData(head_block_number, head_block_id, relative_expiration);
|
||||
|
||||
ArrayList<BaseOperation> operations = new ArrayList<BaseOperation>();
|
||||
UserAccount from = new UserAccount("1.2.138632");
|
||||
UserAccount to = new UserAccount("1.2.129848");
|
||||
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
|
||||
operations.add(new Transfer(from, to, amount, fee));
|
||||
this.transaction = new Transaction(Main.WIF, blockData, operations);
|
||||
byte[] serializedTransaction = this.transaction.toBytes();
|
||||
System.out.println("Serialized transaction");
|
||||
System.out.println(Util.bytesToHex(serializedTransaction));
|
||||
return serializedTransaction;
|
||||
}
|
||||
|
||||
public void testWebSocketTransfer() throws IOException {
|
||||
String login = "{\"id\":%d,\"method\":\"call\",\"params\":[1,\"login\",[\"\",\"\"]]}";
|
||||
String getDatabaseId = "{\"method\": \"call\", \"params\": [1, \"database\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
|
||||
String getHistoryId = "{\"method\": \"call\", \"params\": [1, \"history\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
|
||||
String getNetworkBroadcastId = "{\"method\": \"call\", \"params\": [1, \"network_broadcast\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
|
||||
String getDynamicParameters = "{\"method\": \"call\", \"params\": [0, \"get_dynamic_global_properties\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
|
||||
String rawPayload = "{\"method\": \"call\", \"params\": [%d, \"broadcast_transaction\", [{\"expiration\": \"%s\", \"signatures\": [\"%s\"], \"operations\": [[0, {\"fee\": {\"amount\": 264174, \"asset_id\": \"1.3.0\"}, \"amount\": {\"amount\": 100, \"asset_id\": \"1.3.120\"}, \"to\": \"1.2.129848\", \"extensions\": [], \"from\": \"1.2.138632\"}]], \"ref_block_num\": %d, \"extensions\": [], \"ref_block_prefix\": %d}]], \"jsonrpc\": \"2.0\", \"id\": %d}";
|
||||
|
||||
// String url = "wss://bitshares.openledger.info/ws";
|
||||
String url = "ws://api.devling.xyz:8088";
|
||||
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
|
||||
|
||||
// Create a WebSocket. The timeout value set above is used.
|
||||
WebSocket ws = factory.createSocket(url);
|
||||
|
||||
ws.addListener(new WebSocketAdapter() {
|
||||
|
||||
private DynamicGlobalProperties dynProperties;
|
||||
private int networkBroadcastApiId;
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
System.out.println("onConnected");
|
||||
String payload = String.format(login, 1);
|
||||
System.out.println(">>");
|
||||
System.out.println(payload);
|
||||
websocket.sendText(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception {
|
||||
System.out.println("onDisconnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
System.out.println("<<");
|
||||
String response = frame.getPayloadText();
|
||||
System.out.println(response);
|
||||
Gson gson = new Gson();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
// if(baseResponse.id.equals("1")){
|
||||
// String payload = String.format(getDatabaseId, 2);
|
||||
// System.out.println(">>");
|
||||
// System.out.println(payload);
|
||||
// websocket.sendText(payload);
|
||||
// }else if(baseResponse.id.equals("2")){
|
||||
// String payload = String.format(getHistoryId, 3);
|
||||
// System.out.println(">>");
|
||||
// System.out.println(payload);
|
||||
// websocket.sendText(payload);
|
||||
// }else if(baseResponse.id.equals("3")){
|
||||
if (baseResponse.id == 1) {
|
||||
String payload = String.format(getNetworkBroadcastId, 2);
|
||||
System.out.println(">>");
|
||||
System.out.println(payload);
|
||||
websocket.sendText(payload);
|
||||
// }else if(baseResponse.id.equals("4")){
|
||||
}
|
||||
if (baseResponse.id == 2) {
|
||||
String payload = String.format(getDynamicParameters, 3);
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
networkBroadcastApiId = witnessResponse.result.intValue();
|
||||
System.out.println(">>");
|
||||
System.out.println(payload);
|
||||
websocket.sendText(payload);
|
||||
} else if (baseResponse.id == 3) {
|
||||
// Got dynamic properties
|
||||
Type DynamicGlobalPropertiesResponse = new TypeToken<WitnessResponse<DynamicGlobalProperties>>() {
|
||||
}.getType();
|
||||
WitnessResponse<DynamicGlobalProperties> witnessResponse = gson.fromJson(response, DynamicGlobalPropertiesResponse);
|
||||
dynProperties = witnessResponse.result;
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Date date = dateFormat.parse(dynProperties.time);
|
||||
long expirationTime = (date.getTime() / 1000) + 30;
|
||||
testTransactionSerialization(dynProperties.head_block_number, dynProperties.head_block_id, expirationTime);
|
||||
|
||||
BlockData blockData = new BlockData(dynProperties.head_block_number, dynProperties.head_block_id, expirationTime);
|
||||
byte[] signatureBytes = signMessage();
|
||||
|
||||
String payload = String.format(
|
||||
rawPayload,
|
||||
networkBroadcastApiId,
|
||||
dateFormat.format(new Date(expirationTime * 1000)),
|
||||
Util.bytesToHex(signatureBytes),
|
||||
blockData.getRefBlockNum(),
|
||||
blockData.getRefBlockPrefix(),
|
||||
4);
|
||||
System.out.println(">>");
|
||||
System.out.println(payload);
|
||||
websocket.sendText(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
System.out.println("onError");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnexpectedError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
System.out.println("onUnexpectedError");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
System.out.println("handleCallbackError. Msg: " + cause.getMessage());
|
||||
StackTraceElement[] stackTrace = cause.getStackTrace();
|
||||
for (StackTraceElement line : stackTrace) {
|
||||
System.out.println(line.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
// Connect to the server and perform an opening handshake.
|
||||
// This method blocks until the opening handshake is finished.
|
||||
ws.connect();
|
||||
} catch (OpeningHandshakeException e) {
|
||||
// A violation against the WebSocket protocol was detected
|
||||
// during the opening handshake.
|
||||
System.out.println("OpeningHandshakeException");
|
||||
} catch (WebSocketException e) {
|
||||
// Failed to establish a WebSocket connection.
|
||||
System.out.println("WebSocketException. Msg: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomSerializer() {
|
||||
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
||||
String jsonAmount = amount.toJsonString();
|
||||
System.out.println("JSON amount");
|
||||
System.out.println(jsonAmount);
|
||||
}
|
||||
|
||||
public void testTransactionSerialization() {
|
||||
try {
|
||||
Transaction transaction = new TransferTransactionBuilder()
|
||||
.setSource(new UserAccount("1.2.138632"))
|
||||
.setDestination(new UserAccount("1.2.129848"))
|
||||
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
|
||||
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0")))
|
||||
.setBlockData(new BlockData(43408, 1430521623, 1479231969))
|
||||
.setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey())
|
||||
.build();
|
||||
|
||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||
transactionList.add(transaction);
|
||||
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
|
||||
String jsonCall = call.toJsonString();
|
||||
System.out.println("json call");
|
||||
System.out.println(jsonCall);
|
||||
} catch (MalformedTransactionException e) {
|
||||
System.out.println("MalformedTransactionException. Msg: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testLoginSerialization() {
|
||||
ArrayList<Serializable> loginParams = new ArrayList<>();
|
||||
// loginParams.add("nelson");
|
||||
// loginParams.add("supersecret");
|
||||
loginParams.add(null);
|
||||
loginParams.add(null);
|
||||
ApiCall loginCall = new ApiCall(1, "login", loginParams, "2.0", 1);
|
||||
String jsonLoginCall = loginCall.toJsonString();
|
||||
System.out.println("login call");
|
||||
System.out.println(jsonLoginCall);
|
||||
}
|
||||
|
||||
public void testNetworkBroadcastSerialization() {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ApiCall networkParamsCall = new ApiCall(3, "network_broadcast", params, "2.0", 1);
|
||||
String call = networkParamsCall.toJsonString();
|
||||
System.out.println("network broadcast");
|
||||
System.out.println(call);
|
||||
}
|
||||
|
||||
public void testNetworkBroadcastDeserialization(){
|
||||
String response = "{\"id\":2,\"result\":2}";
|
||||
Gson gson = new Gson();
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
}
|
||||
|
||||
public void testGetDynamicParams() {
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
ApiCall getDynamicParametersCall = new ApiCall(0, "get_dynamic_global_properties", emptyParams, "2.0", 0);
|
||||
System.out.println(getDynamicParametersCall.toJsonString());
|
||||
}
|
||||
|
||||
public void testRequiredFeesResponse() {
|
||||
String response = "{\"id\":1,\"result\":[{\"amount\":264174,\"asset_id\":\"1.3.0\"}]}";
|
||||
Type AccountLookupResponse = new TypeToken<WitnessResponse<List<AssetAmount>>>() {}.getType();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer());
|
||||
WitnessResponse<List<AssetAmount>> witnessResponse = gsonBuilder.create().fromJson(response, AccountLookupResponse);
|
||||
for(AssetAmount assetAmount : witnessResponse.result){
|
||||
System.out.println("asset : "+assetAmount.toJsonString());
|
||||
}
|
||||
}
|
||||
|
||||
public void testTransactionBroadcastSequence(){
|
||||
String url = "ws://api.devling.xyz:8088";
|
||||
WitnessResponseListener listener = new WitnessResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(WitnessResponse response) {
|
||||
System.out.println("onSuccess");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(BaseResponse.Error error) {
|
||||
System.out.println("onError");
|
||||
System.out.println(error.data.message);
|
||||
}
|
||||
};
|
||||
|
||||
try{
|
||||
Transaction transaction = new TransferTransactionBuilder()
|
||||
.setSource(new UserAccount("1.2.138632"))
|
||||
.setDestination(new UserAccount("1.2.129848"))
|
||||
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
|
||||
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0")))
|
||||
.setBlockData(new BlockData(43408, 1430521623, 1479231969))
|
||||
.setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey())
|
||||
.build();
|
||||
|
||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||
transactionList.add(transaction);
|
||||
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
|
||||
|
||||
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
|
||||
try {
|
||||
WebSocket mWebSocket = factory.createSocket(url);
|
||||
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener));
|
||||
mWebSocket.connect();
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException. Msg: "+e.getMessage());
|
||||
} catch (WebSocketException e) {
|
||||
System.out.println("WebSocketException. Msg: "+e.getMessage());
|
||||
}
|
||||
}catch(MalformedTransactionException e){
|
||||
System.out.println("MalformedTransactionException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testAccountLookupDeserialization(){
|
||||
String response = "{\"id\":1,\"result\":[[\"ken\",\"1.2.3111\"],[\"ken-1\",\"1.2.101491\"],[\"ken-k\",\"1.2.108646\"]]}";
|
||||
Type AccountLookupResponse = new TypeToken<WitnessResponse<List<List<String>>>>() {}.getType();
|
||||
Gson gson = new Gson();
|
||||
WitnessResponse<List<List<String>>> witnessResponse = gson.fromJson(response, AccountLookupResponse);
|
||||
for(int i = 0; i < witnessResponse.result.size(); i++){
|
||||
System.out.println("suggested name: "+witnessResponse.result.get(i).get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrivateKeyManipulations(){
|
||||
ECKey privateKey = DumpedPrivateKey.fromBase58(null, Main.WIF).getKey();
|
||||
System.out.println("private key..............: "+Util.bytesToHex(privateKey.getSecretBytes()));
|
||||
System.out.println("public key uncompressed..: "+Util.bytesToHex(privateKey.getPubKey()));
|
||||
System.out.println("public key compressed....: "+Util.bytesToHex(privateKey.getPubKeyPoint().getEncoded(true)));
|
||||
System.out.println("base58...................: "+Base58.encode(privateKey.getPubKeyPoint().getEncoded(true)));
|
||||
System.out.println("base58...................: "+Base58.encode(privateKey.getPubKey()));
|
||||
String brainKeyWords = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH";
|
||||
BrainKey brainKey = new BrainKey(brainKeyWords, 0);
|
||||
}
|
||||
|
||||
public void testGetAccountByName(){
|
||||
try {
|
||||
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
|
||||
WebSocket mWebSocket = factory.createSocket(WITNESS_URL);
|
||||
mWebSocket.addListener(new GetAccountByName("bilthon-83", mListener));
|
||||
mWebSocket.connect();
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException. Msg: "+e.getMessage());
|
||||
} catch (WebSocketException e) {
|
||||
System.out.println("WebSocketException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetRequiredFees() {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
Asset asset = new Asset("1.3.0");
|
||||
UserAccount from = new UserAccount("1.2.138632");
|
||||
UserAccount to = new UserAccount("1.2.129848");
|
||||
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
|
||||
Transfer transfer = new Transfer(from, to, amount, fee);
|
||||
ArrayList<BaseOperation> operations = new ArrayList<>();
|
||||
operations.add(transfer);
|
||||
|
||||
accountParams.add(operations);
|
||||
accountParams.add(asset.getObjectId());
|
||||
|
||||
try {
|
||||
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
|
||||
WebSocket mWebSocket = factory.createSocket(WITNESS_URL);
|
||||
mWebSocket.addListener(new GetRequiredFees(operations, asset, mListener));
|
||||
mWebSocket.connect();
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException. Msg: "+e.getMessage());
|
||||
} catch (WebSocketException e) {
|
||||
System.out.println("WebSocketException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testRandomNumberGeneration(){
|
||||
byte[] seed = new byte[] { new Long(System.nanoTime()).byteValue() };
|
||||
doCountTest(new SHA512Digest(), seed);
|
||||
}
|
||||
|
||||
private void doCountTest(Digest digest, byte[] seed)//, byte[] expectedXors)
|
||||
{
|
||||
DigestRandomGenerator generator = new DigestRandomGenerator(digest);
|
||||
byte[] output = new byte[digest.getDigestSize()];
|
||||
int[] averages = new int[digest.getDigestSize()];
|
||||
byte[] ands = new byte[digest.getDigestSize()];
|
||||
byte[] xors = new byte[digest.getDigestSize()];
|
||||
byte[] ors = new byte[digest.getDigestSize()];
|
||||
|
||||
generator.addSeedMaterial(seed);
|
||||
|
||||
for (int i = 0; i != 1000000; i++)
|
||||
{
|
||||
generator.nextBytes(output);
|
||||
for (int j = 0; j != output.length; j++)
|
||||
{
|
||||
averages[j] += output[j] & 0xff;
|
||||
ands[j] &= output[j];
|
||||
xors[j] ^= output[j];
|
||||
ors[j] |= output[j];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i != output.length; i++) {
|
||||
if ((averages[i] / 1000000) != 127) {
|
||||
System.out.println("average test failed for " + digest.getAlgorithmName());
|
||||
}
|
||||
System.out.println("averages["+i+"] / 1000000: "+averages[i] / 1000000);
|
||||
if (ands[i] != 0) {
|
||||
System.out.println("and test failed for " + digest.getAlgorithmName());
|
||||
}
|
||||
if ((ors[i] & 0xff) != 0xff) {
|
||||
System.out.println("or test failed for " + digest.getAlgorithmName());
|
||||
}
|
||||
// if (xors[i] != expectedXors[i]) {
|
||||
// System.out.println("xor test failed for " + digest.getAlgorithmName());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The final purpose of this test is to convert the plain brainkey at Main.BRAIN_KEY
|
||||
* into the WIF at Main.WIF
|
||||
*/
|
||||
public void testBrainKeyOperations(){
|
||||
BrainKey brainKey = new BrainKey(Main.BRAIN_KEY, 0);
|
||||
}
|
||||
|
||||
}
|
197
src/main/java/com/luminiasoft/bitshares/Transaction.java
Normal file
197
src/main/java/com/luminiasoft/bitshares/Transaction.java
Normal file
|
@ -0,0 +1,197 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||
|
||||
import org.bitcoinj.core.DumpedPrivateKey;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Class used to represent a generic graphene transaction.
|
||||
*/
|
||||
public class Transaction implements ByteSerializable, JsonSerializable {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
public static final String KEY_EXPIRATION = "expiration";
|
||||
public static final String KEY_SIGNATURES = "signatures";
|
||||
public static final String KEY_OPERATIONS = "operations";
|
||||
public static final String KEY_EXTENSIONS = "extensions";
|
||||
public static final String KEY_REF_BLOCK_NUM = "ref_block_num";
|
||||
public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix";
|
||||
|
||||
private ECKey privateKey;
|
||||
private BlockData blockData;
|
||||
private List<BaseOperation> operations;
|
||||
private List<Extension> extensions;
|
||||
|
||||
/**
|
||||
* Transaction constructor.
|
||||
* @param wif: The user's private key in the base58 format.
|
||||
* @param block_data: Block data containing important information used to sign a transaction.
|
||||
* @param operation_list: List of operations to include in the transaction.
|
||||
*/
|
||||
public Transaction(String wif, BlockData block_data, List<BaseOperation> operation_list){
|
||||
this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey();
|
||||
this.blockData = block_data;
|
||||
this.operations = operation_list;
|
||||
this.extensions = new ArrayList<Extension>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction constructor.
|
||||
* @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction.
|
||||
* @param blockData : Block data containing important information used to sign a transaction.
|
||||
* @param operationList : List of operations to include in the transaction.
|
||||
*/
|
||||
public Transaction(ECKey privateKey, BlockData blockData, List<BaseOperation> operationList){
|
||||
this.privateKey = privateKey;
|
||||
this.blockData = blockData;
|
||||
this.operations = operationList;
|
||||
this.extensions = new ArrayList<Extension>();
|
||||
}
|
||||
|
||||
public ECKey getPrivateKey(){
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
public List<BaseOperation> getOperations(){ return this.operations; }
|
||||
|
||||
/**
|
||||
* Obtains a signature of this transaction.
|
||||
* @return: A valid signature of the current transaction.
|
||||
*/
|
||||
public byte[] getSignature(){
|
||||
byte[] serializedTransaction = this.toBytes();
|
||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||
boolean isCanonical = false;
|
||||
int recId = -1;
|
||||
ECKey.ECDSASignature sig = null;
|
||||
while(!isCanonical) {
|
||||
sig = privateKey.sign(hash);
|
||||
if(!sig.isCanonical()){
|
||||
// Signature was not canonical, retrying
|
||||
continue;
|
||||
}else{
|
||||
// Signature is canonical
|
||||
isCanonical = true;
|
||||
}
|
||||
// Now we have to work backwards to figure out the recId needed to recover the signature.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ECKey k = ECKey.recoverFromSignature(i, sig, hash, privateKey.isCompressed());
|
||||
if (k != null && k.getPubKeyPoint().equals(privateKey.getPubKeyPoint())) {
|
||||
recId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0);
|
||||
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
|
||||
sigData[0] = (byte)headerByte;
|
||||
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
|
||||
System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32);
|
||||
return sigData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that creates a serialized byte array with compact information about this transaction
|
||||
* that is needed for the creation of a signature.
|
||||
* @return: byte array with serialized information about this transaction.
|
||||
*/
|
||||
public byte[] toBytes(){
|
||||
// Creating a List of Bytes and adding the first bytes from the chain apiId
|
||||
List<Byte> byteArray = new ArrayList<Byte>();
|
||||
byteArray.addAll(Bytes.asList(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID)));
|
||||
|
||||
// Adding the block data
|
||||
byteArray.addAll(Bytes.asList(this.blockData.toBytes()));
|
||||
|
||||
// Adding the number of operations
|
||||
byteArray.add((byte) this.operations.size());
|
||||
|
||||
// Adding all the operations
|
||||
for(BaseOperation operation : operations){
|
||||
byteArray.add(operation.getId());
|
||||
byteArray.addAll(Bytes.asList(operation.toBytes()));
|
||||
}
|
||||
|
||||
//Adding the number of extensions
|
||||
byteArray.add((byte) this.extensions.size());
|
||||
|
||||
for(Extension extension : extensions){
|
||||
//TODO: Implement the extension serialization
|
||||
}
|
||||
// Adding a last zero byte to match the result obtained by the python-graphenelib code
|
||||
// I'm not exactly sure what's the meaning of this last zero byte, but for now I'll just
|
||||
// leave it here and work on signing the transaction.
|
||||
//TODO: Investigate the origin and meaning of this last byte.
|
||||
byteArray.add((byte) 0 );
|
||||
|
||||
return Bytes.toArray(byteArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(Transaction.class, new TransactionSerializer());
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject toJsonObject() {
|
||||
JsonObject obj = new JsonObject();
|
||||
|
||||
// Formatting expiration time
|
||||
Date expirationTime = new Date(blockData.getRelativeExpiration() * 1000);
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
// Adding expiration
|
||||
obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime));
|
||||
|
||||
// Adding signatures
|
||||
JsonArray signatureArray = new JsonArray();
|
||||
signatureArray.add(Util.bytesToHex(getSignature()));
|
||||
obj.add(KEY_SIGNATURES, signatureArray);
|
||||
|
||||
JsonArray operationsArray = new JsonArray();
|
||||
for(BaseOperation operation : operations){
|
||||
operationsArray.add(operation.toJsonObject());
|
||||
}
|
||||
// Adding operations
|
||||
obj.add(KEY_OPERATIONS, operationsArray);
|
||||
|
||||
// Adding extensions
|
||||
obj.add(KEY_EXTENSIONS, new JsonArray());
|
||||
|
||||
// Adding block data
|
||||
obj.addProperty(KEY_REF_BLOCK_NUM, blockData.getRefBlockNum());
|
||||
obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix());
|
||||
|
||||
return obj;
|
||||
|
||||
}
|
||||
|
||||
class TransactionSerializer implements JsonSerializer<Transaction> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
return transaction.toJsonObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/14/16.
|
||||
*/
|
||||
public abstract class TransactionBuilder {
|
||||
protected ECKey privateKey;
|
||||
protected BlockData blockData;
|
||||
|
||||
public abstract Transaction build() throws MalformedTransactionException;
|
||||
}
|
113
src/main/java/com/luminiasoft/bitshares/Transfer.java
Normal file
113
src/main/java/com/luminiasoft/bitshares/Transfer.java
Normal file
|
@ -0,0 +1,113 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the Transfer operation related functionalities.
|
||||
*/
|
||||
public class Transfer extends BaseOperation {
|
||||
public static final String KEY_FEE = "fee";
|
||||
public static final String KEY_AMOUNT = "amount";
|
||||
public static final String KEY_EXTENSIONS = "extensions";
|
||||
public static final String KEY_FROM = "from";
|
||||
public static final String KEY_TO = "to";
|
||||
|
||||
private AssetAmount fee;
|
||||
private AssetAmount amount;
|
||||
private UserAccount from;
|
||||
private UserAccount to;
|
||||
private Memo memo;
|
||||
private String[] extensions;
|
||||
|
||||
public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){
|
||||
super(OperationType.transfer_operation);
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = transferAmount;
|
||||
this.fee = fee;
|
||||
this.memo = new Memo();
|
||||
}
|
||||
|
||||
public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount){
|
||||
super(OperationType.transfer_operation);
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = transferAmount;
|
||||
this.memo = new Memo();
|
||||
}
|
||||
|
||||
public void setFee(AssetAmount newFee){
|
||||
this.fee = newFee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getId() {
|
||||
return (byte) this.type.ordinal();
|
||||
}
|
||||
|
||||
public UserAccount getFrom(){
|
||||
return this.from;
|
||||
}
|
||||
|
||||
public UserAccount getTo(){
|
||||
return this.to;
|
||||
}
|
||||
|
||||
public AssetAmount getAmount(){
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public AssetAmount getFee(){
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = fee.toBytes();
|
||||
byte[] fromBytes = from.toBytes();
|
||||
byte[] toBytes = to.toBytes();
|
||||
byte[] amountBytes = amount.toBytes();
|
||||
byte[] memoBytes = memo.toBytes();
|
||||
return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, memoBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(Transfer.class, new TransferSerializer());
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.add(KEY_AMOUNT, amount.toJsonObject());
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
jsonObject.addProperty(KEY_FROM, from.toJsonString());
|
||||
jsonObject.addProperty(KEY_TO, to.toJsonString());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
class TransferSerializer implements JsonSerializer<Transfer> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Transfer transfer, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
JsonArray arrayRep = new JsonArray();
|
||||
arrayRep.add(transfer.getId());
|
||||
arrayRep.add(toJsonObject());
|
||||
return arrayRep;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class used to build a transaction containing a transfer operation.
|
||||
*/
|
||||
public class TransferTransactionBuilder extends TransactionBuilder {
|
||||
private List<BaseOperation> operations;
|
||||
private UserAccount sourceAccount;
|
||||
private UserAccount destinationAccount;
|
||||
private AssetAmount transferAmount;
|
||||
private AssetAmount feeAmount;
|
||||
|
||||
public TransferTransactionBuilder setPrivateKey(ECKey key){
|
||||
this.privateKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransferTransactionBuilder setBlockData(BlockData blockData){
|
||||
this.blockData = blockData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransferTransactionBuilder setSource(UserAccount source){
|
||||
this.sourceAccount = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransferTransactionBuilder setDestination(UserAccount destination){
|
||||
this.destinationAccount = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransferTransactionBuilder setAmount(AssetAmount amount){
|
||||
this.transferAmount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransferTransactionBuilder setFee(AssetAmount amount){
|
||||
this.feeAmount = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransferTransactionBuilder addOperation(Transfer transferOperation){
|
||||
if(operations == null){
|
||||
operations = new ArrayList<BaseOperation>();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction build() throws MalformedTransactionException {
|
||||
if(privateKey == null){
|
||||
throw new MalformedTransactionException("Missing private key information");
|
||||
}else if(blockData == null){
|
||||
throw new MalformedTransactionException("Missing block data information");
|
||||
}else if(operations == null){
|
||||
// If the operations list has not been set, we might be able to build one with the
|
||||
// previously provided data. But in order for this to work we have to have all
|
||||
// source, destination and transfer amount data.
|
||||
operations = new ArrayList<>();
|
||||
if(sourceAccount == null){
|
||||
throw new MalformedTransactionException("Missing source account information");
|
||||
}
|
||||
if(destinationAccount == null){
|
||||
throw new MalformedTransactionException("Missing destination account information");
|
||||
}
|
||||
if(transferAmount == null){
|
||||
throw new MalformedTransactionException("Missing transfer amount information");
|
||||
}
|
||||
Transfer transferOperation;
|
||||
if(feeAmount == null){
|
||||
transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount);
|
||||
}else{
|
||||
transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount, feeAmount);
|
||||
}
|
||||
operations.add(transferOperation);
|
||||
}
|
||||
return new Transaction(privateKey, blockData, operations);
|
||||
}
|
||||
}
|
48
src/main/java/com/luminiasoft/bitshares/UserAccount.java
Normal file
48
src/main/java/com/luminiasoft/bitshares/UserAccount.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class tha represents a graphene user account.
|
||||
* Created by nelson on 11/8/16.
|
||||
*/
|
||||
public class UserAccount extends GrapheneObject implements ByteSerializable, JsonSerializable {
|
||||
|
||||
/**
|
||||
* Constructor that expects a user account in the string representation.
|
||||
* That is in the 1.2.x format.
|
||||
* @param id: The string representing the account apiId.
|
||||
*/
|
||||
public UserAccount(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(byteArrayOutputStream);
|
||||
try {
|
||||
Varint.writeUnsignedVarLong(this.instance, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return this.getObjectId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject toJsonObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
28
src/main/java/com/luminiasoft/bitshares/Util.java
Normal file
28
src/main/java/com/luminiasoft/bitshares/Util.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package com.luminiasoft.bitshares;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/8/16.
|
||||
*/
|
||||
public class Util {
|
||||
final private static char[] hexArray = "0123456789abcdef".toCharArray();
|
||||
|
||||
public static byte[] hexToBytes(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for ( int j = 0; j < bytes.length; j++ ) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
224
src/main/java/com/luminiasoft/bitshares/Varint.java
Normal file
224
src/main/java/com/luminiasoft/bitshares/Varint.java
Normal file
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.luminiasoft.bitshares;
|
||||
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Encodes signed and unsigned values using a common variable-length
|
||||
* scheme, found for example in
|
||||
* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html">
|
||||
* Google's Protocol Buffers</a>. It uses fewer bytes to encode smaller values,
|
||||
* but will use slightly more bytes to encode large values.</p>
|
||||
* <p/>
|
||||
* <p>Signed values are further encoded using so-called zig-zag encoding
|
||||
* in order to make them "compatible" with variable-length encoding.</p>
|
||||
*/
|
||||
public final class Varint {
|
||||
|
||||
private Varint() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a value using the variable-length encoding from
|
||||
* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html">
|
||||
* Google Protocol Buffers</a>. It uses zig-zag encoding to efficiently
|
||||
* encode signed values. If values are known to be nonnegative,
|
||||
* {@link #writeUnsignedVarLong(long, DataOutput)} should be used.
|
||||
*
|
||||
* @param value value to encode
|
||||
* @param out to write bytes to
|
||||
* @throws IOException if {@link DataOutput} throws {@link IOException}
|
||||
*/
|
||||
public static void writeSignedVarLong(long value, DataOutput out) throws IOException {
|
||||
// Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
|
||||
writeUnsignedVarLong((value << 1) ^ (value >> 63), out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a value using the variable-length encoding from
|
||||
* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html">
|
||||
* Google Protocol Buffers</a>. Zig-zag is not used, so input must not be negative.
|
||||
* If values can be negative, use {@link #writeSignedVarLong(long, DataOutput)}
|
||||
* instead. This method treats negative input as like a large unsigned value.
|
||||
*
|
||||
* @param value value to encode
|
||||
* @param out to write bytes to
|
||||
* @throws IOException if {@link DataOutput} throws {@link IOException}
|
||||
*/
|
||||
public static void writeUnsignedVarLong(long value, DataOutput out) throws IOException {
|
||||
while ((value & 0xFFFFFFFFFFFFFF80L) != 0L) {
|
||||
out.writeByte(((int) value & 0x7F) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
out.writeByte((int) value & 0x7F);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #writeSignedVarLong(long, DataOutput)
|
||||
*/
|
||||
public static void writeSignedVarInt(int value, DataOutput out) throws IOException {
|
||||
// Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
|
||||
writeUnsignedVarInt((value << 1) ^ (value >> 31), out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #writeUnsignedVarLong(long, DataOutput)
|
||||
*/
|
||||
public static void writeUnsignedVarInt(int value, DataOutput out) throws IOException {
|
||||
while ((value & 0xFFFFFF80) != 0L) {
|
||||
out.writeByte((value & 0x7F) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
out.writeByte(value & 0x7F);
|
||||
}
|
||||
|
||||
public static byte[] writeSignedVarInt(int value) {
|
||||
// Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
|
||||
return writeUnsignedVarInt((value << 1) ^ (value >> 31));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #writeUnsignedVarLong(long, DataOutput)
|
||||
* <p/>
|
||||
* This one does not use streams and is much faster.
|
||||
* Makes a single object each time, and that object is a primitive array.
|
||||
*/
|
||||
public static byte[] writeUnsignedVarInt(int value) {
|
||||
byte[] byteArrayList = new byte[10];
|
||||
int i = 0;
|
||||
while ((value & 0xFFFFFF80) != 0L) {
|
||||
byteArrayList[i++] = ((byte) ((value & 0x7F) | 0x80));
|
||||
value >>>= 7;
|
||||
}
|
||||
byteArrayList[i] = ((byte) (value & 0x7F));
|
||||
byte[] out = new byte[i + 1];
|
||||
for (; i >= 0; i--) {
|
||||
out[i] = byteArrayList[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param in to read bytes from
|
||||
* @return decode value
|
||||
* @throws IOException if {@link DataInput} throws {@link IOException}
|
||||
* @throws IllegalArgumentException if variable-length value does not terminate
|
||||
* after 9 bytes have been read
|
||||
* @see #writeSignedVarLong(long, DataOutput)
|
||||
*/
|
||||
public static long readSignedVarLong(DataInput in) throws IOException {
|
||||
long raw = readUnsignedVarLong(in);
|
||||
// This undoes the trick in writeSignedVarLong()
|
||||
long temp = (((raw << 63) >> 63) ^ raw) >> 1;
|
||||
// This extra step lets us deal with the largest signed values by treating
|
||||
// negative results from read unsigned methods as like unsigned values
|
||||
// Must re-flip the top bit if the original read value had it set.
|
||||
return temp ^ (raw & (1L << 63));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param in to read bytes from
|
||||
* @return decode value
|
||||
* @throws IOException if {@link DataInput} throws {@link IOException}
|
||||
* @throws IllegalArgumentException if variable-length value does not terminate
|
||||
* after 9 bytes have been read
|
||||
* @see #writeUnsignedVarLong(long, DataOutput)
|
||||
*/
|
||||
public static long readUnsignedVarLong(DataInput in) throws IOException {
|
||||
long value = 0L;
|
||||
int i = 0;
|
||||
long b;
|
||||
while (((b = in.readByte()) & 0x80L) != 0) {
|
||||
value |= (b & 0x7F) << i;
|
||||
i += 7;
|
||||
if (i > 63) {
|
||||
throw new IllegalArgumentException("Variable length quantity is too long");
|
||||
}
|
||||
}
|
||||
return value | (b << i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if variable-length value does not terminate
|
||||
* after 5 bytes have been read
|
||||
* @throws IOException if {@link DataInput} throws {@link IOException}
|
||||
* @see #readSignedVarLong(DataInput)
|
||||
*/
|
||||
public static int readSignedVarInt(DataInput in) throws IOException {
|
||||
int raw = readUnsignedVarInt(in);
|
||||
// This undoes the trick in writeSignedVarInt()
|
||||
int temp = (((raw << 31) >> 31) ^ raw) >> 1;
|
||||
// This extra step lets us deal with the largest signed values by treating
|
||||
// negative results from read unsigned methods as like unsigned values.
|
||||
// Must re-flip the top bit if the original read value had it set.
|
||||
return temp ^ (raw & (1 << 31));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if variable-length value does not terminate
|
||||
* after 5 bytes have been read
|
||||
* @throws IOException if {@link DataInput} throws {@link IOException}
|
||||
* @see #readUnsignedVarLong(DataInput)
|
||||
*/
|
||||
public static int readUnsignedVarInt(DataInput in) throws IOException {
|
||||
int value = 0;
|
||||
int i = 0;
|
||||
int b;
|
||||
while (((b = in.readByte()) & 0x80) != 0) {
|
||||
value |= (b & 0x7F) << i;
|
||||
i += 7;
|
||||
if (i > 35) {
|
||||
throw new IllegalArgumentException("Variable length quantity is too long");
|
||||
}
|
||||
}
|
||||
return value | (b << i);
|
||||
}
|
||||
|
||||
public static int readSignedVarInt(byte[] bytes) {
|
||||
int raw = readUnsignedVarInt(bytes);
|
||||
// This undoes the trick in writeSignedVarInt()
|
||||
int temp = (((raw << 31) >> 31) ^ raw) >> 1;
|
||||
// This extra step lets us deal with the largest signed values by treating
|
||||
// negative results from read unsigned methods as like unsigned values.
|
||||
// Must re-flip the top bit if the original read value had it set.
|
||||
return temp ^ (raw & (1 << 31));
|
||||
}
|
||||
|
||||
public static int readUnsignedVarInt(byte[] bytes) {
|
||||
int value = 0;
|
||||
int i = 0;
|
||||
byte rb = Byte.MIN_VALUE;
|
||||
for (byte b : bytes) {
|
||||
rb = b;
|
||||
if ((b & 0x80) == 0) {
|
||||
break;
|
||||
}
|
||||
value |= (b & 0x7f) << i;
|
||||
i += 7;
|
||||
if (i > 35) {
|
||||
throw new IllegalArgumentException("Variable length quantity is too long");
|
||||
}
|
||||
}
|
||||
return value | (rb << i);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.luminiasoft.bitshares.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/14/16.
|
||||
*/
|
||||
public class MalformedTransactionException extends Exception {
|
||||
|
||||
public MalformedTransactionException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.luminiasoft.bitshares.interfaces;
|
||||
|
||||
/**
|
||||
* Interface implemented by all entities for which makes sense to have a byte-array representation.
|
||||
*/
|
||||
public interface ByteSerializable {
|
||||
|
||||
byte[] toBytes();
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.luminiasoft.bitshares.interfaces;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by any entity for which makes sense to have a JSON-formatted string representation.
|
||||
*/
|
||||
public interface JsonSerializable extends Serializable {
|
||||
|
||||
String toJsonString();
|
||||
|
||||
JsonElement toJsonObject();
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.luminiasoft.bitshares.interfaces;
|
||||
|
||||
import com.luminiasoft.bitshares.models.BaseResponse;
|
||||
import com.luminiasoft.bitshares.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class used to represent any listener to network requests.
|
||||
*/
|
||||
public interface WitnessResponseListener {
|
||||
|
||||
void onSuccess(WitnessResponse response);
|
||||
|
||||
void onError(BaseResponse.Error error);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.luminiasoft.bitshares.models;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/15/16.
|
||||
*/
|
||||
public class AccountProperties {
|
||||
public String id;
|
||||
public String membership_expiration_date;
|
||||
public String registrar;
|
||||
public String referrer;
|
||||
public String lifetime_referrer;
|
||||
public long network_fee_percentage;
|
||||
public long lifetime_referrer_fee_percentage;
|
||||
public long referrer_rewards_percentage;
|
||||
public String name;
|
||||
public User owner;
|
||||
public User active;
|
||||
public Options options;
|
||||
public String statistics;
|
||||
public String[] whitelisting_accounts;
|
||||
public String[] blacklisting_accounts;
|
||||
public String[] whitelisted_accounts;
|
||||
public String[] blacklisted_accounts;
|
||||
public Object[] owner_special_authority;
|
||||
public Object[] active_special_authority;
|
||||
public long top_n_control_flags;
|
||||
|
||||
class User {
|
||||
public long weight_threshold;
|
||||
public String[] account_auths; //TODO: Check this type
|
||||
public String[][] key_auths; //TODO: Check how to deserialize this
|
||||
public String[] address_auths;
|
||||
}
|
||||
|
||||
class Options {
|
||||
public String memo_key;
|
||||
public String voting_account;
|
||||
public long num_witness;
|
||||
public long num_committee;
|
||||
public String[] votes; //TODO: Check this type
|
||||
public String[] extensions; //TODO: Check this type
|
||||
}
|
||||
}
|
99
src/main/java/com/luminiasoft/bitshares/models/ApiCall.java
Normal file
99
src/main/java/com/luminiasoft/bitshares/models/ApiCall.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
package com.luminiasoft.bitshares.models;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class used to build a Graphene websocket API call.
|
||||
* @see <a href="http://docs.bitshares.org/api/websocket.html">Websocket Calls & Notifications</a>
|
||||
*/
|
||||
public class ApiCall implements JsonSerializable {
|
||||
public static final String KEY_SEQUENCE_ID = "id";
|
||||
public static final String KEY_METHOD = "method";
|
||||
public static final String KEY_PARAMS = "params";
|
||||
public static final String KEY_JSON_RPC = "jsonrpc";
|
||||
|
||||
public String method;
|
||||
public String methodToCall;
|
||||
public String jsonrpc;
|
||||
public List<Serializable> params;
|
||||
public int apiId;
|
||||
public int sequenceId;
|
||||
|
||||
public ApiCall(int apiId, String methodToCall, List<Serializable> params, String jsonrpc, int sequenceId){
|
||||
this.apiId = apiId;
|
||||
this.method = "call";
|
||||
this.methodToCall = methodToCall;
|
||||
this.jsonrpc = jsonrpc;
|
||||
this.params = params;
|
||||
this.sequenceId = sequenceId;
|
||||
}
|
||||
|
||||
public ApiCall(int apiId, String method, String methodToCall, List<Serializable> params, String jsonrpc, int sequenceId){
|
||||
this.apiId = apiId;
|
||||
this.method = method;
|
||||
this.methodToCall = methodToCall;
|
||||
this.jsonrpc = jsonrpc;
|
||||
this.params = params;
|
||||
this.sequenceId = sequenceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(ApiCall.class, new ApiCallSerializer());
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty(KEY_SEQUENCE_ID, this.sequenceId);
|
||||
obj.addProperty(KEY_METHOD, this.method);
|
||||
JsonArray paramsArray = new JsonArray();
|
||||
paramsArray.add(this.apiId);
|
||||
paramsArray.add(this.methodToCall);
|
||||
JsonArray methodParams = new JsonArray();
|
||||
|
||||
for(int i = 0; i < this.params.size(); i++){
|
||||
if(this.params.get(i) instanceof JsonSerializable){
|
||||
// Sometimes the parameters are objects
|
||||
methodParams.add(((JsonSerializable) this.params.get(i)).toJsonObject());
|
||||
}else if(this.params.get(i) instanceof String || this.params.get(i) == null){
|
||||
// Other times they are plain strings
|
||||
methodParams.add((String) this.params.get(i));
|
||||
}else if(this.params.get(i) instanceof ArrayList){
|
||||
// Other times it might be an array
|
||||
JsonArray array = new JsonArray();
|
||||
ArrayList<JsonSerializable> listArgument = (ArrayList<JsonSerializable>) this.params.get(i);
|
||||
for(int l = 0; l < listArgument.size(); l++){
|
||||
JsonSerializable s = listArgument.get(l);
|
||||
array.add(s.toJsonObject());
|
||||
}
|
||||
methodParams.add(array);
|
||||
}
|
||||
}
|
||||
paramsArray.add(methodParams);
|
||||
obj.add(KEY_PARAMS, paramsArray);
|
||||
obj.addProperty(KEY_JSON_RPC, this.jsonrpc);
|
||||
return obj;
|
||||
}
|
||||
|
||||
class ApiCallSerializer implements JsonSerializer<ApiCall> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ApiCall apiCall, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
return toJsonObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.luminiasoft.bitshares.models;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/12/16.
|
||||
*/
|
||||
public class BaseResponse {
|
||||
public int id;
|
||||
public Error error;
|
||||
|
||||
public static class Error {
|
||||
public ErrorData data;
|
||||
public int code;
|
||||
public String message;
|
||||
public Error(String message){
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ErrorData {
|
||||
public int code;
|
||||
public String name;
|
||||
public String message;
|
||||
//TODO: Include stack data
|
||||
|
||||
public ErrorData(String message){
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.luminiasoft.bitshares.models;
|
||||
|
||||
/**
|
||||
* Class used to deserialize the 'result' field returned by the full node after making a call
|
||||
* to the 'get_dynamic_global_properties' RPC.
|
||||
*/
|
||||
public class DynamicGlobalProperties {
|
||||
public String id;
|
||||
public long head_block_number;
|
||||
public String head_block_id;
|
||||
public String time;
|
||||
public String current_witness;
|
||||
public String next_maintenance_time;
|
||||
public String last_budget_time;
|
||||
public long witness_budget;
|
||||
public long accounts_registered_this_interval;
|
||||
public long recently_missed_count;
|
||||
public long current_aslot;
|
||||
public String recent_slots_filled;
|
||||
public long dynamic_flags;
|
||||
public long last_irreversible_block_num;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.luminiasoft.bitshares.models;
|
||||
|
||||
/**
|
||||
* Generic witness response
|
||||
*/
|
||||
public class WitnessResponse<T> extends BaseResponse{
|
||||
public T result;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package com.luminiasoft.bitshares.ws;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.luminiasoft.bitshares.RPC;
|
||||
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
||||
import com.luminiasoft.bitshares.models.AccountProperties;
|
||||
import com.luminiasoft.bitshares.models.ApiCall;
|
||||
import com.luminiasoft.bitshares.models.BaseResponse;
|
||||
import com.luminiasoft.bitshares.models.WitnessResponse;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketAdapter;
|
||||
import com.neovisionaries.ws.client.WebSocketException;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/15/16.
|
||||
*/
|
||||
public class GetAccountByName extends WebSocketAdapter {
|
||||
|
||||
private String accountName;
|
||||
private WitnessResponseListener mListener;
|
||||
|
||||
public GetAccountByName(String accountName, WitnessResponseListener listener){
|
||||
this.accountName = accountName;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add(this.accountName);
|
||||
ApiCall getAccountByName = new ApiCall(0, RPC.CALL_GET_ACCOUNT_BY_NAME, accountParams, "2.0", 1);
|
||||
websocket.sendText(getAccountByName.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
Gson gson = new Gson();
|
||||
|
||||
Type GetAccountByNameResponse = new TypeToken<WitnessResponse<AccountProperties>>(){}.getType();
|
||||
WitnessResponse<WitnessResponse<AccountProperties>> witnessResponse = gson.fromJson(response, GetAccountByNameResponse);
|
||||
|
||||
if(witnessResponse.error != null){
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
}else{
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
|
||||
websocket.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package com.luminiasoft.bitshares.ws;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.luminiasoft.bitshares.Asset;
|
||||
import com.luminiasoft.bitshares.AssetAmount;
|
||||
import com.luminiasoft.bitshares.BaseOperation;
|
||||
import com.luminiasoft.bitshares.RPC;
|
||||
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
||||
import com.luminiasoft.bitshares.models.ApiCall;
|
||||
import com.luminiasoft.bitshares.models.BaseResponse;
|
||||
import com.luminiasoft.bitshares.models.WitnessResponse;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketAdapter;
|
||||
import com.neovisionaries.ws.client.WebSocketException;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/15/16.
|
||||
*/
|
||||
public class GetRequiredFees extends WebSocketAdapter {
|
||||
|
||||
private WitnessResponseListener mListener;
|
||||
private List<BaseOperation> operations;
|
||||
private Asset asset;
|
||||
|
||||
public GetRequiredFees(List<BaseOperation> operations, Asset asset, WitnessResponseListener listener){
|
||||
this.operations = operations;
|
||||
this.asset = asset;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add((Serializable) this.operations);
|
||||
accountParams.add(this.asset.getObjectId());
|
||||
ApiCall getRequiredFees = new ApiCall(0, RPC.CALL_GET_REQUIRED_FEES, accountParams, "2.0", 1);
|
||||
websocket.sendText(getRequiredFees.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
Gson gson = new Gson();
|
||||
|
||||
Type GetRequiredFeesResponse = new TypeToken<WitnessResponse<List<AssetAmount>>>(){}.getType();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer());
|
||||
WitnessResponse<List<AssetAmount>> witnessResponse = gsonBuilder.create().fromJson(response, GetRequiredFeesResponse);
|
||||
|
||||
if(witnessResponse.error != null){
|
||||
mListener.onError(witnessResponse.error);
|
||||
}else{
|
||||
mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package com.luminiasoft.bitshares.ws;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.luminiasoft.bitshares.BaseOperation;
|
||||
import com.luminiasoft.bitshares.BlockData;
|
||||
import com.luminiasoft.bitshares.RPC;
|
||||
import com.luminiasoft.bitshares.Transaction;
|
||||
import com.luminiasoft.bitshares.Transfer;
|
||||
import com.luminiasoft.bitshares.TransferTransactionBuilder;
|
||||
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
||||
import com.luminiasoft.bitshares.models.ApiCall;
|
||||
import com.luminiasoft.bitshares.models.BaseResponse;
|
||||
import com.luminiasoft.bitshares.models.DynamicGlobalProperties;
|
||||
import com.luminiasoft.bitshares.models.WitnessResponse;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketAdapter;
|
||||
import com.neovisionaries.ws.client.WebSocketException;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Class that will handle the transaction publication procedure.
|
||||
*/
|
||||
public class TransactionBroadcastSequence extends WebSocketAdapter {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
private final static int LOGIN_ID = 1;
|
||||
private final static int GET_NETWORK_BROADCAST_ID = 2;
|
||||
private final static int GET_NETWORK_DYNAMIC_PARAMETERS = 3;
|
||||
private final static int BROADCAST_TRANSACTION = 4;
|
||||
public final static int EXPIRATION_TIME = 30;
|
||||
|
||||
private Transaction transaction;
|
||||
private long expirationTime;
|
||||
private String headBlockId;
|
||||
private long headBlockNumber;
|
||||
private WitnessResponseListener mListener;
|
||||
|
||||
private int currentId = 1;
|
||||
private int broadcastApiId = -1;
|
||||
private int retries = 0;
|
||||
|
||||
/**
|
||||
* Constructor of this class. The ids required
|
||||
* @param transaction: The transaction to be broadcasted.
|
||||
* @param listener: A class implementing the WitnessResponseListener interface. This should
|
||||
* be implemented by the party interested in being notified about the success/failure
|
||||
* of the transaction broadcast operation.
|
||||
*/
|
||||
public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){
|
||||
this.transaction = transaction;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> loginParams = new ArrayList<>();
|
||||
loginParams.add(null);
|
||||
loginParams.add(null);
|
||||
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, "2.0", currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
Gson gson = new Gson();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if(baseResponse.error != null && baseResponse.error.message.indexOf("is_canonical") == -1){
|
||||
mListener.onError(baseResponse.error);
|
||||
websocket.disconnect();
|
||||
}else{
|
||||
currentId++;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
if(baseResponse.id == LOGIN_ID){
|
||||
ApiCall networkApiIdCall = new ApiCall(1, RPC.CALL_NETWORK_BROADCAST, emptyParams, "2.0", currentId);
|
||||
websocket.sendText(networkApiIdCall.toJsonString());
|
||||
}else if(baseResponse.id == GET_NETWORK_BROADCAST_ID){
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
broadcastApiId = witnessResponse.result;
|
||||
|
||||
ApiCall getDynamicParametersCall = new ApiCall(0, RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES, emptyParams, "2.0", currentId);
|
||||
websocket.sendText(getDynamicParametersCall.toJsonString());
|
||||
}else if(baseResponse.id == GET_NETWORK_DYNAMIC_PARAMETERS){
|
||||
Type DynamicGlobalPropertiesResponse = new TypeToken<WitnessResponse<DynamicGlobalProperties>>(){}.getType();
|
||||
WitnessResponse<DynamicGlobalProperties> witnessResponse = gson.fromJson(response, DynamicGlobalPropertiesResponse);
|
||||
DynamicGlobalProperties dynamicProperties = witnessResponse.result;
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Date date = dateFormat.parse(dynamicProperties.time);
|
||||
|
||||
// Obtained block data
|
||||
expirationTime = (date.getTime() / 1000) + EXPIRATION_TIME;
|
||||
headBlockId = dynamicProperties.head_block_id;
|
||||
headBlockNumber = dynamicProperties.head_block_number;
|
||||
|
||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||
transactionList.add(transaction);
|
||||
ApiCall call = new ApiCall(broadcastApiId,
|
||||
RPC.CALL_BROADCAST_TRANSACTION,
|
||||
transactionList,
|
||||
"2.0",
|
||||
currentId);
|
||||
|
||||
// Finally sending transaction
|
||||
websocket.sendText(call.toJsonString());
|
||||
}else if(baseResponse.id >= BROADCAST_TRANSACTION){
|
||||
Type WitnessResponseType = new TypeToken<WitnessResponse<String>>(){}.getType();
|
||||
WitnessResponse<WitnessResponse<String>> witnessResponse = gson.fromJson(response, WitnessResponseType);
|
||||
if(witnessResponse.error == null){
|
||||
mListener.onSuccess(witnessResponse);
|
||||
websocket.disconnect();
|
||||
}else{
|
||||
if(witnessResponse.error.message.indexOf("is_canonical") != -1 && retries < 10){
|
||||
/*
|
||||
* This is a very ugly hack, but it will do for now.
|
||||
*
|
||||
* The issue is that the witness is complaining about the signature not
|
||||
* being canonical even though the bitcoinj ECKey.ECDSASignature.isCanonical()
|
||||
* method says it is! We'll have to dive deeper into this issue and avoid
|
||||
* this error altogether
|
||||
*
|
||||
* But this MUST BE FIXED! Since this hack will only work for transactions
|
||||
* with ONE transfer operation.
|
||||
*/
|
||||
retries++;
|
||||
List<BaseOperation> operations = this.transaction.getOperations();
|
||||
Transfer transfer = (Transfer) operations.get(0);
|
||||
transaction = new TransferTransactionBuilder()
|
||||
.setSource(transfer.getFrom())
|
||||
.setDestination(transfer.getTo())
|
||||
.setAmount(transfer.getAmount())
|
||||
.setFee(transfer.getFee())
|
||||
.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime + EXPIRATION_TIME))
|
||||
.setPrivateKey(transaction.getPrivateKey())
|
||||
.build();
|
||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||
transactionList.add(transaction);
|
||||
ApiCall call = new ApiCall(broadcastApiId,
|
||||
RPC.CALL_BROADCAST_TRANSACTION,
|
||||
transactionList,
|
||||
"2.0",
|
||||
currentId);
|
||||
websocket.sendText(call.toJsonString());
|
||||
}else{
|
||||
mListener.onError(witnessResponse.error);
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue