diff --git a/app/src/main/java/com/tangem/data/network/ElectrumRequest.java b/app/src/main/java/com/tangem/data/network/ElectrumRequest.java index 7fc556cf62..adcb1bac52 100644 --- a/app/src/main/java/com/tangem/data/network/ElectrumRequest.java +++ b/app/src/main/java/com/tangem/data/network/ElectrumRequest.java @@ -17,14 +17,14 @@ public class ElectrumRequest { public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast"; public static final String METHOD_GetFee = "blockchain.estimatefee"; - public JSONObject jsRequestData; - public String answerData; - public String error; - public String walletAddress; + private JSONObject jsRequestData; + String answerData; + private String error = null; + private String walletAddress; public String txHash; - public String TX; - public String host; - public int port; + private String TX; + String host; + int port; private ElectrumRequest() { } @@ -155,15 +155,30 @@ public class ElectrumRequest { return getAnswer().getJSONObject("result"); } - public JSONObject getError() throws JSONException { - JSONObject answer = getAnswer(); - if (answer.has("error")) { - return getAnswer().getJSONObject("error"); - } else { - return null; + public String getError() { + if( answerData!=null ) { + // answer received - return error from it + JSONObject answer = getAnswer(); + if (answer.has("error")) { + try { + return getAnswer().getJSONObject("error").toString(); + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } else { + return null; + } + }else{ + // no answer received - return saved error reason + return error; } } + public void setError(String error) { + this.error = error; + } + public String getResultString() throws JSONException { if (getAnswer().has("result")) { return getAnswer().getString("result"); diff --git a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java index cc0fc7edcc..7ab8fe42e0 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java @@ -28,7 +28,7 @@ public class ServerApiCommon { public interface EstimateFeeListener { void onSuccess(int blockCount, String estimateFeeResponse); - void onFail(String message); + void onFail(int blockCount, String message); } public void setEstimateFee(EstimateFeeListener listener) { @@ -63,13 +63,13 @@ public class ServerApiCommon { estimateFeeListener.onSuccess(blockCount, response.body()); Log.i(TAG, "estimateFee onResponse " + response.code() + " " + response.body()); } else - estimateFeeListener.onFail(response.body()); + estimateFeeListener.onFail(blockCount, response.body()); Log.e(TAG, "estimateFee onResponse " + response.code()); } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - estimateFeeListener.onFail(t.getMessage()); + estimateFeeListener.onFail(blockCount, t.getMessage()); Log.e(TAG, "estimateFee onFailure " + t.getMessage()); } }); diff --git a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java index 19b0404fed..87c4cedb6c 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java @@ -8,6 +8,7 @@ import com.tangem.data.Blockchain; import com.tangem.domain.wallet.bch.BitcoinCashNode; import com.tangem.domain.wallet.btc.BitcoinNode; import com.tangem.domain.wallet.btc.BitcoinNodeTestNet; +import com.tangem.wallet.R; import java.io.BufferedReader; import java.io.IOException; @@ -54,23 +55,15 @@ public class ServerApiElectrum { private int requestsCount=0; - public boolean hasRequests() { - return requestsCount>0; - } - - private String error=null; - public boolean isErrorOccured() { - return error!=null; - } - - public void setErrorOccured(String error) { - this.error=error; + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; } public interface ElectrumRequestDataListener { void onSuccess(ElectrumRequest electrumRequest); - void onFail(String method); + void onFail(ElectrumRequest electrumRequest); } public void setElectrumRequestData(ElectrumRequestDataListener listener) { @@ -79,6 +72,7 @@ public class ServerApiElectrum { public void electrumRequestData(TangemContext ctx, ElectrumRequest electrumRequest) { requestsCount++; + Log.i(TAG, String.format("New request[%d]: %s", requestsCount,electrumRequest.getMethod())); Observable checkElectrumDataObserver = Observable.just(electrumRequest) .doOnNext(electrumRequest1 -> doElectrumRequest(ctx, electrumRequest)) @@ -97,33 +91,47 @@ public class ServerApiElectrum { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); checkElectrumDataObserver.subscribe(new DefaultObserver() { + //TODO remove onNext @Override public void onNext(ElectrumRequest v) { if (electrumRequest.answerData != null) { - requestsCount--; - electrumRequestDataListener.onSuccess(electrumRequest); -// Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null"); + Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null"); } else { - electrumRequestDataListener.onFail(electrumRequest.getMethod()); Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext == null"); } } @Override public void onError(Throwable e) { - electrumRequestDataListener.onFail(electrumRequest.getMethod()); + requestsCount--; Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onError " + e.getMessage()); + Log.e(TAG, String.format("%d requests left in processing",requestsCount)); + electrumRequest.setError(ctx.getString(R.string.cannot_obtain_data_from_blockchain)); + //setErrorOccurred(e.getMessage());//; + electrumRequestDataListener.onFail(electrumRequest); } @Override public void onComplete() { -// Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete"); + requestsCount--; + if (electrumRequest.answerData != null) { + Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData!=null"); + } else { + Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData==null"); + } + Log.e(TAG, String.format("%d requests left in processing",requestsCount)); + if (electrumRequest.answerData != null) { + electrumRequestDataListener.onSuccess(electrumRequest); + } else { +// if( error==null || error.isEmpty() ) setErrorOccurred(ctx.getString(R.string.cannot_obtain_data_from_blockchain)); + electrumRequestDataListener.onFail(electrumRequest); + } } }); } - private List doElectrumRequest(TangemContext ctx, ElectrumRequest electrumRequest) { + private void doElectrumRequest(TangemContext ctx, ElectrumRequest electrumRequest) { String host; int port; String proto; @@ -135,8 +143,7 @@ public class ServerApiElectrum { this.host = host; this.port = port; - return doElectrumRequestTcp(electrumRequest, host, port); - + doElectrumRequestTcp(electrumRequest, host, port); } else if (ctx.getBlockchain() == Blockchain.BitcoinCash) { BitcoinCashNode bitcoinCashNode = BitcoinCashNode.values()[new Random().nextInt(BitcoinCashNode.values().length)]; host = bitcoinCashNode.getHost(); @@ -147,9 +154,9 @@ public class ServerApiElectrum { this.port = port; if (proto.equals("tcp")) { - return doElectrumRequestTcp(electrumRequest, host, port); + doElectrumRequestTcp(electrumRequest, host, port); } else { - return doElectrumRequestSsl(electrumRequest, host, port); + doElectrumRequestSsl(electrumRequest, host, port); } } else if (ctx.getBlockchain() == Blockchain.Bitcoin) { @@ -162,23 +169,19 @@ public class ServerApiElectrum { this.port = port; if (proto.equals("tcp")) { - return doElectrumRequestTcp(electrumRequest, host, port); + doElectrumRequestTcp(electrumRequest, host, port); } else { - return doElectrumRequestSsl(electrumRequest, host, port); + doElectrumRequestSsl(electrumRequest, host, port); } } - return null; } - private List doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) { - List result = new ArrayList<>(); - Collections.addAll(result, electrumRequest); - + private void doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) { try { Socket socket = App.getNetworkComponent().getSocket(); socket.setSoTimeout(3000); + Log.i(TAG, "Start process "+electrumRequest.getMethod()+" @ "+host + ":" + port); socket.connect(new InetSocketAddress(InetAddress.getByName(host), port)); - Log.i(TAG, host + " " + port); try { OutputStream os = socket.getOutputStream(); OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8"); @@ -195,37 +198,38 @@ public class ServerApiElectrum { if (electrumRequest.answerData != null) { Log.i(TAG, ">> " + electrumRequest.answerData); } else { - electrumRequest.error = "No answer from server"; + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_answer)); Log.i(TAG, ">> "); } } catch (ConnectException e) { - e.printStackTrace(); - electrumRequestDataListener.onFail(e.getMessage()); - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); + //e.printStackTrace(); + //electrumRequestDataListener.onFail(e.getMessage()); + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection)); + Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); } finally { - Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " CLOSE"); + Log.i(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " socket.close"); socket.close(); } } catch (IOException e) { - e.printStackTrace(); - electrumRequestDataListener.onFail(e.getMessage()); - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " IOException " + e.getMessage()); + //e.printStackTrace(); + //electrumRequestDataListener.onFail(e.getMessage()); + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error)); + Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage()); } - return result; } - private List doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) { + private void doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) { try { // create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType) { } @@ -253,8 +257,8 @@ public class ServerApiElectrum { Collections.addAll(result, electrumRequest); try { - sslSocket = (SSLSocket) sf.createSocket(host, port); Log.i(TAG, host + " " + port); + sslSocket = (SSLSocket) sf.createSocket(host, port); try { OutputStream os = sslSocket.getOutputStream(); OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8"); @@ -269,30 +273,28 @@ public class ServerApiElectrum { if (electrumRequest.answerData != null) { Log.i(TAG, ">> " + electrumRequest.answerData); } else { - electrumRequest.error = "No answer from server"; + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_answer)); Log.i(TAG, ">> "); } } catch (ConnectException e) { e.printStackTrace(); - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection)); + Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); } finally { - Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " CLOSE"); + Log.i(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " socket.close"); sslSocket.close(); } } catch (IOException e) { e.printStackTrace(); - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " IOException " + e.getMessage()); + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error)); + Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage()); } - - return result; - } catch (NoSuchAlgorithmException | KeyManagementException e) { + electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain)); Log.e(TAG, e.getMessage()); } - - return null; } public String getValidationNodeDescription() { diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java index c6d33242ca..34b2f33f3f 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java @@ -33,17 +33,9 @@ public class ServerApiInfura { private int requestsCount=0; - public boolean hasRequests() { - return requestsCount>0; - } - - private String error=null; - public boolean isErrorOccured() { - return error!=null; - } - - public void setErrorOccured(String error) { - this.error=error; + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; } private InfuraBodyListener infuraBodyListener; diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinData.java b/app/src/main/java/com/tangem/domain/wallet/CoinData.java index 1fe6c38df8..9447ee6935 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinData.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinData.java @@ -47,8 +47,8 @@ public abstract class CoinData { validationNodeDescription = B.getString("validationNodeDescription"); - if (B.containsKey("FailedBalance")) - failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance")); +// if (B.containsKey("FailedBalance")) +// failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance")); if (B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual")); @@ -64,8 +64,8 @@ public abstract class CoinData { if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual); - if (failedBalanceRequestCounter != null) - B.putInt("FailedBalance", failedBalanceRequestCounter.get()); +// if (failedBalanceRequestCounter != null) +// B.putInt("FailedBalance", failedBalanceRequestCounter.get()); B.putFloat("rate", rate); B.putFloat("rateAlter", rateAlter); @@ -143,25 +143,32 @@ public abstract class CoinData { public void clearInfo() { setIsBalanceEqual(false); + setBalanceReceived(false); // TODO check + setValidationNodeDescription(""); + minFee=null; + maxFee=null; + normalFee=null; + rate=0f; + rateAlter=0f; } - private AtomicInteger failedBalanceRequestCounter; - - public int incFailedBalanceRequestCounter() { - if (failedBalanceRequestCounter == null) - failedBalanceRequestCounter = new AtomicInteger(0); - return failedBalanceRequestCounter.incrementAndGet(); - } - - public void resetFailedBalanceRequestCounter() { - failedBalanceRequestCounter = new AtomicInteger(0); - } - - public int getFailedBalanceRequestCounter() { - if (failedBalanceRequestCounter == null) - return 0; - return failedBalanceRequestCounter.get(); - } +// private AtomicInteger failedBalanceRequestCounter; +// +// public int incFailedBalanceRequestCounter() { +// if (failedBalanceRequestCounter == null) +// failedBalanceRequestCounter = new AtomicInteger(0); +// return failedBalanceRequestCounter.incrementAndGet(); +// } +// +// public void resetFailedBalanceRequestCounter() { +// failedBalanceRequestCounter = new AtomicInteger(0); +// } +// +// public int getFailedBalanceRequestCounter() { +// if (failedBalanceRequestCounter == null) +// return 0; +// return failedBalanceRequestCounter.get(); +// } private Boolean balanceEqual; @@ -183,7 +190,7 @@ public abstract class CoinData { this.validationNodeDescription = validationNodeDescription; } - - - + public CoinEngine.Amount minFee = null; + public CoinEngine.Amount normalFee = null; + public CoinEngine.Amount maxFee = null; } diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java index 2ad484f4c8..229593fe4c 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java @@ -252,7 +252,7 @@ public abstract class CoinEngine { { void onPaymentPrepared(byte[] txForSend); } - private OnNeedSendPayment onNeedSendPayment; + protected OnNeedSendPayment onNeedSendPayment; public void setOnNeedSendPayment(OnNeedSendPayment onNeedSendPayment) { this.onNeedSendPayment = onNeedSendPayment; @@ -262,22 +262,34 @@ public abstract class CoinEngine { if(onNeedSendPayment==null) throw new Exception("Payment signed but no callback defined to send!"); onNeedSendPayment.onPaymentPrepared(txForSend); - } - public interface BalanceAndUnspentTransactionsNotifications + public interface BlockchainRequestsCallbacks { + /** + * Notification that the all requests in sequence completed + * Call after a last request completed + * If occurred error return in ctx.error + * @param success -* + */ void onComplete(Boolean success); - boolean needTerminate(); + + /** + * Notification that a new part of data received and it's possible to update view + * May call when some request in the sequence completed but there are still a few requests left + */ + void onProgress(); + + /** + * Return flag that allow to add new or re-requests in the sequence + * Call between requests or when request fail and before re-request + * @return true if not need terminate (e.g. activity is online) + */ + boolean allowAdvance(); } - public abstract void requestBalanceAndUnspentTransactions(BalanceAndUnspentTransactionsNotifications balanceAndUnspentTransactionsNotifications) throws Exception; + public abstract void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception; + public abstract void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception; - public interface FeeRequestsNotifications - { - void onComplete(boolean success, Amount minFee, Amount normalFee, Amount maxFee); - boolean needTerminate(); - } - public abstract void requestFee(FeeRequestsNotifications feeRequestsNotifications, CoinEngine.Amount amount) throws Exception; - + public abstract void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/TangemContext.java b/app/src/main/java/com/tangem/domain/wallet/TangemContext.java index 8f1c9b06a3..5802887489 100644 --- a/app/src/main/java/com/tangem/domain/wallet/TangemContext.java +++ b/app/src/main/java/com/tangem/domain/wallet/TangemContext.java @@ -85,6 +85,10 @@ public class TangemContext { return error; } + public boolean hasError() { + return error!=null && !error.isEmpty(); + } + public void setMessage(String value) { this.message = value; } diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java index 82656791fd..152cca93e5 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java @@ -5,8 +5,10 @@ import android.text.InputFilter; import android.util.Log; import com.tangem.data.network.ElectrumRequest; +import com.tangem.data.network.ServerApiCommon; import com.tangem.data.network.ServerApiElectrum; import com.tangem.domain.wallet.BCHUtils; +import com.tangem.domain.wallet.BTCUtils; import com.tangem.tangemcard.reader.CardProtocol; import com.tangem.domain.wallet.BalanceValidator; import com.tangem.data.Blockchain; @@ -22,6 +24,7 @@ import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.tangemcard.util.Util; +import com.tangem.util.FormatUtil; import com.tangem.wallet.R; import org.json.JSONArray; @@ -31,6 +34,7 @@ import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; +import java.math.RoundingMode; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; @@ -538,7 +542,7 @@ public class BtcCashEngine extends CoinEngine { } @Override - public void onSignCompleted(byte[] signFromCard) throws Exception { + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { for (int i = 0; i < unspentOutputs.size(); ++i) { BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); @@ -550,12 +554,13 @@ public class BtcCashEngine extends CoinEngine { byte[] txForSend = BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal); notifyOnNeedSendPayment(txForSend); + return txForSend; } }; } @Override - public void requestBalanceAndUnspentTransactions(BalanceAndUnspentTransactionsNotifications balanceAndUnspentTransactionsNotifications) throws Exception { + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() { @@ -607,10 +612,10 @@ public class BtcCashEngine extends CoinEngine { Integer height = jsUnspent.getInt("height"); String hash = jsUnspent.getString("tx_hash"); if (height != -1) { - if (!balanceAndUnspentTransactionsNotifications.needTerminate()) { + if (blockchainRequestsCallbacks.allowAdvance()) { serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); } else { - serverApiElectrum.setErrorOccured("Terminated by user"); + ctx.setError("Terminated by user"); } } } @@ -632,15 +637,21 @@ public class BtcCashEngine extends CoinEngine { } } - if (!serverApiElectrum.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiElectrum.isErrorOccured()); + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); } } @Override - public void onFail(String method) { - if (!serverApiElectrum.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiElectrum.isErrorOccured()); + public void onFail(ElectrumRequest electrumRequest) { + Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError()); + ctx.setError(electrumRequest.getError()); + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + }else{ + blockchainRequestsCallbacks.onProgress(); } } }; @@ -651,4 +662,183 @@ public class BtcCashEngine extends CoinEngine { serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(convertToLegacyAddress(coinData.getWallet()))); } + private Integer buildSize(String outputAddress, String outFee, String outAmount) { + //todo - проверить, правильней было бы использовать constructPayment + try { + String myAddress = coinData.getWallet(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + byte[] pbComprKey = ctx.getCard().getWalletPublicKeyRar(); + + // build script for our address + List rawTxList = coinData.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // collect unspent + ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + Long fullAmount = 0L; + for (int i = 0; i < unspentOutputs.size(); i++) { + fullAmount += unspentOutputs.get(i).value; + } + + // get first unspent +// val outPut = unspentOutputs[0] +// val outPutIndex = outPut.outputIndex + + // get prev TX id; +// val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2"; + + Long fees = FormatUtil.ConvertStringToLong(outFee); + Long amount = FormatUtil.ConvertStringToLong(outAmount); + amount -= fees; + + Long change = fullAmount - fees - amount; + + if (amount + fees > fullAmount) { + throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); + } + + byte[][] hashesForSign = new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); i++) { + byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change); + byte[] hashData = Util.calculateSHA256(newTX); + byte[] doubleHashData = Util.calculateSHA256(hashData); +// Log.e("TX_BODY_1", BTCUtils.toHex(newTX)) +// Log.e("TX_HASH_1", BTCUtils.toHex(hashData)) +// Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData)) + +// unspentOutputs[i].bodyDoubleHash = doubleHashData +// unspentOutputs[i].bodyHash = hashData + hashesForSign[i] = doubleHashData; + } + + byte[] signFromCard = new byte[64 * unspentOutputs.size()]; + + for (int i = 0; i < unspentOutputs.size(); i++) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); + unspentOutputs.get(i).scriptForBuild = encodingSign; + } + + byte[] realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change); + + return realTX.length; + } + catch (Exception e) + { + e.printStackTrace(); + Log.e(TAG, "Can't calculate transaction size -> use default!"); + return 256; + } + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { + final int calcSize = buildSize(targetAddress, "0.00", amount.toValueString()); + coinData.minFee=null; + coinData.maxFee=null; + coinData.normalFee=null; + + final ServerApiCommon serverApiCommon = new ServerApiCommon(); + + final ServerApiCommon.EstimateFeeListener estimateFeeListener = new ServerApiCommon.EstimateFeeListener() { + @Override + public void onSuccess(int blockCount, String estimateFeeResponse) { + BigDecimal fee = new BigDecimal(estimateFeeResponse); // BTC per 1 kb + + if (fee.equals(BigDecimal.ZERO)) { + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiCommon.estimateFee(blockCount); + } + return; + } + + if (calcSize != 0) { + fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // per Kb -> per byte + } else { + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiCommon.estimateFee(blockCount); + } + return; + } + + fee = fee.setScale(8, RoundingMode.DOWN); + + switch (blockCount) { + case ServerApiCommon.ESTIMATE_FEE_MINIMAL: + coinData.minFee = new CoinEngine.Amount(fee, getFeeCurrency()); + break; + case ServerApiCommon.ESTIMATE_FEE_NORMAL: + coinData.normalFee = new CoinEngine.Amount(fee, getFeeCurrency()); + break; + case ServerApiCommon.ESTIMATE_FEE_PRIORITY: + coinData.maxFee = new CoinEngine.Amount(fee, getFeeCurrency()); + break; + } + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void onFail(int blockCount, String message) { + // TODO - add fail counter to terminate after NNN tries + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiCommon.estimateFee(blockCount); + } + ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiCommon.setEstimateFee(estimateFeeListener); + + serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY); + serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_NORMAL); + serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL); + + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + final String txStr = BTCUtils.toHex(txForSend); + + ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() { + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { + try { + String resultString = electrumRequest.getResultString(); + if (resultString == null || resultString.isEmpty()) { + ctx.setError("Rejected by node: " + electrumRequest.getError()); + 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(ElectrumRequest electrumRequest) { + ctx.setError(electrumRequest.getError()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiElectrum.setElectrumRequestData(electrumBodyListener); + + + serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + + } + } diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java index 53d97fe589..01a8818e17 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java @@ -21,7 +21,6 @@ import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.tangemcard.util.Util; -import com.tangem.util.FormatUtil; import com.tangem.wallet.R; import com.tangem.data.network.ElectrumRequest; import com.tangem.data.network.ServerApiElectrum; @@ -248,14 +247,15 @@ public class BtcEngine extends CoinEngine { @Override public boolean validateBalance(BalanceValidator balanceValidator) { - if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { - balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); - return false; - } + try { + if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Unknown balance"); + balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + return false; + } - // Workaround before new back-end + // Workaround before new back-end // if (card.getRemainingSignatures() == card.getMaxSignatures()) { // firstLine = "Verified balance"; // secondLine = "Balance confirmed in blockchain. "; @@ -263,24 +263,24 @@ public class BtcEngine extends CoinEngine { // return; // } - if (coinData.getBalanceUnconfirmed() != 0) { - balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); - return false; - } - - if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { - balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); - if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + if (coinData.getBalanceUnconfirmed() != 0) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Transaction in progress"); + balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + return false; } - } - // rule 4 TODO: need to check SignedHashed against number of outputs in blockchain + if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine("Verified balance"); + balanceValidator.setSecondLine("Balance confirmed in blockchain"); + if (coinData.getBalanceInInternalUnits().isZero()) { + balanceValidator.setFirstLine("Empty wallet"); + balanceValidator.setSecondLine(""); + } + } + + // rule 4 TODO: need to check SignedHashed against number of outputs in blockchain // if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0) // { // score = 80; @@ -289,11 +289,11 @@ public class BtcEngine extends CoinEngine { // return; // } - if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { - balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); - } + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine("Verified offline balance"); + balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + } // if(card.getFailedBalanceRequestCounter()!=0) { // score -= 5 * card.getFailedBalanceRequestCounter(); @@ -302,7 +302,7 @@ public class BtcEngine extends CoinEngine { // return; // } - // + // // if(card.isBalanceReceived() && !card.isBalanceEqual()) { // score = 0; // firstLine = "Disputed balance"; @@ -310,7 +310,13 @@ public class BtcEngine extends CoinEngine { // return; // } - return true; + return true; + } + catch (Exception e) + { + e.printStackTrace(); + return false; + } } @Override @@ -384,7 +390,7 @@ public class BtcEngine extends CoinEngine { } @Override - public InternalAmount convertToInternalAmount(Amount amount) throws Exception { + public InternalAmount convertToInternalAmount(Amount amount) { BigDecimal d = amount.multiply(new BigDecimal("100000000")); return new InternalAmount(d, "Satoshi"); } @@ -398,7 +404,7 @@ public class BtcEngine extends CoinEngine { } @Override - public byte[] convertToByteArray(InternalAmount internalAmount) throws Exception { + public byte[] convertToByteArray(InternalAmount internalAmount) { byte[] bytes = Util.longToByteArray(internalAmount.longValueExact()); byte[] reversed = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; @@ -501,7 +507,7 @@ public class BtcEngine extends CoinEngine { } @Override - public void onSignCompleted(byte[] signFromCard) throws Exception { + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { for (int i = 0; i < unspentOutputs.size(); ++i) { BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); @@ -512,17 +518,19 @@ public class BtcEngine extends CoinEngine { byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); notifyOnNeedSendPayment(txForSend); + return txForSend; } }; } @Override - public void requestBalanceAndUnspentTransactions(BalanceAndUnspentTransactionsNotifications balanceAndUnspentTransactionsNotifications) { + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() { + ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { + Log.i(TAG, "onSuccess: "+electrumRequest.getMethod()); if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) { try { String walletAddress = electrumRequest.getParams().getString(0); @@ -543,9 +551,7 @@ public class BtcEngine extends CoinEngine { e.printStackTrace(); Log.e(TAG, "FAIL METHOD_GetBalance Exception"); } - } - - if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { + } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { try { String walletAddress = electrumRequest.getParams().getString(0); JSONArray jsUnspentArray = electrumRequest.getResultArray(); @@ -569,19 +575,17 @@ public class BtcEngine extends CoinEngine { Integer height = jsUnspent.getInt("height"); String hash = jsUnspent.getString("tx_hash"); if (height != -1) { - if (!balanceAndUnspentTransactionsNotifications.needTerminate()) { + if (blockchainRequestsCallbacks.allowAdvance()) { serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); } else { - serverApiElectrum.setErrorOccured("Terminated by user"); + ctx.setError("Terminated by user"); } } } } catch (JSONException e) { e.printStackTrace(); } - } - - if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { + } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { try { String txHash = electrumRequest.txHash; String raw = electrumRequest.getResultString(); @@ -594,98 +598,122 @@ public class BtcEngine extends CoinEngine { } } - if (!serverApiElectrum.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiElectrum.isErrorOccured()); + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); } } @Override - public void onFail(String method) { - if (!serverApiElectrum.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiElectrum.isErrorOccured()); + public void onFail(ElectrumRequest electrumRequest) { + Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError()); + ctx.setError(electrumRequest.getError()); + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + }else{ + blockchainRequestsCallbacks.onProgress(); } } }; - serverApiElectrum.setElectrumRequestData(electrumBodyListener); + serverApiElectrum.setElectrumRequestData(electrumListener); serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); } - Integer buildSize(String outputAddress, String outFee, String outAmount) throws Exception { - String myAddress = coinData.getWallet(); - byte[] pbKey = ctx.getCard().getWalletPublicKey(); - byte[] pbComprKey = ctx.getCard().getWalletPublicKeyRar(); + private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { + //todo - правильней было бы использовать constructPayment + try { +// String myAddress = coinData.getWallet(); +// byte[] pbKey = ctx.getCard().getWalletPublicKey(); +// byte[] pbComprKey = ctx.getCard().getWalletPublicKeyRar(); +// +// // build script for our address +// List rawTxList = coinData.getUnspentTransactions(); +// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; +// +// // collect unspent +// ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); +// +// Long fullAmount = 0L; +// for (int i = 0; i < unspentOutputs.size(); i++) { +// fullAmount += unspentOutputs.get(i).value; +// } +// +// // get first unspent +//// val outPut = unspentOutputs[0] +//// val outPutIndex = outPut.outputIndex +// +// // get prev TX id; +//// val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2"; +// +// Long fees = FormatUtil.ConvertStringToLong("0.00"); +// Long amount = FormatUtil.ConvertStringToLong(outAmount); +// amount -= fees; +// +// Long change = fullAmount - fees - amount; +// +// if (amount + fees > fullAmount) { +// throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); +// } +// +// byte[][] hashesForSign = new byte[unspentOutputs.size()][]; +// +// for (int i = 0; i < unspentOutputs.size(); i++) { +// byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change); +// byte[] hashData = Util.calculateSHA256(newTX); +// byte[] doubleHashData = Util.calculateSHA256(hashData); +//// Log.e("TX_BODY_1", BTCUtils.toHex(newTX)) +//// Log.e("TX_HASH_1", BTCUtils.toHex(hashData)) +//// Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData)) +// +//// unspentOutputs[i].bodyDoubleHash = doubleHashData +//// unspentOutputs[i].bodyHash = hashData +// hashesForSign[i] = doubleHashData; +// } +// +// byte[] signFromCard = new byte[64 * unspentOutputs.size()]; +// +// for (int i = 0; i < unspentOutputs.size(); i++) { +// BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); +// BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); +// byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); +// unspentOutputs.get(i).scriptForBuild = encodingSign; +// } +// +// byte[] realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change); - // build script for our address - List rawTxList = coinData.getUnspentTransactions(); - byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + SignTask.PaymentToSign ps=constructPayment(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress ); + OnNeedSendPayment onNeedSendPaymentBackup=onNeedSendPayment; + onNeedSendPayment=(tx)->{}; // empty function to bypass exception - // collect unspent - ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + byte[][] hashesToSign=ps.getHashesToSign(); + byte[] signFromCard = new byte[64 * hashesToSign.length]; + byte[] txForSend=ps.onSignCompleted(signFromCard); + onNeedSendPayment=onNeedSendPaymentBackup; + Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length)); + return txForSend.length; - Long fullAmount = 0L; - for (int i = 0; i < unspentOutputs.size(); i++) { - fullAmount += unspentOutputs.get(i).value; +// Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length)+" realTX.length="+String.valueOf(realTX.length)); +// +// return realTX.length; + + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "Can't calculate transaction size -> use default!"); + return 256; } - - // get first unspent -// val outPut = unspentOutputs[0] -// val outPutIndex = outPut.outputIndex - - // get prev TX id; -// val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2"; - - Long fees = FormatUtil.ConvertStringToLong(outFee); - Long amount = FormatUtil.ConvertStringToLong(outAmount); - amount -= fees; - - Long change = fullAmount - fees - amount; - - if (amount + fees > fullAmount) { - throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)); - } - - byte[][] hashesForSign = new byte[unspentOutputs.size()][]; - - for (int i = 0; i < unspentOutputs.size(); i++) { - byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change); - byte[] hashData = Util.calculateSHA256(newTX); - byte[] doubleHashData = Util.calculateSHA256(hashData); -// Log.e("TX_BODY_1", BTCUtils.toHex(newTX)) -// Log.e("TX_HASH_1", BTCUtils.toHex(hashData)) -// Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData)) - -// unspentOutputs[i].bodyDoubleHash = doubleHashData -// unspentOutputs[i].bodyHash = hashData - hashesForSign[i] = doubleHashData; - } - - byte[] signFromCard = new byte[64 * unspentOutputs.size()]; - - for (int i = 0; i < unspentOutputs.size(); i++) { - BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)); - BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); - byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey); - unspentOutputs.get(i).scriptForBuild = encodingSign; - } - - byte[] realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change); - - return realTX.length; } @Override - public void requestFee(FeeRequestsNotifications feeRequestsNotifications, CoinEngine.Amount amount) throws Exception { -// request estimate fee listener -// int calcSize = 256; -// try { - - final int calcSize = buildSize(coinData.getWallet(), "0.00", amount.toValueString()); -// } catch (Exception ex) { -// Log.e(TAG,"Build Fee error: "+ ex.getMessage()); -// } + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString()); + Log.e(TAG, String.format("Estimated tx size %d", calcSize)); + coinData.minFee = null; + coinData.maxFee = null; + coinData.normalFee = null; final ServerApiCommon serverApiCommon = new ServerApiCommon(); @@ -695,60 +723,51 @@ public class BtcEngine extends CoinEngine { BigDecimal fee = new BigDecimal(estimateFeeResponse); // BTC per 1 kb if (fee.equals(BigDecimal.ZERO)) { -// progressBar.visibility = View.INVISIBLE - if( !feeRequestsNotifications.needTerminate()) { + if (blockchainRequestsCallbacks.allowAdvance()) { serverApiCommon.estimateFee(blockCount); } return; } if (calcSize != 0) { - fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // per Kb -> per byte + fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024), BigDecimal.ROUND_DOWN); // per Kb -> per byte } else { - if( !feeRequestsNotifications.needTerminate()) { + if (blockchainRequestsCallbacks.allowAdvance()) { serverApiCommon.estimateFee(blockCount); } return; } -// progressBar.visibility = View.INVISIBLE - fee = fee.setScale(8, RoundingMode.DOWN); switch (blockCount) { - case ServerApiCommon.ESTIMATE_FEE_MINIMAL: { - CoinEngine.Amount minFee = new CoinEngine.Amount(fee, getFeeCurrency()); - feeRequestsNotifications.onComplete(true, minFee, null, null); -// if (rgFee.checkedRadioButtonId == R.id.rbMinimalFee) doSetFee(rgFee.checkedRadioButtonId) - } - break; - - case ServerApiCommon.ESTIMATE_FEE_NORMAL: { - CoinEngine.Amount normalFee = new CoinEngine.Amount(fee, getFeeCurrency()); - feeRequestsNotifications.onComplete(true, null, normalFee, null); -// if (rgFee.checkedRadioButtonId == R.id.rbNormalFee) doSetFee(rgFee.checkedRadioButtonId) - } - break; - - case ServerApiCommon.ESTIMATE_FEE_PRIORITY: { - CoinEngine.Amount maxFee = new CoinEngine.Amount(fee, getFeeCurrency()); - feeRequestsNotifications.onComplete(true, null, null, maxFee); -// if (rgFee.checkedRadioButtonId == R.id.rbMaximumFee) doSetFee(rgFee.checkedRadioButtonId) - } + case ServerApiCommon.ESTIMATE_FEE_MINIMAL: + coinData.minFee = new CoinEngine.Amount(fee, getFeeCurrency()); + break; + case ServerApiCommon.ESTIMATE_FEE_NORMAL: + coinData.normalFee = new CoinEngine.Amount(fee, getFeeCurrency()); + break; + case ServerApiCommon.ESTIMATE_FEE_PRIORITY: + coinData.maxFee = new CoinEngine.Amount(fee, getFeeCurrency()); + break; } -// etFee.error = null -// feeRequestSuccess = true -// if (feeRequestSuccess) -// if (feeRequestSuccess && balanceRequestSuccess) -// btnSend.visibility = View.VISIBLE -// dtVerified = Date() + if(coinData.minFee!=null && coinData.normalFee!=null && coinData.maxFee!=null ) { + blockchainRequestsCallbacks.onComplete(true); + }else{ + blockchainRequestsCallbacks.onProgress(); + } } @Override - public void onFail(String message) { - feeRequestsNotifications.onComplete(false, null, null, null); - + public void onFail(int blockCount, String message) { + // TODO - add fail counter to terminate after NNN tries + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiCommon.estimateFee(blockCount); + return; + } + ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); + blockchainRequestsCallbacks.onComplete(false); } }; serverApiCommon.setEstimateFee(estimateFeeListener); @@ -759,84 +778,47 @@ public class BtcEngine extends CoinEngine { } + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + final String txStr = BTCUtils.toHex(txForSend); - // @Override -// public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception { -// -// checkBlockchainDataExists(); -// -// String myAddress = ctx.getCoinData().getWallet(); -// byte[] pbKey = ctx.getCard().getWalletPublicKey(); -// -// // Build script for our address -// List rawTxList = coinData.getUnspentTransactions(); -// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; -// -// // Collect unspent -// ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); -// -// long fullAmount = 0; -// for (int i = 0; i < unspentOutputs.size(); ++i) { -// fullAmount += unspentOutputs.get(i).value; -// } -// -// -// long fees = convertToInternalAmount(feeValue).longValueExact(); -// long amount = convertToInternalAmount(amountValue).longValueExact(); -// long change = fullAmount - amount; -// if (IncFee) { -// amount = amount - fees; -// } else { -// change = change - fees; -// } -// -// if (amount + fees > fullAmount) { -// throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); -// } -// -// byte[][] dataForSign = new byte[unspentOutputs.size()][]; -// -// for (int i = 0; i < unspentOutputs.size(); ++i) { -// byte[] newTX = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change); -// -// byte[] hashData = Util.calculateSHA256(newTX); -// byte[] doubleHashData = Util.calculateSHA256(hashData); -// -// unspentOutputs.get(i).bodyDoubleHash = doubleHashData; -// unspentOutputs.get(i).bodyHash = hashData; -// -// if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) { -// dataForSign[i] = newTX; -// } else { -// dataForSign[i] = doubleHashData; -// } -// } -// -// byte[] signFromCard; -// if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) { -// ByteArrayOutputStream bs = new ByteArrayOutputStream(); -// if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!"); -// for (int i = 0; i < dataForSign.length; i++) { -// if (i != 0 && dataForSign[0].length != dataForSign[i].length) -// throw new Exception("Hashes length must be identical!"); -// bs.write(dataForSign[i]); -// } -// signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), "sha-256x2",bs.toByteArray(),null,null,null).getTLV(TLV.Tag.TAG_Signature).Value; -// } else { -// //ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, ctx.getCard().getIssuer() -// signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, null, null, null).getTLV(TLV.Tag.TAG_Signature).Value; -// // TODO slice signFromCard to hashes.length parts -// } -// -// for (int i = 0; i < unspentOutputs.size(); ++i) { -// BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); -// BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); -// s = CryptoUtil.toCanonicalised(s); -// -// unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); -// } -// -// return BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amount, change); -// } + ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener() { + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { + try { + String resultString = electrumRequest.getResultString(); + if (resultString == null || resultString.isEmpty()) { + ctx.setError("Rejected by node: " + electrumRequest.getError()); + 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(ElectrumRequest electrumRequest) { + ctx.setError(electrumRequest.getError()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiElectrum.setElectrumRequestData(electrumListener); + + + serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java index a36467c6a5..b6203b0bd7 100644 --- a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java @@ -13,7 +13,6 @@ import com.tangem.domain.wallet.CoinEngine; import com.tangem.domain.wallet.ECDSASignatureETH; import com.tangem.domain.wallet.EthTransaction; import com.tangem.domain.wallet.Keccak256; -import com.tangem.domain.wallet.token.TokenData; import com.tangem.tangemcard.data.TangemCard; import com.tangem.domain.wallet.TangemContext; import com.tangem.domain.wallet.BTCUtils; @@ -430,7 +429,7 @@ public class EthEngine extends CoinEngine { } @Override - public void onSignCompleted(byte[] signFromCard) throws Exception { + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { byte[] for_hash = tx.getRawHash(); BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); @@ -451,13 +450,15 @@ public class EthEngine extends CoinEngine { tx.signature.v = (byte) v; Log.e("ETH_v", String.valueOf(v)); - notifyOnNeedSendPayment(tx.getEncoded()); + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendPayment(txForSend); + return txForSend; } }; } @Override - public void requestBalanceAndUnspentTransactions(BalanceAndUnspentTransactionsNotifications balanceAndUnspentTransactionsNotifications) { + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiInfura serverApiInfura = new ServerApiInfura(); // request infura listener ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { @@ -468,19 +469,8 @@ public class EthEngine extends CoinEngine { String balanceCap = infuraResponse.getResult(); balanceCap = balanceCap.substring(2); BigInteger l = new BigInteger(balanceCap, 16); -// BigInteger d = l.divide(new BigInteger("1000000000000000000", 10)); -// Long balance = d.longValue(); - -// (ctx.coinData!! as EthData).setBalanceConfirmed(balance) -// (ctx.coinData!! as EthData).balanceUnconfirmed = 0L - if (ctx.getBlockchain() != Blockchain.Token) { - coinData.setBalanceReceived(true); - coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - } else { - coinData.setBalanceReceived(true); - //(ctx.coinData!! as TokenData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(),ctx.card.tokenSymbol) - ((TokenData) coinData).setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - } + coinData.setBalanceReceived(true); + coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); // Log.i("$TAG eth_get_balance", balanceCap) } @@ -505,62 +495,67 @@ public class EthEngine extends CoinEngine { // Log.i("$TAG eth_getPendingTxCount", pending) } + break; } - if (!serverApiInfura.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiInfura.isErrorOccured()); + if (serverApiInfura.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); } } @Override public void onFail(String method, String message) { - if (!serverApiInfura.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiInfura.isErrorOccured()); + if (!serverApiInfura.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); } } }; serverApiInfura.setInfuraResponse(infuraBodyListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } @Override - public void requestFee(FeeRequestsNotifications feeRequestsNotifications, CoinEngine.Amount amount) throws Exception { + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { ServerApiInfura serverApiInfura = new ServerApiInfura(); // request infura eth gasPrice listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { - if(method== ServerApiInfura.INFURA_ETH_GAS_PRICE) - { - String gasPrice = infuraResponse.getResult(); - gasPrice = gasPrice.substring(2); - // rounding gas price to integer gwei - BigInteger l = new BigInteger(gasPrice, 16).divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L)); + if (method.equals(ServerApiInfura.INFURA_ETH_GAS_PRICE)) { + String gasPrice = infuraResponse.getResult(); + gasPrice = gasPrice.substring(2); + // rounding gas price to integer gwei + BigInteger l = new BigInteger(gasPrice, 16).divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L)); - //val m = if (ctx.blockchain==Blockchain.Token) BigInteger.valueOf(60000) else BigInteger.valueOf(21000) - BigInteger m; - if (amount.getCurrency().equals("ETH")) m = BigInteger.valueOf(60000); - else m = BigInteger.valueOf(21000); + //val m = if (ctx.blockchain==Blockchain.Token) BigInteger.valueOf(60000) else BigInteger.valueOf(21000) +// BigInteger m; +// if (amount.getCurrency().equals("ETH")) m = BigInteger.valueOf(60000); +// else m = BigInteger.valueOf(21000); + BigInteger m = BigInteger.valueOf(60000); - CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei"); - CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(12)).divide(BigDecimal.valueOf(10)), "wei"); - CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(15)).divide(BigDecimal.valueOf(10)), "wei"); + CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei"); + CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(12)).divide(BigDecimal.valueOf(10)), "wei"); + CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(15)).divide(BigDecimal.valueOf(10)), "wei"); - CoinEngine.Amount minFee = convertToAmount(weiMinFee); - CoinEngine.Amount normalFee = convertToAmount(weiNormalFee); - CoinEngine.Amount maxFee = convertToAmount(weiMaxFee); - feeRequestsNotifications.onComplete(true, minFee, normalFee, maxFee); - } + coinData.minFee = convertToAmount(weiMinFee); + coinData.normalFee = convertToAmount(weiNormalFee); + coinData.maxFee = convertToAmount(weiMaxFee); + blockchainRequestsCallbacks.onComplete(true); + } } @Override public void onFail(String method, String message) { - if( method==ServerApiInfura.INFURA_ETH_GAS_PRICE ){ - feeRequestsNotifications.onComplete(false, null, null, null); + if (method == ServerApiInfura.INFURA_ETH_GAS_PRICE) { + ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); + blockchainRequestsCallbacks.onComplete(false); } } }; @@ -569,6 +564,45 @@ public class EthEngine extends CoinEngine { serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); } + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { + + String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); + + ServerApiInfura serverApiInfura = new ServerApiInfura(); + // request infura eth gasPrice listener + ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { + if (infuraResponse.getResult().isEmpty()) { + ctx.setError("Rejected by node: " + infuraResponse.getError()); + blockchainRequestsCallbacks.onComplete(false); + } else { + BigInteger nonce = coinData.getConfirmedTXCount(); + nonce.add(BigInteger.valueOf(1)); + coinData.setConfirmedTXCount(nonce); + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } + } + + @Override + public void onFail(String method, String message) { + if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + serverApiInfura.setInfuraResponse(infuraBodyListener); + + serverApiInfura.infura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + + } + // @Override // public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception { // diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java index b0bfd6cd50..42f24f682a 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java @@ -28,7 +28,11 @@ public class TokenData extends EthData { public void loadFromBundle(Bundle B) { super.loadFromBundle(B); - balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"),"wei"); + if( B.containsKey("BalanceDecimalAlter" )) { + balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei"); + }else{ + balanceAlter=null; + } } @Override diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java index ee00616785..fe687057d6 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java @@ -5,7 +5,6 @@ import android.text.InputFilter; import android.util.Log; import com.google.common.base.Strings; -import com.tangem.data.Blockchain; import com.tangem.data.network.ServerApiInfura; import com.tangem.data.network.model.InfuraResponse; import com.tangem.domain.wallet.BalanceValidator; @@ -170,7 +169,7 @@ public class TokenEngine extends CoinEngine { @Override public boolean isBalanceNotZero() { if (coinData == null) return false; - if (coinData.getBalanceInInternalUnits() == null && coinData.getBalanceAlterInInternalUnits() == null ) return false; + if (coinData.getBalanceInInternalUnits() == null && coinData.getBalanceAlterInInternalUnits() == null) return false; return coinData.getBalanceInInternalUnits().notZero() || coinData.getBalanceAlterInInternalUnits().notZero(); } @@ -185,7 +184,7 @@ public class TokenEngine extends CoinEngine { // TODO: check why Rate=EthRate return "";//convertToAmount(coinData.getBalanceInInternalUnits()).toEquivalentString(coinData.getRate()); } else { - if( coinData.getBalanceAlterInInternalUnits()==null ) return ""; + if (coinData.getBalanceAlterInInternalUnits() == null) return ""; return convertToAmount(coinData.getBalanceAlterInInternalUnits()).toEquivalentString(coinData.getRateAlter()); } } catch (Exception e) { @@ -342,7 +341,7 @@ public class TokenEngine extends CoinEngine { if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) { // token transaction - if( fee.compareTo(balance)>0 ) + if (fee.compareTo(balance) > 0) return false; } else if (amount.getCurrency().equals("ETH") && coinData.getBalanceInInternalUnits().isZero()) { // standard ETH transaction @@ -433,21 +432,12 @@ public class TokenEngine extends CoinEngine { } } -// @Override -// public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception { -// if (amountValue.getCurrency().equals("ETH")) { -// return signETH(feeValue, amountValue, IncFee, targetAddress, protocol); -// } else { -// return signToken(feeValue, amountValue, IncFee, targetAddress, protocol); -// } -// } - private SignTask.PaymentToSign constructPaymentETH(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); - BigInteger weiFee=convertToInternalAmount(feeValue).toBigIntegerExact(); - BigInteger weiAmount=convertToInternalAmount(amountValue).toBigIntegerExact(); + BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact(); + BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact(); if (IncFee) { weiAmount = weiAmount.subtract(weiFee); @@ -469,7 +459,7 @@ public class TokenEngine extends CoinEngine { return new SignTask.PaymentToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod==TangemCard.SigningMethod.Sign_Hash; + return signingMethod == TangemCard.SigningMethod.Sign_Hash; } @Override @@ -495,8 +485,8 @@ public class TokenEngine extends CoinEngine { } @Override - public void onSignCompleted(byte[] signFromCard) throws Exception { - byte[] for_hash=tx.getRawHash(); + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + byte[] for_hash = tx.getRawHash(); BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); s = CryptoUtil.toCanonicalised(s); @@ -516,7 +506,9 @@ public class TokenEngine extends CoinEngine { tx.signature.v = (byte) v; Log.e("ETH_v", String.valueOf(v)); - notifyOnNeedSendPayment(tx.getEncoded()); + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendPayment(txForSend); + return txForSend; } }; } @@ -532,7 +524,7 @@ public class TokenEngine extends CoinEngine { BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact(); - InternalAmount amountDec=convertToInternalAmount(amountValue); + InternalAmount amountDec = convertToInternalAmount(amountValue); BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10); @@ -573,7 +565,7 @@ public class TokenEngine extends CoinEngine { return new SignTask.PaymentToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod==TangemCard.SigningMethod.Sign_Hash; + return signingMethod == TangemCard.SigningMethod.Sign_Hash; } @Override @@ -599,7 +591,7 @@ public class TokenEngine extends CoinEngine { } @Override - public void onSignCompleted(byte[] signFromCard) throws Exception { + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { byte[] for_hash = tx.getRawHash(); BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); @@ -620,7 +612,9 @@ public class TokenEngine extends CoinEngine { tx.signature.v = (byte) v; Log.e("ETH_v", String.valueOf(v)); - notifyOnNeedSendPayment(tx.getEncoded()); + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendPayment(txForSend); + return txForSend; } }; @@ -628,7 +622,7 @@ public class TokenEngine extends CoinEngine { } @Override - public void requestBalanceAndUnspentTransactions(BalanceAndUnspentTransactionsNotifications balanceAndUnspentTransactionsNotifications) { + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiInfura serverApiInfura = new ServerApiInfura(); // request infura listener ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { @@ -639,19 +633,8 @@ public class TokenEngine extends CoinEngine { String balanceCap = infuraResponse.getResult(); balanceCap = balanceCap.substring(2); BigInteger l = new BigInteger(balanceCap, 16); -// BigInteger d = l.divide(new BigInteger("1000000000000000000", 10)); -// Long balance = d.longValue(); - -// (ctx.coinData!! as EthData).setBalanceConfirmed(balance) -// (ctx.coinData!! as EthData).balanceUnconfirmed = 0L - if (ctx.getBlockchain() != Blockchain.Token) { - coinData.setBalanceReceived(true); - coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - } else { - coinData.setBalanceReceived(true); - //(ctx.coinData!! as TokenData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(),ctx.card.tokenSymbol) - ((TokenData) coinData).setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - } + coinData.setBalanceReceived(true); + coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); // Log.i("$TAG eth_get_balance", balanceCap) } @@ -676,6 +659,7 @@ public class TokenEngine extends CoinEngine { // Log.i("$TAG eth_getPendingTxCount", pending) } + break; // case ServerApiInfura.INFURA_ETH_CALL: { try { @@ -683,48 +667,37 @@ public class TokenEngine extends CoinEngine { balanceCap = balanceCap.substring(2); BigInteger l = new BigInteger(balanceCap, 16); Long balance = l.longValue(); -// if (l.compareTo(BigInteger.ZERO) == 0) { -// //ctx.card!!.blockchainID = Blockchain.Ethereum.id -// ctx.card!!.addTokenToBlockchainName() -// -// //TODO check -// //ctx.blockchain=lBlockchain.Ethereum -// -// requestCounter-- -// if (requestCounter == 0) srl!!.isRefreshing = false -// -// requestInfura(ServerApiCommon.INFURA_ETH_GET_BALANCE, "") -// requestInfura(ServerApiCommon.INFURA_ETH_GET_TRANSACTION_COUNT, "") -// requestInfura(ServerApiCommon.INFURA_ETH_GET_PENDING_COUNT, "") -// return -// } coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - if (!balanceAndUnspentTransactionsNotifications.needTerminate()) { + if (blockchainRequestsCallbacks.allowAdvance()) { serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { - serverApiInfura.setErrorOccured("Terminated by user"); + ctx.setError("Terminated by user"); } } catch (Exception e) { e.printStackTrace(); } } + break; } - if (!serverApiInfura.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiInfura.isErrorOccured()); + if (serverApiInfura.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); } } @Override public void onFail(String method, String message) { - if (!serverApiInfura.hasRequests()) { - balanceAndUnspentTransactionsNotifications.onComplete(serverApiInfura.isErrorOccured()); + if (!serverApiInfura.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); } } }; @@ -733,6 +706,87 @@ public class TokenEngine extends CoinEngine { serverApiInfura.infura(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); } + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { + ServerApiInfura serverApiInfura = new ServerApiInfura(); + // request infura eth gasPrice listener + ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + String gasPrice = infuraResponse.getResult(); + gasPrice = gasPrice.substring(2); + // rounding gas price to integer gwei + BigInteger l = new BigInteger(gasPrice, 16).divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L)); + + //val m = if (ctx.blockchain==Blockchain.Token) BigInteger.valueOf(60000) else BigInteger.valueOf(21000) + BigInteger m; + if (amount.getCurrency().equals("ETH")) m = BigInteger.valueOf(60000); + else m = BigInteger.valueOf(21000); + + CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei"); + CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(12)).divide(BigDecimal.valueOf(10)), "wei"); + CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(15)).divide(BigDecimal.valueOf(10)), "wei"); + + try { + coinData.minFee = convertToAmount(weiMinFee); + coinData.normalFee = convertToAmount(weiNormalFee); + coinData.maxFee = convertToAmount(weiMaxFee); + } catch (Exception e) { + e.printStackTrace(); + } + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void onFail(String method, String message) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiInfura.setInfuraResponse(infuraBodyListener); + + serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { + + String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); + + ServerApiInfura serverApiInfura = new ServerApiInfura(); + // request infura eth gasPrice listener + ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { + if (infuraResponse.getResult().isEmpty()) { + ctx.setError("Rejected by node: " + infuraResponse.getError()); + blockchainRequestsCallbacks.onComplete(false); + } else { + BigInteger nonce = coinData.getConfirmedTXCount(); + nonce.add(BigInteger.valueOf(1)); + coinData.setConfirmedTXCount(nonce); + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } + } + + @Override + public void onFail(String method, String message) { + if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + serverApiInfura.setInfuraResponse(infuraBodyListener); + + serverApiInfura.infura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + + } + // public byte[] signETH(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception { // BigInteger nonceValue = coinData.getConfirmedTXCount(); diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt index df26fa1b02..afcb2662c2 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt @@ -9,29 +9,20 @@ import android.support.v7.app.AppCompatActivity import android.text.Editable import android.text.Html import android.text.TextWatcher -import android.util.Log import android.view.KeyEvent import android.view.View import android.widget.Toast -import com.tangem.data.network.ElectrumRequest -import com.tangem.data.network.ServerApiCommon -import com.tangem.data.network.ServerApiElectrum -import com.tangem.data.network.ServerApiInfura -import com.tangem.data.network.model.InfuraResponse -import com.tangem.tangemcard.android.reader.NfcManager -import com.tangem.domain.wallet.* -import com.tangem.domain.wallet.btc.BtcData import com.tangem.data.Blockchain +import com.tangem.domain.wallet.CoinEngine +import com.tangem.domain.wallet.CoinEngineFactory +import com.tangem.domain.wallet.TangemContext +import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.tangemcard.data.TangemCard import com.tangem.tangemcard.data.loadFromBundle -import com.tangem.tangemcard.util.Util -import com.tangem.util.* +import com.tangem.util.UtilHelper import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_confirm_payment.* import java.io.IOException -import java.math.BigDecimal -import java.math.BigInteger -import java.math.RoundingMode import java.util.* class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { @@ -49,11 +40,8 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { private lateinit var ctx: TangemContext private lateinit var amount: CoinEngine.Amount - private var feeRequestSuccess = false + // private var feeRequestSuccess = false // private var balanceRequestSuccess = false - private var minFee: CoinEngine.Amount? = null - private var maxFee: CoinEngine.Amount? = null - private var normalFee: CoinEngine.Amount? = null private var isIncludeFee: Boolean = true private var requestPIN2Count = 0 private var nodeCheck = true @@ -97,7 +85,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { etFee.setText("") btnSend.visibility = View.INVISIBLE - feeRequestSuccess = false +// feeRequestSuccess = false // balanceRequestSuccess = false if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token) { @@ -110,9 +98,9 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { // requestElectrum(ctx.card, ElectrumRequest.checkBalance(ctx.card!!.wallet)) - ctx.coinData!!.resetFailedBalanceRequestCounter() +// ctx.coinData!!.resetFailedBalanceRequestCounter() - progressBar.visibility = View.VISIBLE +// progressBar.visibility = View.VISIBLE // requestEstimateFee() } @@ -193,30 +181,37 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } val coinEngine = CoinEngineFactory.create(ctx) + + progressBar.visibility = View.VISIBLE + coinEngine!!.requestFee( - object : CoinEngine.FeeRequestsNotifications { - override fun onComplete(success: Boolean, minFee: CoinEngine.Amount?, normalFee: CoinEngine.Amount?, maxFee: CoinEngine.Amount?) { + object : CoinEngine.BlockchainRequestsCallbacks { + override fun onComplete(success: Boolean) { if (success) { - this@ConfirmPaymentActivity.minFee = minFee - this@ConfirmPaymentActivity.normalFee = normalFee - this@ConfirmPaymentActivity.maxFee = maxFee - doSetFee(rgFee.checkedRadioButtonId) - etFee.error = null - btnSend.visibility = View.VISIBLE - feeRequestSuccess = true -// balanceRequestSuccess = true + + onProgress() + +// etFee.error = null + +// feeRequestSuccess = true + // balanceRequestSuccess = true + progressBar.visibility = View.INVISIBLE dtVerified = Date() } else { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + finishWithError(Activity.RESULT_CANCELED, ctx.error) } } - override fun needTerminate(): Boolean { - return !UtilHelper.isOnline(this@ConfirmPaymentActivity) + override fun onProgress() { + doSetFee(rgFee.checkedRadioButtonId) + } + + override fun allowAdvance(): Boolean { + return UtilHelper.isOnline(this@ConfirmPaymentActivity) } }, - amount - ) + etWallet.text.toString(), + amount) // request electrum listener @@ -420,7 +415,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } // TODO - move to BtcEngine - @Throws(Exception::class) +// @Throws(Exception::class) // private fun requestElectrum(ctx: TangemContext, electrumRequest: ElectrumRequest) { // if (UtilHelper.isOnline(this)) { @@ -455,20 +450,29 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { var txtFee = "" when (checkedRadioButtonId) { R.id.rbMinimalFee -> - if (minFee != null) - txtFee = minFee!!.toValueString() - else - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + if (ctx.coinData.minFee != null) { + txtFee = ctx.coinData.minFee!!.toValueString() + btnSend.visibility = View.VISIBLE + }else { + btnSend.visibility = View.INVISIBLE +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + } R.id.rbNormalFee -> - if (normalFee != null) - txtFee = normalFee!!.toValueString() - else - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + if (ctx.coinData.normalFee != null) { + txtFee = ctx.coinData.normalFee!!.toValueString() + btnSend.visibility = View.VISIBLE + }else { + btnSend.visibility = View.INVISIBLE +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + } R.id.rbMaximumFee -> - if (maxFee != null) - txtFee = maxFee!!.toValueString() - else - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + if (ctx.coinData.maxFee != null) { + txtFee = ctx.coinData.maxFee!!.toValueString() + btnSend.visibility = View.VISIBLE + }else { +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) + btnSend.visibility = View.INVISIBLE + } } etFee.setText(txtFee.replace(',', '.')) } diff --git a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt index f91991a62e..8057a788ac 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt @@ -7,10 +7,6 @@ import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.KeyEvent import android.widget.Toast -import com.tangem.data.network.ElectrumRequest -import com.tangem.data.network.ServerApiElectrum -import com.tangem.data.network.ServerApiInfura -import com.tangem.data.network.model.InfuraResponse import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.domain.wallet.* import com.tangem.domain.wallet.eth.EthData @@ -27,11 +23,11 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { const val EXTRA_TX: String = "TX" } - private var serverApiInfura: ServerApiInfura = ServerApiInfura() - private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum() +// private var serverApiInfura: ServerApiInfura = ServerApiInfura() +// private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum() private lateinit var ctx: TangemContext - private var tx: String? = null + private var tx: ByteArray? = null private var nfcManager: NfcManager? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -43,71 +39,89 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { nfcManager = NfcManager(this, this) ctx = TangemContext.loadFromBundle(this, intent.extras) - tx = intent.getStringExtra(EXTRA_TX) + tx = intent.getByteArrayExtra(EXTRA_TX) val engine = CoinEngineFactory.create(ctx) - if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token) - requestInfura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, "") - else if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) - requestElectrum(ctx, ElectrumRequest.broadcast(ctx.coinData!!.wallet, tx)) - else if (ctx.blockchain == Blockchain.BitcoinCash) - requestElectrum(ctx, ElectrumRequest.broadcast(ctx.coinData!!.wallet, tx)) + engine!!.requestSendTransaction( + object : CoinEngine.BlockchainRequestsCallbacks { + override fun onComplete(success: Boolean) { + if (success) { + finishWithSuccess() + } else { + finishWithError(this@SendTransactionActivity.getString(R.string.try_again_failed_to_send_transaction)) + } + } + + override fun onProgress() { + } + + override fun allowAdvance(): Boolean { + return UtilHelper.isOnline(this@SendTransactionActivity) + } + }, + tx + ) + + +// if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token) +// requestInfura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, "") +// else if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) +// requestElectrum(ctx, ElectrumRequest.broadcast(ctx.coinData!!.wallet, tx)) +// else if (ctx.blockchain == Blockchain.BitcoinCash) +// requestElectrum(ctx, ElectrumRequest.broadcast(ctx.coinData!!.wallet, tx)) // request electrum listener - val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener { - override fun onSuccess(electrumRequest: ElectrumRequest?) { - if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_SendTransaction)) { - try { - if (electrumRequest.resultString.isNullOrEmpty()) - finishWithError("Rejected by node: " + electrumRequest.getError()) - else - finishWithSuccess() - } - catch (e: Exception) - { - if( e.message!=null ) - { - finishWithError(e.message!!) - }else{ - finishWithError(e.javaClass.name) - } - } - } - } - - override fun onFail(message: String?) { - finishWithError(message!!) - } - } - serverApiElectrum.setElectrumRequestData(electrumBodyListener) +// val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener { +// override fun onSuccess(electrumRequest: ElectrumRequest?) { +// if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_SendTransaction)) { +// try { +// if (electrumRequest.resultString.isNullOrEmpty()) +// finishWithError("Rejected by node: " + electrumRequest.getError()) +// else +// finishWithSuccess() +// } catch (e: Exception) { +// if (e.message != null) { +// finishWithError(e.message!!) +// } else { +// finishWithError(e.javaClass.name) +// } +// } +// } +// } +// +// override fun onFail(message: String?) { +// finishWithError(message!!) +// } +// } +// serverApiElectrum.setElectrumRequestData(electrumBodyListener) // request infura listener - val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener { - override fun onSuccess(method: String, infuraResponse: InfuraResponse) { - when (method) { - ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> { - if (infuraResponse.result.isEmpty()) - finishWithError("Rejected by node: " + infuraResponse.error) - else { - val nonce = (ctx.coinData!! as EthData).confirmedTXCount - nonce.add(BigInteger.valueOf(1)) - (ctx.coinData!! as EthData).confirmedTXCount = nonce - finishWithSuccess() - } - } - } - } - - override fun onFail(method: String, message: String) { - when (method) { - ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> { - finishWithError(message) - } - } - } - } - serverApiInfura.setInfuraResponse(infuraBodyListener) +// val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener { +// override fun onSuccess(method: String, infuraResponse: InfuraResponse) { +// when (method) { +// ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> { +// if (infuraResponse.result.isEmpty()) +// finishWithError("Rejected by node: " + infuraResponse.error) +// else { +// val nonce = (ctx.coinData!! as EthData).confirmedTXCount +// nonce.add(BigInteger.valueOf(1)) +// (ctx.coinData!! as EthData).confirmedTXCount = nonce +// finishWithSuccess() +// } +// } +// } +// } +// +// override fun onFail(method: String, message: String) { +// when (method) { +// ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> { +// finishWithError(message) +// } +// } +// } +// } +// serverApiInfura.setInfuraResponse(infuraBodyListener) } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { @@ -143,20 +157,20 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } } - private fun requestInfura(method: String, contract: String) { - if (UtilHelper.isOnline(this)) { - serverApiInfura.infura(method, 67, ctx.coinData!!.wallet, contract, tx) - } else - finishWithError(getString(R.string.no_connection)) - } - - private fun requestElectrum(ctx: TangemContext, electrumRequest: ElectrumRequest) { - if (UtilHelper.isOnline(this)) { - serverApiElectrum.electrumRequestData(ctx, electrumRequest) - } else - finishWithError(getString(R.string.no_connection)) - } +// private fun requestInfura(method: String, contract: String) { +// if (UtilHelper.isOnline(this)) { +// serverApiInfura.infura(method, 67, ctx.coinData!!.wallet, contract, tx) +// } else +// finishWithError(getString(R.string.no_connection)) +// } +// private fun requestElectrum(ctx: TangemContext, electrumRequest: ElectrumRequest) { +// if (UtilHelper.isOnline(this)) { +// serverApiElectrum.electrumRequestData(ctx, electrumRequest) +// } else +// finishWithError(getString(R.string.no_connection)) +// } +// private fun finishWithSuccess() { val intent = Intent() intent.putExtra("message", getString(R.string.transaction_has_been_successfully_signed)) diff --git a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt index 4466ea5315..7ba52a40d0 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt @@ -144,4 +144,143 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card ?: throw CardProtocol.TangemException("Can't create CoinEngine!") coinEngine.setOnNeedSendPayment { tx -> if (tx != null) { - // [REDACTED_TODO_COMMENT] \ No newline at end of file + val intent = Intent(this, SendTransactionActivity::class.java) + ctx.saveToIntent(intent) + intent.putExtra(SendTransactionActivity.EXTRA_TX, tx) + startActivityForResult(intent, SignPaymentActivity.REQUEST_CODE_SEND_PAYMENT) + } + } + val paymentToSign = coinEngine.constructPayment(amount, fee, isIncludeFee, outAddressStr) + + signPaymentTask = SignTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, paymentToSign) + signPaymentTask!!.start() + } else { +// Log.d(TAG, "Mismatch card UID (" + sUID + " instead of " + card!!.uid + ")") + nfcManager!!.ignoreTag(isoDep.tag) + } + + }catch (e: CardProtocol.TangemException_WrongAmount) + { + try { + val intent = Intent() + intent.putExtra("message", getString(R.string.cannot_sign_transaction_wrong_amount)) + intent.putExtra("UID", ctx.card.uid) + intent.putExtra("Card", ctx.card.asBundle) + setResult(Activity.RESULT_CANCELED, intent) + finish() + } catch (e: Exception) { + e.printStackTrace() + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + override fun onReadStart(cardProtocol: CardProtocol) { + progressBar!!.post { + progressBar!!.visibility = View.VISIBLE + progressBar!!.progress = 5 + } + } + + override fun onReadProgress(protocol: CardProtocol, progress: Int) { + progressBar!!.post { progressBar!!.progress = progress } + } + + override fun onReadFinish(cardProtocol: CardProtocol?) { + signPaymentTask = null + if (cardProtocol != null) { + if (cardProtocol.error == null) { + progressBar!!.post { + progressBar!!.progress = 100 + progressBar!!.progressTintList = ColorStateList.valueOf(Color.GREEN) + } + } else { + lastReadSuccess = false + if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { + progressBar!!.post { + progressBar!!.progress = 100 + progressBar!!.progressTintList = ColorStateList.valueOf(Color.RED) + } + progressBar!!.postDelayed({ + try { + progressBar!!.progress = 0 + progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY) + progressBar!!.visibility = View.INVISIBLE + val intent = Intent() + intent.putExtra("message", getString(R.string.cannot_sign_transaction__make_sure_you_enter_correct_pin_2)) + intent.putExtra("UID", cardProtocol.card.uid) + intent.putExtra("Card", cardProtocol.card.asBundle) + setResult(RESULT_INVALID_PIN, intent) + finish() + } catch (e: Exception) { + e.printStackTrace() + } + }, 500) + } else { + if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) { + try { + val intent = Intent() + intent.putExtra("message", getString(R.string.cannot_sign_transaction_wrong_amount)) + intent.putExtra("UID", cardProtocol.card.uid) + intent.putExtra("Card", cardProtocol.card.asBundle) + setResult(Activity.RESULT_CANCELED, intent) + finish() + } catch (e: Exception) { + e.printStackTrace() + } + } + progressBar!!.post { + if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allReadyShowed) { + NoExtendedLengthSupportDialog.message = getText(R.string.the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.the_nfc_adapter_length_apdu_advice).toString() + NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG) + } + } else { + Toast.makeText(baseContext, R.string.try_to_scan_again, Toast.LENGTH_LONG).show() + } + progressBar!!.progress = 100 + progressBar!!.progressTintList = ColorStateList.valueOf(Color.RED) + } + } + } + } + + progressBar!!.postDelayed({ + try { + progressBar!!.progress = 0 + progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY) + progressBar!!.visibility = View.INVISIBLE + } catch (e: Exception) { + e.printStackTrace() + } + }, 500) + } + + override fun onReadCancel() { + signPaymentTask = null + + progressBar!!.postDelayed({ + try { + progressBar!!.progress = 0 + progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY) + progressBar!!.visibility = View.INVISIBLE + } catch (e: Exception) { + e.printStackTrace() + } + }, 500) + } + + override fun onReadWait(msec: Int) { + WaitSecurityDelayDialog.OnReadWait(this, msec) + } + + override fun onReadBeforeRequest(timeout: Int) { + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout) + } + + override fun onReadAfterRequest() { + WaitSecurityDelayDialog.onReadAfterRequest(this) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt index 48ccfa9006..626a410857 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -69,7 +69,17 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific private var cardProtocol: CardProtocol? = null private val inactiveColor: ColorStateList by lazy { resources.getColorStateList(R.color.btn_dark) } private val activeColor: ColorStateList by lazy { resources.getColorStateList(R.color.colorAccent) } - private var requestCounter = 0 + private var requestCounter: Int = 0 + set(value) + { + field=value + Log.i(TAG, "requestCounter, set $field") + if (field <= 0 && srl!=null && srl.isRefreshing ) { + Log.e(TAG, "+++++++++++ FINISH REFRESH") + if (srl != null) srl!!.isRefreshing = false + //updateViews() + } + } private var timerRepeatRefresh: Timer? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -395,6 +405,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific // request card verify and get info listener val cardVerifyAndGetInfoListener: ServerApiTangem.CardVerifyAndGetInfoListener = object : ServerApiTangem.CardVerifyAndGetInfoListener { override fun onSuccess(cardVerifyAndGetArtworkResponse: CardVerifyAndGetInfo.Response?) { + Log.i(TAG,"cardVerifyAndGetInfoListener onSuccess") val result = cardVerifyAndGetArtworkResponse?.results!![0] if (result.error != null) { ctx.card!!.isOnlineVerified = false @@ -402,7 +413,9 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } ctx.card!!.isOnlineVerified = result.passed - if (requestCounter == 0) updateViews() +// if (requestCounter == 0) + requestCounter-- + updateViews() if (!result.passed) return @@ -421,6 +434,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } if (result.artwork != null && App.localStorage.checkNeedUpdateArtwork(result.artwork)) { Log.w(TAG, "Artwork '${result.artwork!!.id}' updated, need download") + requestCounter++ serverApiTangem.requestArtwork(result.artwork!!.id, result.artwork!!.getUpdateDate(), ctx.card!!) updateViews() } @@ -428,7 +442,9 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } override fun onFail(message: String?) { - + Log.i(TAG,"cardVerifyAndGetInfoListener onFail") + requestCounter-- + updateViews() } } serverApiTangem.setCardVerifyAndGetInfoListener(cardVerifyAndGetInfoListener) @@ -436,12 +452,17 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific // request artwork listener val artworkListener: ServerApiTangem.ArtworkListener = object : ServerApiTangem.ArtworkListener { override fun onSuccess(artworkId: String?, inputStream: InputStream?, updateDate: Date?) { + Log.i(TAG,"artworkListener onSuccess") App.localStorage.updateArtwork(artworkId!!, inputStream!!, updateDate!!) + requestCounter-- ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!)) + updateViews() } override fun onFail(message: String?) { - + Log.i(TAG,"artworkListener onFail") + requestCounter-- + updateViews() } } serverApiTangem.setArtworkListener(artworkListener) @@ -454,14 +475,6 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } } - private fun counterMinus() { - requestCounter-- - if (requestCounter == 0) { - if (srl != null) srl!!.isRefreshing = false - updateViews() - } - } - override fun onResume() { super.onResume() nfcManager!!.onResume() @@ -714,12 +727,12 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific timerHideErrorAndMessage = null } - if (ctx.error == null || ctx.error.isEmpty()) { - tvError.visibility = View.GONE - tvError.text = "" - } else { + if (ctx.hasError()) { tvError.visibility = View.VISIBLE tvError.text = ctx.error + } else { + tvError.visibility = View.GONE + tvError.text = "" } if (ctx.message == null || ctx.message.isEmpty()) { @@ -775,19 +788,22 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific btnExtract.backgroundTintList = inactiveColor } - ctx.error = null - ctx.message = null + //TODO why ??? +// ctx.error = null +// ctx.message = null } private fun refresh() { if (ctx.card == null) return // clear all card data and request again - srl?.isRefreshing = true ctx.coinData.clearInfo() ctx.error = null ctx.message = null + + Log.e(TAG, "============= START REFRESH") requestCounter = 0 + srl?.isRefreshing = true updateViews() @@ -796,14 +812,24 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific val coinEngine = CoinEngineFactory.create(ctx) requestCounter++ coinEngine!!.requestBalanceAndUnspentTransactions( - object : CoinEngine.BalanceAndUnspentTransactionsNotifications { - override fun onComplete(success: Boolean?) { - counterMinus() + object : CoinEngine.BlockchainRequestsCallbacks { + override fun onComplete(success: Boolean) { + Log.i(TAG, "requestBalanceAndUnspentTransactions onComplete: "+success.toString()+", request counter "+requestCounter.toString()) + requestCounter-- + if(! success) + { + Log.e(TAG, "ctx.error: "+ctx.error) + } updateViews() } - override fun needTerminate(): Boolean { - return !UtilHelper.isOnline(context as Activity) + override fun onProgress() { + Log.i(TAG, "requestBalanceAndUnspentTransactions onProgress") + updateViews() + } + + override fun allowAdvance(): Boolean { + return UtilHelper.isOnline(context as Activity) } } ) @@ -867,19 +893,24 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific private fun requestVerifyAndGetInfo() { if (UtilHelper.isOnline(context as Activity)) { if ((ctx.card!!.isOnlineVerified == null || !ctx.card!!.isOnlineVerified)) { + Log.i(TAG, "requestVerifyAndGetInfo") + requestCounter++ serverApiTangem.cardVerifyAndGetInfo(ctx.card) } } else { Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() + Log.e(TAG, "+++++++++++ Hide refresh 1") srl?.isRefreshing = false } } private fun requestRateInfo(cryptoId: String) { if (UtilHelper.isOnline(context as Activity)) { + Log.i(TAG, "requestRateInfo") serverApiCommon.rateInfoData(cryptoId) } else { Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() + Log.e(TAG, "+++++++++++ Hide refresh 2") srl?.isRefreshing = false } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dfe00212b1..16073f4101 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,9 @@ If you forget your new PIN you will lose your money forever! If you use default PIN someone can steal your money! Cannot obtain data from blockchain + Cannot obtain data from blockchain (connection refused) + Cannot obtain data from blockchain (empty answer received) + Cannot obtain data from blockchain (communication error) Sending cached transaction… NOT IMPLEMENTED This banknote is protected by default PIN1 code diff --git a/tangemcard-common/src/main/java/com/tangem/tangemcard/tasks/SignTask.java b/tangemcard-common/src/main/java/com/tangem/tangemcard/tasks/SignTask.java index 9e83d7fd99..512aa917e3 100644 --- a/tangemcard-common/src/main/java/com/tangem/tangemcard/tasks/SignTask.java +++ b/tangemcard-common/src/main/java/com/tangem/tangemcard/tasks/SignTask.java @@ -28,7 +28,7 @@ public class SignTask extends CustomReadCardTask { byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception; - void onSignCompleted(byte[] signature) throws Exception; + byte[] onSignCompleted(byte[] signature) throws Exception; } private PaymentToSign paymentToSign;