Implementation of model for alt-coin (not graphene cryptos)

Insight api base implementation
master
hvarona 2018-08-02 23:02:23 -04:00
parent 44f43cd126
commit f40d996282
19 changed files with 2397 additions and 0 deletions

View File

@ -83,4 +83,9 @@ dependencies {
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
implementation 'commons-codec:commons-codec:1.11'
implementation ('io.socket:socket.io-client:0.8.3') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
}

View File

@ -0,0 +1,160 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
/**
* Handles all the calls for the Socket.IO of the insight api
*
* Only gets new transaction in real time for each address of an Account
*
*/
public class AccountActivityWatcher {
/**
* The mAccount to be monitor
*/
private final GeneralCoinAccount mAccount;
/**
* The list of address to monitor
*/
private List<String> mWatchAddress = new ArrayList<>();
/**
* the Socket.IO
*/
private Socket mSocket;
/**
* This app mContext, used to save on the DB
*/
private final Context mContext;
/**
* Handles the address/transaction notification.
* Then calls the GetTransactionData to get the info of the new transaction
*/
private final Emitter.Listener onAddressTransaction = new Emitter.Listener() {
@Override
public void call(Object... os) {
try {
System.out.println("Receive accountActivtyWatcher " + os[0].toString() );
String txid = ((JSONObject) os[0]).getString(InsightApiConstants.sTxTag);
new GetTransactionData(txid, mAccount, mContext).start();
} catch (JSONException ex) {
Logger.getLogger(AccountActivityWatcher.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
/**
* Handles the connect of the Socket.IO
*/
private final Emitter.Listener onConnect = new Emitter.Listener() {
@Override
public void call(Object... os) {
System.out.println("Connected to accountActivityWatcher");
JSONArray array = new JSONArray();
for(String addr : mWatchAddress) {
array.put(addr);
}
mSocket.emit(InsightApiConstants.sSubscribeEmmit, InsightApiConstants.sChangeAddressRoom, array);
}
};
/**
* Handles the disconnect of the Socket.Io
* Reconcects the mSocket
*/
private final Emitter.Listener onDisconnect = new Emitter.Listener() {
@Override
public void call(Object... os) {
System.out.println("Disconnected to accountActivityWatcher");
mSocket.connect();
}
};
/**
* Error handler, doesn't need reconnect, the mSocket.io do that by default
*/
private final Emitter.Listener onError = new Emitter.Listener() {
@Override
public void call(Object... os) {
System.out.println("Error to accountActivityWatcher ");
for(Object ob : os) {
System.out.println("accountActivityWatcher " + ob.toString());
}
}
};
/**
* Basic constructor
*
* @param mAccount The mAccount to be monitor
* @param mContext This app mContext
*/
public AccountActivityWatcher(GeneralCoinAccount mAccount, Context mContext) {
//String serverUrl = InsightApiConstants.protocol + "://" + InsightApiConstants.getAddress(mAccount.getCoin()) + ":" + InsightApiConstants.getPort(mAccount.getCoin()) + "/"+InsightApiConstants.getRawPath(mAccount.getCoin())+"/mSocket.io/";
String serverUrl = InsightApiConstants.sProtocolSocketIO + "://" + InsightApiConstants.getAddress(mAccount.getCryptoCoin()) + ":" + InsightApiConstants.getPort(mAccount.getCryptoCoin()) + "/";
this.mAccount = mAccount;
this.mContext = mContext;
System.out.println("accountActivityWatcher " + serverUrl);
try {
IO.Options opts = new IO.Options();
System.out.println("accountActivityWatcher default path " + opts.path);
this.mSocket = IO.socket(serverUrl);
this.mSocket.on(Socket.EVENT_CONNECT, onConnect);
this.mSocket.on(Socket.EVENT_DISCONNECT, onDisconnect);
this.mSocket.on(Socket.EVENT_ERROR, onError);
this.mSocket.on(Socket.EVENT_CONNECT_ERROR, onError);
this.mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, onError);
this.mSocket.on(InsightApiConstants.sChangeAddressRoom, onAddressTransaction);
} catch (URISyntaxException e) {
//TODO change exception handler
e.printStackTrace();
}
}
/**
* Add an address to be monitored, it can be used after the connect
* @param address The String address to monitor
*/
public void addAddress(String address) {
mWatchAddress.add(address);
if (this.mSocket.connected()) {
mSocket.emit(InsightApiConstants.sSubscribeEmmit, InsightApiConstants.sChangeAddressRoom, new String[]{address});
}
}
/**
* Connects the Socket
*/
public void connect() {
//TODO change to use log
System.out.println("accountActivityWatcher connecting");
try{
this.mSocket.connect();
}catch(Exception e){
//TODO change exception handler
System.out.println("accountActivityWatcher exception " + e.getMessage());
}
}
/**
* Disconnects the Socket
*/
public void disconnect() {this.mSocket.disconnect();}
}

View File

@ -0,0 +1,83 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import android.content.Context;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Broadcast a transaction, using the InsightApi
*
*/
public class BroadcastTransaction extends Thread implements Callback<Txi> {
/**
* The rawTX as Hex String
*/
private String mRawTx;
/**
* The serviceGenerator to call
*/
private InsightApiServiceGenerator mServiceGenerator;
/**
* This app context, used to save on the DB
*/
private Context mContext;
/**
* The account who sign the transaction
*/
private GeneralCoinAccount mAccount;
/**
* Basic Consturctor
* @param RawTx The RawTX in Hex String
* @param account The account who signs the transaction
* @param context This app context
*/
public BroadcastTransaction(String RawTx, GeneralCoinAccount account, Context context){
String serverUrl = InsightApiConstants.sProtocol + "://" + InsightApiConstants.getAddress(account.getCryptoCoin()) +"/";
this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl);
this.mContext = context;
this.mRawTx = RawTx;
this.mAccount = account;
}
/**
* Handles the response of the call
*
*/
@Override
public void onResponse(Call<Txi> call, Response<Txi> response) {
if (response.isSuccessful()) {
//TODO invalidated send
//TODO call getTransactionData
GetTransactionData trData = new GetTransactionData(response.body().txid,this.mAccount,this.mContext);
trData.start();
} else {
System.out.println("SENDTEST: not succesful " + response.message());
//TODO change how to handle invalid transaction
}
}
/**
* Handles the failures of the call
*/
@Override
public void onFailure(Call<Txi> call, Throwable t) {
//TODO change how to handle invalid transaction
System.out.println("SENDTEST: sendError " + t.getMessage() );
}
/**
* Starts the call of the service
*/
@Override
public void run() {
InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class);
Call<Txi> broadcastTransaction = service.broadcastTransaction(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),this.mRawTx);
broadcastTransaction.enqueue(this);
}
}

View File

@ -0,0 +1,74 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import com.google.gson.JsonObject;
import java.io.IOException;
import cy.agorise.crystalwallet.enums.CryptoCoin;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Get the estimete fee amount from an insight api server.
* This class gets the rate of the fee for a giving coin in about to block for a transaction to be
* confirmated.
*
* This ammount is giving as amount of currency / kbytes, as example btc / kbytes
*
*/
public abstract class GetEstimateFee {
//TODO add a funciton to get the rate of a specific port
/**
* The funciton to get the rate for the transaction be included in the next 2 blocks
* @param coin The coin to get the rate
* @return The rate number (coin/kbytes)
* @throws IOException If the server answer null, or the rate couldn't be calculated
*/
public static long getEstimateFee(final CryptoCoin coin) throws IOException {
String serverUrl = InsightApiConstants.sProtocol + "://"
+ InsightApiConstants.getAddress(coin) + "/";
InsightApiServiceGenerator serviceGenerator = new InsightApiServiceGenerator(serverUrl);
InsightApiService service = serviceGenerator.getService(InsightApiService.class);
Call<JsonObject> call = service.estimateFee(InsightApiConstants.getPath(coin));
final Object SYNC = new Object();
final JsonObject answer = new JsonObject();
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
synchronized (SYNC) {
answer.addProperty("answer",
(long) (response.body().get("2").getAsDouble()* Math.pow(10, coin.getPrecision())));
SYNC.notifyAll();
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
synchronized (SYNC) {
SYNC.notifyAll();
}
}
});
synchronized (SYNC){
for(int i = 0; i < 6; i++) {
try {
SYNC.wait(5000);
} catch (InterruptedException e) {
// this interruption never rises
}
if(answer.get("answer")!=null){
break;
}
}
}
if(answer.get("answer")==null){
throw new IOException("");
}
return (long) (answer.get("answer").getAsDouble());
}
}

View File

@ -0,0 +1,207 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.AddressTxi;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout;
import cy.agorise.crystalwallet.models.GTxIO;
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
import cy.agorise.crystalwallet.models.GeneralCoinAddress;
import cy.agorise.crystalwallet.models.GeneralTransaction;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Get all the transaction data of the addresses of an account
*
*/
public class GetTransactionByAddress extends Thread implements Callback<AddressTxi> {
/**
* The account to be query
*/
private GeneralCoinAccount mAccount;
/**
* The list of address to query
*/
private List<GeneralCoinAddress> mAddresses = new ArrayList<>();
/**
* The serviceGenerator to call
*/
private InsightApiServiceGenerator mServiceGenerator;
/**
* This app context, used to save on the DB
*/
private Context mContext;
/**
* Basic consturcotr
* @param account The account to be query
* @param context This app context
*/
public GetTransactionByAddress(GeneralCoinAccount account, Context context) {
String serverUrl = InsightApiConstants.sProtocol + "://" + InsightApiConstants.getAddress(account.getCryptoCoin()) +"/";
this.mAccount = account;
this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl);
this.mContext = context;
}
/**
* add an address to be query
* @param address the address to be query
*/
public void addAddress(GeneralCoinAddress address) {
this.mAddresses.add(address);
}
/**
* Handle the response
* @param call The call with the addresTxi object
* @param response the response status object
*/
@Override
public void onResponse(Call<AddressTxi> call, Response<AddressTxi> response) {
if (response.isSuccessful()) {
boolean changed = false;
AddressTxi addressTxi = response.body();
for (Txi txi : addressTxi.items) {
GeneralCoinAccount tempAccount = null;
GeneralTransaction transaction = new GeneralTransaction();
transaction.setAccount(this.mAccount);
transaction.setTxid(txi.txid);
transaction.setBlock(txi.blockheight);
transaction.setDate(new Date(txi.time * 1000));
transaction.setFee((long) (txi.fee * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
transaction.setConfirm(txi.confirmations);
transaction.setType(this.mAccount.getCryptoCoin());
transaction.setBlockHeight(txi.blockheight);
for (Vin vin : txi.vin) {
GTxIO input = new GTxIO();
input.setAmount((long) (vin.value * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
input.setTransaction(transaction);
input.setOut(true);
input.setType(this.mAccount.getCryptoCoin());
String addr = vin.addr;
input.setAddressString(addr);
input.setIndex(vin.n);
input.setScriptHex(vin.scriptSig.hex);
input.setOriginalTxid(vin.txid);
for (GeneralCoinAddress address : this.mAddresses) {
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
input.setAddress(address);
tempAccount = address.getAccount();
if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) {
address.getTransactionOutput().add(input);
}
changed = true;
}
}
transaction.getTxInputs().add(input);
}
for (Vout vout : txi.vout) {
if(vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0){
// The address is null, this must be a memo
String hex = vout.scriptPubKey.hex;
int opReturnIndex = hex.indexOf("6a");
if(opReturnIndex >= 0) {
byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex+2,opReturnIndex+4),16)];
for(int i = 0; i < memoBytes.length;i++){
memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex+4+(i*2),opReturnIndex+6+(i*2)),16);
}
transaction.setMemo(new String(memoBytes));
}
}else {
GTxIO output = new GTxIO();
output.setAmount((long) (vout.value * Math.pow(10, this.mAccount.getCryptoCoin().getPrecision())));
output.setTransaction(transaction);
output.setOut(false);
output.setType(this.mAccount.getCryptoCoin());
String addr = vout.scriptPubKey.addresses[0];
output.setAddressString(addr);
output.setIndex(vout.n);
output.setScriptHex(vout.scriptPubKey.hex);
for (GeneralCoinAddress address : this.mAddresses) {
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
output.setAddress(address);
tempAccount = address.getAccount();
if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) {
address.getTransactionInput().add(output);
}
changed = true;
}
}
transaction.getTxOutputs().add(output);
}
}
if(txi.txlock && txi.confirmations< this.mAccount.getCryptoNet().getConfirmationsNeeded()){
transaction.setConfirm(this.mAccount.getCryptoNet().getConfirmationsNeeded());
}
//TODO database
/*SCWallDatabase db = new SCWallDatabase(this.mContext);
long idTransaction = db.getGeneralTransactionId(transaction);
if (idTransaction == -1) {
db.putGeneralTransaction(transaction);
} else {
transaction.setId(idTransaction);
db.updateGeneralTransaction(transaction);
}*/
if (tempAccount != null && transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) {
new GetTransactionData(transaction.getTxid(), tempAccount, this.mContext, true).start();
}
for (GeneralCoinAddress address : this.mAddresses) {
if (address.updateTransaction(transaction)) {
break;
}
}
}
if(changed) {
this.mAccount.balanceChange();
}
}
}
/**
* Failure of the call
* @param call The call object
* @param t The reason for the failure
*/
@Override
public void onFailure(Call<AddressTxi> call, Throwable t) {
Log.e("GetTransactionByAddress", "Error in json format");
}
/**
* Function to start the insight api call
*/
@Override
public void run() {
if (this.mAddresses.size() > 0) {
StringBuilder addressToQuery = new StringBuilder();
for (GeneralCoinAddress address : this.mAddresses) {
addressToQuery.append(address.getAddressString(this.mAccount.getNetworkParam())).append(",");
}
addressToQuery.deleteCharAt(addressToQuery.length() - 1);
InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class);
Call<AddressTxi> addressTxiCall = service.getTransactionByAddress(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),addressToQuery.toString());
addressTxiCall.enqueue(this);
}
}
}

View File

