Updated on 2026-08-14

This commit is contained in:
Tangem 2018-12-18 14:11:24 +03:00
commit 2922eab134
24 changed files with 2106 additions and 937 deletions

View file

@ -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");

View file

@ -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<String> call, @NonNull Throwable t) {
estimateFeeListener.onFail(t.getMessage());
estimateFeeListener.onFail(blockCount, t.getMessage());
Log.e(TAG, "estimateFee onFailure " + t.getMessage());
}
});

View file

@ -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<ElectrumRequest>.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<ElectrumRequest>.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<ElectrumRequest> 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<ElectrumRequest>() {
//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<ElectrumRequest> 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<ElectrumRequest> doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) {
List<ElectrumRequest> 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, ">> <NULL>");
}
} 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<ElectrumRequest> 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, ">> <NULL>");
}
} 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);
}
}

View file

@ -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<InfuraResponse> call, @NonNull Response<InfuraResponse> response) {
if (response.code() == 200) {
requestsCount--;
infuraBodyListener.onSuccess(method, response.body());
Log.i(TAG, "infura " + method + " onResponse " + response.code());
} else {

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// collect unspent
ArrayList<UnspentOutputInfo> 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));
}
}

View file

@ -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<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
//
// String myAddress = ctx.getCoinData().getWallet();
// byte[] pbKey = ctx.getCard().getWalletPublicKey();
// // collect unspent
// ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
//
// // Build script for our address
// List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
//
// // Collect unspent
// ArrayList<UnspentOutputInfo> 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));
}
}

View file

@ -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();

View file

@ -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());
}

View file

@ -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();

View file

@ -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<ByteArray>(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(',', '.'))
}

View file

@ -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)
}

View file

@ -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))

View file

@ -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]
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)
}
}

View file

@ -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")

View file

@ -39,7 +39,6 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = VerifyCard::class.java.simpleName
}
private var nfcManager: NfcManager? = null