From ef75cfed25d5b2f4e2364c1de5c68a329e789df4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2019 18:04:03 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/data/network/BlockcypherApi.java | 25 + .../tangem/data/network/BlockcypherToken.kt | 7 + .../java/com/tangem/data/network/Server.java | 22 + .../data/network/ServerApiBlockcypher.java | 127 ++++ .../com/tangem/data/network/ServerURL.java | 2 + .../data/network/model/BlockcypherBody.java | 9 + .../data/network/model/BlockcypherResponse.kt | 45 ++ .../java/com/tangem/di/NetworkComponent.kt | 3 + .../main/java/com/tangem/di/NetworkModule.kt | 13 + .../java/com/tangem/wallet/btc/BtcEngine.java | 581 +++++++++++------- 10 files changed, 626 insertions(+), 208 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/BlockcypherApi.java create mode 100644 app/src/main/java/com/tangem/data/network/BlockcypherToken.kt create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java create mode 100644 app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java create mode 100644 app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt diff --git a/app/src/main/java/com/tangem/data/network/BlockcypherApi.java b/app/src/main/java/com/tangem/data/network/BlockcypherApi.java new file mode 100644 index 0000000000..9e05655889 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/BlockcypherApi.java @@ -0,0 +1,25 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.BlockcypherBody; +import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherFee; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface BlockcypherApi { + @GET(Server.ApiBlockcypher.Method.MAIN) + Call blockcypherMain(); + + @GET(Server.ApiBlockcypher.Method.ADDRESS) + Call blockcypherAddress(@Path("address") String address); + + @Headers("Content-Type: application/json") + @POST(Server.ApiBlockcypher.Method.PUSH) + Call blockcypherPush(@Body BlockcypherBody blockcypherBody, @Query("token") String token); +} diff --git a/app/src/main/java/com/tangem/data/network/BlockcypherToken.kt b/app/src/main/java/com/tangem/data/network/BlockcypherToken.kt new file mode 100644 index 0000000000..32bf303df9 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/BlockcypherToken.kt @@ -0,0 +1,7 @@ +package com.tangem.data.network + +enum class BlockcypherToken(val token: String) { + T_001("aa8184b0e0894b88a5688e01b3dc1e82"), + T_002("56c4ca23c6484c8f8864c32fde4def8d"), + T_003("66a8a37c5e9d4d2c9bb191acfe7f93aa") +} \ No newline at end of file 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..b657448aa4 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -55,4 +55,26 @@ public class Server { static final String N_6 = URL_ESTIMATEFEE + "n/6"; } } + + public static class ApiBinance { + public static final String URL_BINANCE = ServerURL.API_BINANCE; + static final String API_V1 = "api/v1/"; + + public static class Method { + static final String ACCOUNT = URL_BINANCE + API_V1 + "account"; + static final String FEES = URL_BINANCE + API_V1 + "fees"; + static final String BROADCAST = URL_BINANCE + API_V1 + "broadcast"; + } + } + + public static class ApiBlockcypher { + public static final String URL_BLOCKCYPHER = ServerURL.API_BLOCKCYPHER; + static final String V1_BTC_MAIN = "v1/btc/main"; + + public static class Method { + static final String MAIN = URL_BLOCKCYPHER + V1_BTC_MAIN; + static final String ADDRESS = MAIN + "/addrs/{address}?unspentOnly=true&includeScript=true"; + static final String PUSH = MAIN + "/txs/push"; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java new file mode 100644 index 0000000000..997bd47299 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java @@ -0,0 +1,127 @@ +package com.tangem.data.network; + +import android.util.Log; + +import com.tangem.App; +import com.tangem.data.network.model.BlockcypherBody; +import com.tangem.data.network.model.BlockcypherFee; +import com.tangem.data.network.model.BlockcypherResponse; + +import java.util.Random; + +import androidx.annotation.NonNull; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class ServerApiBlockcypher { + private static String TAG = ServerApiRipple.class.getSimpleName(); + + public static final String BLOCKCYPHER_ADDRESS = "blockcypher_address"; + public static final String BLOCKCYPHER_FEE = "blockcypher_fee"; + public static final String BLOCKCYPHER_SEND = "blockcypher_send"; + + 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(String method, BlockcypherResponse blockcypherResponse); + + void onSuccess(String method, BlockcypherFee blockcypherFee); + + void onFail(String method, String message); + } + + public void setResponseListener(ResponseListener listener) { + responseListener = listener; + } + + public void requestData(String method, String wallet, String tx) { + requestsCount++; + BlockcypherApi blockcypherApi = App.Companion.getNetworkComponent().getRetrofitBlockcypher().create(BlockcypherApi.class); + + switch (method) { + case BLOCKCYPHER_ADDRESS: + Call addressCall = blockcypherApi.blockcypherAddress(wallet); + addressCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + break; + + case BLOCKCYPHER_FEE: + Call feeCall = blockcypherApi.blockcypherMain(); + feeCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + break; + + case BLOCKCYPHER_SEND: + BlockcypherToken blockcypherToken = BlockcypherToken.values()[new Random().nextInt(BlockcypherToken.values().length)]; + + Call sendCall = blockcypherApi.blockcypherPush(new BlockcypherBody(tx), blockcypherToken.getToken()); + sendCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 201) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + break; + + default: + responseListener.onFail(method, "undeclared method"); + Log.e(TAG, "requestData " + method + " onFailure - undeclared method"); + break; + } + } +} 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..b66e76a6ce 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -7,4 +7,6 @@ class ServerURL { 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/"; + static final String API_BINANCE = "https://testnet-dex.binance.org/"; + static final String API_BLOCKCYPHER = "https://api.blockcypher.com/"; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java b/app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java new file mode 100644 index 0000000000..065b899dcf --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/BlockcypherBody.java @@ -0,0 +1,9 @@ +package com.tangem.data.network.model; + +public class BlockcypherBody { + private String tx; + + public BlockcypherBody(String tx) { + this.tx = tx; + } +} diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt new file mode 100644 index 0000000000..d86b7e0840 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt @@ -0,0 +1,45 @@ +package com.tangem.data.network.model + +import com.google.gson.annotations.SerializedName + +data class BlockcypherResponse( + @SerializedName("address") + var address: String? = null, + + @SerializedName("balance") + var balance: Long? = null, + + @SerializedName("unconfirmed_balance") + var unconfirmed_balance: Long? = null, + + @SerializedName("txrefs") + var txrefs: List? = null +) + +data class BlockcypherTxref( + @SerializedName("tx_hash") + var tx_hash: String? = null, + + @SerializedName("tx_output_n") + var tx_output_n: Int? = null, + + @SerializedName("value") + var value: Long? = null, + + @SerializedName("confirmations") + var confirmations: Long? = null, + + @SerializedName("script") + var script: String? = null +) + +data class BlockcypherFee( + @SerializedName("low_fee_per_kb") + var low_fee_per_kb: Long? = null, + + @SerializedName("medium_fee_per_kb") + var medium_fee_per_kb: Long? = null, + + @SerializedName("high_fee_per_kb") + var high_fee_per_kb: Long? = 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..90074caed1 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.ApiBlockcypher.URL_BLOCKCYPHER) + val retrofitBlockcypher: 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..75a253c676 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.kt +++ b/app/src/main/java/com/tangem/di/NetworkModule.kt @@ -84,6 +84,19 @@ internal class NetworkModule { return builder.build() } + @Singleton + @Provides + @Named(Server.ApiBlockcypher.URL_BLOCKCYPHER) + fun provideRetrofitBlockcypher(): Retrofit { + val builder = Retrofit.Builder() + .baseUrl(Server.ApiBlockcypher.URL_BLOCKCYPHER) + .addConverterFactory(GsonConverterFactory.create()) + .client(createOkHttpClient()) + 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..6452c9caeb 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -4,31 +4,28 @@ import android.net.Uri; import android.text.InputFilter; import android.util.Log; -import com.tangem.App; import com.tangem.card_common.data.TangemCard; import com.tangem.card_common.reader.CardProtocol; import com.tangem.card_common.tasks.SignTask; 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.ServerApiElectrum; +import com.tangem.data.network.ServerApiBlockcypher; +import com.tangem.data.network.model.BlockcypherFee; +import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherTxref; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.util.DerEncodingUtil; import com.tangem.wallet.BTCUtils; import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.Base58; import com.tangem.wallet.CoinData; import com.tangem.wallet.CoinEngine; +import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; import com.tangem.wallet.Transaction; import com.tangem.wallet.UnspentOutputInfo; -import com.tangem.util.CryptoUtil; -import com.tangem.util.DecimalDigitsInputFilter; -import com.tangem.util.DerEncodingUtil; -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; @@ -39,7 +36,6 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; public class BtcEngine extends CoinEngine { @@ -378,7 +374,7 @@ public class BtcEngine extends CoinEngine { @Override public Amount convertToAmount(InternalAmount internalAmount) { - BigDecimal d = internalAmount.divide(new BigDecimal("100000000")); + BigDecimal d = internalAmount.divide(new BigDecimal("100000000")).setScale(8, RoundingMode.DOWN); return new Amount(d, getBalanceCurrency()); } @@ -421,18 +417,22 @@ public class BtcEngine extends CoinEngine { @Override public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { - final ArrayList unspentOutputs; + ArrayList unspentOutputs = new ArrayList<>(); checkBlockchainDataExists(); String myAddress = ctx.getCoinData().getWallet(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); - // Build script for our address - List rawTxList = coinData.getUnspentTransactions(); - byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + 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)); + } - // Collect unspent - unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); +// // Build script for our address +// List rawTxList = coinData.getUnspentTransactions(); +// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; +// +// // Collect unspent +// unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); long fullAmount = 0; for (int i = 0; i < unspentOutputs.size(); ++i) { @@ -524,148 +524,210 @@ public class BtcEngine extends CoinEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); - ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.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"); + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + Log.i(TAG, "onSuccess: " + method); + try { + String walletAddress = blockcypherResponse.getAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); } - } 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"); - } + Long confBalance = blockcypherResponse.getBalance(); + Long unconfirmedBalance = blockcypherResponse.getUnconfirmed_balance(); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription("blockcypher.com"); + //checkPending(blockchainRequestsCallbacks); TODO: rework checkPending - 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(); + coinData.getUnspentTransactions().clear(); + for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = txref.getTx_hash(); + trUnspent.Amount = txref.getValue(); + trUnspent.Height = txref.getTx_output_n(); //TODO: height -> outIndex + trUnspent.Raw = txref.getScript(); //TODO: raw -> script + coinData.getUnspentTransactions().add(trUnspent); } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); } - if (serverApiElectrum.isRequestsSequenceCompleted()) { + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); } } + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions"); + ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions"); + blockchainRequestsCallbacks.onComplete(false); + } + @Override - public void onFail(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(); - } + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setResponseListener(electrumListener); + serverApiBlockcypher.setResponseListener(blockcypherListener); - serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); - serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); + serverApiBlockcypher.requestData(ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); } - private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { - ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); +// @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())); +// } - 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)); - } - } - } +// private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { +// if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { +// 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)); +// } +// } +// } protected Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { @@ -730,7 +792,7 @@ public class BtcEngine extends CoinEngine { // // byte[] realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change); - SignTask.TransactionToSign ps = constructTransaction(new Amount(outAmount, getBalanceCurrency()), new Amount("0.00", getFeeCurrency()), true, outputAddress); + SignTask.TransactionToSign ps = constructTransaction(new Amount(outAmount, getBalanceCurrency()), new Amount("0.00000001", getFeeCurrency()), true, outputAddress); OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction; onNeedSendTransaction = (tx) -> { }; // empty function to bypass exception @@ -758,103 +820,206 @@ 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 ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); - final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { @Override - public void onSuccess(ElectrumRequest electrumRequest) { - BigDecimal kbFee; - if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) { - try { - kbFee = new BigDecimal(electrumRequest.getResultString()); //fee per KB + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + Log.e(TAG, "Wrong response type for requestFee"); + ctx.setError("Wrong response type for requestFee"); + blockchainRequestsCallbacks.onComplete(false); + } - if (kbFee.equals(BigDecimal.ZERO)) { - serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); - } + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.i(TAG, "onSuccess: " + method); + try { + BigDecimal minByteFee = new BigDecimal(blockcypherFee.getLow_fee_per_kb()).divide(BigDecimal.valueOf(1024)); + BigDecimal normalByteFee = new BigDecimal(blockcypherFee.getMedium_fee_per_kb()).divide(BigDecimal.valueOf(1024)); + BigDecimal maxByteFee = new BigDecimal(blockcypherFee.getHigh_fee_per_kb()).divide(BigDecimal.valueOf(1024)); - 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.InternalAmount minIntAmount = new CoinEngine.InternalAmount(minByteFee.multiply(BigDecimal.valueOf(calcSize)), "satoshi"); + CoinEngine.InternalAmount normalIntAmount = new CoinEngine.InternalAmount(normalByteFee.multiply(BigDecimal.valueOf(calcSize)), "satoshi"); + CoinEngine.InternalAmount maxIntAmount = new CoinEngine.InternalAmount(maxByteFee.multiply(BigDecimal.valueOf(calcSize)), "satoshi"); - 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 = convertToAmount(minIntAmount); + coinData.normalFee = convertToAmount(normalIntAmount); + coinData.maxFee = convertToAmount(maxIntAmount); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_FEE Exception"); + } - 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); -// } else { -// blockchainRequestsCallbacks.onProgress(); -// } - - } catch (JSONException e) { - e.printStackTrace(); - } + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); } } @Override - public void onFail(ElectrumRequest electrumRequest) { - ctx.setError(electrumRequest.getError()); + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setResponseListener(electrumListener); - serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); + serverApiBlockcypher.setResponseListener(blockcypherListener); + + serverApiBlockcypher.requestData(ServerApiBlockcypher.BLOCKCYPHER_FEE, "", ""); } +// @Override +// 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; +// +// final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); +// +// 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 +// +// if (kbFee.equals(BigDecimal.ZERO)) { +// serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); +// } +// +// 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)); +// +// 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); +// +// 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); +//// } else { +//// blockchainRequestsCallbacks.onProgress(); +//// } +// +// } catch (JSONException e) { +// e.printStackTrace(); +// } +// } +// } +// +// @Override +// public void onFail(ElectrumRequest electrumRequest) { +// ctx.setError(electrumRequest.getError()); +// blockchainRequestsCallbacks.onComplete(false); +// } +// }; +// serverApiElectrum.setResponseListener(electrumListener); +// +// serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); +// } + +// @Override +// public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { +// final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); +// final String txStr = BTCUtils.toHex(txForSend); +// +// 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)); +// +// } + @Override public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); final String txStr = BTCUtils.toHex(txForSend); - ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.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); - } + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + String resultString = blockcypherResponse.toString(); + try { + if (resultString.isEmpty()) { + ctx.setError("No response from node"); + blockchainRequestsCallbacks.onComplete(false); + } else { // TODO: Make check for a valid send response + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + Log.e(TAG, resultString); } } } + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestSendTransaction"); + ctx.setError("Wrong response type for requestSendTransaction"); + blockchainRequestsCallbacks.onComplete(false); + } + @Override - public void onFail(ElectrumRequest electrumRequest) { - ctx.setError(electrumRequest.getError()); + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setResponseListener(electrumListener); - - - serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + serverApiBlockcypher.setResponseListener(blockcypherListener); + serverApiBlockcypher.requestData(ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr); } - } \ No newline at end of file