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)
This commit is contained in:
Henry Varona 2018-09-16 23:43:59 -04:00
parent 3fb3dc9cac
commit 593b875f25
5 changed files with 317 additions and 16 deletions

View file

@ -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<CryptoNet,BroadcastTransaction> broadcaster = new HashMap();
private static HashMap<CryptoNet,GetTransactionByAddress> transactionGetters = new HashMap();
private static HashMap<CryptoNet,GetTransactionData> 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());
}
});
}
}

View file

@ -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<JsonObject> call = service.estimateFee(InsightApiConstants.getPath(coin));
final JsonObject answer = new JsonObject();
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> 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<JsonObject> call = service.estimateFee(serverUrl);
final JsonObject answer = new JsonObject();
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
listener.estimateFee((long) (answer.get("answer").getAsDouble()));
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
listener.estimateFee(-1);
}
});
@Override
public void onFailure(Call<JsonObject> 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();
}
}

View file

@ -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<GeneralCoinAddress> addresses = request.getAccount().getAddresses();
List<GTxIO> utxos = new ArrayList();
for(GeneralCoinAddress address : addresses){
List<GTxIO> 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);
}
}));
}
}

View file

@ -323,6 +323,14 @@ public abstract class GeneralCoinAccount extends CryptoNetAccount {
public abstract List<CryptoNetBalance> getBalance();
public DeterministicKey getChangeKey() {
return mChangeKey;
}
public DeterministicKey getExternalKey() {
return mExternalKey;
}
/**
* Compare the transaction, to order it for the list of transaction
*/

View file

@ -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;
}
}