diff --git a/app/build.gradle b/app/build.gradle index 34923e0ccb..cba81ab1dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,8 +49,8 @@ android { flavorDimensions "" productFlavors { tangemAccess { - versionCode 112 - versionName "0.907.1." + generateVersionName() + versionCode 114 + versionName "0.908.1." + generateVersionName() applicationId "com.tangem.wallet" signingConfig signingConfigs.debug } diff --git a/app/src/main/assets/issuers.json b/app/src/main/assets/issuers.json index f2e7cb0f8a..b1863c76ab 100644 --- a/app/src/main/assets/issuers.json +++ b/app/src/main/assets/issuers.json @@ -8,15 +8,6 @@ "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" } }, - { - "id": "TANGEM SDK OLD", - "dataKey": { - "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" - }, - "transactionKey": { - "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" - } - }, { "id": "TANGEM", "dataKey": { @@ -35,15 +26,6 @@ "publicKey": "04D292BFC802FFA70ABB1C094A19F6C6766DD353114C25415871BE4586129648CFD77B22FBC1AF3C612497732A04CF845B41D760AFDD820051AB250798503DB340" } }, - { - "id": "Qlear BAP", - "dataKey": { - "publicKey": "046A9CA7B99B2D8C8CB3ECCC9F1F7667ED4D324F3B3A9A83CBA0FEA8D26038D3385DE95C095EC0A92623D2C123844C8DB0FE03B90A18CC01EB3730DC3C325690A1" - }, - "transactionKey": { - "publicKey": "044C8A04CA3E0D0F42799D7E356A8EDB15C97627B3EF0FA5BBDF83B4298E816ACCDED2656347CB5C42CA994DFF8389BE801591C252065E426A369F98D7E96A5AAD" - } - }, { "id": "Qlear Protocol", "dataKey": { @@ -63,16 +45,75 @@ } }, { - "id": "CARDANO", + "id": "Whirl", "dataKey": { - "publicKey": "0445C312089E57A6952F96D74FF75C962E1E2C2DDA5FB4195661B92E2744791350FE6C60E1A1D145C6A34D8BEAD57506A90FE4A149A9AA43CFF3C6CAE0748B771E", - "privateKeyEncrypted": "2791FA18DFD4D2AD11509266C4721CE9BB4E9C7C8A06BF5071C6294EAB1F47A153CFDEBAF804425E5ED49AFEB70DD741" + "publicKey": "040B3484CB923DAB12915B7B4FC174543C242A4E49AD9046B2816F03DAFC344E7570CBCC245F5D82FAFDB7E413538238EDE2C196EEC2F9F91911AFC9C37F757810" }, "transactionKey": { - "publicKey": "04969A008E8A61F92EF8D380DB7BB7B8F6F667CA169934371F544F6697D1D2AF97FB87B8D75F550CD782472D46BFCADF354AF17D807C1D2EB82E184C367CACF1FC", - "privateKeyEncrypted": "4707EEB8704D53EB1B6D905604C394D1514C205E3EC16423961BB82A7A47896783F8035BE56D6DAE5DE200E67F2D92E3" + "publicKey": "042EC0898ADF5AF4883D2F077E89CE464766E8E47FA03612F5248494C88A923CA2C444EE71D3A2BE67F13FD65C19E114B577F2B12CE856F1AA80DEBB406F325171" + } + }, + { + "id": "D1", + "dataKey": { + "publicKey": "046F1155C3E75C2E0A4B816F53B739BA9DB3FC27C4CDE20E634AF09CA2D88A5199F97AE474B5833E69AD5A43FFB2F62E4E9B7A8BCC271805A5DD19A7AB9C9888D6" + }, + "transactionKey": { + "publicKey": "04AE9E9C50073ABCF3219A96806BE8B95DCA97A12A0DE3516C25959266F5DFD032B75E1CC926DAAC274EE0E638A2BDB717C4B9C6F28808D6BEAB2F4CBF91E2DD52" + } + }, + { + "id": "Coinstruction", + "dataKey": { + "publicKey": "042D6D305E2927FE18762CF2E74B207B3D7239279248450C31C88C4FEBDBC62D07976D10F7838CA52AF5A7ACB052D09EC8C513E7AC204673D2D3D078DBD859DF49" + }, + "transactionKey": { + "publicKey": "045B0561BD9E089E54D730D75F5205A1A3E094A2209D0F11002F2423D63F508BFD161F8BBA8FBAC441AD893287D67141BBEE39E403C43D12A9FE1A25BCF3A144E9" + } + }, + { + "id": "EvenToken", + "dataKey": { + "publicKey": "040FB60B37682AED10AB8B9DB324BBB10D7CEDF9D189332212C36A641CF81314E6CC8C219245DF04813DBB26931C547B6A4D55A10B5FFB163F54643A97213276F8" + }, + "transactionKey": { + "publicKey": "044CEB1A3C2931F55441B39EB06F938542309DDD436DA486DE605187418163B777684C15D10EE5029CDEFB73DBA2BF90173D27CACEBF4CABFD4553C63021ABBEBB" + } + }, + { + "id": "CARDANO", + "dataKey": { + "publicKey": "0445C312089E57A6952F96D74FF75C962E1E2C2DDA5FB4195661B92E2744791350FE6C60E1A1D145C6A34D8BEAD57506A90FE4A149A9AA43CFF3C6CAE0748B771E" + }, + "transactionKey": { + "publicKey": "04969A008E8A61F92EF8D380DB7BB7B8F6F667CA169934371F544F6697D1D2AF97FB87B8D75F550CD782472D46BFCADF354AF17D807C1D2EB82E184C367CACF1FC" + } + }, + { + "id": "RSK", + "dataKey": { + "publicKey": "04997C29B1158D5A5B7EDCF7A7844EA76C630C80CD326C83F2983C2EF0E632B37282D69CEEDB6445B79B26D73073122E22AB0FE1E9A1CA41D991C45057D9DF0F24" + }, + "transactionKey": { + "publicKey": "040E9DDC53387F1006F39451AF11EBD377B98484FC81D24A96A55DEFDDF6F1EE35F07B089EFD05E4E93BDE20372171274ECD8D8805D0CB1FFEF012673F4E330C95" + } + }, + { + "id": "DIGIX", + "dataKey": { + "publicKey": "049533C082D074ACC285E9B7E97F50F0883DF0308871232ED2370F19CA8993F01C5D4ABAF848949C7628D48F60F68DA3E5726A53C394619179D42D7F20D347001E" + }, + "transactionKey": { + "publicKey": "04346D597CBE34A409C4CC77AED4DB2332D18C181DD6EB0A695FEA8672FF790B1C2518DF3A357484BB1EC89BCB1C3F2A962B2781DF094C8EE0F9C45D98BBA92610" + } + }, + { + "id": "Start2Coin", + "dataKey": { + "publicKey": "043D21AFD0E20468F210547079819CB947031F4A31E591A4193E98DEE84AFB5976066CDD43F94820E0B5F82C063603B48C9A84B4C28C3B6DD9E54BB66A1C75036F" + }, + "transactionKey": { + "publicKey": "0442C6413C81FCCF6FA18318ECEA270F7B7242E82863A9208253A85BEC394E5F58E6A12F9FE8A708BD05B73862C8833EB0634FF98E527277F625D4AB139B86AEAC" } } - - -] +] \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/BlockcypherApi.java b/app/src/main/java/com/tangem/data/network/BlockcypherApi.java index 2ef6251651..d8de09d725 100644 --- a/app/src/main/java/com/tangem/data/network/BlockcypherApi.java +++ b/app/src/main/java/com/tangem/data/network/BlockcypherApi.java @@ -3,6 +3,7 @@ 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 com.tangem.data.network.model.BlockcypherTx; import retrofit2.Call; import retrofit2.http.Body; @@ -19,6 +20,9 @@ public interface BlockcypherApi { @GET(Server.ApiBlockcypher.Method.ADDRESS) Call blockcypherAddress(@Path("blockchain") String blockchain, @Path("network") String network, @Path("address") String address); + @GET(Server.ApiBlockcypher.Method.TXS) + Call blockcypherTxs(@Path("blockchain") String blockchain, @Path("network") String network, @Path("txHash") String txHash); + @Headers("Content-Type: application/json") @POST(Server.ApiBlockcypher.Method.PUSH) Call blockcypherPush(@Path("blockchain") String blockchain, @Path("network") String network, @Body BlockcypherBody blockcypherBody, @Query("token") String token); 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 8d79d87be5..943488dc02 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -114,6 +114,7 @@ public class Server { public static class Method { static final String MAIN = URL_BLOCKCYPHER + V1_MAIN; static final String ADDRESS = MAIN + "/addrs/{address}?unspentOnly=true&includeScript=true"; + static final String TXS = MAIN + "/txs/{txHash}?includeHex=true"; static final String PUSH = MAIN + "/txs/push"; } } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java index 33add147b9..f593b1545c 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java @@ -7,6 +7,7 @@ import com.tangem.data.Blockchain; import com.tangem.data.network.model.BlockcypherBody; import com.tangem.data.network.model.BlockcypherFee; import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherTx; import java.util.Random; @@ -20,6 +21,7 @@ public class ServerApiBlockcypher { public static final String BLOCKCYPHER_ADDRESS = "blockcypher_address"; public static final String BLOCKCYPHER_FEE = "blockcypher_fee"; + public static final String BLOCKCYPHER_TXS = "blockcypher_txs"; public static final String BLOCKCYPHER_SEND = "blockcypher_send"; private int requestsCount = 0; @@ -31,6 +33,8 @@ public class ServerApiBlockcypher { private ResponseListener responseListener; + private TxResponseListener txResponseListener; + public interface ResponseListener { void onSuccess(String method, BlockcypherResponse blockcypherResponse); @@ -39,10 +43,20 @@ public class ServerApiBlockcypher { void onFail(String method, String message); } + public interface TxResponseListener { + void onSuccess(BlockcypherTx blockcypherTx); + + void onFail(String message); + } + public void setResponseListener(ResponseListener listener) { responseListener = listener; } + public void setTxResponseListener(TxResponseListener txListener) { + txResponseListener = txListener; + } + public void requestData(String blockchainID, String method, String wallet, String tx) { requestsCount++; String blockchain = blockchainID.toLowerCase(); @@ -60,8 +74,8 @@ public class ServerApiBlockcypher { addressCall.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + requestsCount--; if (response.code() == 200) { - requestsCount--; responseListener.onSuccess(method, response.body()); Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { @@ -72,6 +86,7 @@ public class ServerApiBlockcypher { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; responseListener.onFail(method, String.valueOf(t.getMessage())); Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } @@ -83,8 +98,8 @@ public class ServerApiBlockcypher { feeCall.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + requestsCount--; if (response.code() == 200) { - requestsCount--; responseListener.onSuccess(method, response.body()); Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { @@ -95,12 +110,37 @@ public class ServerApiBlockcypher { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; responseListener.onFail(method, String.valueOf(t.getMessage())); Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } }); break; + case BLOCKCYPHER_TXS: + Call txsCall = blockcypherApi.blockcypherTxs(blockchain, network, tx); + txsCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call,@NonNull Response response) { + requestsCount--; + if (response.code() == 200) { + txResponseListener.onSuccess(response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + txResponseListener.onFail(String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; + txResponseListener.onFail(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)]; @@ -108,8 +148,8 @@ public class ServerApiBlockcypher { sendCall.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + requestsCount--; if (response.code() == 201) { - requestsCount--; responseListener.onSuccess(method, response.body()); Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { @@ -120,6 +160,7 @@ public class ServerApiBlockcypher { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; responseListener.onFail(method, String.valueOf(t.getMessage())); Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } @@ -127,6 +168,7 @@ public class ServerApiBlockcypher { break; default: + requestsCount--; 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/ServerApiSoChain.java b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java index 2f4d171a6f..0c73849056 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java @@ -13,8 +13,6 @@ import retrofit2.Response; public class ServerApiSoChain { - public static String NETWORK_BTC = "BTC"; - private static String TAG = ServerApiSoChain.class.getSimpleName(); private int requestsCount = 0; @@ -86,9 +84,9 @@ public class ServerApiSoChain { call.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + requestsCount--; Log.i(TAG, "requestAddressBalance onResponse " + response.code()); if (response.code() == 200) { - requestsCount--; addressInfoListener.onSuccess(response.body()); } else { addressInfoListener.onFail(String.valueOf(response.code())); @@ -97,6 +95,7 @@ public class ServerApiSoChain { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); addressInfoListener.onFail(String.valueOf(t.getMessage())); } @@ -111,9 +110,9 @@ public class ServerApiSoChain { call.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + requestsCount--; Log.i(TAG, "requestAddressBalance onResponse " + response.code()); if (response.code() == 200) { - requestsCount--; addressInfoListener.onSuccess(response.body()); } else { addressInfoListener.onFail(String.valueOf(response.code())); @@ -122,6 +121,7 @@ public class ServerApiSoChain { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); addressInfoListener.onFail(String.valueOf(t.getMessage())); } @@ -138,9 +138,9 @@ public class ServerApiSoChain { call.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { + requestsCount--; Log.i(TAG, "requestAddressBalance onResponse " + response.code()); if (response.code() == 200) { - requestsCount--; sendTxListener.onSuccess(response.body()); } else { sendTxListener.onFail(String.valueOf(response.code())); @@ -149,6 +149,7 @@ public class ServerApiSoChain { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); sendTxListener.onFail(String.valueOf(t.getMessage())); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java b/app/src/main/java/com/tangem/data/network/ServerApiStellar.java index ea844facbc..af38916d5d 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiStellar.java @@ -138,7 +138,7 @@ public class ServerApiStellar { }); } - private void doStellarRequest(TangemContext ctx, StellarRequest.Base stellarRequest) throws IOException { + public void doStellarRequest(TangemContext ctx, StellarRequest.Base stellarRequest) throws IOException { stellarRequest.setError(null); try { Server server; 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 index d86b7e0840..b470cba5ed 100644 --- a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt @@ -33,6 +33,11 @@ data class BlockcypherTxref( var script: String? = null ) +data class BlockcypherTx( + @SerializedName("hex") + var hex: String? = null +) + data class BlockcypherFee( @SerializedName("low_fee_per_kb") var low_fee_per_kb: Long? = null, diff --git a/app/src/main/java/com/tangem/wallet/BCHUtils.java b/app/src/main/java/com/tangem/wallet/BCHUtils.java index 7c36d50153..6276cd5170 100644 --- a/app/src/main/java/com/tangem/wallet/BCHUtils.java +++ b/app/src/main/java/com/tangem/wallet/BCHUtils.java @@ -267,8 +267,8 @@ public final class BCHUtils { ArrayList unspentOutputs = new ArrayList<>(); for (BtcData.UnspentTransaction current : rawTxList) { - byte[] rawTxByte = BCHUtils.fromHex(current.Raw); - if (rawTxByte == null || current.Raw.isEmpty()) { + byte[] rawTxByte = BCHUtils.fromHex(current.script); + if (rawTxByte == null || current.script.isEmpty()) { continue; } diff --git a/app/src/main/java/com/tangem/wallet/BTCUtils.java b/app/src/main/java/com/tangem/wallet/BTCUtils.java index 180495a55e..f08cf2786e 100644 --- a/app/src/main/java/com/tangem/wallet/BTCUtils.java +++ b/app/src/main/java/com/tangem/wallet/BTCUtils.java @@ -190,8 +190,8 @@ public final class BTCUtils { ArrayList unspentOutputs = new ArrayList<>(); for (BtcData.UnspentTransaction current : rawTxList) { - byte[] rawTxByte = BTCUtils.fromHex(current.Raw); - if (rawTxByte == null || current.Raw.isEmpty()) { + byte[] rawTxByte = BTCUtils.fromHex(current.script); + if (rawTxByte == null || current.script.isEmpty()) { continue; } diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java index 11de555ac1..afff4db54d 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/wallet/CoinEngine.java @@ -209,8 +209,6 @@ public abstract class CoinEngine { public abstract InputFilter[] getAmountInputFilters(); - public abstract String getOfflineBalanceHTML(); - public abstract String evaluateFeeEquivalent(String fee); public abstract String getFeeCurrency(); @@ -237,6 +235,16 @@ public abstract class CoinEngine { public abstract String getUnspentInputsDescription(); + public String getOfflineBalanceHTML() { + return ""; + } + +// public String getOfflineBalanceHTML() { +// InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); +// Amount offlineAmount = convertToAmount(offlineInternalAmount); +// return offlineAmount.toDescriptionString(getDecimals()); +// } + public void defineWallet() throws CardProtocol.TangemException { try { String wallet = calculateAddress(ctx.getCard().getWalletPublicKey()); diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java index f29bc7e7f4..c0b936d269 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java @@ -89,13 +89,6 @@ public class BtcCashEngine extends CoinEngine { return "BCH"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; @@ -591,8 +584,8 @@ public class BtcCashEngine extends CoinEngine { 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"); + trUnspent.amount = jsUnspent.getLong("value"); + trUnspent.outputN = jsUnspent.getInt("height"); coinData.getUnspentTransactions().add(trUnspent); } } catch (JSONException e) { @@ -623,7 +616,7 @@ public class BtcCashEngine extends CoinEngine { String raw = electrumRequest.getResultString(); for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { if (tx.txID.equals(txHash)) - tx.Raw = raw; + tx.script = raw; } } catch (JSONException e) { e.printStackTrace(); diff --git a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java b/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java index d3927dee8d..4936c28295 100644 --- a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java +++ b/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java @@ -109,13 +109,6 @@ public class BinanceEngine extends CoinEngine { return "BNB"; } - @Override - public String getOfflineBalanceHTML() { //TODO:check - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcData.java b/app/src/main/java/com/tangem/wallet/btc/BtcData.java index d024d9d91d..24d82d0ed0 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcData.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcData.java @@ -17,14 +17,16 @@ public class BtcData extends CoinData { private Long balanceConfirmed, balanceUnconfirmed; + private boolean useBlockcypher = false; + public String getUnspentInputsDescription() { try { int gatheredUnspents = 0; if( unspentTransactions==null ) return ""; for (int i = 0; i < unspentTransactions.size(); i++) { - if (unspentTransactions.get(i).Raw != null && unspentTransactions.get(i).Raw.length() > 1) gatheredUnspents++; + if (unspentTransactions.get(i).script != null && unspentTransactions.get(i).script.length() > 1) gatheredUnspents++; } - return String.valueOf(unspentTransactions.size()) + " unspents (" + String.valueOf(gatheredUnspents) + " received)"; + return unspentTransactions.size() + " unspents (" + gatheredUnspents + " received)"; } catch (Exception e) { @@ -35,24 +37,24 @@ public class BtcData extends CoinData { public static class UnspentTransaction { public String txID; - public Long Amount; - public Integer Height; - public String Raw = ""; + public Long amount; + public Integer outputN; + public String script = ""; public Bundle getAsBundle() { Bundle B = new Bundle(); B.putString("txID", txID); - B.putLong("Amount", Amount); - B.putInt("Height", Height); - B.putString("Raw", Raw); + B.putLong("Amount", amount); + B.putInt("OutputN", outputN); + B.putString("Script", script); return B; } public void loadFromBundle(Bundle B) { txID = B.getString("txID"); - Amount = B.getLong("Amount"); - Height = B.getInt("Height"); - Raw = B.getString("Raw"); + amount = B.getLong("Amount"); + outputN = B.getInt("OutputN"); + script = B.getString("Script"); } } @@ -83,6 +85,7 @@ public class BtcData extends CoinData { i++; } } + if (B.containsKey("UseBlockcypher")) useBlockcypher = B.getBoolean("UseBlockcypher"); } @Override @@ -98,6 +101,7 @@ public class BtcData extends CoinData { } if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed); if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed); + if (useBlockcypher) B.putBoolean("UseBlockcypher", true); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -131,4 +135,11 @@ public class BtcData extends CoinData { return balanceConfirmed != null || balanceUnconfirmed != null; } + public boolean isUseBlockcypher() { + return useBlockcypher; + } + + public void setUseBlockcypher(boolean useBlockcypher) { + this.useBlockcypher = useBlockcypher; + } } 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 1dddef4063..22d1075ccc 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -11,11 +11,14 @@ 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.Server; +import com.tangem.data.network.ServerApiBlockcypher; import com.tangem.data.network.ServerApiCommon; -import com.tangem.data.network.ServerApiElectrum; import com.tangem.data.network.ServerApiSoChain; +import com.tangem.data.network.model.BlockcypherFee; +import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherTx; +import com.tangem.data.network.model.BlockcypherTxref; import com.tangem.data.network.model.SoChain; import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; @@ -30,9 +33,7 @@ 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; @@ -99,13 +100,6 @@ public class BtcEngine extends CoinEngine { return "BTC"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; @@ -384,7 +378,7 @@ public class BtcEngine extends CoinEngine { @Override public Amount convertToAmount(InternalAmount internalAmount) { - BigDecimal d = internalAmount.divide(new BigDecimal("100000000")).setScale(8, RoundingMode.DOWN); + BigDecimal d = internalAmount.divide(new BigDecimal("100000000")).setScale(getDecimals(), RoundingMode.DOWN); return new Amount(d, getBalanceCurrency()); } @@ -435,7 +429,7 @@ public class BtcEngine extends CoinEngine { // /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)); +// unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null)); // } if (useElectrum) { @@ -448,7 +442,7 @@ public class BtcEngine extends CoinEngine { } 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)); + unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null)); } } long fullAmount = 0; @@ -541,108 +535,10 @@ public class BtcEngine extends CoinEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { - if (useElectrum) { - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); + ctx.setError(null); - 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())); - - } else { - final ServerApiSoChain serverApi = new ServerApiSoChain(); + if (!coinData.isUseBlockcypher()) { + final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() { @Override @@ -659,16 +555,26 @@ public class BtcEngine extends CoinEngine { 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"); + ctx.setError("FAIL METHOD_GetBalance JSONException"); } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "FAIL METHOD_GetBalance Exception"); + ctx.setError("FAIL METHOD_GetBalance Exception"); } - if (serverApi.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + if (serverApiSoChain.isRequestsSequenceCompleted()) { + if (!coinData.isUseBlockcypher()) { + checkPending(blockchainRequestsCallbacks); + } else { + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } } else { blockchainRequestsCallbacks.onProgress(); } @@ -688,13 +594,13 @@ public class BtcEngine extends CoinEngine { 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(); + trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact(); + trUnspent.outputN = tx.getOutput_no(); + trUnspent.script = tx.getScript_hex(); coinData.getUnspentTransactions().add(trUnspent); // if (blockchainRequestsCallbacks.allowAdvance()) { -// //serverApi.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); +// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); // } else { // ctx.setError("Terminated by user"); // } @@ -704,38 +610,36 @@ public class BtcEngine extends CoinEngine { Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); } - if (serverApi.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + if (serverApiSoChain.isRequestsSequenceCompleted()) { + if (!coinData.isUseBlockcypher()) { + checkPending(blockchainRequestsCallbacks); + } else { + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } } else { blockchainRequestsCallbacks.onProgress(); } } -// -// } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { -// try { -// String txHash = electrumRequest.txHash; -// String raw = electrumRequest.getResultString(); -// for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { -// if (tx.txID.equals(txHash)) -// tx.Raw = raw; -// } -// } catch (JSONException e) { -// e.printStackTrace(); -// } -// } - -// } - - @Override public void onFail(String message) { Log.i(TAG, "onFail: " + message); - ctx.setError(message); + coinData.setUseBlockcypher(true); // ctx.setError(R.string.cannot_obtain_data_from_blockchain); - if (serverApi.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + if (serverApiSoChain.isRequestsSequenceCompleted()) { +// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } } else { blockchainRequestsCallbacks.onProgress(); } @@ -743,57 +647,78 @@ public class BtcEngine extends CoinEngine { }; - serverApi.setAddressInfoListener(addressInfoListener); + serverApiSoChain.setAddressInfoListener(addressInfoListener); - serverApi.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); - serverApi.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); + serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); + serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + 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(Server.ApiBlockcypher.URL_BLOCKCYPHER); + + coinData.getUnspentTransactions().clear(); + if (blockcypherResponse.getTxrefs() != null) { + for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = txref.getTx_hash(); + trUnspent.amount = txref.getValue(); + trUnspent.outputN = txref.getTx_output_n(); + trUnspent.script = txref.getScript(); + coinData.getUnspentTransactions().add(trUnspent); + } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); + } + + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + checkPending(blockchainRequestsCallbacks); + } 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(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_ADDRESS, ctx.getCoinData().getWallet(), ""); } } - private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { 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(); + if (!coinData.isUseBlockcypher()) { + ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() { @Override @@ -803,11 +728,10 @@ public class BtcEngine extends CoinEngine { 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()) { + if (serverApiSoChain.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); @@ -817,87 +741,73 @@ public class BtcEngine extends CoinEngine { @Override public void onFail(String message) { Log.i(TAG, "onFail: GetTx " + message); - if (serverApi.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + if (serverApiSoChain.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); } else { blockchainRequestsCallbacks.onProgress(); } } }; - serverApi.setTransactionInfoListener(listener); + serverApiSoChain.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); + try { + serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + } else { + ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() { + + @Override + public void onSuccess(BlockcypherTx response) { + Log.i(TAG, "onSuccess: BlockcypherTx"); + try { + if (response.getHex() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex()); + } + } catch (Exception e) { + Log.e(TAG, "onFail: BlockcypherTx" + response); + } + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: BlockcypherTx " + message); + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + serverApiBlockcypher.setTxResponseListener(listener); + + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + try { + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } } } + } else { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } } -// /blockcypher/ -// @Override -// public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { -// final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); -// -// 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 -// -// 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 (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(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_ADDRESS, ctx.getCoinData().getWallet(), ""); -// } - - protected Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { //todo - правильней было бы использовать constructTransaction try { @@ -992,60 +902,7 @@ public class BtcEngine extends CoinEngine { coinData.maxFee = null; coinData.normalFee = null; - if (useElectrum) { - - 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()); - } else { - + if (!coinData.isUseBlockcypher()) { final ServerApiCommon serverApiCommon = new ServerApiCommon(); final ServerApiCommon.EstimatedFeeListener estimatedFeeListener = new ServerApiCommon.EstimatedFeeListener() { @@ -1069,7 +926,7 @@ public class BtcEngine extends CoinEngine { return; } - fee = fee.setScale(8, RoundingMode.DOWN); + fee = fee.setScale(getDecimals(), RoundingMode.DOWN); switch (blockCount) { case ServerApiCommon.ESTIMATE_FEE_MINIMAL: @@ -1093,12 +950,19 @@ public class BtcEngine extends CoinEngine { @Override public void onFail(int blockCount, String message) { // TODO - add fail counter to terminate after NNN tries - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiCommon.requestBtcEstimatedFee(blockCount); - return; +// 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); + coinData.setUseBlockcypher(true); + try { + requestFee(blockchainRequestsCallbacks, targetAddress, amount); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); } - ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); - blockchainRequestsCallbacks.onComplete(false); } }; serverApiCommon.setBtcEstimatedFeeListener(estimatedFeeListener); @@ -1107,110 +971,63 @@ public class BtcEngine extends CoinEngine { serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_NORMAL); serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL); - } - } + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); -// /blockcypher/ -// @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 ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); -// -// 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); -// } -// -// 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)); -// -// 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"); -// -// coinData.minFee = convertToAmount(minIntAmount); -// coinData.normalFee = convertToAmount(normalIntAmount); -// coinData.maxFee = convertToAmount(maxIntAmount); -// } catch (Exception e) { -// e.printStackTrace(); -// Log.e(TAG, "FAIL BLOCKCYPHER_FEE Exception"); -// } -// -// if (serverApiBlockcypher.isRequestsSequenceCompleted()) { -// blockchainRequestsCallbacks.onComplete(!ctx.hasError()); -// } else { -// blockchainRequestsCallbacks.onProgress(); -// } -// } -// -// @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_FEE, "", ""); -// } - - @Override - public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { - final String txStr = BTCUtils.toHex(txForSend); - if (useElectrum) { - final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - - ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { + 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) { + Log.e(TAG, "Wrong response type for requestFee"); + ctx.setError("Wrong response type for requestFee"); + blockchainRequestsCallbacks.onComplete(false); + } + + 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)); + + 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"); + + coinData.minFee = convertToAmount(minIntAmount); + coinData.normalFee = convertToAmount(normalIntAmount); + coinData.maxFee = convertToAmount(maxIntAmount); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_FEE Exception"); + } + + 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); + + serverApiBlockcypher.setResponseListener(blockcypherListener); + + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_FEE, "", ""); + } + } - serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); - } else { - final ServerApiSoChain serverApi = new ServerApiSoChain(); + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { + final String txStr = BTCUtils.toHex(txForSend); + if (!coinData.isUseBlockcypher()) { + final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() { @Override @@ -1243,27 +1060,221 @@ public class BtcEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApi.setSendTxListener(listener); - serverApi.requestSendTransaction(ctx.getBlockchain(), txStr); + serverApiSoChain.setSendTxListener(listener); + serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr); + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + 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); } } +} -// /blockcypher/ -// @Override -// public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { -// final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); -// final String txStr = BTCUtils.toHex(txForSend); +//electrum balance +// final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); // -// ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { -// @Override -// public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { -// String resultString = blockcypherResponse.toString(); +// 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 { -// if (resultString.isEmpty()) { -// ctx.setError("No response from node"); +// 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.outputN = 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.script = 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())); + +//electrum fee +// 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()); + +//electrum send +// final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); +// +// 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 { // TODO: Make check for a valid send response +// } else { // ctx.setError(null); // blockchainRequestsCallbacks.onComplete(true); // } @@ -1274,27 +1285,57 @@ public class BtcEngine extends CoinEngine { // } 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()); +// 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)); -} \ No newline at end of file +//electrum check pending +//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)); \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/cardano/CardanoData.java b/app/src/main/java/com/tangem/wallet/cardano/CardanoData.java index 9e46ad937a..e3ba66dc73 100644 --- a/app/src/main/java/com/tangem/wallet/cardano/CardanoData.java +++ b/app/src/main/java/com/tangem/wallet/cardano/CardanoData.java @@ -22,7 +22,7 @@ public class CardanoData extends CoinData { // int gatheredUnspents = 0; if( unspentOutputs ==null ) return ""; // for (int i = 0; i < unspentOutputs.size(); i++) { -// if (unspentOutputs.get(i).Raw != null && unspentOutputs.get(i).Raw.length() > 1) gatheredUnspents++; +// if (unspentOutputs.get(i).script != null && unspentOutputs.get(i).script.length() > 1) gatheredUnspents++; // } return String.valueOf(unspentOutputs.size()) + " unspents";// (" + String.valueOf(gatheredUnspents) + " received)"; } diff --git a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java index e3519b2b28..73d154ee51 100644 --- a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java +++ b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java @@ -103,13 +103,6 @@ public class CardanoEngine extends CoinEngine { return "ADA"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; diff --git a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java index bc7c58c4d9..b3c3853da9 100644 --- a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java @@ -85,13 +85,6 @@ public class DucatusEngine extends BtcEngine { return "LTC"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; @@ -525,7 +518,7 @@ public class DucatusEngine extends BtcEngine { String txHash = new String(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(raw)))); //TODO: check for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { if (tx.txID.equals(txHash)) - tx.Raw = raw; + tx.script = raw; } } catch (Exception e) { e.printStackTrace(); @@ -548,8 +541,8 @@ public class DucatusEngine extends BtcEngine { for (InsightResponse utxo : utxoList) { BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); trUnspent.txID = utxo.getTxid(); - trUnspent.Amount = utxo.getSatoshis(); - trUnspent.Height = utxo.getHeight(); + trUnspent.amount = utxo.getSatoshis(); + trUnspent.outputN = utxo.getHeight(); coinData.getUnspentTransactions().add(trUnspent); } diff --git a/app/src/main/java/com/tangem/wallet/eos/EosEngine.java b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java index 8dc387cafd..17f281efad 100644 --- a/app/src/main/java/com/tangem/wallet/eos/EosEngine.java +++ b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java @@ -115,13 +115,6 @@ public class EosEngine extends CoinEngine { return Blockchain.Eos.getCurrency(); } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; @@ -336,8 +329,8 @@ public class EosEngine extends CoinEngine { // return address; // byte[] csum = Ripemd160.from(pkCompressed).bytes(); -// csum = Raw.copy(csum, 0, 4); -// byte[] addy = Raw.concat(pkCompressed, csum); +// csum = script.copy(csum, 0, 4); +// byte[] addy = script.concat(pkCompressed, csum); // StringBuffer bf = new StringBuffer("EOS"); // bf.append(Base58.encode(addy)); // return bf.toString() + " " + address; diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index e1c98c5bd8..bfc7238d51 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -91,13 +91,6 @@ public class EthEngine extends CoinEngine { return Blockchain.Ethereum.getCurrency(); } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; diff --git a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java index 3bf36b1450..680c112119 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -2,15 +2,11 @@ package com.tangem.wallet.ltc; import android.net.Uri; import android.text.InputFilter; -import android.util.Log; -import com.tangem.data.network.ElectrumRequest; -import com.tangem.data.network.ServerApiElectrum; 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.TangemContext; import com.tangem.wallet.Transaction; import com.tangem.wallet.UnspentOutputInfo; @@ -25,8 +21,6 @@ import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.wallet.R; -import org.json.JSONException; - import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; @@ -36,7 +30,6 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; public class LtcEngine extends BtcEngine { private static final String TAG = LtcEngine.class.getSimpleName(); @@ -88,13 +81,6 @@ public class LtcEngine extends BtcEngine { return "LTC"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; @@ -402,7 +388,7 @@ public class LtcEngine extends BtcEngine { 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)); + unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null)); } long fullAmount = 0; diff --git a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java index 6b8853af1f..a961068cb3 100644 --- a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java @@ -83,11 +83,6 @@ public class NftTokenEngine extends CoinEngine { return new InputFilter[0]; } - @Override - public String getOfflineBalanceHTML() { - return ctx.getString(R.string.not_implemented); - } - protected String getContractAddress(TangemCard card) { return card.getContractAddress(); } diff --git a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java index 63df366125..c207da2832 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -138,11 +138,6 @@ public class TokenEngine extends CoinEngine { return this.getBlockchain().getCurrency(); } - @Override - public String getOfflineBalanceHTML() { - return ctx.getString(R.string.not_implemented); - } - protected static int getChainDecimals() { return 18; } diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java index a2e848e234..ad3946d022 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java @@ -1,6 +1,7 @@ package com.tangem.wallet.xlm; import android.net.Uri; +import android.os.StrictMode; import android.text.InputFilter; import android.util.Log; @@ -18,7 +19,9 @@ import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; import org.stellar.sdk.AssetTypeNative; +import org.stellar.sdk.CreateAccountOperation; import org.stellar.sdk.KeyPair; +import org.stellar.sdk.Operation; import org.stellar.sdk.PaymentOperation; import org.stellar.sdk.Transaction; import org.stellar.sdk.TransactionEx; @@ -85,13 +88,6 @@ public class XlmEngine extends CoinEngine { return "XLM"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; @@ -356,12 +352,22 @@ public class XlmEngine extends CoinEngine { @Override public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + + StrictMode.setThreadPolicy(policy); if (IncFee) { amountValue = new Amount(amountValue.subtract(feeValue), amountValue.getCurrency()); } - TransactionEx transaction = TransactionEx.buildEx(60, coinData.getAccountResponse(), new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build()); + Operation operation; + if (isAccountCreated(targetAddress)) + operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build(); + else + operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build(); + + TransactionEx transaction = TransactionEx.buildEx(60, coinData.getAccountResponse(), operation); + if (transaction.getFee() != convertToInternalAmount(feeValue).intValueExact()) { throw new Exception("Invalid fee!"); @@ -408,6 +414,26 @@ public class XlmEngine extends CoinEngine { }; } + + // network call inside, don't use on main thread + private boolean isAccountCreated(String address) { + final ServerApiStellar serverApi = new ServerApiStellar(); + + StellarRequest.Balance request = new StellarRequest.Balance(address); + + try { + serverApi.doStellarRequest(ctx, request); + } catch (IOException e) { + Log.e(TAG, e.getMessage()); + return true; // suppose account is created if anything goes wrong TODO:check + } + + if (request.errorResponse != null && request.errorResponse.getCode() == 404) + return false; + else + return true; + } + @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiStellar serverApi = new ServerApiStellar(); @@ -539,6 +565,8 @@ public class XlmEngine extends CoinEngine { return false; } - public int pendingTransactionTimeoutInSeconds() { return 10; } + public int pendingTransactionTimeoutInSeconds() { + return 10; + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index 1e2edabcb4..3df5e26532 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -63,8 +63,10 @@ public class XrpData extends CoinData { public CoinEngine.InternalAmount getBalanceInInternalUnits() { if (balanceUnconfirmed != null) return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceUnconfirmed).subtract(BigDecimal.valueOf(reserve)), "Drops"); - else + else if (balanceConfirmed != null) return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceConfirmed).subtract(BigDecimal.valueOf(reserve)), "Drops"); + else + return null; } // public Long getBalanceUnconfirmed() { diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index fbf7fe16d3..9a7bb12594 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -79,13 +79,6 @@ public class XrpEngine extends CoinEngine { return "XRP"; } - @Override - public String getOfflineBalanceHTML() { - InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); - Amount offlineAmount = convertToAmount(offlineInternalAmount); - return offlineAmount.toDescriptionString(getDecimals()); - } - @Override public boolean isBalanceNotZero() { if (coinData == null) return false; diff --git a/card-common/src/main/java/com/tangem/card_common/data/Manufacturer.java b/card-common/src/main/java/com/tangem/card_common/data/Manufacturer.java index 680a3f3fb5..23de40b9df 100644 --- a/card-common/src/main/java/com/tangem/card_common/data/Manufacturer.java +++ b/card-common/src/main/java/com/tangem/card_common/data/Manufacturer.java @@ -9,7 +9,8 @@ public enum Manufacturer { Unknown("", "Unknown"), SMARTCASH_AG("SMART CASH AG", "TANGEM AG"), DEVELOPERS_SMARTCASH_AG("DEVELOP CASH AG", "TANGEM AG (DEVELOPERS)"), - SMARTCASH("SMART CASH", "TANGEM"); + SMARTCASH("SMART CASH", "TANGEM"), + TANGEM("TANGEM", "TANGEM"); private String ID; private String officialName; diff --git a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt index f7a1d91c9a..86c32f5572 100644 --- a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt +++ b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt @@ -49,7 +49,7 @@ class LocalStorage artworks = HashMap() } - if (artworks.count() < 23) { + if (artworks.count() < 28) { // forceSave=true only on the last one putResourceArtworkToCatalog(R.drawable.card_default, false) putResourceArtworkToCatalog(R.drawable.card_default_nft, false) @@ -73,6 +73,11 @@ class LocalStorage putResourceArtworkToCatalog(R.drawable.card_ru032, false) putResourceArtworkToCatalog(R.drawable.card_ru037, false) putResourceArtworkToCatalog(R.drawable.card_ru039, false) + putResourceArtworkToCatalog(R.drawable.card_ru040, false) + putResourceArtworkToCatalog(R.drawable.card_ru041, false) + putResourceArtworkToCatalog(R.drawable.card_ru042, false) + putResourceArtworkToCatalog(R.drawable.card_ru043, false) + putResourceArtworkToCatalog(R.drawable.card_tg044, false) putResourceArtworkToCatalog(R.drawable.card_ff32, true) } if (batchesFile.exists()) { @@ -223,6 +228,10 @@ class LocalStorage hexCID in "CB05000010000000".."CB05000010009999" -> R.drawable.card_ru006 + //LTC and DGX + hexCID in "CB25000000000000".."CB25000000099999" -> R.drawable.card_ru043 + hexCID in "CB26000000000000".."CB26000000099999" -> R.drawable.card_tg044 + card.batch == "0004" -> R.drawable.card_ru006 card.batch == "0006" -> R.drawable.card_ru006 card.batch == "0010" -> R.drawable.card_ru006 @@ -247,7 +256,12 @@ class LocalStorage card.batch == "0021" -> R.drawable.card_ru031 card.batch == "0022" -> R.drawable.card_ru032 card.batch == "0025" -> R.drawable.card_ru037 + card.batch == "0027" -> R.drawable.card_ru038 + card.batch == "0030" -> R.drawable.card_ru038 card.batch == "0026" -> R.drawable.card_ru039 + card.batch == "0028" -> R.drawable.card_ru040 + card.batch == "0029" -> R.drawable.card_ru041 + card.batch == "0031" -> R.drawable.card_ru042 card.batch == "FF32" -> R.drawable.card_ff32 else -> null diff --git a/server-android/src/main/res/drawable/card_ru038.png b/server-android/src/main/res/drawable/card_ru038.png new file mode 100644 index 0000000000..8f7598bc99 Binary files /dev/null and b/server-android/src/main/res/drawable/card_ru038.png differ diff --git a/server-android/src/main/res/drawable/card_ru040.png b/server-android/src/main/res/drawable/card_ru040.png new file mode 100644 index 0000000000..9c3d27447a Binary files /dev/null and b/server-android/src/main/res/drawable/card_ru040.png differ diff --git a/server-android/src/main/res/drawable/card_ru041.png b/server-android/src/main/res/drawable/card_ru041.png new file mode 100644 index 0000000000..48fff948ce Binary files /dev/null and b/server-android/src/main/res/drawable/card_ru041.png differ diff --git a/server-android/src/main/res/drawable/card_ru042.png b/server-android/src/main/res/drawable/card_ru042.png new file mode 100644 index 0000000000..3206ceeb17 Binary files /dev/null and b/server-android/src/main/res/drawable/card_ru042.png differ diff --git a/server-android/src/main/res/drawable/card_ru043.png b/server-android/src/main/res/drawable/card_ru043.png new file mode 100644 index 0000000000..4d260d6f47 Binary files /dev/null and b/server-android/src/main/res/drawable/card_ru043.png differ diff --git a/server-android/src/main/res/drawable/card_tg044.png b/server-android/src/main/res/drawable/card_tg044.png new file mode 100644 index 0000000000..ac612d6034 Binary files /dev/null and b/server-android/src/main/res/drawable/card_tg044.png differ