From 593b875f25203994ea2870b15cda3d481e494d95 Mon Sep 17 00:00:00 2001 From: Henry Varona Date: Sun, 16 Sep 2018 23:43:59 -0400 Subject: [PATCH] Added the insight apigenerator that handles all service calls to the servers, Added the GeneralAccountManager that handles all communication used on bitcoin likes crypto accounts Rearrenge the send to the manager Change estimatefee to work with the new Architecture Added request for send generalaccount transactions (bitcoin like) --- .../apigenerator/InsightApiGenerator.java | 56 +++++++ .../insightapi/GetEstimateFee.java | 39 +++-- .../manager/GeneralAccountManager.java | 145 ++++++++++++++++++ .../models/GeneralCoinAccount.java | 8 + .../GeneralAccountSendRequest.java | 85 ++++++++++ 5 files changed, 317 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java new file mode 100644 index 0000000..54fe88e --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java @@ -0,0 +1,56 @@ +package cy.agorise.crystalwallet.apigenerator; + +import android.content.Context; + +import java.util.HashMap; + +import cy.agorise.crystalwallet.apigenerator.insightapi.BroadcastTransaction; +import cy.agorise.crystalwallet.apigenerator.insightapi.GetEstimateFee; +import cy.agorise.crystalwallet.apigenerator.insightapi.GetTransactionByAddress; +import cy.agorise.crystalwallet.apigenerator.insightapi.GetTransactionData; +import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.network.CryptoNetManager; + +public class InsightApiGenerator { + + private static HashMap broadcaster = new HashMap(); + private static HashMap transactionGetters = new HashMap(); + private static HashMap transacitonFollowers = new HashMap(); + + public static void getTransactionFromAddress(CryptoNet cryptoNet, String address, + ApiRequest request, Context context, + boolean subscribe){ + if(!transactionGetters.containsKey(cryptoNet)){ + //TODO change this line + transactionGetters.put(cryptoNet,new GetTransactionByAddress(null,CryptoNetManager.getURL(cryptoNet),context)); + } + + } + + public static void followTransaction(CryptoNet cryptoNet, String txid, Context context){ + + } + + public static void broadcastTransaction(CryptoNet cryptoNet, String rawtx, ApiRequest request){ + if(!broadcaster.containsKey(cryptoNet)){ + //TODO change to multiple broadcast + broadcaster.put(cryptoNet,new BroadcastTransaction(rawtx,null, + CryptoNetManager.getURL(cryptoNet),null)); + broadcaster.get(cryptoNet).start(); + } + } + + public static void getEstimateFee(CryptoNet cryptoNet, final ApiRequest request){ + GetEstimateFee.getEstimateFee(CryptoNetManager.getURL(cryptoNet), new GetEstimateFee.estimateFeeListener() { + @Override + public void estimateFee(long value) { + request.listener.success(value,request.getId()); + } + + @Override + public void fail() { + request.listener.fail(request.getId()); + } + }); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetEstimateFee.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetEstimateFee.java index c175238..520594c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetEstimateFee.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetEstimateFee.java @@ -22,29 +22,36 @@ public abstract class GetEstimateFee { /** * The funciton to get the rate for the transaction be included in the next 2 blocks - * @param coin The coin to get the rate + * @param serverUrl The url of the insight server + * @param listener the listener to this answer */ - public static void getEstimateFee(final CryptoCoin coin, String serverUrl, final estimateFeeListener listener) { - InsightApiServiceGenerator serviceGenerator = new InsightApiServiceGenerator(serverUrl); - InsightApiService service = serviceGenerator.getService(InsightApiService.class); - Call call = service.estimateFee(InsightApiConstants.getPath(coin)); - final JsonObject answer = new JsonObject(); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - listener.estimateFee((long) (answer.get("answer").getAsDouble())); + public static void getEstimateFee(String serverUrl, final estimateFeeListener listener) { + try { + InsightApiServiceGenerator serviceGenerator = new InsightApiServiceGenerator(serverUrl); + InsightApiService service = serviceGenerator.getService(InsightApiService.class); + Call call = service.estimateFee(serverUrl); + final JsonObject answer = new JsonObject(); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + listener.estimateFee((long) (answer.get("answer").getAsDouble())); - } + } - @Override - public void onFailure(Call call, Throwable t) { - listener.estimateFee(-1); - } - }); + @Override + public void onFailure(Call call, Throwable t) { + listener.fail(); + listener.estimateFee(-1); + } + }); + }catch(Exception e){ + listener.fail(); + } } public static interface estimateFeeListener{ public void estimateFee(long value); + public void fail(); } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java new file mode 100644 index 0000000..1a4c6fa --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java @@ -0,0 +1,145 @@ +package cy.agorise.crystalwallet.manager; + +import android.content.Context; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutPoint; +import org.bitcoinj.crypto.ChildNumber; +import org.bitcoinj.crypto.HDKeyDerivation; +import org.bitcoinj.script.Script; + +import java.util.ArrayList; +import java.util.List; + +import cy.agorise.crystalwallet.apigenerator.ApiRequest; +import cy.agorise.crystalwallet.apigenerator.ApiRequestListener; +import cy.agorise.crystalwallet.apigenerator.InsightApiGenerator; +import cy.agorise.crystalwallet.apigenerator.insightapi.BroadcastTransaction; +import cy.agorise.crystalwallet.models.CryptoNetAccount; +import cy.agorise.crystalwallet.models.GTxIO; +import cy.agorise.crystalwallet.models.GeneralCoinAddress; +import cy.agorise.crystalwallet.models.GeneralTransaction; +import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequest; +import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequestsListener; +import cy.agorise.crystalwallet.requestmanagers.GeneralAccountSendRequest; +import cy.agorise.graphenej.Util; + +public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInfoRequestsListener { + + @Override + public void createAccountFromSeed(CryptoNetAccount account, ManagerRequest request, Context context) { + + } + + @Override + public void importAccountFromSeed(CryptoNetAccount account, Context context) { + + } + + @Override + public void loadAccountFromDB(CryptoNetAccount account, Context context) { + + } + + @Override + public void onNewRequest(CryptoNetInfoRequest request) { + + } + + public void send(final GeneralAccountSendRequest request){ + //TODO check server connection + //TODO validate to address + + InsightApiGenerator.getEstimateFee(request.getAccount().getCryptoNet(),new ApiRequest(1, new ApiRequestListener() { + @Override + public void success(Object answer, int idPetition) { + Transaction tx = new Transaction(request.getAccount().getNetworkParam()); + long currentAmount = 0; + long fee = -1; + long feeRate = (Long) answer; + fee = 226 * feeRate; + + List addresses = request.getAccount().getAddresses(); + List utxos = new ArrayList(); + for(GeneralCoinAddress address : addresses){ + List addrUtxos = address.getUTXos(); + for(GTxIO addrUtxo : addrUtxos){ + utxos.add(addrUtxo); + currentAmount += addrUtxo.getAmount(); + if(currentAmount >= request.getAmount()+ fee){ + break; + } + } + if(currentAmount >= request.getAmount() + fee){ + break; + } + } + + + if(currentAmount< request.getAmount() + fee){ + request.setStatus(GeneralAccountSendRequest.StatusCode.NO_BALANCE); + return; + } + + //String to an address + Address toAddr = Address.fromBase58(request.getAccount().getNetworkParam(), request.getToAccount()); + tx.addOutput(Coin.valueOf(request.getAmount()), toAddr); + + if(request.getMemo()!= null && !request.getMemo().isEmpty()){ + String memo = request.getMemo(); + if(request.getMemo().length()>40){ + memo = memo.substring(0,40); + } + byte[]scriptByte = new byte[memo.length()+2]; + scriptByte[0] = 0x6a; + scriptByte[1] = (byte) memo.length(); + System.arraycopy(memo.getBytes(),0,scriptByte,2,memo.length()); + Script memoScript = new Script(scriptByte); + tx.addOutput(Coin.valueOf(0),memoScript); + } + + //Change address + long remain = currentAmount - request.getAmount() - fee; + if( remain > 0 ) { + Address changeAddr = Address.fromBase58(request.getAccount().getNetworkParam(), request.getAccount().getNextChangeAddress()); + tx.addOutput(Coin.valueOf(remain), changeAddr); + } + + for(GTxIO utxo: utxos) { + Sha256Hash txHash = Sha256Hash.wrap(utxo.getTransaction().getTxid()); + Script script = new Script(Util.hexToBytes(utxo.getScriptHex())); + TransactionOutPoint outPoint = new TransactionOutPoint(request.getAccount().getNetworkParam(), utxo.getIndex(), txHash); + if(utxo.getAddress().getKey().isPubKeyOnly()){ + if(utxo.getAddress().isIsChange()){ + utxo.getAddress().setKey(HDKeyDerivation.deriveChildKey(request.getAccount().getChangeKey(), new ChildNumber(utxo.getAddress().getIndex(), false))); + }else{ + utxo.getAddress().setKey(HDKeyDerivation.deriveChildKey(request.getAccount().getExternalKey(), new ChildNumber(utxo.getAddress().getIndex(), false))); + } + } + tx.addSignedInput(outPoint, script, utxo.getAddress().getKey(), Transaction.SigHash.ALL, true); + } + + InsightApiGenerator.broadcastTransaction(request.getAccount().getCryptoNet(),Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() { + @Override + public void success(Object answer, int idPetition) { + request.setStatus(GeneralAccountSendRequest.StatusCode.SUCCEEDED); + } + + @Override + public void fail(int idPetition) { + request.setStatus(GeneralAccountSendRequest.StatusCode.PETITION_FAILED); + } + })); + } + + @Override + public void fail(int idPetition) { + request.setStatus(GeneralAccountSendRequest.StatusCode.NO_FEE); + + } + })); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/GeneralCoinAccount.java b/app/src/main/java/cy/agorise/crystalwallet/models/GeneralCoinAccount.java index 68f44f2..af756b7 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/GeneralCoinAccount.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/GeneralCoinAccount.java @@ -323,6 +323,14 @@ public abstract class GeneralCoinAccount extends CryptoNetAccount { public abstract List getBalance(); + public DeterministicKey getChangeKey() { + return mChangeKey; + } + + public DeterministicKey getExternalKey() { + return mExternalKey; + } + /** * Compare the transaction, to order it for the list of transaction */ diff --git a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java new file mode 100644 index 0000000..2922680 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/GeneralAccountSendRequest.java @@ -0,0 +1,85 @@ +package cy.agorise.crystalwallet.requestmanagers; + +import android.content.Context; + +import cy.agorise.crystalwallet.enums.CryptoCoin; +import cy.agorise.crystalwallet.models.GeneralCoinAccount; + +public class GeneralAccountSendRequest extends CryptoNetInfoRequest { + /** + * The status code of this request + */ + public enum StatusCode{ + NOT_STARTED, + SUCCEEDED, + NO_INTERNET, + NO_SERVER_CONNECTION, + BAD_TO_ADDRESS, + NO_FEE, + NO_BALANCE, + PETITION_FAILED + } + + // The app context + private Context mContext; + //The soruce Account + private GeneralCoinAccount mAccount; + // The destination account address + private String mToAccount; + // The amount of the transaction + private long mAmount; + // The memo, can be null + private String mMemo; + // The state of this request + private StatusCode status = StatusCode.NOT_STARTED; + + public GeneralAccountSendRequest(CryptoCoin coin, Context context, GeneralCoinAccount account, String toAccount, long amount, String memo) { + super(coin); + this.mContext = context; + this.mAccount = account; + this.mToAccount = toAccount; + this.mAmount = amount; + this.mMemo = memo; + } + + public GeneralAccountSendRequest(CryptoCoin coin, Context context, GeneralCoinAccount account, String toAccount, long amount) { + this(coin,context,account,toAccount,amount,null); + + } + + public Context getContext() { + return mContext; + } + + public GeneralCoinAccount getAccount() { + return mAccount; + } + + public String getToAccount() { + return mToAccount; + } + + public long getAmount() { + return mAmount; + } + + public String getMemo() { + return mMemo; + } + + public void validate(){ + if ((this.status != StatusCode.NOT_STARTED)){ + this._fireOnCarryOutEvent(); + } + } + + public void setStatus(StatusCode code){ + this.status = code; + this._fireOnCarryOutEvent(); + } + + public StatusCode getStatus() { + return status; + } + +}