@ -0,0 +1,196 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import android.content.Context;
import java.util.Date;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout;
import cy.agorise.crystalwallet.models.GTxIO;
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
import cy.agorise.crystalwallet.models.GeneralCoinAddress;
import cy.agorise.crystalwallet.models.GeneralTransaction;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* CThis class retrieve the data of a single transaction
*/
public class GetTransactionData extends Thread implements Callback<Txi> {
/**
* The account to be query
*/
private final GeneralCoinAccount mAccount;
/**
* The transaction txid to be query
*/
private String mTxId;
/**
* The serviceGenerator to call
*/
private InsightApiServiceGenerator mServiceGenerator;
/**
* This app context, used to save on the DB
*/
private Context mContext;
/**
* If has to wait for another confirmation
*/
private boolean mMustWait = false;
/**
* Constructor used to query for a transaction with unknown confirmations
* @param txid The txid of the transaciton to be query
* @param account The account to be query
* @param context This app Context
*/
public GetTransactionData(String txid, GeneralCoinAccount account, Context context) {
this(txid, account, context, false);
}
/**
* Consturctor to be used qhen the confirmations of the transaction are known
* @param txid The txid of the transaciton to be query
* @param account The account to be query
* @param context This app Context
* @param mustWait If there is less confirmation that needed
*/
public GetTransactionData(String txid, GeneralCoinAccount account, Context context, boolean mustWait) {
String serverUrl = InsightApiConstants.sProtocol + "://" + InsightApiConstants.getAddress(account.getCryptoCoin()) +"/";
this.mAccount = account;
this.mTxId= txid;
this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl);
this.mContext = context;
this.mMustWait = mustWait;
}
/**
* Function to start the insight api call
*/
@Override
public void run() {
if (this.mMustWait) {
//We are waiting for confirmation
try {
Thread.sleep(InsightApiConstants.sWaitTime);
} catch (InterruptedException ignored) {
//TODO this exception never rises
}
}
InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class);
Call<Txi> txiCall = service.getTransaction(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),this.mTxId);
txiCall.enqueue(this);
}
@Override
public void onResponse(Call<Txi> call, Response<Txi> response) {
if (response.isSuccessful()) {
Txi txi = response.body();
GeneralTransaction transaction = new GeneralTransaction();
transaction.setAccount(this.mAccount);
transaction.setTxid(txi.txid);
transaction.setBlock(txi.blockheight);
transaction.setDate(new Date(txi.time * 1000));
transaction.setFee((long) (txi.fee * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
transaction.setConfirm(txi.confirmations);
transaction.setType(this.mAccount.getCryptoCoin());
transaction.setBlockHeight(txi.blockheight);
for (Vin vin : txi.vin) {
GTxIO input = new GTxIO();
input.setAmount((long) (vin.value * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
input.setTransaction(transaction);
input.setOut(true);
input.setType(this.mAccount.getCryptoCoin());
String addr = vin.addr;
input.setAddressString(addr);
input.setIndex(vin.n);
input.setScriptHex(vin.scriptSig.hex);
input.setOriginalTxid(vin.txid);
for (GeneralCoinAddress address : this.mAccount.getAddresses()) {
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
input.setAddress(address);
if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) {
address.getTransactionOutput().add(input);
}
}
}
transaction.getTxInputs().add(input);
}
for (Vout vout : txi.vout) {
if(vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0){
// The address is null, this must be a memo
String hex = vout.scriptPubKey.hex;
int opReturnIndex = hex.indexOf("6a");
if(opReturnIndex >= 0) {
byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex+2,opReturnIndex+4),16)];
for(int i = 0; i < memoBytes.length;i++){
memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex+4+(i*2),opReturnIndex+6+(i*2)),16);
}
transaction.setMemo(new String(memoBytes));
System.out.println("Memo read : " + transaction.getMemo()); //TODO log this line
}
}else {
GTxIO output = new GTxIO();
output.setAmount((long) (vout.value * Math.pow(10, this.mAccount.getCryptoCoin().getPrecision())));
output.setTransaction(transaction);
output.setOut(false);
output.setType(this.mAccount.getCryptoCoin());
String addr = vout.scriptPubKey.addresses[0];
output.setAddressString(addr);
output.setIndex(vout.n);
output.setScriptHex(vout.scriptPubKey.hex);
for (GeneralCoinAddress address : this.mAccount.getAddresses()) {
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
output.setAddress(address);
if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) {
address.getTransactionInput().add(output);
}
}
}
transaction.getTxOutputs().add(output);
}
}
// This is for features like dash instantSend
if(txi.txlock && txi.confirmations< this.mAccount.getCryptoNet().getConfirmationsNeeded()){
transaction.setConfirm(this.mAccount.getCryptoNet().getConfirmationsNeeded());
}
//TODO database
/*SCWallDatabase db = new SCWallDatabase(this.mContext);
long idTransaction = db.getGeneralTransactionId(transaction);
if (idTransaction == -1) {
db.putGeneralTransaction(transaction);
} else {
transaction.setId(idTransaction);
db.updateGeneralTransaction(transaction);
}*/
this.mAccount.updateTransaction(transaction);
this.mAccount.balanceChange();
if (transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) {
//If transaction weren't confirmed, add the transaction to watch for change on the confirmations
new GetTransactionData(this.mTxId, this.mAccount, this.mContext, true).start();
}
}
}
/**
* TODO handle the failure response
* @param call the Call object
* @param t the reason of the failure
*/
@Override
public void onFailure(Call<Txi> call, Throwable t) {
}
}

View File

@ -0,0 +1,116 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import java.util.HashMap;
import cy.agorise.crystalwallet.enums.CryptoCoin;
/**
* Class holds all constant related to the Insight Api
*
*/
abstract class InsightApiConstants {
/**
* Protocol of the insight api calls
*/
static final String sProtocol = "https";
/**
* Protocol of the insigiht api Socket.IO connection
*/
static final String sProtocolSocketIO = "http";
/**
* Contains each url information for each coin
*/
private static final HashMap<CryptoCoin,AddressPort> sServerAddressPort = new HashMap<>();
/**
* Insight api Socket.IO new transaction by address notification
*/
static final String sChangeAddressRoom = "bitcoind/addresstxid";
/**
* Socket.io subscribe command
*/
static final String sSubscribeEmmit = "subscribe";
/**
* Tag used in the response of the address transaction notification
*/
static final String sTxTag = "txid";
/**
* Wait time to check for confirmations
*/
static long sWaitTime = (30 * 1000); //wait 1 minute
//Filled the serverAddressPort maps with static data
static{
//serverAddressPort.put(Coin.BITCOIN,new AddressPort("fr.blockpay.ch",3002,"node/btc/testnet","insight-api"));
sServerAddressPort.put(CryptoCoin.BITCOIN,new AddressPort("fr.blockpay.ch",3003,"node/btc/testnet","insight-api"));
//serverAddressPort.put(Coin.BITCOIN_TEST,new AddressPort("fr.blockpay.ch",3003,"node/btc/testnet","insight-api"));
sServerAddressPort.put(CryptoCoin.LITECOIN,new AddressPort("fr.blockpay.ch",3009,"node/ltc","insight-lite-api"));
sServerAddressPort.put(CryptoCoin.DASH,new AddressPort("fr.blockpay.ch",3005,"node/dash","insight-api-dash"));
sServerAddressPort.put(CryptoCoin.DOGECOIN,new AddressPort("fr.blockpay.ch",3006,"node/dogecoin","insight-api"));
}
/**
* Get the insight api server address
* @param coin The coin of the API to find
* @return The String address of the server, can be a name or the IP
*/
static String getAddress(CryptoCoin coin){
return sServerAddressPort.get(coin).mServerAddress;
}
/**
* Get the port of the server Insight API
* @param coin The coin of the API to find
* @return The server number port
*/
static int getPort(CryptoCoin coin){
return sServerAddressPort.get(coin).mPort;
}
/**
* Get the url path of the server Insight API
* @param coin The coin of the API to find
* @return The path of the Insight API
*/
static String getPath(CryptoCoin coin){
return sServerAddressPort.get(coin).mPath + "/" + sServerAddressPort.get(coin).mInsightPath;
}
/**
* Contains all the url info neccessary to connects to the insight api
*/
private static class AddressPort{
/**
* The server address
*/
final String mServerAddress;
/**
* The port used in the Socket.io
*/
final int mPort;
/**
* The path of the coin server
*/
final String mPath;
/**
* The path of the insight api
*/
final String mInsightPath;
/**
* Constructor
* @param serverAddress The server address of the Insight API
* @param port the port number of the Insight API
* @param path the path to the Insight API before the last /
* @param insightPath the path after the last / of the Insight API
*/
AddressPort(String serverAddress, int port, String path, String insightPath) {
this.mServerAddress = serverAddress;
this.mPort = port;
this.mPath = path;
this.mInsightPath = insightPath;
}
}
}

View File

@ -0,0 +1,52 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import com.google.gson.JsonObject;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.AddressTxi;
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
/**
* Holds each call to the insigh api server
*/
interface InsightApiService {
/**
* The query for the info of a single transaction
* @param path The path of the insight api without the server address
* @param txid the transasction to be query
*/
@GET("{path}/tx/{txid}")
Call<Txi> getTransaction(@Path(value = "path", encoded = true) String path, @Path(value = "txid", encoded = true) String txid);
/**
* The query for the transasctions of multiples addresses
* @param path The path of the insight api without the server address
* @param addrs the addresses to be query each separated with a ","
*/
@GET("{path}/addrs/{addrs}/txs")
Call<AddressTxi> getTransactionByAddress(@Path(value = "path", encoded = true) String path, @Path(value = "addrs", encoded = true) String addrs);
/**
* Broadcast Transaction
* @param path The path of the insight api without the server address
* @param rawtx the rawtx to send in Hex String
*/
@FormUrlEncoded
@POST("{path}/tx/send")
Call<Txi> broadcastTransaction(@Path(value = "path", encoded = true) String path, @Field("rawtx") String rawtx);
/**
* Get the estimate rate fee for a coin in the Insight API
* @param path The path of the insight api without the server address
*/
@GET("{path}/utils/estimatefee?nbBlocks=2")
Call<JsonObject> estimateFee(@Path(value = "path", encoded = true) String path);
}

View File

@ -0,0 +1,130 @@
package cy.agorise.crystalwallet.apigenerator.insightapi;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Generatir fir tge okhttp connection of the Insight API
* TODO finish documentation
*/
class InsightApiServiceGenerator {
/**
* Tag used for logging
*/
public static String TAG = "InsightApiServiceGenerator";
/**
* The complete uri to connect to the insight api, this change from coin to coin
*/
private static String sApiBaseUrl;
/**
* Loggin interceptor
*/
private static HttpLoggingInterceptor sLogging;
/**
* Http builder
*/
private static OkHttpClient.Builder sClientBuilder;
/**
* Builder for the retrofit class
*/
private static Retrofit.Builder sBuilder;
/**
*
*/
private static HashMap<Class<?>, Object> sServices;
/**
* Constructor, using the url of a insigth api coin
* @param apiBaseUrl The complete url to the server of the insight api
*/
InsightApiServiceGenerator(String apiBaseUrl) {
sApiBaseUrl= apiBaseUrl;
sLogging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
sClientBuilder = new OkHttpClient.Builder().addInterceptor(sLogging);
sBuilder = new Retrofit.Builder().baseUrl(sApiBaseUrl).addConverterFactory(GsonConverterFactory.create());
sServices = new HashMap<>();
}
/**
*
* @param klass
* @param thing
* @param <T>
*/
private static <T> void setService(Class<T> klass, T thing) {
sServices.put(klass, thing);
}
/**
*
* @param serviceClass
* @param <T>
* @return
*/
public <T> T getService(Class<T> serviceClass) {
T service = serviceClass.cast(sServices.get(serviceClass));
if (service == null) {
service = createService(serviceClass);
setService(serviceClass, service);
}
return service;
}
/**
*
* @param serviceClass
* @param <S>
* @return
*/
private static <S> S createService(Class<S> serviceClass) {
sClientBuilder.interceptors().add(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Request original = chain.request();
okhttp3.Request.Builder requestBuilder = original.newBuilder().method(original.method(), original.body());
okhttp3.Request request = requestBuilder.build();
return chain.proceed(request);
}
});
sClientBuilder.readTimeout(5, TimeUnit.MINUTES);
sClientBuilder.connectTimeout(5, TimeUnit.MINUTES);
OkHttpClient client = sClientBuilder.build();
Retrofit retrofit = sBuilder.client(client).build();
return retrofit.create(serviceClass);
}
/**
*
* @return
*/
public static InsightApiService Create() {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.interceptors().add(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Request original = chain.request();
// Customize the request
okhttp3.Request request = original.newBuilder().method(original.method(), original.body()).build();
return chain.proceed(request);
}
});
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder().baseUrl(sApiBaseUrl).client(client).build();
return retrofit.create(InsightApiService.class);
}
}

View File

@ -0,0 +1,24 @@
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
/**
* Represents the address txi of a insishgt api response
*/
public class AddressTxi {
/**
* The total number of items
*/
public int totalItems;
/**
* The start index of the current txi
*/
public int from;
/**
* the last index of the current txi
*/
public int to;
/**
* The arrays of txi of this response
*/
public Txi[] items;
}

View File

@ -0,0 +1,24 @@
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
/**
* The transasction Script public keym is used to validate
*/
public class ScriptPubKey {
/**
* The code to validate in hex
*/
public String hex;
/**
* the code to validate this transaction
*/
public String asm;
/**
* the acoin address involved
*/
public String[] addresses;
/**
* The type of the hash
*/
public String type;
}

View File

@ -0,0 +1,16 @@
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
/**
* Reprensents the Script signature of an trnasaction Input
*/
public class ScriptSig {
/**
* The hex
*/
public String hex;
/**
* the asm
*/
public String asm;
}

View File

@ -0,0 +1,69 @@
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
/**
* Represents one transaction input of the insight API
*/
public class Txi {
/**
* The id of this transaction
*/
public String txid;
/**
* the version number of this transaction
*/
public int version;
/**
* Time to hold this transaction
*/
public long locktime;
/**
* The array of the transaction inputs
*/
public Vin[] vin;
/**
* the array of the transactions outputs
*/
public Vout[] vout;
/**
* this block hash
*/
public String blockhash;
/**
* The blockheight where this transaction belongs, if 0 this transactions hasn't be included in any block yet
*/
public int blockheight;
/**
* Number of confirmations
*/
public int confirmations;
/**
* The time of the first broadcast fo this transaction
*/
public long time;
/**
* The time which this transaction was included
*/
public long blocktime;
/**
* Total value to transactions outputs
*/
public double valueOut;
/**
* The size in bytes
*/
public int size;
/**
* Total value of transactions inputs
*/
public double valueIn;
/**
* Fee of this transaction has to be valueIn - valueOut
*/
public double fee;
/**
* This is only for dash, is the instantsend state
*/
public boolean txlock=false;
}

