Added pgrahenej library
Added generator class Added accountmanager
This commit is contained in:
parent
d5dbe02a50
commit
60df3ae6f9
102 changed files with 9661 additions and 0 deletions
|
@ -45,4 +45,11 @@ dependencies {
|
|||
compile "android.arch.paging:runtime:1.0.0-alpha1";
|
||||
|
||||
compile 'com.idescout.sql:sqlscout-server:2.0';
|
||||
|
||||
compile 'com.google.code.gson:gson:2.2.4'
|
||||
compile 'com.squareup.retrofit2:retrofit:2.1.0'
|
||||
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
|
||||
compile 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||
compile 'com.neovisionaries:nv-websocket-client:1.30'
|
||||
compile 'org.tukaani:xz:1.6'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package cy.agorise.crystalwallet.apigenerator;
|
||||
|
||||
/**
|
||||
* Created by henry on 26/9/2017.
|
||||
*/
|
||||
|
||||
public class GrapheneApiGenerator {
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cy.agorise.crystalwallet.manager;
|
||||
|
||||
import cy.agorise.crystalwallet.models.CryptoNetAccount;
|
||||
|
||||
/**
|
||||
* Created by henry on 26/9/2017.
|
||||
*/
|
||||
|
||||
public class BitsharesAccountManager implements CryptoAccountManager {
|
||||
@Override
|
||||
public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAccountFromDB(CryptoNetAccount account) {
|
||||
|
||||
}
|
||||
}
|
166
app/src/main/java/cy/agorise/graphenej/AccountOptions.java
Normal file
166
app/src/main/java/cy/agorise/graphenej/AccountOptions.java
Normal file
|
@ -0,0 +1,166 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import cy.agorise.graphenej.interfaces.GrapheneSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/5/16.
|
||||
*/
|
||||
public class AccountOptions implements GrapheneSerializable {
|
||||
public static final String KEY_MEMO_KEY = "memo_key";
|
||||
public static final String KEY_NUM_COMMITTEE = "num_committee";
|
||||
public static final String KEY_NUM_WITNESS = "num_witness";
|
||||
public static final String KEY_VOTES = "votes";
|
||||
public static final String KEY_VOTING_ACCOUNT = "voting_account";
|
||||
public static final String KEY_EXTENSIONS = Extensions.KEY_EXTENSIONS;
|
||||
|
||||
private PublicKey memo_key;
|
||||
private UserAccount voting_account;
|
||||
private int num_witness;
|
||||
private int num_comittee;
|
||||
private Vote[] votes;
|
||||
private Extensions extensions;
|
||||
|
||||
public AccountOptions(){
|
||||
voting_account = new UserAccount(UserAccount.PROXY_TO_SELF);
|
||||
this.votes = new Vote[0];
|
||||
this.extensions = new Extensions();
|
||||
}
|
||||
|
||||
public AccountOptions(PublicKey memoKey){
|
||||
this();
|
||||
this.memo_key = memoKey;
|
||||
}
|
||||
|
||||
//TODO: Implement constructor that takes a Vote array.
|
||||
|
||||
public PublicKey getMemoKey() {
|
||||
return memo_key;
|
||||
}
|
||||
|
||||
public void setMemoKey(PublicKey memo_key) {
|
||||
this.memo_key = memo_key;
|
||||
}
|
||||
|
||||
public UserAccount getVotingAccount() {
|
||||
return voting_account;
|
||||
}
|
||||
|
||||
public void setVotingAccount(UserAccount voting_account) {
|
||||
this.voting_account = voting_account;
|
||||
}
|
||||
|
||||
public int getNumWitness() {
|
||||
return num_witness;
|
||||
}
|
||||
|
||||
public void setNumWitness(int num_witness) {
|
||||
this.num_witness = num_witness;
|
||||
}
|
||||
|
||||
public int getNumComittee() {
|
||||
return num_comittee;
|
||||
}
|
||||
|
||||
public void setNum_comittee(int num_comittee) {
|
||||
this.num_comittee = num_comittee;
|
||||
}
|
||||
|
||||
public Vote[] getVotes() {
|
||||
return votes;
|
||||
}
|
||||
|
||||
public void setVotes(Vote[] votes) {
|
||||
this.votes = votes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
List<Byte> byteArray = new ArrayList<Byte>();
|
||||
|
||||
if(memo_key != null){
|
||||
// Adding byte to indicate that there is memo data
|
||||
byteArray.add((byte) 1);
|
||||
|
||||
// Adding memo key
|
||||
byteArray.addAll(Bytes.asList(memo_key.toBytes()));
|
||||
|
||||
// Adding voting account
|
||||
byteArray.addAll(Bytes.asList(voting_account.toBytes()));
|
||||
|
||||
// Adding num_witness
|
||||
byteArray.addAll(Bytes.asList(Util.revertShort(Short.valueOf((short) num_witness))));
|
||||
|
||||
// Adding num_committee
|
||||
byteArray.addAll(Bytes.asList(Util.revertShort(Short.valueOf((short) num_comittee))));
|
||||
|
||||
// Vote's array length
|
||||
byteArray.add((byte) votes.length);
|
||||
|
||||
for(Vote vote : votes){
|
||||
//TODO: Check this serialization
|
||||
byteArray.addAll(Bytes.asList(vote.toBytes()));
|
||||
}
|
||||
|
||||
// Account options's extensions
|
||||
byteArray.addAll(Bytes.asList(extensions.toBytes()));
|
||||
}else{
|
||||
byteArray.add((byte) 0);
|
||||
}
|
||||
return Bytes.toArray(byteArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonObject options = new JsonObject();
|
||||
options.addProperty(KEY_MEMO_KEY, new Address(memo_key.getKey()).toString());
|
||||
options.addProperty(KEY_NUM_COMMITTEE, num_comittee);
|
||||
options.addProperty(KEY_NUM_WITNESS, num_witness);
|
||||
options.addProperty(KEY_VOTING_ACCOUNT, voting_account.getObjectId());
|
||||
JsonArray votesArray = new JsonArray();
|
||||
for(Vote vote : votes){
|
||||
//TODO: Add votes representation
|
||||
}
|
||||
options.add(KEY_VOTES, votesArray);
|
||||
options.add(KEY_EXTENSIONS, extensions.toJsonObject());
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used while parsing the 'get_account_by_name' API call response.
|
||||
* TODO: Implement all other details besides the key
|
||||
*/
|
||||
public static class AccountOptionsDeserializer implements JsonDeserializer<AccountOptions> {
|
||||
|
||||
@Override
|
||||
public AccountOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject baseObject = json.getAsJsonObject();
|
||||
AccountOptions options;
|
||||
try {
|
||||
Address address = new Address(baseObject.get(KEY_MEMO_KEY).getAsString());
|
||||
options = new AccountOptions(address.getPublicKey());
|
||||
} catch (MalformedAddressException e) {
|
||||
System.out.println("MalformedAddressException. Msg: "+e.getMessage());
|
||||
options = new AccountOptions();
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
64
app/src/main/java/cy/agorise/graphenej/Address.java
Normal file
64
app/src/main/java/cy/agorise/graphenej/Address.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import org.bitcoinj.core.Base58;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.spongycastle.crypto.digests.RIPEMD160Digest;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate address-related operations.
|
||||
*/
|
||||
public class Address {
|
||||
|
||||
public final static String BITSHARES_PREFIX = "BTS";
|
||||
|
||||
private PublicKey publicKey;
|
||||
private String prefix;
|
||||
|
||||
public Address(ECKey key) {
|
||||
this.publicKey = new PublicKey(key);
|
||||
this.prefix = BITSHARES_PREFIX;
|
||||
}
|
||||
|
||||
public Address(ECKey key, String prefix) {
|
||||
this.publicKey = new PublicKey(key);
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public Address(String address) throws MalformedAddressException {
|
||||
this.prefix = address.substring(0, 3);
|
||||
byte[] decoded = Base58.decode(address.substring(3, address.length()));
|
||||
byte[] pubKey = Arrays.copyOfRange(decoded, 0, decoded.length - 4);
|
||||
byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length);
|
||||
publicKey = new PublicKey(ECKey.fromPublicOnly(pubKey));
|
||||
byte[] calculatedChecksum = calculateChecksum(pubKey);
|
||||
for(int i = 0; i < calculatedChecksum.length; i++){
|
||||
if(checksum[i] != calculatedChecksum[i]){
|
||||
throw new MalformedAddressException("Checksum error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey(){
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
byte[] pubKey = this.publicKey.toBytes();
|
||||
byte[] checksum = calculateChecksum(pubKey);
|
||||
byte[] pubKeyChecksummed = Bytes.concat(pubKey, checksum);
|
||||
return this.prefix + Base58.encode(pubKeyChecksummed);
|
||||
}
|
||||
|
||||
private byte[] calculateChecksum(byte[] data){
|
||||
byte[] checksum = new byte[160 / 8];
|
||||
RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
|
||||
ripemd160Digest.update(data, 0, data.length);
|
||||
ripemd160Digest.doFinal(checksum, 0);
|
||||
return Arrays.copyOfRange(checksum, 0, 4);
|
||||
}
|
||||
}
|
187
app/src/main/java/cy/agorise/graphenej/Asset.java
Normal file
187
app/src/main/java/cy/agorise/graphenej/Asset.java
Normal file
|
@ -0,0 +1,187 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Asset extends GrapheneObject {
|
||||
public final static String TAG = "Asset";
|
||||
|
||||
public static final String KEY_ID = "id";
|
||||
public static final String KEY_SYMBOL = "symbol";
|
||||
public static final String KEY_PRECISION = "precision";
|
||||
public static final String KEY_ISSUER = "issuer";
|
||||
public static final String KEY_OPTIONS = "options";
|
||||
public static final String KEY_MAX_SUPPLY = "max_supply";
|
||||
public static final String KEY_MARKET_FEE_PERCENT = "market_fee_percent";
|
||||
public static final String KEY_MARKET_FEE = "max_market_fee";
|
||||
public static final String KEY_ISSUER_PERMISSIONS = "issuer_permissions";
|
||||
public static final String KEY_FLAGS = "flags";
|
||||
public static final String KEY_CORE_EXCHANGE_RATE = "core_exchange_rate";
|
||||
public static final String KEY_DESCRIPTION = "description";
|
||||
public static final String KEY_DYNAMIC_ASSET_DATA_ID = "dynamic_asset_data_id";
|
||||
public static final String KEY_BITASSET_DATA_ID = "bitasset_data_id";
|
||||
|
||||
/**
|
||||
* Enum type used to represent the possible types an asset can be classified into.
|
||||
*/
|
||||
public enum AssetType {
|
||||
CORE_ASSET,
|
||||
UIA,
|
||||
SMART_COIN,
|
||||
PREDICTION_MARKET
|
||||
}
|
||||
|
||||
private String symbol;
|
||||
private int precision = -1;
|
||||
private String issuer;
|
||||
private String description;
|
||||
private String dynamic_asset_data_id;
|
||||
private AssetOptions options;
|
||||
private String bitasset_data_id;
|
||||
private AssetType mAssetType;
|
||||
|
||||
/**
|
||||
* Simple constructor
|
||||
* @param id
|
||||
*/
|
||||
public Asset(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param id: The graphene object id.
|
||||
* @param symbol: The asset symbol.
|
||||
* @param precision: The asset precision.
|
||||
*/
|
||||
public Asset(String id, String symbol, int precision){
|
||||
super(id);
|
||||
this.symbol = symbol;
|
||||
this.precision = precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param id: The graphene object id.
|
||||
* @param symbol: The asset symbol.
|
||||
* @param precision: The asset precision.
|
||||
* @param issuer: Graphene object id of the issuer.
|
||||
*/
|
||||
public Asset(String id, String symbol, int precision, String issuer){
|
||||
super(id);
|
||||
this.symbol = symbol;
|
||||
this.precision = precision;
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
public String getSymbol(){
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
public void setSymbol(String symbol){
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public void setPrecision(int precision){
|
||||
this.precision = precision;
|
||||
}
|
||||
|
||||
public int getPrecision(){
|
||||
return this.precision;
|
||||
}
|
||||
|
||||
public void setIssuer(String issuer){ this.issuer = issuer; }
|
||||
|
||||
public String getIssuer() { return this.issuer; }
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setAssetOptions(AssetOptions options){
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public AssetOptions getAssetOptions(){
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public String getBitassetId(){
|
||||
return this.bitasset_data_id;
|
||||
}
|
||||
|
||||
public void setBitassetDataId(String id){
|
||||
this.bitasset_data_id = id;
|
||||
}
|
||||
|
||||
public AssetType getAssetType() {
|
||||
return mAssetType;
|
||||
}
|
||||
|
||||
public void setAssetType(AssetType mAssetType) {
|
||||
this.mAssetType = mAssetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getObjectId() == null ? 0 : this.getObjectId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if(other instanceof Asset){
|
||||
return this.getObjectId().equals(((Asset)other).getObjectId());
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used to instantiate a simple version of the Asset class from the response of the
|
||||
* 'lookup_asset_symbols' API call.
|
||||
*/
|
||||
public static class AssetDeserializer implements JsonDeserializer<Asset> {
|
||||
|
||||
@Override
|
||||
public Asset deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
String id = object.get(KEY_ID).getAsString();
|
||||
String symbol = object.get(KEY_SYMBOL).getAsString();
|
||||
int precision = object.get(KEY_PRECISION).getAsInt();
|
||||
String issuer = object.get(KEY_ISSUER).getAsString();
|
||||
JsonObject optionsJson = object.get(KEY_OPTIONS).getAsJsonObject();
|
||||
JsonElement bitassetDataId = object.get(KEY_BITASSET_DATA_ID);
|
||||
|
||||
// Deserializing asset options
|
||||
AssetOptions options = new AssetOptions();
|
||||
options.setMaxSupply(UnsignedLong.valueOf(optionsJson.get(KEY_MAX_SUPPLY).getAsString()));
|
||||
options.setMarketFeePercent(optionsJson.get(KEY_MARKET_FEE_PERCENT).getAsInt());
|
||||
options.setMaxMarketFee(UnsignedLong.valueOf(optionsJson.get(KEY_MARKET_FEE).getAsString()));
|
||||
options.setIssuerPermissions(optionsJson.get(KEY_ISSUER_PERMISSIONS).getAsLong());
|
||||
options.setFlags(optionsJson.get(KEY_FLAGS).getAsInt());
|
||||
if(optionsJson.has(KEY_DESCRIPTION))
|
||||
options.setDescription(optionsJson.get(KEY_DESCRIPTION).getAsString());
|
||||
//TODO: Deserialize core_exchange_rate field
|
||||
|
||||
Asset asset = new Asset(id, symbol, precision, issuer);
|
||||
asset.setAssetOptions(options);
|
||||
if(bitassetDataId != null){
|
||||
asset.setBitassetDataId(bitassetDataId.getAsString());
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
}
|
||||
}
|
198
app/src/main/java/cy/agorise/graphenej/AssetAmount.java
Normal file
198
app/src/main/java/cy/agorise/graphenej/AssetAmount.java
Normal file
|
@ -0,0 +1,198 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.math.DoubleMath;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import cy.agorise.graphenej.errors.IncompatibleOperation;
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two asset amounts. They must refer to the same Asset type.
|
||||
* @param other: The other AssetAmount to add to this.
|
||||
* @return: A new instance of the AssetAmount class with the combined amount.
|
||||
*/
|
||||
public AssetAmount add(AssetAmount other){
|
||||
if(!this.getAsset().getObjectId().equals(other.getAsset().getObjectId())){
|
||||
throw new IncompatibleOperation("Cannot add two AssetAmount instances that refer to different assets");
|
||||
}
|
||||
UnsignedLong combined = this.amount.plus(other.getAmount());
|
||||
return new AssetAmount(combined, asset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an aditional amount of base units to this AssetAmount.
|
||||
* @param additional: The amount to add.
|
||||
* @return: A new instance of the AssetAmount class with the added aditional.
|
||||
*/
|
||||
public AssetAmount add(long additional){
|
||||
UnsignedLong combined = this.amount.plus(UnsignedLong.valueOf(additional));
|
||||
return new AssetAmount(combined, asset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts another instance of AssetAmount from this one. This method will always
|
||||
* return absolute values.
|
||||
* @param other: The other asset amount to subtract from this.
|
||||
* @return: The absolute value of the subtraction of the other minus this asset amount.
|
||||
*/
|
||||
public AssetAmount subtract(AssetAmount other){
|
||||
if(!this.getAsset().getObjectId().equals(other.getAsset().getObjectId())){
|
||||
throw new IncompatibleOperation("Cannot subtract two AssetAmount instances that refer to different assets");
|
||||
}
|
||||
UnsignedLong result = null;
|
||||
if(this.amount.compareTo(other.getAmount()) < 0){
|
||||
result = other.getAmount().minus(this.amount);
|
||||
}else{
|
||||
result = this.amount.minus(other.getAmount());
|
||||
}
|
||||
return new AssetAmount(result, asset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the current amount by a factor provided as the first parameter. The second parameter
|
||||
* specifies the rounding method to be used.
|
||||
* @param factor: The multiplying factor
|
||||
* @param roundingMode: The rounding mode as an instance of the {@link RoundingMode} class
|
||||
* @return The same AssetAmount instance, but with the changed amount value.
|
||||
*/
|
||||
public AssetAmount multiplyBy(double factor, RoundingMode roundingMode){
|
||||
this.amount = UnsignedLong.valueOf(DoubleMath.roundToLong(this.amount.longValue() * factor, roundingMode));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the current amount by a factor, using the {@link RoundingMode#HALF_DOWN} constant.
|
||||
* @param factor: The multiplying factor
|
||||
* @return The same AssetAmount instance, but with the changed amount value.
|
||||
*/
|
||||
public AssetAmount multiplyBy(double factor){
|
||||
return this.multiplyBy(factor, RoundingMode.HALF_DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides the current amount by a divisor provided as the first parameter. The second parameter
|
||||
* specifies the rounding method to be used.
|
||||
* @param divisor: The divisor
|
||||
* @return: The same AssetAMount instance, but with the divided amount value
|
||||
*/
|
||||
public AssetAmount dividedBy(double divisor, RoundingMode roundingMode){
|
||||
this.amount = UnsignedLong.valueOf(DoubleMath.roundToLong(this.amount.longValue() / divisor, roundingMode));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Divides the current amount by a divisor provided as the first parameter, using
|
||||
* the {@link RoundingMode#HALF_DOWN} constant
|
||||
* @param divisor: The divisor
|
||||
* @return: The same AssetAMount instance, but with the divided amount value
|
||||
*/
|
||||
public AssetAmount dividedBy(double divisor){
|
||||
return this.dividedBy(divisor, RoundingMode.HALF_DOWN);
|
||||
}
|
||||
|
||||
public void setAmount(UnsignedLong amount){
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public UnsignedLong getAmount(){
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public Asset getAsset(){ return this.asset; }
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(byteArrayOutputStream);
|
||||
try {
|
||||
Varint.writeUnsignedVarLong(asset.instance, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Getting asset id
|
||||
byte[] assetId = byteArrayOutputStream.toByteArray();
|
||||
byte[] value = Util.revertLong(this.amount.longValue());
|
||||
|
||||
// Concatenating and returning value + asset id
|
||||
return Bytes.concat(value, assetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmountSerializer());
|
||||
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 AssetAmountSerializer 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used for this class
|
||||
*/
|
||||
public static class AssetAmountDeserializer 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;
|
||||
}
|
||||
}
|
||||
}
|
84
app/src/main/java/cy/agorise/graphenej/AssetOptions.java
Normal file
84
app/src/main/java/cy/agorise/graphenej/AssetOptions.java
Normal file
|
@ -0,0 +1,84 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/13/16.
|
||||
*/
|
||||
public class AssetOptions {
|
||||
// TODO: Use these constants instead of using cryptic constants like 128 and 511
|
||||
public static final int CHARGE_MARKET_FEE = 0x01;
|
||||
public static final int WHITE_LIST = 0x02;
|
||||
public static final int OVERRIDE_AUTHORITY = 0x04;
|
||||
public static final int TRANSFER_RESTRICTED = 0x08;
|
||||
public static final int DISABLE_FORCE_SETTLE = 0x10;
|
||||
public static final int GLOBAL_SETTLE = 0x20;
|
||||
public static final int DISABLE_CONFIDENTIAL = 0x40;
|
||||
public static final int WITNESS_FED_ASSET = 0x80;
|
||||
public static final int COMITEE_FED_ASSET = 0x100;
|
||||
|
||||
private UnsignedLong max_supply;
|
||||
private float market_fee_percent;
|
||||
private UnsignedLong max_market_fee;
|
||||
private long issuer_permissions;
|
||||
private int flags;
|
||||
private Price core_exchange_rate;
|
||||
//TODO: Implement whitelist_authorities, blacklist_authorities, whitelist_markets, blacklist_markets and extensions
|
||||
private String description;
|
||||
|
||||
public UnsignedLong getMaxSupply() {
|
||||
return max_supply;
|
||||
}
|
||||
|
||||
public void setMaxSupply(UnsignedLong max_supply) {
|
||||
this.max_supply = max_supply;
|
||||
}
|
||||
|
||||
public float getMarketFeePercent() {
|
||||
return market_fee_percent;
|
||||
}
|
||||
|
||||
public void setMarketFeePercent(float market_fee_percent) {
|
||||
this.market_fee_percent = market_fee_percent;
|
||||
}
|
||||
|
||||
public UnsignedLong getMaxMarketFee() {
|
||||
return max_market_fee;
|
||||
}
|
||||
|
||||
public void setMaxMarketFee(UnsignedLong max_market_fee) {
|
||||
this.max_market_fee = max_market_fee;
|
||||
}
|
||||
|
||||
public long getIssuerPermissions() {
|
||||
return issuer_permissions;
|
||||
}
|
||||
|
||||
public void setIssuerPermissions(long issuer_permissions) {
|
||||
this.issuer_permissions = issuer_permissions;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(int flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public Price getCoreExchangeRate() {
|
||||
return core_exchange_rate;
|
||||
}
|
||||
|
||||
public void setCoreExchangeRate(Price core_exchange_rate) {
|
||||
this.core_exchange_rate = core_exchange_rate;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
236
app/src/main/java/cy/agorise/graphenej/Authority.java
Normal file
236
app/src/main/java/cy/agorise/graphenej/Authority.java
Normal file
|
@ -0,0 +1,236 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import cy.agorise.graphenej.interfaces.GrapheneSerializable;
|
||||
|
||||
/**
|
||||
* Class used to represent the weighted set of keys and accounts that must approve operations.
|
||||
*
|
||||
* {@see <a href="https://bitshares.org/doxygen/structgraphene_1_1chain_1_1authority.html">Authority</a>}
|
||||
*/
|
||||
public class Authority implements GrapheneSerializable {
|
||||
public static final String KEY_ACCOUNT_AUTHS = "account_auths";
|
||||
public static final String KEY_KEY_AUTHS = "key_auths";
|
||||
public static final String KEY_WEIGHT_THRESHOLD = "weight_threshold";
|
||||
public static final String KEY_EXTENSIONS = "extensions";
|
||||
|
||||
private long weight_threshold;
|
||||
private HashMap<UserAccount, Long> account_auths;
|
||||
private HashMap<PublicKey, Long> key_auths;
|
||||
private Extensions extensions;
|
||||
|
||||
public Authority(){
|
||||
this.weight_threshold = 1;
|
||||
this.account_auths = new HashMap<UserAccount, Long>();
|
||||
this.key_auths = new HashMap<PublicKey, Long>();
|
||||
extensions = new Extensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the authority class that takes every possible detail.
|
||||
* @param weight_threshold: The total weight threshold
|
||||
* @param keyAuths: Map of key to weights relationships. Can be null.
|
||||
* @param accountAuths: Map of account to weights relationships. Can be null.
|
||||
* @throws MalformedAddressException
|
||||
*/
|
||||
public Authority(long weight_threshold, HashMap<PublicKey, Long> keyAuths, HashMap<UserAccount, Long> accountAuths) {
|
||||
this();
|
||||
this.weight_threshold = weight_threshold;
|
||||
if(keyAuths != null)
|
||||
this.key_auths = keyAuths;
|
||||
else
|
||||
this.key_auths = new HashMap<>();
|
||||
if(accountAuths != null)
|
||||
this.account_auths = accountAuths;
|
||||
else
|
||||
this.account_auths = new HashMap<>();
|
||||
}
|
||||
|
||||
public long getWeightThreshold() {
|
||||
return weight_threshold;
|
||||
}
|
||||
|
||||
public void setWeightThreshold(long weight_threshold) {
|
||||
this.weight_threshold = weight_threshold;
|
||||
}
|
||||
|
||||
public void setKeyAuthorities(HashMap<Address, Long> keyAuths){
|
||||
if(keyAuths != null){
|
||||
for(Address address : keyAuths.keySet()){
|
||||
key_auths.put(address.getPublicKey(), keyAuths.get(address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setAccountAuthorities(HashMap<UserAccount, Long> accountAuthorities){
|
||||
this.account_auths = accountAuthorities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return: Returns a list of public keys linked to this authority
|
||||
*/
|
||||
public List<PublicKey> getKeyAuthList(){
|
||||
ArrayList<PublicKey> keys = new ArrayList<>();
|
||||
for(PublicKey pk : key_auths.keySet()){
|
||||
keys.add(pk);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return: Returns a list of accounts linked to this authority
|
||||
*/
|
||||
public List<UserAccount> getAccountAuthList(){
|
||||
ArrayList<UserAccount> accounts = new ArrayList<>();
|
||||
for(UserAccount account : account_auths.keySet()){
|
||||
accounts.add(account);
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public HashMap<PublicKey, Long> getKeyAuths(){
|
||||
return this.key_auths;
|
||||
}
|
||||
|
||||
public HashMap<UserAccount, Long> getAccountAuths(){
|
||||
return this.account_auths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonObject authority = new JsonObject();
|
||||
authority.addProperty(KEY_WEIGHT_THRESHOLD, weight_threshold);
|
||||
JsonArray keyAuthArray = new JsonArray();
|
||||
JsonArray accountAuthArray = new JsonArray();
|
||||
|
||||
for(PublicKey publicKey : key_auths.keySet()){
|
||||
JsonArray subArray = new JsonArray();
|
||||
Address address = new Address(publicKey.getKey());
|
||||
subArray.add(address.toString());
|
||||
subArray.add(key_auths.get(publicKey));
|
||||
keyAuthArray.add(subArray);
|
||||
}
|
||||
|
||||
for(UserAccount key : account_auths.keySet()){
|
||||
JsonArray subArray = new JsonArray();
|
||||
subArray.add(key.toString());
|
||||
subArray.add(key_auths.get(key));
|
||||
accountAuthArray.add(subArray);
|
||||
}
|
||||
authority.add(KEY_KEY_AUTHS, keyAuthArray);
|
||||
authority.add(KEY_ACCOUNT_AUTHS, accountAuthArray);
|
||||
authority.add(KEY_EXTENSIONS, extensions.toJsonObject());
|
||||
return authority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
List<Byte> byteArray = new ArrayList<Byte>();
|
||||
// Adding number of authorities
|
||||
byteArray.add(Byte.valueOf((byte) (account_auths.size() + key_auths.size())));
|
||||
|
||||
// If the authority is not empty of references, we serialize its contents
|
||||
// otherwise its only contribution will be a zero byte
|
||||
if(account_auths.size() + key_auths.size() > 0){
|
||||
// Weight threshold
|
||||
byteArray.addAll(Bytes.asList(Util.revertInteger(new Integer((int) weight_threshold))));
|
||||
|
||||
// Number of account authorities
|
||||
byteArray.add((byte) account_auths.size());
|
||||
|
||||
//TODO: Check the account authorities serialization
|
||||
// Serializing individual accounts and their corresponding weights
|
||||
for(UserAccount account : account_auths.keySet()){
|
||||
byteArray.addAll(Bytes.asList(account.toBytes()));
|
||||
byteArray.addAll(Bytes.asList(Util.revertShort(account_auths.get(account).shortValue())));
|
||||
}
|
||||
|
||||
// Number of key authorities
|
||||
byteArray.add((byte) key_auths.size());
|
||||
|
||||
// Serializing individual keys and their corresponding weights
|
||||
for(PublicKey publicKey : key_auths.keySet()){
|
||||
byteArray.addAll(Bytes.asList(publicKey.toBytes()));
|
||||
byteArray.addAll(Bytes.asList(Util.revertShort(key_auths.get(publicKey).shortValue())));
|
||||
}
|
||||
|
||||
// Adding number of extensions
|
||||
byteArray.add((byte) extensions.size());
|
||||
}
|
||||
return Bytes.toArray(byteArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
Authority authority = (Authority) obj;
|
||||
HashMap<PublicKey, Long> keyAuths = authority.getKeyAuths();
|
||||
HashMap<UserAccount, Long> accountAuths = authority.getAccountAuths();
|
||||
System.out.println("key auths match: "+this.key_auths.equals(keyAuths));
|
||||
System.out.println("account auths match: "+this.account_auths.equals(accountAuths));
|
||||
System.out.println("weight threshold matches: "+(this.weight_threshold == authority.weight_threshold));
|
||||
return this.key_auths.equals(keyAuths) &&
|
||||
this.account_auths.equals(accountAuths) &&
|
||||
this.weight_threshold == authority.weight_threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used while parsing the 'get_account_by_name' API call response.
|
||||
*
|
||||
* This will deserialize an account authority in the form:
|
||||
*
|
||||
* {
|
||||
* "weight_threshold": 1,
|
||||
* "account_auths": [],
|
||||
* "key_auths": [["BTS6yoiaoC4p23n31AV4GnMy5QDh5yUQEUmU4PmNxRQPGg7jjPkBq",1]],
|
||||
* "address_auths": []
|
||||
* }
|
||||
*/
|
||||
public static class AuthorityDeserializer implements JsonDeserializer<Authority> {
|
||||
|
||||
@Override
|
||||
public Authority deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject baseObject = json.getAsJsonObject();
|
||||
long weightThreshold = baseObject.get(KEY_WEIGHT_THRESHOLD).getAsLong();
|
||||
JsonArray keyAuthArray = baseObject.getAsJsonArray(KEY_KEY_AUTHS);
|
||||
JsonArray accountAuthArray = baseObject.getAsJsonArray(KEY_ACCOUNT_AUTHS);
|
||||
HashMap<PublicKey, Long> keyAuthMap = new HashMap<>();
|
||||
HashMap<UserAccount, Long> accountAuthMap = new HashMap<>();
|
||||
for(int i = 0; i < keyAuthArray.size(); i++){
|
||||
JsonArray subArray = keyAuthArray.get(i).getAsJsonArray();
|
||||
String addr = subArray.get(0).getAsString();
|
||||
long weight = subArray.get(1).getAsLong();
|
||||
try {
|
||||
keyAuthMap.put(new Address(addr).getPublicKey(), weight);
|
||||
} catch (MalformedAddressException e) {
|
||||
System.out.println("MalformedAddressException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < accountAuthArray.size(); i++){
|
||||
JsonArray subArray = accountAuthArray.get(i).getAsJsonArray();
|
||||
String userId = subArray.get(0).getAsString();
|
||||
long weight = subArray.get(1).getAsLong();
|
||||
UserAccount userAccount = new UserAccount(userId);
|
||||
accountAuthMap.put(userAccount, weight);
|
||||
}
|
||||
return new Authority(weightThreshold, keyAuthMap, accountAuthMap);
|
||||
}
|
||||
}
|
||||
}
|
78
app/src/main/java/cy/agorise/graphenej/BIP39.java
Normal file
78
app/src/main/java/cy/agorise/graphenej/BIP39.java
Normal file
|
@ -0,0 +1,78 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.bitcoinj.core.Base58;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.crypto.HDKeyDerivation;
|
||||
import org.bitcoinj.crypto.MnemonicCode;
|
||||
import org.spongycastle.crypto.digests.RIPEMD160Digest;
|
||||
import org.spongycastle.crypto.digests.SHA512Digest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hvarona
|
||||
*/
|
||||
public class BIP39 {
|
||||
|
||||
private final ECKey mPrivateKey;
|
||||
|
||||
public BIP39(String words, String passphrase) {
|
||||
|
||||
byte[] seed = MnemonicCode.toSeed(Arrays.asList(words.split(" ")), passphrase);
|
||||
mPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed);
|
||||
|
||||
}
|
||||
|
||||
public String getUncompressedAddress() {
|
||||
RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
|
||||
SHA512Digest sha512Digest = new SHA512Digest();
|
||||
sha512Digest.update(mPrivateKey.decompress().getPubKey(), 0, mPrivateKey.decompress().getPubKey().length);
|
||||
byte[] intermediate = new byte[512 / 8];
|
||||
sha512Digest.doFinal(intermediate, 0);
|
||||
ripemd160Digest.update(intermediate, 0, intermediate.length);
|
||||
byte[] output = new byte[160 / 8];
|
||||
ripemd160Digest.doFinal(output, 0);
|
||||
String encoded = Base58.encode(output);
|
||||
byte[] checksum = new byte[(160 / 8) + 4];
|
||||
System.arraycopy(calculateChecksum(output), 0, checksum, checksum.length - 4, 4);
|
||||
System.arraycopy(output, 0, checksum, 0, output.length);
|
||||
|
||||
return ("BTS" + Base58.encode(checksum));
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
|
||||
SHA512Digest sha512Digest = new SHA512Digest();
|
||||
sha512Digest.update(mPrivateKey.getPubKey(), 0, mPrivateKey.getPubKey().length);
|
||||
byte[] intermediate = new byte[512 / 8];
|
||||
sha512Digest.doFinal(intermediate, 0);
|
||||
ripemd160Digest.update(intermediate, 0, intermediate.length);
|
||||
byte[] output = new byte[160 / 8];
|
||||
ripemd160Digest.doFinal(output, 0);
|
||||
String encoded = Base58.encode(output);
|
||||
byte[] checksum = new byte[(160 / 8) + 4];
|
||||
System.arraycopy(calculateChecksum(output), 0, checksum, checksum.length - 4, 4);
|
||||
System.arraycopy(output, 0, checksum, 0, output.length);
|
||||
|
||||
return ("BTS" + Base58.encode(checksum));
|
||||
}
|
||||
|
||||
public byte[] calculateChecksum(byte[] input) {
|
||||
byte[] answer = new byte[4];
|
||||
RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
|
||||
ripemd160Digest.update(input, 0, input.length);
|
||||
byte[] output = new byte[160 / 8];
|
||||
ripemd160Digest.doFinal(output, 0);
|
||||
System.arraycopy(output, 0, answer, 0, 4);
|
||||
return answer;
|
||||
}
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return mPrivateKey.getPubKey();
|
||||
}
|
||||
|
||||
public ECKey getPrivateKey() {
|
||||
return mPrivateKey;
|
||||
}
|
||||
|
||||
}
|
35
app/src/main/java/cy/agorise/graphenej/BaseOperation.java
Normal file
35
app/src/main/java/cy/agorise/graphenej/BaseOperation.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/5/16.
|
||||
*/
|
||||
public abstract class BaseOperation implements ByteSerializable, JsonSerializable {
|
||||
|
||||
public static final String KEY_FEE = "fee";
|
||||
public static final String KEY_EXTENSIONS = "extensions";
|
||||
|
||||
protected OperationType type;
|
||||
protected Extensions extensions;
|
||||
|
||||
public BaseOperation(OperationType type){
|
||||
this.type = type;
|
||||
this.extensions = new Extensions();
|
||||
}
|
||||
|
||||
public byte getId() {
|
||||
return (byte) this.type.ordinal();
|
||||
}
|
||||
|
||||
public abstract void setFee(AssetAmount assetAmount);
|
||||
|
||||
public JsonElement toJsonObject(){
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
return array;
|
||||
}
|
||||
}
|
126
app/src/main/java/cy/agorise/graphenej/BlockData.java
Normal file
126
app/src/main/java/cy/agorise/graphenej/BlockData.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
|
||||
/**
|
||||
* 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 expiration;
|
||||
|
||||
/**
|
||||
* 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: Expiration time specified as a POSIX or
|
||||
* <a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a>
|
||||
*/
|
||||
public BlockData(int ref_block_num, long ref_block_prefix, long relative_expiration){
|
||||
this.refBlockNum = ref_block_num;
|
||||
this.refBlockPrefix = ref_block_prefix;
|
||||
this.expiration = 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 expiration time.
|
||||
*/
|
||||
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.setRefBlockNum(head_block_number);
|
||||
this.setRefBlockPrefix(head_block_id);
|
||||
this.expiration = relative_expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain setter for the ref_block_num field, assuming its value is exactly the one passed in the argument.
|
||||
* @param refBlockNum: The 'ref_block_num' field.
|
||||
*/
|
||||
public void setRefBlockNum(int refBlockNum) {
|
||||
this.refBlockNum = refBlockNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter that receives the block number, and takes the 16 lower bits of it to obtain the
|
||||
* 'ref_block_num' value.
|
||||
* @param blockNumber: The block number.
|
||||
*/
|
||||
public void setRefBlockNum(long blockNumber){
|
||||
this.refBlockNum = ((int) blockNumber ) & 0xFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain setter fot the 'ref_block_prefix' field, assumint its value is exactly the one passed in the argument.
|
||||
* @param refBlockPrefix
|
||||
*/
|
||||
public void setRefBlockPrefix(long refBlockPrefix) {
|
||||
this.refBlockPrefix = refBlockPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter that receives the head block id, and turns it into the little format required for the
|
||||
* 'ref_block_prefix' field.
|
||||
* @param headBlockId: The head block id as obtained from the network updates.
|
||||
*/
|
||||
public void setRefBlockPrefix(String headBlockId){
|
||||
String hashData = headBlockId.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.refBlockPrefix = Long.parseLong(builder.toString(), 16);
|
||||
}
|
||||
|
||||
public int getRefBlockNum() {
|
||||
return refBlockNum;
|
||||
}
|
||||
|
||||
public long getRefBlockPrefix() {
|
||||
return refBlockPrefix;
|
||||
}
|
||||
|
||||
public long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(long expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
|
||||
@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.expiration >> 8 * (i - REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
138
app/src/main/java/cy/agorise/graphenej/BrainKey.java
Normal file
138
app/src/main/java/cy/agorise/graphenej/BrainKey.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import org.bitcoinj.core.DumpedPrivateKey;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.crypto.SecureRandomGenerator;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate all BrainKey-related operations.
|
||||
*/
|
||||
public class BrainKey {
|
||||
|
||||
// The size of the word dictionary
|
||||
public static final int DICT_WORD_COUNT = 49744;
|
||||
|
||||
/* The required number of words */
|
||||
public static final int BRAINKEY_WORD_COUNT = 12;
|
||||
|
||||
/* The default sequence number is zero */
|
||||
public static final int DEFAULT_SEQUENCE_NUMBER = 0;
|
||||
|
||||
/* The corresponding private key derivated from the brain key */
|
||||
private ECKey mPrivateKey;
|
||||
|
||||
/* The actual words from this brain key + the sequence number */
|
||||
private String mBrainKey;
|
||||
|
||||
/* The sequence number */
|
||||
private int sequenceNumber;
|
||||
|
||||
/**
|
||||
* Method that will generate a random brain key
|
||||
*
|
||||
* @param words The list of words from the graphene specification
|
||||
* dictionary.
|
||||
* @return A random sequence of words
|
||||
*/
|
||||
public static String suggest(String words) {
|
||||
String[] wordArray = words.split(",");
|
||||
ArrayList<String> suggestedBrainKey = new ArrayList<String>();
|
||||
assert (wordArray.length == DICT_WORD_COUNT);
|
||||
SecureRandom secureRandom = SecureRandomGenerator.getSecureRandom();
|
||||
int index;
|
||||
for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) {
|
||||
index = secureRandom.nextInt(DICT_WORD_COUNT - 1);
|
||||
suggestedBrainKey.add(wordArray[index].toUpperCase());
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for(String word : suggestedBrainKey){
|
||||
stringBuilder.append(word);
|
||||
stringBuilder.append(" ");
|
||||
}
|
||||
return stringBuilder.toString().trim();
|
||||
}
|
||||
/**
|
||||
* BrainKey constructor that takes as argument a specific brain key word
|
||||
* sequence and generates the private key and address from that.
|
||||
*
|
||||
* @param words The brain key specifying the private key
|
||||
* @param sequence Sequence number
|
||||
*/
|
||||
public BrainKey(String words, int sequence) {
|
||||
this.mBrainKey = words;
|
||||
this.sequenceNumber = 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);
|
||||
mPrivateKey = ECKey.fromPrivate(result);
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgotithmException. Msg: " + e.getMessage());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
System.out.println("UnsupportedEncodingException. Msg: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of bytes representing the public key.
|
||||
* @return
|
||||
*/
|
||||
public byte[] getPublicKey() {
|
||||
return mPrivateKey.getPubKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private key as an instance of the ECKey class.
|
||||
* @return
|
||||
*/
|
||||
public ECKey getPrivateKey() {
|
||||
return mPrivateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private key in the Wallet Import Format for the uncompressed private key.
|
||||
* @see <a href="https://en.bitcoin.it/wiki/Wallet_import_format">WIF</a>
|
||||
* @return
|
||||
*/
|
||||
public String getWalletImportFormat(){
|
||||
DumpedPrivateKey wif = this.mPrivateKey.decompress().getPrivateKeyEncoded(NetworkParameters.fromID(NetworkParameters.ID_MAINNET));
|
||||
return wif.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public address derived from this brain key
|
||||
* @param prefix: The prefix to use in this address.
|
||||
* @return An instance of the {@link Address} class
|
||||
*/
|
||||
public Address getPublicAddress(String prefix){
|
||||
return new Address(ECKey.fromPublicOnly(getPublicKey()), prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Brain key words getter
|
||||
* @return: The word sequence that comprises this brain key
|
||||
*/
|
||||
public String getBrainKey(){
|
||||
return mBrainKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence number getter
|
||||
* @return: The sequence number used alongside with the brain key words in order
|
||||
* to derive the private key
|
||||
*/
|
||||
public int getSequenceNumber(){
|
||||
return sequenceNumber;
|
||||
}
|
||||
}
|
16
app/src/main/java/cy/agorise/graphenej/Chains.java
Normal file
16
app/src/main/java/cy/agorise/graphenej/Chains.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
}
|
160
app/src/main/java/cy/agorise/graphenej/Converter.java
Normal file
160
app/src/main/java/cy/agorise/graphenej/Converter.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
|
||||
import cy.agorise.graphenej.errors.IncompleteAssetError;
|
||||
import cy.agorise.graphenej.models.BucketObject;
|
||||
|
||||
/**
|
||||
* Generic converter class used to translate the market information contained in a BucketObject and/or Price instances.
|
||||
*
|
||||
* Created by nelson on 12/23/16.
|
||||
*/
|
||||
public class Converter {
|
||||
private final String TAG = this.getClass().getName();
|
||||
public static final int OPEN_VALUE = 0;
|
||||
public static final int CLOSE_VALUE = 1;
|
||||
public static final int HIGH_VALUE = 2;
|
||||
public static final int LOW_VALUE = 3;
|
||||
|
||||
public static final int BASE_TO_QUOTE = 100;
|
||||
public static final int QUOTE_TO_BASE = 101;
|
||||
|
||||
private Asset base;
|
||||
private Asset quote;
|
||||
private BucketObject bucket;
|
||||
|
||||
/**
|
||||
* Constructor meant to be used trying to perform a conversion and in possession of a Price object.
|
||||
*/
|
||||
public Converter(){}
|
||||
|
||||
/**
|
||||
* Constructor meant to be used when trying to perform a conversion and in possession of
|
||||
* a BucketObject, typically resulting from a 'get_market_history' API call.
|
||||
* @param base
|
||||
* @param quote
|
||||
* @param bucket
|
||||
*/
|
||||
public Converter(Asset base, Asset quote, BucketObject bucket){
|
||||
this.base = base;
|
||||
this.quote = quote;
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to obtain the equivalence between two assets considering their precisions
|
||||
* and given the specific time bucket passed in the constructor.
|
||||
*
|
||||
* The resulting double value will tell us how much of a given asset, a unit of
|
||||
* its pair is worth.
|
||||
*
|
||||
* The second argument is used to specify which of the assets should
|
||||
* be taken as a unit reference.
|
||||
*
|
||||
* For instance if used with the BASE_TO_QUOTE constant, this method will tell us how
|
||||
* many of the quote asset will make up for a unit of the base asset. And the opposite
|
||||
* is true for the QUOTE_TO_BASE contant.
|
||||
*
|
||||
* @param bucketAttribute: The desired bucket attribute to take in consideration. Can
|
||||
* be any of the following: OPEN_VALUE, CLOSE_VALUE, HIGH_VALUE or
|
||||
* LOW_VALUE.
|
||||
* @param direction: One of two constants 'BASE_TO_QUOTE' or 'QUOTE_TO_BASE' used to specify
|
||||
* which of the two assets is the one used as a unitary reference.
|
||||
* @return: double value representing how much of one asset, a unit of the paired asset
|
||||
* was worth at the point in time specified by the time bucket and the bucket parameter.
|
||||
*/
|
||||
public double getConversionRate(int bucketAttribute, int direction){
|
||||
if(this.base.getPrecision() == -1 || this.quote.getPrecision() == -1){
|
||||
throw new IncompleteAssetError();
|
||||
}
|
||||
BigDecimal baseValue;
|
||||
BigDecimal quoteValue;
|
||||
switch (bucketAttribute){
|
||||
case OPEN_VALUE:
|
||||
baseValue = bucket.open_base;
|
||||
quoteValue = bucket.open_quote;
|
||||
break;
|
||||
case CLOSE_VALUE:
|
||||
baseValue = bucket.close_base;
|
||||
quoteValue = bucket.close_quote;
|
||||
break;
|
||||
case HIGH_VALUE:
|
||||
baseValue = bucket.high_base;
|
||||
quoteValue = bucket.high_quote;
|
||||
break;
|
||||
case LOW_VALUE:
|
||||
baseValue = bucket.low_base;
|
||||
quoteValue = bucket.low_quote;
|
||||
break;
|
||||
default:
|
||||
baseValue = bucket.close_base;
|
||||
quoteValue = bucket.close_quote;
|
||||
}
|
||||
double basePrecisionAdjusted = baseValue.divide(BigDecimal.valueOf((long) Math.pow(10, base.getPrecision()))).doubleValue();
|
||||
double quotePrecisionAdjusted = quoteValue.divide(BigDecimal.valueOf((long) Math.pow(10, quote.getPrecision()))).doubleValue();
|
||||
if(direction == QUOTE_TO_BASE){
|
||||
return basePrecisionAdjusted / quotePrecisionAdjusted;
|
||||
}else{
|
||||
return quotePrecisionAdjusted / basePrecisionAdjusted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given asset amount to the corresponding pair used when creating this class.
|
||||
* @param assetAmount: The asset to convert from.
|
||||
* @param bucketAttribute: The bucket attribute to use as a reference. Possible values are OPEN_VALUE,
|
||||
* CLOSE_VALUE, HIGH_VALUE or LOW_VALUE.
|
||||
* @return: The converted value in base units, that is the number of a unit x 10^precision
|
||||
*/
|
||||
public long convert(AssetAmount assetAmount, int bucketAttribute) {
|
||||
double conversionRate = 0;
|
||||
double precisionFactor = 0.0;
|
||||
if(assetAmount.getAsset().equals(this.base)){
|
||||
conversionRate = this.getConversionRate(bucketAttribute, BASE_TO_QUOTE);
|
||||
precisionFactor = Math.pow(10, this.quote.getPrecision()) / Math.pow(10, this.base.getPrecision());
|
||||
}else if(assetAmount.getAsset().equals(this.quote)){
|
||||
conversionRate = this.getConversionRate(bucketAttribute, QUOTE_TO_BASE);
|
||||
precisionFactor = Math.pow(10, this.base.getPrecision()) / Math.pow(10, this.quote.getPrecision());
|
||||
}
|
||||
long assetAmountValue = assetAmount.getAmount().longValue();
|
||||
long convertedBaseValue = (long) (assetAmountValue * conversionRate * precisionFactor);
|
||||
return convertedBaseValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to obtain the conversion rate between two assets given in a Price instance as recovered by the
|
||||
* 'get_limit_orders' API call.
|
||||
*
|
||||
* The same rules that apply for the {@link #getConversionRate(int bucketAttribute, int direction) getConversionRate}
|
||||
* are valid for the 'direction' argument.
|
||||
*
|
||||
* @param price: The Price object instance
|
||||
* @param direction: The direction from which to perform the conversion, can be only one of BASE_TO_QUOTE or
|
||||
* QUOTE_TO_BASE.
|
||||
* @return: A double representing the exchange rate.
|
||||
*/
|
||||
public double getConversionRate(Price price, int direction){
|
||||
Asset base = price.base.getAsset();
|
||||
Asset quote = price.quote.getAsset();
|
||||
if(base.getPrecision() == -1 || quote.getPrecision() == -1){
|
||||
throw new IncompleteAssetError("The given asset instance must provide precision information");
|
||||
}
|
||||
double conversionRate = 0;
|
||||
double precisionFactor = 0.0;
|
||||
MathContext mathContext = new MathContext(Math.max(base.getPrecision(), quote.getPrecision()));
|
||||
BigDecimal baseValue = BigDecimal.valueOf(price.base.getAmount().longValue());
|
||||
BigDecimal quoteValue = BigDecimal.valueOf(price.quote.getAmount().doubleValue());
|
||||
// System.out.println(String.format("base: %d, quote: %d", baseValue.longValue(), quoteValue.longValue()));
|
||||
if(direction == BASE_TO_QUOTE){
|
||||
conversionRate = quoteValue.divide(baseValue, mathContext).doubleValue();
|
||||
precisionFactor = Math.pow(10, base.getPrecision()) / Math.pow(10, quote.getPrecision());
|
||||
}else{
|
||||
conversionRate = baseValue.divide(quoteValue, mathContext).doubleValue();
|
||||
precisionFactor = Math.pow(10, quote.getPrecision()) / Math.pow(10, base.getPrecision());
|
||||
}
|
||||
// System.out.println(String.format("conversion rate: %.4f, precision factor: %.2f", conversionRate, precisionFactor));
|
||||
return conversionRate * precisionFactor;
|
||||
}
|
||||
}
|
43
app/src/main/java/cy/agorise/graphenej/Extensions.java
Normal file
43
app/src/main/java/cy/agorise/graphenej/Extensions.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Extensions implements JsonSerializable, ByteSerializable {
|
||||
public static final String KEY_EXTENSIONS = "extensions";
|
||||
|
||||
private ArrayList<JsonSerializable> extensions;
|
||||
|
||||
public Extensions(){
|
||||
extensions = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
for(JsonSerializable o : extensions)
|
||||
array.add(o.toJsonObject());
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return new byte[1];
|
||||
}
|
||||
|
||||
public int size(){
|
||||
return extensions.size();
|
||||
}
|
||||
}
|
239
app/src/main/java/cy/agorise/graphenej/FileBin.java
Normal file
239
app/src/main/java/cy/agorise/graphenej/FileBin.java
Normal file
|
@ -0,0 +1,239 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import cy.agorise.graphenej.crypto.SecureRandomStrengthener;
|
||||
import cy.agorise.graphenej.models.backup.WalletBackup;
|
||||
|
||||
/**
|
||||
* Class to manage the backup files
|
||||
*
|
||||
* @author Henry Varona
|
||||
* @author Nelson R. Pérez
|
||||
*/
|
||||
public abstract class FileBin {
|
||||
|
||||
public static final int PUBLIC_KEY_LENGTH = 33;
|
||||
|
||||
/**
|
||||
* Method that receives as input both the bytes from the bin backup and the string used to encrypt the
|
||||
* data contained in it.
|
||||
*
|
||||
* The procedure of deserializing the wallet backup involves first decrypting the data and then decompressing
|
||||
* it using the LZMA algorithm. Once this two steps are performed, the resulting byte sequence represents
|
||||
* a JSON-formatted object with one or more wallet and private keys information.
|
||||
*
|
||||
* @param input: Input bytes
|
||||
* @param password: Password used to derive the encryption key
|
||||
* @return: An instance of the WalletBackup class.
|
||||
*/
|
||||
public static WalletBackup deserializeWalletBackup(byte[] input, String password){
|
||||
try{
|
||||
byte[] publicKey = new byte[PUBLIC_KEY_LENGTH];
|
||||
byte[] rawDataEncripted = new byte[input.length - PUBLIC_KEY_LENGTH];
|
||||
|
||||
System.arraycopy(input, 0, publicKey, 0, PUBLIC_KEY_LENGTH);
|
||||
System.arraycopy(input, PUBLIC_KEY_LENGTH, rawDataEncripted, 0, rawDataEncripted.length);
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
ECKey randomECKey = ECKey.fromPublicOnly(publicKey);
|
||||
byte[] finalKey = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(password.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded();
|
||||
MessageDigest md1 = MessageDigest.getInstance("SHA-512");
|
||||
finalKey = md1.digest(finalKey);
|
||||
byte[] decryptedData = Util.decryptAES(rawDataEncripted, Util.bytesToHex(finalKey).getBytes());
|
||||
|
||||
byte[] checksum = new byte[4];
|
||||
System.arraycopy(decryptedData, 0, checksum, 0, 4);
|
||||
byte[] compressedData = new byte[decryptedData.length - 4];
|
||||
System.arraycopy(decryptedData, 4, compressedData, 0, compressedData.length);
|
||||
|
||||
byte[] decompressedData = Util.decompress(compressedData, Util.LZMA);
|
||||
String walletString = new String(decompressedData, "UTF-8");
|
||||
System.out.println("Wallet str: "+walletString);
|
||||
return new GsonBuilder().create().fromJson(walletString, WalletBackup.class);
|
||||
}catch(NoSuchAlgorithmException e){
|
||||
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
System.out.println("UnsupportedEncodingException. Msg: "+e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] serializeWalletBackup(WalletBackup walletBackup, String password){
|
||||
SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance();
|
||||
//randomStrengthener.addEntropySource(new AndroidRandomSource());
|
||||
SecureRandom secureRandom = randomStrengthener.generateAndSeedRandomNumberGenerator();
|
||||
|
||||
try{
|
||||
String json = new GsonBuilder().create().toJson(walletBackup, WalletBackup.class);
|
||||
byte[] compressed = Util.compress(json.getBytes(), Util.LZMA);
|
||||
System.out.println("json: "+json);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] checksum = md.digest(compressed);
|
||||
byte[] checksummed = new byte[compressed.length + 4];
|
||||
|
||||
System.arraycopy(checksum, 0, checksummed, 0, 4);
|
||||
System.arraycopy(compressed, 0, checksummed, 4, compressed.length);
|
||||
byte[] randomKey = new byte[32];
|
||||
secureRandom.nextBytes(randomKey);
|
||||
ECKey randomECKey = ECKey.fromPrivate(md.digest(randomKey));
|
||||
byte[] randPubKey = randomECKey.getPubKey();
|
||||
byte[] sharedSecret = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(password.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded();
|
||||
MessageDigest md1 = MessageDigest.getInstance("SHA-512");
|
||||
byte[] finalKey = md1.digest(sharedSecret);
|
||||
checksummed = Util.encryptAES(checksummed, Util.byteToString(finalKey).getBytes());
|
||||
|
||||
byte[] finalPayload = new byte[checksummed.length + randPubKey.length];
|
||||
System.arraycopy(randPubKey, 0, finalPayload, 0, randPubKey.length);
|
||||
System.arraycopy(checksummed, 0, finalPayload, randPubKey.length, checksummed.length);
|
||||
|
||||
return finalPayload;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
System.out.println("UnsupportedEncodingException. Msg: "+e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the brainkey fron an input of bytes
|
||||
*
|
||||
* @param input Array of bytes of the file to be processed
|
||||
* @param password the pin code
|
||||
* @return the brainkey file, or null if the file or the password are
|
||||
* incorrect
|
||||
*
|
||||
* @deprecated use {@link #deserializeWalletBackup(byte[], String)} instead, as it is a more complete method
|
||||
* that will return a WalletBackup class instance.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getBrainkeyFromByte(byte[] input, String password) {
|
||||
try {
|
||||
byte[] publicKey = new byte[33];
|
||||
byte[] rawDataEncripted = new byte[input.length - 33];
|
||||
|
||||
System.arraycopy(input, 0, publicKey, 0, publicKey.length);
|
||||
System.arraycopy(input, 33, rawDataEncripted, 0, rawDataEncripted.length);
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
ECKey randomECKey = ECKey.fromPublicOnly(publicKey);
|
||||
byte[] finalKey = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(password.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded();
|
||||
MessageDigest md1 = MessageDigest.getInstance("SHA-512");
|
||||
finalKey = md1.digest(finalKey);
|
||||
byte[] rawData = Util.decryptAES(rawDataEncripted, Util.byteToString(finalKey).getBytes());
|
||||
if(rawData == null) return null;
|
||||
|
||||
byte[] checksum = new byte[4];
|
||||
System.arraycopy(rawData, 0, checksum, 0, 4);
|
||||
byte[] compressedData = new byte[rawData.length - 4];
|
||||
System.arraycopy(rawData, 4, compressedData, 0, compressedData.length);
|
||||
|
||||
byte[] wallet_object_bytes = Util.decompress(compressedData, Util.XZ);
|
||||
if(wallet_object_bytes == null) return null;
|
||||
String wallet_string = new String(wallet_object_bytes, "UTF-8");
|
||||
JsonObject wallet = new JsonParser().parse(wallet_string).getAsJsonObject();
|
||||
if (wallet.get("wallet").isJsonArray()) {
|
||||
wallet = wallet.get("wallet").getAsJsonArray().get(0).getAsJsonObject();
|
||||
} else {
|
||||
wallet = wallet.get("wallet").getAsJsonObject();
|
||||
}
|
||||
|
||||
byte[] encKey_enc = new BigInteger(wallet.get("encryption_key").getAsString(), 16).toByteArray();
|
||||
byte[] temp = new byte[encKey_enc.length - (encKey_enc[0] == 0 ? 1 : 0)];
|
||||
System.arraycopy(encKey_enc, (encKey_enc[0] == 0 ? 1 : 0), temp, 0, temp.length);
|
||||
byte[] encKey = Util.decryptAES(temp, password.getBytes("UTF-8"));
|
||||
temp = new byte[encKey.length];
|
||||
System.arraycopy(encKey, 0, temp, 0, temp.length);
|
||||
|
||||
byte[] encBrain = new BigInteger(wallet.get("encrypted_brainkey").getAsString(), 16).toByteArray();
|
||||
while (encBrain[0] == 0) {
|
||||
byte[] temp2 = new byte[encBrain.length - 1];
|
||||
System.arraycopy(encBrain, 1, temp2, 0, temp2.length);
|
||||
encBrain = temp2;
|
||||
}
|
||||
String BrainKey = new String((Util.decryptAES(encBrain, temp)), "UTF-8");
|
||||
|
||||
return BrainKey;
|
||||
|
||||
} catch (UnsupportedEncodingException | NoSuchAlgorithmException ex) {
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to generate the file form a brainkey
|
||||
*
|
||||
* @param BrainKey The input brainkey
|
||||
* @param password The pin code
|
||||
* @param accountName The Account Name
|
||||
* @return The array byte of the file, or null if an error happens
|
||||
*
|
||||
* @deprecated use {@link #serializeWalletBackup(WalletBackup, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static byte[] getBytesFromBrainKey(String BrainKey, String password, String accountName) {
|
||||
|
||||
try {
|
||||
byte[] encKey = new byte[32];
|
||||
SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance();
|
||||
//randomStrengthener.addEntropySource(new AndroidRandomSource());
|
||||
SecureRandom secureRandom = randomStrengthener.generateAndSeedRandomNumberGenerator();
|
||||
secureRandom.nextBytes(encKey);
|
||||
byte[] encKey_enc = Util.encryptAES(encKey, password.getBytes("UTF-8"));
|
||||
byte[] encBrain = Util.encryptAES(BrainKey.getBytes("ASCII"), encKey);
|
||||
|
||||
/**
|
||||
* Data to Store
|
||||
*/
|
||||
JsonObject wallet = new JsonObject();
|
||||
wallet.add("encryption_key", new JsonParser().parse(Util.byteToString(encKey_enc)));
|
||||
wallet.add("encrypted_brainkey", new JsonParser().parse(Util.byteToString(encBrain)));
|
||||
JsonObject wallet_object = new JsonObject();
|
||||
wallet_object.add("wallet", wallet);
|
||||
JsonArray accountNames = new JsonArray();
|
||||
JsonObject jsonAccountName = new JsonObject();
|
||||
jsonAccountName.add("name", new JsonParser().parse(accountName));
|
||||
accountNames.add(jsonAccountName);
|
||||
wallet_object.add("linked_accounts", accountNames);
|
||||
byte[] compressedData = Util.compress(wallet_object.toString().getBytes("UTF-8"), Util.XZ);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] checksum = md.digest(compressedData);
|
||||
byte[] rawData = new byte[compressedData.length + 4];
|
||||
System.arraycopy(checksum, 0, rawData, 0, 4);
|
||||
System.arraycopy(compressedData, 0, rawData, 4, compressedData.length);
|
||||
byte[] randomKey = new byte[32];
|
||||
secureRandom.nextBytes(randomKey);
|
||||
ECKey randomECKey = ECKey.fromPrivate(md.digest(randomKey));
|
||||
byte[] randPubKey = randomECKey.getPubKey();
|
||||
byte[] finalKey = randomECKey.getPubKeyPoint().multiply(ECKey.fromPrivate(md.digest(password.getBytes("UTF-8"))).getPrivKey()).normalize().getXCoord().getEncoded();
|
||||
MessageDigest md1 = MessageDigest.getInstance("SHA-512");
|
||||
finalKey = md1.digest(finalKey);
|
||||
rawData = Util.encryptAES(rawData, Util.byteToString(finalKey).getBytes());
|
||||
|
||||
byte[] result = new byte[rawData.length + randPubKey.length];
|
||||
System.arraycopy(randPubKey, 0, result, 0, randPubKey.length);
|
||||
System.arraycopy(rawData, 0, result, randPubKey.length, rawData.length);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (UnsupportedEncodingException | NoSuchAlgorithmException ex) {
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
116
app/src/main/java/cy/agorise/graphenej/GrapheneObject.java
Normal file
116
app/src/main/java/cy/agorise/graphenej/GrapheneObject.java
Normal file
|
@ -0,0 +1,116 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
/**
|
||||
* <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 {
|
||||
public static final String KEY_ID = "id";
|
||||
|
||||
public static final int PROTOCOL_SPACE = 1;
|
||||
public static final int IMPLEMENTATION_SPACE = 2;
|
||||
|
||||
@Expose
|
||||
protected String id;
|
||||
|
||||
protected int space;
|
||||
protected int type;
|
||||
protected long instance;
|
||||
|
||||
public GrapheneObject(String id){
|
||||
this.id = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this object.
|
||||
* @return: Instance of the ObjectType enum.
|
||||
*/
|
||||
public ObjectType getObjectType(){
|
||||
switch(space){
|
||||
case PROTOCOL_SPACE:
|
||||
switch(type){
|
||||
case 1:
|
||||
return ObjectType.BASE_OBJECT;
|
||||
case 2:
|
||||
return ObjectType.ACCOUNT_OBJECT;
|
||||
case 3:
|
||||
return ObjectType.ASSET_OBJECT;
|
||||
case 4:
|
||||
return ObjectType.FORCE_SETTLEMENT_OBJECT;
|
||||
case 5:
|
||||
return ObjectType.COMMITTEE_MEMBER_OBJECT;
|
||||
case 6:
|
||||
return ObjectType.WITNESS_OBJECT;
|
||||
case 7:
|
||||
return ObjectType.LIMIT_ORDER_OBJECT;
|
||||
case 8:
|
||||
return ObjectType.CALL_ORDER_OBJECT;
|
||||
case 9:
|
||||
return ObjectType.CUSTOM_OBJECT;
|
||||
case 10:
|
||||
return ObjectType.PROPOSAL_OBJECT;
|
||||
case 11:
|
||||
return ObjectType.OPERATION_HISTORY_OBJECT;
|
||||
case 12:
|
||||
return ObjectType.WITHDRAW_PERMISSION_OBJECT;
|
||||
case 13:
|
||||
return ObjectType.VESTING_BALANCE_OBJECT;
|
||||
case 14:
|
||||
return ObjectType.WORKER_OBJECT;
|
||||
case 15:
|
||||
return ObjectType.BALANCE_OBJECT;
|
||||
}
|
||||
case IMPLEMENTATION_SPACE:
|
||||
switch(type){
|
||||
case 0:
|
||||
return ObjectType.GLOBAL_PROPERTY_OBJECT;
|
||||
case 1:
|
||||
return ObjectType.DYNAMIC_GLOBAL_PROPERTY_OBJECT;
|
||||
case 3:
|
||||
return ObjectType.ASSET_DYNAMIC_DATA;
|
||||
case 4:
|
||||
return ObjectType.ASSET_BITASSET_DATA;
|
||||
case 5:
|
||||
return ObjectType.ACCOUNT_BALANCE_OBJECT;
|
||||
case 6:
|
||||
return ObjectType.ACCOUNT_STATISTICS_OBJECT;
|
||||
case 7:
|
||||
return ObjectType.TRANSACTION_OBJECT;
|
||||
case 8:
|
||||
return ObjectType.BLOCK_SUMMARY_OBJECT;
|
||||
case 9:
|
||||
return ObjectType.ACCOUNT_TRANSACTION_HISTORY_OBJECT;
|
||||
case 10:
|
||||
return ObjectType.BLINDED_BALANCE_OBJECT;
|
||||
case 11:
|
||||
return ObjectType.CHAIN_PROPERTY_OBJECT;
|
||||
case 12:
|
||||
return ObjectType.WITNESS_SCHEDULE_OBJECT;
|
||||
case 13:
|
||||
return ObjectType.BUDGET_RECORD_OBJECT;
|
||||
case 14:
|
||||
return ObjectType.SPECIAL_AUTHORITY_OBJECT;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
111
app/src/main/java/cy/agorise/graphenej/Invoice.java
Normal file
111
app/src/main/java/cy/agorise/graphenej/Invoice.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import org.bitcoinj.core.Base58;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class used to handle invoice generation, compression and QR-Code data derivation,
|
||||
* as detailed in <a href="http://docs.bitshares.eu/integration/merchants/merchant-protocol.html">this</> link.
|
||||
* @author Nelson R. Pérez
|
||||
*/
|
||||
public class Invoice implements JsonSerializable {
|
||||
private String to;
|
||||
private String to_label;
|
||||
private String memo;
|
||||
private String currency;
|
||||
private LineItem[] line_items;
|
||||
private String note;
|
||||
private String callback;
|
||||
|
||||
public Invoice(String to, String to_label, String memo, String currency, LineItem[] items, String note, String callback){
|
||||
this.to = to;
|
||||
this.to_label = to_label;
|
||||
this.memo = memo;
|
||||
this.currency = currency;
|
||||
this.line_items = items;
|
||||
this.note = note;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public String getToLabel() {
|
||||
return to_label;
|
||||
}
|
||||
|
||||
public void setToLabel(String to_label) {
|
||||
this.to_label = to_label;
|
||||
}
|
||||
|
||||
public String getNote() {
|
||||
return note;
|
||||
}
|
||||
|
||||
public void setNote(String note) {
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
public String getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public void setTo(String to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public String getMemo() {
|
||||
return memo;
|
||||
}
|
||||
|
||||
public void setMemo(String memo) {
|
||||
this.memo = memo;
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
public void setCurrency(String currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public LineItem[] getLineItems() {
|
||||
return line_items;
|
||||
}
|
||||
|
||||
public void setLineItems(LineItem[] line_items) {
|
||||
this.line_items = line_items;
|
||||
}
|
||||
|
||||
public String getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
public void setCallback(String callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String toQrCode(Invoice invoice){
|
||||
String json = invoice.toJsonString();
|
||||
return Base58.encode(Util.compress(json.getBytes(), Util.LZMA));
|
||||
}
|
||||
|
||||
public static Invoice fromQrCode(String encoded){
|
||||
String json = new String(Util.decompress(Base58.decode(encoded), Util.LZMA));
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(json, Invoice.class);
|
||||
}
|
||||
}
|
136
app/src/main/java/cy/agorise/graphenej/LimitOrder.java
Normal file
136
app/src/main/java/cy/agorise/graphenej/LimitOrder.java
Normal file
|
@ -0,0 +1,136 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author henry
|
||||
*/
|
||||
public class LimitOrder extends GrapheneObject implements ByteSerializable {
|
||||
|
||||
public static final String KEY_EXPIRATION = "expiration";
|
||||
public static final String KEY_SELLER = "seller";
|
||||
public static final String KEY_FOR_SALE = "for_sale";
|
||||
public static final String KEY_DEFERRED_FEE = "deferred_fee";
|
||||
public static final String KEY_PRICE = "sell_price";
|
||||
|
||||
private String expiration;
|
||||
private UserAccount seller;
|
||||
private long forSale;
|
||||
private long deferredFee;
|
||||
private Price sellPrice;
|
||||
|
||||
public LimitOrder(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public String getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(String expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public UserAccount getSeller() {
|
||||
return seller;
|
||||
}
|
||||
|
||||
public void setSeller(UserAccount seller) {
|
||||
this.seller = seller;
|
||||
}
|
||||
|
||||
public long getForSale() {
|
||||
return forSale;
|
||||
}
|
||||
|
||||
public void setForSale(long forSale) {
|
||||
this.forSale = forSale;
|
||||
}
|
||||
|
||||
public long getDeferredFee() {
|
||||
return deferredFee;
|
||||
}
|
||||
|
||||
public void setDeferredFee(long deferredFee) {
|
||||
this.deferredFee = deferredFee;
|
||||
}
|
||||
|
||||
public Price getSellPrice() {
|
||||
return sellPrice;
|
||||
}
|
||||
|
||||
public void setSellPrice(Price sellPrice) {
|
||||
this.sellPrice = sellPrice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(byteArrayOutputStream);
|
||||
byte[] serialized = null;
|
||||
try {
|
||||
Varint.writeUnsignedVarLong(this.instance, out);
|
||||
serialized = byteArrayOutputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer for the LimitOrder class, used to deserialize a json-formatted string in
|
||||
* the following format:
|
||||
*
|
||||
* {
|
||||
* "id": "1.7.2389233",
|
||||
* "expiration": "2017-04-21T15:40:04",
|
||||
* "seller": "1.2.114363",
|
||||
* "forSale": "10564959415",
|
||||
* "sell_price": {
|
||||
* "base": {
|
||||
* "amount": "10565237932",
|
||||
* "asset_id": "1.3.0"
|
||||
* },
|
||||
* "quote": {
|
||||
* "amount": 5803878,
|
||||
* "asset_id": "1.3.121"
|
||||
* }
|
||||
* },
|
||||
* "deferredFee": 0
|
||||
* }
|
||||
*/
|
||||
public static class LimitOrderDeserializer implements JsonDeserializer<LimitOrder> {
|
||||
|
||||
@Override
|
||||
public LimitOrder deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
String id = object.get(KEY_ID).getAsString();
|
||||
String expiration = object.get(KEY_EXPIRATION).getAsString();
|
||||
UserAccount seller = context.deserialize(object.get(KEY_SELLER), UserAccount.class);
|
||||
String forSale = object.get(KEY_FOR_SALE).getAsString();
|
||||
Price price = context.deserialize(object.get(KEY_PRICE), Price.class);
|
||||
long deferredFee = object.get(KEY_DEFERRED_FEE).getAsLong();
|
||||
|
||||
LimitOrder limitOrder = new LimitOrder(id);
|
||||
limitOrder.setExpiration(expiration);
|
||||
limitOrder.setSeller(seller);
|
||||
limitOrder.setForSale(Long.parseLong(forSale));
|
||||
limitOrder.setSellPrice(price);
|
||||
limitOrder.setDeferredFee(deferredFee);
|
||||
return limitOrder;
|
||||
}
|
||||
}
|
||||
}
|
40
app/src/main/java/cy/agorise/graphenej/LineItem.java
Normal file
40
app/src/main/java/cy/agorise/graphenej/LineItem.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/11/17.
|
||||
*/
|
||||
public class LineItem {
|
||||
private String label;
|
||||
private int quantity;
|
||||
private double price;
|
||||
|
||||
public LineItem(String label, int quantity, double price){
|
||||
this.label = label;
|
||||
this.quantity = quantity;
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(int quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public String getLabel(){
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
12
app/src/main/java/cy/agorise/graphenej/MarketTrade.java
Normal file
12
app/src/main/java/cy/agorise/graphenej/MarketTrade.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author henry
|
||||
*/
|
||||
public class MarketTrade {
|
||||
public String date;
|
||||
public double price;
|
||||
public double amount;
|
||||
public double value;
|
||||
}
|
182
app/src/main/java/cy/agorise/graphenej/ObjectType.java
Normal file
182
app/src/main/java/cy/agorise/graphenej/ObjectType.java
Normal file
|
@ -0,0 +1,182 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* Enum type used to list all possible object types and obtain their space + type id
|
||||
*/
|
||||
|
||||
public enum ObjectType {
|
||||
BASE_OBJECT,
|
||||
ACCOUNT_OBJECT,
|
||||
ASSET_OBJECT,
|
||||
FORCE_SETTLEMENT_OBJECT,
|
||||
COMMITTEE_MEMBER_OBJECT,
|
||||
WITNESS_OBJECT,
|
||||
LIMIT_ORDER_OBJECT,
|
||||
CALL_ORDER_OBJECT,
|
||||
CUSTOM_OBJECT,
|
||||
PROPOSAL_OBJECT,
|
||||
OPERATION_HISTORY_OBJECT,
|
||||
WITHDRAW_PERMISSION_OBJECT,
|
||||
VESTING_BALANCE_OBJECT,
|
||||
WORKER_OBJECT,
|
||||
BALANCE_OBJECT,
|
||||
GLOBAL_PROPERTY_OBJECT,
|
||||
DYNAMIC_GLOBAL_PROPERTY_OBJECT,
|
||||
ASSET_DYNAMIC_DATA,
|
||||
ASSET_BITASSET_DATA,
|
||||
ACCOUNT_BALANCE_OBJECT,
|
||||
ACCOUNT_STATISTICS_OBJECT,
|
||||
TRANSACTION_OBJECT,
|
||||
BLOCK_SUMMARY_OBJECT,
|
||||
ACCOUNT_TRANSACTION_HISTORY_OBJECT,
|
||||
BLINDED_BALANCE_OBJECT,
|
||||
CHAIN_PROPERTY_OBJECT,
|
||||
WITNESS_SCHEDULE_OBJECT,
|
||||
BUDGET_RECORD_OBJECT,
|
||||
SPECIAL_AUTHORITY_OBJECT;
|
||||
|
||||
private int getSpace(){
|
||||
int space = 1;
|
||||
switch(this){
|
||||
case BASE_OBJECT:
|
||||
case ACCOUNT_OBJECT:
|
||||
case ASSET_OBJECT:
|
||||
case FORCE_SETTLEMENT_OBJECT:
|
||||
case COMMITTEE_MEMBER_OBJECT:
|
||||
case WITNESS_OBJECT:
|
||||
case LIMIT_ORDER_OBJECT:
|
||||
case CALL_ORDER_OBJECT:
|
||||
case CUSTOM_OBJECT:
|
||||
case PROPOSAL_OBJECT:
|
||||
case OPERATION_HISTORY_OBJECT:
|
||||
case WITHDRAW_PERMISSION_OBJECT:
|
||||
case VESTING_BALANCE_OBJECT:
|
||||
case WORKER_OBJECT:
|
||||
case BALANCE_OBJECT:
|
||||
space = 1;
|
||||
break;
|
||||
case GLOBAL_PROPERTY_OBJECT:
|
||||
case DYNAMIC_GLOBAL_PROPERTY_OBJECT:
|
||||
case ASSET_DYNAMIC_DATA:
|
||||
case ASSET_BITASSET_DATA:
|
||||
case ACCOUNT_BALANCE_OBJECT:
|
||||
case ACCOUNT_STATISTICS_OBJECT:
|
||||
case TRANSACTION_OBJECT:
|
||||
case BLOCK_SUMMARY_OBJECT:
|
||||
case ACCOUNT_TRANSACTION_HISTORY_OBJECT:
|
||||
case BLINDED_BALANCE_OBJECT:
|
||||
case CHAIN_PROPERTY_OBJECT:
|
||||
case WITNESS_SCHEDULE_OBJECT:
|
||||
case BUDGET_RECORD_OBJECT:
|
||||
case SPECIAL_AUTHORITY_OBJECT:
|
||||
space = 2;
|
||||
break;
|
||||
}
|
||||
return space;
|
||||
}
|
||||
|
||||
private int getType(){
|
||||
int type = 0;
|
||||
switch(this){
|
||||
case BASE_OBJECT:
|
||||
type = 1;
|
||||
break;
|
||||
case ACCOUNT_OBJECT:
|
||||
type = 2;
|
||||
break;
|
||||
case ASSET_OBJECT:
|
||||
type = 3;
|
||||
break;
|
||||
case FORCE_SETTLEMENT_OBJECT:
|
||||
type = 4;
|
||||
break;
|
||||
case COMMITTEE_MEMBER_OBJECT:
|
||||
type = 5;
|
||||
break;
|
||||
case WITNESS_OBJECT:
|
||||
type = 6;
|
||||
break;
|
||||
case LIMIT_ORDER_OBJECT:
|
||||
type = 7;
|
||||
break;
|
||||
case CALL_ORDER_OBJECT:
|
||||
type = 8;
|
||||
break;
|
||||
case CUSTOM_OBJECT:
|
||||
type = 9;
|
||||
break;
|
||||
case PROPOSAL_OBJECT:
|
||||
type = 10;
|
||||
break;
|
||||
case OPERATION_HISTORY_OBJECT:
|
||||
type = 11;
|
||||
break;
|
||||
case WITHDRAW_PERMISSION_OBJECT:
|
||||
type = 12;
|
||||
break;
|
||||
case VESTING_BALANCE_OBJECT:
|
||||
type = 13;
|
||||
break;
|
||||
case WORKER_OBJECT:
|
||||
type = 14;
|
||||
break;
|
||||
case BALANCE_OBJECT:
|
||||
type = 15;
|
||||
break;
|
||||
case GLOBAL_PROPERTY_OBJECT:
|
||||
type = 0;
|
||||
break;
|
||||
case DYNAMIC_GLOBAL_PROPERTY_OBJECT:
|
||||
type = 1;
|
||||
break;
|
||||
case ASSET_DYNAMIC_DATA:
|
||||
type = 3;
|
||||
break;
|
||||
case ASSET_BITASSET_DATA:
|
||||
type = 4;
|
||||
break;
|
||||
case ACCOUNT_BALANCE_OBJECT:
|
||||
type = 5;
|
||||
break;
|
||||
case ACCOUNT_STATISTICS_OBJECT:
|
||||
type = 6;
|
||||
break;
|
||||
case TRANSACTION_OBJECT:
|
||||
type = 7;
|
||||
break;
|
||||
case BLOCK_SUMMARY_OBJECT:
|
||||
type = 8;
|
||||
break;
|
||||
case ACCOUNT_TRANSACTION_HISTORY_OBJECT:
|
||||
type = 9;
|
||||
break;
|
||||
case BLINDED_BALANCE_OBJECT:
|
||||
type = 10;
|
||||
break;
|
||||
case CHAIN_PROPERTY_OBJECT:
|
||||
type = 11;
|
||||
break;
|
||||
case WITNESS_SCHEDULE_OBJECT:
|
||||
type = 12;
|
||||
break;
|
||||
case BUDGET_RECORD_OBJECT:
|
||||
type = 13;
|
||||
break;
|
||||
case SPECIAL_AUTHORITY_OBJECT:
|
||||
type = 14;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to return the generic object type in the form space.type.0.
|
||||
*
|
||||
* Not to be confused with {@link GrapheneObject#getObjectId()}, which will return
|
||||
* the full object id in the form space.type.id.
|
||||
*
|
||||
* @return: The generic object type
|
||||
*/
|
||||
public String getGenericObjectId(){
|
||||
return String.format("%d.%d.0", getSpace(), getType());
|
||||
}
|
||||
}
|
55
app/src/main/java/cy/agorise/graphenej/OperationType.java
Normal file
55
app/src/main/java/cy/agorise/graphenej/OperationType.java
Normal file
|
@ -0,0 +1,55 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* Enum type used to keep track of all the operation types and their corresponding ids.
|
||||
*
|
||||
* <a href="https://bitshares.org/doxygen/operations_8hpp_source.html">Source</a>
|
||||
*
|
||||
* 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
|
||||
}
|
46
app/src/main/java/cy/agorise/graphenej/Optional.java
Normal file
46
app/src/main/java/cy/agorise/graphenej/Optional.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.GrapheneSerializable;
|
||||
|
||||
/**
|
||||
* Container template class used whenever we have an optional field.
|
||||
*
|
||||
* The idea here is that the binary serialization of this field should be performed
|
||||
* in a specific way determined by the field implementing the {@link ByteSerializable}
|
||||
* interface, more specifically using the {@link ByteSerializable#toBytes()} method.
|
||||
*
|
||||
* However, if the field is missing, the Optional class should be able to know how
|
||||
* to serialize it, as this is always done by placing an zero byte.
|
||||
*/
|
||||
public class Optional<T extends GrapheneSerializable> implements GrapheneSerializable {
|
||||
private T optionalField;
|
||||
|
||||
public Optional(T field){
|
||||
optionalField = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
if(optionalField == null)
|
||||
return new byte[] { (byte) 0 };
|
||||
else
|
||||
return optionalField.toBytes();
|
||||
}
|
||||
|
||||
public boolean isSet(){
|
||||
return this.optionalField != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return optionalField.toJsonString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
return optionalField.toJsonObject();
|
||||
}
|
||||
}
|
98
app/src/main/java/cy/agorise/graphenej/OrderBook.java
Normal file
98
app/src/main/java/cy/agorise/graphenej/OrderBook.java
Normal file
|
@ -0,0 +1,98 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.math.DoubleMath;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
|
||||
/**
|
||||
* This class will maintain a snapshot of the order book between two assets.
|
||||
*
|
||||
* It also provides a handy method that should return the appropriate LimitOrderCreateOperation
|
||||
* object needed in case the user wants to perform market-priced operations.
|
||||
*
|
||||
* It is important to keep the order book updated, ideally by listening to blockchain events, and calling the 'update' method.
|
||||
*
|
||||
* Created by nelson on 3/25/17.
|
||||
*/
|
||||
public class OrderBook {
|
||||
private List<LimitOrder> limitOrders;
|
||||
|
||||
public OrderBook(List<LimitOrder> limitOrders){
|
||||
this.limitOrders = limitOrders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current limit order by the list provided as parameter.
|
||||
* @param limitOrders: New list of orders
|
||||
*/
|
||||
public void update(List<LimitOrder> limitOrders){
|
||||
this.limitOrders = limitOrders;
|
||||
}
|
||||
|
||||
public void update(LimitOrder limitOrder){
|
||||
//TODO: Implement the method that will update a single limit order from the order book
|
||||
}
|
||||
|
||||
/**
|
||||
* High level method used to exchange a specific amount of an asset (The base) for another
|
||||
* one (The quote) at market value.
|
||||
*
|
||||
* It should analyze the order book and figure out the optimal amount of the base asset to give
|
||||
* away in order to obtain the desired amount of the quote asset.
|
||||
*
|
||||
* @param seller: User account of the seller, used to build the limit order create operation
|
||||
* @param myBaseAsset: The asset the user is willing to give away
|
||||
* @param myQuoteAmount: The amount of a given asset the user wants
|
||||
* @param expiration: The expiration time of the limit order
|
||||
*
|
||||
* @return An instance of the LimitOrderCreateOperation class, which is ready to be broadcasted.
|
||||
*/
|
||||
public LimitOrderCreateOperation exchange(UserAccount seller, Asset myBaseAsset, AssetAmount myQuoteAmount, int expiration){
|
||||
AssetAmount toSell = new AssetAmount(UnsignedLong.valueOf(calculateRequiredBase(myQuoteAmount)), myBaseAsset);
|
||||
AssetAmount toReceive = myQuoteAmount;
|
||||
LimitOrderCreateOperation buyOrder = new LimitOrderCreateOperation(seller, toSell, toReceive, expiration, true);
|
||||
|
||||
return buyOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a specific amount of a desired asset, this method will calculate how much of the corresponding
|
||||
* asset we need to offer to perform a successful transaction with the current order book.
|
||||
* @param quoteAmount: The amount of the desired asset.
|
||||
* @return: The minimum amount of the base asset that we need to give away
|
||||
*/
|
||||
public long calculateRequiredBase(AssetAmount quoteAmount){
|
||||
long totalBought = 0;
|
||||
long totalSold = 0;
|
||||
for(int i = 0; i < this.limitOrders.size() && totalBought < quoteAmount.getAmount().longValue(); i++){
|
||||
LimitOrder order = this.limitOrders.get(i);
|
||||
|
||||
// If the base asset is the same as our quote asset, we have a match
|
||||
if(order.getSellPrice().base.getAsset().getObjectId().equals(quoteAmount.getAsset().getObjectId())){
|
||||
// My quote amount, is the order's base amount
|
||||
long orderAmount = order.getForSale();
|
||||
|
||||
// The amount of the quote asset we still need
|
||||
long stillNeed = quoteAmount.getAmount().longValue() - totalBought;
|
||||
|
||||
// If the offered amount is greater than what we still need, we exchange just what we need
|
||||
if(orderAmount >= stillNeed) {
|
||||
totalBought += stillNeed;
|
||||
double additionalRatio = (double) stillNeed / (double) order.getSellPrice().base.getAmount().longValue();
|
||||
double additionalAmount = order.getSellPrice().quote.getAmount().longValue() * additionalRatio;
|
||||
long longAdditional = DoubleMath.roundToLong(additionalAmount, RoundingMode.HALF_UP);
|
||||
totalSold += longAdditional;
|
||||
}else{
|
||||
// If the offered amount is less than what we need, we exchange the whole order
|
||||
totalBought += orderAmount;
|
||||
totalSold += order.getSellPrice().quote.getAmount().longValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalSold;
|
||||
}
|
||||
}
|
28
app/src/main/java/cy/agorise/graphenej/Price.java
Normal file
28
app/src/main/java/cy/agorise/graphenej/Price.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* The price struct stores asset prices in the Graphene system.
|
||||
*
|
||||
* A price is defined as a ratio between two assets, and represents a possible exchange rate
|
||||
* between those two assets. prices are generally not stored in any simplified form, i.e. a price
|
||||
* of (1000 CORE)/(20 USD) is perfectly normal.
|
||||
|
||||
* The assets within a price are labeled base and quote. Throughout the Graphene code base,
|
||||
* the convention used is that the base asset is the asset being sold, and the quote asset is
|
||||
* the asset being purchased, where the price is represented as base/quote, so in the example
|
||||
* price above the seller is looking to sell CORE asset and get USD in return.
|
||||
*
|
||||
* Note: Taken from the Graphene doxygen.
|
||||
* Created by nelson on 12/16/16.
|
||||
*/
|
||||
public class Price {
|
||||
public AssetAmount base;
|
||||
public AssetAmount quote;
|
||||
}
|
56
app/src/main/java/cy/agorise/graphenej/PublicKey.java
Normal file
56
app/src/main/java/cy/agorise/graphenej/PublicKey.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/30/16.
|
||||
*/
|
||||
public class PublicKey implements ByteSerializable, Serializable {
|
||||
private ECKey publicKey;
|
||||
|
||||
public PublicKey(ECKey key) {
|
||||
if(key.hasPrivKey()){
|
||||
throw new IllegalStateException("Passing a private key to PublicKey constructor");
|
||||
}
|
||||
this.publicKey = key;
|
||||
}
|
||||
|
||||
public ECKey getKey(){
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
if(publicKey.isCompressed()) {
|
||||
return publicKey.getPubKey();
|
||||
}else{
|
||||
publicKey = ECKey.fromPublicOnly(ECKey.compressPoint(publicKey.getPubKeyPoint()));
|
||||
return publicKey.getPubKey();
|
||||
}
|
||||
}
|
||||
|
||||
public String getAddress(){
|
||||
ECKey pk = ECKey.fromPublicOnly(publicKey.getPubKey());
|
||||
if(!pk.isCompressed()){
|
||||
ECPoint point = ECKey.compressPoint(pk.getPubKeyPoint());
|
||||
pk = ECKey.fromPublicOnly(point);
|
||||
}
|
||||
return new Address(pk).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return publicKey.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
PublicKey other = (PublicKey) obj;
|
||||
return this.publicKey.equals(other.getKey());
|
||||
}
|
||||
}
|
32
app/src/main/java/cy/agorise/graphenej/RPC.java
Normal file
32
app/src/main/java/cy/agorise/graphenej/RPC.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/16/16.
|
||||
*/
|
||||
public class RPC {
|
||||
public static final String VERSION = "2.0";
|
||||
public static final String CALL_LOGIN = "login";
|
||||
public static final String CALL_NETWORK_BROADCAST = "network_broadcast";
|
||||
public static final String CALL_HISTORY = "history";
|
||||
public static final String CALL_DATABASE = "database";
|
||||
public static final String CALL_ASSET = "asset";
|
||||
public static final String CALL_SET_SUBSCRIBE_CALLBACK = "set_subscribe_callback";
|
||||
public static final String CALL_CANCEL_ALL_SUBSCRIPTIONS = "cancel_all_subscriptions";
|
||||
public static final String CALL_GET_ACCOUNT_BY_NAME = "get_account_by_name";
|
||||
public static final String CALL_GET_ACCOUNTS = "get_accounts";
|
||||
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";
|
||||
public static final String CALL_GET_KEY_REFERENCES = "get_key_references";
|
||||
public static final String CALL_GET_RELATIVE_ACCOUNT_HISTORY = "get_relative_account_history";
|
||||
public static final String CALL_LOOKUP_ACCOUNTS = "lookup_accounts";
|
||||
public static final String CALL_LIST_ASSETS = "list_assets";
|
||||
public static final String GET_OBJECTS = "get_objects";
|
||||
public static final String GET_ACCOUNT_BALANCES = "get_account_balances";
|
||||
public static final String CALL_LOOKUP_ASSET_SYMBOLS = "lookup_asset_symbols";
|
||||
public static final String CALL_GET_BLOCK_HEADER = "get_block_header";
|
||||
public static final String CALL_GET_LIMIT_ORDERS = "get_limit_orders";
|
||||
public static final String CALL_GET_TRADE_HISTORY = "get_trade_history";
|
||||
public static final String CALL_GET_MARKET_HISTORY = "get_market_history";
|
||||
public static final String CALL_GET_ALL_ASSET_HOLDERS = "get_all_asset_holders";
|
||||
}
|
377
app/src/main/java/cy/agorise/graphenej/Transaction.java
Normal file
377
app/src/main/java/cy/agorise/graphenej/Transaction.java
Normal file
|
@ -0,0 +1,377 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
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.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
/**
|
||||
* Class used to represent a generic Graphene transaction.
|
||||
*/
|
||||
public class Transaction implements ByteSerializable, JsonSerializable {
|
||||
|
||||
/* Default expiration time */
|
||||
public static final int DEFAULT_EXPIRATION_TIME = 30;
|
||||
|
||||
/* Constant field names used for serialization/deserialization purposes */
|
||||
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 Extensions extensions;
|
||||
|
||||
/**
|
||||
* 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 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(DumpedPrivateKey.fromBase58(null, wif).getKey(), block_data, operation_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used to build a Transaction object without a private key. This kind of object
|
||||
* is used to represent a transaction data that we don't intend to serialize and sign.
|
||||
* @param blockData: Block data instance, containing information about the location of this transaction in the blockchain.
|
||||
* @param operationList: The list of operations included in this transaction.
|
||||
*/
|
||||
public Transaction(BlockData blockData, List<BaseOperation> operationList){
|
||||
this.blockData = blockData;
|
||||
this.operations = operationList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the block data
|
||||
* @param blockData: New block data
|
||||
*/
|
||||
public void setBlockData(BlockData blockData){
|
||||
this.blockData = blockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fees for all operations in this transaction.
|
||||
* @param fees: New fees to apply
|
||||
*/
|
||||
public void setFees(List<AssetAmount> fees){
|
||||
for(int i = 0; i < operations.size(); i++)
|
||||
operations.get(i).setFee(fees.get(i));
|
||||
}
|
||||
|
||||
public ECKey getPrivateKey(){
|
||||
return this.privateKey;
|
||||
}
|
||||
|
||||
public List<BaseOperation> getOperations(){ return this.operations; }
|
||||
|
||||
/**
|
||||
* This method is used to query whether the instance has a private key.
|
||||
* @return
|
||||
*/
|
||||
public boolean hasPrivateKey(){
|
||||
return this.privateKey != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a signature of this transaction. Please note that due to the current reliance on
|
||||
* bitcoinj to generate the signatures, and due to the fact that it uses deterministic
|
||||
* ecdsa signatures, we are slightly modifying the expiration time of the transaction while
|
||||
* we look for a signature that will be accepted by the graphene network.
|
||||
*
|
||||
* This should then be called before any other serialization method.
|
||||
* @return: A valid signature of the current transaction.
|
||||
*/
|
||||
public byte[] getGrapheneSignature(){
|
||||
boolean isGrapheneCanonical = false;
|
||||
byte[] sigData = null;
|
||||
|
||||
while(!isGrapheneCanonical) {
|
||||
byte[] serializedTransaction = this.toBytes();
|
||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||
int recId = -1;
|
||||
ECKey.ECDSASignature sig = privateKey.sign(hash);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
|
||||
int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0);
|
||||
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);
|
||||
|
||||
// Further "canonicality" tests
|
||||
if(((sigData[0] & 0x80) != 0) || (sigData[0] == 0) ||
|
||||
((sigData[1] & 0x80) != 0) || ((sigData[32] & 0x80) != 0) ||
|
||||
(sigData[32] == 0) || ((sigData[33] & 0x80) != 0)){
|
||||
this.blockData.setExpiration(this.blockData.getExpiration() + 1);
|
||||
}else{
|
||||
isGrapheneCanonical = true;
|
||||
}
|
||||
}
|
||||
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 extensions byte
|
||||
byteArray.addAll(Bytes.asList(this.extensions.toBytes()));
|
||||
|
||||
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();
|
||||
|
||||
// Getting the signature before anything else,
|
||||
// since this might change the transaction expiration data slightly
|
||||
byte[] signature = getGrapheneSignature();
|
||||
|
||||
// Formatting expiration time
|
||||
Date expirationTime = new Date(blockData.getExpiration() * 1000);
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
// Adding expiration
|
||||
obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime));
|
||||
|
||||
// Adding signatures
|
||||
JsonArray signatureArray = new JsonArray();
|
||||
signatureArray.add(Util.bytesToHex(signature));
|
||||
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 used to encapsulate the procedure to be followed when converting a transaction from a
|
||||
* java object to its JSON string format representation.
|
||||
*/
|
||||
public static class TransactionSerializer implements JsonSerializer<Transaction> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
return transaction.toJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static inner class used to encapsulate the procedure to be followed when converting a transaction from its
|
||||
* JSON string format representation into a java object instance.
|
||||
*/
|
||||
public static class TransactionDeserializer implements JsonDeserializer<Transaction> {
|
||||
|
||||
@Override
|
||||
public Transaction deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
|
||||
// Parsing block data information
|
||||
int refBlockNum = jsonObject.get(KEY_REF_BLOCK_NUM).getAsInt();
|
||||
long refBlockPrefix = jsonObject.get(KEY_REF_BLOCK_PREFIX).getAsLong();
|
||||
String expiration = jsonObject.get(KEY_EXPIRATION).getAsString();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0));
|
||||
BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime());
|
||||
|
||||
// Parsing operation list
|
||||
BaseOperation operation = null;
|
||||
ArrayList<BaseOperation> operationList = new ArrayList<>();
|
||||
try {
|
||||
for (JsonElement jsonOperation : jsonObject.get(KEY_OPERATIONS).getAsJsonArray()) {
|
||||
int operationId = jsonOperation.getAsJsonArray().get(0).getAsInt();
|
||||
if (operationId == OperationType.TRANSFER_OPERATION.ordinal()) {
|
||||
operation = context.deserialize(jsonOperation, TransferOperation.class);
|
||||
} else if (operationId == OperationType.LIMIT_ORDER_CREATE_OPERATION.ordinal()) {
|
||||
operation = context.deserialize(jsonOperation, LimitOrderCreateOperation.class);
|
||||
} else if (operationId == OperationType.LIMIT_ORDER_CANCEL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.CALL_ORDER_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.FILL_ORDER_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ACCOUNT_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ACCOUNT_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ACCOUNT_WHITELIST_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ACCOUNT_UPGRADE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ACCOUNT_TRANSFER_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_UPDATE_BITASSET_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_UPDATE_FEED_PRODUCERS_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_ISSUE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_RESERVE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_FUND_FEE_POOL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_SETTLE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_GLOBAL_SETTLE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_PUBLISH_FEED_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WITNESS_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WITNESS_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.PROPOSAL_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.PROPOSAL_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.PROPOSAL_DELETE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WITHDRAW_PERMISSION_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WITHDRAW_PERMISSION_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WITHDRAW_PERMISSION_CLAIM_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WITHDRAW_PERMISSION_DELETE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.COMMITTEE_MEMBER_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.COMMITTEE_MEMBER_UPDATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.COMMITTEE_MEMBER_UPDATE_GLOBAL_PARAMETERS_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.VESTING_BALANCE_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.VESTING_BALANCE_WITHDRAW_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.WORKER_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.CUSTOM_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSERT_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.BALANCE_CLAIM_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.OVERRIDE_TRANSFER_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.TRANSFER_TO_BLIND_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.BLIND_TRANSFER_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.TRANSFER_FROM_BLIND_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_SETTLE_CANCEL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_CLAIM_FEES_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
}
|
||||
if (operation != null) operationList.add(operation);
|
||||
operation = null;
|
||||
}
|
||||
return new Transaction(blockData, operationList);
|
||||
}catch(Exception e){
|
||||
System.out.println("Exception. Msg: "+e.getMessage());
|
||||
for(StackTraceElement el : e.getStackTrace()){
|
||||
System.out.println(el.getFileName()+"#"+el.getMethodName()+":"+el.getLineNumber());
|
||||
}
|
||||
}
|
||||
return new Transaction(blockData, operationList);
|
||||
}
|
||||
}
|
||||
}
|
329
app/src/main/java/cy/agorise/graphenej/UserAccount.java
Normal file
329
app/src/main/java/cy/agorise/graphenej/UserAccount.java
Normal file
|
@ -0,0 +1,329 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class that represents a graphene user account.
|
||||
*/
|
||||
public class UserAccount extends GrapheneObject implements ByteSerializable, JsonSerializable {
|
||||
|
||||
public static final String PROXY_TO_SELF = "1.2.5";
|
||||
public static final String KEY_MEMBERSHIP_EXPIRATION_DATE = "membership_expiration_date";
|
||||
public static final String KEY_REGISTRAR = "registrar";
|
||||
public static final String KEY_REFERRER = "referrer";
|
||||
public static final String KEY_LIFETIME_REFERRER = "lifetime_referrer";
|
||||
public static final String KEY_NETWORK_FEE_PERCENTAGE = "network_fee_percentage";
|
||||
public static final String KEY_LIFETIME_REFERRER_FEE_PERCENTAGE = "lifetime_referrer_fee_percentage";
|
||||
public static final String KEY_REFERRER_REWARD_PERCENTAGE = "referrer_rewards_percentage";
|
||||
public static final String KEY_NAME = "name";
|
||||
public static final String KEY_OWNER = "owner";
|
||||
public static final String KEY_ACTIVE = "active";
|
||||
public static final String KEY_OPTIONS = "options";
|
||||
public static final String KEY_STATISTICS = "statistics";
|
||||
public static final String KEY_WHITELISTING_ACCOUNTS = "whitelisting_accounts";
|
||||
public static final String KEY_BLACKLISTING_ACCOUNTS = "blacklisting_accounts";
|
||||
public static final String KEY_WHITELISTED_ACCOUNTS = "whitelisted_accounts";
|
||||
public static final String KEY_BLACKLISTED_ACCOUNTS = "blacklisted_accounts";
|
||||
public static final String KEY_OWNER_SPECIAL_AUTHORITY = "owner_special_authority";
|
||||
public static final String KEY_ACTIVE_SPECIAL_AUTHORITY = "active_special_authority";
|
||||
public static final String KEY_N_CONTROL_FLAGS = "top_n_control_flags";
|
||||
|
||||
@Expose
|
||||
private String name;
|
||||
|
||||
@Expose
|
||||
private Authority owner;
|
||||
|
||||
@Expose
|
||||
private Authority active;
|
||||
|
||||
@Expose
|
||||
private AccountOptions options;
|
||||
|
||||
@Expose
|
||||
private String statistics;
|
||||
|
||||
@Expose
|
||||
private long membershipExpirationDate;
|
||||
|
||||
@Expose
|
||||
private String registrar;
|
||||
|
||||
@Expose
|
||||
private String referrer;
|
||||
|
||||
@Expose
|
||||
private String lifetimeReferrer;
|
||||
|
||||
@Expose
|
||||
private long networkFeePercentage;
|
||||
|
||||
@Expose
|
||||
private long lifetimeReferrerFeePercentage;
|
||||
|
||||
@Expose
|
||||
private long referrerRewardsPercentage;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructor that expects a user account in the string representation.
|
||||
* That is in the 1.2.x format.
|
||||
* @param id: The string representing the user account.
|
||||
*/
|
||||
public UserAccount(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that expects a user account withe the proper graphene object id and an account name.
|
||||
* @param id: The string representing the user account.
|
||||
* @param name: The name of this user account.
|
||||
*/
|
||||
public UserAccount(String id, String name){
|
||||
super(id);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the account name field.
|
||||
* @return: The name of this account.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for the account name field.
|
||||
* @param name: The account name.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this.getObjectId().equals(((UserAccount)o).getObjectId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getObjectId().hashCode();
|
||||
}
|
||||
|
||||
@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() {
|
||||
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
|
||||
return gson.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject toJsonObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.toJsonString();
|
||||
}
|
||||
|
||||
public long getMembershipExpirationDate() {
|
||||
return membershipExpirationDate;
|
||||
}
|
||||
|
||||
public void setMembershipExpirationDate(long membershipExpirationDate) {
|
||||
this.membershipExpirationDate = membershipExpirationDate;
|
||||
}
|
||||
|
||||
public String getRegistrar() {
|
||||
return registrar;
|
||||
}
|
||||
|
||||
public void setRegistrar(String registrar) {
|
||||
this.registrar = registrar;
|
||||
}
|
||||
|
||||
public String getReferrer() {
|
||||
return referrer;
|
||||
}
|
||||
|
||||
public void setReferrer(String referrer) {
|
||||
this.referrer = referrer;
|
||||
}
|
||||
|
||||
public String getLifetimeReferrer() {
|
||||
return lifetimeReferrer;
|
||||
}
|
||||
|
||||
public void setLifetimeReferrer(String lifetimeReferrer) {
|
||||
this.lifetimeReferrer = lifetimeReferrer;
|
||||
}
|
||||
|
||||
public long getNetworkFeePercentage() {
|
||||
return networkFeePercentage;
|
||||
}
|
||||
|
||||
public void setNetworkFeePercentage(long networkFeePercentage) {
|
||||
this.networkFeePercentage = networkFeePercentage;
|
||||
}
|
||||
|
||||
public long getLifetimeReferrerFeePercentage() {
|
||||
return lifetimeReferrerFeePercentage;
|
||||
}
|
||||
|
||||
public void setLifetimeReferrerFeePercentage(long lifetimeReferrerFeePercentage) {
|
||||
this.lifetimeReferrerFeePercentage = lifetimeReferrerFeePercentage;
|
||||
}
|
||||
|
||||
public long getReferrerRewardsPercentage() {
|
||||
return referrerRewardsPercentage;
|
||||
}
|
||||
|
||||
public void setReferrerRewardsPercentage(long referrerRewardsPercentage) {
|
||||
this.referrerRewardsPercentage = referrerRewardsPercentage;
|
||||
}
|
||||
|
||||
public Authority getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(Authority owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public Authority getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(Authority active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public AccountOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(AccountOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public String getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public void setStatistics(String statistics) {
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializer used to build a UserAccount instance from the full JSON-formatted response obtained
|
||||
* by the 'get_objects' API call.
|
||||
*/
|
||||
public static class UserAccountFullDeserializer implements JsonDeserializer<UserAccount> {
|
||||
|
||||
@Override
|
||||
public UserAccount deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonAccount = json.getAsJsonObject();
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
|
||||
// Retrieving and deserializing fields
|
||||
String id = jsonAccount.get(KEY_ID).getAsString();
|
||||
String name = jsonAccount.get(KEY_NAME).getAsString();
|
||||
UserAccount userAccount = new UserAccount(id, name);
|
||||
AccountOptions options = context.deserialize(jsonAccount.get(KEY_OPTIONS), AccountOptions.class);
|
||||
Authority owner = context.deserialize(jsonAccount.get(KEY_OWNER), Authority.class);
|
||||
Authority active = context.deserialize(jsonAccount.get(KEY_ACTIVE), Authority.class);
|
||||
|
||||
// Setting deserialized fields into the created instance
|
||||
userAccount.setRegistrar(jsonAccount.get(KEY_REGISTRAR).getAsString());
|
||||
|
||||
// Handling the deserialization and assignation of the membership date, which internally
|
||||
// is stored as a long POSIX time value
|
||||
try{
|
||||
Date date = dateFormat.parse(jsonAccount.get(KEY_MEMBERSHIP_EXPIRATION_DATE).getAsString());
|
||||
userAccount.setMembershipExpirationDate(date.getTime());
|
||||
} catch (ParseException e) {
|
||||
System.out.println("ParseException. Msg: "+e.getMessage());
|
||||
}
|
||||
|
||||
// Setting the other fields
|
||||
userAccount.setReferrer(jsonAccount.get(KEY_REFERRER).getAsString());
|
||||
userAccount.setLifetimeReferrer(jsonAccount.get(KEY_LIFETIME_REFERRER).getAsString());
|
||||
userAccount.setNetworkFeePercentage(jsonAccount.get(KEY_NETWORK_FEE_PERCENTAGE).getAsLong());
|
||||
userAccount.setLifetimeReferrerFeePercentage(jsonAccount.get(KEY_LIFETIME_REFERRER_FEE_PERCENTAGE).getAsLong());
|
||||
userAccount.setReferrerRewardsPercentage(jsonAccount.get(KEY_REFERRER_REWARD_PERCENTAGE).getAsLong());
|
||||
userAccount.setOwner(owner);
|
||||
userAccount.setActive(active);
|
||||
userAccount.setOptions(options);
|
||||
userAccount.setStatistics(jsonAccount.get(KEY_STATISTICS).getAsString());
|
||||
return userAccount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used to deserialize user accounts provided as response from the 'lookup_accounts' api call.
|
||||
* This response contains serialized user accounts in the form [[{id1},{name1}][{id1},{name1}]].
|
||||
*
|
||||
* For instance:
|
||||
* [["bilthon-1","1.2.139205"],["bilthon-2","1.2.139207"],["bilthon-2016","1.2.139262"]]
|
||||
*
|
||||
* So this class will pick up this data and turn it into a UserAccount object.
|
||||
*/
|
||||
public static class UserAccountDeserializer implements JsonDeserializer<UserAccount> {
|
||||
|
||||
@Override
|
||||
public UserAccount deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonArray array = json.getAsJsonArray();
|
||||
String name = array.get(0).getAsString();
|
||||
String id = array.get(1).getAsString();
|
||||
return new UserAccount(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used to deserialize user accounts as provided by the response of the 'get_key_references' api call.
|
||||
* This response contains serialized user accounts in the form [["id1","id2"]]
|
||||
*/
|
||||
public static class UserAccountSimpleDeserializer implements JsonDeserializer<UserAccount> {
|
||||
|
||||
@Override
|
||||
public UserAccount deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
String id = json.getAsString();
|
||||
return new UserAccount(id);
|
||||
}
|
||||
}
|
||||
}
|
347
app/src/main/java/cy/agorise/graphenej/Util.java
Normal file
347
app/src/main/java/cy/agorise/graphenej/Util.java
Normal file
|
@ -0,0 +1,347 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.tukaani.xz.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.InvalidCipherTextException;
|
||||
import org.spongycastle.crypto.engines.AESFastEngine;
|
||||
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate common utility methods
|
||||
*/
|
||||
public class Util {
|
||||
public static final String TAG = "Util";
|
||||
private static final char[] hexArray = "0123456789abcdef".toCharArray();
|
||||
public static final int LZMA = 0;
|
||||
public static final int XZ = 1;
|
||||
|
||||
/**
|
||||
* AES encryption key length in bytes
|
||||
*/
|
||||
public static final int KEY_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* Time format used across the platform
|
||||
*/
|
||||
public static final String TIME_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
|
||||
|
||||
/**
|
||||
* Converts an hexadecimal string to its corresponding byte[] value.
|
||||
* @param s: String with hexadecimal numbers representing a byte array.
|
||||
* @return: The actual byte array.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array, into a user-friendly hexadecimal string.
|
||||
* @param bytes: A byte array.
|
||||
* @return: A string with the representation of the byte array.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an ascii string to a byte array.
|
||||
* @param data: Arbitrary ascii-encoded string.
|
||||
* @return: Array of bytes.
|
||||
*/
|
||||
public static byte[] hexlify(String data){
|
||||
ByteBuffer buffer = ByteBuffer.allocate(data.length());
|
||||
for(char letter : data.toCharArray()){
|
||||
buffer.put((byte) letter);
|
||||
}
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that compresses data using the LZMA algorithm.
|
||||
* @param inputBytes Input bytes of the data to be compressed.
|
||||
* @param which Which subclass of the FinishableOutputStream to use.
|
||||
* @return Compressed data
|
||||
* @author Henry Varona
|
||||
*/
|
||||
public static byte[] compress(byte[] inputBytes, int which) {
|
||||
FinishableOutputStream out = null;
|
||||
try {
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(inputBytes);
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream(2048);
|
||||
LZMA2Options options = new LZMA2Options();
|
||||
if(which == Util.LZMA) {
|
||||
out = new LZMAOutputStream(output, options, -1);
|
||||
}else if(which == Util.XZ){
|
||||
out = new XZOutputStream(output, options);
|
||||
}
|
||||
byte[] inputBuffer = new byte[inputBytes.length];
|
||||
int size;
|
||||
while ((size = input.read(inputBuffer)) != -1) {
|
||||
out.write(inputBuffer, 0, size);
|
||||
}
|
||||
out.finish();
|
||||
return output.toByteArray();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that decompresses data that has been compressed using the LZMA algorithm
|
||||
* by the {@link Util#compress(byte[], int)} method.
|
||||
* @param inputBytes Compressed data.
|
||||
* @param which Which subclass if InputStream to use.
|
||||
* @return Uncompressed data
|
||||
* @author Henry Varona
|
||||
*/
|
||||
public static byte[] decompress(byte[] inputBytes, int which) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
System.out.println("Bytes: "+Util.bytesToHex(inputBytes));
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(inputBytes);
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream(16*2048);
|
||||
if(which == XZ) {
|
||||
in = new XZInputStream(input);
|
||||
}else if(which == LZMA){
|
||||
in = new LZMAInputStream(input);
|
||||
}
|
||||
int size;
|
||||
try{
|
||||
while ((size = in.read()) != -1) {
|
||||
output.write(size);
|
||||
}
|
||||
}catch(CorruptedInputException e){
|
||||
// Taking property byte
|
||||
byte[] properties = Arrays.copyOfRange(inputBytes, 0, 1);
|
||||
// Taking dict size bytes
|
||||
byte[] dictSize = Arrays.copyOfRange(inputBytes, 1, 5);
|
||||
// Taking uncompressed size bytes
|
||||
byte[] uncompressedSize = Arrays.copyOfRange(inputBytes, 5, 13);
|
||||
|
||||
// Reversing bytes in header
|
||||
byte[] header = Bytes.concat(properties, Util.revertBytes(dictSize), Util.revertBytes(uncompressedSize));
|
||||
byte[] payload = Arrays.copyOfRange(inputBytes, 13, inputBytes.length);
|
||||
|
||||
// Trying again
|
||||
input = new ByteArrayInputStream(Bytes.concat(header, payload));
|
||||
output = new ByteArrayOutputStream(2048);
|
||||
if(which == XZ) {
|
||||
in = new XZInputStream(input);
|
||||
}else if(which == LZMA){
|
||||
in = new LZMAInputStream(input);
|
||||
}
|
||||
try{
|
||||
while ((size = in.read()) != -1) {
|
||||
output.write(size);
|
||||
}
|
||||
}catch(CorruptedInputException ex){
|
||||
System.out.println("CorruptedInputException. Msg: "+ex.getMessage());
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
return output.toByteArray();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of bytes with the underlying data used to represent an integer in the reverse form.
|
||||
* This is useful for endianess switches, meaning that if you give this function a big-endian integer
|
||||
* it will return it's little-endian bytes.
|
||||
* @param input An Integer value.
|
||||
* @return The array of bytes that represent this value in the reverse format.
|
||||
*/
|
||||
public static byte[] revertInteger(Integer input){
|
||||
return ByteBuffer.allocate(Integer.SIZE / 8).putInt(Integer.reverseBytes(input)).array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same operation as in the revertInteger function, but in this case for a short (2 bytes) value.
|
||||
* @param input A Short value
|
||||
* @return The array of bytes that represent this value in the reverse format.
|
||||
*/
|
||||
public static byte[] revertShort(Short input){
|
||||
return ByteBuffer.allocate(Short.SIZE / 8).putShort(Short.reverseBytes(input)).array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same operation as in the revertInteger function, but in this case for a long (8 bytes) value.
|
||||
* @param input A Long value
|
||||
* @return The array of bytes that represent this value in the reverse format.
|
||||
*/
|
||||
public static byte[] revertLong(Long input) {
|
||||
return ByteBuffer.allocate(Long.SIZE / 8).putLong(Long.reverseBytes(input)).array();
|
||||
}
|
||||
|
||||
public static byte[] revertBytes(byte[] array){
|
||||
byte[] reverted = new byte[array.length];
|
||||
for(int i = 0; i < reverted.length; i++){
|
||||
reverted[i] = array[array.length - i - 1];
|
||||
}
|
||||
return reverted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to encrypt a message with AES
|
||||
* @param input data to encrypt
|
||||
* @param key key for encryption
|
||||
* @return AES Encription of input
|
||||
*/
|
||||
public static byte[] encryptAES(byte[] input, byte[] key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
byte[] result = md.digest(key);
|
||||
byte[] ivBytes = new byte[16];
|
||||
System.arraycopy(result, 32, ivBytes, 0, 16);
|
||||
byte[] sksBytes = new byte[32];
|
||||
System.arraycopy(result, 0, sksBytes, 0, 32);
|
||||
|
||||
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
cipher.init(true, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes));
|
||||
byte[] temp = new byte[input.length + (16 - (input.length % 16))];
|
||||
System.arraycopy(input, 0, temp, 0, input.length);
|
||||
Arrays.fill(temp, input.length, temp.length, (byte) (16 - (input.length % 16)));
|
||||
byte[] out = new byte[cipher.getOutputSize(temp.length)];
|
||||
int proc = cipher.processBytes(temp, 0, temp.length, out, 0);
|
||||
cipher.doFinal(out, proc);
|
||||
temp = new byte[out.length - 16];
|
||||
System.arraycopy(out, 0, temp, 0, temp.length);
|
||||
return temp;
|
||||
} catch (NoSuchAlgorithmException | DataLengthException | IllegalStateException | InvalidCipherTextException ex) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to decrypt a message with AES encryption
|
||||
* @param input data to decrypt
|
||||
* @param key key for decryption
|
||||
* @return input decrypted with AES. Null if the decrypt failed (Bad Key)
|
||||
*/
|
||||
public static byte[] decryptAES(byte[] input, byte[] key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
byte[] result = md.digest(key);
|
||||
byte[] ivBytes = new byte[16];
|
||||
System.arraycopy(result, 32, ivBytes, 0, 16);
|
||||
byte[] sksBytes = new byte[32];
|
||||
System.arraycopy(result, 0, sksBytes, 0, 32);
|
||||
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
|
||||
cipher.init(false, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes));
|
||||
|
||||
byte[] pre_out = new byte[cipher.getOutputSize(input.length)];
|
||||
int proc = cipher.processBytes(input, 0, input.length, pre_out, 0);
|
||||
int proc2 = cipher.doFinal(pre_out, proc);
|
||||
byte[] out = new byte[proc+proc2];
|
||||
System.arraycopy(pre_out, 0, out, 0, proc+proc2);
|
||||
|
||||
//Unpadding
|
||||
byte countByte = (byte)((byte)out[out.length-1] % 16);
|
||||
int count = countByte & 0xFF;
|
||||
|
||||
if ((count > 15) || (count <= 0)){
|
||||
return out;
|
||||
}
|
||||
|
||||
byte[] temp = new byte[count];
|
||||
System.arraycopy(out, out.length - count, temp, 0, temp.length);
|
||||
byte[] temp2 = new byte[count];
|
||||
Arrays.fill(temp2, (byte) count);
|
||||
if (Arrays.equals(temp, temp2)) {
|
||||
temp = new byte[out.length - count];
|
||||
System.arraycopy(out, 0, temp, 0, out.length - count);
|
||||
return temp;
|
||||
} else {
|
||||
return out;
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | DataLengthException | IllegalStateException | InvalidCipherTextException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an array of bytes to an hex String representation
|
||||
* @param input array of bytes to transform as a string
|
||||
* @return Input as a String
|
||||
*/
|
||||
public static String byteToString(byte[] input) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte in : input) {
|
||||
if ((in & 0xff) < 0x10) {
|
||||
result.append("0");
|
||||
}
|
||||
result.append(Integer.toHexString(in & 0xff));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a base value to its standard version, considering the precision of the asset.
|
||||
*
|
||||
* By standard representation we mean here the value that is usually presented to the user,
|
||||
* and which already takes into account the precision of the asset.
|
||||
*
|
||||
* For example, a base representation of the core token BTS would be 260000. By taking into
|
||||
* consideration the precision, the same value when converted to the standard format will
|
||||
* be 2.6 BTS.
|
||||
*
|
||||
* @param assetAmount: The asset amount instance.
|
||||
* @return: Converts from base to standard representation.
|
||||
*/
|
||||
public static double fromBase(AssetAmount assetAmount){
|
||||
long value = assetAmount.getAmount().longValue();
|
||||
int precision = assetAmount.getAsset().getPrecision();
|
||||
if(precision != 0)
|
||||
return value / Math.pow(10, precision);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value and its corresponding precision to a base value.
|
||||
* @param value: The value in the standard format
|
||||
* @param precision: The precision of the asset.
|
||||
* @return: A value in its base representation.
|
||||
*/
|
||||
public static long toBase(double value, int precision){
|
||||
return (long) (value * Math.pow(10, precision));
|
||||
}
|
||||
}
|
224
app/src/main/java/cy/agorise/graphenej/Varint.java
Normal file
224
app/src/main/java/cy/agorise/graphenej/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 cy.agorise.graphenej;
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
33
app/src/main/java/cy/agorise/graphenej/Vote.java
Normal file
33
app/src/main/java/cy/agorise/graphenej/Vote.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/5/16.
|
||||
*/
|
||||
public class Vote implements ByteSerializable {
|
||||
private int type;
|
||||
private int instance;
|
||||
|
||||
public Vote(String vote){
|
||||
String[] parts = vote.split(":");
|
||||
assert(parts.length == 2);
|
||||
this.type = Integer.valueOf(parts[0]);
|
||||
this.instance = Integer.valueOf(parts[1]);
|
||||
}
|
||||
|
||||
public Vote(int type, int instance){
|
||||
this.type = type;
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%d:%d", this.type, this.instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return new byte[] { (byte) this.instance, (byte) this.type };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketAdapter;
|
||||
import com.neovisionaries.ws.client.WebSocketException;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.NodeErrorListener;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
|
||||
/**
|
||||
* Base class that should be extended by any implementation of a specific request to the full node.
|
||||
*/
|
||||
public abstract class BaseGrapheneHandler extends WebSocketAdapter {
|
||||
|
||||
protected WitnessResponseListener mListener;
|
||||
protected NodeErrorListener mErrorListener;
|
||||
|
||||
/**
|
||||
* The 'id' field of a message to the node. This is used in order to multiplex different messages
|
||||
* using the same websocket connection.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* {"id":5,"method":"call","params":[0,"get_accounts",[["1.2.100"]]],"jsonrpc":"2.0"}
|
||||
*
|
||||
* The 'requestId' here is 5.
|
||||
*/
|
||||
protected long requestId;
|
||||
|
||||
/**
|
||||
* Constructor (The original constructor, should be replaced with the one that receives
|
||||
* NodeErrorListener instead of WitnessResponseListener)
|
||||
*
|
||||
* @param listener listener to be notified in if an error occurs
|
||||
*/
|
||||
@Deprecated
|
||||
public BaseGrapheneHandler(WitnessResponseListener listener){
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param listener listener to be notified if an error occurs
|
||||
*/
|
||||
public BaseGrapheneHandler(NodeErrorListener listener){
|
||||
this.mErrorListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
System.out.println("onError. cause: "+cause.getMessage());
|
||||
mErrorListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
websocket.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
System.out.println("handleCallbackError. message: "+cause.getMessage()+", error: "+cause.getClass());
|
||||
for (StackTraceElement element : cause.getStackTrace()){
|
||||
System.out.println(element.getFileName()+"#"+element.getClassName()+":"+element.getLineNumber());
|
||||
}
|
||||
// Should be replaced for mErrorListener (NodeErrorListener type) only in the future
|
||||
if(mErrorListener != null){
|
||||
mErrorListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
}
|
||||
else{
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
}
|
||||
|
||||
websocket.disconnect();
|
||||
}
|
||||
|
||||
public void setRequestId(long id){
|
||||
this.requestId = id;
|
||||
}
|
||||
|
||||
public long getRequestId(){
|
||||
return this.requestId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class that implements get_account_balances request handler.
|
||||
*
|
||||
* Get an account’s balances in various assets.
|
||||
*
|
||||
* The response returns the balances of the account
|
||||
*
|
||||
* @see <a href="https://goo.gl/faFdey">get_account_balances API doc</a>
|
||||
*
|
||||
*/
|
||||
public class GetAccountBalances extends BaseGrapheneHandler {
|
||||
|
||||
private UserAccount mUserAccount;
|
||||
private List<Asset> mAssetList;
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param userAccount account to get balances for
|
||||
* @param assets list of the assets to get balances of; if empty, get all assets account
|
||||
* has a balance in
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccountBalances(UserAccount userAccount, List<Asset> assets, boolean oneTime, WitnessResponseListener listener) {
|
||||
super(listener);
|
||||
this.mUserAccount = userAccount;
|
||||
this.mAssetList = assets;
|
||||
this.mOneTime = oneTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param userAccount account to get balances for
|
||||
* @param assets list of the assets to get balances of; if empty, get all assets account
|
||||
* has a balance in
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccountBalances(UserAccount userAccount, List<Asset> assets, WitnessResponseListener listener) {
|
||||
this(userAccount, assets, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> assetList = new ArrayList<>();
|
||||
for(Asset asset : mAssetList){
|
||||
assetList.add(asset.getObjectId());
|
||||
}
|
||||
params.add(mUserAccount.getObjectId());
|
||||
params.add(assetList);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.GET_ACCOUNT_BALANCES, params, RPC.VERSION, 0);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println("<< "+frame.getPayloadText());
|
||||
}
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
|
||||
Type WitnessResponseType = new TypeToken<WitnessResponse<List<AssetAmount>>>(){}.getType();
|
||||
WitnessResponse<List<AssetAmount>> witnessResponse = gsonBuilder.create().fromJson(response, WitnessResponseType);
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println(">> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
}
|
102
app/src/main/java/cy/agorise/graphenej/api/GetAccountByName.java
Normal file
102
app/src/main/java/cy/agorise/graphenej/api/GetAccountByName.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.AccountOptions;
|
||||
import cy.agorise.graphenej.Authority;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.AccountProperties;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_account_by_name request handler.
|
||||
*
|
||||
* Get an account’s info by name.
|
||||
*
|
||||
* The response returns account data that refer to the name.
|
||||
*
|
||||
* @see <a href="https://goo.gl/w75qjV">get_account_by_name API doc</a>
|
||||
*/
|
||||
public class GetAccountByName extends BaseGrapheneHandler {
|
||||
|
||||
private String accountName;
|
||||
private WitnessResponseListener mListener;
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param accountName name of the account to get info
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccountByName(String accountName, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.accountName = accountName;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param accountName name of the account to get info
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccountByName(String accountName, WitnessResponseListener listener){
|
||||
this(accountName, true, 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, RPC.VERSION, 1);
|
||||
websocket.sendText(getAccountByName.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
Type GetAccountByNameResponse = new TypeToken<WitnessResponse<AccountProperties>>(){}.getType();
|
||||
builder.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer());
|
||||
builder.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer());
|
||||
WitnessResponse<AccountProperties> witnessResponse = builder.create().fromJson(response, GetAccountByNameResponse);
|
||||
|
||||
if(witnessResponse.error != null){
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
}else{
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/26/16.
|
||||
*/
|
||||
//TODO: Implement if needed: http://docs.bitshares.eu/api/history.html?highlight=get_market_history#account-history-api
|
||||
public class GetAccountHistory {
|
||||
}
|
139
app/src/main/java/cy/agorise/graphenej/api/GetAccounts.java
Normal file
139
app/src/main/java/cy/agorise/graphenej/api/GetAccounts.java
Normal file
|
@ -0,0 +1,139 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.AccountOptions;
|
||||
import cy.agorise.graphenej.Authority;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.AccountProperties;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_accounts request handler.
|
||||
*
|
||||
* Get a list of accounts by ID.
|
||||
*
|
||||
* The response returns the accounts corresponding to the provided IDs.
|
||||
*
|
||||
* @see <a href="https://goo.gl/r5RqKG">get_accounts API doc</a>
|
||||
*/
|
||||
public class GetAccounts extends BaseGrapheneHandler {
|
||||
private String accountId;
|
||||
private List<UserAccount> userAccounts;
|
||||
private WitnessResponseListener mListener;
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Constructor for one account only.
|
||||
*
|
||||
* @param accountId ID of the account to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccounts(String accountId, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.accountId = accountId;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for account list.
|
||||
*
|
||||
* @param accounts list with the accounts to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccounts(List<UserAccount> accounts, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.userAccounts = accounts;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response. (Account based)
|
||||
*
|
||||
* @param accountId ID of the account to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccounts(String accountId, WitnessResponseListener listener){
|
||||
this(accountId, true, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response. (Account List
|
||||
* based)
|
||||
*
|
||||
* @param accounts list with the accounts to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAccounts(List<UserAccount> accounts, WitnessResponseListener listener){
|
||||
this(accounts, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> params = new ArrayList();
|
||||
ArrayList<Serializable> accountIds = new ArrayList();
|
||||
if(accountId == null){
|
||||
for(UserAccount account : userAccounts) {
|
||||
accountIds.add(account.getObjectId());
|
||||
}
|
||||
}else{
|
||||
accountIds.add(accountId);
|
||||
}
|
||||
params.add(accountIds);
|
||||
ApiCall getAccountByAddress = new ApiCall(0, RPC.CALL_GET_ACCOUNTS, params, RPC.VERSION, (int) requestId);
|
||||
websocket.sendText(getAccountByAddress.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
Type GetAccountByAddressResponse = new TypeToken<WitnessResponse<List<AccountProperties>>>() {}.getType();
|
||||
builder.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer());
|
||||
builder.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer());
|
||||
WitnessResponse<List<AccountProperties>> witnessResponse = builder.create().fromJson(response, GetAccountByAddressResponse);
|
||||
|
||||
if (witnessResponse.error != null) {
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
} else {
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class that implements get_all_asset_holders request handler.
|
||||
*
|
||||
* Get a list of all system assets with holders count.
|
||||
*
|
||||
* The response returns the list of all assets with holders count.
|
||||
*
|
||||
* @see <a href="https://goo.gl/AgTSLU">get_all_asset_holders API doc (source code ref.)</a>
|
||||
*/
|
||||
public class GetAllAssetHolders extends BaseGrapheneHandler {
|
||||
private final static int LOGIN_ID = 1;
|
||||
private final static int GET_ASSET_API_ID = 2;
|
||||
private final static int GET_ALL_ASSET_HOLDERS_COUNT = 3;
|
||||
|
||||
private int currentId = 1;
|
||||
private int assetApiId = -1;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetAllAssetHolders(boolean oneTime, WitnessResponseListener listener) {
|
||||
super(listener);
|
||||
this.mOneTime = oneTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @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 GetAllAssetHolders(WitnessResponseListener listener) { this(true, 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, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
String response = frame.getPayloadText();
|
||||
Gson gson = new Gson();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if(baseResponse.error != null){
|
||||
mListener.onError(baseResponse.error);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else {
|
||||
currentId++;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
if (baseResponse.id == LOGIN_ID) {
|
||||
ApiCall networkApiIdCall = new ApiCall(1, RPC.CALL_ASSET, emptyParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(networkApiIdCall.toJsonString());
|
||||
}else if(baseResponse.id == GET_ASSET_API_ID){
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
assetApiId = witnessResponse.result;
|
||||
|
||||
ApiCall apiCall = new ApiCall(assetApiId, RPC.CALL_GET_ALL_ASSET_HOLDERS, emptyParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
} else if (baseResponse.id == GET_ALL_ASSET_HOLDERS_COUNT) {
|
||||
Type AssetTokenHolders = new TypeToken<WitnessResponse<List<AssetHolderCount>>>(){}.getType();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(AssetHolderCount.class, new AssetHolderCount.HoldersCountDeserializer());
|
||||
WitnessResponse<List<AssetHolderCount>> witnessResponse = builder.create().fromJson(response, AssetTokenHolders);
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else{
|
||||
System.out.println("current id: "+currentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
}
|
129
app/src/main/java/cy/agorise/graphenej/api/GetBlockHeader.java
Normal file
129
app/src/main/java/cy/agorise/graphenej/api/GetBlockHeader.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.BlockHeader;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_block_header request handler.
|
||||
*
|
||||
* Retrieve a block header.
|
||||
*
|
||||
* The request returns the header of the referenced block, or null if no matching block was found
|
||||
*
|
||||
* @see <a href="https://goo.gl/qw1eeb">get_block_header API doc</a>
|
||||
*/
|
||||
public class GetBlockHeader extends BaseGrapheneHandler {
|
||||
|
||||
// Sequence of message ids
|
||||
private final static int LOGIN_ID = 1;
|
||||
private final static int GET_DATABASE_ID = 2;
|
||||
private final static int GET_BLOCK_HEADER_ID = 3;
|
||||
|
||||
private long blockNumber;
|
||||
private WitnessResponseListener mListener;
|
||||
|
||||
private int currentId = LOGIN_ID;
|
||||
private int apiId = -1;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param blockNumber height of the block whose header should be returned
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetBlockHeader(long blockNumber, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.blockNumber = blockNumber;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param blockNumber height of the block whose header should be returned
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetBlockHeader(long blockNumber, WitnessResponseListener listener){
|
||||
this(blockNumber, true, 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, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
System.out.println("<<< "+response);
|
||||
|
||||
Gson gson = new Gson();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if(baseResponse.error != null){
|
||||
mListener.onError(baseResponse.error);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else {
|
||||
currentId++;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
if(baseResponse.id == LOGIN_ID){
|
||||
ApiCall getDatabaseId = new ApiCall(1, RPC.CALL_DATABASE, emptyParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(getDatabaseId.toJsonString());
|
||||
}else if(baseResponse.id == GET_DATABASE_ID){
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
apiId = witnessResponse.result.intValue();
|
||||
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
String blockNum = String.format("%d", this.blockNumber);
|
||||
params.add(blockNum);
|
||||
|
||||
ApiCall loginCall = new ApiCall(apiId, RPC.CALL_GET_BLOCK_HEADER, params, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}else if(baseResponse.id == GET_BLOCK_HEADER_ID){
|
||||
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<BlockHeader>>(){}.getType();
|
||||
WitnessResponse<BlockHeader> transfersResponse = gson.fromJson(response, RelativeAccountHistoryResponse);
|
||||
mListener.onSuccess(transfersResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
129
app/src/main/java/cy/agorise/graphenej/api/GetKeyReferences.java
Normal file
129
app/src/main/java/cy/agorise/graphenej/api/GetKeyReferences.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_key_references request handler.
|
||||
*
|
||||
* Retrieve the keys that refer to the address/list of addresses.
|
||||
*
|
||||
* The request returns all accounts that refer to the key or account id in their owner or active authorities.
|
||||
*
|
||||
* @see <a href="https://goo.gl/np8CYF">get_key_references API doc</a>
|
||||
*/
|
||||
public class GetKeyReferences extends BaseGrapheneHandler {
|
||||
|
||||
private List<Address> addresses;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param address address to be query
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not (false)
|
||||
* after the response
|
||||
* @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 GetKeyReferences(Address address, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
addresses = new ArrayList<>();
|
||||
addresses.add(address);
|
||||
this.mOneTime = oneTime;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param addresses list of addresses to be query
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not (false)
|
||||
* after the response
|
||||
* @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 GetKeyReferences(List<Address> addresses, boolean oneTime, WitnessResponseListener listener) {
|
||||
super(listener);
|
||||
this.addresses = addresses;
|
||||
this.mListener = listener;
|
||||
this.mOneTime = oneTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the websocket connection closes after the response.
|
||||
*
|
||||
* @param address
|
||||
* @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 GetKeyReferences(Address address, WitnessResponseListener listener){
|
||||
this(address, true, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the websocket connection closes after the response.
|
||||
*
|
||||
* @param addresses
|
||||
* @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 GetKeyReferences(List<Address> addresses, WitnessResponseListener listener) {
|
||||
this(addresses, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> inner = new ArrayList();
|
||||
for(Address addr : addresses){
|
||||
inner.add(addr.toString());
|
||||
}
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(inner);
|
||||
ApiCall getAccountByAddress = new ApiCall(0, RPC.CALL_GET_KEY_REFERENCES, params, RPC.VERSION, 1);
|
||||
websocket.sendText(getAccountByAddress.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
Type GetAccountByAddressResponse = new TypeToken<WitnessResponse<List<List<UserAccount>>>>(){}.getType();
|
||||
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
|
||||
WitnessResponse<List<List<UserAccount>>> witnessResponse = builder.create().fromJson(response, GetAccountByAddressResponse);
|
||||
if (witnessResponse.error != null) {
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
} else {
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
137
app/src/main/java/cy/agorise/graphenej/api/GetLimitOrders.java
Normal file
137
app/src/main/java/cy/agorise/graphenej/api/GetLimitOrders.java
Normal file
|
@ -0,0 +1,137 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.LimitOrder;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_limit_orders request handler.
|
||||
*
|
||||
* Get limit orders in a given market.
|
||||
*
|
||||
* The request returns the limit orders, ordered from least price to greatest
|
||||
*
|
||||
* @see <a href="https://goo.gl/5sRTRq">get_limit_orders API doc</a>
|
||||
*
|
||||
*/
|
||||
public class GetLimitOrders extends BaseGrapheneHandler {
|
||||
|
||||
private String a;
|
||||
private String b;
|
||||
private int limit;
|
||||
private WitnessResponseListener mListener;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param a id of asset being sold
|
||||
* @param b id of asset being purchased
|
||||
* @param limit maximum number of orders to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetLimitOrders(String a, String b, int limit, boolean oneTime, WitnessResponseListener listener) {
|
||||
super(listener);
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.limit = limit;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param a id of asset being sold
|
||||
* @param b id of asset being purchased
|
||||
* @param limit maximum number of orders to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetLimitOrders(String a, String b, int limit, WitnessResponseListener listener) {
|
||||
this(a, b, limit, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add(this.a);
|
||||
accountParams.add(this.b);
|
||||
accountParams.add(this.limit);
|
||||
ApiCall getAccountByName = new ApiCall(0, RPC.CALL_GET_LIMIT_ORDERS, accountParams, RPC.VERSION, 1);
|
||||
websocket.sendText(getAccountByName.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
try {
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
Type GetLimitOrdersResponse = new TypeToken<WitnessResponse<List<LimitOrder>>>() {}.getType();
|
||||
builder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
|
||||
builder.registerTypeAdapter(LimitOrder.class, new LimitOrder.LimitOrderDeserializer());
|
||||
WitnessResponse<List<LimitOrder>> witnessResponse = builder.create().fromJson(response, GetLimitOrdersResponse);
|
||||
if (witnessResponse.error != null) {
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
} else {
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
243
app/src/main/java/cy/agorise/graphenej/api/GetMarketHistory.java
Normal file
243
app/src/main/java/cy/agorise/graphenej/api/GetMarketHistory.java
Normal file
|
@ -0,0 +1,243 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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 cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.BucketObject;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_market_history request handler.
|
||||
*
|
||||
* Get mar
|
||||
*
|
||||
* @see <a href="https://goo.gl/hfVFBW">get_market_history API doc</a>
|
||||
*
|
||||
*/
|
||||
public class GetMarketHistory extends BaseGrapheneHandler {
|
||||
|
||||
// Sequence of message ids
|
||||
private final static int LOGIN_ID = 1;
|
||||
private final static int GET_HISTORY_ID = 2;
|
||||
private final static int GET_HISTORY_DATA = 3;
|
||||
|
||||
// API call parameters
|
||||
private Asset base;
|
||||
private Asset quote;
|
||||
private long bucket;
|
||||
private Date start;
|
||||
private Date end;
|
||||
private WitnessResponseListener mListener;
|
||||
private WebSocket mWebsocket;
|
||||
private int currentId = 1;
|
||||
private int apiId = -1;
|
||||
private int counter = 0;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param base asset which history is desired
|
||||
* @param quote asset which the base price asset will be compared to
|
||||
* @param bucket the time interval (in seconds) for each point should be (analog to
|
||||
* candles on a candle stick graph).
|
||||
* Note: The bucket value is discrete and node dependent. The default value
|
||||
* is 3600s. To get the available buckets of a node use
|
||||
* get_all_asset_holders API call.
|
||||
* @param start datetime of of the most recent operation to retrieve (Note: The name is
|
||||
* counter intuitive, but it follow the original API parameter name)
|
||||
* @param end datetime of the the earliest operation to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetMarketHistory(Asset base, Asset quote, long bucket, Date start, Date end, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.base = base;
|
||||
this.quote = quote;
|
||||
this.bucket = bucket;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param base asset which history is desired
|
||||
* @param quote asset which the base price asset will be compared to
|
||||
* @param bucket the time interval (in seconds) for each point should be (analog to
|
||||
* candles on a candle stick graph).
|
||||
* Note: The bucket value is discrete and node dependent. The default value
|
||||
* is 3600s. To get the available buckets of a node use
|
||||
* get_all_asset_holders API call.
|
||||
* @param start datetime of of the most recent operation to retrieve (Note: The name is
|
||||
* counter intuitive, but it follow the original API parameter name)
|
||||
* @param end datetime of the the earliest operation to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetMarketHistory(Asset base, Asset quote, long bucket, Date start, Date end, WitnessResponseListener listener){
|
||||
this(base, quote, bucket, start, end, true, listener);
|
||||
}
|
||||
|
||||
public Asset getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public void setBase(Asset base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
public Asset getQuote() {
|
||||
return quote;
|
||||
}
|
||||
|
||||
public void setQuote(Asset quote) {
|
||||
this.quote = quote;
|
||||
}
|
||||
|
||||
public long getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
public void setBucket(long bucket) {
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
public Date getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Date start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public Date getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(Date end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public int getCount(){
|
||||
return this.counter;
|
||||
}
|
||||
|
||||
public void disconnect(){
|
||||
if(mWebsocket != null && mWebsocket.isOpen()){
|
||||
mWebsocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries the 'get_market_history' API call.
|
||||
* Hopefully with different 'start' and 'stop' parameters.
|
||||
*/
|
||||
public void retry(){
|
||||
sendHistoricalMarketDataRequest();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
mWebsocket = websocket;
|
||||
ArrayList<Serializable> loginParams = new ArrayList<>();
|
||||
loginParams.add(null);
|
||||
loginParams.add(null);
|
||||
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
System.out.println("<<< "+response);
|
||||
Gson gson = new Gson();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if(baseResponse.error != null){
|
||||
mListener.onError(baseResponse.error);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else{
|
||||
currentId++;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
if(baseResponse.id == LOGIN_ID){
|
||||
ApiCall getRelativeAccountHistoryId = new ApiCall(1, RPC.CALL_HISTORY, emptyParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(getRelativeAccountHistoryId.toJsonString());
|
||||
} else if(baseResponse.id == GET_HISTORY_ID){
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
apiId = witnessResponse.result.intValue();
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
|
||||
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(this.base.getObjectId());
|
||||
params.add(this.quote.getObjectId());
|
||||
params.add(this.bucket);
|
||||
params.add(dateFormat.format(this.start));
|
||||
params.add(dateFormat.format(this.end));
|
||||
|
||||
ApiCall getRelativeAccountHistoryCall = new ApiCall(apiId, RPC.CALL_GET_MARKET_HISTORY, params, RPC.VERSION, currentId);
|
||||
websocket.sendText(getRelativeAccountHistoryCall.toJsonString());
|
||||
}else if(baseResponse.id >= GET_HISTORY_DATA){
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
Type MarketHistoryResponse = new TypeToken<WitnessResponse<List<BucketObject>>>(){}.getType();
|
||||
builder.registerTypeAdapter(BucketObject.class, new BucketObject.BucketDeserializer());
|
||||
WitnessResponse<List<BucketObject>> marketHistoryResponse = builder.create().fromJson(response, MarketHistoryResponse);
|
||||
mListener.onSuccess(marketHistoryResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually sends the 'get_market_history' API call request. This method might be called multiple
|
||||
* times during the life-cycle of this instance because we might not have gotten anything
|
||||
* in the first requested interval.
|
||||
*/
|
||||
private void sendHistoricalMarketDataRequest(){
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
|
||||
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(this.base.getObjectId());
|
||||
params.add(this.quote.getObjectId());
|
||||
params.add(this.bucket);
|
||||
params.add(dateFormat.format(this.start));
|
||||
params.add(dateFormat.format(this.end));
|
||||
|
||||
ApiCall getRelativeAccountHistoryCall = new ApiCall(apiId, RPC.CALL_GET_MARKET_HISTORY, params, RPC.VERSION, currentId);
|
||||
mWebsocket.sendText(getRelativeAccountHistoryCall.toJsonString());
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
139
app/src/main/java/cy/agorise/graphenej/api/GetObjects.java
Normal file
139
app/src/main/java/cy/agorise/graphenej/api/GetObjects.java
Normal file
|
@ -0,0 +1,139 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.AccountOptions;
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.Authority;
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BitAssetData;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class that implements get_objects request handler.
|
||||
*
|
||||
* Get the objects corresponding to the provided IDs.
|
||||
*
|
||||
* The response returns a list of objects retrieved, in the order they are mentioned in ids
|
||||
*
|
||||
* @see <a href="https://goo.gl/isRfeg">get_objects API doc</a>
|
||||
*
|
||||
*/
|
||||
public class GetObjects extends BaseGrapheneHandler {
|
||||
private List<String> ids;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param ids list of IDs of the objects to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetObjects(List<String> ids, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.ids = ids;
|
||||
this.mOneTime = oneTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param ids list of IDs of the objects to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetObjects(List<String> ids, WitnessResponseListener listener){
|
||||
this(ids, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> subParams = new ArrayList<>();
|
||||
for(String id : this.ids){
|
||||
subParams.add(id);
|
||||
}
|
||||
params.add(subParams);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.GET_OBJECTS, params, RPC.VERSION, 0);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println("<< "+frame.getPayloadText());
|
||||
}
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountFullDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer());
|
||||
Gson gson = gsonBuilder.create();
|
||||
|
||||
List<GrapheneObject> parsedResult = new ArrayList<>();
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonArray resultArray = parser.parse(response).getAsJsonObject().get(WitnessResponse.KEY_RESULT).getAsJsonArray();
|
||||
for(JsonElement element : resultArray){
|
||||
String id = element.getAsJsonObject().get(GrapheneObject.KEY_ID).getAsString();
|
||||
GrapheneObject grapheneObject = new GrapheneObject(id);
|
||||
switch (grapheneObject.getObjectType()){
|
||||
case ASSET_OBJECT:
|
||||
Asset asset = gson.fromJson(element, Asset.class);
|
||||
parsedResult.add(asset);
|
||||
break;
|
||||
case ACCOUNT_OBJECT:
|
||||
UserAccount account = gson.fromJson(element, UserAccount.class);
|
||||
parsedResult.add(account);
|
||||
break;
|
||||
case ASSET_BITASSET_DATA:
|
||||
Type BitAssetDataType = new TypeToken<WitnessResponse<List<BitAssetData>>>(){}.getType();
|
||||
WitnessResponse<List<BitAssetData>> witnessResponse = gsonBuilder.create().fromJson(response, BitAssetDataType);
|
||||
BitAssetData bitAssetData = witnessResponse.result.get(0);
|
||||
parsedResult.add(bitAssetData);
|
||||
}
|
||||
}
|
||||
|
||||
WitnessResponse<List<GrapheneObject>> output = new WitnessResponse<>();
|
||||
output.result = parsedResult;
|
||||
mListener.onSuccess(output);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println(">> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.HistoricalTransfer;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the communication sequence used to retrieve the transaction history of
|
||||
* a given user.
|
||||
*/
|
||||
public class GetRelativeAccountHistory extends BaseGrapheneHandler {
|
||||
// Sequence of message ids
|
||||
private final static int LOGIN_ID = 1;
|
||||
private final static int GET_HISTORY_ID = 2;
|
||||
private final static int GET_HISTORY_DATA = 3;
|
||||
|
||||
// Default value constants
|
||||
public static final int DEFAULT_STOP = 0;
|
||||
public static final int DEFAULT_START = 0;
|
||||
public static final int MAX_LIMIT = 100;
|
||||
|
||||
// API call parameters
|
||||
private UserAccount mUserAccount;
|
||||
private int stop;
|
||||
private int limit;
|
||||
private int start;
|
||||
private WitnessResponseListener mListener;
|
||||
private WebSocket mWebsocket;
|
||||
|
||||
private int currentId = 1;
|
||||
private int apiId = -1;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Constructor that takes all possible parameters.
|
||||
*
|
||||
* @param userAccount The user account to be queried
|
||||
* @param stop Sequence number of earliest operation
|
||||
* @param limit Maximum number of operations to retrieve (must not exceed 100)
|
||||
* @param start Sequence number of the most recent operation to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetRelativeAccountHistory(UserAccount userAccount, int stop, int limit, int start, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
if(limit > MAX_LIMIT) limit = MAX_LIMIT;
|
||||
this.mUserAccount = userAccount;
|
||||
this.stop = stop;
|
||||
this.limit = limit;
|
||||
this.start = start;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that uses the default values, and sets the limit to its maximum possible value.
|
||||
*
|
||||
* @param userAccount The user account to be queried
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetRelativeAccountHistory(UserAccount userAccount, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.mUserAccount = userAccount;
|
||||
this.stop = DEFAULT_STOP;
|
||||
this.limit = MAX_LIMIT;
|
||||
this.start = DEFAULT_START;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that takes all possible parameters for the query.
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param userAccount The user account to be queried
|
||||
* @param stop Sequence number of earliest operation
|
||||
* @param limit Maximum number of operations to retrieve (must not exceed 100)
|
||||
* @param start Sequence number of the most recent operation to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetRelativeAccountHistory(UserAccount userAccount, int stop, int limit, int start, WitnessResponseListener listener){
|
||||
this(userAccount, stop, limit, start, true, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that uses the default values, and sets the limit to its maximum possible value.
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param userAccount The user account to be queried
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetRelativeAccountHistory(UserAccount userAccount, WitnessResponseListener listener){
|
||||
this(userAccount, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
mWebsocket = websocket;
|
||||
ArrayList<Serializable> loginParams = new ArrayList<>();
|
||||
loginParams.add(null);
|
||||
loginParams.add(null);
|
||||
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
System.out.println("<<< "+response);
|
||||
Gson gson = new Gson();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if(baseResponse.error != null){
|
||||
mListener.onError(baseResponse.error);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else{
|
||||
currentId++;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
if(baseResponse.id == LOGIN_ID){
|
||||
ApiCall getRelativeAccountHistoryId = new ApiCall(1, RPC.CALL_HISTORY, emptyParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(getRelativeAccountHistoryId.toJsonString());
|
||||
}else if(baseResponse.id == GET_HISTORY_ID){
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
|
||||
apiId = witnessResponse.result.intValue();
|
||||
|
||||
sendRelativeAccountHistoryRequest();
|
||||
}else if(baseResponse.id >= GET_HISTORY_DATA){
|
||||
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<List<HistoricalTransfer>>>(){}.getType();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
WitnessResponse<List<HistoricalTransfer>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
|
||||
mListener.onSuccess(transfersResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the actual get_relative_account_history request.
|
||||
*/
|
||||
private void sendRelativeAccountHistoryRequest(){
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(mUserAccount.getObjectId());
|
||||
params.add(this.stop);
|
||||
params.add(this.limit);
|
||||
params.add(this.start);
|
||||
|
||||
ApiCall getRelativeAccountHistoryCall = new ApiCall(apiId, RPC.CALL_GET_RELATIVE_ACCOUNT_HISTORY, params, RPC.VERSION, currentId);
|
||||
mWebsocket.sendText(getRelativeAccountHistoryCall.toJsonString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the arguments and makes a new call to the get_relative_account_history API.
|
||||
*
|
||||
* @param stop Sequence number of earliest operation
|
||||
* @param limit Maximum number of operations to retrieve (must not exceed 100)
|
||||
* @param start Sequence number of the most recent operation to retrieve
|
||||
*/
|
||||
public void retry(int stop, int limit, int start){
|
||||
this.stop = stop;
|
||||
this.limit = limit;
|
||||
this.start = start;
|
||||
sendRelativeAccountHistoryRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the WebSocket.
|
||||
*/
|
||||
public void disconnect(){
|
||||
if(mWebsocket != null && mWebsocket.isOpen() && mOneTime){
|
||||
mWebsocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
113
app/src/main/java/cy/agorise/graphenej/api/GetRequiredFees.java
Normal file
113
app/src/main/java/cy/agorise/graphenej/api/GetRequiredFees.java
Normal file
|
@ -0,0 +1,113 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.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 cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class that implements get_required_fees request handler.
|
||||
*
|
||||
* For each operation calculate the required fee in the specified asset type.
|
||||
*
|
||||
* @see <a href="https://goo.gl/MB4TXq">get_required_fees API doc</a>
|
||||
*/
|
||||
public class GetRequiredFees extends BaseGrapheneHandler {
|
||||
|
||||
private WitnessResponseListener mListener;
|
||||
private List<BaseOperation> operations;
|
||||
private Asset asset;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param operations list of operations that fee should be calculated
|
||||
* @param asset specify the asset of the operations
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetRequiredFees(List<BaseOperation> operations, Asset asset, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.operations = operations;
|
||||
this.asset = asset;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param operations list of operations that fee should be calculated
|
||||
* @param asset specify the asset of the operations
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetRequiredFees(List<BaseOperation> operations, Asset asset, WitnessResponseListener listener){
|
||||
this(operations, asset, true, 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, RPC.VERSION, 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.AssetAmountDeserializer());
|
||||
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()));
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
127
app/src/main/java/cy/agorise/graphenej/api/GetTradeHistory.java
Normal file
127
app/src/main/java/cy/agorise/graphenej/api/GetTradeHistory.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.MarketTrade;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements get_trade_history request handler.
|
||||
*
|
||||
* Get recent trades for the market assetA:assetB for a time interval
|
||||
* Note: Currently, timezone offsets are not supported. The time must be UTC.
|
||||
*
|
||||
* The request returns the all trades of the passed pair of asset at a specific time interval.
|
||||
*
|
||||
* @see <a href="https://goo.gl/Y1x3bE">get_trade_history API doc</a>
|
||||
*
|
||||
*/
|
||||
public class GetTradeHistory extends BaseGrapheneHandler {
|
||||
|
||||
private String a;
|
||||
private String b;
|
||||
private String toTime;
|
||||
private String fromTime;
|
||||
private int limit;
|
||||
private WitnessResponseListener mListener;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param a name of the first asset
|
||||
* @param b name of the second asset
|
||||
* @param toTime stop time as a UNIX timestamp
|
||||
* @param fromTime start time as a UNIX timestamp
|
||||
* @param limit number of transactions to retrieve, capped at 100
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetTradeHistory(String a, String b, String toTime, String fromTime,int limit, boolean oneTime, WitnessResponseListener listener) {
|
||||
super(listener);
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.toTime = toTime;
|
||||
this.fromTime = fromTime;
|
||||
this.limit = limit;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param a name of the first asset
|
||||
* @param b name of the second asset
|
||||
* @param toTime stop time as a UNIX timestamp
|
||||
* @param fromTime start time as a UNIX timestamp
|
||||
* @param limit number of transactions to retrieve, capped at 100
|
||||
* @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 operation.
|
||||
*/
|
||||
public GetTradeHistory(String a, String b, String toTime, String fromTime,int limit, WitnessResponseListener listener) {
|
||||
this(a, b, toTime, fromTime, limit, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add(this.a);
|
||||
accountParams.add(this.b);
|
||||
accountParams.add(this.toTime);
|
||||
accountParams.add(this.fromTime);
|
||||
accountParams.add(this.limit);
|
||||
|
||||
ApiCall getAccountByName = new ApiCall(0, RPC.CALL_GET_TRADE_HISTORY, accountParams, RPC.VERSION, 1);
|
||||
websocket.sendText(getAccountByName.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if (frame.isTextFrame()) {
|
||||
System.out.println("<<< " + frame.getPayloadText());
|
||||
}
|
||||
try {
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
Type GetTradeHistoryResponse = new TypeToken<WitnessResponse<List<MarketTrade>>>() {
|
||||
}.getType();
|
||||
WitnessResponse<List<MarketTrade>> witnessResponse = builder.create().fromJson(response, GetTradeHistoryResponse);
|
||||
if (witnessResponse.error != null) {
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
} else {
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if (frame.isTextFrame()) {
|
||||
System.out.println(">>> " + frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
}
|
146
app/src/main/java/cy/agorise/graphenej/api/ListAssets.java
Normal file
146
app/src/main/java/cy/agorise/graphenej/api/ListAssets.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WebSocketAdapter class used to send a request a 'list_assets' API call to the witness node.
|
||||
*
|
||||
* The API imposes a limit of of 100 assets per request, but if the user of this class wants
|
||||
* to get a list of all assets, the LIST_ALL constant must be used as second argument in the
|
||||
* constructor. Internally we are going to perform multiple calls in order to satisfy the user's
|
||||
* request.
|
||||
*
|
||||
* @see: <a href="http://docs.bitshares.org/development/namespaces/app.html"></a>
|
||||
*/
|
||||
public class ListAssets extends BaseGrapheneHandler {
|
||||
/**
|
||||
* Constant that must be used as argument to the constructor of this class to indicate
|
||||
* that the user wants to get all existing assets.
|
||||
*/
|
||||
public static final int LIST_ALL = -1;
|
||||
|
||||
/**
|
||||
* Internal constant used to represent the maximum limit of assets retrieved in one call.
|
||||
*/
|
||||
private final int MAX_BATCH_SIZE = 100;
|
||||
|
||||
private List<Asset> assets;
|
||||
private String lowerBound;
|
||||
private int limit;
|
||||
private int requestCounter = 0;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param lowerBoundSymbol Lower bound of symbol names to retrieve
|
||||
* @param limit Maximum number of assets to fetch, if the constant LIST_ALL
|
||||
* is passed, all existing assets will be retrieved.
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public ListAssets(String lowerBoundSymbol, int limit, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.lowerBound = lowerBoundSymbol;
|
||||
this.limit = limit;
|
||||
this.mOneTime = oneTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param lowerBoundSymbol Lower bound of symbol names to retrieve
|
||||
* @param limit Maximum number of assets to fetch, if the constant LIST_ALL
|
||||
* is passed, all existing assets will be retrieved.
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public ListAssets(String lowerBoundSymbol, int limit, WitnessResponseListener listener){
|
||||
this(lowerBoundSymbol, limit, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(this.lowerBound);
|
||||
if(limit > MAX_BATCH_SIZE || limit == LIST_ALL){
|
||||
params.add(MAX_BATCH_SIZE);
|
||||
}else{
|
||||
params.add(this.limit);
|
||||
}
|
||||
ApiCall apiCall = new ApiCall(0, RPC.CALL_LIST_ASSETS, params, RPC.VERSION, 0);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
Type LookupAssetSymbolsResponse = new TypeToken<WitnessResponse<List<Asset>>>(){}.getType();
|
||||
gsonBuilder.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer());
|
||||
WitnessResponse<List<Asset>> witnessResponse = gsonBuilder.create().fromJson(response, LookupAssetSymbolsResponse);
|
||||
if(this.limit != LIST_ALL && this.limit < MAX_BATCH_SIZE){
|
||||
// If the requested number of assets was below
|
||||
// the limit, we just call the listener.
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else{
|
||||
// Updating counter to keep track of how many batches we already retrieved.
|
||||
requestCounter++;
|
||||
if(this.assets == null){
|
||||
this.assets = new ArrayList<>();
|
||||
}
|
||||
this.assets.addAll(witnessResponse.result);
|
||||
|
||||
// Checking to see if we're done
|
||||
if(this.limit == LIST_ALL && witnessResponse.result.size() < MAX_BATCH_SIZE){
|
||||
// In case we requested all assets, we might be in the last round whenever
|
||||
// we got less than the requested amount.
|
||||
witnessResponse.result = this.assets;
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else if(this.assets.size() == this.limit){
|
||||
// We already have the required amount of assets
|
||||
witnessResponse.result = this.assets;
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else{
|
||||
// We still need to fetch some more assets
|
||||
this.lowerBound = this.assets.get(this.assets.size() - 1).getSymbol();
|
||||
int nextBatch = this.limit == LIST_ALL ? MAX_BATCH_SIZE : this.limit - (MAX_BATCH_SIZE * requestCounter);
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(this.lowerBound);
|
||||
params.add(nextBatch);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.CALL_LIST_ASSETS, params, RPC.VERSION, 0);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
app/src/main/java/cy/agorise/graphenej/api/LookupAccounts.java
Normal file
134
app/src/main/java/cy/agorise/graphenej/api/LookupAccounts.java
Normal file
|
@ -0,0 +1,134 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements lookup_accounts request handler.
|
||||
*
|
||||
* Get names and IDs for registered accounts.
|
||||
*
|
||||
* The request returns a map of account names to corresponding IDs.
|
||||
*
|
||||
* @see <a href="https://goo.gl/zhPjuW">lookup_accounts API doc</a>
|
||||
*/
|
||||
public class LookupAccounts extends BaseGrapheneHandler {
|
||||
|
||||
public static final int DEFAULT_MAX = 1000;
|
||||
private final String accountName;
|
||||
private int maxAccounts = DEFAULT_MAX;
|
||||
private final WitnessResponseListener mListener;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param accountName account name used at the query
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public LookupAccounts(String accountName, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.accountName = accountName;
|
||||
this.maxAccounts = DEFAULT_MAX;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with maxAccounts
|
||||
*
|
||||
* @param accountName account name used at the query
|
||||
* @param maxAccounts maximum number of results to return (must not exceed 1000)
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public LookupAccounts(String accountName, int maxAccounts, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.accountName = accountName;
|
||||
this.maxAccounts = maxAccounts;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param accountName account name used at the query
|
||||
* @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 operation.
|
||||
*/
|
||||
public LookupAccounts(String accountName, WitnessResponseListener listener){
|
||||
this(accountName, true, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param accountName account name used at the query
|
||||
* @param maxAccounts maximum number of results to return (must not exceed 1000)
|
||||
* @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 operation.
|
||||
*/
|
||||
public LookupAccounts(String accountName, int maxAccounts, WitnessResponseListener listener){
|
||||
this(accountName, maxAccounts, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add(this.accountName);
|
||||
accountParams.add(this.maxAccounts);
|
||||
ApiCall getAccountByName = new ApiCall(0, RPC.CALL_LOOKUP_ACCOUNTS, accountParams, RPC.VERSION, 1);
|
||||
websocket.sendText(getAccountByName.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
String response = frame.getPayloadText();
|
||||
|
||||
Type LookupAccountsResponse = new TypeToken<WitnessResponse<List<UserAccount>>>(){}.getType();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountDeserializer());
|
||||
WitnessResponse<List<UserAccount>> witnessResponse = builder.create().fromJson(response, LookupAccountsResponse);
|
||||
if(witnessResponse.error != null){
|
||||
this.mListener.onError(witnessResponse.error);
|
||||
}else{
|
||||
this.mListener.onSuccess(witnessResponse);
|
||||
}
|
||||
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that implements lookup_asset_symbols request handler.
|
||||
*
|
||||
* Get the assets corresponding to the provided IDs.
|
||||
*
|
||||
* The response returns the assets corresponding to the provided symbols or IDs.
|
||||
*
|
||||
* @see <a href="https://goo.gl/WvREGV">lookup_asset_symbols API doc</a>
|
||||
*/
|
||||
public class LookupAssetSymbols extends BaseGrapheneHandler {
|
||||
private WitnessResponseListener mListener;
|
||||
private List<Asset> assets;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param assets list of the assets to retrieve
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public LookupAssetSymbols(List<Asset> assets, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.assets = assets;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param assets list of the assets to retrieve
|
||||
* @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 operation.
|
||||
*/
|
||||
public LookupAssetSymbols(List<Asset> assets, WitnessResponseListener listener){
|
||||
this(assets, true, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<String> subArray = new ArrayList<>();
|
||||
for(Asset asset : this.assets){
|
||||
subArray.add(asset.getObjectId());
|
||||
params.add(subArray);
|
||||
}
|
||||
ApiCall loginCall = new ApiCall(0, RPC.CALL_LOOKUP_ASSET_SYMBOLS, params, RPC.VERSION, 0);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String response = frame.getPayloadText();
|
||||
System.out.println("<<< "+response);
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
Type LookupAssetSymbolsResponse = new TypeToken<WitnessResponse<List<Asset>>>(){}.getType();
|
||||
gsonBuilder.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer());
|
||||
WitnessResponse<List<Asset>> witnessResponse = gsonBuilder.create().fromJson(response, LookupAssetSymbolsResponse);
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.errors.RepeatedRequestIdException;
|
||||
import cy.agorise.graphenej.interfaces.NodeErrorListener;
|
||||
import cy.agorise.graphenej.interfaces.SubscriptionHub;
|
||||
import cy.agorise.graphenej.interfaces.SubscriptionListener;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.SubscriptionResponse;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
/**
|
||||
* A WebSocket adapter prepared to be used as a basic dispatch hub for subscription messages.
|
||||
*/
|
||||
public class SubscriptionMessagesHub extends BaseGrapheneHandler implements SubscriptionHub {
|
||||
|
||||
private WebSocket mWebsocket;
|
||||
|
||||
// Sequence of message ids
|
||||
public final static int LOGIN_ID = 1;
|
||||
public final static int GET_DATABASE_ID = 2;
|
||||
public final static int SUBSCRIPTION_REQUEST = 3;
|
||||
|
||||
// ID of subscription notifications
|
||||
public final static int SUBSCRIPTION_NOTIFICATION = 4;
|
||||
|
||||
/**
|
||||
* Id attributed to the indivitual 'get_objects' API call required for a fine-grained
|
||||
* subscription request.
|
||||
*/
|
||||
public final static int MANUAL_SUBSCRIPTION_ID = 5;
|
||||
|
||||
private SubscriptionResponse.SubscriptionResponseDeserializer mSubscriptionDeserializer;
|
||||
private Gson gson;
|
||||
private String user;
|
||||
private String password;
|
||||
private boolean clearFilter;
|
||||
private int currentId;
|
||||
private int databaseApiId = -1;
|
||||
private int subscriptionCounter = 0;
|
||||
private HashMap<Long, BaseGrapheneHandler> mHandlerMap = new HashMap<>();
|
||||
|
||||
// State variables
|
||||
private boolean isUnsubscribing;
|
||||
private boolean isSubscribed;
|
||||
|
||||
/**
|
||||
* Constructor used to create a subscription message hub that will call the set_subscribe_callback
|
||||
* API with the clear_filter parameter set to false, meaning that it will only receive automatic updates
|
||||
* from objects we register.
|
||||
*
|
||||
* A list of ObjectTypes must be provided, otherwise we won't get any update.
|
||||
*
|
||||
* @param user User name, in case the node to which we're going to connect to requires
|
||||
* authentication
|
||||
* @param password Password, same as above
|
||||
* @param clearFilter Whether to automatically subscribe of not to the notification feed.
|
||||
* @param errorListener Callback that will be fired in case there is an error.
|
||||
*/
|
||||
public SubscriptionMessagesHub(String user, String password, boolean clearFilter, NodeErrorListener errorListener){
|
||||
super(errorListener);
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.clearFilter = clearFilter;
|
||||
this.mSubscriptionDeserializer = new SubscriptionResponse.SubscriptionResponseDeserializer();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(SubscriptionResponse.class, mSubscriptionDeserializer);
|
||||
builder.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer());
|
||||
builder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer());
|
||||
builder.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer());
|
||||
builder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
|
||||
builder.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer());
|
||||
this.gson = builder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used to create a subscription message hub that will call the
|
||||
* set_subscribe_callback API with the clear_filter parameter set to false, meaning that it will
|
||||
* only receive updates from objects we register.
|
||||
*
|
||||
* @param user User name, in case the node to which we're going to connect to requires
|
||||
* authentication
|
||||
* @param password Password, same as above
|
||||
* @param errorListener Callback that will be fired in case there is an error.
|
||||
*/
|
||||
public SubscriptionMessagesHub(String user, String password, NodeErrorListener errorListener){
|
||||
this(user, password, false, errorListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSubscriptionListener(SubscriptionListener listener){
|
||||
this.mSubscriptionDeserializer.addSubscriptionListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscriptionListener(SubscriptionListener listener){
|
||||
this.mSubscriptionDeserializer.removeSubscriptionListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubscriptionListener> getSubscriptionListeners() {
|
||||
return this.mSubscriptionDeserializer.getSubscriptionListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
this.mWebsocket = websocket;
|
||||
ArrayList<Serializable> loginParams = new ArrayList<>();
|
||||
currentId = LOGIN_ID;
|
||||
loginParams.add(user);
|
||||
loginParams.add(password);
|
||||
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String message = frame.getPayloadText();
|
||||
System.out.println("<< "+message);
|
||||
if(currentId == LOGIN_ID){
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
ApiCall getDatabaseId = new ApiCall(1, RPC.CALL_DATABASE, emptyParams, RPC.VERSION, currentId);
|
||||
websocket.sendText(getDatabaseId.toJsonString());
|
||||
currentId++;
|
||||
}else if(currentId == GET_DATABASE_ID){
|
||||
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
|
||||
WitnessResponse<Integer> witnessResponse = gson.fromJson(message, ApiIdResponse);
|
||||
databaseApiId = witnessResponse.result;
|
||||
|
||||
subscribe();
|
||||
} else if(currentId == SUBSCRIPTION_REQUEST){
|
||||
List<SubscriptionListener> subscriptionListeners = mSubscriptionDeserializer.getSubscriptionListeners();
|
||||
|
||||
if(!isUnsubscribing){
|
||||
isSubscribed = true;
|
||||
}
|
||||
|
||||
// If we haven't subscribed to all requested subscription channels yet,
|
||||
// just send one more subscription
|
||||
if(subscriptionListeners != null &&
|
||||
subscriptionListeners.size() > 0 &&
|
||||
subscriptionCounter < subscriptionListeners.size()){
|
||||
|
||||
ArrayList<Serializable> objects = new ArrayList<>();
|
||||
ArrayList<Serializable> payload = new ArrayList<>();
|
||||
for(SubscriptionListener listener : subscriptionListeners){
|
||||
objects.add(listener.getInterestObjectType().getGenericObjectId());
|
||||
}
|
||||
|
||||
payload.add(objects);
|
||||
ApiCall subscribe = new ApiCall(databaseApiId, RPC.GET_OBJECTS, payload, RPC.VERSION, MANUAL_SUBSCRIPTION_ID);
|
||||
websocket.sendText(subscribe.toJsonString());
|
||||
subscriptionCounter++;
|
||||
}else{
|
||||
WitnessResponse witnessResponse = gson.fromJson(message, WitnessResponse.class);
|
||||
if(witnessResponse.result != null &&
|
||||
mHandlerMap.get(witnessResponse.id) != null){
|
||||
// This is the response to a request that was submitted to the message hub
|
||||
// and whose handler was stored in the "request id" -> "handler" map
|
||||
BaseGrapheneHandler handler = mHandlerMap.get(witnessResponse.id);
|
||||
handler.onTextFrame(websocket, frame);
|
||||
mHandlerMap.remove(witnessResponse.id);
|
||||
}else{
|
||||
// If we've already subscribed to all requested subscription channels, we
|
||||
// just proceed to deserialize content.
|
||||
// The deserialization is handled by all those TypeAdapters registered in the class
|
||||
// constructor while building the gson instance.
|
||||
SubscriptionResponse response = gson.fromJson(message, SubscriptionResponse.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
System.out.println(">> "+frame.getPayloadText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method that sends a subscription request to the full node
|
||||
*/
|
||||
private void subscribe(){
|
||||
isUnsubscribing = false;
|
||||
|
||||
ArrayList<Serializable> subscriptionParams = new ArrayList<>();
|
||||
subscriptionParams.add(String.format("%d", SUBSCRIPTION_NOTIFICATION));
|
||||
subscriptionParams.add(clearFilter);
|
||||
ApiCall getDatabaseId = new ApiCall(databaseApiId, RPC.CALL_SET_SUBSCRIBE_CALLBACK, subscriptionParams, RPC.VERSION, SUBSCRIPTION_REQUEST);
|
||||
mWebsocket.sendText(getDatabaseId.toJsonString());
|
||||
currentId = SUBSCRIPTION_REQUEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method used to re-establish a subscription after it was cancelled by a previous
|
||||
* call to the {@see #cancelSubscriptions()} method call.
|
||||
*
|
||||
* Please note that you should repeat the registration step for every interested listener, since
|
||||
* those were probably lost after the previous {@see #cancelSubscriptions()} method call.
|
||||
*/
|
||||
public void resubscribe(){
|
||||
if(mWebsocket.isOpen()){
|
||||
subscribe();
|
||||
}else{
|
||||
throw new IllegalStateException("Websocket is not open, can't resubscribe");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that sends a subscription cancellation request to the full node, and also
|
||||
* de-registers all subscription and request listeners.
|
||||
*/
|
||||
public void cancelSubscriptions(){
|
||||
isSubscribed = false;
|
||||
isUnsubscribing = true;
|
||||
|
||||
ApiCall unsubscribe = new ApiCall(databaseApiId, RPC.CALL_CANCEL_ALL_SUBSCRIPTIONS, new ArrayList<Serializable>(), RPC.VERSION, SUBSCRIPTION_REQUEST);
|
||||
mWebsocket.sendText(unsubscribe.toJsonString());
|
||||
|
||||
// Clearing all subscription listeners
|
||||
mSubscriptionDeserializer.clearAllSubscriptionListeners();
|
||||
|
||||
// Clearing all request handler listners
|
||||
mHandlerMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to check the current state of the connection.
|
||||
*
|
||||
* @return True if the websocket is open and there is an active subscription, false otherwise.
|
||||
*/
|
||||
public boolean isSubscribed(){
|
||||
return this.mWebsocket.isOpen() && isSubscribed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to reset all internal variables.
|
||||
*/
|
||||
public void reset(){
|
||||
currentId = 0;
|
||||
databaseApiId = -1;
|
||||
subscriptionCounter = 0;
|
||||
}
|
||||
|
||||
public void addRequestHandler(BaseGrapheneHandler handler) throws RepeatedRequestIdException {
|
||||
if(mHandlerMap.get(handler.getRequestId()) != null){
|
||||
throw new RepeatedRequestIdException("Already registered handler with id: "+handler.getRequestId());
|
||||
}
|
||||
|
||||
mHandlerMap.put(handler.getRequestId(), handler);
|
||||
|
||||
try {
|
||||
// Artificially calling the 'onConnected' method of the handler.
|
||||
// The underlying websocket was already connected, but from the WebSocketAdapter
|
||||
// point of view it doesn't make a difference.
|
||||
handler.onConnected(mWebsocket, null);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Exception. Msg: "+e.getMessage());
|
||||
System.out.println("Exception type: "+e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BlockData;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
* Class that will handle the transaction publication procedure.
|
||||
*/
|
||||
public class TransactionBroadcastSequence extends BaseGrapheneHandler {
|
||||
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 GET_REQUIRED_FEES = 4;
|
||||
private final static int BROADCAST_TRANSACTION = 5;
|
||||
|
||||
private Asset feeAsset;
|
||||
private Transaction transaction;
|
||||
private WitnessResponseListener mListener;
|
||||
|
||||
private int currentId = 1;
|
||||
private int broadcastApiId = -1;
|
||||
|
||||
private boolean mOneTime;
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*
|
||||
* @param transaction transaction to be broadcasted.
|
||||
* @param oneTime boolean value indicating if WebSocket must be closed (true) or not
|
||||
* (false) after the response
|
||||
* @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 operation.
|
||||
*/
|
||||
public TransactionBroadcastSequence(Transaction transaction, Asset feeAsset, boolean oneTime, WitnessResponseListener listener){
|
||||
super(listener);
|
||||
this.transaction = transaction;
|
||||
this.feeAsset = feeAsset;
|
||||
this.mOneTime = oneTime;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using this constructor the WebSocket connection closes after the response.
|
||||
*
|
||||
* @param transaction: 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 operation.
|
||||
*/
|
||||
public TransactionBroadcastSequence(Transaction transaction, Asset feeAsset, WitnessResponseListener listener){
|
||||
this(transaction, feeAsset, true, 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, RPC.VERSION, currentId);
|
||||
websocket.sendText(loginCall.toJsonString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame())
|
||||
System.out.println("<<< "+frame.getPayloadText());
|
||||
String response = frame.getPayloadText();
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer());
|
||||
Gson gson = builder.create();
|
||||
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
|
||||
if(baseResponse.error != null){
|
||||
mListener.onError(baseResponse.error);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}else{
|
||||
currentId++;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
if(baseResponse.id == LOGIN_ID){
|
||||
ApiCall networkApiIdCall = new ApiCall(1, RPC.CALL_NETWORK_BROADCAST, emptyParams, RPC.VERSION, 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;
|
||||
|
||||
// Building API call to request dynamic network properties
|
||||
ApiCall getDynamicParametersCall = new ApiCall(0,
|
||||
RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES,
|
||||
emptyParams,
|
||||
RPC.VERSION,
|
||||
currentId);
|
||||
|
||||
// Requesting network properties
|
||||
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;
|
||||
|
||||
// Adjusting dynamic block data to every transaction
|
||||
long expirationTime = (dynamicProperties.time.getTime() / 1000) + Transaction.DEFAULT_EXPIRATION_TIME;
|
||||
String headBlockId = dynamicProperties.head_block_id;
|
||||
long headBlockNumber = dynamicProperties.head_block_number;
|
||||
transaction.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime));
|
||||
|
||||
// Building a new API call to request fees information
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add((Serializable) transaction.getOperations());
|
||||
accountParams.add(this.feeAsset.getObjectId());
|
||||
ApiCall getRequiredFees = new ApiCall(0, RPC.CALL_GET_REQUIRED_FEES, accountParams, RPC.VERSION, currentId);
|
||||
|
||||
// Requesting fee amount
|
||||
websocket.sendText(getRequiredFees.toJsonString());
|
||||
}else if(baseResponse.id == GET_REQUIRED_FEES){
|
||||
Type GetRequiredFeesResponse = new TypeToken<WitnessResponse<List<AssetAmount>>>(){}.getType();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
WitnessResponse<List<AssetAmount>> requiredFeesResponse = gsonBuilder.create().fromJson(response, GetRequiredFeesResponse);
|
||||
|
||||
// Setting fees
|
||||
transaction.setFees(requiredFeesResponse.result);
|
||||
ArrayList<Serializable> transactions = new ArrayList<>();
|
||||
transactions.add(transaction);
|
||||
|
||||
ApiCall call = new ApiCall(broadcastApiId,
|
||||
RPC.CALL_BROADCAST_TRANSACTION,
|
||||
transactions,
|
||||
RPC.VERSION,
|
||||
currentId);
|
||||
|
||||
// Finally broadcasting transaction
|
||||
websocket.sendText(call.toJsonString());
|
||||
}else if(baseResponse.id >= BROADCAST_TRANSACTION){
|
||||
Type WitnessResponseType = new TypeToken<WitnessResponse<String>>(){}.getType();
|
||||
WitnessResponse<String> witnessResponse = gson.fromJson(response, WitnessResponseType);
|
||||
mListener.onSuccess(witnessResponse);
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
if(frame.isTextFrame()){
|
||||
System.out.println(">>> "+frame.getPayloadText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
|
||||
System.out.println("onError. cause: "+cause.getMessage());
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
|
||||
System.out.println("handleCallbackError. cause: "+cause.getMessage()+", error: "+cause.getClass());
|
||||
for (StackTraceElement element : cause.getStackTrace()){
|
||||
System.out.println(element.getFileName()+"#"+element.getClassName()+":"+element.getLineNumber());
|
||||
}
|
||||
mListener.onError(new BaseResponse.Error(cause.getMessage()));
|
||||
if(mOneTime){
|
||||
websocket.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package cy.agorise.graphenej.api.android;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.api.BaseGrapheneHandler;
|
||||
import cy.agorise.graphenej.api.SubscriptionMessagesHub;
|
||||
import cy.agorise.graphenej.errors.RepeatedRequestIdException;
|
||||
import cy.agorise.graphenej.interfaces.NodeErrorListener;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate all connections that should be done to a node (with node hop support).
|
||||
*
|
||||
* This class is intended to be used as a central broker for all full node API requests. It should
|
||||
* be used as a singleton under an application.
|
||||
*/
|
||||
public class NodeConnection {
|
||||
/**
|
||||
* List of URLs of the nodes
|
||||
*/
|
||||
private List<String> mUrlList;
|
||||
/**
|
||||
* Index of the current node from the list
|
||||
*/
|
||||
private int mUrlIndex;
|
||||
private WebsocketWorkerThread mThread;
|
||||
private SubscriptionMessagesHub mMessagesHub;
|
||||
private long requestCounter = SubscriptionMessagesHub.MANUAL_SUBSCRIPTION_ID + 1;
|
||||
private WitnessResponseListener mErrorListener;
|
||||
|
||||
private static NodeConnection instance;
|
||||
|
||||
private String mUser;
|
||||
private String mPassword;
|
||||
private boolean mSubscribe;
|
||||
|
||||
/*
|
||||
* Get the instance of the NodeConnection which is intended to be used as a Singleton.
|
||||
*/
|
||||
public static NodeConnection getInstance(){
|
||||
if(instance == null){
|
||||
instance = new NodeConnection();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public NodeConnection(){
|
||||
this.mUrlList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a WebSocket URL node that will be added to the list used at node hop scheme.
|
||||
*
|
||||
* @param url: URL of the node
|
||||
*/
|
||||
public void addNodeUrl(String url){
|
||||
System.out.println("addNodeUrl: "+url);
|
||||
this.mUrlList.add(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a list of WebSocket URL nodes that will be added to the current list and
|
||||
* be used at node hop scheme.
|
||||
*
|
||||
* @param urlList: List of URLs of the nodes
|
||||
*/
|
||||
public void addNodeUrls(List<String> urlList){
|
||||
List<String> newList = new ArrayList<String>(mUrlList);
|
||||
newList.addAll(urlList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of WebSocket URL nodes.
|
||||
*
|
||||
* @return List of URLs of the nodes
|
||||
*/
|
||||
public List<String> getNodeUrls(){
|
||||
return this.mUrlList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear list of WebSocket URL nodes.
|
||||
*/
|
||||
public void clearNodeList(){
|
||||
this.mUrlList.clear();
|
||||
}
|
||||
|
||||
private NodeErrorListener mInternalErrorListener = new NodeErrorListener() {
|
||||
@Override
|
||||
public void onError(BaseResponse.Error error) {
|
||||
System.out.println("NodeConnect Error. Msg: "+error);
|
||||
|
||||
connect(mUser, mPassword, mSubscribe, mErrorListener);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
/**
|
||||
* Method that will try to connect to one of the nodes. If the connection fails
|
||||
* a subsequent call to this method will try to connect with the next node in the
|
||||
* list if there is one.
|
||||
*
|
||||
* @param user user credential used for restricted requested that needed to be
|
||||
* logged
|
||||
* @param password password credential used for restricted requested that needed to be
|
||||
* logged
|
||||
* @param subscribe if the node should be subscribed to the node
|
||||
* @param errorListener a class implementing the WitnessResponseListener interface. This
|
||||
* should be implemented by the party interested in being notified
|
||||
* about the failure of the desired broadcast operation.
|
||||
*/
|
||||
public void connect(String user, String password, boolean subscribe, WitnessResponseListener errorListener) {
|
||||
if(this.mUrlList.size() > 0){
|
||||
mUser = user;
|
||||
mPassword = password;
|
||||
mSubscribe = subscribe;
|
||||
System.out.println("Connecting to: "+ this.mUrlList.get(mUrlIndex));
|
||||
mErrorListener = errorListener;
|
||||
mThread = new WebsocketWorkerThread(this.mUrlList.get(mUrlIndex), mInternalErrorListener);
|
||||
mUrlIndex = mUrlIndex + 1 % this.mUrlList.size();
|
||||
|
||||
mMessagesHub = new SubscriptionMessagesHub(user, password, subscribe, mInternalErrorListener);
|
||||
mThread.addListener(mMessagesHub);
|
||||
mThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the API Handler to the node.
|
||||
*
|
||||
* @param handler request handler to be added to the connection
|
||||
* @throws RepeatedRequestIdException
|
||||
*/
|
||||
public void addRequestHandler(BaseGrapheneHandler handler) throws RepeatedRequestIdException {
|
||||
handler.setRequestId(requestCounter);
|
||||
requestCounter++;
|
||||
mMessagesHub.addRequestHandler(handler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package cy.agorise.graphenej.api.android;
|
||||
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
import com.neovisionaries.ws.client.WebSocketException;
|
||||
import com.neovisionaries.ws.client.WebSocketFactory;
|
||||
import com.neovisionaries.ws.client.WebSocketListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.NodeErrorListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.test.NaiveSSLContext;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the thread where the WebSocket does the requests.
|
||||
*
|
||||
*/
|
||||
public class WebsocketWorkerThread extends Thread {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
// When debugging we'll use a NaiveSSLContext
|
||||
public static final boolean DEBUG = true;
|
||||
|
||||
private final int TIMEOUT = 5000;
|
||||
private WebSocket mWebSocket;
|
||||
private NodeErrorListener mErrorListener;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param url URL of the WebSocket
|
||||
*/
|
||||
public WebsocketWorkerThread(String url){
|
||||
try {
|
||||
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(TIMEOUT);
|
||||
|
||||
if(DEBUG){
|
||||
SSLContext context = NaiveSSLContext.getInstance("TLS");
|
||||
|
||||
// Set the custom SSL context.
|
||||
factory.setSSLContext(context);
|
||||
}
|
||||
|
||||
mWebSocket = factory.createSocket(url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException. Msg: "+e.getMessage());
|
||||
} catch(NullPointerException e){
|
||||
System.out.println("NullPointerException at WebsocketWorkerThreas. Msg: "+e.getMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with connection error listener.
|
||||
*
|
||||
* @param url URL of the WebSocket
|
||||
* @param errorListener a class implementing the NodeErrorListener interface. This
|
||||
* should be implemented by the party interested in being notified
|
||||
* about the failure of the connection.
|
||||
*/
|
||||
public WebsocketWorkerThread(String url, NodeErrorListener errorListener){
|
||||
try {
|
||||
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(TIMEOUT);
|
||||
|
||||
if(DEBUG){
|
||||
SSLContext context = NaiveSSLContext.getInstance("TLS");
|
||||
|
||||
// Set the custom SSL context.
|
||||
factory.setSSLContext(context);
|
||||
}
|
||||
|
||||
mWebSocket = factory.createSocket(url);
|
||||
mErrorListener = errorListener;
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException. Msg: "+e.getMessage());
|
||||
} catch(NullPointerException e){
|
||||
System.out.println("NullPointerException at WebsocketWorkerThreas. Msg: "+e.getMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method call when the thread is started.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mWebSocket.connect();
|
||||
} catch (WebSocketException e) {
|
||||
System.out.println("WebSocketException. Msg: "+e.getMessage());
|
||||
mErrorListener.onError(new BaseResponse.Error(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a WebSocketListener to the thread that will run. This should be implemented by the party
|
||||
* interested in being notified about the response value of a request.
|
||||
*
|
||||
* @param listener listener implemented to be notified when the socket get a response from the
|
||||
* node
|
||||
*/
|
||||
public void addListener(WebSocketListener listener){
|
||||
mWebSocket.addListener(listener);
|
||||
}
|
||||
}
|
1
app/src/main/java/cy/agorise/graphenej/brainkeydict.txt
Normal file
1
app/src/main/java/cy/agorise/graphenej/brainkeydict.txt
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2013, 2014 Megion Research and Development GmbH
|
||||
*
|
||||
* Licensed under the Microsoft Reference Source License (MS-RSL)
|
||||
*
|
||||
* This license governs use of the accompanying software. If you use the software, you accept this license.
|
||||
* If you do not accept the license, do not use the software.
|
||||
*
|
||||
* 1. Definitions
|
||||
* The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
* "You" means the licensee of the software.
|
||||
* "Your company" means the company you worked for when you downloaded the software.
|
||||
* "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes
|
||||
* of debugging your products, maintaining your products, or enhancing the interoperability of your products with the
|
||||
* software, and specifically excludes the right to distribute the software outside of your company.
|
||||
* "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor
|
||||
* under this license.
|
||||
*
|
||||
* 2. Grant of Rights
|
||||
* (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
|
||||
* worldwide, royalty-free copyright license to reproduce the software for reference use.
|
||||
* (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
|
||||
* worldwide, royalty-free patent license under licensed patents for reference use.
|
||||
*
|
||||
* 3. Limitations
|
||||
* (A) No Trademark License- This license does not grant you any rights to use the Licensor’s name, logo, or trademarks.
|
||||
* (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software
|
||||
* (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
|
||||
* (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties,
|
||||
* guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot
|
||||
* change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability,
|
||||
* fitness for a particular purpose and non-infringement.
|
||||
*/
|
||||
|
||||
package cy.agorise.graphenej.crypto;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
public class AndroidRandomSource implements RandomSource, EntropySource {
|
||||
|
||||
@Override
|
||||
public synchronized void nextBytes(byte[] bytes) {
|
||||
// On Android we use /dev/urandom for providing random data
|
||||
File file = new File("/dev/urandom");
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("Unable to generate random bytes on this Android device");
|
||||
}
|
||||
try {
|
||||
FileInputStream stream = new FileInputStream(file);
|
||||
DataInputStream dis = new DataInputStream(stream);
|
||||
dis.readFully(bytes);
|
||||
dis.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to generate random bytes on this Android device", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer provideEntropy() {
|
||||
byte[] buffer = new byte[ 256 / 8];
|
||||
nextBytes(buffer);
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(buffer.length);
|
||||
byteBuffer.put(buffer, 0, buffer.length);
|
||||
return byteBuffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package cy.agorise.graphenej.crypto;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A simple interface that can be used to retrieve entropy from any source.
|
||||
*
|
||||
* @author owlstead
|
||||
*/
|
||||
public interface EntropySource {
|
||||
/**
|
||||
* Retrieves the entropy.
|
||||
* The position of the ByteBuffer must be advanced to the limit by any users calling this method.
|
||||
* The values of the bytes between the position and limit should be set to zero by any users calling this method.
|
||||
*
|
||||
* @return entropy within the position and limit of the given buffer
|
||||
*/
|
||||
ByteBuffer provideEntropy();
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2013, 2014 Megion Research & Development GmbH
|
||||
*
|
||||
* Licensed 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 cy.agorise.graphenej.crypto;
|
||||
|
||||
public interface RandomSource {
|
||||
/**
|
||||
* Generates a user specified number of random bytes
|
||||
*
|
||||
* @param bytes
|
||||
* The array to fill with random bytes
|
||||
*/
|
||||
void nextBytes(byte[] bytes);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package cy.agorise.graphenej.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/20/16.
|
||||
*/
|
||||
public class SecureRandomGenerator {
|
||||
|
||||
public static SecureRandom getSecureRandom(){
|
||||
SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance();
|
||||
// randomStrengthener.addEntropySource(new AndroidRandomSource());
|
||||
return randomStrengthener.generateAndSeedRandomNumberGenerator();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package cy.agorise.graphenej.crypto;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A strengthener that can be used to generate and re-seed random number
|
||||
* generators that do not seed themselves appropriately.
|
||||
*
|
||||
* @author owlstead
|
||||
*/
|
||||
public class SecureRandomStrengthener {
|
||||
private static final String DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR = "SHA1PRNG";
|
||||
|
||||
private static final EntropySource mTimeEntropySource = new EntropySource() {
|
||||
|
||||
final ByteBuffer timeBuffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE
|
||||
* 2);
|
||||
|
||||
@Override
|
||||
public ByteBuffer provideEntropy() {
|
||||
this.timeBuffer.clear();
|
||||
this.timeBuffer.putLong(System.currentTimeMillis());
|
||||
this.timeBuffer.putLong(System.nanoTime());
|
||||
this.timeBuffer.flip();
|
||||
return this.timeBuffer;
|
||||
}
|
||||
};
|
||||
|
||||
private final String algorithm;
|
||||
private final List<EntropySource> entropySources = new LinkedList<EntropySource>();
|
||||
private final MessageDigest digest;
|
||||
private final ByteBuffer seedBuffer;
|
||||
|
||||
/**
|
||||
* Generates an instance of a {@link SecureRandomStrengthener} that
|
||||
* generates and re-seeds instances of {@code "SHA1PRNG"}.
|
||||
*
|
||||
* @return the strengthener, never null
|
||||
*/
|
||||
public static SecureRandomStrengthener getInstance() {
|
||||
return new SecureRandomStrengthener(
|
||||
DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an instance of a {@link SecureRandomStrengthener} that
|
||||
* generates instances of the given argument. Note that the availability of
|
||||
* the given algorithm arguments in not tested until generation.
|
||||
*
|
||||
* @param algorithm
|
||||
* the algorithm indicating the {@link SecureRandom} instance to
|
||||
* use
|
||||
* @return the strengthener, never null
|
||||
*/
|
||||
public static SecureRandomStrengthener getInstance(final String algorithm) {
|
||||
return new SecureRandomStrengthener(algorithm);
|
||||
}
|
||||
|
||||
private SecureRandomStrengthener(final String algorithm) {
|
||||
if (algorithm == null || algorithm.length() == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Please provide a PRNG algorithm string such as SHA1PRNG");
|
||||
}
|
||||
|
||||
this.algorithm = algorithm;
|
||||
try {
|
||||
this.digest = MessageDigest.getInstance("SHA1");
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(
|
||||
"MessageDigest to create seed not available", e);
|
||||
}
|
||||
this.seedBuffer = ByteBuffer.allocate(this.digest.getDigestLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entropy source, which will be called for each generation and
|
||||
* re-seeding of the given random number generator.
|
||||
*
|
||||
* @param source
|
||||
* the source of entropy
|
||||
*/
|
||||
public void addEntropySource(final EntropySource source) {
|
||||
if (source == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"EntropySource should not be null");
|
||||
}
|
||||
this.entropySources.add(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and seeds a random number generator of the configured
|
||||
* algorithm. Calls the {@link EntropySource#provideEntropy()} method of all
|
||||
* added sources of entropy.
|
||||
*
|
||||
* @return the random number generator
|
||||
*/
|
||||
public SecureRandom generateAndSeedRandomNumberGenerator() {
|
||||
final SecureRandom secureRandom;
|
||||
try {
|
||||
secureRandom = SecureRandom.getInstance(this.algorithm);
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("PRNG is not available", e);
|
||||
}
|
||||
|
||||
reseed(secureRandom);
|
||||
return secureRandom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-seeds the random number generator. Calls the
|
||||
* {@link EntropySource#provideEntropy()} method of all added sources of
|
||||
* entropy.
|
||||
*
|
||||
* @param secureRandom
|
||||
* the random number generator to re-seed
|
||||
*/
|
||||
public void reseed(final SecureRandom secureRandom) {
|
||||
this.seedBuffer.clear();
|
||||
secureRandom.nextBytes(this.seedBuffer.array());
|
||||
|
||||
for (final EntropySource source : this.entropySources) {
|
||||
final ByteBuffer entropy = source.provideEntropy();
|
||||
if (entropy == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final ByteBuffer wipeBuffer = entropy.duplicate();
|
||||
this.digest.update(entropy);
|
||||
wipe(wipeBuffer);
|
||||
}
|
||||
|
||||
this.digest.update(mTimeEntropySource.provideEntropy());
|
||||
this.digest.update(this.seedBuffer);
|
||||
this.seedBuffer.clear();
|
||||
// remove data from seedBuffer so it won't be retrievable
|
||||
|
||||
// reuse
|
||||
|
||||
try {
|
||||
this.digest.digest(this.seedBuffer.array(), 0,
|
||||
this.seedBuffer.capacity());
|
||||
} catch (final DigestException e) {
|
||||
throw new IllegalStateException(
|
||||
"DigestException should not be thrown", e);
|
||||
}
|
||||
secureRandom.setSeed(this.seedBuffer.array());
|
||||
|
||||
wipe(this.seedBuffer);
|
||||
}
|
||||
|
||||
private void wipe(final ByteBuffer buf) {
|
||||
while (buf.hasRemaining()) {
|
||||
buf.put((byte) 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/20/16.
|
||||
*/
|
||||
public class ChecksumException extends Exception {
|
||||
public ChecksumException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/18/17.
|
||||
*/
|
||||
public class IncompatibleOperation extends RuntimeException {
|
||||
|
||||
public IncompatibleOperation(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/25/16.
|
||||
*/
|
||||
public class IncompleteAssetError extends RuntimeException{
|
||||
|
||||
public IncompleteAssetError(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IncompleteAssetError(){
|
||||
super("The asset used in this method is probably incomplete, Assets instances can be created with just its id information but this context requires more information");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/1/16.
|
||||
*/
|
||||
public class MalformedAddressException extends Exception {
|
||||
public MalformedAddressException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 3/1/17.
|
||||
*/
|
||||
public class MalformedOperationException extends RuntimeException {
|
||||
|
||||
public MalformedOperationException(String msg){
|
||||
super(msg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/14/16.
|
||||
*/
|
||||
public class MalformedTransactionException extends Exception {
|
||||
|
||||
public MalformedTransactionException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package cy.agorise.graphenej.errors;
|
||||
|
||||
/**
|
||||
* Created by nelson on 6/27/17.
|
||||
*/
|
||||
|
||||
public class RepeatedRequestIdException extends Exception {
|
||||
|
||||
public RepeatedRequestIdException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package cy.agorise.graphenej.interfaces;
|
||||
|
||||
/**
|
||||
* Interface implemented by all entities for which makes sense to have a byte-array representation.
|
||||
*/
|
||||
public interface ByteSerializable {
|
||||
|
||||
byte[] toBytes();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package cy.agorise.graphenej.interfaces;
|
||||
|
||||
/**
|
||||
* Interface used to group both ByteSerializable and JsonSerializable interfaces.
|
||||
*/
|
||||
public interface GrapheneSerializable extends ByteSerializable, JsonSerializable {
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package cy.agorise.graphenej.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 and object representation.
|
||||
*/
|
||||
public interface JsonSerializable extends Serializable {
|
||||
|
||||
String toJsonString();
|
||||
|
||||
JsonElement toJsonObject();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package cy.agorise.graphenej.interfaces;
|
||||
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by any listener to network errors.
|
||||
*/
|
||||
public interface NodeErrorListener {
|
||||
void onError(BaseResponse.Error error);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cy.agorise.graphenej.interfaces;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by any class that hosts a SubscriptionResponseDeserializer and wants to
|
||||
* expose an interface for its management of its listeners.
|
||||
*
|
||||
* Created by nelson on 1/30/17.
|
||||
*/
|
||||
public interface SubscriptionHub {
|
||||
|
||||
/**
|
||||
* Adds a given listener to the list of subscription listeners.
|
||||
* @param listener: The SubscriptionListener to add.
|
||||
*/
|
||||
void addSubscriptionListener(SubscriptionListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given listener from the list.
|
||||
* @param listener: The SubscriptionListener to remove.
|
||||
*/
|
||||
void removeSubscriptionListener(SubscriptionListener listener);
|
||||
|
||||
/**
|
||||
* Retrieves a list of all subscription listeners.
|
||||
* @return
|
||||
*/
|
||||
List<SubscriptionListener> getSubscriptionListeners();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cy.agorise.graphenej.interfaces;
|
||||
|
||||
import cy.agorise.graphenej.ObjectType;
|
||||
import cy.agorise.graphenej.models.SubscriptionResponse;
|
||||
|
||||
/**
|
||||
* Generic interface that must be implemented by any class that wants to be informed about a specific
|
||||
* event notification.
|
||||
*
|
||||
* Created by nelson on 1/26/17.
|
||||
*/
|
||||
public interface SubscriptionListener {
|
||||
|
||||
/**
|
||||
* Every subscription listener must implement a method that returns the type of object it is
|
||||
* interested in.
|
||||
* @return: Instance of the ObjectType enum class.
|
||||
*/
|
||||
ObjectType getInterestObjectType();
|
||||
|
||||
|
||||
/**
|
||||
* Method called whenever there is an update that might be of interest for this listener.
|
||||
* Note however that the objects returned inside the SubscriptionResponse are not guaranteed to be
|
||||
* only of the object type requested by this class in the getInterestObjectType.
|
||||
*
|
||||
* @param response: SubscriptionResponse instance, which may or may not contain an object of interest.
|
||||
*/
|
||||
void onSubscriptionUpdate(SubscriptionResponse response);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package cy.agorise.graphenej.interfaces;
|
||||
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.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,23 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/12/17.
|
||||
*/
|
||||
|
||||
public class AccountBalanceUpdate extends GrapheneObject implements Serializable {
|
||||
public static final String KEY_OWNER = "owner";
|
||||
public static final String KEY_ASSET_TYPE = "asset_type";
|
||||
public static final String KEY_BALANCE = "balance";
|
||||
|
||||
public String owner;
|
||||
public String asset_type;
|
||||
public long balance;
|
||||
|
||||
public AccountBalanceUpdate(String id) {
|
||||
super(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import cy.agorise.graphenej.AccountOptions;
|
||||
import cy.agorise.graphenej.Authority;
|
||||
|
||||
/**
|
||||
* 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 Authority owner;
|
||||
public Authority active;
|
||||
public AccountOptions 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;
|
||||
}
|
110
app/src/main/java/cy/agorise/graphenej/models/ApiCall.java
Normal file
110
app/src/main/java/cy/agorise/graphenej/models/ApiCall.java
Normal file
|
@ -0,0 +1,110 @@
|
|||
package cy.agorise.graphenej.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 cy.agorise.graphenej.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 (Number.class.isInstance(this.params.get(i))){
|
||||
// Other times they are numbers
|
||||
methodParams.add( (Number) this.params.get(i));
|
||||
}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<Serializable> listArgument = (ArrayList<Serializable>) this.params.get(i);
|
||||
for (int l = 0; l < listArgument.size(); l++) {
|
||||
Serializable element = listArgument.get(l);
|
||||
if (element instanceof JsonSerializable)
|
||||
array.add(((JsonSerializable) element).toJsonObject());
|
||||
else if (element instanceof String) {
|
||||
array.add((String) element);
|
||||
}
|
||||
}
|
||||
methodParams.add(array);
|
||||
}else if(this.params.get(i) instanceof Boolean){
|
||||
methodParams.add((boolean) this.params.get(i));
|
||||
}else{
|
||||
System.out.println("Skipping parameter of type: "+this.params.get(i).getClass());
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
13
app/src/main/java/cy/agorise/graphenej/models/AssetFeed.java
Normal file
13
app/src/main/java/cy/agorise/graphenej/models/AssetFeed.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import cy.agorise.graphenej.Price;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/9/17.
|
||||
*/
|
||||
public class AssetFeed {
|
||||
public Price settlement_price;
|
||||
public long maintenance_collateral_ratio;
|
||||
public long maximum_short_squeeze_ratio;
|
||||
public Price core_exchange_rate;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.*;
|
||||
import cy.agorise.graphenej.Asset;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/25/17.
|
||||
*/
|
||||
public class AssetHolderCount {
|
||||
public static final String KEY_ASSET_ID = "asset_id";
|
||||
public static final String KEY_COUNT = "count";
|
||||
|
||||
public Asset asset;
|
||||
public long count;
|
||||
|
||||
public static class HoldersCountDeserializer implements JsonDeserializer<AssetHolderCount> {
|
||||
|
||||
@Override
|
||||
public AssetHolderCount deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
AssetHolderCount holdersCount = new AssetHolderCount();
|
||||
holdersCount.asset = new Asset(jsonObject.get(KEY_ASSET_ID).getAsString());
|
||||
holdersCount.count = jsonObject.get(KEY_COUNT).getAsLong();
|
||||
return holdersCount;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/12/16.
|
||||
*/
|
||||
public class BaseResponse {
|
||||
public long 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,25 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.Price;
|
||||
|
||||
/**
|
||||
* This is the representation of the response from the 'get_objects' call with
|
||||
* a 2.4.x id, which will retrieve a 'impl_asset_bitasset_data_type'.
|
||||
*
|
||||
* Created by nelson on 1/8/17.
|
||||
*/
|
||||
public class BitAssetData extends GrapheneObject {
|
||||
public Object[] feeds;
|
||||
public AssetFeed current_feed;
|
||||
public String current_feed_publication_time;
|
||||
public Object options;
|
||||
public long force_settled_volume;
|
||||
public boolean is_prediction_market;
|
||||
public Price settlement_price;
|
||||
public long settlement_fund;
|
||||
|
||||
public BitAssetData(String id) {
|
||||
super(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/13/16.
|
||||
*/
|
||||
public class BlockHeader {
|
||||
public String previous;
|
||||
public String timestamp;
|
||||
public String witness;
|
||||
public String transaction_merkle_root;
|
||||
public Object[] extension;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/28/17.
|
||||
*/
|
||||
public class BroadcastedTransaction extends GrapheneObject implements Serializable {
|
||||
public static final String KEY_TRX = "trx";
|
||||
public static final String KEY_TRX_ID = "trx_id";
|
||||
|
||||
private Transaction trx;
|
||||
private String trx_id;
|
||||
|
||||
public BroadcastedTransaction(String id){
|
||||
super(id);
|
||||
}
|
||||
|
||||
public void setTransaction(Transaction t){
|
||||
this.trx = t;
|
||||
}
|
||||
|
||||
public Transaction getTransaction() {
|
||||
return trx;
|
||||
}
|
||||
|
||||
public void setTransactionId(String id){
|
||||
this.trx_id = id;
|
||||
}
|
||||
|
||||
public String getTransactionId() {
|
||||
return trx_id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.*;
|
||||
import cy.agorise.graphenej.Asset;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/22/16.
|
||||
*/
|
||||
public class BucketObject {
|
||||
public static final String KEY_HIGH_BASE = "high_base";
|
||||
public static final String KEY_HIGH_QUOTE = "high_quote";
|
||||
public static final String KEY_LOW_BASE = "low_base";
|
||||
public static final String KEY_LOW_QUOTE = "low_quote";
|
||||
public static final String KEY_OPEN_BASE = "open_base";
|
||||
public static final String KEY_OPEN_QUOTE = "open_quote";
|
||||
public static final String KEY_CLOSE_BASE = "close_base";
|
||||
public static final String KEY_CLOSE_QUOTE = "close_quote";
|
||||
public static final String KEY_BASE_VOLUME = "base_volume";
|
||||
public static final String KEY_QUOTE_VOLUME = "quote_volume";
|
||||
public static final String KEY_BASE = "base";
|
||||
public static final String KEY_QUOTE = "quote";
|
||||
public static final String KEY_SECONDS = "seconds";
|
||||
public static final String KEY_OPEN = "open";
|
||||
public static final String KEY_KEY = "key";
|
||||
|
||||
public String id;
|
||||
public Key key;
|
||||
public BigDecimal high_base;
|
||||
public BigDecimal high_quote;
|
||||
public BigDecimal low_base;
|
||||
public BigDecimal low_quote;
|
||||
public BigDecimal open_base;
|
||||
public BigDecimal open_quote;
|
||||
public BigDecimal close_base;
|
||||
public BigDecimal close_quote;
|
||||
public BigDecimal base_volume;
|
||||
public BigDecimal quote_volume;
|
||||
|
||||
public static class Key {
|
||||
public Asset base;
|
||||
public Asset quote;
|
||||
public long seconds;
|
||||
public Date open;
|
||||
}
|
||||
|
||||
public static class BucketDeserializer implements JsonDeserializer<BucketObject> {
|
||||
|
||||
@Override
|
||||
public BucketObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonBucket = json.getAsJsonObject();
|
||||
BucketObject bucket = new BucketObject();
|
||||
bucket.high_base = jsonBucket.get(KEY_HIGH_BASE).getAsBigDecimal();
|
||||
bucket.high_quote = jsonBucket.get(KEY_HIGH_QUOTE).getAsBigDecimal();
|
||||
bucket.low_base = jsonBucket.get(KEY_LOW_BASE).getAsBigDecimal();
|
||||
bucket.low_quote = jsonBucket.get(KEY_LOW_QUOTE).getAsBigDecimal();
|
||||
bucket.open_base = jsonBucket.get(KEY_OPEN_BASE).getAsBigDecimal();
|
||||
bucket.open_quote = jsonBucket.get(KEY_OPEN_QUOTE).getAsBigDecimal();
|
||||
bucket.close_base = jsonBucket.get(KEY_CLOSE_BASE).getAsBigDecimal();
|
||||
bucket.close_quote = jsonBucket.get(KEY_CLOSE_QUOTE).getAsBigDecimal();
|
||||
bucket.base_volume = jsonBucket.get(KEY_BASE_VOLUME).getAsBigDecimal();
|
||||
bucket.quote_volume = jsonBucket.get(KEY_QUOTE_VOLUME).getAsBigDecimal();
|
||||
bucket.key = new Key();
|
||||
String baseId = jsonBucket.get(KEY_KEY).getAsJsonObject().get(KEY_BASE).getAsString();
|
||||
String quoteId = jsonBucket.get(KEY_KEY).getAsJsonObject().get(KEY_QUOTE).getAsString();
|
||||
bucket.key.base = new Asset(baseId);
|
||||
bucket.key.quote = new Asset(quoteId);
|
||||
bucket.key.seconds = jsonBucket.get(KEY_KEY).getAsJsonObject().get(KEY_SECONDS).getAsLong();
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
try {
|
||||
bucket.key.open = dateFormat.parse(jsonBucket.get(KEY_KEY).getAsJsonObject().get(KEY_OPEN).getAsString());
|
||||
} catch (ParseException e) {
|
||||
System.out.println("ParseException while deserializing BucketObject. Msg: "+e.getMessage());
|
||||
}
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
/**
|
||||
* 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 extends GrapheneObject implements Serializable {
|
||||
public static final String KEY_HEAD_BLOCK_NUMBER = "head_block_number";
|
||||
public static final String KEY_HEAD_BLOCK_ID ="head_block_id";
|
||||
public static final String KEY_TIME = "time";
|
||||
public static final String KEY_CURRENT_WITNESS = "current_witness";
|
||||
public static final String KEY_NEXT_MAINTENANCE_TIME = "next_maintenance_time";
|
||||
public static final String KEY_LAST_BUDGET_TIME = "last_budget_time";
|
||||
public static final String KEY_WITNESS_BUDGET = "witness_budget";
|
||||
public static final String KEY_ACCOUNTS_REGISTERED_THIS_INTERVAL = "accounts_registered_this_interval";
|
||||
public static final String KEY_RECENTLY_MISSED_COUNT = "recently_missed_count";
|
||||
public static final String KEY_CURRENT_ASLOT = "current_aslot";
|
||||
public static final String KEY_RECENT_SLOTS_FILLED = "recent_slots_filled";
|
||||
public static final String KEY_DYNAMIC_FLAGS = "dynamic_flags";
|
||||
public static final String KEY_LAST_IRREVERSIBLE_BLOCK_NUM = "last_irreversible_block_num";
|
||||
|
||||
public long head_block_number;
|
||||
public String head_block_id;
|
||||
public Date time;
|
||||
public String current_witness;
|
||||
public Date 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 int dynamic_flags;
|
||||
public long last_irreversible_block_num;
|
||||
|
||||
public DynamicGlobalProperties(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that will parse the JSON element containing the dynamic global properties object and
|
||||
* return an instance of the {@link DynamicGlobalProperties} class.
|
||||
*/
|
||||
public static class DynamicGlobalPropertiesDeserializer implements JsonDeserializer<DynamicGlobalProperties> {
|
||||
|
||||
@Override
|
||||
public DynamicGlobalProperties deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
|
||||
// Creating an instance of the DynamicGlobalProperties
|
||||
DynamicGlobalProperties dynamicGlobal = new DynamicGlobalProperties(jsonElement.getAsJsonObject().get(KEY_ID).getAsString());
|
||||
|
||||
// Start to fill in the parsed details
|
||||
dynamicGlobal.head_block_number = jsonObject.get(DynamicGlobalProperties.KEY_HEAD_BLOCK_NUMBER).getAsLong();
|
||||
dynamicGlobal.head_block_id = jsonObject.get(DynamicGlobalProperties.KEY_HEAD_BLOCK_ID).getAsString();
|
||||
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
try {
|
||||
dynamicGlobal.time = dateFormat.parse(jsonObject.get(DynamicGlobalProperties.KEY_TIME).getAsString());
|
||||
} catch (ParseException e) {
|
||||
System.out.println("ParseException. Msg: "+e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
dynamicGlobal.next_maintenance_time = dateFormat.parse(jsonObject.get(DynamicGlobalProperties.KEY_NEXT_MAINTENANCE_TIME).getAsString());
|
||||
} catch (ParseException e) {
|
||||
System.out.println("ParseException. Msg: "+e.getMessage());
|
||||
}
|
||||
|
||||
dynamicGlobal.current_witness = jsonObject.get(DynamicGlobalProperties.KEY_CURRENT_WITNESS).getAsString();
|
||||
dynamicGlobal.last_budget_time = jsonObject.get(DynamicGlobalProperties.KEY_LAST_BUDGET_TIME).getAsString();
|
||||
dynamicGlobal.witness_budget = jsonObject.get(DynamicGlobalProperties.KEY_WITNESS_BUDGET).getAsLong();
|
||||
dynamicGlobal.accounts_registered_this_interval = jsonObject.get(DynamicGlobalProperties.KEY_ACCOUNTS_REGISTERED_THIS_INTERVAL).getAsLong();
|
||||
dynamicGlobal.recently_missed_count = jsonObject.get(DynamicGlobalProperties.KEY_RECENTLY_MISSED_COUNT).getAsLong();
|
||||
dynamicGlobal.current_aslot = jsonObject.get(DynamicGlobalProperties.KEY_CURRENT_ASLOT).getAsLong();
|
||||
dynamicGlobal.recent_slots_filled = jsonObject.get(DynamicGlobalProperties.KEY_RECENT_SLOTS_FILLED).getAsString();
|
||||
dynamicGlobal.dynamic_flags = jsonObject.get(DynamicGlobalProperties.KEY_DYNAMIC_FLAGS).getAsInt();
|
||||
dynamicGlobal.last_irreversible_block_num = jsonObject.get(DynamicGlobalProperties.KEY_LAST_IRREVERSIBLE_BLOCK_NUM).getAsLong();
|
||||
return dynamicGlobal;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
|
||||
/**
|
||||
* This class offers support to deserialization of transfer operations received by the API
|
||||
* method get_relative_account_history.
|
||||
*
|
||||
* More operations types might be listed in the response of that method, but by using this class
|
||||
* those will be filtered out of the parsed result.
|
||||
*/
|
||||
public class HistoricalTransfer {
|
||||
private String id;
|
||||
private TransferOperation op;
|
||||
public Object[] result;
|
||||
private long block_num;
|
||||
private long trx_in_block;
|
||||
private long op_in_trx;
|
||||
private long virtual_op;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public TransferOperation getOperation() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOperation(TransferOperation op) {
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public long getBlockNum() {
|
||||
return block_num;
|
||||
}
|
||||
|
||||
public void setBlockNum(long block_num) {
|
||||
this.block_num = block_num;
|
||||
}
|
||||
|
||||
public long getTransactionsInBlock() {
|
||||
return trx_in_block;
|
||||
}
|
||||
|
||||
public void setTransactionsInBlock(long trx_in_block) {
|
||||
this.trx_in_block = trx_in_block;
|
||||
}
|
||||
|
||||
public long getOperationsInTrx() {
|
||||
return op_in_trx;
|
||||
}
|
||||
|
||||
public void setOperationsInTrx(long op_in_trx) {
|
||||
this.op_in_trx = op_in_trx;
|
||||
}
|
||||
|
||||
public long getVirtualOp() {
|
||||
return virtual_op;
|
||||
}
|
||||
|
||||
public void setVirtualOp(long virtual_op) {
|
||||
this.virtual_op = virtual_op;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.ObjectType;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.interfaces.SubscriptionListener;
|
||||
|
||||
/**
|
||||
* Class that represents a generic subscription response.
|
||||
* The template for every subscription response is the following:
|
||||
*
|
||||
* {
|
||||
* "method": "notice"
|
||||
* "params": [
|
||||
* SUBSCRIPTION_ID,
|
||||
* [[
|
||||
* { "id": "2.1.0", ... },
|
||||
* { "id": ... },
|
||||
* { "id": ... },
|
||||
* { "id": ... }
|
||||
* ]]
|
||||
* ],
|
||||
* }
|
||||
*
|
||||
* As of 1/2017, the witness API returns all sort of events, not just the ones we're interested in once we
|
||||
* make a call to the 'set_subscribe_callback', regardless of whether the 'clear_filter' parameter is set to
|
||||
* true or false.
|
||||
*
|
||||
* To minimize CPU usage, we introduce a scheme of selective parsing, implemented by the static inner class
|
||||
* SubscriptionResponseDeserializer.
|
||||
*
|
||||
* Created by nelson on 1/12/17.
|
||||
*/
|
||||
public class SubscriptionResponse {
|
||||
private static final String TAG = "SubscriptionResponse";
|
||||
public static final String KEY_ID = "id";
|
||||
public static final String KEY_METHOD = "method";
|
||||
public static final String KEY_PARAMS = "params";
|
||||
|
||||
public int id;
|
||||
public String method;
|
||||
public List<Serializable> params;
|
||||
|
||||
/**
|
||||
* Inner static class used to parse and deserialize subscription responses in a partial way,
|
||||
* depending on the amount of SubscriptionListeners we might have registered.
|
||||
*
|
||||
* The rationale behind these architecture is to avoid wasting computational resources parsing unneeded
|
||||
* objects that might come once the are subscribed to the witness notifications.
|
||||
*/
|
||||
public static class SubscriptionResponseDeserializer implements JsonDeserializer<SubscriptionResponse> {
|
||||
/**
|
||||
* Map of ObjectType to Integer used to keep track of the current amount of listener per type
|
||||
*/
|
||||
private HashMap<ObjectType, Integer> listenerTypeCount;
|
||||
|
||||
/**
|
||||
* List of listeners
|
||||
*/
|
||||
private LinkedList<SubscriptionListener> mListeners;
|
||||
|
||||
/**
|
||||
* Constructor that will just create a list of SubscriptionListeners and
|
||||
* a map of ObjectType to integer in order to keep track of how many listeners
|
||||
* to each type of object we have.
|
||||
*/
|
||||
public SubscriptionResponseDeserializer(){
|
||||
mListeners = new LinkedList<>();
|
||||
listenerTypeCount = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subscription listener to the list.
|
||||
* @param subscriptionListener: Class implementing the {@see SubscriptionListener} interface
|
||||
* to be added to the list.
|
||||
*/
|
||||
public void addSubscriptionListener(SubscriptionListener subscriptionListener){
|
||||
int currentCount = 0;
|
||||
if(listenerTypeCount.containsKey(subscriptionListener.getInterestObjectType())){
|
||||
currentCount = listenerTypeCount.get(subscriptionListener.getInterestObjectType());
|
||||
}
|
||||
this.listenerTypeCount.put(subscriptionListener.getInterestObjectType(), currentCount + 1);
|
||||
this.mListeners.add(subscriptionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the full list of SubscriptionListeners registered.
|
||||
* @return
|
||||
*/
|
||||
public List<SubscriptionListener> getSubscriptionListeners(){
|
||||
return this.mListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a subscription listener to the list.
|
||||
* @param subscriptionListener: Class implementing the {@see SubscriptionListener} interface
|
||||
* to be removed from the list.
|
||||
*/
|
||||
public void removeSubscriptionListener(SubscriptionListener subscriptionListener){
|
||||
int currentCount = listenerTypeCount.get(subscriptionListener.getInterestObjectType());
|
||||
if(currentCount != 0){
|
||||
this.listenerTypeCount.put(subscriptionListener.getInterestObjectType(), currentCount);
|
||||
}else{
|
||||
System.out.println("Trying to remove subscription listener, but none is registered!");
|
||||
}
|
||||
this.mListeners.remove(subscriptionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all registered subscription listeners
|
||||
*/
|
||||
public void clearAllSubscriptionListeners(){
|
||||
this.mListeners.clear();
|
||||
this.listenerTypeCount.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
SubscriptionResponse response = new SubscriptionResponse();
|
||||
JsonObject responseObject = json.getAsJsonObject();
|
||||
if(!responseObject.has(KEY_METHOD)){
|
||||
return response;
|
||||
}
|
||||
response.method = responseObject.get(KEY_METHOD).getAsString();
|
||||
|
||||
JsonArray paramsArray = responseObject.get(KEY_PARAMS).getAsJsonArray();
|
||||
response.params = new ArrayList<>();
|
||||
response.params.add(paramsArray.get(0).getAsInt());
|
||||
ArrayList<Serializable> secondArgument = new ArrayList<>();
|
||||
response.params.add(secondArgument);
|
||||
|
||||
// Hash map used to record the type of objects present in this subscription message
|
||||
// and only alert listeners that might be interested
|
||||
HashMap<ObjectType, Boolean> objectMap = new HashMap<>();
|
||||
|
||||
JsonArray subArray = paramsArray.get(1).getAsJsonArray().get(0).getAsJsonArray();
|
||||
for(JsonElement object : subArray){
|
||||
if(object.isJsonObject()){
|
||||
GrapheneObject grapheneObject = new GrapheneObject(object.getAsJsonObject().get(KEY_ID).getAsString());
|
||||
|
||||
int listenerTypeCount = 0;
|
||||
if(this.listenerTypeCount.containsKey(grapheneObject.getObjectType())){
|
||||
listenerTypeCount = this.listenerTypeCount.get(grapheneObject.getObjectType());
|
||||
}
|
||||
/*
|
||||
* Here's where we apply the selective deserialization logic, meaning we only completely deserialize
|
||||
* an object contained in a notification if there is at least one registered listener interested in
|
||||
* objects of that type.
|
||||
*/
|
||||
if(listenerTypeCount > 0){
|
||||
JsonObject jsonObject = object.getAsJsonObject();
|
||||
if(grapheneObject.getObjectType() == ObjectType.ACCOUNT_BALANCE_OBJECT){
|
||||
AccountBalanceUpdate balanceObject = new AccountBalanceUpdate(grapheneObject.getObjectId());
|
||||
balanceObject.owner = jsonObject.get(AccountBalanceUpdate.KEY_OWNER).getAsString();
|
||||
balanceObject.asset_type = jsonObject.get(AccountBalanceUpdate.KEY_ASSET_TYPE).getAsString();
|
||||
balanceObject.balance = jsonObject.get(AccountBalanceUpdate.KEY_BALANCE).getAsLong();
|
||||
objectMap.put(ObjectType.ACCOUNT_BALANCE_OBJECT, true);
|
||||
secondArgument.add(balanceObject);
|
||||
}else if(grapheneObject.getObjectType() == ObjectType.DYNAMIC_GLOBAL_PROPERTY_OBJECT){
|
||||
DynamicGlobalProperties dynamicGlobalProperties = context.deserialize(object, DynamicGlobalProperties.class);
|
||||
objectMap.put(ObjectType.DYNAMIC_GLOBAL_PROPERTY_OBJECT, true);
|
||||
secondArgument.add(dynamicGlobalProperties);
|
||||
}else if(grapheneObject.getObjectType() == ObjectType.TRANSACTION_OBJECT){
|
||||
BroadcastedTransaction broadcastedTransaction = new BroadcastedTransaction(grapheneObject.getObjectId());
|
||||
broadcastedTransaction.setTransaction((Transaction) context.deserialize(jsonObject.get(BroadcastedTransaction.KEY_TRX), Transaction.class));
|
||||
broadcastedTransaction.setTransactionId(jsonObject.get(BroadcastedTransaction.KEY_TRX_ID).getAsString());
|
||||
objectMap.put(ObjectType.TRANSACTION_OBJECT, true);
|
||||
secondArgument.add(broadcastedTransaction);
|
||||
}else{
|
||||
//TODO: Add support for other types of objects
|
||||
}
|
||||
}
|
||||
}else{
|
||||
secondArgument.add(object.getAsString());
|
||||
}
|
||||
}
|
||||
for(SubscriptionListener listener : mListeners){
|
||||
// Only notify the listener if there is an object of interest in
|
||||
// this notification
|
||||
if(objectMap.containsKey(listener.getInterestObjectType())){
|
||||
listener.onSubscriptionUpdate(response);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Generic witness response
|
||||
*/
|
||||
public class WitnessResponse<T> extends BaseResponse{
|
||||
public static final String KEY_ID = "id";
|
||||
public static final String KEY_RESULT = "result";
|
||||
|
||||
public T result;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package cy.agorise.graphenej.models.backup;
|
||||
|
||||
/**
|
||||
* Class used to represent an entry in the "linked_accounts" field of the JSON-formatted backup file.
|
||||
* Created by nelson on 2/15/17.
|
||||
*/
|
||||
public class LinkedAccount {
|
||||
private String name;
|
||||
private String chainId;
|
||||
|
||||
public LinkedAccount(String name, String chainId){
|
||||
this.name = name;
|
||||
this.chainId = chainId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getChainId() {
|
||||
return chainId;
|
||||
}
|
||||
|
||||
public void setChainId(String chainId) {
|
||||
this.chainId = chainId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package cy.agorise.graphenej.models.backup;
|
||||
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
|
||||
/**
|
||||
* Class used to represent an entry in the "private_keys" array field in the JSON-formatted
|
||||
* backup file.
|
||||
*
|
||||
* Created by nelson on 2/14/17.
|
||||
*/
|
||||
public class PrivateKeyBackup {
|
||||
public String encrypted_key;
|
||||
public String pubkey;
|
||||
public int brainkey_sequence;
|
||||
public int id;
|
||||
|
||||
public PrivateKeyBackup(byte[] privateKey, int brainkeySequence, int id, byte[] encryptionKey){
|
||||
this.encrypted_key = encryptPrivateKey(privateKey, encryptionKey);
|
||||
this.brainkey_sequence = brainkeySequence;
|
||||
this.id = id;
|
||||
deriveAddress(privateKey);
|
||||
}
|
||||
|
||||
public byte[] decryptPrivateKey(byte[] encryptionKey){
|
||||
return Util.decryptAES(Util.hexToBytes(encrypted_key), encryptionKey);
|
||||
}
|
||||
|
||||
public String encryptPrivateKey(byte[] data, byte[] encryptionKey){
|
||||
return Util.bytesToHex(Util.encryptAES(data, encryptionKey));
|
||||
}
|
||||
|
||||
private void deriveAddress(byte[] privateKey){
|
||||
Address address = new Address(ECKey.fromPublicOnly(ECKey.fromPrivate(privateKey).getPubKey()));
|
||||
this.pubkey = address.toString();
|
||||
}
|
||||
}
|
202
app/src/main/java/cy/agorise/graphenej/models/backup/Wallet.java
Normal file
202
app/src/main/java/cy/agorise/graphenej/models/backup/Wallet.java
Normal file
|
@ -0,0 +1,202 @@
|
|||
package cy.agorise.graphenej.models.backup;
|
||||
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.crypto.SecureRandomGenerator;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class holds data deserialized from a wallet in the backup file.
|
||||
*
|
||||
* Created by nelson on 2/14/17.
|
||||
*/
|
||||
public class Wallet {
|
||||
private String public_name;
|
||||
private String password_pubkey;
|
||||
private String encryption_key;
|
||||
private String encrypted_brainkey;
|
||||
private String brainkey_pubkey;
|
||||
private int brainkey_sequence;
|
||||
private String brainkey_backup_date;
|
||||
private String created;
|
||||
private String last_modified;
|
||||
private String chain_id;
|
||||
private String id;
|
||||
private String backup_date;
|
||||
|
||||
/**
|
||||
* No args constructor
|
||||
*/
|
||||
public Wallet(){}
|
||||
|
||||
public Wallet(String name){
|
||||
this.public_name = name;
|
||||
this.id = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wallet constructor that takes a few arguments.
|
||||
* @param name: The name of this wallet.
|
||||
* @param brainKey: The brain key to be used.
|
||||
* @param brainkeySequence: The brain key sequence.
|
||||
* @param chainId: The chain id
|
||||
* @param password: Password used to encrypt all sensitive data.
|
||||
*/
|
||||
public Wallet(String name, String brainKey, int brainkeySequence, String chainId, String password){
|
||||
this(name);
|
||||
SecureRandom secureRandom = SecureRandomGenerator.getSecureRandom();
|
||||
byte[] decryptedKey = new byte[Util.KEY_LENGTH];
|
||||
secureRandom.nextBytes(decryptedKey);
|
||||
this.encryption_key = Util.bytesToHex(Util.encryptAES(decryptedKey, password.getBytes()));
|
||||
this.encrypted_brainkey = Util.bytesToHex(Util.encryptAES(brainKey.getBytes(), decryptedKey));
|
||||
this.brainkey_sequence = brainkeySequence;
|
||||
this.chain_id = chainId;
|
||||
|
||||
try {
|
||||
byte[] passwordHash = Sha256Hash.hash(password.getBytes("UTF8"));
|
||||
this.password_pubkey = new Address(ECKey.fromPublicOnly(ECKey.fromPrivate(passwordHash).getPubKey())).toString();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
byte[] brainkeyHash = Sha256Hash.hash(brainKey.getBytes("UTF8"));
|
||||
this.brainkey_pubkey = new Address(ECKey.fromPublicOnly(ECKey.fromPrivate(brainkeyHash).getPubKey())).toString();
|
||||
} catch(UnsupportedEncodingException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
this.created = dateFormat.format(now);
|
||||
this.last_modified = created;
|
||||
this.backup_date = created;
|
||||
this.brainkey_backup_date = created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that will return the decrypted version of the "encrypted_brainkey" field.
|
||||
* @param password: Password used to encrypt the encryption key.
|
||||
* @return: The brainkey in its plaintext version.
|
||||
*/
|
||||
public String decryptBrainKey(String password){
|
||||
byte[] decryptedKey = getEncryptionKey(password);
|
||||
byte[] encryptedBrainKey = Util.hexToBytes(encrypted_brainkey);
|
||||
return new String(Util.decryptAES(encryptedBrainKey, decryptedKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the encryption key, which is also provided in an encrypted form.
|
||||
* @param password: The password used to encrypt the encryption key.
|
||||
* @return: The encryption key.
|
||||
*/
|
||||
public byte[] getEncryptionKey(String password){
|
||||
return Util.decryptAES(Util.hexToBytes(encryption_key), password.getBytes());
|
||||
}
|
||||
|
||||
public String getPrivateName() {
|
||||
return public_name;
|
||||
}
|
||||
|
||||
public void setPrivateName(String privateName) {
|
||||
this.public_name = privateName;
|
||||
}
|
||||
|
||||
public String getPasswordPubkey() {
|
||||
return password_pubkey;
|
||||
}
|
||||
|
||||
public void setPasswordPubkey(String password_pubkey) {
|
||||
this.password_pubkey = password_pubkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cyphertext version of the encryption key.
|
||||
* @return: Encryption key in its cyphertext version.
|
||||
*/
|
||||
public String getEncryptionKey() {
|
||||
return encryption_key;
|
||||
}
|
||||
|
||||
public void setEncryptionKey(String encryption_key) {
|
||||
this.encryption_key = encryption_key;
|
||||
}
|
||||
|
||||
public String getEncryptedBrainkey() {
|
||||
return encrypted_brainkey;
|
||||
}
|
||||
|
||||
public void setEncryptedBrainkey(String encrypted_brainkey) {
|
||||
this.encrypted_brainkey = encrypted_brainkey;
|
||||
}
|
||||
|
||||
public String getBrainkeyPubkey() {
|
||||
return brainkey_pubkey;
|
||||
}
|
||||
|
||||
public void setBrainkeyPubkey(String brainkey_pubkey) {
|
||||
this.brainkey_pubkey = brainkey_pubkey;
|
||||
}
|
||||
|
||||
public int getBrainkeySequence() {
|
||||
return brainkey_sequence;
|
||||
}
|
||||
|
||||
public void setBrainkeySequence(int brainkey_sequence) {
|
||||
this.brainkey_sequence = brainkey_sequence;
|
||||
}
|
||||
|
||||
public String getBrainkeyBackup_date() {
|
||||
return brainkey_backup_date;
|
||||
}
|
||||
|
||||
public void setBrainkeyBackupDate(String brainkey_backup_date) {
|
||||
this.brainkey_backup_date = brainkey_backup_date;
|
||||
}
|
||||
|
||||
public String getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(String created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public String getLastModified() {
|
||||
return last_modified;
|
||||
}
|
||||
|
||||
public void setLastModified(String last_modified) {
|
||||
this.last_modified = last_modified;
|
||||
}
|
||||
|
||||
public String getChainId() {
|
||||
return chain_id;
|
||||
}
|
||||
|
||||
public void setChainId(String chain_id) {
|
||||
this.chain_id = chain_id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBackupDate() {
|
||||
return backup_date;
|
||||
}
|
||||
|
||||
public void setBackupDate(String backup_date) {
|
||||
this.backup_date = backup_date;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package cy.agorise.graphenej.models.backup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class is used to represent the JSON-formatted version of the file backup containing one or more
|
||||
* wallets and keys.
|
||||
*
|
||||
* Created by nelson on 2/14/17.
|
||||
*/
|
||||
public class WalletBackup {
|
||||
private Wallet[] wallet;
|
||||
private PrivateKeyBackup[] private_keys;
|
||||
private LinkedAccount[] linked_accounts;
|
||||
|
||||
public WalletBackup(List<Wallet> wallets, List<PrivateKeyBackup> privateKeys, List<LinkedAccount> linkedAccounts){
|
||||
this.wallet = wallets.toArray(new Wallet[wallets.size()]);
|
||||
this.private_keys = privateKeys.toArray(new PrivateKeyBackup[privateKeys.size()]);
|
||||
this.linked_accounts = linkedAccounts.toArray(new LinkedAccount[linkedAccounts.size()]);
|
||||
}
|
||||
|
||||
public Wallet[] getWallets(){
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public PrivateKeyBackup[] getPrivateKeys(){
|
||||
return private_keys;
|
||||
}
|
||||
|
||||
public LinkedAccount[] getLinkedAccounts(){
|
||||
return linked_accounts;
|
||||
}
|
||||
|
||||
public Wallet getWallet(int index){
|
||||
return wallet[index];
|
||||
}
|
||||
|
||||
public PrivateKeyBackup getPrivateKeyBackup(int index){
|
||||
return private_keys[index];
|
||||
}
|
||||
|
||||
public int getWalletCount(){
|
||||
return wallet.length;
|
||||
}
|
||||
|
||||
public int getKeyCount(){
|
||||
return private_keys.length;
|
||||
}
|
||||
}
|
272
app/src/main/java/cy/agorise/graphenej/objects/Memo.java
Normal file
272
app/src/main/java/cy/agorise/graphenej/objects/Memo.java
Normal file
|
@ -0,0 +1,272 @@
|
|||
package cy.agorise.graphenej.objects;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.PublicKey;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.errors.ChecksumException;
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
public class Memo implements ByteSerializable, JsonSerializable {
|
||||
public final static String TAG = "Memo";
|
||||
public static final String KEY_FROM = "from";
|
||||
public static final String KEY_TO = "to";
|
||||
public static final String KEY_NONCE = "nonce";
|
||||
public static final String KEY_MESSAGE = "message";
|
||||
|
||||
private Address from;
|
||||
private Address to;
|
||||
private long nonce;
|
||||
private byte[] message;
|
||||
private String plaintextMessage;
|
||||
|
||||
public String getPlaintextMessage() {
|
||||
if(plaintextMessage == null)
|
||||
return "";
|
||||
else
|
||||
return plaintextMessage;
|
||||
}
|
||||
|
||||
public void setPlaintextMessage(String plaintextMessage) {
|
||||
this.plaintextMessage = plaintextMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty Constructor
|
||||
*/
|
||||
public Memo() {
|
||||
this.from = null;
|
||||
this.to = null;
|
||||
this.message = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used for private memos.
|
||||
* @param from: Address of sender
|
||||
* @param to: Address of recipient.
|
||||
* @param nonce: Nonce used in the encryption.
|
||||
* @param message: Message in ciphertext.
|
||||
*/
|
||||
public Memo(Address from, Address to, long nonce, byte[] message){
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.nonce = nonce;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor intended to be used with public memos
|
||||
* @param message: Message in plaintext.
|
||||
*/
|
||||
public Memo(String message){
|
||||
this.message = message.getBytes();
|
||||
}
|
||||
|
||||
public Address getSource(){
|
||||
return this.from;
|
||||
}
|
||||
|
||||
public Address getDestination(){
|
||||
return this.to;
|
||||
}
|
||||
|
||||
public long getNonce(){
|
||||
return this.nonce;
|
||||
}
|
||||
|
||||
public byte[] getByteMessage(){
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public String getStringMessage(){
|
||||
if(this.message != null)
|
||||
return new String(this.message);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to decrypt memo data.
|
||||
* @param privateKey: Private key of the sender.
|
||||
* @param publicKey: Public key of the recipient.
|
||||
* @param nonce: The nonce.
|
||||
* @param message: Plaintext message.
|
||||
* @return: The encrypted version of the message.
|
||||
*/
|
||||
public static byte[] encryptMessage(ECKey privateKey, PublicKey publicKey, long nonce, String message){
|
||||
byte[] encrypted = null;
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
|
||||
|
||||
// Getting nonce bytes
|
||||
String stringNonce = String.format("%d", nonce);
|
||||
byte[] nonceBytes = Arrays.copyOfRange(Util.hexlify(stringNonce), 0, stringNonce.length());
|
||||
|
||||
// Getting shared secret
|
||||
byte[] secret = publicKey.getKey().getPubKeyPoint().multiply(privateKey.getPrivKey()).normalize().getXCoord().getEncoded();
|
||||
|
||||
// SHA-512 of shared secret
|
||||
byte[] ss = sha512.digest(secret);
|
||||
|
||||
byte[] seed = Bytes.concat(nonceBytes, Util.hexlify(Util.bytesToHex(ss)));
|
||||
|
||||
// Calculating checksum
|
||||
byte[] sha256Msg = sha256.digest(message.getBytes());
|
||||
byte[] checksum = Arrays.copyOfRange(sha256Msg, 0, 4);
|
||||
|
||||
// Concatenating checksum + message bytes
|
||||
byte[] msgFinal = Bytes.concat(checksum, message.getBytes());
|
||||
|
||||
// Applying encryption
|
||||
encrypted = Util.encryptAES(msgFinal, seed);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
System.out.println("NoSuchAlgotithmException. Msg:"+ ex.getMessage());
|
||||
}
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to encrypt memo data.
|
||||
* @param privateKey: Private key of the sender.
|
||||
* @param destinationAddress: Address of the recipient.
|
||||
* @param nonce: The nonce.
|
||||
* @param message: Plaintext message.
|
||||
* @return: The encrypted version of the message.
|
||||
*/
|
||||
public static byte[] encryptMessage(ECKey privateKey, Address destinationAddress, long nonce, String message){
|
||||
return encryptMessage(privateKey, destinationAddress.getPublicKey(), nonce, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method used to decrypt memo data.
|
||||
* @param privateKey: The private key of the recipient.
|
||||
* @param publicKey: The public key of the sender.
|
||||
* @param nonce: The nonce.
|
||||
* @param message: The encrypted message.
|
||||
* @return: The plaintext version of the enrcrypted message.
|
||||
* @throws ChecksumException
|
||||
*/
|
||||
public static String decryptMessage(ECKey privateKey, PublicKey publicKey, long nonce, byte[] message) throws ChecksumException {
|
||||
String plaintext = "";
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
|
||||
|
||||
// Getting nonce bytes
|
||||
String stringNonce = String.format("%d", nonce);
|
||||
byte[] nonceBytes = Arrays.copyOfRange(Util.hexlify(stringNonce), 0, stringNonce.length());
|
||||
|
||||
// Getting shared secret
|
||||
byte[] secret = publicKey.getKey().getPubKeyPoint().multiply(privateKey.getPrivKey()).normalize().getXCoord().getEncoded();
|
||||
|
||||
// SHA-512 of shared secret
|
||||
byte[] ss = sha512.digest(secret);
|
||||
|
||||
byte[] seed = Bytes.concat(nonceBytes, Util.hexlify(Util.bytesToHex(ss)));
|
||||
|
||||
// Calculating checksum
|
||||
byte[] sha256Msg = sha256.digest(message);
|
||||
|
||||
|
||||
// Applying decryption
|
||||
byte[] temp = Util.decryptAES(message, seed);
|
||||
byte[] checksum = Arrays.copyOfRange(temp, 0, 4);
|
||||
byte[] decrypted = Arrays.copyOfRange(temp, 4, temp.length);
|
||||
plaintext = new String(decrypted);
|
||||
byte[] checksumConfirmation = Arrays.copyOfRange(sha256.digest(decrypted), 0, 4);
|
||||
boolean checksumVerification = Arrays.equals(checksum, checksumConfirmation);
|
||||
if(!checksumVerification){
|
||||
throw new ChecksumException("Invalid checksum found while performing decryption");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgotithmException. Msg:"+ e.getMessage());
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to decrypt memo data.
|
||||
* @param privateKey: The private key of the recipient.
|
||||
* @param sourceAddress: The public address key of the sender.
|
||||
* @param nonce: The nonce.
|
||||
* @param message: The encrypted message.
|
||||
* @return: The plaintext version of the enrcrypted message.
|
||||
* @throws ChecksumException
|
||||
*/
|
||||
public static String decryptMessage(ECKey privateKey, Address sourceAddress, long nonce, byte[] message) throws ChecksumException {
|
||||
return decryptMessage(privateKey, sourceAddress.getPublicKey(), nonce, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implement metod, serialized this Object
|
||||
* @return the byte array of this object serialized
|
||||
*/
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
if ((this.from == null) && (this.to == null) && (this.message == null)) {
|
||||
return new byte[]{(byte) 0};
|
||||
} else if(this.from == null && this.to == null & this.message != null){
|
||||
return Bytes.concat(new byte[]{1},
|
||||
new byte[]{(byte)0},
|
||||
new byte[]{(byte)0},
|
||||
new byte[]{(byte)0},
|
||||
new byte[]{(byte) this.message.length},
|
||||
this.message);
|
||||
} else {
|
||||
byte[] nonceBytes = Util.revertLong(nonce);
|
||||
|
||||
ECPoint senderPoint = ECKey.compressPoint(from.getPublicKey().getKey().getPubKeyPoint());
|
||||
PublicKey senderPublicKey = new PublicKey(ECKey.fromPublicOnly(senderPoint));
|
||||
|
||||
ECPoint recipientPoint = ECKey.compressPoint(to.getPublicKey().getKey().getPubKeyPoint());
|
||||
PublicKey recipientPublicKey = new PublicKey(ECKey.fromPublicOnly(recipientPoint));
|
||||
|
||||
return Bytes.concat(new byte[]{1},
|
||||
senderPublicKey.toBytes(),
|
||||
recipientPublicKey.toBytes(),
|
||||
nonceBytes,
|
||||
new byte[]{(byte) this.message.length},
|
||||
this.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonObject memoObject = new JsonObject();
|
||||
if ((this.from == null) && (this.to == null)) {
|
||||
// Public memo
|
||||
// TODO: Add public memo support
|
||||
// memoObject.addProperty(KEY_FROM, "");
|
||||
// memoObject.addProperty(KEY_TO, "");
|
||||
// memoObject.addProperty(KEY_NONCE, "");
|
||||
// memoObject.addProperty(KEY_MESSAGE, Util.bytesToHex(this.message));
|
||||
return null;
|
||||
}else{
|
||||
memoObject.addProperty(KEY_FROM, this.from.toString());
|
||||
memoObject.addProperty(KEY_TO, this.to.toString());
|
||||
memoObject.addProperty(KEY_NONCE, String.format("%d", this.nonce));
|
||||
memoObject.addProperty(KEY_MESSAGE, Util.bytesToHex(this.message));
|
||||
}
|
||||
return memoObject;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import cy.agorise.graphenej.*;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate operations related to the ACCOUNT_UPDATE_OPERATION.
|
||||
*/
|
||||
public class AccountUpdateOperation extends BaseOperation {
|
||||
public static final String KEY_ACCOUNT = "account";
|
||||
public static final String KEY_OWNER = "owner";
|
||||
public static final String KEY_ACTIVE = "active";
|
||||
public static final String KEY_FEE = "fee";
|
||||
public static final String KEY_NEW_OPTIONS = "new_options";
|
||||
public static final String KEY_EXTENSIONS = "extensions";
|
||||
|
||||
private AssetAmount fee;
|
||||
private UserAccount account;
|
||||
private Optional<Authority> owner;
|
||||
private Optional<Authority> active;
|
||||
private Optional<AccountOptions> new_options;
|
||||
|
||||
/**
|
||||
* Account update operation constructor.
|
||||
* @param account User account to update. Can't be null.
|
||||
* @param owner Owner authority to set. Can be null.
|
||||
* @param active Active authority to set. Can be null.
|
||||
* @param options Active authority to set. Can be null.
|
||||
* @param fee The fee to pay. Can be null.
|
||||
*/
|
||||
public AccountUpdateOperation(UserAccount account, Authority owner, Authority active, AccountOptions options, AssetAmount fee){
|
||||
super(OperationType.ACCOUNT_UPDATE_OPERATION);
|
||||
this.fee = fee;
|
||||
this.account = account;
|
||||
this.owner = new Optional<>(owner);
|
||||
this.active = new Optional<>(active);
|
||||
this.new_options = new Optional<>(options);
|
||||
extensions = new Extensions();
|
||||
}
|
||||
|
||||
public AccountUpdateOperation(UserAccount account, Authority owner, Authority active, AccountOptions options){
|
||||
this(account, owner, active, options, new AssetAmount(UnsignedLong.valueOf(0), new Asset("1.3.0")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount fee){
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public void setOwner(Authority owner){
|
||||
this.owner = new Optional<>(owner);
|
||||
}
|
||||
|
||||
public void setActive(Authority active){
|
||||
this.active = new Optional<>(active);
|
||||
}
|
||||
|
||||
public void setAccountOptions(AccountOptions options){
|
||||
this.new_options = new Optional<>(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
|
||||
JsonObject accountUpdate = new JsonObject();
|
||||
accountUpdate.add(KEY_FEE, fee.toJsonObject());
|
||||
accountUpdate.addProperty(KEY_ACCOUNT, account.getObjectId());
|
||||
if(owner.isSet())
|
||||
accountUpdate.add(KEY_OWNER, owner.toJsonObject());
|
||||
if(active.isSet())
|
||||
accountUpdate.add(KEY_ACTIVE, active.toJsonObject());
|
||||
if(new_options.isSet())
|
||||
accountUpdate.add(KEY_NEW_OPTIONS, new_options.toJsonObject());
|
||||
accountUpdate.add(KEY_EXTENSIONS, extensions.toJsonObject());
|
||||
array.add(accountUpdate);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = fee.toBytes();
|
||||
byte[] accountBytes = account.toBytes();
|
||||
byte[] ownerBytes = owner.toBytes();
|
||||
byte[] activeBytes = active.toBytes();
|
||||
byte[] newOptionsBytes = new_options.toBytes();
|
||||
byte[] extensionBytes = extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, accountBytes, ownerBytes, activeBytes, newOptionsBytes, extensionBytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import cy.agorise.graphenej.*;
|
||||
import cy.agorise.graphenej.errors.MalformedOperationException;
|
||||
|
||||
/**
|
||||
* Created by nelson on 3/1/17.
|
||||
*/
|
||||
public class AccountUpdateOperationBuilder extends BaseOperationBuilder {
|
||||
private AssetAmount fee;
|
||||
private UserAccount account;
|
||||
private Authority owner;
|
||||
private Authority active;
|
||||
private AccountOptions new_options;
|
||||
|
||||
public AccountUpdateOperationBuilder setFee(AssetAmount fee) {
|
||||
this.fee = fee;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountUpdateOperationBuilder setAccount(UserAccount account) {
|
||||
this.account = account;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountUpdateOperationBuilder setOwner(Authority owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountUpdateOperationBuilder setActive(Authority active) {
|
||||
this.active = active;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountUpdateOperationBuilder setOptions(AccountOptions newOptions) {
|
||||
this.new_options = newOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountUpdateOperation build() {
|
||||
AccountUpdateOperation operation;
|
||||
if(this.account == null){
|
||||
throw new MalformedOperationException("This operation requires an account to be set");
|
||||
}else{
|
||||
if(owner != null || active != null || new_options != null){
|
||||
if(fee == null){
|
||||
operation = new AccountUpdateOperation(account, owner, active, new_options);
|
||||
}else{
|
||||
operation = new AccountUpdateOperation(account, owner, active, new_options, fee);
|
||||
}
|
||||
}else{
|
||||
throw new MalformedOperationException("This operation requires at least either an authority or account options change");
|
||||
}
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
|
||||
/**
|
||||
* Base template for all operation-specific factory classes.
|
||||
*/
|
||||
public abstract class BaseOperationBuilder {
|
||||
|
||||
/**
|
||||
* Must be implemented and return the specific operation the
|
||||
* factory is supposed to build.
|
||||
*
|
||||
* @return: A usable instance of a given operation.
|
||||
*/
|
||||
public abstract BaseOperation build();
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import cy.agorise.graphenej.*;
|
||||
|
||||
/**
|
||||
* Created by nelson on 3/21/17.
|
||||
*/
|
||||
public class LimitOrderCancelOperation extends BaseOperation {
|
||||
|
||||
// Constants used in the JSON representation
|
||||
public static final String KEY_FEE_PAYING_ACCOUNT = "fee_paying_account";
|
||||
public static final String KEY_ORDER_ID = "order";
|
||||
|
||||
|
||||
public LimitOrderCancelOperation(LimitOrder order, UserAccount feePayingAccount) {
|
||||
super(OperationType.LIMIT_ORDER_CANCEL_OPERATION);
|
||||
this.order = order;
|
||||
this.feePayingAccount = feePayingAccount;
|
||||
}
|
||||
|
||||
// Inner fields of a limit order cancel operation
|
||||
private AssetAmount fee;
|
||||
private UserAccount feePayingAccount;
|
||||
private LimitOrder order;
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = (JsonArray) super.toJsonObject();
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
if(fee != null)
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_FEE_PAYING_ACCOUNT, feePayingAccount.getObjectId());
|
||||
jsonObject.addProperty(KEY_ORDER_ID, order.getObjectId());
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount assetAmount) {
|
||||
this.fee = assetAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = this.fee.toBytes();
|
||||
byte[] feePayingAccountBytes = this.feePayingAccount.toBytes();
|
||||
byte[] orderIdBytes = this.order.toBytes();
|
||||
byte[] extensions = this.extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, feePayingAccountBytes, orderIdBytes, extensions);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
/**
|
||||
* Operation used to denote the creation of a limit order on the blockchain.
|
||||
*
|
||||
* The blockchain will atempt to sell amount_to_sell.asset_id for as much min_to_receive.asset_id as possible.
|
||||
* The fee will be paid by the seller's account. Market fees will apply as specified by the issuer of both the
|
||||
* selling asset and the receiving asset as a percentage of the amount exchanged.
|
||||
*
|
||||
* If either the selling asset or the receiving asset is white list restricted, the order will only be created
|
||||
* if the seller is on the white list of the restricted asset type.
|
||||
*
|
||||
* Market orders are matched in the order they are included in the block chain.
|
||||
*/
|
||||
public class LimitOrderCreateOperation extends BaseOperation {
|
||||
// Number of bytes used for the expiration field.
|
||||
private final int EXPIRATION_BYTE_LENGTH = 4;
|
||||
|
||||
// Constants used in the JSON representation
|
||||
public static final String KEY_SELLER = "seller";
|
||||
public static final String KEY_AMOUNT_TO_SELL = "amount_to_sell";
|
||||
public static final String KEY_MIN_TO_RECEIVE = "min_to_receive";
|
||||
public static final String KEY_EXPIRATION = "expiration";
|
||||
public static final String KEY_FILL_OR_KILL = "fill_or_kill";
|
||||
|
||||
// Inner fields of a limit order
|
||||
private AssetAmount fee;
|
||||
private UserAccount seller;
|
||||
private AssetAmount amountToSell;
|
||||
private AssetAmount minToReceive;
|
||||
private int expiration;
|
||||
private boolean fillOrKill;
|
||||
|
||||
/**
|
||||
* @param seller: Id of the seller
|
||||
* @param toSell: Id of the asset to sell
|
||||
* @param minToReceive: The minimum amount of the asset to receive
|
||||
* @param expiration: Expiration in seconds
|
||||
* @param fillOrKill: If this flag is set the entire order must be filled or the operation is rejected.
|
||||
*/
|
||||
public LimitOrderCreateOperation(UserAccount seller, AssetAmount toSell, AssetAmount minToReceive, int expiration, boolean fillOrKill){
|
||||
super(OperationType.LIMIT_ORDER_CREATE_OPERATION);
|
||||
this.seller = seller;
|
||||
this.amountToSell = toSell;
|
||||
this.minToReceive = minToReceive;
|
||||
this.expiration = expiration;
|
||||
this.fillOrKill = fillOrKill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = (JsonArray) super.toJsonObject();
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
if(fee != null)
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_SELLER, seller.getObjectId());
|
||||
jsonObject.add(KEY_AMOUNT_TO_SELL, amountToSell.toJsonObject());
|
||||
jsonObject.add(KEY_MIN_TO_RECEIVE, minToReceive.toJsonObject());
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
jsonObject.addProperty(KEY_EXPIRATION, simpleDateFormat.format(new Date(((long) expiration) * 1000)));
|
||||
jsonObject.addProperty(KEY_FILL_OR_KILL, this.fillOrKill ? "true" : "false");
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount assetAmount) {
|
||||
this.fee = assetAmount;
|
||||
}
|
||||
|
||||
public AssetAmount getFee(){ return this.fee; }
|
||||
|
||||
public UserAccount getSeller() {
|
||||
return seller;
|
||||
}
|
||||
|
||||
public void setSeller(UserAccount seller) {
|
||||
this.seller = seller;
|
||||
}
|
||||
|
||||
public AssetAmount getAmountToSell() {
|
||||
return amountToSell;
|
||||
}
|
||||
|
||||
public void setAmountToSell(AssetAmount amountToSell) {
|
||||
this.amountToSell = amountToSell;
|
||||
}
|
||||
|
||||
public AssetAmount getMinToReceive() {
|
||||
return minToReceive;
|
||||
}
|
||||
|
||||
public void setMinToReceive(AssetAmount minToReceive) {
|
||||
this.minToReceive = minToReceive;
|
||||
}
|
||||
|
||||
public int getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(int expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public boolean isFillOrKill() {
|
||||
return fillOrKill;
|
||||
}
|
||||
|
||||
public void setFillOrKill(boolean fillOrKill) {
|
||||
this.fillOrKill = fillOrKill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = this.fee.toBytes();
|
||||
byte[] sellerBytes = this.seller.toBytes();
|
||||
byte[] amountBytes = this.amountToSell.toBytes();
|
||||
byte[] minAmountBytes = this.minToReceive.toBytes();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(EXPIRATION_BYTE_LENGTH);
|
||||
buffer.putInt(this.expiration);
|
||||
byte[] expirationBytes = Util.revertBytes(buffer.array());
|
||||
|
||||
byte[] fillOrKill = this.fillOrKill ? new byte[]{ 0x1 } : new byte[]{ 0x0 };
|
||||
byte[] extensions = this.extensions.toBytes();
|
||||
|
||||
return Bytes.concat(feeBytes, sellerBytes, amountBytes, minAmountBytes, expirationBytes, fillOrKill, extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializer used to convert the JSON-formatted representation of a limit_order_create_operation
|
||||
* into its java object version.
|
||||
*
|
||||
* The following is an example of the serialized form of this operation:
|
||||
*
|
||||
* [
|
||||
* 1,
|
||||
* {
|
||||
* "fee": {
|
||||
* "amount": 14676,
|
||||
* "asset_id": "1.3.0"
|
||||
* },
|
||||
* "seller": "1.2.36449",
|
||||
* "amount_to_sell": {
|
||||
* "amount": 945472,
|
||||
* "asset_id": "1.3.850"
|
||||
* },
|
||||
* "min_to_receive": {
|
||||
* "amount": 4354658,
|
||||
* "asset_id": "1.3.861"
|
||||
* },
|
||||
* "expiration": "1963-11-25T06:31:44",
|
||||
* "fill_or_kill": false,
|
||||
* "extensions": []
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
*
|
||||
*/
|
||||
public static class LimitOrderCreateDeserializer implements JsonDeserializer<LimitOrderCreateOperation> {
|
||||
|
||||
@Override
|
||||
public LimitOrderCreateOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if(json.isJsonArray()){
|
||||
// This block is used just to check if we are in the first step of the deserialization
|
||||
// when we are dealing with an array.
|
||||
JsonArray serializedTransfer = json.getAsJsonArray();
|
||||
if(serializedTransfer.get(0).getAsInt() != OperationType.LIMIT_ORDER_CREATE_OPERATION.ordinal()){
|
||||
// If the operation type does not correspond to a transfer operation, we return null
|
||||
return null;
|
||||
}else{
|
||||
// Calling itself recursively, this is only done once, so there will be no problems.
|
||||
return context.deserialize(serializedTransfer.get(1), LimitOrderCreateOperation.class);
|
||||
}
|
||||
}else{
|
||||
// This block is called in the second recursion and takes care of deserializing the
|
||||
// limit order data itself.
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
|
||||
AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class);
|
||||
UserAccount seller = context.deserialize(jsonObject.get(KEY_SELLER), UserAccount.class);
|
||||
AssetAmount amountToSell = context.deserialize(jsonObject.get(KEY_AMOUNT_TO_SELL), AssetAmount.class);
|
||||
AssetAmount minToReceive = context.deserialize(jsonObject.get(KEY_MIN_TO_RECEIVE), AssetAmount.class);
|
||||
String expiration = jsonObject.get(KEY_EXPIRATION).getAsString();
|
||||
boolean fillOrKill = jsonObject.get(KEY_FILL_OR_KILL).getAsBoolean();
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
int expirationPosix = 0;
|
||||
try {
|
||||
Date expirationDate = simpleDateFormat.parse(expiration);
|
||||
expirationPosix = (int) (expirationDate.getTime() / 1000);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Creating an instance of the LimitOrderCreateOperation and setting the fee
|
||||
LimitOrderCreateOperation operation = new LimitOrderCreateOperation(seller, amountToSell, minToReceive, expirationPosix, fillOrKill);
|
||||
operation.setFee(fee);
|
||||
return operation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import cy.agorise.graphenej.objects.Memo;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the TransferOperation operation related functionalities.
|
||||
*/
|
||||
public class TransferOperation extends BaseOperation {
|
||||
public static final String KEY_AMOUNT = "amount";
|
||||
public static final String KEY_FROM = "from";
|
||||
public static final String KEY_TO = "to";
|
||||
public static final String KEY_MEMO = "memo";
|
||||
|
||||
private AssetAmount fee;
|
||||
private AssetAmount amount;
|
||||
private UserAccount from;
|
||||
private UserAccount to;
|
||||
private Memo memo;
|
||||
|
||||
public TransferOperation(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 TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount){
|
||||
super(OperationType.TRANSFER_OPERATION);
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = transferAmount;
|
||||
this.memo = new Memo();
|
||||
}
|
||||
|
||||
public UserAccount getFrom(){
|
||||
return this.from;
|
||||
}
|
||||
|
||||
public UserAccount getTo(){
|
||||
return this.to;
|
||||
}
|
||||
|
||||
public AssetAmount getAssetAmount(){
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public AssetAmount getFee(){
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
public void setAssetAmount(AssetAmount assetAmount){
|
||||
this.amount = assetAmount;
|
||||
}
|
||||
|
||||
public void setFrom(UserAccount from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public void setTo(UserAccount to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public void setMemo(Memo memo) {
|
||||
this.memo = memo;
|
||||
}
|
||||
|
||||
public Memo getMemo() {
|
||||
return this.memo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount newFee){
|
||||
this.fee = newFee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = fee.toBytes();
|
||||
byte[] fromBytes = from.toBytes();
|
||||
byte[] toBytes = to.toBytes();
|
||||
byte[] amountBytes = amount.toBytes();
|
||||
byte[] memoBytes = memo.toBytes();
|
||||
byte[] extensions = this.extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, memoBytes, extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
//TODO: Evaluate using simple Gson class to return a simple string representation and drop the TransferSerializer class
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferSerializer());
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
if(fee != null)
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_FROM, from.getObjectId());
|
||||
jsonObject.addProperty(KEY_TO, to.getObjectId());
|
||||
jsonObject.add(KEY_AMOUNT, amount.toJsonObject());
|
||||
jsonObject.add(KEY_MEMO, memo.toJsonObject());
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static class TransferSerializer implements JsonSerializer<TransferOperation> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(TransferOperation transfer, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
JsonArray arrayRep = new JsonArray();
|
||||
arrayRep.add(transfer.getId());
|
||||
arrayRep.add(transfer.toJsonObject());
|
||||
return arrayRep;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This deserializer will work on any transfer operation serialized in the 'array form' used a lot in
|
||||
* the Graphene Blockchain API.
|
||||
*
|
||||
* An example of this serialized form is the following:
|
||||
*
|
||||
* [
|
||||
* 0,
|
||||
* {
|
||||
* "fee": {
|
||||
* "amount": 264174,
|
||||
* "asset_id": "1.3.0"
|
||||
* },
|
||||
* "from": "1.2.138632",
|
||||
* "to": "1.2.129848",
|
||||
* "amount": {
|
||||
* "amount": 100,
|
||||
* "asset_id": "1.3.0"
|
||||
* },
|
||||
* "extensions": []
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* It will convert this data into a nice TransferOperation object.
|
||||
*/
|
||||
public static class TransferDeserializer implements JsonDeserializer<TransferOperation> {
|
||||
|
||||
@Override
|
||||
public TransferOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
System.out.println("Deserialized bitch start. Msg: "+ json.getAsString());
|
||||
if(json.isJsonArray()){
|
||||
// This block is used just to check if we are in the first step of the deserialization
|
||||
// when we are dealing with an array.
|
||||
JsonArray serializedTransfer = json.getAsJsonArray();
|
||||
if(serializedTransfer.get(0).getAsInt() != OperationType.TRANSFER_OPERATION.ordinal()){
|
||||
// If the operation type does not correspond to a transfer operation, we return null
|
||||
return null;
|
||||
}else{
|
||||
// Calling itself recursively, this is only done once, so there will be no problems.
|
||||
return context.deserialize(serializedTransfer.get(1), TransferOperation.class);
|
||||
}
|
||||
}else{
|
||||
// This block is called in the second recursion and takes care of deserializing the
|
||||
// transfer data itself.
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
|
||||
// Deserializing AssetAmount objects
|
||||
AssetAmount amount = context.deserialize(jsonObject.get(KEY_AMOUNT), AssetAmount.class);
|
||||
AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class);
|
||||
|
||||
// Deserializing UserAccount objects
|
||||
UserAccount from = new UserAccount(jsonObject.get(KEY_FROM).getAsString());
|
||||
UserAccount to = new UserAccount(jsonObject.get(KEY_TO).getAsString());
|
||||
TransferOperation transfer = new TransferOperation(from, to, amount, fee);
|
||||
|
||||
// Deserializing Memo if it exists
|
||||
System.out.println("Deserialized bitch. Msg: "+ jsonObject.getAsString());
|
||||
if(jsonObject.get(KEY_MEMO) != null){
|
||||
JsonObject memoObj = jsonObject.get(KEY_MEMO).getAsJsonObject();
|
||||
try{
|
||||
Address memoFrom = new Address(memoObj.get(Memo.KEY_FROM).getAsString());
|
||||
Address memoTo = new Address(memoObj.get(KEY_TO).getAsString());
|
||||
long nonce = UnsignedLong.valueOf(memoObj.get(Memo.KEY_NONCE).getAsString()).longValue();
|
||||
byte[] message = Util.hexToBytes(memoObj.get(Memo.KEY_MESSAGE).getAsString());
|
||||
Memo memo = new Memo(memoFrom, memoTo, nonce, message);
|
||||
transfer.setMemo(memo);
|
||||
}catch(MalformedAddressException e){
|
||||
System.out.println("MalformedAddressException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return transfer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue