From de2391c6f8accc42da451483e0783e83ff06e435 Mon Sep 17 00:00:00 2001 From: Javier Varona Date: Sat, 15 Sep 2018 21:25:41 -0400 Subject: [PATCH 1/3] - Put the loading dialog code of the backup restore before the import request is added - Fixing an error triggered when the import seed form is filled too fast. The bitshare name request of the account gets a null listener and makes the app to crash --- .../fragments/ImportAccountOptionsFragment.java | 8 ++++---- .../requestmanagers/CryptoNetInfoRequest.java | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java index 6d3fe60..259e0ad 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java @@ -196,15 +196,15 @@ public class ImportAccountOptionsFragment extends DialogFragment { } }); - FileServiceRequests.getInstance().addRequest(importBackupRequest); - /* - * Show loading dialog - * */ + * Show loading dialog + * */ crystalDialog = new CrystalDialog((Activity) getContext()); crystalDialog.setText(getContext().getString(R.string.Creating_backup_from_file)); crystalDialog.progress(); crystalDialog.show(); + + FileServiceRequests.getInstance().addRequest(importBackupRequest); } }) .setNegativeButton("Cancel", diff --git a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java index bdeecbc..f45a5b1 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java +++ b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java @@ -37,7 +37,9 @@ public abstract class CryptoNetInfoRequest { } protected void _fireOnCarryOutEvent(){ - listener.onCarryOut(); + if (listener != null) { + listener.onCarryOut(); + } CryptoNetInfoRequests.getInstance().removeRequest(this); } From 593b875f25203994ea2870b15cda3d481e494d95 Mon Sep 17 00:00:00 2001 From: Henry Varona Date: Sun, 16 Sep 2018 23:43:59 -0400 Subject: [PATCH 2/3] 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; + } + +} From 35f273de23502400ef4560ff79bb2b192cfd8357 Mon Sep 17 00:00:00 2001 From: Javier Varona Date: Mon, 17 Sep 2018 22:02:21 -0400 Subject: [PATCH 3/3] - Fixed Transaction List not updating when it should - Now the transaction list searches for bitshares account names too --- .../java/cy/agorise/crystalwallet/dao/TransactionDao.java | 2 +- .../crystalwallet/fragments/TransactionsFragment.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java index 6e10049..3ba5a9e 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java @@ -24,7 +24,7 @@ public interface TransactionDao { "LEFT JOIN crypto_net_account cna ON cct.account_id = cna.id " + "LEFT JOIN contact c ON c.id = (SELECT ca.contact_id FROM contact_address ca WHERE ca.address LIKE (CASE is_input WHEN 1 THEN cct.\"from\" ELSE cct.\"to\" END) LIMIT 1) " + "LEFT JOIN bitshares_account_name_cache banc ON banc.account_id = (CASE is_input WHEN 1 THEN cct.\"from\" ELSE cct.\"to\" END) " + - "WHERE user_account_name LIKE '%'||:search||'%' OR contact_name LIKE '%'||:search||'%' OR cct.\"from\" LIKE '%'||:search||'%' OR cct.\"to\" LIKE '%'||:search||'%'"; + "WHERE user_account_name LIKE '%'||:search||'%' OR contact_name LIKE '%'||:search||'%' OR cct.\"from\" LIKE '%'||:search||'%' OR cct.\"to\" LIKE '%'||:search||'%' OR banc.name LIKE '%'||:search||'%'"; @Query("SELECT * FROM crypto_coin_transaction") LiveData> getAll(); diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/TransactionsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/TransactionsFragment.java index 83ce128..6e61b70 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/TransactionsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/TransactionsFragment.java @@ -10,6 +10,7 @@ import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.text.Editable; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -112,9 +113,9 @@ public class TransactionsFragment extends Fragment { TransactionOrderSpinnerAdapter.TransactionOrderSpinnerItem orderSelected = (TransactionOrderSpinnerAdapter.TransactionOrderSpinnerItem)(spTransactionsOrder.getSelectedItem()); - if (transactionsLiveData != null){ - transactionsLiveData.removeObservers(this); - } + //if (transactionsLiveData != null){ + //transactionsLiveData.removeObservers(this); + //} transactionListViewModel.initTransactionList(orderSelected.getField(),etTransactionSearch.getText().toString()); transactionsLiveData = transactionListViewModel.getTransactionList(); @@ -122,6 +123,7 @@ public class TransactionsFragment extends Fragment { transactionsLiveData.observe(this, new Observer>() { @Override public void onChanged(@Nullable PagedList cryptoCoinTransactions) { + Log.i("Transactions","Transactions have change! Count:"+cryptoCoinTransactions.size()); transactionListView.setData(cryptoCoinTransactions, fragment); } });