diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..681f41ae2a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java index 680c112119..b8b581bd36 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -2,7 +2,18 @@ package com.tangem.wallet.ltc; import android.net.Uri; import android.text.InputFilter; +import android.util.Log; +import com.tangem.App; +import com.tangem.data.local.PendingTransactionsStorage; +import com.tangem.data.network.Server; +import com.tangem.data.network.ServerApiBlockcypher; +import com.tangem.data.network.ServerApiSoChain; +import com.tangem.data.network.model.BlockcypherFee; +import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherTx; +import com.tangem.data.network.model.BlockcypherTxref; +import com.tangem.data.network.model.SoChain; import com.tangem.wallet.BTCUtils; import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.Base58; @@ -21,6 +32,8 @@ import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.wallet.R; +import org.json.JSONException; + import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; @@ -478,6 +491,281 @@ public class LtcEngine extends BtcEngine { }; } + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + ctx.setError(null); + + if (!coinData.isUseBlockcypher()) { + final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + + ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() { + @Override + public void onSuccess(SoChain.Response.AddressBalance response) { + try { + String walletAddress = response.getData().getAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact(); + Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact(); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(Server.ApiSoChain.URL); + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); + ctx.setError("FAIL METHOD_GetBalance JSONException"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance Exception"); + ctx.setError("FAIL METHOD_GetBalance Exception"); + } + if (serverApiSoChain.isRequestsSequenceCompleted()) { + if (!coinData.isUseBlockcypher()) { + checkPending(blockchainRequestsCallbacks); + } else { + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + + } + + @Override + public void onSuccess(SoChain.Response.TxUnspent response) { + String walletAddress = response.getData().getAddress(); + try { + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + coinData.getUnspentTransactions().clear(); + if (response.getData().getTxs() != null) + for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = tx.getTxid(); + trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact(); + trUnspent.outputN = tx.getOutput_no(); + trUnspent.script = tx.getScript_hex(); + coinData.getUnspentTransactions().add(trUnspent); + +// if (blockchainRequestsCallbacks.allowAdvance()) { +// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); +// } else { +// ctx.setError("Terminated by user"); +// } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } + + if (serverApiSoChain.isRequestsSequenceCompleted()) { + if (!coinData.isUseBlockcypher()) { + checkPending(blockchainRequestsCallbacks); + } else { + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: " + message); + coinData.setUseBlockcypher(true); +// ctx.setError(R.string.cannot_obtain_data_from_blockchain); + if (serverApiSoChain.isRequestsSequenceCompleted()) { +// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + + serverApiSoChain.setAddressInfoListener(addressInfoListener); + + serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); + serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { + @Override + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + Log.i(TAG, "onSuccess: " + method); + try { + String walletAddress = blockcypherResponse.getAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = blockcypherResponse.getBalance(); + Long unconfirmedBalance = blockcypherResponse.getUnconfirmed_balance(); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(Server.ApiBlockcypher.URL_BLOCKCYPHER); + + coinData.getUnspentTransactions().clear(); + if (blockcypherResponse.getTxrefs() != null) { + for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = txref.getTx_hash(); + trUnspent.amount = txref.getValue(); + trUnspent.outputN = txref.getTx_output_n(); + trUnspent.script = txref.getScript(); + coinData.getUnspentTransactions().add(trUnspent); + } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); + } + + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + checkPending(blockchainRequestsCallbacks); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions"); + ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions"); + blockchainRequestsCallbacks.onComplete(false); + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + + serverApiBlockcypher.setResponseListener(blockcypherListener); + + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); + } + } + + private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { + + if (!coinData.isUseBlockcypher()) { + ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + + ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() { + @Override + public void onSuccess(SoChain.Response.GetTx response) { + Log.i(TAG, "onSuccess: GetTx"); + try { + if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex()); + } + } catch (Exception e) { + Log.e(TAG, "onFail: GetTx" + response); + } + if (serverApiSoChain.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: GetTx " + message); + if (serverApiSoChain.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApiSoChain.setTransactionInfoListener(listener); + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + try { + serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + } else { + ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() { + + @Override + public void onSuccess(BlockcypherTx response) { + Log.i(TAG, "onSuccess: BlockcypherTx"); + try { + if (response.getHex() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex()); + } + } catch (Exception e) { + Log.e(TAG, "onFail: BlockcypherTx" + response); + } + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: BlockcypherTx " + message); + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + serverApiBlockcypher.setTxResponseListener(listener); + + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + try { + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + } + } else { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + private final static BigDecimal relayFee = new BigDecimal(0.00001).setScale(8, RoundingMode.DOWN); @Override @@ -486,6 +774,92 @@ public class LtcEngine extends BtcEngine { blockchainRequestsCallbacks.onComplete(true); } + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { + final String txStr = BTCUtils.toHex(txForSend); + if (!coinData.isUseBlockcypher()) { + final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + + ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() { + @Override + public void onSuccess(SoChain.Response.SendTx response) { + try { + if (response.getStatus() == null || response.getData() == null) { + ctx.setError("Rejected by node: invalid answer received"); + blockchainRequestsCallbacks.onComplete(false); + } else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) { + ctx.setError("Rejected by node: " + response.getData()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + } + } + } + + @Override + public void onFail(String message) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiSoChain.setSendTxListener(listener); + serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr); + + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { + @Override + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + String resultString = blockcypherResponse.toString(); + try { + if (resultString.isEmpty()) { + ctx.setError("No response from node"); + blockchainRequestsCallbacks.onComplete(false); + } else { // TODO: Make check for a valid send response + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + Log.e(TAG, resultString); + } + } + } + + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestSendTransaction"); + ctx.setError("Wrong response type for requestSendTransaction"); + blockchainRequestsCallbacks.onComplete(false); + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiBlockcypher.setResponseListener(blockcypherListener); + + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr); + } + } + @Override public boolean allowSelectFeeLevel() { return false; diff --git a/build.gradle b/build.gradle index 58b21916a3..8820962821 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 662c6f9706..212388dbad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Apr 18 12:21:12 MSK 2019 +#Wed Aug 21 11:57:23 MSK 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip