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 aa8839cc28..591b359973 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() { } @@ -142,8 +142,8 @@ public class ElectrumRequest { return ""; } - public boolean isMethod(String methodName) throws JSONException { - return jsRequestData.getString("method").equals(methodName); + public boolean isMethod(String methodName) { + return getMethod().equals(methodName); } public JSONArray getParams() throws JSONException { @@ -154,15 +154,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 227a6d9812..a3267d633a 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; @@ -41,6 +42,17 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DefaultObserver; import io.reactivex.schedulers.Schedulers; +/** + * Request processor for Electrum Api + * Every request live cycle: + * 1. In application create request and call {@link ServerApiElectrum}.electrumRequestData(..) + * 2. Try send every request for max 4 times, + * 3. If all 4 times fail call DefaultObserver.onError (defined in .electrumRequestData(..)) and than + * {@link ElectrumRequestDataListener}.onFail(...) callback + * Error can be acquired with {@link ElectrumRequest}.getError() method + * 4. If request network communication finished successfully then call DefaultObserver.onComplete (defined in .electrumRequestData) and than + * {@link ElectrumRequestDataListener}.onSuccess(...) callback + */ public class ServerApiElectrum { private static String TAG = ServerApiElectrum.class.getSimpleName(); @@ -52,17 +64,47 @@ public class ServerApiElectrum { private String host; private int port; - public interface ElectrumRequestDataListener { - void onSuccess(ElectrumRequest electrumRequest); + private int requestsCount=0; - void onFail(String method); + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; } + /** + * Interface for notification every request result + */ + public interface ElectrumRequestDataListener { + + /** + * Notify that request processing was successful + * @param electrumRequest - processed request containing received answer {@see electrumRequest.getAnswer() method} + */ + void onSuccess(ElectrumRequest electrumRequest); + /** + * Notify that request processing was successful + * @param electrumRequest - processed request containing occurred error {@see electrumRequest.getError() method} + */ + void onFail(ElectrumRequest electrumRequest); + } + + /** + * Set notificaion listener + * @param listener + */ public void setElectrumRequestData(ElectrumRequestDataListener listener) { electrumRequestDataListener = listener; } + + /** + * Start process request + * @param ctx + * @param electrumRequest + */ 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)) @@ -81,32 +123,50 @@ 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) { - 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); } + /** + * Called after completion request processing + */ @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; @@ -118,8 +178,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(); @@ -130,9 +189,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) { @@ -145,23 +204,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"); @@ -178,37 +233,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) { } @@ -236,8 +292,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"); @@ -252,34 +308,33 @@ 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() { return "Electrum, " + host + ":" + String.valueOf(port); } + } \ No newline at end of file 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 fb8e407099..34b2f33f3f 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java @@ -31,6 +31,13 @@ public class ServerApiInfura { public static final String INFURA_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction"; public static final String INFURA_ETH_GAS_PRICE = "eth_gasPrice"; + private int requestsCount=0; + + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; + } + private InfuraBodyListener infuraBodyListener; public interface InfuraBodyListener { @@ -44,6 +51,7 @@ public class ServerApiInfura { } public void infura(String method, int id, String wallet, String contract, String tx) { + requestsCount++; InfuraApi infuraApi = App.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class); InfuraBody infuraBody; @@ -77,6 +85,7 @@ public class ServerApiInfura { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.code() == 200) { + requestsCount--; infuraBodyListener.onSuccess(method, response.body()); Log.i(TAG, "infura " + method + " onResponse " + response.code()); } else { 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 99fb710d2a..83b0f987bf 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java @@ -3,7 +3,6 @@ package com.tangem.domain.wallet; import android.net.Uri; import android.text.InputFilter; -import com.tangem.data.Blockchain; import com.tangem.tangemcard.reader.CardProtocol; import com.tangem.tangemcard.tasks.SignTask; @@ -28,36 +27,35 @@ public abstract class CoinEngine { public InternalAmount() { super(0); - currency=""; + currency = ""; } public InternalAmount(String amountString, String currency) { - super(amountString.replace(',','.')); - this.currency=currency; + super(amountString.replace(',', '.')); + this.currency = currency; } public InternalAmount(long amount, String currency) { super(amount); - this.currency=currency; + this.currency = currency; } public InternalAmount(BigDecimal amount, String currency) { super(amount.unscaledValue(), amount.scale()); - this.currency=currency; + this.currency = currency; } public InternalAmount(BigInteger amount, String currency) { super(new BigDecimal(amount).unscaledValue(), new BigDecimal(amount).scale()); - this.currency=currency; + this.currency = currency; } - public boolean notZero() - { - return compareTo(BigDecimal.ZERO)>0; + public boolean notZero() { + return compareTo(BigDecimal.ZERO) > 0; } public boolean isZero() { - return compareTo(BigDecimal.ZERO)==0; + return compareTo(BigDecimal.ZERO) == 0; } public String getCurrency() { @@ -75,7 +73,7 @@ public abstract class CoinEngine { df.setGroupingUsed(false); - BigDecimal bd=new BigDecimal(unscaledValue(), scale()); + BigDecimal bd = new BigDecimal(unscaledValue(), scale()); bd.setScale(decimals, ROUND_DOWN); return df.format(bd); } @@ -96,11 +94,11 @@ public abstract class CoinEngine { public Amount() { super(0); - currency=""; + currency = ""; } public Amount(String amountString, String currency) { - super(amountString.replace(',','.')); + super(amountString.replace(',', '.')); this.currency = currency; } @@ -123,13 +121,12 @@ public abstract class CoinEngine { return super.toString() + " " + currency; } - public boolean notZero() - { - return compareTo(BigDecimal.ZERO)>0; + public boolean notZero() { + return compareTo(BigDecimal.ZERO) > 0; } public String toDescriptionString(int decimals) { - return toValueString(decimals)+ " " + currency; + return toValueString(decimals) + " " + currency; } public String toValueString(int decimals) { @@ -143,7 +140,7 @@ public abstract class CoinEngine { df.setGroupingUsed(false); - BigDecimal bd=new BigDecimal(unscaledValue(), scale()); + BigDecimal bd = new BigDecimal(unscaledValue(), scale()); bd.setScale(decimals, ROUND_DOWN); return df.format(bd); } @@ -164,7 +161,7 @@ public abstract class CoinEngine { } public boolean isZero() { - return compareTo(BigDecimal.ZERO)==0; + return compareTo(BigDecimal.ZERO) == 0; } } @@ -223,9 +220,11 @@ public abstract class CoinEngine { public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException; public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception; + public abstract Amount convertToAmount(String strAmount, String currency); public abstract InternalAmount convertToInternalAmount(Amount amount) throws Exception; + public abstract InternalAmount convertToInternalAmount(byte[] bytes) throws Exception; public abstract byte[] convertToByteArray(InternalAmount internalAmount) throws Exception; @@ -238,31 +237,108 @@ public abstract class CoinEngine { try { String wallet = calculateAddress(ctx.getCard().getWalletPublicKey()); ctx.getCoinData().setWallet(wallet); - } - catch (Exception e) - { + } catch (Exception e) { ctx.getCoinData().setWallet("ERROR"); throw new CardProtocol.TangemException("Can't define wallet address"); } } + /** + * Create instance of {@link SignTask.PaymentToSign} used for transaction signing and sending + * + * Transaction processing sequence: + * 1. User enter transaction attributes + * 2. Application create instance of {@link SignTask.PaymentToSign} by call {@see constructPayment} + * 3. Application set notification when transaction were prepared {@see setOnNeedSendPayment} and start {@link SignTask} + * 4. User tap card and card sign transaction + * 5. Application receive {@link CoinEngine.OnNeedSendPayment} notification with prepared raw transaction + * 6. Application show user information that transaction ready for sending and start sending procedure by call {@see requestSendTransaction} + * 7. Application receive notification of sending result through {@link CoinEngine.BlockchainRequestsCallbacks} and show result to user + * + * @param amountValue - amount of desired transaction + * @param feeValue - fee amount of desired transaction + * @param IncFee - true if fee amount is included in amountValue (amountValue is total amount of transaction) + * @param targetAddress - target address of transaction + * @return instance of {@link SignTask.PaymentToSign} + * @throws Exception if something goes wrong + */ public abstract SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception; - public interface OnNeedSendPayment - { + /** + * Interface used to notify main application when new transaction is prepared to send + */ + public interface OnNeedSendPayment { void onPaymentPrepared(byte[] txForSend); } - private OnNeedSendPayment onNeedSendPayment; + protected OnNeedSendPayment onNeedSendPayment; + + + /** + * Set notification callback when new transaction is prepared to send + */ public void setOnNeedSendPayment(OnNeedSendPayment onNeedSendPayment) { this.onNeedSendPayment = onNeedSendPayment; } protected void notifyOnNeedSendPayment(byte[] txForSend) throws Exception { - if(onNeedSendPayment==null) + if (onNeedSendPayment == null) throw new Exception("Payment signed but no callback defined to send!"); onNeedSendPayment.onPaymentPrepared(txForSend); - } + + /** + * Interface used to notify/querying application during processing sequence of request to blockchain nodes/servers + */ + public interface BlockchainRequestsCallbacks { + /** + * Notification that the all requests in sequence completed + * Call after a last request completed + * If occurred error return in {@link TangemContext} {@see TangemContext.getError()} + * + * @param success -* + */ + void onComplete(Boolean success); + + /** + * 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(); + } + + /** + * Start sequence of request to blockchain nodes needed to get balance and other information (for example unspent transaction) needed to + * show current state of wallet and prepare new withdrawal transaction + * Save result in {@link CoinData} + * If occurred error can be get at onComplete callback in {@link TangemContext}.getError() + * @param blockchainRequestsCallbacks - notifications + * @throws Exception if something goes wrong + */ + public abstract void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception; + + /** + * Start sequence of request to blockchain nodes needed to get fee amount for a new transaction + * Save result in {@link CoinData} minFee, maxFee, normalFee + * @param blockchainRequestsCallbacks - notifications + * @throws Exception if something goes wrong + */ + public abstract void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception; + + /** + * Start sequence of request to blockchain nodes needed to send new transaction + * If occurred error can be get at onComplete callback in {@link TangemContext}.getError() + * @param blockchainRequestsCallbacks - notifications + * @throws Exception if something goes wrong + */ + 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 8fd87ee969..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 @@ -2,8 +2,13 @@ package com.tangem.domain.wallet.bch; import android.net.Uri; 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; @@ -19,11 +24,17 @@ 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; +import org.json.JSONException; +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; @@ -33,6 +44,7 @@ import java.util.List; public class BtcCashEngine extends CoinEngine { + private static final String TAG = BtcCashEngine.class.getSimpleName(); public BtcData coinData = null; public BtcCashEngine(TangemContext context) throws Exception { @@ -167,7 +179,9 @@ public class BtcCashEngine extends CoinEngine { // // return true; - return CashAddr.isValidCashAddress(address); + if (CashAddr.isValidCashAddress(address)) + return true; + return false; } @Override @@ -177,7 +191,7 @@ public class BtcCashEngine extends CoinEngine { @Override public Uri getShareWalletUriExplorer() { - return Uri.parse("https://bch.btc.com/" + ctx.getCoinData().getWallet()); + return Uri.parse((ctx.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + ctx.getCoinData().getWallet()); } @Override @@ -219,7 +233,7 @@ public class BtcCashEngine extends CoinEngine { if (fee.isZero() || amount.isZero()) return false; - if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee)<0)) + if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0)) return false; if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0) @@ -314,8 +328,8 @@ public class BtcCashEngine extends CoinEngine { @Override public String getBalanceEquivalent() { if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; - Amount balance=getBalance(); - if( balance==null ) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; return balance.toEquivalentString(coinData.getRate()); } @@ -424,25 +438,22 @@ public class BtcCashEngine extends CoinEngine { return coinData.getUnspentInputsDescription(); } +// @Override +// public String getAmountDescription(TangemCard mCard, String amount) throws Exception { +// return mCard.getAmountDescription(Double.parseDouble(amount)); +// } + @Override public void defineWallet() throws CardProtocol.TangemException { try { String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar()); ctx.getCoinData().setWallet(wallet); - } - catch (Exception e) - { + } catch (Exception e) { ctx.getCoinData().setWallet("ERROR"); throw new CardProtocol.TangemException("Can't define wallet address"); } - } -// @Override -// public String getAmountDescription(TangemCard mCard, String amount) throws Exception { -// return mCard.getAmountDescription(Double.parseDouble(amount)); -// } - @Override public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { @@ -481,9 +492,9 @@ public class BtcCashEngine extends CoinEngine { final long amountFinal = amount; final long changeFinal = change; - byte[][] txForSign= new byte[unspentOutputs.size()][]; - byte[][] bodyHash= new byte[unspentOutputs.size()][]; - byte[][] bodyDoubleHash= new byte[unspentOutputs.size()][]; + byte[][] txForSign = new byte[unspentOutputs.size()][]; + byte[][] bodyHash = new byte[unspentOutputs.size()][]; + byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][]; for (int i = 0; i < unspentOutputs.size(); ++i) { txForSign[i] = BCHUtils.buildTXForSign(srcLegacyAddress, destLegacyAddress, srcLegacyAddress, unspentOutputs, i, amount, change); @@ -495,12 +506,12 @@ public class BtcCashEngine extends CoinEngine { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw; + return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; } @Override public byte[][] getHashesToSign() throws Exception { - byte[][] dataForSign=new byte[unspentOutputs.size()][]; + byte[][] dataForSign = new byte[unspentOutputs.size()][]; if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!"); for (int i = 0; i < unspentOutputs.size(); ++i) { dataForSign[i] = bodyDoubleHash[i]; @@ -531,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)); @@ -540,10 +551,294 @@ public class BtcCashEngine extends CoinEngine { unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey); } - byte[] txForSend=BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal); + byte[] txForSend = BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal); notifyOnNeedSendPayment(txForSend); + return txForSend; } }; } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + + ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() { + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) { + try { + String walletAddress = electrumRequest.getParams().getString(0); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = electrumRequest.getResult().getLong("confirmed"); + Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed"); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(serverApiElectrum.getValidationNodeDescription()); + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance Exception"); + } + } + + if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { + try { + String walletAddress = electrumRequest.getParams().getString(0); + JSONArray jsUnspentArray = electrumRequest.getResultArray(); + try { + coinData.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + coinData.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } + + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); + } else { + ctx.setError("Terminated by user"); + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { + try { + String txHash = electrumRequest.txHash; + String raw = electrumRequest.getResultString(); + for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { + if (tx.txID.equals(txHash)) + tx.Raw = raw; + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + 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.electrumRequestData(ctx, ElectrumRequest.checkBalance(convertToLegacyAddress(coinData.getWallet()))); + 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 778a88d06d..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 @@ -2,7 +2,9 @@ package com.tangem.domain.wallet.btc; import android.net.Uri; import android.text.InputFilter; +import android.util.Log; +import com.tangem.data.network.ServerApiCommon; import com.tangem.tangemcard.reader.CardProtocol; import com.tangem.domain.wallet.BalanceValidator; import com.tangem.domain.wallet.Base58; @@ -20,10 +22,18 @@ import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.tangemcard.util.Util; import com.tangem.wallet.R; +import com.tangem.data.network.ElectrumRequest; +import com.tangem.data.network.ServerApiElectrum; + + +import org.json.JSONArray; +import org.json.JSONException; +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; @@ -33,6 +43,8 @@ import java.util.List; public class BtcEngine extends CoinEngine { + private static final String TAG = BtcEngine.class.getSimpleName(); + public BtcData coinData = null; public BtcEngine(TangemContext context) throws Exception { @@ -235,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. "; @@ -250,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; @@ -276,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(); @@ -289,7 +302,7 @@ public class BtcEngine extends CoinEngine { // return; // } - // + // // if(card.isBalanceReceived() && !card.isBalanceEqual()) { // score = 0; // firstLine = "Disputed balance"; @@ -297,7 +310,13 @@ public class BtcEngine extends CoinEngine { // return; // } - return true; + return true; + } + catch (Exception e) + { + e.printStackTrace(); + return false; + } } @Override @@ -371,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"); } @@ -385,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]; @@ -431,8 +450,8 @@ public class BtcEngine extends CoinEngine { change = change - fees; } - final long amountFinal=amount; - final long changeFinal=change; + final long amountFinal = amount; + final long changeFinal = change; if (amount + fees > fullAmount) { throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); @@ -440,7 +459,7 @@ public class BtcEngine extends CoinEngine { final byte[][] txForSign = new byte[unspentOutputs.size()][]; final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][]; - final byte[][] bodyHash= new byte[unspentOutputs.size()][]; + final byte[][] bodyHash = new byte[unspentOutputs.size()][]; for (int i = 0; i < unspentOutputs.size(); ++i) { txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change); @@ -452,12 +471,12 @@ public class BtcEngine extends CoinEngine { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw; + return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; } @Override public byte[][] getHashesToSign() throws Exception { - byte[][] dataForSign=new byte[unspentOutputs.size()][]; + byte[][] dataForSign = new byte[unspentOutputs.size()][]; if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!"); for (int i = 0; i < unspentOutputs.size(); ++i) { dataForSign[i] = bodyDoubleHash[i]; @@ -488,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)); @@ -497,89 +516,309 @@ public class BtcEngine extends CoinEngine { unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); } - byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); + byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); notifyOnNeedSendPayment(txForSend); + return txForSend; } }; } -// @Override -// public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception { + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + + 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); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = electrumRequest.getResult().getLong("confirmed"); + Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed"); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(serverApiElectrum.getValidationNodeDescription()); + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance Exception"); + } + } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { + try { + String walletAddress = electrumRequest.getParams().getString(0); + JSONArray jsUnspentArray = electrumRequest.getResultArray(); + try { + coinData.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + coinData.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } + + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); + } else { + ctx.setError("Terminated by user"); + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { + try { + String txHash = electrumRequest.txHash; + String raw = electrumRequest.getResultString(); + for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { + if (tx.txID.equals(txHash)) + tx.Raw = raw; + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + 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(electrumListener); + + serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); + serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); + } + + private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { + //todo - правильней было бы использовать constructPayment + try { +// String myAddress = coinData.getWallet(); +// byte[] pbKey = ctx.getCard().getWalletPublicKey(); +// byte[] pbComprKey = ctx.getCard().getWalletPublicKeyRar(); // -// checkBlockchainDataExists(); +// // build script for our address +// List rawTxList = coinData.getUnspentTransactions(); +// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; // -// String myAddress = ctx.getCoinData().getWallet(); -// byte[] pbKey = ctx.getCard().getWalletPublicKey(); +// // collect unspent +// ArrayList unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); // -// // 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; +// Long fullAmount = 0L; +// for (int i = 0; i < unspentOutputs.size(); i++) { +// fullAmount += unspentOutputs.get(i).value; // } -// } // -// 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]); +// // 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)); // } -// 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); +// byte[][] hashesForSign = new byte[unspentOutputs.size()][]; // -// unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); -// } +// 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)) // -// return BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amount, change); -// } +//// 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); + + 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 + + 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; + +// 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; + } + } + + @Override + 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(); + + 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), BigDecimal.ROUND_DOWN); // 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; + } + + if(coinData.minFee!=null && coinData.normalFee!=null && coinData.maxFee!=null ) { + blockchainRequestsCallbacks.onComplete(true); + }else{ + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + 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); + + 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) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + final String txStr = BTCUtils.toHex(txForSend); + + 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 67942dc0c1..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 @@ -4,6 +4,8 @@ import android.net.Uri; import android.text.InputFilter; import android.util.Log; +import com.tangem.data.network.ServerApiInfura; +import com.tangem.data.network.model.InfuraResponse; import com.tangem.domain.wallet.BalanceValidator; import com.tangem.data.Blockchain; import com.tangem.domain.wallet.CoinData; @@ -57,7 +59,7 @@ public class EthEngine extends CoinEngine { } @Override - public boolean awaitingConfirmation(){ + public boolean awaitingConfirmation() { return false; } @@ -71,10 +73,10 @@ public class EthEngine extends CoinEngine { @Override public String getBalanceHTML() { - Amount balance=getBalance(); - if( balance!=null ) { + Amount balance = getBalance(); + if (balance != null) { return balance.toDescriptionString(getDecimals()); - }else{ + } else { return ""; } } @@ -93,7 +95,7 @@ public class EthEngine extends CoinEngine { @Override public boolean isBalanceNotZero() { - if( coinData ==null ) return false; + if (coinData == null) return false; if (coinData.getBalanceInInternalUnits() == null) return false; return coinData.getBalanceInInternalUnits().notZero(); } @@ -183,8 +185,8 @@ public class EthEngine extends CoinEngine { @Override public String getBalanceEquivalent() { - Amount balance=getBalance(); - if( balance==null ) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; return balance.toEquivalentString(coinData.getRate()); } @@ -200,8 +202,8 @@ public class EthEngine extends CoinEngine { } @Override - public InternalAmount convertToInternalAmount(Amount amount){ - return new InternalAmount(amount.multiply(new BigDecimal("1000000000000000000")),"wei"); + public InternalAmount convertToInternalAmount(Amount amount) { + return new InternalAmount(amount.multiply(new BigDecimal("1000000000000000000")), "wei"); } @Override @@ -218,7 +220,7 @@ public class EthEngine extends CoinEngine { @Override public boolean hasBalanceInfo() { - return coinData.getBalanceInInternalUnits()!=null; + return coinData.getBalanceInInternalUnits() != null; } @Override @@ -254,14 +256,14 @@ public class EthEngine extends CoinEngine { @Override public InputFilter[] getAmountInputFilters() { - return new InputFilter[] { new DecimalDigitsInputFilter(getDecimals()) }; + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; } @Override - public boolean checkNewTransactionAmount(Amount amount){ - if( coinData ==null ) return false; - Amount balance=getBalance(); - if (balance==null || amount.compareTo(balance) > 0) { + public boolean checkNewTransactionAmount(Amount amount) { + if (coinData == null) return false; + Amount balance = getBalance(); + if (balance == null || amount.compareTo(balance) > 0) { return false; } return true; @@ -292,7 +294,7 @@ public class EthEngine extends CoinEngine { try { BigDecimal cardBalance = getBalance(); - if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee)<0)) + if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee) < 0)) return false; if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0) @@ -346,9 +348,7 @@ public class EthEngine extends CoinEngine { try { Amount feeValue = new Amount(fee, ctx.getBlockchain().getCurrency()); return feeValue.toEquivalentString(coinData.getRate()); - } - catch (Exception e) - { + } catch (Exception e) { e.printStackTrace(); return ""; } @@ -380,8 +380,8 @@ public class EthEngine extends CoinEngine { 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); @@ -403,7 +403,7 @@ public class EthEngine 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 @@ -429,8 +429,8 @@ public class EthEngine 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); @@ -450,12 +450,160 @@ 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 + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiInfura serverApiInfura = new ServerApiInfura(); + // request infura listener + ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + switch (method) { + case ServerApiInfura.INFURA_ETH_GET_BALANCE: { + String balanceCap = infuraResponse.getResult(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + coinData.setBalanceReceived(true); + coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); + +// Log.i("$TAG eth_get_balance", balanceCap) + } + break; + + case ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT: { + String nonce = infuraResponse.getResult(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + coinData.setConfirmedTXCount(count); + + +// Log.i("$TAG eth_getTransCount", nonce) + } + break; + + case ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT: { + String pending = infuraResponse.getResult(); + pending = pending.substring(2); + BigInteger count = new BigInteger(pending, 16); + coinData.setUnconfirmedTXCount(count); + +// Log.i("$TAG eth_getPendingTxCount", pending) + } + break; + } + + if (serverApiInfura.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String method, String message) { + 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(), "", ""); + } + + @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) { + 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); + 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"); + + 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) { + ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); + 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); + + } + + // @Override // public byte[] sign(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/domain/wallet/token/TokenData.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenData.java index 3c653824ee..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 @@ -20,7 +20,6 @@ public class TokenData extends EthData { return balanceAlter; } - public void setBalanceAlterInInternalUnits(CoinEngine.InternalAmount value) { balanceAlter = value; } @@ -29,10 +28,10 @@ public class TokenData extends EthData { public void loadFromBundle(Bundle B) { super.loadFromBundle(B); - if (B.containsKey("BalanceDecimalAlter")) { + if( B.containsKey("BalanceDecimalAlter" )) { balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei"); - } else { - balanceAlter = null; + }else{ + balanceAlter=null; } } @@ -41,9 +40,7 @@ public class TokenData extends EthData { super.saveToBundle(B); try { - if (balanceAlter != null) { - B.putString("BalanceDecimalAlter", balanceAlter.toString()); - } + B.putString("BalanceDecimalAlter", balanceAlter.toString()); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } 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 1c62899d86..847603aa23 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 @@ -1,16 +1,20 @@ package com.tangem.domain.wallet.token; import android.net.Uri; +import android.os.Bundle; import android.text.InputFilter; import android.util.Log; import com.google.common.base.Strings; +import com.tangem.data.network.ServerApiInfura; +import com.tangem.data.network.model.InfuraResponse; import com.tangem.domain.wallet.BalanceValidator; import com.tangem.domain.wallet.CoinData; 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.eth.EthData; import com.tangem.tangemcard.data.TangemCard; import com.tangem.domain.wallet.TangemContext; import com.tangem.domain.wallet.BTCUtils; @@ -43,6 +47,13 @@ public class TokenEngine extends CoinEngine { ctx.setCoinData(coinData); } else if (ctx.getCoinData() instanceof TokenData) { coinData = (TokenData) ctx.getCoinData(); + } else if (ctx.getCoinData() instanceof EthData) { + // special case with receive card data substitution from server at the moment + Bundle B=new Bundle(); + ctx.getCoinData().saveToBundle(B); + coinData = new TokenData(); + coinData.loadFromBundle(B); + ctx.setCoinData(coinData); } else { throw new Exception("Invalid type of Blockchain data for TokenEngine"); } @@ -167,7 +178,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(); } @@ -182,7 +193,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) { @@ -339,7 +350,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 @@ -430,21 +441,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); @@ -466,7 +468,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 @@ -492,8 +494,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); @@ -513,7 +515,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; } }; } @@ -529,7 +533,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); @@ -570,7 +574,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 @@ -596,7 +600,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)); @@ -617,13 +621,182 @@ 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; } }; } + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiInfura serverApiInfura = new ServerApiInfura(); + // request infura listener + ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + switch (method) { + case ServerApiInfura.INFURA_ETH_GET_BALANCE: { + String balanceCap = infuraResponse.getResult(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + coinData.setBalanceReceived(true); + coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); + +// Log.i("$TAG eth_get_balance", balanceCap) + } + break; + + case ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT: { + String nonce = infuraResponse.getResult(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + coinData.setConfirmedTXCount(count); + + +// Log.i("$TAG eth_getTransCount", nonce) + } + break; + + case ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT: { + String pending = infuraResponse.getResult(); + pending = pending.substring(2); + BigInteger count = new BigInteger(pending, 16); + coinData.setUnconfirmedTXCount(count); + +// Log.i("$TAG eth_getPendingTxCount", pending) + } + break; +// + case ServerApiInfura.INFURA_ETH_CALL: { + try { + String balanceCap = infuraResponse.getResult(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + Long balance = l.longValue(); + coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); + +// Log.i("$TAG eth_call", balanceCap) + + 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 { + ctx.setError("Terminated by user"); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + break; + + } + if (serverApiInfura.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String method, String message) { + if (!serverApiInfura.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + serverApiInfura.setInfuraResponse(infuraBodyListener); + + 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(); // byte[] pbKey = ctx.getCard().getWalletPublicKey(); 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 bc43826b87..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,31 +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 org.json.JSONException -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 com.tangem.wallet.R.string.fee 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 { @@ -44,23 +33,20 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { private var nfcManager: NfcManager? = null - private var serverApiCommon: ServerApiCommon = ServerApiCommon() - private var serverApiInfura: ServerApiInfura = ServerApiInfura() - private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum() +// private var serverApiCommon: ServerApiCommon = ServerApiCommon() +// private var serverApiInfura: ServerApiInfura = ServerApiInfura() +// private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum() private lateinit var ctx: TangemContext private lateinit var amount: CoinEngine.Amount - 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 feeRequestSuccess = false + // private var balanceRequestSuccess = false private var isIncludeFee: Boolean = true private var requestPIN2Count = 0 private var nodeCheck = true private var dtVerified: Date? = null - private var calcSize: Int = 0 +// private var calcSize: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -86,7 +72,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { amount = CoinEngine.Amount(intent.getStringExtra(SignPaymentActivity.EXTRA_AMOUNT), intent.getStringExtra(SignPaymentActivity.EXTRA_AMOUNT_CURRENCY)) - if (ctx.blockchain == Blockchain.Token && amount.currency!="ETH") + if (ctx.blockchain == Blockchain.Token && amount.currency != "ETH") tvIncFee.visibility = View.INVISIBLE else tvIncFee.visibility = View.VISIBLE @@ -99,31 +85,24 @@ 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) { rgFee.isEnabled = false - requestInfura(ServerApiInfura.INFURA_ETH_GAS_PRICE) - - } else if (ctx.blockchain == Blockchain.BitcoinCash) { - rgFee.isEnabled = false - - progressBar.visibility = View.VISIBLE - - requestElectrum(ctx, ElectrumRequest.getFee()) +// requestInfura(ServerApiInfura.INFURA_ETH_GAS_PRICE) } else { rgFee.isEnabled = true // requestElectrum(ctx.card, ElectrumRequest.checkBalance(ctx.card!!.wallet)) - ctx.coinData!!.resetFailedBalanceRequestCounter() +// ctx.coinData!!.resetFailedBalanceRequestCounter() - progressBar.visibility = View.VISIBLE +// progressBar.visibility = View.VISIBLE - requestEstimateFee() +// requestEstimateFee() } // set listeners @@ -201,146 +180,166 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2) } - // request electrum listener - val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener { - override fun onSuccess(electrumRequest: ElectrumRequest?) { - var fee: BigDecimal - if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetFee)) { - try { - //if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty)) - fee = BigDecimal(electrumRequest.resultString) //fee per KB + val coinEngine = CoinEngineFactory.create(ctx) - if (fee == BigDecimal.ZERO) { - requestElectrum(ctx, ElectrumRequest.getFee()) - } + progressBar.visibility = View.VISIBLE - if (calcSize.toLong() != 0L) { - fee = fee.multiply(BigDecimal(calcSize.toLong())).divide(BigDecimal(1024)) // (per KB -> per byte)*size + coinEngine!!.requestFee( + object : CoinEngine.BlockchainRequestsCallbacks { + override fun onComplete(success: Boolean) { + if (success) { + + onProgress() + +// etFee.error = null + +// feeRequestSuccess = true + // balanceRequestSuccess = true + progressBar.visibility = View.INVISIBLE + dtVerified = Date() } else { - requestElectrum(ctx, ElectrumRequest.getFee()) + finishWithError(Activity.RESULT_CANCELED, ctx.error) } - - progressBar.visibility = View.INVISIBLE - val relayFee : BigDecimal = BigDecimal(0.00001) - - //compare fee to usual relay fee - if (fee.compareTo(relayFee) == -1) { - fee = relayFee - } - fee = fee.setScale(8, RoundingMode.DOWN) - - var feeAmount: CoinEngine.Amount = CoinEngine.Amount(fee, "BCH") - minFee = feeAmount - normalFee = feeAmount - maxFee = feeAmount - doSetFee(rgFee.checkedRadioButtonId) - etFee.error = null - btnSend.visibility = View.VISIBLE - feeRequestSuccess = true - dtVerified = Date() - - } catch (e: JSONException) { - e.printStackTrace() } - } - } - override fun onFail(message: String?) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes)) - } + override fun onProgress() { + doSetFee(rgFee.checkedRadioButtonId) + } - } - serverApiElectrum.setElectrumRequestData(electrumBodyListener) + override fun allowAdvance(): Boolean { + return UtilHelper.isOnline(this@ConfirmPaymentActivity) + } + }, + etWallet.text.toString(), + amount) + + + // request electrum listener +// val electrumBodyListener: ServerApiHelperElectrum.ElectrumRequestDataListener = object : ServerApiHelperElectrum.ElectrumRequestDataListener { +// override fun onSuccess(electrumRequest: ElectrumRequest?) { +// if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) { +// try { +// if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty)) +// val engine = CoinEngineFactory.create(ctx) +// val balance = engine.convertToAmount(CoinEngine.InternalAmount(electrumRequest.result.getLong("confirmed") + electrumRequest.result.getLong("unconfirmed"), "Satoshi")) +// val amount = CoinEngine.Amount(etAmount.text.toString(), ctx.blockchain.currency) +// if (balance < amount) { +// etFee.error = getString(R.string.not_enough_funds) +// } else { +// etFee.error = null +// balanceRequestSuccess = true +// if (feeRequestSuccess && balanceRequestSuccess) { +// btnSend.visibility = View.VISIBLE +// } +// dtVerified = Date() +// nodeCheck = true +// } +// } catch (e: JSONException) { +// e.printStackTrace() +//// requestElectrum(ctx.card!!, ElectrumRequest.checkBalance(ctx.card!!.wallet)) +// } +// } +// } +// +// override fun onFail(message: String?) { +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes)) +// } +// +// } +// serverApiHelperElectrum.setElectrumRequestData(electrumBodyListener) // request infura eth gasPrice listener - val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener { - override fun onSuccess(method: String, infuraResponse: InfuraResponse) { - when (method) { - ServerApiInfura.INFURA_ETH_GAS_PRICE -> { - var gasPrice = infuraResponse.result - gasPrice = gasPrice.substring(2) - //TODO - remove Gwei - // rounding gas price to integer gwei - val l = BigInteger(gasPrice, 16).divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L)) +// val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener { +// override fun onSuccess(method: String, infuraResponse: InfuraResponse) { +// when (method) { +// ServerApiInfura.INFURA_ETH_GAS_PRICE -> { +// var gasPrice = infuraResponse.result +// gasPrice = gasPrice.substring(2) +// //TODO - remove Gwei +// // rounding gas price to integer gwei +// val l = 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) +// val m = if (amount.currency != "ETH") BigInteger.valueOf(60000) else BigInteger.valueOf(21000) +// val weiMinFee = CoinEngine.InternalAmount(l.multiply(m), "wei") +// val weiNormalFee = CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(12)).divide(BigDecimal.valueOf(10)), "wei") +// val weiMaxFee = CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(15)).divide(BigDecimal.valueOf(10)), "wei") +// +// minFee = engine.convertToAmount(weiMinFee) +// normalFee = engine.convertToAmount(weiNormalFee) +// maxFee = engine.convertToAmount(weiMaxFee) +// doSetFee(rgFee.checkedRadioButtonId) +// //etFee.setText(weiNormalFee.toValueString()) +// etFee.error = null +// btnSend.visibility = View.VISIBLE +// feeRequestSuccess = true +//// balanceRequestSuccess = true +// dtVerified = Date() +// } +// } +// } +// +// override fun onFail(method: String, message: String) { +// when (method) { +// ServerApiInfura.INFURA_ETH_GAS_PRICE -> { +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) +// } +// } +// } +// } +// serverApiInfura.setInfuraResponse(infuraBodyListener) - //val m = if (ctx.blockchain==Blockchain.Token) BigInteger.valueOf(60000) else BigInteger.valueOf(21000) - val m = if (amount.currency != "ETH") BigInteger.valueOf(60000) else BigInteger.valueOf(21000) - val weiMinFee = CoinEngine.InternalAmount(l.multiply(m), "wei") - val weiNormalFee = CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(12)).divide(BigDecimal.valueOf(10)), "wei") - val weiMaxFee = CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(15)).divide(BigDecimal.valueOf(10)), "wei") - - minFee = engine.convertToAmount(weiMinFee) - normalFee = engine.convertToAmount(weiNormalFee) - maxFee = engine.convertToAmount(weiMaxFee) - doSetFee(rgFee.checkedRadioButtonId) - //etFee.setText(weiNormalFee.toValueString()) - etFee.error = null - btnSend.visibility = View.VISIBLE - feeRequestSuccess = true - dtVerified = Date() - } - } - } - - override fun onFail(method: String, message: String) { - when (method) { - ServerApiInfura.INFURA_ETH_GAS_PRICE -> { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) - } - } - } - } - serverApiInfura.setInfuraResponse(infuraBodyListener) - - // request estimate fee listener - val estimateFeeListener: ServerApiCommon.EstimateFeeListener = object : ServerApiCommon.EstimateFeeListener { - override fun onSuccess(blockCount: Int, estimateFeeResponse: String?) { - var fee: BigDecimal - fee = BigDecimal(estimateFeeResponse) // BTC per 1 kb - - if (fee == BigDecimal.ZERO) { - progressBar.visibility = View.INVISIBLE - requestEstimateFee() - } - - if (calcSize.toLong() != 0L) { - fee = fee.multiply(BigDecimal(calcSize.toLong())).divide(BigDecimal(1024)) // per Kb -> per byte - } else { - requestEstimateFee() - } - - progressBar.visibility = View.INVISIBLE - - fee = fee.setScale(8, RoundingMode.DOWN) - - when (blockCount) { - ServerApiCommon.ESTIMATE_FEE_MINIMAL -> { - minFee = CoinEngine.Amount(fee, engine.feeCurrency) - if (rgFee.checkedRadioButtonId == R.id.rbMinimalFee) doSetFee(rgFee.checkedRadioButtonId) - } - - ServerApiCommon.ESTIMATE_FEE_NORMAL -> { - normalFee = CoinEngine.Amount(fee, engine.feeCurrency) - if (rgFee.checkedRadioButtonId == R.id.rbNormalFee) doSetFee(rgFee.checkedRadioButtonId) - } - - ServerApiCommon.ESTIMATE_FEE_PRIORITY -> { - maxFee = CoinEngine.Amount(fee, engine.feeCurrency) - if (rgFee.checkedRadioButtonId == R.id.rbMaximumFee) doSetFee(rgFee.checkedRadioButtonId) - } - } - - etFee.error = null - feeRequestSuccess = true - btnSend.visibility = View.VISIBLE - dtVerified = Date() - } - - override fun onFail(message: String?) { - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)) - } - } - serverApiCommon.setEstimateFee(estimateFeeListener) +// // request estimate fee listener +// val estimateFeeListener: ServerApiCommon.EstimateFeeListener = object : ServerApiCommon.EstimateFeeListener { +// override fun onSuccess(blockCount: Int, estimateFeeResponse: String?) { +// var fee: BigDecimal? +// fee = BigDecimal(estimateFeeResponse) // BTC per 1 kb +// +// if (fee == BigDecimal.ZERO) { +// progressBar.visibility = View.INVISIBLE +// requestEstimateFee() +// } +// +// if (calcSize.toLong() != 0L) { +// fee = fee.multiply(BigDecimal(calcSize.toLong())).divide(BigDecimal(1024)) // per Kb -> per byte +// } else { +// requestEstimateFee() +// } +// +// progressBar.visibility = View.INVISIBLE +// +// fee = fee!!.setScale(8, RoundingMode.DOWN) +// +// when (blockCount) { +// ServerApiCommon.ESTIMATE_FEE_MINIMAL -> { +// minFee = CoinEngine.Amount(fee, engine.feeCurrency) +// if (rgFee.checkedRadioButtonId == R.id.rbMinimalFee) doSetFee(rgFee.checkedRadioButtonId) +// } +// +// ServerApiCommon.ESTIMATE_FEE_NORMAL -> { +// normalFee = CoinEngine.Amount(fee, engine.feeCurrency) +// if (rgFee.checkedRadioButtonId == R.id.rbNormalFee) doSetFee(rgFee.checkedRadioButtonId) +// } +// +// ServerApiCommon.ESTIMATE_FEE_PRIORITY -> { +// maxFee = CoinEngine.Amount(fee, engine.feeCurrency) +// if (rgFee.checkedRadioButtonId == R.id.rbMaximumFee) doSetFee(rgFee.checkedRadioButtonId) +// } +// } +// +// etFee.error = null +// feeRequestSuccess = true +// if (feeRequestSuccess) +//// if (feeRequestSuccess && balanceRequestSuccess) +// btnSend.visibility = View.VISIBLE +// dtVerified = Date() +// } +// +// override fun onFail(message: String?) { +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)) +// } +// } +// serverApiCommon.setEstimateFee(estimateFeeListener) } public override fun onResume() { @@ -416,131 +415,64 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } // TODO - move to BtcEngine - @Throws(Exception::class) - internal fun buildSize(outputAddress: String, outFee: String, outAmount: String): Int { - val myAddress = ctx.coinData!!.wallet - val pbKey = ctx.card!!.walletPublicKey - val pbComprKey = ctx.card!!.walletPublicKeyRar +// @Throws(Exception::class) - // build script for our address - val rawTxList = (ctx.coinData!! as BtcData).unspentTransactions - val outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes +// private fun requestElectrum(ctx: TangemContext, electrumRequest: ElectrumRequest) { +// if (UtilHelper.isOnline(this)) { +// serverApiElectrum.electrumRequestData(ctx, electrumRequest) +// } else +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) +// } - // collect unspent - val unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend) +// private fun requestInfura(method: String) { +// if (UtilHelper.isOnline(this)) { +// serverApiInfura.infura(method, 67, ctx.coinData!!.wallet, "", "") +// } else +// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) +// } - var fullAmount: Long = 0 - for (i in unspentOutputs.indices) { - fullAmount += unspentOutputs[i].value - } +// private fun requestEstimateFee() { +// if (calcSize == 0) { +// calcSize = 256 +// try { +// +// calcSize = buildSize(etWallet!!.text.toString(), "0.00", etAmount.text.toString()) +// } catch (ex: Exception) { +// Log.e("Build Fee error", ex.message) +// } +// } +// serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY) +// serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_NORMAL) +// serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL) +// } - // get first unspent -// val outPut = unspentOutputs[0] -// val outPutIndex = outPut.outputIndex - - // get prev TX id; -// val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2"; - - val fees = FormatUtil.ConvertStringToLong(outFee) - var amount = FormatUtil.ConvertStringToLong(outAmount) - amount -= fees - - val change = fullAmount - fees - amount - - if (amount + fees > fullAmount) { - throw Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount)) - } - - val hashesForSign = arrayOfNulls(unspentOutputs.size) - - for (i in unspentOutputs.indices) { - val newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change) - val hashData = Util.calculateSHA256(newTX) - val 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 - } - - val signFromCard = ByteArray(64 * unspentOutputs.size) - - for (i in unspentOutputs.indices) { - val r = BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64)) - val s = BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)) - val encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey) - unspentOutputs[i].scriptForBuild = encodingSign - } - - val realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change) - - return realTX.size - } - - private fun requestElectrum(ctx: TangemContext, electrumRequest: ElectrumRequest) { - if( calcSize==0 ) - { - calcSize = 256 - try { - - calcSize = buildSize(etWallet!!.text.toString(), "0.00", etAmount.text.toString()) - } catch (ex: Exception) { - Log.e("Build Fee error", ex.message) - } - } - if (UtilHelper.isOnline(this)) { - serverApiElectrum.electrumRequestData(ctx, electrumRequest) - } else - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) - } - - private fun requestInfura(method: String) { - if (UtilHelper.isOnline(this)) { - serverApiInfura.infura(method, 67, ctx.coinData!!.wallet, "", "") - } else - finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain)) - } - - private fun requestEstimateFee() { - if( calcSize==0 ) - { - calcSize = 256 - try { - - calcSize = buildSize(etWallet!!.text.toString(), "0.00", etAmount.text.toString()) - } catch (ex: Exception) { - Log.e("Build Fee error", ex.message) - } - } - serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY) - serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_NORMAL) - serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL) - } - - private fun - - - doSetFee(checkedRadioButtonId: Int) { + private fun doSetFee(checkedRadioButtonId: Int) { 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/MainActivity.kt b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt index 86e7dbe7d3..7a9f10344d 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt @@ -271,7 +271,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco } catch (e: Exception) { e.printStackTrace() - nfcManager!!.notifyReadResult(false) + nfcManager.notifyReadResult(false) } } @@ -279,11 +279,11 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco super.onResume() animate() ReadCardInfoTask.resetLastReadInfo() - nfcManager!!.onResume() + nfcManager.onResume() } public override fun onPause() { - nfcManager!!.onPause() + nfcManager.onPause() if (readCardInfoTask != null) { readCardInfoTask!!.cancel(true) } @@ -292,7 +292,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco public override fun onStop() { // dismiss enable NFC dialog - nfcManager!!.onStop() + nfcManager.onStop() if (readCardInfoTask != null) { readCardInfoTask!!.cancel(true) } 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 f96fc86169..96cd64cf83 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -22,20 +22,11 @@ import android.widget.Toast import com.tangem.App import com.tangem.Constant import com.tangem.data.Blockchain -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.domain.wallet.BalanceValidator import com.tangem.domain.wallet.CoinEngine import com.tangem.domain.wallet.CoinEngineFactory import com.tangem.domain.wallet.TangemContext -import com.tangem.domain.wallet.bch.BtcCashEngine -import com.tangem.domain.wallet.btc.BtcData -import com.tangem.domain.wallet.eth.EthData -import com.tangem.domain.wallet.token.TokenData -import com.tangem.domain.wallet.token.TokenEngine import com.tangem.presentation.activity.* import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog import com.tangem.presentation.dialog.PINSwapWarningDialog @@ -55,9 +46,7 @@ import com.tangem.tangemserver.android.model.CardVerifyAndGetInfo import com.tangem.util.UtilHelper import com.tangem.wallet.R import kotlinx.android.synthetic.main.fr_loaded_wallet.* -import org.json.JSONException import java.io.InputStream -import java.math.BigInteger import java.util.* class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notifications, SharedPreferences.OnSharedPreferenceChangeListener { @@ -68,8 +57,6 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific private lateinit var nfcManager: NfcManager private var serverApiCommon: ServerApiCommon = ServerApiCommon() - private var serverApiInfura: ServerApiInfura = ServerApiInfura() - private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum() private var serverApiTangem: ServerApiTangem = ServerApiTangem() private var singleToast: Toast? = null @@ -85,7 +72,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?) { @@ -114,10 +111,6 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific btnExtract.isEnabled = false btnExtract.backgroundTintList = inactiveColor - refresh() - - startVerify(lastTag) - tvWallet.text = ctx.coinData.wallet // set listeners @@ -193,7 +186,6 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific if (cardProtocol != null) // openVerifyCard(cardProtocol!!) (activity as LoadedWalletActivity).navigator.showVerifyCard(context as Activity, ctx) - else showSingleToast(R.string.need_attach_card_again) } @@ -206,213 +198,215 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific else if (ctx.card!!.remainingSignatures == 0) showSingleToast(R.string.card_has_no_remaining_signature) else { - (activity as LoadedWalletActivity).navigator.showPreparePayment(context as Activity, ctx) -// val intent = Intent(activity, PreparePaymentActivity::class.java) -// ctx.saveToIntent(intent) -// startActivityForResult(intent, Constant.REQUEST_CODE_SEND_PAYMENT) + val intent = Intent(activity, PreparePaymentActivity::class.java) + ctx.saveToIntent(intent) + startActivityForResult(intent, Constant.REQUEST_CODE_SEND_PAYMENT) } } else Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() } // request electrum listener - val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener { - override fun onSuccess(electrumRequest: ElectrumRequest?) { - if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) { - try { - val walletAddress = electrumRequest.params.getString(0) - val confBalance = electrumRequest.result.getLong("confirmed") - val unconfirmedBalance = electrumRequest.result.getLong("unconfirmed") - ctx.coinData!!.isBalanceReceived = true - (ctx.coinData!! as BtcData).setBalanceConfirmed(confBalance) - (ctx.coinData!! as BtcData).balanceUnconfirmed = unconfirmedBalance - (ctx.coinData!! as BtcData).validationNodeDescription = serverApiElectrum.validationNodeDescription - } catch (e: JSONException) { - e.printStackTrace() - Log.e(TAG, "FAIL METHOD_GetBalance JSONException") - } - } - - if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { - try { - val walletAddress = electrumRequest.params.getString(0) - val jsUnspentArray = electrumRequest.resultArray - try { - (ctx.coinData!! as BtcData).unspentTransactions.clear() - for (i in 0 until jsUnspentArray.length()) { - val jsUnspent = jsUnspentArray.getJSONObject(i) - val trUnspent = BtcData.UnspentTransaction() - trUnspent.txID = jsUnspent.getString("tx_hash") - trUnspent.Amount = jsUnspent.getInt("value") - trUnspent.Height = jsUnspent.getInt("height") - (ctx.coinData!! as BtcData).unspentTransactions.add(trUnspent) - } - } catch (e: JSONException) { - e.printStackTrace() - Log.e(TAG, "FAIL METHOD_ListUnspent JSONException") - } - - for (i in 0 until jsUnspentArray.length()) { - val jsUnspent = jsUnspentArray.getJSONObject(i) - val height = jsUnspent.getInt("height") - val hash = jsUnspent.getString("tx_hash") - if (height != -1) { - requestElectrum(ElectrumRequest.getTransaction(walletAddress, hash)) - } - } - } catch (e: JSONException) { - e.printStackTrace() - } - } - - if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { - try { - val txHash = electrumRequest.txHash - val raw = electrumRequest.resultString - val listTx = (ctx.coinData!! as BtcData).unspentTransactions - for (tx in listTx) { - if (tx.txID == txHash) - tx.Raw = raw - } - } catch (e: JSONException) { - e.printStackTrace() - } - } - - if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { - - } - - counterMinus() - } - - override fun onFail(method: String?) { - - } - } - 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_GET_BALANCE -> { - var balanceCap = infuraResponse.result - balanceCap = balanceCap.substring(2) - val l = BigInteger(balanceCap, 16) -// val d = l.divide(BigInteger("1000000000000000000", 10)) -// val balance = d.toLong() - -// (ctx.coinData!! as EthData).setBalanceConfirmed(balance) -// (ctx.coinData!! as EthData).balanceUnconfirmed = 0L - if (ctx.blockchain != Blockchain.Token) { - (ctx.coinData!! as EthData).isBalanceReceived = true - (ctx.coinData!! as EthData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(), "wei") - } else { - (ctx.coinData!! as TokenData).isBalanceReceived = true - //(ctx.coinData!! as TokenData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(),ctx.card.tokenSymbol) - (ctx.coinData!! as TokenData).balanceAlterInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(), "wei") - } - -// Log.i("$TAG eth_get_balance", balanceCap) - } - - ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT -> { - var nonce = infuraResponse.result - nonce = nonce.substring(2) - val count = BigInteger(nonce, 16) - (ctx.coinData!! as EthData).confirmedTXCount = count - - -// Log.i("$TAG eth_getTransCount", nonce) - } - - ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT -> { - var pending = infuraResponse.result - pending = pending.substring(2) - val count = BigInteger(pending, 16) - (ctx.coinData!! as EthData).unconfirmedTXCount = count - -// Log.i("$TAG eth_getPendingTxCount", pending) - } - - ServerApiInfura.INFURA_ETH_CALL -> { - try { - var balanceCap = infuraResponse.result - balanceCap = balanceCap.substring(2) - val l = BigInteger(balanceCap, 16) - val balance = l.toLong() -// if (l.compareTo(BigInteger.ZERO) == 0) { -// //ctx.card!!.blockchainID = Blockchain.Ethereum.id -// ctx.card!!.addTokenToBlockchainName() +// val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener { +// override fun onSuccess(electrumRequest: ElectrumRequest?) { +// if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) { +// try { +// val walletAddress = electrumRequest.params.getString(0) +// val confBalance = electrumRequest.result.getLong("confirmed") +// val unconfirmedBalance = electrumRequest.result.getLong("unconfirmed") +// ctx.coinData!!.isBalanceReceived = true +// (ctx.coinData!! as BtcData).setBalanceConfirmed(confBalance) +// (ctx.coinData!! as BtcData).balanceUnconfirmed = unconfirmedBalance +// (ctx.coinData!! as BtcData).validationNodeDescription = serverApiElectrum.validationNodeDescription +// } catch (e: JSONException) { +// e.printStackTrace() +// Log.e(TAG, "FAIL METHOD_GetBalance JSONException") +// } +// } // -// //TODO check -// //ctx.blockchain=lBlockchain.Ethereum +// if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { +// try { +// val walletAddress = electrumRequest.params.getString(0) +// val jsUnspentArray = electrumRequest.resultArray +// try { +// (ctx.coinData!! as BtcData).unspentTransactions.clear() +// for (i in 0 until jsUnspentArray.length()) { +// val jsUnspent = jsUnspentArray.getJSONObject(i) +// val trUnspent = BtcData.UnspentTransaction() +// trUnspent.txID = jsUnspent.getString("tx_hash") +// trUnspent.Amount = jsUnspent.getInt("value") +// trUnspent.Height = jsUnspent.getInt("height") +// (ctx.coinData!! as BtcData).unspentTransactions.add(trUnspent) +// } +// } catch (e: JSONException) { +// e.printStackTrace() +// Log.e(TAG, "FAIL METHOD_ListUnspent JSONException") +// } // -// requestCounter-- -// if (requestCounter == 0) srl!!.isRefreshing = false +// for (i in 0 until jsUnspentArray.length()) { +// val jsUnspent = jsUnspentArray.getJSONObject(i) +// val height = jsUnspent.getInt("height") +// val hash = jsUnspent.getString("tx_hash") +// if (height != -1) { +// requestElectrum(ElectrumRequest.getTransaction(walletAddress, hash)) +// } +// } +// } catch (e: JSONException) { +// e.printStackTrace() +// } +// } // -// requestInfura(ServerApiCommon.INFURA_ETH_GET_BALANCE, "") -// requestInfura(ServerApiCommon.INFURA_ETH_GET_TRANSACTION_COUNT, "") -// requestInfura(ServerApiCommon.INFURA_ETH_GET_PENDING_COUNT, "") +// if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { +// try { +// val txHash = electrumRequest.txHash +// val raw = electrumRequest.resultString +// val listTx = (ctx.coinData!! as BtcData).unspentTransactions +// for (tx in listTx) { +// if (tx.txID == txHash) +// tx.Raw = raw +// } +// } catch (e: JSONException) { +// e.printStackTrace() +// } +// } +// +// if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { +// +// } +// +// counterMinus() +// } +// +// override fun onFail(method: String?) { +// +// } +// } +// 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_GET_BALANCE -> { +// var balanceCap = infuraResponse.result +// balanceCap = balanceCap.substring(2) +// val l = BigInteger(balanceCap, 16) +//// val d = l.divide(BigInteger("1000000000000000000", 10)) +//// val balance = d.toLong() +// +//// (ctx.coinData!! as EthData).setBalanceConfirmed(balance) +//// (ctx.coinData!! as EthData).balanceUnconfirmed = 0L +// if (ctx.blockchain != Blockchain.Token) { +// (ctx.coinData!! as EthData).isBalanceReceived = true +// (ctx.coinData!! as EthData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(), "wei") +// } else { +// (ctx.coinData!! as TokenData).isBalanceReceived = true +// //(ctx.coinData!! as TokenData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(),ctx.card.tokenSymbol) +// (ctx.coinData!! as TokenData).balanceAlterInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(), "wei") +// } +// +//// Log.i("$TAG eth_get_balance", balanceCap) +// } +// +// ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT -> { +// var nonce = infuraResponse.result +// nonce = nonce.substring(2) +// val count = BigInteger(nonce, 16) +// (ctx.coinData!! as EthData).confirmedTXCount = count +// +// +//// Log.i("$TAG eth_getTransCount", nonce) +// } +// +// ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT -> { +// var pending = infuraResponse.result +// pending = pending.substring(2) +// val count = BigInteger(pending, 16) +// (ctx.coinData!! as EthData).unconfirmedTXCount = count +// +//// Log.i("$TAG eth_getPendingTxCount", pending) +// } +// +// ServerApiInfura.INFURA_ETH_CALL -> { +// try { +// var balanceCap = infuraResponse.result +// balanceCap = balanceCap.substring(2) +// val l = BigInteger(balanceCap, 16) +// val balance = l.toLong() +//// 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 +//// } +// (ctx.coinData!! as EthData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(), ctx.card.tokenSymbol) +// +//// Log.i("$TAG eth_call", balanceCap) +// +// requestInfura(ServerApiInfura.INFURA_ETH_GET_BALANCE, "") +// requestInfura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, "") +// requestInfura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, "") +// } catch (e: JSONException) { +// e.printStackTrace() +// } catch (e: NumberFormatException) { +// e.printStackTrace() +// } catch (e: Exception) { +// e.printStackTrace() +// } +// } +// +// ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> { +// try { +// var hashTX: String +// try { +// val tmp = infuraResponse.result +// hashTX = tmp +// } catch (e: JSONException) { // return // } - (ctx.coinData!! as EthData).balanceInInternalUnits = CoinEngine.InternalAmount(l.toBigDecimal(), ctx.card.tokenSymbol) - -// Log.i("$TAG eth_call", balanceCap) - - requestInfura(ServerApiInfura.INFURA_ETH_GET_BALANCE, "") - requestInfura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, "") - requestInfura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, "") - } catch (e: JSONException) { - e.printStackTrace() - } catch (e: NumberFormatException) { - e.printStackTrace() - } catch (e: Exception) { - e.printStackTrace() - } - } - - ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> { - try { - var hashTX: String - try { - val tmp = infuraResponse.result - hashTX = tmp - } catch (e: JSONException) { - return - } - - if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { - hashTX = hashTX.substring(2) - } - - Log.e("$TAG TX_RESULT", hashTX) - - val nonce = (ctx.coinData!! as EthData).confirmedTXCount - nonce.add(BigInteger.valueOf(1)) - (ctx.coinData!! as EthData).confirmedTXCount = nonce - - Log.e("$TAG TX_RESULT", hashTX) - - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - counterMinus() - } - - override fun onFail(method: String, message: String) { - - } - } - serverApiInfura.setInfuraResponse(infuraBodyListener) +// +// if (hashTX.startsWith("0x") || hashTX.startsWith("0X")) { +// hashTX = hashTX.substring(2) +// } +// +// Log.e("$TAG TX_RESULT", hashTX) +// +// val nonce = (ctx.coinData!! as EthData).confirmedTXCount +// nonce.add(BigInteger.valueOf(1)) +// (ctx.coinData!! as EthData).confirmedTXCount = nonce +// +// Log.e("$TAG TX_RESULT", hashTX) +// +// } catch (e: Exception) { +// e.printStackTrace() +// } +// } +// } +// +// counterMinus() +// } +// +// override fun onFail(method: String, message: String) { +// +// } +// } +// serverApiInfura.setInfuraResponse(infuraBodyListener) // 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") + if( activity==null || !UtilHelper.isOnline(activity!!)) return + val result = cardVerifyAndGetArtworkResponse?.results!![0] if (result.error != null) { ctx.card!!.isOnlineVerified = false @@ -420,7 +414,10 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } ctx.card!!.isOnlineVerified = result.passed - if (requestCounter == 0) updateViews() + + requestCounter-- +// if (requestCounter == 0) + updateViews() if (!result.passed) return @@ -428,17 +425,11 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific Log.w(TAG, "Batch ${result.batch} info changed to '$result'") ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!)) App.localStorage.applySubstitution(ctx.card!!) - //todo - check this is not need after refactoring -// if (ctx.blockchain == Blockchain.Token || ctx.blockchain == Blockchain.Ethereum) { -// ctx.card!!.setBlockchainIDFromCard(Blockchain.Ethereum.id) - - //ctx.blockchain=Blockchain.Ethereum - //engine=engine!!.swithToOtherEngine(Blockchain.Ethereum) -// } refresh() } 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() } @@ -446,7 +437,10 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } override fun onFail(message: String?) { - + Log.i(TAG,"cardVerifyAndGetInfoListener onFail") + if( activity==null || !UtilHelper.isOnline(activity!!)) return + requestCounter-- + updateViews() } } serverApiTangem.setCardVerifyAndGetInfoListener(cardVerifyAndGetInfoListener) @@ -454,47 +448,51 @@ 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") + if( activity==null || !UtilHelper.isOnline(activity!!)) return 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") + if( activity==null || !UtilHelper.isOnline(activity!!)) return + requestCounter-- + updateViews() } } serverApiTangem.setArtworkListener(artworkListener) // request rate info listener serverApiCommon.setRateInfoData { + if( activity==null || !UtilHelper.isOnline(activity!!)) return@setRateInfoData val rate = it.priceUsd.toFloat() ctx.coinData!!.rate = rate ctx.coinData!!.rateAlter = rate } - } - private fun counterMinus() { - requestCounter-- - if (requestCounter == 0) { - if (srl != null) srl!!.isRefreshing = false - updateViews() - } + refresh() + + startVerify(lastTag) } override fun onResume() { super.onResume() - nfcManager!!.onResume() + nfcManager.onResume() } override fun onPause() { super.onPause() - nfcManager!!.onPause() + nfcManager.onPause() if (timerRepeatRefresh != null) timerRepeatRefresh!!.cancel() } override fun onStop() { super.onStop() - nfcManager!!.onStop() + nfcManager.onStop() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -727,17 +725,19 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } fun updateViews() { + if( activity==null || !UtilHelper.isOnline(activity!!)) return + if (timerHideErrorAndMessage != null) { timerHideErrorAndMessage!!.cancel() 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()) { @@ -785,7 +785,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific } else tvBlockchain.text = ctx.blockchainName - if (engine.hasBalanceInfo()) { + if (requestCounter==0 && engine.hasBalanceInfo()) { btnExtract.isEnabled = true btnExtract.backgroundTintList = activeColor } else { @@ -793,95 +793,131 @@ 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() requestVerifyAndGetInfo() + val coinEngine = CoinEngineFactory.create(ctx) + requestCounter++ + coinEngine!!.requestBalanceAndUnspentTransactions( + object : CoinEngine.BlockchainRequestsCallbacks { + override fun onComplete(success: Boolean) { + Log.i(TAG, "requestBalanceAndUnspentTransactions onComplete: "+success.toString()+", request counter "+requestCounter.toString()) + if( activity==null || !UtilHelper.isOnline(activity!!)) return + requestCounter-- + if(! success) + { + Log.e(TAG, "ctx.error: "+ctx.error) + } + updateViews() + } + + override fun onProgress() { + if( activity==null || !UtilHelper.isOnline(activity!!)) return + Log.i(TAG, "requestBalanceAndUnspentTransactions onProgress") + updateViews() + } + + override fun allowAdvance(): Boolean { + return UtilHelper.isOnline(context as Activity) + } + } + ) + + // Bitcoin if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet) { ctx.coinData.setIsBalanceEqual(true) - requestElectrum(ElectrumRequest.checkBalance(ctx.coinData!!.wallet)) - requestElectrum(ElectrumRequest.listUnspent(ctx.coinData!!.wallet)) +// requestElectrum(ElectrumRequest.checkBalance(ctx.coinData!!.wallet)) +// requestElectrum(ElectrumRequest.listUnspent(ctx.coinData!!.wallet)) requestRateInfo("bitcoin") } // BitcoinCash else if (ctx.blockchain == Blockchain.BitcoinCash) { ctx.coinData.setIsBalanceEqual(true) - val engine = CoinEngineFactory.create(ctx) - - requestElectrum(ElectrumRequest.checkBalance((engine as BtcCashEngine).convertToLegacyAddress(ctx.coinData!!.wallet))) - requestElectrum(ElectrumRequest.listUnspent(engine.convertToLegacyAddress(ctx.coinData!!.wallet))) +// val engine = CoinEngineFactory.create(ctx) +// +// requestElectrum(ElectrumRequest.checkBalance((engine as BtcCashEngine).convertToLegacyAddress(ctx.coinData!!.wallet))) +// requestElectrum(ElectrumRequest.listUnspent(engine.convertToLegacyAddress(ctx.coinData!!.wallet))) requestRateInfo("bitcoin-cash") } // Ethereum else if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet) { - requestInfura(ServerApiInfura.INFURA_ETH_GET_BALANCE, "") - requestInfura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, "") - requestInfura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, "") +// requestInfura(ServerApiInfura.INFURA_ETH_GET_BALANCE, "") +// requestInfura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, "") +// requestInfura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, "") requestRateInfo("ethereum") } // Token else if (ctx.blockchain == Blockchain.Token) { - val engine = CoinEngineFactory.create(ctx) - requestInfura(ServerApiInfura.INFURA_ETH_CALL, (engine as TokenEngine).getContractAddress(ctx.card)) +// val engine = CoinEngineFactory.create(ctx) +// requestInfura(ServerApiInfura.INFURA_ETH_CALL, (engine as TokenEngine).getContractAddress(ctx.card)) requestRateInfo("ethereum") } } - private fun requestElectrum(electrumRequest: ElectrumRequest) { - if (UtilHelper.isOnline(context as Activity)) { - requestCounter++ - serverApiElectrum.electrumRequestData(ctx, electrumRequest) - } else { - Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() - srl?.isRefreshing = false - } - } +// private fun requestElectrum(electrumRequest: ElectrumRequest) { +// if (UtilHelper.isOnline(context as Activity)) { +// requestCounter++ +// serverApiElectrum.electrumRequestData(ctx, electrumRequest) +// } else { +// Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() +// srl?.isRefreshing = false +// } +// } - private fun requestInfura(method: String, contract: String) { - if (UtilHelper.isOnline(context as Activity)) { - requestCounter++ - serverApiInfura.infura(method, 67, ctx.coinData!!.wallet, contract, "") - } else { - Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() - srl?.isRefreshing = false - } - } +// private fun requestInfura(method: String, contract: String) { +// if (UtilHelper.isOnline(context as Activity)) { +// requestCounter++ +// serverApiInfura.infura(method, 67, ctx.coinData!!.wallet, contract, "") +// } else { +// Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() +// srl?.isRefreshing = false +// } +// } 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 } } @@ -894,7 +930,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific val sUID = Util.byteArrayToHexString(uid) if (ctx.card.uid != sUID) { // Log.d(TAG, "Invalid UID: $sUID") - nfcManager!!.ignoreTag(isoDep.tag) + nfcManager.ignoreTag(isoDep.tag) return } else { // Log.v(TAG, "UID: $sUID") diff --git a/app/src/main/java/com/tangem/presentation/fragment/VerifyCard.kt b/app/src/main/java/com/tangem/presentation/fragment/VerifyCard.kt index f8804310d0..cefc25ff1f 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/VerifyCard.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/VerifyCard.kt @@ -39,7 +39,6 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback { companion object { val TAG: String = VerifyCard::class.java.simpleName - } private var nfcManager: NfcManager? = null 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/data/external/CardDataSubstitutionProvider.java b/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/CardDataSubstitutionProvider.java index fd8c23e624..ceaf756749 100644 --- a/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/CardDataSubstitutionProvider.java +++ b/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/CardDataSubstitutionProvider.java @@ -2,6 +2,11 @@ package com.tangem.tangemcard.data.external; import com.tangem.tangemcard.data.TangemCard; + +/** + * This interface provide method to make substitution of read card data (token symbol, contract address) + * if they was unknown when the card was produced + */ public interface CardDataSubstitutionProvider { void applySubstitution(TangemCard card); } diff --git a/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/FirmwaresDigestsProvider.java b/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/FirmwaresDigestsProvider.java index 6ee6a58069..86e8ba4175 100644 --- a/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/FirmwaresDigestsProvider.java +++ b/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/FirmwaresDigestsProvider.java @@ -1,5 +1,9 @@ package com.tangem.tangemcard.data.external; +/** + * This interfaces provide function to randomly select parameters to run one VerifyCode command, check answer and + * state that card is genuine or not + */ public interface FirmwaresDigestsProvider { VerifyCodeRecord selectRandomVerifyCodeBlock(String firmwareVersion); diff --git a/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/PINsProvider.java b/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/PINsProvider.java index 0db22fbbfb..05cabc54af 100644 --- a/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/PINsProvider.java +++ b/tangemcard-common/src/main/java/com/tangem/tangemcard/data/external/PINsProvider.java @@ -2,10 +2,29 @@ package com.tangem.tangemcard.data.external; import java.util.List; +/** + * Interface of PINsProvider - object that know some list of PINs (used when start first time read), PIN2 (used for protected operation) and store last used PIN + * to use it in following operations + */ public interface PINsProvider { + + /** + * @return list of known PINs + * This PINs used when start reading of card + * When start reading a PINs from this list used sequential in search PIN algorithm until right PIN found + */ + List getPINs(); + + /** + * @return PIN2 for protected operations + */ String getPIN2(); + + /** + * Call after successful first time reading of card to store founded PIN (normally this PIN must be returned in next time {@see getPINs} at first position) + * @param pin + */ void setLastUsedPIN(String pin); - List getPINs(); } 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; diff --git a/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt b/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt index 009c26bb75..426ac14e0e 100644 --- a/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt +++ b/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt @@ -112,7 +112,7 @@ class LocalStorage var sData: String? = result.substitution?.data var sSignature: String? = result.substitution?.signature if (card.batch != result.batch) { - Log.e("CardDataSubstitutionProvider", "Invalid batch received!") + Log.e("CardDataSubstitution", "Invalid batch received!") return false } if (!BatchInfo.CardDataSubstitution.verifySignature(card, sData, sSignature)) {