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 diff --git a/app/build.gradle b/app/build.gradle index 09713384e7..cdd92011dd 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/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 3365ca5590..a77aefa188 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,18 @@ 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 GET_TX = "api/v2/get_tx/{network}/{txid}"; + 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..2f4d171a6f --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java @@ -0,0 +1,183 @@ +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; + } + + + public interface AddressInfoListener { + void onSuccess(SoChain.Response.AddressBalance response); + + void onSuccess(SoChain.Response.TxUnspent response); + + void onFail(String message); + } + + 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; + } + + + 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: + 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--; + addressInfoListener.onSuccess(response.body()); + } else { + addressInfoListener.onFail(String.valueOf(response.code())); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); + addressInfoListener.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--; + addressInfoListener.onSuccess(response.body()); + } else { + addressInfoListener.onFail(String.valueOf(response.code())); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "requestAddressBalance onFailure " + 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())); + } + }); + } + + 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/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index e6d4f1f4da..f3b7eae48a 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://pro-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..ea1f3fb89d --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/SoChainApi.java @@ -0,0 +1,26 @@ +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); + + @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 new file mode 100644 index 0000000000..65a7103689 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/SoChain.kt @@ -0,0 +1,70 @@ +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 + + } + + 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 + } + + 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/di/NetworkComponent.kt b/app/src/main/java/com/tangem/di/NetworkComponent.kt index cc5a3ef060..06182cb4a8 100644 --- a/app/src/main/java/com/tangem/di/NetworkComponent.kt +++ b/app/src/main/java/com/tangem/di/NetworkComponent.kt @@ -35,6 +35,9 @@ interface NetworkComponent { @get:Named(Server.ApiBlockcypher.URL_BLOCKCYPHER) val retrofitBlockcypher: 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 912ec80d24..4f1da7af02 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.kt +++ b/app/src/main/java/com/tangem/di/NetworkModule.kt @@ -109,6 +109,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 3427fa0bbe..973091b5ec 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -4,16 +4,19 @@ 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.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.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; import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; @@ -27,6 +30,10 @@ import com.tangem.wallet.TangemContext; import com.tangem.wallet.Transaction; import com.tangem.wallet.UnspentOutputInfo; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; @@ -36,11 +43,14 @@ 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 { private static final String TAG = BtcEngine.class.getSimpleName(); + private static boolean useElectrum = false; + public BtcData coinData = null; public BtcEngine(TangemContext context) throws Exception { @@ -417,23 +427,30 @@ public class BtcEngine extends CoinEngine { @Override public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { - ArrayList unspentOutputs = new ArrayList<>(); + final ArrayList unspentOutputs; checkBlockchainDataExists(); String myAddress = ctx.getCoinData().getWallet(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); - 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)); +// /blockcypher/ +// 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)); +// } + + 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)); + } } - -// // 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) { fullAmount += unspentOutputs.get(i).value; @@ -523,130 +540,179 @@ public class BtcEngine extends CoinEngine { } @Override - public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + if (useElectrum) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { - @Override - 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!"); - } - 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 + 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"); + } - 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); + 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(); } - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); } - if (serverApiBlockcypher.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(); + } } - } + }; - 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); - } + serverApiElectrum.setResponseListener(electrumListener); - @Override - public void onFail(String method, String message) { - Log.i(TAG, "onFail: " + method + " " + message); - ctx.setError(message); - blockchainRequestsCallbacks.onComplete(false); - } - }; + serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); + serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); - serverApiBlockcypher.setResponseListener(blockcypherListener); + } else { + final ServerApiSoChain serverApi = new ServerApiSoChain(); - serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); - } + ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() { + @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(); + } + + } -// @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; @@ -659,75 +725,177 @@ public class BtcEngine extends CoinEngine { // 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(); +// } + + + @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.setAddressInfoListener(addressInfoListener); + + 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); //TODO Need remove transaction by RAW HEX + } + + } 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 { + 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); + } + } + } + } + +// /blockcypher/ +// @Override +// public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { +// 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()); +// public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { +// Log.i(TAG, "onSuccess: " + method); // try { -// if (electrumRequest.getResultString() != null) { -// App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), electrumRequest.txHash); +// String walletAddress = blockcypherResponse.getAddress(); +// if (!walletAddress.equals(coinData.getWallet())) { +// // todo - check +// throw new Exception("Invalid wallet address in answer!"); // } +// 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 // +// 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) { -// Log.e(TAG, "onFail: " + electrumRequest.getMethod() + " " + electrumRequest.getError()); +// 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(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); -// 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)); -// } +// serverApiBlockcypher.setResponseListener(blockcypherListener); +// +// serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); // } -// } protected Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { @@ -824,55 +992,125 @@ public class BtcEngine extends CoinEngine { coinData.maxFee = null; coinData.normalFee = null; - final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + if (useElectrum) { - ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { - @Override - 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); - } + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - 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)); + 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 - 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"); + if (kbFee.equals(BigDecimal.ZERO)) { + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); + } - coinData.minFee = convertToAmount(minIntAmount); - coinData.normalFee = convertToAmount(normalIntAmount); - coinData.maxFee = convertToAmount(maxIntAmount); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL BLOCKCYPHER_FEE Exception"); + 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(); + } + } } - if (serverApiBlockcypher.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - } else { - blockchainRequestsCallbacks.onProgress(); + @Override + public void onFail(ElectrumRequest electrumRequest) { + ctx.setError(electrumRequest.getError()); + blockchainRequestsCallbacks.onComplete(false); } - } + }; + serverApiElectrum.setResponseListener(electrumListener); - @Override - public void onFail(String method, String message) { - Log.i(TAG, "onFail: " + method + " " + message); - ctx.setError(message); - blockchainRequestsCallbacks.onComplete(false); - } - }; + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); + } else { - serverApiBlockcypher.setResponseListener(blockcypherListener); + final ServerApiCommon serverApiCommon = new ServerApiCommon(); - serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_FEE, "", ""); + 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); + + } } +// /blockcypher/ // @Override // public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { // final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString()); @@ -881,145 +1119,182 @@ public class BtcEngine extends CoinEngine { // 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()); -// } - -// @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)); +// serverApiBlockcypher.setResponseListener(blockcypherListener); // +// serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_FEE, "", ""); // } @Override - public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { - final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { final String txStr = BTCUtils.toHex(txForSend); + if (useElectrum) { + final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { - @Override - 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); + 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); + } + } } } - } - 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()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiElectrum.setResponseListener(electrumListener); - @Override - public void onFail(String method, String message) { - Log.i(TAG, "onFail: " + method + " " + message); - ctx.setError(message); - blockchainRequestsCallbacks.onComplete(false); - } - }; - serverApiBlockcypher.setResponseListener(blockcypherListener); - serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr); + 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 { + 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); + 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(String message) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApi.setSendTxListener(listener); + serverApi.requestSendTransaction(ctx.getBlockchain(), txStr); + + } } + +// /blockcypher/ +// @Override +// public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { +// final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); +// final String txStr = BTCUtils.toHex(txForSend); +// +// ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { +// @Override +// 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(String method, String message) { +// Log.i(TAG, "onFail: " + method + " " + message); +// ctx.setError(message); +// blockchainRequestsCallbacks.onComplete(false); +// } +// }; +// serverApiBlockcypher.setResponseListener(blockcypherListener); +// +// serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr); +// } + } \ No newline at end of file diff --git a/build.gradle b/build.gradle index ba5d47436c..712dc864a1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.21' + ext.kotlin_version = '1.3.30' repositories { google() jcenter()