From 6fd870bc9ac1d78f91cec35853ba91125db04aba Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2019 19:31:19 +0300 Subject: [PATCH 1/5] Updated on 2026-08-14 --- .../java/com/tangem/data/network/Server.java | 11 + .../tangem/data/network/ServerApiSoChain.java | 104 +++++ .../com/tangem/data/network/ServerURL.java | 1 + .../com/tangem/data/network/SoChainApi.java | 23 ++ .../com/tangem/data/network/model/SoChain.kt | 46 +++ .../java/com/tangem/di/NetworkComponent.kt | 3 + .../main/java/com/tangem/di/NetworkModule.kt | 12 + .../java/com/tangem/wallet/btc/BtcEngine.java | 374 ++++++++++++------ 8 files changed, 450 insertions(+), 124 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiSoChain.java create mode 100644 app/src/main/java/com/tangem/data/network/SoChainApi.java create mode 100644 app/src/main/java/com/tangem/data/network/model/SoChain.kt diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 832e0495c2..daf83d7554 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -32,6 +32,17 @@ public class Server { } } + + public static class ApiSoChain { + public static final String URL = ServerURL.API_SOCHAIN_V2; + + public static class Method { + public static final String ADDRESS_BALANCE = "api/v2/get_address_balance/{network}/{address}"; + public static final String UNSPENT_TX = "api/v2/get_tx_unspent/{network}/{address}"; + public static final String SEND_TRANSACTION = "api/v2/send_tx/{network}"; + } + } + /** * https://public-node.rsk.co/ */ diff --git a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java new file mode 100644 index 0000000000..862af12b63 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java @@ -0,0 +1,104 @@ +package com.tangem.data.network; + +import android.util.Log; + +import com.tangem.App; +import com.tangem.data.Blockchain; +import com.tangem.data.network.model.SoChain; + +import androidx.annotation.NonNull; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class ServerApiSoChain { + + public static String NETWORK_BTC = "BTC"; + + private static String TAG = ServerApiSoChain.class.getSimpleName(); + + 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 ResponseListener responseListener; + + public interface ResponseListener { + void onSuccess(SoChain.Response.AddressBalance response); + + void onSuccess(SoChain.Response.TxUnspent response); + + void onFail(String message); + } + + public void setResponseListener(ResponseListener listener) { + responseListener = listener; + } + + private String getNetwork(Blockchain blockchain) throws Exception { + switch (blockchain) { + case Bitcoin: + return "BTC"; + case BitcoinTestNet: + return "BTCTEST"; + case Litecoin: + return "LTC"; + default: + throw new Exception("SoChainAPI don't support blockchain " + blockchain.getID()); + } + } + + public void requestAddressBalance(Blockchain blockchain, String wallet) throws Exception { + requestsCount++; + SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); + + Call call = api.getAddressBalance(getNetwork(blockchain), wallet); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + Log.i(TAG, "requestAddressBalance onResponse " + response.code()); + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(response.body()); + } else { + responseListener.onFail(String.valueOf(response.code())); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); + responseListener.onFail(String.valueOf(t.getMessage())); + } + }); + } + + public void requestUnspentTx(Blockchain blockchain, String wallet) throws Exception { + requestsCount++; + SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); + + Call call = api.getUnspentTx(getNetwork(blockchain), wallet); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + Log.i(TAG, "requestAddressBalance onResponse " + response.code()); + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(response.body()); + } else { + responseListener.onFail(String.valueOf(response.code())); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); + responseListener.onFail(String.valueOf(t.getMessage())); + } + }); + } + +} diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index b33d4c4ba4..8d0c8df96e 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -4,6 +4,7 @@ class ServerURL { static final String API_TANGEM = "https://verify.tangem.com/"; static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/"; static final String API_INFURA = "https://mainnet.infura.io/"; + static final String API_SOCHAIN_V2 = "https://chain.so/"; static final String API_ESTIMATEFEE = "https://estimatefee.com/"; static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/"; static final String API_ROOTSTOCK = "https://public-node.rsk.co/"; diff --git a/app/src/main/java/com/tangem/data/network/SoChainApi.java b/app/src/main/java/com/tangem/data/network/SoChainApi.java new file mode 100644 index 0000000000..b9c956a000 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/SoChainApi.java @@ -0,0 +1,23 @@ +package com.tangem.data.network; + + +import com.tangem.data.network.model.SoChain; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; + +public interface SoChainApi { + @GET(Server.ApiSoChain.Method.ADDRESS_BALANCE) + Call getAddressBalance(@Path("network") String network, @Path("address") String address); + + @GET(Server.ApiSoChain.Method.UNSPENT_TX) + Call getUnspentTx(@Path("network") String network, @Path("address") String address); + + @Headers("Content-Type: application/json") + @POST(Server.ApiSoChain.Method.SEND_TRANSACTION) + Call sendTransaction(@Path("network") String network, @Body SoChain.Request.SendTx body); +} diff --git a/app/src/main/java/com/tangem/data/network/model/SoChain.kt b/app/src/main/java/com/tangem/data/network/model/SoChain.kt new file mode 100644 index 0000000000..2730df7776 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/SoChain.kt @@ -0,0 +1,46 @@ +package com.tangem.data.network.model + +class SoChain { + class Request { + class SendTx{ + var tx_hex: String? = null + } + } + + class Response { + class AddressBalance { + class Data { + var network: String? = null + var address: String? = null + var confirmed_balance: String? = null + var unconfirmed_balance: String? = null + var confirmations: String? = null + } + + var status: String? = null + var data: Data? = null + } + + class TxUnspent { + class Data { + class Tx { + var txid: String? = null //"9b5c8fbeb1e42bb2a6da40e2eab49c368d1a205707a1ec88aa13f0f2ecdfe944", + var output_no: Int? = null // 0, + var script_asm: String? = null // "OP_DUP OP_HASH160 8541eb0593bb19c3755198e7d2a71e134da21a97 OP_EQUALVERIFY OP_CHECKSIG", + var script_hex: String? = null // "76a9148541eb0593bb19c3755198e7d2a71e134da21a9788ac", + var value: String? = null // "11.38404832", + var confirmations: Long? = null + var time: Long? = null// : 1555509495 + } + + var network: String? = null + var address: String? = null + var txs: Array? = null + } + + var status: String? = null + var data: Data? = null + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/NetworkComponent.kt b/app/src/main/java/com/tangem/di/NetworkComponent.kt index 7621faf5a2..d04db07e22 100644 --- a/app/src/main/java/com/tangem/di/NetworkComponent.kt +++ b/app/src/main/java/com/tangem/di/NetworkComponent.kt @@ -29,6 +29,9 @@ interface NetworkComponent { @get:Named(Server.ApiRootstock.URL_ROOTSTOCK) val retrofitRootstock: Retrofit + @get:Named(Server.ApiSoChain.URL) + val retrofitSoChain: Retrofit + @get:Named("socket") val socket: Socket diff --git a/app/src/main/java/com/tangem/di/NetworkModule.kt b/app/src/main/java/com/tangem/di/NetworkModule.kt index 7e982995e5..93bb0aa3eb 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.kt +++ b/app/src/main/java/com/tangem/di/NetworkModule.kt @@ -84,6 +84,18 @@ internal class NetworkModule { return builder.build() } + @Singleton + @Provides + @Named(Server.ApiSoChain.URL) + fun provideRetrofitSoChain(): Retrofit { + val builder = Retrofit.Builder() + .baseUrl(Server.ApiSoChain.URL) + .addConverterFactory(GsonConverterFactory.create()) + if (BuildConfig.DEBUG) + builder.client(createOkHttpClient()) + return builder.build() + } + private fun createOkHttpClient(): OkHttpClient { return OkHttpClient.Builder().addInterceptor(createHttpLoggingInterceptor()).build() } diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 7245c9c4ba..73b6871752 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -12,7 +12,10 @@ import com.tangem.card_common.util.Util; import com.tangem.data.Blockchain; import com.tangem.data.local.PendingTransactionsStorage; import com.tangem.data.network.ElectrumRequest; +import com.tangem.data.network.Server; import com.tangem.data.network.ServerApiElectrum; +import com.tangem.data.network.ServerApiSoChain; +import com.tangem.data.network.model.SoChain; import com.tangem.wallet.BTCUtils; import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.Base58; @@ -45,6 +48,8 @@ public class BtcEngine extends CoinEngine { private static final String TAG = BtcEngine.class.getSimpleName(); + private static boolean useElectrum = false; + public BtcData coinData = null; public BtcEngine(TangemContext context) throws Exception { @@ -427,13 +432,20 @@ public class BtcEngine extends CoinEngine { String myAddress = ctx.getCoinData().getWallet(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); - // Build script for our address - List rawTxList = coinData.getUnspentTransactions(); - byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; - // Collect unspent - unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + if (useElectrum) { + // Build script for our address + List rawTxList = coinData.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + // Collect unspent + unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + } else { + unspentOutputs = new ArrayList<>(); + for (BtcData.UnspentTransaction utxo : coinData.getUnspentTransactions()) { + unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.Raw)), utxo.Amount, utxo.Height, -1, utxo.txID, null)); + } + } long fullAmount = 0; for (int i = 0; i < unspentOutputs.size(); ++i) { fullAmount += unspentOutputs.get(i).value; @@ -523,123 +535,82 @@ public class BtcEngine extends CoinEngine { } @Override - public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - - ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { - @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()); - checkPending(blockchainRequestsCallbacks); - } 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.getLong("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.requestData(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()); -// ctx.setError(R.string.cannot_obtain_data_from_blockchain); - if (serverApiElectrum.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } - } - }; - - serverApiElectrum.setResponseListener(electrumListener); - - serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); - serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); - } - - private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { - ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + if (useElectrum) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { Log.i(TAG, "onSuccess: " + electrumRequest.getMethod()); - try { - if (electrumRequest.getResultString() != null) { - App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), electrumRequest.txHash); + 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()); + checkPending(blockchainRequestsCallbacks); + } 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.getLong("value"); + trUnspent.Height = jsUnspent.getInt("height"); + coinData.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } - } catch (Exception e) { - Log.e(TAG, "onFail: " + electrumRequest.getMethod() + " " + electrumRequest.getError()); + 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.requestData(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 { @@ -650,6 +621,7 @@ public class BtcEngine extends CoinEngine { @Override public void onFail(ElectrumRequest electrumRequest) { Log.i(TAG, "onFail: " + electrumRequest.getMethod() + " " + electrumRequest.getError()); + ctx.setError(electrumRequest.getError()); // ctx.setError(R.string.cannot_obtain_data_from_blockchain); if (serverApiElectrum.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); @@ -660,9 +632,163 @@ public class BtcEngine extends CoinEngine { }; serverApiElectrum.setResponseListener(electrumListener); - for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { - String txHash = BTCUtils.toHex(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))); - serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(ctx.getCoinData().getWallet(), txHash)); + + serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); + serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); + + } else { + final ServerApiSoChain serverApi = new ServerApiSoChain(); + + ServerApiSoChain.ResponseListener responseListener = new ServerApiSoChain.ResponseListener() { + @Override + public void onSuccess(SoChain.Response.AddressBalance response) { + try { + String walletAddress = response.getData().getAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact(); + Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact(); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(Server.ApiSoChain.URL); + checkPending(blockchainRequestsCallbacks); + } 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 (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + + } + + @Override + public void onSuccess(SoChain.Response.TxUnspent response) { + String walletAddress = response.getData().getAddress(); + try { + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + coinData.getUnspentTransactions().clear(); + if (response.getData().getTxs() != null) + for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = tx.getTxid(); + trUnspent.Amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact(); + trUnspent.Height = tx.getOutput_no(); + trUnspent.Raw = tx.getScript_hex(); + coinData.getUnspentTransactions().add(trUnspent); + +// if (blockchainRequestsCallbacks.allowAdvance()) { +// //serverApi.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); +// } else { +// ctx.setError("Terminated by user"); +// } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } + + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + + } + +// +// } 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(); +// } +// } + +// } + + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: " + message); + ctx.setError(message); +// ctx.setError(R.string.cannot_obtain_data_from_blockchain); + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + + serverApi.setResponseListener(responseListener); + + serverApi.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); + serverApi.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); + } + } + + private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { + + if (useElectrum) { + ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + + ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + Log.i(TAG, "onSuccess: " + electrumRequest.getMethod()); + try { + if (electrumRequest.getResultString() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), electrumRequest.txHash); + } + + } catch (Exception e) { + Log.e(TAG, "onFail: " + electrumRequest.getMethod() + " " + electrumRequest.getError()); + } + 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(R.string.cannot_obtain_data_from_blockchain); + if (serverApiElectrum.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApiElectrum.setResponseListener(electrumListener); + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txHash = BTCUtils.toHex(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx()))); + serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(ctx.getCoinData().getWallet(), txHash)); + } + } else { + throw new Exception("Not supported!"); } } } @@ -758,13 +884,13 @@ public class BtcEngine extends CoinEngine { public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { 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; + coinData.minFee = null; + coinData.maxFee = null; + coinData.normalFee = null; final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { BigDecimal kbFee; @@ -784,9 +910,9 @@ public class BtcEngine extends CoinEngine { BigDecimal normalFee = normalByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); BigDecimal maxFee = maxByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); - CoinEngine.Amount minAmount = new CoinEngine.Amount(minFee, ctx.getBlockchain().getCurrency()); - CoinEngine.Amount normalAmount = new CoinEngine.Amount(normalFee, ctx.getBlockchain().getCurrency()); - CoinEngine.Amount maxAmount = new CoinEngine.Amount(maxFee, ctx.getBlockchain().getCurrency()); + CoinEngine.Amount minAmount = new CoinEngine.Amount(minFee, ctx.getBlockchain().getCurrency()); + CoinEngine.Amount normalAmount = new CoinEngine.Amount(normalFee, ctx.getBlockchain().getCurrency()); + CoinEngine.Amount maxAmount = new CoinEngine.Amount(maxFee, ctx.getBlockchain().getCurrency()); coinData.minFee = minAmount; coinData.normalFee = normalAmount; From 5f3d316ab74648e9fdaa619b43f01ec8bcd0ee9a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2019 00:20:24 +0300 Subject: [PATCH 2/5] Updated on 2026-08-14 --- .idea/codeStyles/Project.xml | 29 --- app/build.gradle | 2 +- .../tangem/data/network/ServerApiSoChain.java | 61 ++++- .../com/tangem/data/network/SoChainApi.java | 2 +- .../com/tangem/data/network/model/SoChain.kt | 10 + .../java/com/tangem/wallet/btc/BtcEngine.java | 219 +++++++++++++----- build.gradle | 4 +- card-android/build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- server-android/build.gradle | 2 +- 10 files changed, 230 insertions(+), 105 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 30aa626c23..0000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d70d548ee7..7d8d06f4cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'kotlin-kapt' static def generateVersionName() { def gitCommitsCount = "git rev-list HEAD --count".execute().text.trim() - def gitCurrentBranch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() +// def gitCurrentBranch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() return gitCommitsCount.toInteger() + 831// + "\\n" + gitCurrentBranch } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java index 862af12b63..df8cac1af1 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java @@ -24,9 +24,7 @@ public class ServerApiSoChain { return requestsCount <= 0; } - private ResponseListener responseListener; - - public interface ResponseListener { + public interface AddressInfoListener { void onSuccess(SoChain.Response.AddressBalance response); void onSuccess(SoChain.Response.TxUnspent response); @@ -34,8 +32,22 @@ public class ServerApiSoChain { void onFail(String message); } - public void setResponseListener(ResponseListener listener) { - responseListener = listener; + private AddressInfoListener addressInfoListener; + + public void setAddressInfoListener(AddressInfoListener listener) { + addressInfoListener = listener; + } + + public interface SendTxListener { + void onSuccess(SoChain.Response.SendTx response); + + void onFail(String message); + } + + private SendTxListener sendTxListener; + + public void setSendTxListener(SendTxListener listener) { + sendTxListener = listener; } private String getNetwork(Blockchain blockchain) throws Exception { @@ -62,16 +74,16 @@ public class ServerApiSoChain { Log.i(TAG, "requestAddressBalance onResponse " + response.code()); if (response.code() == 200) { requestsCount--; - responseListener.onSuccess(response.body()); + addressInfoListener.onSuccess(response.body()); } else { - responseListener.onFail(String.valueOf(response.code())); + addressInfoListener.onFail(String.valueOf(response.code())); } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); - responseListener.onFail(String.valueOf(t.getMessage())); + addressInfoListener.onFail(String.valueOf(t.getMessage())); } }); } @@ -87,16 +99,43 @@ public class ServerApiSoChain { Log.i(TAG, "requestAddressBalance onResponse " + response.code()); if (response.code() == 200) { requestsCount--; - responseListener.onSuccess(response.body()); + addressInfoListener.onSuccess(response.body()); } else { - responseListener.onFail(String.valueOf(response.code())); + addressInfoListener.onFail(String.valueOf(response.code())); } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); - responseListener.onFail(String.valueOf(t.getMessage())); + addressInfoListener.onFail(String.valueOf(t.getMessage())); + } + }); + } + + public void requestSendTransaction(Blockchain blockchain, String txHEX) throws Exception { + requestsCount++; + SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); + + SoChain.Request.SendTx tx=new SoChain.Request.SendTx(); + tx.setTx_hex(txHEX); + Call call = api.sendTransaction(getNetwork(blockchain), tx); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + Log.i(TAG, "requestAddressBalance onResponse " + response.code()); + if (response.code() == 200) { + requestsCount--; + sendTxListener.onSuccess(response.body()); + } else { + sendTxListener.onFail(String.valueOf(response.code())); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); + sendTxListener.onFail(String.valueOf(t.getMessage())); } }); } diff --git a/app/src/main/java/com/tangem/data/network/SoChainApi.java b/app/src/main/java/com/tangem/data/network/SoChainApi.java index b9c956a000..ddb982d09a 100644 --- a/app/src/main/java/com/tangem/data/network/SoChainApi.java +++ b/app/src/main/java/com/tangem/data/network/SoChainApi.java @@ -19,5 +19,5 @@ public interface SoChainApi { @Headers("Content-Type: application/json") @POST(Server.ApiSoChain.Method.SEND_TRANSACTION) - Call sendTransaction(@Path("network") String network, @Body SoChain.Request.SendTx body); + Call sendTransaction(@Path("network") String network, @Body SoChain.Request.SendTx body); } diff --git a/app/src/main/java/com/tangem/data/network/model/SoChain.kt b/app/src/main/java/com/tangem/data/network/model/SoChain.kt index 2730df7776..cc42d6204a 100644 --- a/app/src/main/java/com/tangem/data/network/model/SoChain.kt +++ b/app/src/main/java/com/tangem/data/network/model/SoChain.kt @@ -42,5 +42,15 @@ class SoChain { var data: Data? = null } + + class SendTx { + class Data { + var network: String? = null + var txid: String? = null + var tx_hex: String? = null + } + var status: String? = null + var data: Data? = null + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 73b6871752..6e2ea3aef5 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -13,6 +13,7 @@ import com.tangem.data.Blockchain; import com.tangem.data.local.PendingTransactionsStorage; import com.tangem.data.network.ElectrumRequest; import com.tangem.data.network.Server; +import com.tangem.data.network.ServerApiCommon; import com.tangem.data.network.ServerApiElectrum; import com.tangem.data.network.ServerApiSoChain; import com.tangem.data.network.model.SoChain; @@ -639,7 +640,7 @@ public class BtcEngine extends CoinEngine { } else { final ServerApiSoChain serverApi = new ServerApiSoChain(); - ServerApiSoChain.ResponseListener responseListener = new ServerApiSoChain.ResponseListener() { + ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() { @Override public void onSuccess(SoChain.Response.AddressBalance response) { try { @@ -738,7 +739,7 @@ public class BtcEngine extends CoinEngine { }; - serverApi.setResponseListener(responseListener); + serverApi.setAddressInfoListener(addressInfoListener); serverApi.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); serverApi.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); @@ -888,71 +889,177 @@ public class BtcEngine extends CoinEngine { coinData.maxFee = null; coinData.normalFee = null; - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + if (useElectrum) { - final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { - @Override - public void onSuccess(ElectrumRequest electrumRequest) { - BigDecimal kbFee; - if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) { - try { - kbFee = new BigDecimal(electrumRequest.getResultString()); //fee per KB + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - if (kbFee.equals(BigDecimal.ZERO)) { - serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); - } + final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + BigDecimal kbFee; + if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) { + try { + kbFee = new BigDecimal(electrumRequest.getResultString()); //fee per KB - BigDecimal minByteFee = kbFee.divide(new BigDecimal(1024)); // per KB -> per byte - BigDecimal normalByteFee = minByteFee.add(new BigDecimal(0.00000010)); - BigDecimal maxByteFee = minByteFee.add(new BigDecimal(0.00000025)); + if (kbFee.equals(BigDecimal.ZERO)) { + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); + } - BigDecimal minFee = minByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); - BigDecimal normalFee = normalByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); - BigDecimal maxFee = maxByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); + BigDecimal minByteFee = kbFee.divide(new BigDecimal(1024)); // per KB -> per byte + BigDecimal normalByteFee = minByteFee.add(new BigDecimal(0.00000010)); + BigDecimal maxByteFee = minByteFee.add(new BigDecimal(0.00000025)); - CoinEngine.Amount minAmount = new CoinEngine.Amount(minFee, ctx.getBlockchain().getCurrency()); - CoinEngine.Amount normalAmount = new CoinEngine.Amount(normalFee, ctx.getBlockchain().getCurrency()); - CoinEngine.Amount maxAmount = new CoinEngine.Amount(maxFee, ctx.getBlockchain().getCurrency()); + BigDecimal minFee = minByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); + BigDecimal normalFee = normalByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); + BigDecimal maxFee = maxByteFee.multiply(new BigDecimal(calcSize)).setScale(8, RoundingMode.DOWN); - coinData.minFee = minAmount; - coinData.normalFee = normalAmount; - coinData.maxFee = maxAmount; + CoinEngine.Amount minAmount = new CoinEngine.Amount(minFee, ctx.getBlockchain().getCurrency()); + CoinEngine.Amount normalAmount = new CoinEngine.Amount(normalFee, ctx.getBlockchain().getCurrency()); + CoinEngine.Amount maxAmount = new CoinEngine.Amount(maxFee, ctx.getBlockchain().getCurrency()); + + coinData.minFee = minAmount; + coinData.normalFee = normalAmount; + coinData.maxFee = maxAmount; // if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) { - blockchainRequestsCallbacks.onComplete(true); + blockchainRequestsCallbacks.onComplete(true); // } else { // blockchainRequestsCallbacks.onProgress(); // } - } catch (JSONException e) { - e.printStackTrace(); + } catch (JSONException e) { + e.printStackTrace(); + } } } - } - @Override - public void onFail(ElectrumRequest electrumRequest) { - ctx.setError(electrumRequest.getError()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - serverApiElectrum.setResponseListener(electrumListener); + @Override + public void onFail(ElectrumRequest electrumRequest) { + ctx.setError(electrumRequest.getError()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiElectrum.setResponseListener(electrumListener); - serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); + } else { + + final ServerApiCommon serverApiCommon = new ServerApiCommon(); + + final ServerApiCommon.EstimatedFeeListener estimatedFeeListener = new ServerApiCommon.EstimatedFeeListener() { + @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.requestBtcEstimatedFee(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.requestBtcEstimatedFee(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.requestBtcEstimatedFee(blockCount); + return; + } + ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiCommon.setBtcEstimatedFeeListener(estimatedFeeListener); + + serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY); + serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_NORMAL); + serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL); + + } } @Override - public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { final String txStr = BTCUtils.toHex(txForSend); + if (useElectrum) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { - @Override - public void onSuccess(ElectrumRequest electrumRequest) { - if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { + ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + @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.setResponseListener(electrumListener); + + + serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + } else { + final ServerApiSoChain serverApi = new ServerApiSoChain(); + + ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() { + @Override + public void onSuccess(SoChain.Response.SendTx response) { try { - String resultString = electrumRequest.getResultString(); - if (resultString == null || resultString.isEmpty()) { - ctx.setError("Rejected by node: " + electrumRequest.getError()); + if (response.getStatus() == null || response.getData() == null) { + ctx.setError("Rejected by node: invalid answer received"); + blockchainRequestsCallbacks.onComplete(false); + } else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) { + ctx.setError("Rejected by node: " + response.getData()); blockchainRequestsCallbacks.onComplete(false); } else { ctx.setError(null); @@ -968,19 +1075,17 @@ public class BtcEngine extends CoinEngine { } } } - } - @Override - public void onFail(ElectrumRequest electrumRequest) { - ctx.setError(electrumRequest.getError()); - blockchainRequestsCallbacks.onComplete(false); - } - }; - serverApiElectrum.setResponseListener(electrumListener); - - - serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + @Override + public void onFail(String message) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApi.setSendTxListener(listener); + serverApi.requestSendTransaction(ctx.getBlockchain(), txStr); + } } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 07821a2684..c4a30cd5d8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '1.3.21' + ext.kotlin_version = '1.3.30' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' } diff --git a/card-android/build.gradle b/card-android/build.gradle index 6d22133a08..956d0c88fe 100644 --- a/card-android/build.gradle +++ b/card-android/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation project(':card-common') // implementation 'com.github.TangemCash.card_android-common:card_android-android:0.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0-alpha01' + implementation 'androidx.appcompat:appcompat:1.1.0-alpha04' implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.scottyab:rootbeer-lib:0.0.7' testImplementation 'junit:junit:4.12' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6fea634781..c494404a7f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jan 18 12:06:47 MSK 2019 +#Wed Apr 17 22:33:22 MSK 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/server-android/build.gradle b/server-android/build.gradle index f79ad5009e..7aaa4af308 100644 --- a/server-android/build.gradle +++ b/server-android/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation project(':card-common') // implementation 'com.github.TangemCash.card_android-common:card_android-android:0.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0-alpha03' + implementation 'androidx.appcompat:appcompat:1.1.0-alpha04' implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation 'com.squareup.retrofit2:retrofit:2.5.0' From c0826690b6c0a323b3255fa5f62ce5d55038c100 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Apr 2019 11:28:54 +0300 Subject: [PATCH 3/5] Updated on 2026-08-14 --- .../java/com/tangem/data/network/Server.java | 1 + .../tangem/data/network/ServerApiSoChain.java | 40 +++++++++++++++++++ .../com/tangem/data/network/SoChainApi.java | 3 ++ .../com/tangem/data/network/model/SoChain.kt | 14 +++++++ .../java/com/tangem/wallet/btc/BtcEngine.java | 40 ++++++++++++++++++- 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index daf83d7554..82bece6c7d 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -39,6 +39,7 @@ public class Server { public static class Method { public static final String ADDRESS_BALANCE = "api/v2/get_address_balance/{network}/{address}"; public static final String UNSPENT_TX = "api/v2/get_tx_unspent/{network}/{address}"; + public static final String GET_TX = "api/v2/get_tx/{network}/{txid}"; public static final String SEND_TRANSACTION = "api/v2/send_tx/{network}"; } } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java index df8cac1af1..2f4d171a6f 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java @@ -24,6 +24,7 @@ public class ServerApiSoChain { return requestsCount <= 0; } + public interface AddressInfoListener { void onSuccess(SoChain.Response.AddressBalance response); @@ -50,6 +51,20 @@ public class ServerApiSoChain { sendTxListener = listener; } + + public interface TransactionInfoListener { + void onSuccess(SoChain.Response.GetTx response); + + void onFail(String message); + } + + private TransactionInfoListener txInfoListener; + + public void setTransactionInfoListener(TransactionInfoListener listener) { + txInfoListener=listener; + } + + private String getNetwork(Blockchain blockchain) throws Exception { switch (blockchain) { case Bitcoin: @@ -140,4 +155,29 @@ public class ServerApiSoChain { }); } + public void requestTransactionInfo(Blockchain blockchain, String txId) throws Exception { + requestsCount++; + SoChainApi api = App.Companion.getNetworkComponent().getRetrofitSoChain().create(SoChainApi.class); + + Call call = api.getTx(getNetwork(blockchain), txId); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + Log.i(TAG, "requestAddressBalance onResponse " + response.code()); + if (response.code() == 200) { + requestsCount--; + txInfoListener.onSuccess(response.body()); + } else { + txInfoListener.onFail(String.valueOf(response.code())); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); + txInfoListener.onFail(String.valueOf(t.getMessage())); + } + }); + } + } diff --git a/app/src/main/java/com/tangem/data/network/SoChainApi.java b/app/src/main/java/com/tangem/data/network/SoChainApi.java index ddb982d09a..ea1f3fb89d 100644 --- a/app/src/main/java/com/tangem/data/network/SoChainApi.java +++ b/app/src/main/java/com/tangem/data/network/SoChainApi.java @@ -17,6 +17,9 @@ public interface SoChainApi { @GET(Server.ApiSoChain.Method.UNSPENT_TX) Call getUnspentTx(@Path("network") String network, @Path("address") String address); + @GET(Server.ApiSoChain.Method.GET_TX) + Call getTx(@Path("network") String network, @Path("txid") String txId); + @Headers("Content-Type: application/json") @POST(Server.ApiSoChain.Method.SEND_TRANSACTION) Call sendTransaction(@Path("network") String network, @Body SoChain.Request.SendTx body); diff --git a/app/src/main/java/com/tangem/data/network/model/SoChain.kt b/app/src/main/java/com/tangem/data/network/model/SoChain.kt index cc42d6204a..65a7103689 100644 --- a/app/src/main/java/com/tangem/data/network/model/SoChain.kt +++ b/app/src/main/java/com/tangem/data/network/model/SoChain.kt @@ -52,5 +52,19 @@ class SoChain { var status: String? = null var data: Data? = null } + + class GetTx { + class Data { + // restricted data + var network: String? = null + var address: String? = null + var txid: String? = null + var tx_hex: String? = null + } + + var status: String? = null + var data: Data? = null + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 6e2ea3aef5..291eb989b5 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -758,7 +758,7 @@ public class BtcEngine extends CoinEngine { Log.i(TAG, "onSuccess: " + electrumRequest.getMethod()); try { if (electrumRequest.getResultString() != null) { - App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), electrumRequest.txHash); + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), electrumRequest.txHash); //TODO Need remove transaction by RAW HEX } } catch (Exception e) { @@ -789,7 +789,43 @@ public class BtcEngine extends CoinEngine { serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(ctx.getCoinData().getWallet(), txHash)); } } else { - throw new Exception("Not supported!"); + ServerApiSoChain serverApi = new ServerApiSoChain(); + + ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() { + @Override + public void onSuccess(SoChain.Response.GetTx response) { + Log.i(TAG, "onSuccess: GetTx"); + try { + if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex()); + } + + } catch (Exception e) { + Log.e(TAG, "onFail: GetTx" + response); + } + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: GetTx " + message); + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApi.setTransactionInfoListener(listener); + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + serverApi.requestTransactionInfo(ctx.getBlockchain(), txId); + } } } } From c7b70d0ecbd03dee11eb549999056451cf2fa6bf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Jul 2019 16:57:07 +0300 Subject: [PATCH 4/5] Updated on 2026-08-14 --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ceb1b04599..eb1d791a13 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ .idea/caches .idea/dictionaries .idea/runConfigurations.xml +.idea/encodings.xml +.idea/codeStyles/codeStyleConfig.xml From 4c6a8955ebfcd5f8419d33bce6b1517b30886f87 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Jul 2019 13:04:10 +0300 Subject: [PATCH 5/5] Updated on 2026-08-14 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cdd92011dd..4f70a66534 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,8 +36,8 @@ android { flavorDimensions "" productFlavors { tangemAccess { - versionCode 111 - versionName "0.906.2." + generateVersionName() + versionCode 112 + versionName "0.907.1." + generateVersionName() applicationId "com.tangem.wallet" signingConfig signingConfigs.debug }