View File

@ -0,0 +1,44 @@
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
/**
* This represents a transaction input
*/
public class Vin {
/**
* The original transaction id where this transaction is an output
*/
public String txid;
/**
*
*/
public int vout;
/**
* Sequence fo the transaction
*/
public long sequence;
/**
* Order of the transasction input on the transasction
*/
public int n;
/**
* The script signature
*/
public ScriptSig scriptSig;
/**
* The addr of this transaction
*/
public String addr;
/**
* Value in satoshi
*/
public long valueSat;
/**
* Calue of this transaction
*/
public double value;
/**
*
*/
public String doubleSpentTxID;
}

View File

@ -0,0 +1,32 @@
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
/**
* Represents a Transasction output
*/
public class Vout {
/**
* The amount of coin
*/
public double value;
/**
* the order of this transaciton output on the transaction
*/
public int n;
/**
* The script public key
*/
public ScriptPubKey scriptPubKey;
/**
* If this transaciton output was spent what txid it belongs
*/
public String spentTxId;
/**
* The index on the transaction that this transaction was spent
*/
public String spentIndex;
/**
* The block height of the transaction this output was spent
*/
public String spentHeight;
}

View File

@ -0,0 +1,166 @@
package cy.agorise.crystalwallet.models;
import cy.agorise.crystalwallet.enums.CryptoCoin;
/**
* General Coin Transaction Input/Output
*
* This class represent each Input or Output Transaction of a General Coin Transaction
*
* Created by henry on 06/02/2017.
*/
public class GTxIO {
/**
* The id on the database
*/
private long mId = -1;
/**
* The Coin type of this transaction
*/
private CryptoCoin mType;
/**
* The index on the transaction Input/Output
*/
private int mIndex;
/**
* The address that this transaction Input/Output belongs
*/
private GeneralCoinAddress mAddress;
/**
* The transaction that this Input/Output belongs
*/
private GeneralTransaction mTransaction;
/**
* The amount
*/
private long mAmount;
/**
* If this transaction is output or input
*/
private boolean mIsOut;
/**
* The address of this transaction as String
*/
private String mAddressString;
/**
* The Script as Hex
*/
private String mScriptHex;
/**
* If this is a transaction output, the original transaction where this is input
*/
private String mOriginalTxId;
/**
* Empty Constructor
*/
public GTxIO() {
}
/**
* General Constructor, used by the DB.
*
* @param id The id in the dataabase
* @param type The coin mType
* @param address The addres fo an account on the wallet, or null if the address is external
* @param transaction The transaction where this belongs
* @param amount The amount with the lowest precision
* @param isOut if this is an output
* @param addressString The string of the General Coin address, this can't be null
* @param index The index on the transaction
* @param scriptHex The script in hex String
*/
public GTxIO(long id, CryptoCoin type, GeneralCoinAddress address, GeneralTransaction transaction, long amount, boolean isOut, String addressString, int index, String scriptHex) {
this.mId = id;
this.mType = type;
this.mAddress = address;
this.mTransaction = transaction;
this.mAmount = amount;
this.mIsOut = isOut;
this.mAddressString = addressString;
this.mIndex = index;
this.mScriptHex = scriptHex;
}
public long getId() {
return mId;
}
public void setId(long id) {
this.mId = id;
}
public CryptoCoin getType() {
return mType;
}
public void setType(CryptoCoin type) {
this.mType = type;
}
public int getIndex() {
return mIndex;
}
public void setIndex(int index) {
this.mIndex = index;
}
public GeneralCoinAddress getAddress() {
return mAddress;
}
public void setAddress(GeneralCoinAddress address) {
this.mAddress = address;
}
public GeneralTransaction getTransaction() {
return mTransaction;
}
public void setTransaction(GeneralTransaction transaction) {
this.mTransaction = transaction;
}
public long getAmount() {
return mAmount;
}
public void setAmount(long amount) {
this.mAmount = amount;
}
public boolean isOut() {
return mIsOut;
}
public void setOut(boolean out) {
mIsOut = out;
}
public String getAddressString() {
return mAddressString;
}
public void setAddressString(String addressString) {
this.mAddressString = addressString;
}
public String getScriptHex() {
return mScriptHex;
}
public void setScriptHex(String scriptHex) {
this.mScriptHex = scriptHex;
}
public String getOriginalTxid() {
return mOriginalTxId;
}
public void setOriginalTxid(String originalTxid) {
this.mOriginalTxId = originalTxid;
}
}

View File

