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:
parent
3fb3dc9cac
commit
593b875f25
5 changed files with 317 additions and 16 deletions
|
@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue