This commit is contained in:
Javier Varona 2018-09-17 22:02:40 -04:00
commit e601b90a63
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,12 +22,14 @@ public abstract class GetEstimateFee {
/** /**
* The funciton to get the rate for the transaction be included in the next 2 blocks * 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) { public static void getEstimateFee(String serverUrl, final estimateFeeListener listener) {
try {
InsightApiServiceGenerator serviceGenerator = new InsightApiServiceGenerator(serverUrl); InsightApiServiceGenerator serviceGenerator = new InsightApiServiceGenerator(serverUrl);
InsightApiService service = serviceGenerator.getService(InsightApiService.class); InsightApiService service = serviceGenerator.getService(InsightApiService.class);
Call<JsonObject> call = service.estimateFee(InsightApiConstants.getPath(coin)); Call<JsonObject> call = service.estimateFee(serverUrl);
final JsonObject answer = new JsonObject(); final JsonObject answer = new JsonObject();
call.enqueue(new Callback<JsonObject>() { call.enqueue(new Callback<JsonObject>() {
@Override @Override
@ -38,13 +40,18 @@ public abstract class GetEstimateFee {
@Override @Override
public void onFailure(Call<JsonObject> call, Throwable t) { public void onFailure(Call<JsonObject> call, Throwable t) {
listener.fail();
listener.estimateFee(-1); listener.estimateFee(-1);
} }
}); });
}catch(Exception e){
listener.fail();
}
} }
public static interface estimateFeeListener{ public static interface estimateFeeListener{
public void estimateFee(long value); 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 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 * 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;
}
}