@ -0,0 +1,392 @@
package cy.agorise.crystalwallet.models;
import android.content.Context;
import android.util.Log;
import com.google.gson.JsonObject;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import cy.agorise.crystalwallet.enums.CryptoCoin;
import cy.agorise.crystalwallet.enums.CryptoNet;
/**
* Created by henry on 2/8/2018.
*/
public abstract class GeneralCoinAccount extends CryptoNetAccount {
/**
* The account number of the BIP-44
*/
protected int mAccountNumber;
/**
* The index of the last used external address
*/
protected int mLastExternalIndex;
/**
* The indes of the last used change address
*/
protected int mLastChangeIndex;
/**
* The account key, this is calculated as a cache
*/
protected DeterministicKey mAccountKey;
/**
* With this key we can calculate the external addresses
*/
protected DeterministicKey mExternalKey;
/**
* With this key we can calculate the change address
*/
protected DeterministicKey mChangeKey;
/**
* The keys for externals addresses
*/
protected HashMap<Integer, GeneralCoinAddress> mExternalKeys = new HashMap<Integer,GeneralCoinAddress>();
/**
* The keys for the change addresses
*/
protected HashMap<Integer, GeneralCoinAddress> mChangeKeys = new HashMap<Integer,GeneralCoinAddress>();
/**
* The list of transaction that involves this account
*/
protected List<GeneralTransaction> mTransactions = new ArrayList<GeneralTransaction>();
/**
* The Limit gap define in the BIP-44
*/
private final static int sAddressGap = 20;
/**
* is the coin number defined by the SLIP-44
*/
private final int mCoinNumber;
public GeneralCoinAccount(long mId, AccountSeed seed, int mAccountIndex, CryptoNet mCryptoNet, int mAccountNumber, int mLastExternalIndex, int mLastChangeIndex, int mCoinNumber) {
super(mId, seed.getId(), mAccountIndex, mCryptoNet);
this.mAccountNumber = mAccountNumber;
this.mLastExternalIndex = mLastExternalIndex;
this.mLastChangeIndex = mLastChangeIndex;
this.mCoinNumber = mCoinNumber;
calculateAddresses((DeterministicKey) seed.getPrivateKey());
}
/**
* Setter for the transactions of this account, this is used from the database
*/
public void setTransactions(List<GeneralTransaction> transactions) {
this.mTransactions = transactions;
}
/**
* Calculates each basic key, not the addresses keys using the BIP-44
*/
private void calculateAddresses(DeterministicKey masterKey) {
DeterministicKey purposeKey = HDKeyDerivation.deriveChildKey(masterKey,
new ChildNumber(44, true));
DeterministicKey coinKey = HDKeyDerivation.deriveChildKey(purposeKey,
new ChildNumber(this.mCoinNumber, true));
this.mAccountKey = HDKeyDerivation.deriveChildKey(coinKey,
new ChildNumber(this.mAccountNumber, true));
this.mExternalKey = HDKeyDerivation.deriveChildKey(this.mAccountKey,
new ChildNumber(0, false));
this.mChangeKey = HDKeyDerivation.deriveChildKey(this.mAccountKey,
new ChildNumber(1, false));
}
/**
* Calculate the external address keys until the index + gap
*/
public void calculateGapExternal() {
for (int i = 0; i < this.mLastExternalIndex + this.sAddressGap; i++) {
if (!this.mExternalKeys.containsKey(i)) {
this.mExternalKeys.put(i, new GeneralCoinAddress(this, false, i,
HDKeyDerivation.deriveChildKey(this.mExternalKey,
new ChildNumber(i, false))));
}
}
}
/**
* Calculate the change address keys until the index + gap
*/
public void calculateGapChange() {
for (int i = 0; i < this.mLastChangeIndex + this.sAddressGap; i++) {
if (!this.mChangeKeys.containsKey(i)) {
this.mChangeKeys.put(i, new GeneralCoinAddress(this, true, i,
HDKeyDerivation.deriveChildKey(this.mChangeKey,
new ChildNumber(i, false))));
}
}
}
//TODO check init address
/*public List<GeneralCoinAddress> getAddresses(SCWallDatabase db) {
//TODO check for used address
this.getNextReceiveAddress();
this.getNextChangeAddress();
this.calculateGapExternal();
this.calculateGapChange();
List<GeneralCoinAddress> addresses = new ArrayList();
addresses.addAll(this.mChangeKeys.values());
addresses.addAll(this.mExternalKeys.values());
this.saveAddresses(db);
return addresses;
}*/
/**
* Get the list of all the address, external and change addresses
* @return a list with all the addresses of this account
*/
public List<GeneralCoinAddress> getAddresses() {
List<GeneralCoinAddress> addresses = new ArrayList();
addresses.addAll(this.mChangeKeys.values());
addresses.addAll(this.mExternalKeys.values());
return addresses;
}
/**
* Charges the list of addresse of this account, this is used from the database
*/
public void loadAddresses(List<GeneralCoinAddress> addresses) {
for (GeneralCoinAddress address : addresses) {
if (address.isIsChange()) {
this.mChangeKeys.put(address.getIndex(), address);
} else {
this.mExternalKeys.put(address.getIndex(), address);
}
}
}
//TODO save address
/*public void saveAddresses(SCWallDatabase db) {
for (GeneralCoinAddress externalAddress : this.mExternalKeys.values()) {
if (externalAddress.getId() == -1) {
long id = db.putGeneralCoinAddress(externalAddress);
if(id != -1)
externalAddress.setId(id);
} else {
db.updateGeneralCoinAddress(externalAddress);
}
}
for (GeneralCoinAddress changeAddress : this.mChangeKeys.values()) {
if (changeAddress.getId() == -1) {
Log.i("SCW","change address id " + changeAddress.getId());
long id = db.putGeneralCoinAddress(changeAddress);
if(id != -1)
changeAddress.setId(id);
} else {
db.updateGeneralCoinAddress(changeAddress);
}
}
db.updateGeneralCoinAccount(this);
}*/
/**
* Getter of the account number
*/
public int getAccountNumber() {
return this.mAccountNumber;
}
/**
* Getter of the last external address used index
*/
public int getLastExternalIndex() {
return this.mLastExternalIndex;
}
/**
* Getter of the last change address used index
*/
public int getLastChangeIndex() {
return this.mLastChangeIndex;
}
/**
* Getter of the next receive address
* @return The next unused receive address to be used
*/
public abstract String getNextReceiveAddress();
/**
* Getter of the next change address
* @return The next unused change address to be used
*/
public abstract String getNextChangeAddress();
/**
* Transfer coin amount to another address
*
* @param toAddress The destination address
* @param coin the coin
* @param amount the amount to send in satoshi
* @param memo the memo, this can be empty
* @param context the android context
*/
public abstract void send(String toAddress, CryptoCoin coin, long amount, String memo,
Context context);
/**
* Transform this account into json object to be saved in the bin file, or any other file
*/
public JsonObject toJson() {
JsonObject answer = new JsonObject();
answer.addProperty("type", this.getCryptoNet().name());
answer.addProperty("name", this.getName());
answer.addProperty("accountNumber", this.mAccountNumber);
answer.addProperty("changeIndex", this.mLastChangeIndex);
answer.addProperty("externalIndex", this.mLastExternalIndex);
return answer;
}
/**
* Getter of the list of transactions
*/
public List<GeneralTransaction> getTransactions() {
List<GeneralTransaction> transactions = new ArrayList();
for (GeneralCoinAddress address : this.mExternalKeys.values()) {
for (GTxIO giotx : address.getTransactionInput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
for (GTxIO giotx : address.getTransactionOutput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
}
for (GeneralCoinAddress address : this.mChangeKeys.values()) {
for (GTxIO giotx : address.getTransactionInput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
for (GTxIO giotx : address.getTransactionOutput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
;
}
Collections.sort(transactions, new TransactionsCustomComparator());
return transactions;
}
public CryptoCoin getCryptoCoin(){
return CryptoCoin.valueOf(this.getCryptoNet().name());
}
/**
* Get the address as string of an adrees index
* @param index The index of the address
* @param change if it is change addres or is a external address
* @return The Address as string
*/
public abstract String getAddressString(int index, boolean change);
/**
* Get the GeneralCoinAddress object of an address
* @param index the index of the address
* @param change if it is change addres or is a external address
* @return The GeneralCoinAddress of the address
*/
public abstract GeneralCoinAddress getAddress(int index, boolean change);
/**
* Return the network parameters, this is used for the bitcoiinj library
*/
public abstract NetworkParameters getNetworkParam();
/**
* Triggers the event onBalanceChange
*/
public void balanceChange() {
this._fireOnChangeBalance(this.getBalance().get(0)); //TODO make it more genertic
}
public abstract List<CryptoNetBalance> getBalance();
/**
* Compare the transaction, to order it for the list of transaction
*/
public class TransactionsCustomComparator implements Comparator<GeneralTransaction> {
@Override
public int compare(GeneralTransaction o1, GeneralTransaction o2) {
return o1.getDate().compareTo(o2.getDate());
}
}
/**
* Add listener for the onChangebalance Event
*/
/*public void addChangeBalanceListener(ChangeBalanceListener listener) {
this.mChangeBalanceListeners.add(listener);
}*/
/**
* Fire the onChangeBalance event
*/
protected void _fireOnChangeBalance(CryptoNetBalance balance) {
/*for (ChangeBalanceListener listener : this.mChangeBalanceListeners) {
listener.balanceChange(balance);
}*/
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeneralCoinAccount that = (GeneralCoinAccount) o;
if (this.getCryptoNet() != that.getCryptoNet()) return false;
if (this.getAccountNumber() != that.getAccountNumber()) return false;
return this.mAccountKey != null ? this.mAccountKey.equals(that.mAccountKey)
: that.mAccountKey == null;
}
@Override
public int hashCode() {
int result = this.getAccountNumber();
result = 31 * result + (this.mAccountKey != null ? this.mAccountKey.hashCode() : 0);
return result;
}
/**
* Updates a transaction
*
* @param transaction The transaction to update
*/
public void updateTransaction(GeneralTransaction transaction){
// Checks if it has an external address
for (GeneralCoinAddress address : this.mExternalKeys.values()) {
if(address.updateTransaction(transaction)){
return;
}
}
for (GeneralCoinAddress address : this.mChangeKeys.values()) {
if(address.updateTransaction(transaction)){
return;
}
}
}
}

View File

@ -0,0 +1,371 @@
package cy.agorise.crystalwallet.models;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.DeterministicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import cy.agorise.graphenej.Util;
/**
* Represents an Address of a General Coin Account
*/
public class GeneralCoinAddress {
/**
* The id on the database
*/
private long mId = -1;
/**
* The account that this address belongs
*/
private final GeneralCoinAccount mAccount;
/**
* If this is change or external
*/
private final boolean mIsChange;
/**
* The index fo this address in the account
*/
private final int mIndex;
/**
* The ky used to calculate the address
*/
private ECKey mKey;
/**
* The list of the transactions that used this address as input
*/
private List<GTxIO> mTransactionInput = new ArrayList<>();
/**
* The list of the transactions that used this address as output
*/
private List<GTxIO> mTransactionOutput = new ArrayList<>();
/**
* Contrsutcotr used from the database
* @param id The id on the database
* @param account The account of this address
* @param isChange if it is change or external address
* @param index the index on the account of this address
* @param publicHexKey The public Address String
*/
public GeneralCoinAddress(long id, GeneralCoinAccount account, boolean isChange, int index, String publicHexKey) {
this.mId = id;
this.mAccount = account;
this.mIsChange = isChange;
this.mIndex = index;
this.mKey = ECKey.fromPublicOnly(Util.hexToBytes(publicHexKey));
}
/**
* Basic constructor
* @param account The account of this address
* @param isChange if it is change or external address
* @param index The index on the account of this address
* @param key The key to generate the private and the public key of this address
*/
public GeneralCoinAddress(GeneralCoinAccount account, boolean isChange, int index, DeterministicKey key) {
this.mId = -1;
this.mAccount = account;
this.mIsChange = isChange;
this.mIndex = index;
this.mKey = key;
}
/**
* Getter of the database id
*/
public long getId() {
return mId;
}
/**
* Setter of the database id
*/
public void setId(long id) {
this.mId = id;
}
/**
* Getter for he account
*/
public GeneralCoinAccount getAccount() {
return mAccount;
}
/**
* Indicates if this addres is change, if not is external
*/
public boolean isIsChange() {
return mIsChange;
}
/**
* Getter for the index on the account of this address
*/
public int getIndex() {
return mIndex;
}
/**
* Getter for the key of this address
*/
public ECKey getKey() {
return mKey;
}
/**
* Set the key for generate private key, this is used when this address is loaded from the database
* and want to be used to send transactions
* @param key The key that generates the private and the public key
*/
public void setKey(DeterministicKey key) {
this.mKey = key;
}
/**
* Get the address as a String
* @param param The network param of this address
*/
public String getAddressString(NetworkParameters param) {
return mKey.toAddress(param).toString();
}
/**
* Returns the bitcoinj Address representing this address
* @param param The network parameter of this address
*/
public Address getAddress(NetworkParameters param) {
return mKey.toAddress(param);
}
/**
* Gets the list of transaction that this address is input
*/
public List<GTxIO> getTransactionInput() {
return mTransactionInput;
}
/**
* Set the transactions that this address is input
*/
public void setTransactionInput(List<GTxIO> transactionInput) {
this.mTransactionInput = transactionInput;
}
/**
* Find if this address is input of a transaction
* @param inputToFind The GTxIO to find
* @param param The network parameter of this address
* @return if this address belongs to the transaction
*/
public boolean hasTransactionInput(GTxIO inputToFind, NetworkParameters param) {
for (GTxIO input : mTransactionInput) {
if ((input.getTransaction().getTxid().equals(inputToFind.getTransaction().getTxid()))
&& (input.getAddress().getAddressString(param).equals(inputToFind.getAddress()
.getAddressString(param)))) {
return true;
}
}
return false;
}
/**
* Gets the list of transaction that this address is output
*/
public List<GTxIO> getTransactionOutput() {
return mTransactionOutput;
}
/**
* Find if this address is output of a transaction
* @param outputToFind The GTxIO to find
* @param param the network parameter of this address
* @return if this address belongs to the transaction
*/
public boolean hasTransactionOutput(GTxIO outputToFind, NetworkParameters param) {
for (GTxIO output : mTransactionOutput) {
if ((output.getTransaction().getTxid().equals(outputToFind.getTransaction().getTxid()))
&& (output.getAddress().getAddressString(param).equals(outputToFind.getAddress()
.getAddressString(param)))) {
return true;
}
}
return false;
}
/**
* Sets the list of transaction that this address is output
*/
public void setTransactionOutput(List<GTxIO> outputTransaction) {
this.mTransactionOutput = outputTransaction;
}
/**
* Get the amount of uncofirmed balance
*/
public long getUnconfirmedBalance() {
long answer = 0;
for (GTxIO input : mTransactionInput) {
if (input.getTransaction().getConfirm() < mAccount.getCryptoNet().getConfirmationsNeeded()) {
answer += input.getAmount();
}
}
for (GTxIO output : mTransactionOutput) {
if (output.getTransaction().getConfirm() < mAccount.getCryptoNet().getConfirmationsNeeded()) {
answer -= output.getAmount();
}
}
return answer;
}
/**
* Get the amount of confirmed balance
*/
public long getConfirmedBalance() {
long answer = 0;
for (GTxIO input : mTransactionInput) {
if (input.getTransaction().getConfirm() >= mAccount.getCryptoNet().getConfirmationsNeeded()) {
answer += input.getAmount();
}
}
for (GTxIO output : mTransactionOutput) {
if (output.getTransaction().getConfirm() >= mAccount.getCryptoNet().getConfirmationsNeeded()) {
answer -= output.getAmount();
}
}
return answer;
}
/**
* Get the date of the last transaction or null if there is no transaction
*/
public Date getLastDate() {
Date lastDate = null;
for (GTxIO input : mTransactionInput) {
if (lastDate == null || lastDate.before(input.getTransaction().getDate())) {
lastDate = input.getTransaction().getDate();
}
}
for (GTxIO output : mTransactionOutput) {
if (lastDate == null || lastDate.before(output.getTransaction().getDate())) {
lastDate = output.getTransaction().getDate();
}
}
return lastDate;
}
/**
* Get the amount of the less cofnirmed transaction, this is used to set how confirmations are
* left
*/
public int getLessConfirmed(){
int lessConfirm = -1;
for (GTxIO input : mTransactionInput) {
if (lessConfirm == -1 || input.getTransaction().getConfirm() < lessConfirm) {
lessConfirm = input.getTransaction().getConfirm();
}
}
for (GTxIO output : mTransactionOutput) {
if (lessConfirm == -1 || output.getTransaction().getConfirm() < lessConfirm) {
lessConfirm = output.getTransaction().getConfirm();
}
}
return lessConfirm;
}
/**
* Gets the unspend transactions input
* @return The list with the unspend transasctions
*/
public List<GTxIO> getUTXos(){
List<GTxIO> utxo = new ArrayList<>();
for(GTxIO gitx : mTransactionInput){
boolean find = false;
for(GTxIO gotx : mTransactionOutput){
if(gitx.getTransaction().getTxid().equals(gotx.getOriginalTxid())){
find = true;
break;
}
}
if(!find){
utxo.add(gitx);
}
}
return utxo;
}
/**
* Fire the onBalanceChange event
*/
public void BalanceChange() {
this.getAccount().balanceChange();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeneralCoinAddress that = (GeneralCoinAddress) o;
return mIsChange == that.mIsChange && mIndex == that.mIndex && mId == -1
&& (mAccount != null ? mAccount.equals(that.mAccount) : that.mAccount == null
&& (mKey != null ? mKey.equals(that.mKey) : that.mKey == null
&& (mTransactionInput != null ? mTransactionInput.equals(that.mTransactionInput)
: that.mTransactionInput == null && (mTransactionOutput != null
? mTransactionOutput.equals(that.mTransactionOutput)
: that.mTransactionOutput == null))));
}
@Override
public int hashCode() {
int result = (int) mId;
result = 31 * result + (mAccount != null ? mAccount.hashCode() : 0);
result = 31 * result + (mIsChange ? 1 : 0);
result = 31 * result + mIndex;
result = 31 * result + (mKey != null ? mKey.hashCode() : 0);
result = 31 * result + (mTransactionInput != null ? mTransactionInput.hashCode() : 0);
result = 31 * result + (mTransactionOutput != null ? mTransactionOutput.hashCode() : 0);
return result;
}
/**
* Update the transactions of this Address
* @param transaction The transaction to update
* @return true if this address has the transaction false otherwise
*/
public boolean updateTransaction(GeneralTransaction transaction){
for(GTxIO gitx : mTransactionInput){
if(gitx.getTransaction().equals(transaction)){
gitx.getTransaction().setConfirm(transaction.getConfirm());
gitx.getTransaction().setBlock(transaction.getBlock());
gitx.getTransaction().setBlockHeight(transaction.getBlockHeight());
gitx.getTransaction().setDate(transaction.getDate());
gitx.getTransaction().setMemo(transaction.getMemo());
return true;
}
}
for(GTxIO gotx : mTransactionOutput){
if(gotx.getTransaction().equals(transaction)){
gotx.getTransaction().setConfirm(transaction.getConfirm());
gotx.getTransaction().setBlock(transaction.getBlock());
gotx.getTransaction().setBlockHeight(transaction.getBlockHeight());
gotx.getTransaction().setDate(transaction.getDate());
gotx.getTransaction().setMemo(transaction.getMemo());
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,236 @@
package cy.agorise.crystalwallet.models;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import cy.agorise.crystalwallet.enums.CryptoCoin;
/**
* A General Coin Transaction, of Cryptocurrency like bitcoin
*
* Created by henry on 06/02/2017.
*/
public class GeneralTransaction {
/**
* The id on the database
*/
private long mId = -1;
/**
* The Tx id of this transaciton
*/
private String mTxId;
/**
* the type of crypto coin fo this transaction
*/
private CryptoCoin mType;
/**
* If this is confirmed, the block where it belongs, 0 means this hasn't be included in any block
*/
private long mBlock;
/**
* The amount of fee of this transaction
*/
private long mFee;
/**
* the number of confirmations of this transacion, 0 means it hasn't been included in any block
*/
private int mConfirm;
/**
* The date of this transaction first broadcast
*/
private Date mDate;
/**
* The height of this transaction on the block
*/
private int mBlockHeight;
/**
* The memo of this transaciton
*/
private String mMemo = null;
/**
* The account that this transaction belong as input or output.
*/
private GeneralCoinAccount mAccount;
/**
* The inputs of this transactions
*/
private List<GTxIO> mTxInputs = new ArrayList();
/**
* the outputs of this transasctions
*/
private List<GTxIO> mTxOutputs = new ArrayList();
/**
* empty constructor
*/
public GeneralTransaction() {
}
/**
* Constructor form the database
* @param id the id on the database
* @param txid the txid of this transaction
* @param type The cryptocoin type
* @param block The block where this transaction is, 0 means this hasn't be confirmed
* @param fee the fee of this transaction
* @param confirm the number of confirmations of this transasciton
* @param date the date of this transaction
* @param blockHeight the height on the block where this transasciton is
* @param memo the memo of this transaction
* @param account The account to this transaction belongs, as input or output
*/
public GeneralTransaction(long id, String txid, CryptoCoin type, long block, long fee, int confirm, Date date, int blockHeight, String memo, GeneralCoinAccount account) {
this.mId = id;
this.mTxId = txid;
this.mType = type;
this.mBlock = block;
this.mFee = fee;
this.mConfirm = confirm;
this.mDate = date;
this.mBlockHeight = blockHeight;
this.mMemo = memo;
this.mAccount = account;
}
public long getId() {
return mId;
}
public void setId(long id) {
this.mId = id;
}
public String getTxid() { return mTxId; }
public void setTxid(String txid) { this.mTxId = txid; }
public CryptoCoin getType() {
return mType;
}
public void setType(CryptoCoin type) {
this.mType = type;
}
public long getBlock() {
return mBlock;
}
public void setBlock(long block) {
this.mBlock = block;
}
public long getFee() {
return mFee;
}
public void setFee(long fee) {
this.mFee = fee;
}
public int getConfirm() {
return mConfirm;
}
public void setConfirm(int confirm) {
this.mConfirm = confirm;
}
public Date getDate() {
return mDate;
}
public void setDate(Date date) {
this.mDate = date;
}
public int getBlockHeight() {
return mBlockHeight;
}
public void setBlockHeight(int blockHeight) {
this.mBlockHeight = blockHeight;
}
public String getMemo() {
return mMemo;
}
public void setMemo(String memo) {
this.mMemo = memo;
}
public List<GTxIO> getTxInputs() {
return mTxInputs;
}
public void setTxInputs(List<GTxIO> txInputs) {
this.mTxInputs = txInputs;
}
public List<GTxIO> getTxOutputs() {
return mTxOutputs;
}
public void setTxOutputs(List<GTxIO> txOutputs) {
this.mTxOutputs = txOutputs;
}
public GeneralCoinAccount getAccount() {
return mAccount;
}
public void setAccount(GeneralCoinAccount account) {
this.mAccount = account;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeneralTransaction that = (GeneralTransaction) o;
if (mTxId != null ? !mTxId.equals(that.mTxId) : that.mTxId != null) return false;
return mType == that.mType;
}
@Override
public int hashCode() {
int result = mTxId != null ? mTxId.hashCode() : 0;
result = 31 * result + mType.hashCode();
return result;
}
/**
* Returns how this transaction changes the balance of the account
* @return The amount of balance this transasciton adds to the total balance of the account
*/
public double getAccountBalanceChange(){
double balance = 0;
boolean theresAccountInput = false;
for (GTxIO txInputs : this.getTxInputs()){
if (txInputs.isOut() && (txInputs.getAddress() != null)){
balance += -txInputs.getAmount();
theresAccountInput = true;
}
}
for (GTxIO txOutput : this.getTxOutputs()){
if (!txOutput.isOut() && (txOutput.getAddress() != null)){
balance += txOutput.getAmount();
}
}
if (theresAccountInput){
balance += -this.getFee();
}
return balance;
}
}