diff --git a/app/build.gradle b/app/build.gradle index 0b381805ed..fb452b53ea 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,7 +46,7 @@ android { applicationIdSuffix ".debug_beta" buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("true")' manifestPlaceholders = [fabric_api_key: properties.hasProperty('fabric_api_key') - ? project.property('fabric_api_key') : 000] + ? project.property('fabric_api_key') : '000'] } } flavorDimensions "" @@ -91,27 +91,26 @@ dependencies { implementation project(':tangem-card') implementation project(':tangem-sdk') implementation project(':server-android') - // implementation 'com.github.TangemCash:card-common:0.1.2' - // implementation 'com.github.TangemCash:card-android:0.1.3' - // implementation 'com.github.TangemCash:server-android:0.1.4' - implementation 'android.arch.navigation:navigation-fragment:1.0.0' - implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0' + + implementation 'androidx.navigation:navigation-fragment:2.1.0' + implementation 'androidx.navigation:navigation-ui-ktx:2.1.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' - implementation "androidx.lifecycle:lifecycle-runtime:2.0.0" - implementation "androidx.lifecycle:lifecycle-common-java8:2.0.0" - implementation 'androidx.appcompat:appcompat:1.1.0-rc01' - implementation 'androidx.fragment:fragment:1.1.0-rc04' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' + implementation "androidx.lifecycle:lifecycle-runtime:2.1.0" + implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.fragment:fragment:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation "androidx.preference:preference:1.1.0-rc01" - implementation 'androidx.biometric:biometric:1.0.0-alpha04' - implementation 'com.google.android.material:material:1.1.0-alpha09' - implementation 'androidx.core:core-ktx:1.0.2' + implementation "androidx.preference:preference:1.1.0" + implementation 'androidx.biometric:biometric:1.0.0-beta01' + implementation 'androidx.core:core-ktx:1.1.0' + implementation 'com.google.android.material:material:1.1.0-alpha10' + implementation 'com.google.dagger:dagger:2.21' kapt 'com.google.dagger:dagger-compiler:2.21' annotationProcessor 'com.google.dagger:dagger-compiler:2.21' - implementation 'com.google.zxing:core:3.3.3' + implementation 'com.google.zxing:core:3.4.0' implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.madgag.spongycastle:core:1.56.0.0' implementation 'com.madgag.spongycastle:prov:1.56.0.0' diff --git a/app/src/main/java/com/tangem/Constant.kt b/app/src/main/java/com/tangem/Constant.kt index 5fcec8f2a6..db0b7531de 100644 --- a/app/src/main/java/com/tangem/Constant.kt +++ b/app/src/main/java/com/tangem/Constant.kt @@ -97,4 +97,6 @@ object Constant { const val REQUEST_CODE_SCAN_QR_SECRET = "REQUEST_CODE_SCAN_QR_SECRET" const val REQUEST_CODE_SCAN_QR_USER_ID = "REQUEST_CODE_SCAN_QR_USER_ID" + const val TERMINAL_PRIVATE_KEY = "terminalPrivateKey" + const val TERMINAL_PUBLIC_KEY = "terminalPublicKey" } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 2f50bdd5de..782234d4ed 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -25,6 +25,7 @@ public enum Blockchain { MaticTestNet("MATIC/test", "MTX", 1.0, R.drawable.tangem2, "Matic Testnet"), Stellar("XLM", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"), StellarTestNet("XLM/test", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"), + StellarAsset("Asset", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"), Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { diff --git a/app/src/main/java/com/tangem/data/dp/PrefsManager.kt b/app/src/main/java/com/tangem/data/dp/PrefsManager.kt index a37c609121..b616aa5435 100644 --- a/app/src/main/java/com/tangem/data/dp/PrefsManager.kt +++ b/app/src/main/java/com/tangem/data/dp/PrefsManager.kt @@ -3,9 +3,9 @@ package com.tangem.data.dp import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences - import com.orhanobut.hawk.Hawk import com.tangem.Constant +import com.tangem.tangem_card.reader.CardCrypto class PrefsManager { companion object { @@ -47,4 +47,18 @@ class PrefsManager { } fun getAllCids(): String = Hawk.get("CID_Key", "") + + val terminalKeys: Map + get() { + val privateKey = Hawk.get(Constant.TERMINAL_PRIVATE_KEY) ?: byteArrayOf() + val publicKey = Hawk.get(Constant.TERMINAL_PUBLIC_KEY) ?: byteArrayOf() + return if (privateKey.isNotEmpty() && publicKey.isNotEmpty()) { + mapOf(Constant.TERMINAL_PRIVATE_KEY to privateKey, Constant.TERMINAL_PUBLIC_KEY to publicKey) + } else { + val keys = CardCrypto.generateTerminalKeys() + Hawk.put(Constant.TERMINAL_PRIVATE_KEY, keys[Constant.TERMINAL_PRIVATE_KEY]) + Hawk.put(Constant.TERMINAL_PUBLIC_KEY, keys[Constant.TERMINAL_PUBLIC_KEY]) + keys + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java index b057787494..0e51596e15 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java @@ -2,13 +2,14 @@ package com.tangem.data.network; import android.util.Log; +import androidx.annotation.NonNull; + import com.tangem.data.network.model.AdaliteBody; import com.tangem.data.network.model.AdaliteResponse; import com.tangem.data.network.model.AdaliteResponseUtxo; import java.util.List; -import androidx.annotation.NonNull; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -26,7 +27,14 @@ public class ServerApiAdalite { private int requestsCount = 0; - public static String lastNode; + private final String adaliteURL1 = "https://explorer2.adalite.io"; //TODO: make random selection, add more?, move + private final String adaliteURL2 = "https://nodes.southeastasia.cloudapp.azure.com"; + + private String currentURL = adaliteURL1; + + public String getCurrentURL() { + return currentURL; + } public boolean isRequestsSequenceCompleted() { Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); @@ -50,12 +58,14 @@ public class ServerApiAdalite { } public void requestData(String method, String wallet, String tx) { + requestData(method, wallet, tx, false); + } + + public void requestData(String method, String wallet, String tx, boolean isRetry) { requestsCount++; - String adaliteURL = "https://explorer2.adalite.io"; //TODO: make random selection - this.lastNode = adaliteURL; //TODO: show node instead of URL Retrofit retrofitAdalite = new Retrofit.Builder() - .baseUrl(adaliteURL) + .baseUrl(currentURL) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -69,20 +79,32 @@ public class ServerApiAdalite { 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 { - responseListener.onFail(method, String.valueOf(response.code())); Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + + if (!isRetry) { + retryRequest(method, wallet, tx); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + } } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - responseListener.onFail(method, String.valueOf(t.getMessage())); Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + requestsCount--; + + if (!isRetry) { + retryRequest(method, wallet, tx); + } else { + responseListener.onFail(method, String.valueOf(t.getMessage())); + } } }); break; @@ -92,20 +114,32 @@ public class ServerApiAdalite { outputsCall.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 { - responseListener.onFail(method, String.valueOf(response.code())); Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + + if (!isRetry) { + retryRequest(method, wallet, tx); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + } } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - responseListener.onFail(method, String.valueOf(t.getMessage())); Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + requestsCount--; + + if (!isRetry) { + retryRequest(method, wallet, tx); + } else { + responseListener.onFail(method, String.valueOf(t.getMessage())); + } } }); break; @@ -115,30 +149,48 @@ public class ServerApiAdalite { sendCall.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 { - responseListener.onFail(method, String.valueOf(response.code())); Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + + if (!isRetry) { + retryRequest(method, wallet, tx); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + } } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - responseListener.onFail(method, String.valueOf(t.getMessage())); Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + requestsCount--; + + if (!isRetry) { + retryRequest(method, wallet, tx); + } else { + responseListener.onFail(method, String.valueOf(t.getMessage())); + } } }); break; default: + requestsCount--; responseListener.onFail(method, "undeclared method"); Log.e(TAG, "requestData " + method + " onFailure - undeclared method"); break; } } + + private void retryRequest(String method, String wallet, String tx) { + currentURL = adaliteURL2; + requestData(method, wallet, tx, true); + } } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java index a477364f12..bf53beb20d 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java @@ -1,8 +1,9 @@ package com.tangem.data.network; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import com.tangem.App; import com.tangem.data.network.model.InfuraBody; import com.tangem.data.network.model.InfuraResponse; @@ -84,8 +85,9 @@ public class ServerApiInfura { call.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 { @@ -96,6 +98,7 @@ public class ServerApiInfura { @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()); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java index 7771a2d2f2..e3eae3384a 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java @@ -2,12 +2,13 @@ package com.tangem.data.network; import android.util.Log; +import androidx.annotation.NonNull; + import com.tangem.data.network.model.InsightBody; import com.tangem.data.network.model.InsightResponse; import java.util.List; -import androidx.annotation.NonNull; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -64,8 +65,9 @@ public class ServerApiInsight { call.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 { @@ -76,6 +78,7 @@ public class ServerApiInsight { @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()); } @@ -108,8 +111,9 @@ public class ServerApiInsight { call.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 { @@ -120,6 +124,7 @@ public class ServerApiInsight { @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()); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiMatic.java b/app/src/main/java/com/tangem/data/network/ServerApiMatic.java index 8686efa7ef..448fa02fc6 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiMatic.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiMatic.java @@ -1,8 +1,9 @@ package com.tangem.data.network; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import com.tangem.App; import com.tangem.data.network.model.InfuraBody; import com.tangem.data.network.model.InfuraResponse; @@ -84,8 +85,9 @@ public class ServerApiMatic { call.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 { @@ -96,6 +98,7 @@ public class ServerApiMatic { @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()); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRipple.java b/app/src/main/java/com/tangem/data/network/ServerApiRipple.java index e4c82722fb..85563c0df4 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiRipple.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiRipple.java @@ -135,8 +135,9 @@ public class ServerApiRipple { call.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 { @@ -147,6 +148,7 @@ public class ServerApiRipple { @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()); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java index 198f5cc6b0..ce1c152a39 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java @@ -1,8 +1,9 @@ package com.tangem.data.network; -import androidx.annotation.NonNull; import android.util.Log; +import androidx.annotation.NonNull; + import com.tangem.App; import com.tangem.data.network.model.InfuraBody; import com.tangem.data.network.model.InfuraResponse; @@ -84,8 +85,9 @@ public class ServerApiRootstock { call.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 { @@ -96,6 +98,7 @@ public class ServerApiRootstock { @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()); } 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 0c73849056..ceb90697d5 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiSoChain.java @@ -2,11 +2,12 @@ package com.tangem.data.network; import android.util.Log; +import androidx.annotation.NonNull; + 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; @@ -164,9 +165,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--; txInfoListener.onSuccess(response.body()); } else { txInfoListener.onFail(String.valueOf(response.code())); @@ -175,6 +176,7 @@ public class ServerApiSoChain { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { + requestsCount--; Log.e(TAG, "requestAddressBalance onFailure " + t.getMessage()); txInfoListener.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 18541956a1..9882a26902 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiStellar.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiStellar.java @@ -31,6 +31,15 @@ import io.reactivex.schedulers.Schedulers; * {@link Listener}.onSuccess(...) callback */ public class ServerApiStellar { + + public ServerApiStellar(Blockchain blockchain) { + if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset) { + currentURL = ServerURL.API_STELLAR; + } else { + currentURL = ServerURL.API_STELLAR_TESTNET; + } + } + private static String TAG = ServerApiStellar.class.getSimpleName(); /** @@ -41,6 +50,12 @@ public class ServerApiStellar { private int requestsCount = 0; + private String currentURL; + + public String getCurrentURL() { + return currentURL; + } + public boolean isRequestsSequenceCompleted() { LOG.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); return requestsCount <= 0; @@ -82,9 +97,15 @@ public class ServerApiStellar { * @param ctx * @param stellarRequest */ + public void requestData(TangemContext ctx, StellarRequest.Base stellarRequest) { + requestData(ctx, stellarRequest, false); + } + + public void requestData(TangemContext ctx, StellarRequest.Base stellarRequest, boolean isRetry) { requestsCount++; LOG.i(TAG, String.format("New request[%d]: %s", requestsCount, stellarRequest.getClass().getSimpleName())); + Observable stellarObserver = Observable.just(stellarRequest) .doOnEach(stellarRequest1 -> doStellarRequest(ctx, stellarRequest)) @@ -96,9 +117,9 @@ public class ServerApiStellar { return Observable.just(stellarRequest1); } ) - .retryWhen(errors -> errors - .filter(throwable -> (throwable instanceof IOException) || (throwable instanceof ErrorResponse)) - .zipWith(Observable.range(1, 4), (n, i) -> i)) +// .retryWhen(errors -> errors +// .filter(throwable -> (throwable instanceof IOException) || (throwable instanceof ErrorResponse)) +// .zipWith(Observable.range(1, 4), (n, i) -> i)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); @@ -114,9 +135,14 @@ public class ServerApiStellar { requestsCount--; LOG.e(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onError " + e.getMessage()); LOG.e(TAG, String.format("%d requests left in processing", requestsCount)); - stellarRequest.setError(ctx.getString(R.string.loaded_wallet_error_obtaining_blockchain_data)); - //setErrorOccurred(e.getMessage());//; - listener.onFail(stellarRequest); + + if (isRetry || stellarRequest.errorResponse.getCode() == 404) { + stellarRequest.setError(e.getMessage()); + //setErrorOccurred(e.getMessage());//; + listener.onFail(stellarRequest); + } else { + retryRequest(ctx, stellarRequest); + } } /** @@ -128,7 +154,12 @@ public class ServerApiStellar { LOG.e(TAG, String.format("%d requests left in processing", requestsCount)); if (stellarRequest.getError() != null) { LOG.i(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onComplete, error!=null"); - listener.onFail(stellarRequest); + + if (isRetry || stellarRequest.errorResponse.getCode() == 404) { + listener.onFail(stellarRequest); + } else { + retryRequest(ctx, stellarRequest); + } } else { LOG.e(TAG, "requestData " + stellarRequest.getClass().getSimpleName() + " onComplete, error==null"); listener.onSuccess(stellarRequest); @@ -142,12 +173,12 @@ public class ServerApiStellar { stellarRequest.setError(null); try { Server server; - if (ctx.getBlockchain() == Blockchain.Stellar) { + if (ctx.getBlockchain() == Blockchain.Stellar || ctx.getBlockchain() == Blockchain.StellarAsset) { Network.usePublicNetwork(); - server = new Server(ServerURL.API_STELLAR); + server = new Server(currentURL); } else if (ctx.getBlockchain() == Blockchain.StellarTestNet) { Network.useTestNetwork(); - server = new Server(ServerURL.API_STELLAR_TESTNET); + server = new Server(currentURL); } else { throw new IOException("Wrong blockchain for ServerApiStellar"); } @@ -166,4 +197,9 @@ public class ServerApiStellar { } } + public void retryRequest (TangemContext ctx, StellarRequest.Base stellarRequest) { + currentURL = ServerURL.API_STELLAR_RESERVE; + requestData(ctx, stellarRequest, true); + } + } \ No newline at end of file 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 45515132d2..f1eda5d63e 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -11,8 +11,9 @@ class ServerURL { static final String API_BLOCKCYPHER = "https://api.blockcypher.com/"; static final String API_BINANCE = "https://dex.binance.org/"; static final String API_BINANCE_TESTNET = "https://testnet-dex.binance.org/"; - static final String API_MATIC_TESTNET = "https://testnet2.matic.network"; + static final String API_MATIC_TESTNET = "https://testnet2.matic.network/"; static final String API_STELLAR = "https://horizon.stellar.org/"; - static final String API_STELLAR_TESTNET = "https://horizon-testnet.stellar.org"; + static final String API_STELLAR_RESERVE = "https://horizon.sui.li/"; + static final String API_STELLAR_TESTNET = "https://horizon-testnet.stellar.org/"; static final String API_BLOCKCHAIN_INFO = "https://blockchain.info/"; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt index 3ec382c142..b68c82ab28 100644 --- a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt @@ -1,26 +1,21 @@ -@file:Suppress("ObsoleteExperimentalCoroutines") - package com.tangem.ui.activity -import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo -import android.content.pm.PackageManager import android.nfc.NfcAdapter import android.nfc.Tag import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProviders +import androidx.navigation.findNavController import com.scottyab.rootbeer.RootBeer import com.tangem.App -import com.tangem.Constant import com.tangem.di.ToastHelper import com.tangem.tangem_sdk.android.nfc.NfcLifecycleObserver import com.tangem.tangem_sdk.android.reader.NfcManager import com.tangem.ui.dialog.RootFoundDialog +import com.tangem.util.navigateSafely import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import javax.inject.Inject @@ -44,6 +39,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) { val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) if (tag != null) { + findNavController(R.id.nav_host_fragment).navigateSafely(R.id.main) onTagDiscovered(tag) } } @@ -59,8 +55,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - verifyPermissions() - nfcManager = NfcManager(this, this) lifecycle.addObserver(NfcLifecycleObserver(nfcManager)) @@ -79,13 +73,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { RootFoundDialog().show(supportFragmentManager, RootFoundDialog.TAG) } - private fun verifyPermissions() { - NfcManager.verifyPermissions(this) - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), Constant.REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS) - } - } - override fun onTagDiscovered(tag: Tag) { val activeFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) ?.childFragmentManager?.primaryNavigationFragment diff --git a/app/src/main/java/com/tangem/ui/activity/SendTransactionActivity.kt b/app/src/main/java/com/tangem/ui/activity/SendTransactionActivity.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/com/tangem/ui/activity/SettingsActivity.kt b/app/src/main/java/com/tangem/ui/activity/SettingsActivity.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt b/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt index 776d159e6b..80bbf19131 100644 --- a/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/BaseFragment.kt @@ -11,6 +11,7 @@ import androidx.navigation.fragment.NavHostFragment.findNavController import com.tangem.ui.activity.MainActivity import com.tangem.ui.navigation.NavigationResult import com.tangem.ui.navigation.NavigationResultListener +import com.tangem.util.navigateSafely abstract class BaseFragment : Fragment() { @@ -74,13 +75,7 @@ abstract class BaseFragment : Fragment() { } protected fun navigateToDestination(@IdRes destination: Int, data: Bundle? = null) { - try { - findNavController(this).navigate(destination, data) - } catch (e: IllegalArgumentException) { - Log.w(this::class.java.simpleName, e.message) - } catch (e: IllegalStateException) { - Log.w(this::class.java.simpleName, e.message) - } + findNavController(this).navigateSafely(destination, data) } protected fun navigateBackWithResult(resultCode: Int, data: Bundle? = null, diff --git a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt index 98d5aea292..ecfff929d0 100644 --- a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt @@ -1,6 +1,7 @@ package com.tangem.ui.fragment import android.app.Activity +import android.app.AlertDialog import android.nfc.NfcAdapter import android.nfc.Tag import android.nfc.tech.IsoDep @@ -10,6 +11,7 @@ import android.widget.PopupMenu import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf +import androidx.lifecycle.ViewModelProviders import com.tangem.App import com.tangem.Constant import com.tangem.data.Logger @@ -17,6 +19,7 @@ import com.tangem.tangem_card.data.TangemCard import com.tangem.tangem_card.reader.CardProtocol import com.tangem.tangem_card.tasks.CustomReadCardTask import com.tangem.tangem_card.tasks.ReadCardInfoTask +import com.tangem.tangem_sdk.android.data.PINStorage import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation import com.tangem.tangem_sdk.android.reader.NfcReader import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD @@ -56,6 +59,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader private var task: CustomReadCardTask? = null private var lastTag: Tag? = null private var zipFile: File? = null + private var unknownBlockchain = false override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { setHasOptionsMenu(true) @@ -82,7 +86,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) -// viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) + viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) // // // show snackbar about new version app // viewModel.getVersionName().observe(this, Observer { text -> @@ -112,6 +116,11 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader } override fun onTagDiscovered(tag: Tag) { + if (unknownBlockchain) { + (activity as MainActivity).nfcManager.ignoreTag(tag) + return + } + try { // get IsoDep handle and run cardReader thread val isoDep = IsoDep.get(tag) @@ -122,6 +131,10 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader lastTag = tag + val terminalKeys = viewModel.getTerminalKeys() + PINStorage.setTerminalPrivateKey(terminalKeys[Constant.TERMINAL_PRIVATE_KEY]) + PINStorage.setTerminalPublicKey(terminalKeys[Constant.TERMINAL_PUBLIC_KEY]) + task = ReadCardInfoTask(NfcReader((activity as MainActivity).nfcManager, isoDep), App.localStorage, App.pinStorage, this) task?.start() @@ -159,6 +172,10 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader val card = TangemCard(uid) cardInfo.getBundle(EXTRA_TANGEM_CARD)?.let { card.loadFromBundle(it) } + val terminalKeys = viewModel.getTerminalKeys() + card.terminalPrivateKey = terminalKeys[Constant.TERMINAL_PRIVATE_KEY] + card.terminalPublicKey = terminalKeys[Constant.TERMINAL_PUBLIC_KEY] + val ctx = TangemContext(card) when { @@ -172,7 +189,8 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader ctx.saveToBundle(bundle) navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY, R.id.action_main_to_loadedWalletFragment, bundle) - + } else { + showUnkownBlockchainWarning() } } card.status == TangemCard.Status.Empty -> { @@ -219,6 +237,17 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader rlProgressBar?.postDelayed({ rlProgressBar?.visibility = View.GONE }, 500) } + private fun showUnkownBlockchainWarning() { + unknownBlockchain = true + AlertDialog.Builder(context) + .setTitle(R.string.dialog_warning) + .setMessage(R.string.alert_unknown_blockchain) + .setPositiveButton(R.string.general_ok) { _, _ -> } + .setOnDismissListener { unknownBlockchain = false } + .create() + .show() + } + override fun onReadCancel() { task = null ReadCardInfoTask.resetLastReadInfo() diff --git a/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt b/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt index d9b47667c1..ce2cc664db 100644 --- a/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt +++ b/app/src/main/java/com/tangem/ui/fragment/MainViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.tangem.Constant +import com.tangem.data.dp.PrefsManager import com.tangem.data.network.ServerApiCommon import com.tangem.wallet.BuildConfig @@ -35,4 +36,9 @@ class MainViewModel : ViewModel() { } serverApiCommon.requestLastVersion() } + + fun getTerminalKeys(): Map { + return PrefsManager.getInstance().terminalKeys + } + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/QrScanFragment.kt b/app/src/main/java/com/tangem/ui/fragment/QrScanFragment.kt deleted file mode 100644 index 90063ec96c..0000000000 --- a/app/src/main/java/com/tangem/ui/fragment/QrScanFragment.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.tangem.ui.fragment - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.app.ActivityCompat -import com.google.zxing.Result -import com.tangem.Constant -import me.dm7.barcodescanner.zxing.ZXingScannerView - -class QrScanFragment : BaseFragment(), ZXingScannerView.ResultHandler { - companion object { - fun callingIntent(context: Context): Intent { - return Intent(context, QrScanFragment::class.java) - } - } - - override val layoutId = 0 - - private var scannerView: ZXingScannerView? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (ActivityCompat.checkSelfPermission(context!!, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) - ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.CAMERA), 1) - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - scannerView = ZXingScannerView(activity) - return scannerView - } - - override fun onPause() { - super.onPause() - scannerView?.stopCamera() - - } - - override fun onResume() { - super.onResume() - scannerView?.setResultHandler(this) - scannerView?.startCamera() - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - 1 -> { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) - - else { - navigateBackWithResult(Activity.RESULT_CANCELED) - } - } - } - } - - override fun handleResult(result: Result) { - val data = Bundle() - data.putString(Constant.EXTRA_QR_CODE, result.text) - - navigateBackWithResult(Activity.RESULT_OK, data) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt index cf8e7642dc..e7543ce473 100644 --- a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt @@ -333,36 +333,48 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. var features = "" features += if (ctx.card!!.allowSwapPIN()!! && ctx.card!!.allowSwapPIN2()!!) { - "Allows change PIN1 and PIN2\n" + getString(R.string.details_both_pins_can_be_changed) } else if (ctx.card!!.allowSwapPIN()!!) { - "Allows change PIN1\n" + getString(R.string.details_pin1_can_be_changed) } else if (ctx.card!!.allowSwapPIN2()!!) { - "Allows change PIN2\n" + getString(R.string.details_pin2_can_be_changed) } else { - "Fixed PIN1 and PIN2\n" + getString(R.string.details_both_pins_fixed) } if (ctx.card!!.needCVC()!!) - features += "Requires CVC\n" + features += getString(R.string.details_required_cvc) if (ctx.card!!.supportDynamicNDEF()!!) { - features += "Dynamic NDEF for iOS\n" + features += getString(R.string.details_dynamic_ndef) } else if (ctx.card!!.supportNDEF()!!) - features += "NDEF\n" + features += getString(R.string.details_ndef) if (ctx.card!!.supportBlock()!!) - features += "Blockable\n" + features += getString(R.string.details_blockable) + + if (ctx.card!!.supportLinkingTerminal()) { + features += getString(R.string.details_linking_card_supported) + llLinkedCard.visibility = View.VISIBLE + } - if (ctx.card!!.supportOnlyOneCommandAtTime()!!) - features += "Atomic command mode" + if (ctx.card!!.supportOnlyOneCommandAtTime()) + features += getString(R.string.details_atomic_commmands) if (features.endsWith("\n")) features = features.substring(0, features.length - 1) tvFeatures.text = features + val textIsLinked = if (ctx.card.terminalIsLinked) { + getString(R.string.details_linked_card_to_phone) + } else { + getString(R.string.general_no) + } + tvIsLinked.text = textIsLinked + if (ctx.card!!.useDefaultPIN1()) { imgPIN.setImageResource(R.drawable.unlock_pin1) imgPIN.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_default_pin_1, Toast.LENGTH_LONG).show() } diff --git a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt b/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt index 9c21c5fb41..f9c5e38416 100644 --- a/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/additional/PrepareCryptonitOtherApiWithdrawalFragment.kt @@ -11,6 +11,7 @@ import com.tangem.data.Blockchain import com.tangem.data.network.CryptonitOtherApi import com.tangem.ui.activity.MainActivity import com.tangem.ui.fragment.BaseFragment +import com.tangem.ui.fragment.qr.CameraPermissionManager import com.tangem.ui.navigation.NavigationResultListener import com.tangem.wallet.CoinEngineFactory import com.tangem.wallet.R @@ -27,6 +28,7 @@ class PrepareCryptonitOtherApiWithdrawalFragment : BaseFragment(), NavigationRes override val layoutId = R.layout.fragment_prepare_cryptonit_other_api_withdrawal + private val cameraPermissionManager: CameraPermissionManager by lazy { CameraPermissionManager(this) } private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } private val cryptonit: CryptonitOtherApi by lazy { CryptonitOtherApi(context) } @@ -69,23 +71,11 @@ class PrepareCryptonitOtherApiWithdrawalFragment : BaseFragment(), NavigationRes // } } - ivCameraKey.setOnClickListener { - navigateForResult( - Constant.REQUEST_CODE_SCAN_QR_KEY, - R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) - } + ivCameraKey.setOnClickListener { checkPermissionsAndRunCamera() } - ivCameraSecret.setOnClickListener { - navigateForResult( - Constant.REQUEST_CODE_SCAN_QR_SECRET, - R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) - } + ivCameraSecret.setOnClickListener { checkPermissionsAndRunCamera() } - ivCameraUserId.setOnClickListener { - navigateForResult( - Constant.REQUEST_CODE_SCAN_QR_USER_ID, - R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) - } + ivCameraUserId.setOnClickListener { checkPermissionsAndRunCamera() } ivRefreshBalance.setOnClickListener { doRequestBalance() } @@ -125,6 +115,14 @@ class PrepareCryptonitOtherApiWithdrawalFragment : BaseFragment(), NavigationRes doRequestBalance() } + private fun checkPermissionsAndRunCamera() { + if (cameraPermissionManager.isPermissionGranted()) { + navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) + } else { + cameraPermissionManager.requirePermission() + } + } + override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { if (resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) { when (requestCode) { @@ -166,4 +164,11 @@ class PrepareCryptonitOtherApiWithdrawalFragment : BaseFragment(), NavigationRes } } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + cameraPermissionManager.handleRequestPermissionResult(requestCode, grantResults) { + navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/qr/CameraPermissionManager.kt b/app/src/main/java/com/tangem/ui/fragment/qr/CameraPermissionManager.kt new file mode 100644 index 0000000000..250370d40a --- /dev/null +++ b/app/src/main/java/com/tangem/ui/fragment/qr/CameraPermissionManager.kt @@ -0,0 +1,34 @@ +package com.tangem.ui.fragment.qr + +import android.Manifest +import android.content.pm.PackageManager +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.content.PermissionChecker +import com.tangem.ui.fragment.BaseFragment +import com.tangem.wallet.R + +class CameraPermissionManager(val fragment: BaseFragment) { + + fun isPermissionGranted(): Boolean { + return ContextCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.CAMERA) == + PermissionChecker.PERMISSION_GRANTED + } + + fun handleRequestPermissionResult(requestCode: Int, grantResults: IntArray, action: () -> Unit) { + when (requestCode) { + 1 -> { + if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(fragment.requireContext(), R.string.general_toast_no_permission, Toast.LENGTH_LONG).show() + } else { + action.invoke() + } + } + } + } + + fun requirePermission() { + fragment.requestPermissions(arrayOf(Manifest.permission.CAMERA), 1) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/qr/QrScanFragment.kt b/app/src/main/java/com/tangem/ui/fragment/qr/QrScanFragment.kt new file mode 100644 index 0000000000..9ee01f8cd9 --- /dev/null +++ b/app/src/main/java/com/tangem/ui/fragment/qr/QrScanFragment.kt @@ -0,0 +1,40 @@ +package com.tangem.ui.fragment.qr + +import android.app.Activity +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.zxing.Result +import com.tangem.Constant +import com.tangem.ui.fragment.BaseFragment +import me.dm7.barcodescanner.zxing.ZXingScannerView + +class QrScanFragment : BaseFragment(), ZXingScannerView.ResultHandler { + + override val layoutId = 0 + private var scannerView: ZXingScannerView? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + scannerView = ZXingScannerView(activity) + return scannerView + } + + override fun onPause() { + super.onPause() + scannerView?.stopCamera() + } + + override fun onResume() { + super.onResume() + scannerView?.setResultHandler(this) + scannerView?.startCamera() + } + + override fun handleResult(result: Result) { + val data = Bundle() + data.putString(Constant.EXTRA_QR_CODE, result.text) + navigateBackWithResult(Activity.RESULT_OK, data) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 70fa44d1fd..b2ef26de9b 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -595,23 +595,23 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte } if (ctx.message == null || ctx.message.isEmpty()) { - tvMessage.text = "" - tvMessage.visibility = View.GONE + tvMessage?.text = "" + tvMessage?.visibility = View.GONE } else { - tvMessage.text = ctx.message - tvMessage.visibility = View.VISIBLE + tvMessage?.text = ctx.message + tvMessage?.visibility = View.VISIBLE } - if (tvError.visibility == View.VISIBLE || tvMessage.visibility == View.VISIBLE) { + if (tvError?.visibility == View.VISIBLE || tvMessage?.visibility == View.VISIBLE) { timerHideErrorAndMessage = Timer() - timerHideErrorAndMessage!!.schedule( + timerHideErrorAndMessage?.schedule( timerTask { activity?.runOnUiThread { tvMessage?.visibility = View.GONE tvError?.visibility = View.GONE // clear only already viewed messages - if (tvMessage.text == ctx.message) ctx.message = null - if (tvError.text == ctx.error) ctx.error = null + if (tvMessage?.text == ctx.message) ctx.message = null + if (tvError?.text == ctx.error) ctx.error = null } }, 5000) @@ -627,9 +627,9 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte val validator = BalanceValidator() // TODO why attest=false? validator.check(ctx, false) - context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1.setTextColor(it) } - tvBalanceLine1.text = validator.firstLine - tvBalanceLine2.text = validator.getSecondLine(false) + context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) } + tvBalanceLine1?.text = getString(validator.firstLine) + tvBalanceLine2?.text = getString(validator.getSecondLine(false)) } val engine = CoinEngineFactory.create(ctx) @@ -698,7 +698,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte // Bitcoin, Litecoin, BitcoinCash, Stellar if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash || - ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet) { + ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) { ctx.coinData.setIsBalanceEqual(true) } diff --git a/app/src/main/java/com/tangem/util/NavigationExtensions.kt b/app/src/main/java/com/tangem/util/NavigationExtensions.kt new file mode 100644 index 0000000000..ff4df71b13 --- /dev/null +++ b/app/src/main/java/com/tangem/util/NavigationExtensions.kt @@ -0,0 +1,16 @@ +package com.tangem.util + +import android.os.Bundle +import android.util.Log +import androidx.annotation.IdRes +import androidx.navigation.NavController + +fun NavController.navigateSafely(@IdRes destination: Int, data: Bundle? = null) { + try { + this.navigate(destination, data) + } catch (e: IllegalArgumentException) { + Log.w(this::class.java.simpleName, e.message) + } catch (e: IllegalStateException) { + Log.w(this::class.java.simpleName, e.message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/BalanceValidator.java b/app/src/main/java/com/tangem/wallet/BalanceValidator.java index 9e5bc56951..9457557164 100644 --- a/app/src/main/java/com/tangem/wallet/BalanceValidator.java +++ b/app/src/main/java/com/tangem/wallet/BalanceValidator.java @@ -1,36 +1,40 @@ package com.tangem.wallet; +import androidx.annotation.StringRes; + import com.tangem.App; import com.tangem.tangem_card.data.TangemCard; public class BalanceValidator { - private String firstLine; - private String secondLine; + private @StringRes int firstLine; + private @StringRes int secondLine; private int score; private boolean hasPending; - public String getFirstLine() { + public @StringRes int getFirstLine() { return firstLine; } - public void setFirstLine(String value) { + public void setFirstLine(@StringRes int value) { firstLine = value; } - public String getSecondLine(Boolean recommend) { - if (!recommend) return secondLine; - if (score > 89) { - return "Safe to accept. " + secondLine; - } else if (score > 74) { - return "Not fully safe to accept. " + secondLine; - } else if (score > 30) { - return "Not safe to accept. " + secondLine; - } else { - return "Do not accept! " + secondLine; - } + public @StringRes int getSecondLine(Boolean recommend) { +// if (!recommend) + return secondLine; + +// if (score > 89) { +// return "Safe to accept. " + secondLine; +// } else if (score > 74) { +// return "Not fully safe to accept. " + secondLine; +// } else if (score > 30) { +// return "Not safe to accept. " + secondLine; +// } else { +// return "Do not accept! " + secondLine; +// } } - public void setSecondLine(String value) { + public void setSecondLine(@StringRes int value) { secondLine = value; } @@ -55,8 +59,8 @@ public class BalanceValidator { } public void check(TangemContext ctx, Boolean attest) { - firstLine = "Verification failed"; - secondLine = ""; + firstLine = R.string.balance_validator_first_line_verification_failed; + secondLine = R.string.empty_string; TangemCard card = ctx.getCard(); CoinEngine engine = CoinEngineFactory.INSTANCE.create(ctx); @@ -66,8 +70,8 @@ public class BalanceValidator { if( hasPending ) { - firstLine = "Pending transaction..."; - secondLine = "Swipe down to refresh"; + firstLine = R.string.balance_validator_first_line_pending_transaction; + secondLine = R.string.balance_validator_second_line_swipe_to_refresh; return; } @@ -76,38 +80,38 @@ public class BalanceValidator { if (!card.isWalletPublicKeyValid()) { score = 0; - firstLine = "Verification failed"; - secondLine = "Wallet verification failed. Tap again."; + firstLine = R.string.balance_validator_first_line_verification_failed; + secondLine = R.string.balance_validator_second_line_verification_failed; return; } if (card.isOnlineVerified() != null && !card.isOnlineVerified()) { score = 0; - firstLine = "Not genuine banknote"; - secondLine = "Tangem Attestation service says the banknote is not genuine."; + firstLine = R.string.balance_validator_first_line_not_genuine; + secondLine = R.string.balance_validator_second_line_failed_attestation; return; } if (card.isCodeConfirmed() != null && !card.isCodeConfirmed()) { score = 0; - firstLine = "Not genuine banknote"; - secondLine = "Firmware binary code verification failed"; + firstLine = R.string.balance_validator_first_line_not_genuine; + secondLine = R.string.balance_validator_second_line_failed_binary_code_verification; return; } if (card.PIN2 == TangemCard.PIN2_Mode.CustomPIN2) { score = 0; - firstLine = "Locked with PIN2"; - secondLine = "Ask the holder to disable PIN2 before accepting"; + firstLine = R.string.balance_validator_first_line_locked_pin2; + secondLine = R.string.balance_validator_second_line_disable_pin_2; return; } // rule 2.b if (card.isOnlineVerified()) { - secondLine += "Verified note identity. "; + secondLine += R.string.balance_validator_first_line_verify_identity; } else { score = 80; - secondLine += "Card identity was not verified. Cannot reach Tangem attestation service. "; + secondLine += R.string.balance_validator_second_line_identity_not_verified; } } } diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt index b289fac550..10aaf161f1 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt @@ -16,6 +16,7 @@ import com.tangem.wallet.matic.MaticTokenEngine import com.tangem.wallet.nftToken.NftTokenEngine import com.tangem.wallet.rsk.RskEngine import com.tangem.wallet.rsk.RskTokenEngine +import com.tangem.wallet.xlm.XlmAssetEngine import com.tangem.wallet.xlm.XlmEngine import com.tangem.wallet.xrp.XrpEngine @@ -46,6 +47,7 @@ object CoinEngineFactory { Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine() Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine() Blockchain.StellarTestNet, Blockchain.Stellar -> XlmEngine() + Blockchain.StellarAsset -> XlmAssetEngine() Blockchain.Eos -> EosEngine() else -> null } @@ -80,6 +82,8 @@ object CoinEngineFactory { MaticTokenEngine(context) else if (Blockchain.Stellar == context.blockchain || Blockchain.StellarTestNet == context.blockchain) XlmEngine(context) + else if (Blockchain.StellarAsset == context.blockchain) + XlmAssetEngine(context) else if (Blockchain.Eos == context.blockchain) EosEngine(context) else diff --git a/app/src/main/java/com/tangem/wallet/TangemContext.java b/app/src/main/java/com/tangem/wallet/TangemContext.java index aba5dc3e3f..07cb229329 100644 --- a/app/src/main/java/com/tangem/wallet/TangemContext.java +++ b/app/src/main/java/com/tangem/wallet/TangemContext.java @@ -39,6 +39,9 @@ public class TangemContext { if ((blockchain == Blockchain.Rootstock) && card.isToken()) { return Blockchain.RootstockToken; } + if (blockchain == Blockchain.Stellar && card.isToken()) { + return Blockchain.StellarAsset; + } return blockchain; } @@ -58,6 +61,9 @@ public class TangemContext { if (blockchain == Blockchain.NftToken) { return card.getTokenSymbol().substring(4) + "
" + getBlockchain().getOfficialName() + " NFT token"; } + if (blockchain == Blockchain.StellarAsset) { + return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " asset"; + } return blockchain.getOfficialName(); } diff --git a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt index b31677c586..3355580efe 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt +++ b/app/src/main/java/com/tangem/wallet/bch/BitcoinCashNode.kt @@ -2,7 +2,6 @@ package com.tangem.wallet.bch enum class BitcoinCashNode(val host: String, val port: Int, val proto: String) { N_001("electrumx.hillsideinternet.com", 50002, "ssl"), - N_002("electron.coinucopia.io", 50002, "ssl"), - N_003("blackie.c3-soft.com", 50002, "ssl"), - N_004("electrum.imaginary.cash", 50002, "ssl"), + N_002("blackie.c3-soft.com", 50002, "ssl"), + N_003("electrum.imaginary.cash", 50002, "ssl"), } \ No newline at end of file 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 25d5aa6cc2..953259ba17 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java @@ -234,8 +234,8 @@ public class BtcCashEngine extends CoinEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } @@ -249,18 +249,18 @@ public class BtcCashEngine extends CoinEngine { if (coinData.getBalanceUnconfirmed() != 0) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } @@ -275,8 +275,8 @@ public class BtcCashEngine extends CoinEngine { if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } // if(card.getFailedBalanceRequestCounter()!=0) { 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 e61cb7115f..ef2d8e087f 100644 --- a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java +++ b/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java @@ -226,30 +226,30 @@ public class BinanceEngine extends CoinEngine { if(coinData.isError404()) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("No account or network error"); - balanceValidator.setSecondLine("To create account send funds to this address"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_account); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account); } else { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } } if (coinData.isBalanceReceived()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalance().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalance().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } return true; 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 818006bcca..0fe90dd9f0 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -257,8 +257,8 @@ public class BtcEngine extends CoinEngine { try { if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } @@ -272,18 +272,18 @@ public class BtcEngine extends CoinEngine { if (coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed()) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } @@ -298,8 +298,8 @@ public class BtcEngine extends CoinEngine { if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } // if(card.getFailedBalanceRequestCounter()!=0) { @@ -641,11 +641,7 @@ public class BtcEngine extends CoinEngine { Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); } - if (serverApiBlockcypher.isRequestsSequenceCompleted()) { - checkPending(blockchainRequestsCallbacks); - } else { - blockchainRequestsCallbacks.onProgress(); - } + checkPending(blockchainRequestsCallbacks); } public void onSuccess(String method, BlockcypherFee blockcypherFee) { 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 6cf578abe3..ede859b03e 100644 --- a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java +++ b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java @@ -192,7 +192,7 @@ public class CardanoEngine extends CoinEngine { @Override public boolean checkNewTransactionAmount(Amount amount) { - if( BuildConfig.FLAVOR==Constant.FLAVOR_TANGEM_CARDANO ) { + if (BuildConfig.FLAVOR == Constant.FLAVOR_TANGEM_CARDANO) { return true; } if (coinData == null) return false; @@ -236,25 +236,25 @@ public class CardanoEngine extends CoinEngine { try { if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } if (coinData.isBalanceReceived()) {// && coinData.isBalanceEqual()) { TODO:check balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } return true; @@ -586,7 +586,8 @@ public class CardanoEngine extends CoinEngine { Amount feeDummy = new Amount(new BigDecimal(0.2).setScale(getDecimals(), RoundingMode.DOWN), getFeeCurrency()); try { OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction; - onNeedSendTransaction =(tx)->{}; // empty function to bypass exception + onNeedSendTransaction = (tx) -> { + }; // empty function to bypass exception SignTask.TransactionToSign ttsDummy = constructTransaction(amount, feeDummy, true, targetAddress); byte[] txForSendDummy = ttsDummy.onSignCompleted(new byte[64]); @@ -617,7 +618,7 @@ public class CardanoEngine extends CoinEngine { } coinData.setBalanceReceived(true); coinData.setBalance(adaliteResponse.getRight().getCaBalance().getGetCoin()); - coinData.setValidationNodeDescription(ServerApiAdalite.lastNode); + coinData.setValidationNodeDescription(serverApiAdalite.getCurrentURL()); //check pending if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { @@ -632,8 +633,8 @@ public class CardanoEngine extends CoinEngine { } } catch (Exception e) { - e.printStackTrace(); Log.e(TAG, "FAIL ADALITE_ADDRESS Exception"); + e.printStackTrace(); } if (serverApiAdalite.isRequestsSequenceCompleted()) { @@ -658,6 +659,7 @@ public class CardanoEngine extends CoinEngine { } } catch (Exception e) { + Log.e(TAG, "FAIL ADALITE_UNSPENT_OUTPUTS Exception"); e.printStackTrace(); } @@ -665,7 +667,6 @@ public class CardanoEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); - Log.e(TAG, "FAIL ADALITE_UNSPENT_OUTPUTS Exception"); } } @@ -678,9 +679,13 @@ public class CardanoEngine extends CoinEngine { @Override public void onFail(String method, String message) { - Log.i(TAG, "onFail: " + method + " " + message); + Log.e(TAG, "onFail: " + method + " " + message); ctx.setError(message); - blockchainRequestsCallbacks.onComplete(false); + if (serverApiAdalite.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); + } } }; @@ -752,10 +757,10 @@ public class CardanoEngine extends CoinEngine { @Override public void onFail(String method, String message) { - if (!serverApiAdalite.isRequestsSequenceCompleted()) { - ctx.setError(message); - blockchainRequestsCallbacks.onComplete(false); - } + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } }; serverApiAdalite.setResponseListener(responseListener); @@ -769,7 +774,9 @@ public class CardanoEngine extends CoinEngine { } @Override - public int pendingTransactionTimeoutInSeconds() { return 60; } + public int pendingTransactionTimeoutInSeconds() { + return 60; + } private String CalculateTxHash(String tx) throws CborException { Array txArray = (Array) CborDecoder.decode(BTCUtils.fromHex(tx)).get(0); 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 fa48a81bd5..4a29f13377 100644 --- a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java @@ -226,8 +226,8 @@ public class DucatusEngine extends BtcEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } @@ -241,18 +241,18 @@ public class DucatusEngine extends BtcEngine { if (coinData.getBalanceUnconfirmed() != 0) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived()) {// && coinData.isBalanceEqual()) { TODO:check balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } @@ -267,8 +267,8 @@ public class DucatusEngine extends BtcEngine { if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } // if(card.getFailedBalanceRequestCounter()!=0) { @@ -562,7 +562,7 @@ public class DucatusEngine extends BtcEngine { @Override public void onFail(String method, String message) { - if (!serverApiInsight.isRequestsSequenceCompleted()) { + if (!serverApiInsight.isRequestsSequenceCompleted()) { //TODO: rework request sequence ctx.setError(message); blockchainRequestsCallbacks.onComplete(false); } 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 235923561e..51705e5d92 100644 --- a/app/src/main/java/com/tangem/wallet/eos/EosEngine.java +++ b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java @@ -277,25 +277,25 @@ public class EosEngine extends CoinEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (getBalance() == null) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } if (coinData.isBalanceReceived()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (getBalance().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Restore internet connection to obtain trusted balance from blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online); } return true; 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 d0d50fa172..fb92bb5751 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -311,32 +311,32 @@ public class EthEngine extends CoinEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (getBalance() == null) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } if (!coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount())) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (getBalance().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Restore internet connection to obtain trusted balance from blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online); } return true; @@ -509,9 +509,12 @@ public class EthEngine extends CoinEngine { @Override public void onFail(String method, String message) { - if (!serverApiInfura.isRequestsSequenceCompleted()) { - ctx.setError(message); + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + if (serverApiInfura.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); } } }; 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 e57578e274..b8d834bce6 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -235,8 +235,8 @@ public class LtcEngine extends BtcEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } @@ -250,18 +250,18 @@ public class LtcEngine extends BtcEngine { if (coinData.getBalanceUnconfirmed() != 0) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } @@ -276,8 +276,8 @@ public class LtcEngine extends BtcEngine { if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online); } // if(card.getFailedBalanceRequestCounter()!=0) { @@ -418,8 +418,8 @@ public class LtcEngine extends BtcEngine { change = change - fees; } - final long amountFinal=amount; - final long changeFinal=change; + final long amountFinal = amount; + final long changeFinal = change; if (amount + fees > fullAmount) { throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); @@ -427,7 +427,7 @@ public class LtcEngine extends BtcEngine { final byte[][] txForSign = new byte[unspentOutputs.size()][]; final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][]; - final byte[][] bodyHash= new byte[unspentOutputs.size()][]; + final byte[][] bodyHash = new byte[unspentOutputs.size()][]; for (int i = 0; i < unspentOutputs.size(); ++i) { txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change); @@ -439,13 +439,14 @@ public class LtcEngine extends BtcEngine { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { - return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw; + return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; } @Override public byte[][] getHashesToSign() throws Exception { - byte[][] dataForSign=new byte[unspentOutputs.size()][]; - if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + byte[][] dataForSign = new byte[unspentOutputs.size()][]; + if (txForSign.length > 10) + throw new Exception("To much hashes in one transaction!"); for (int i = 0; i < unspentOutputs.size(); ++i) { dataForSign[i] = bodyDoubleHash[i]; } @@ -484,7 +485,7 @@ public class LtcEngine extends BtcEngine { unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); } - byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); + byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); notifyOnNeedSendTransaction(txForSend); return txForSend; } @@ -641,15 +642,13 @@ public class LtcEngine extends BtcEngine { } } } catch (Exception e) { - e.printStackTrace(); Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); + e.printStackTrace(); + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); } - if (serverApiBlockcypher.isRequestsSequenceCompleted()) { - checkPending(blockchainRequestsCallbacks); - } else { - blockchainRequestsCallbacks.onProgress(); - } + checkPending(blockchainRequestsCallbacks); } public void onSuccess(String method, BlockcypherFee blockcypherFee) { diff --git a/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java b/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java index b6ebf6c762..6568157b56 100644 --- a/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java @@ -1,6 +1,7 @@ package com.tangem.wallet.matic; import android.net.Uri; +import android.util.Log; import com.tangem.data.Blockchain; import com.tangem.data.network.ServerApiMatic; @@ -168,9 +169,12 @@ public class MaticTokenEngine extends TokenEngine { @Override public void onFail(String method, String message) { - if (!serverApiMatic.isRequestsSequenceCompleted()) { - ctx.setError(message); + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + if (serverApiMatic.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); } } }; 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 c8a78b7650..859d8f3d24 100644 --- a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java @@ -15,6 +15,7 @@ import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.CoinData; import com.tangem.wallet.CoinEngine; import com.tangem.wallet.Keccak256; +import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; import com.tangem.wallet.eth.EthData; import com.tangem.wallet.token.TokenData; @@ -210,20 +211,20 @@ public class NftTokenEngine extends CoinEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (coinData.getBalanceInInternalUnits() == null) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("No connection"); - balanceValidator.setSecondLine("Authenticity cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_connection); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_authenticity_not_verified); return false; } if (coinData.isBalanceReceived()) { if (isBalanceNotZero()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified in blockchain"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_in_blockchain); + balanceValidator.setSecondLine(R.string.empty_string); } else { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Authenticity was not verified"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_authenticity); + balanceValidator.setSecondLine(R.string.empty_string); } } @@ -277,10 +278,9 @@ public class NftTokenEngine extends CoinEngine { @Override public void onFail(String method, String message) { - if (!serverApiInfura.isRequestsSequenceCompleted()) { - ctx.setError(message); - blockchainRequestsCallbacks.onComplete(false); - } + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); } }; serverApiInfura.setResponseListener(responseListener); diff --git a/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java index c1eee2ed17..d968f05e93 100644 --- a/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java @@ -111,9 +111,12 @@ public class RskEngine extends EthEngine { @Override public void onFail(String method, String message) { - if (!serverApiRootstock.isRequestsSequenceCompleted()) { - ctx.setError(message); + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + if (serverApiRootstock.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); } } }; diff --git a/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java index cf07726bb0..0f54555f16 100644 --- a/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java @@ -140,9 +140,12 @@ public class RskTokenEngine extends TokenEngine { @Override public void onFail(String method, String message) { - if (!serverApiRootstock.isRequestsSequenceCompleted()) { - ctx.setError(message); + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + if (serverApiRootstock.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); } } }; 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 4021a462f3..df1a0088f1 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -382,32 +382,32 @@ public class TokenEngine extends CoinEngine { public boolean validateBalance(BalanceValidator balanceValidator) { if (getBalance() == null) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } if (!coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount())) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (getBalance().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Restore internet connection to obtain trusted balance from blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online); } return true; @@ -708,9 +708,12 @@ public class TokenEngine extends CoinEngine { @Override public void onFail(String method, String message) { - if (!serverApiInfura.isRequestsSequenceCompleted()) { - ctx.setError(message); + Log.e(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + if (serverApiInfura.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); } } }; diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java new file mode 100644 index 0000000000..84c40a7b53 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetData.java @@ -0,0 +1,177 @@ +package com.tangem.wallet.xlm; + +import android.os.Bundle; +import android.util.Log; + +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; + +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.responses.AccountResponse; +import org.stellar.sdk.responses.LedgerResponse; + +import java.math.BigDecimal; + +/* + * Created by dvol on 7.01.2019. + */ + +public class XlmAssetData extends CoinData { + + + public static class AccountResponseEx extends AccountResponse { + AccountResponseEx(String accountId, Long sequenceNumber) { + super(KeyPair.fromAccountId(accountId), sequenceNumber); + } + } + + private CoinEngine.Amount xlmBalance, assetBalance = null; + + private Long sequenceNumber = 0L; + private CoinEngine.Amount baseReserve = new CoinEngine.Amount("0.5", "XLM"); + private CoinEngine.Amount baseFee = new CoinEngine.Amount("0.00001", "XLM"); + private boolean error404 = false; + + @Override + public void clearInfo() { + super.clearInfo(); + xlmBalance = null; + assetBalance = null; + error404 = false; + } + + CoinEngine.Amount getXlmBalance() { + if (xlmBalance != null) { + return new CoinEngine.Amount(xlmBalance.subtract(getReserve()), "XLM"); + } else { + return null; + } + } + + CoinEngine.Amount getAssetBalance() { + return assetBalance; + } + + CoinEngine.Amount getReserve() { + return new CoinEngine.Amount(baseReserve.multiply(BigDecimal.valueOf(3)), "XLM"); + } + + CoinEngine.Amount getBaseFee() { + return baseFee; + } + + AccountResponse getAccountResponse() { + return new AccountResponseEx(getWallet(), sequenceNumber); + } + + void setAccountResponse(AccountResponse accountResponse) { + for (AccountResponse.Balance responseBalance : accountResponse.getBalances()) { + if (responseBalance.getAssetType().equals("native")) { + xlmBalance = new CoinEngine.Amount(responseBalance.getBalance(), "XLM"); + } else { + assetBalance = new CoinEngine.Amount(responseBalance.getBalance(), responseBalance.getAssetCode()); + } + } + sequenceNumber = accountResponse.getSequenceNumber(); + setBalanceReceived(true); + } + + void setLedgerResponse(LedgerResponse ledgerResponse) { + XlmEngine xlmEngine = new XlmEngine(); + baseReserve = xlmEngine.convertToAmount(new CoinEngine.InternalAmount(ledgerResponse.getBaseReserveInStroops(), "stroops")); + baseFee = xlmEngine.convertToAmount(new CoinEngine.InternalAmount(ledgerResponse.getBaseFeeInStroops(), "stroops")); + } + + public void incSequenceNumber() { + sequenceNumber++; + } + + public boolean isError404() { + return error404; + } + + public void setError404(boolean error404) { + this.error404 = error404; + } + + public boolean isAssetBalanceZero() { + if (assetBalance != null && assetBalance.notZero()) + return false; + else + return true; + } + + @Override + public void loadFromBundle(Bundle B) { + super.loadFromBundle(B); + + if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) { + xlmBalance = new CoinEngine.Amount(B.getString("BalanceDecimal"), B.getString("BalanceCurrency")); + } else { + xlmBalance = null; + } + + if (B.containsKey("AssetBalanceCurrency") && B.containsKey("AssetBalanceDecimal")) { + assetBalance = new CoinEngine.Amount(B.getString("AssetBalanceDecimal"), B.getString("AssetBalanceCurrency")); + } else { + assetBalance = null; + } + + if (B.containsKey("sequenceNumber")) { + sequenceNumber = B.getLong("sequenceNumber"); + } else { + sequenceNumber = 0L; + } + + if (B.containsKey("BaseReserveCurrency") && B.containsKey("BaseReserveDecimal")) { + baseReserve = new CoinEngine.Amount(B.getString("BaseReserveDecimal"), B.getString("BaseReserveCurrency")); + } else { + baseReserve = new CoinEngine.Amount("0.5", "XLM"); + } + + if (B.containsKey("BaseFeeCurrency") && B.containsKey("BaseFeeDecimal")) { + baseFee = new CoinEngine.Amount(B.getString("BaseFeeDecimal"), B.getString("BaseFeeCurrency")); + } else { + baseFee = new CoinEngine.Amount("0.00001", "XLM"); + } + + if (B.containsKey("Error404")) error404 = B.getBoolean("Error404"); + else error404 = false; + } + + @Override + public void saveToBundle(Bundle B) { + super.saveToBundle(B); + try { + if (xlmBalance != null) { + B.putString("BalanceCurrency", xlmBalance.getCurrency()); + B.putString("BalanceDecimal", xlmBalance.toValueString()); + } + + if (assetBalance != null) { + B.putString("AssetBalanceCurrency", assetBalance.getCurrency()); + B.putString("AssetBalanceDecimal", assetBalance.toValueString()); + } + + if (sequenceNumber != null) { + B.putLong("sequenceNumber", sequenceNumber); + } + + if (baseReserve != null) { + B.putString("BaseReserveCurrency", baseReserve.getCurrency()); + B.putString("BaseReserveDecimal", baseReserve.toValueString()); + } + + if (baseFee != null) { + B.putString("BaseFeeCurrency", baseFee.getCurrency()); + B.putString("BaseFeeDecimal", baseFee.toValueString()); + } + + if (error404) B.putBoolean("Error404", true); + + } catch (Exception e) { + Log.e("Can't save to bundle ", e.getMessage()); + } + } +} + diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java new file mode 100644 index 0000000000..b3c2b98d83 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java @@ -0,0 +1,610 @@ +package com.tangem.wallet.xlm; + +import android.net.Uri; +import android.os.StrictMode; +import android.text.InputFilter; +import android.util.Log; + +import com.tangem.data.Blockchain; +import com.tangem.data.network.ServerApiStellar; +import com.tangem.data.network.StellarRequest; +import com.tangem.tangem_card.data.TangemCard; +import com.tangem.tangem_card.tasks.SignTask; +import com.tangem.tangem_card.util.Util; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.wallet.BalanceValidator; +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; +import com.tangem.wallet.R; +import com.tangem.wallet.TangemContext; + +import org.stellar.sdk.Asset; +import org.stellar.sdk.AssetTypeNative; +import org.stellar.sdk.ChangeTrustOperation; +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; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * Created by dvol on 7.01.2019. + *

+ * PS. To create and fill testnet account just open https://friendbot.stellar.org/?addr=XXX in browser + **/ + +public class XlmAssetEngine extends CoinEngine { + + private static final String TAG = XlmAssetEngine.class.getSimpleName(); + + public XlmAssetData coinData = null; + + public XlmAssetEngine(TangemContext context) throws Exception { + super(context); + if (context.getCoinData() == null) { + coinData = new XlmAssetData(); + context.setCoinData(coinData); + } else if (context.getCoinData() instanceof XlmAssetData) { + coinData = (XlmAssetData) context.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for XlmAssetEngine"); + } + } + + public XlmAssetEngine() { + super(); + } + + private static int getDecimals() { + return 7; + } + + + private void checkBlockchainDataExists() throws Exception { + if (coinData == null) throw new Exception("No blockchain data"); + } + + @Override + public boolean awaitingConfirmation() { + if (coinData == null) return false; + //TODO + return false;//coinData.getBalanceUnconfirmed() != 0; + } + + @Override + public Amount getBalance() { + if (!hasBalanceInfo()) return null; + if (!coinData.isAssetBalanceZero()) + return coinData.getAssetBalance(); + else + return coinData.getXlmBalance(); + } + + @Override + public String getBalanceHTML() { + Amount balance = coinData.getXlmBalance(); + Amount assetBalance = coinData.getAssetBalance(); + + if (balance != null) { + if (!coinData.isAssetBalanceZero()) { + return " " + assetBalance.toDescriptionString(getDecimals()) + "
"+ balance.toDescriptionString(getDecimals()) + " for fee + " + coinData.getReserve().toDescriptionString(getDecimals()) + " reserve"; + } + return " " + balance.toDescriptionString(getDecimals()) + "
+ " + coinData.getReserve().toDescriptionString(getDecimals()) + " reserve"; + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + if (!coinData.isAssetBalanceZero()) { + return coinData.getAssetBalance().getCurrency(); + } else { + return "XLM"; + } + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getXlmBalance() == null) return false; + return coinData.getXlmBalance().notZero(); + } + + @Override + public boolean hasBalanceInfo() { + if (coinData == null) return false; + return coinData.getXlmBalance() != null; + } + + + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.general_wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.loaded_wallet_message_wait); + } else { + return true; + } + return false; + } + + @Override + public String getFeeCurrency() { + return "XLM"; + } + + @Override + public boolean validateAddress(String address) { + try { + KeyPair kp = KeyPair.fromAccountId(address); + // TODO is it possible to check address testNet or not +// if (ctx.getBlockchain() == Blockchain.StellarTestNet) { +// return false; +// } + } catch (Exception e) { + return false; + } + return true; + } + + + @Override + public boolean isNeedCheckNode() { + return false; + } + + @Override + public Uri getWalletExplorerUri() { + return Uri.parse("http://stellarchain.io/address/" + ctx.getCoinData().getWallet()); + } + + @Override + public Uri getShareWalletUri() { + //TODO - how to construct payment query intent for stellar? +// if (ctx.getCard().getDenomination() != null) { +// return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString()); +// } else { + return Uri.parse(ctx.getCoinData().getWallet()); +// } + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (coinData == null) return false; + + Amount balance; + if (!coinData.isAssetBalanceZero()) { + balance = coinData.getAssetBalance(); + } else { + balance = coinData.getXlmBalance(); + } + + if (amount.compareTo(balance) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { + try { + checkBlockchainDataExists(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if (feeValue == null || amountValue == null) + return false; + + if (feeValue.isZero() || amountValue.isZero()) + return false; + + if (!coinData.isAssetBalanceZero()) { + if (amountValue.compareTo(coinData.getAssetBalance()) > 0 || feeValue.compareTo(coinData.getXlmBalance()) > 0) + return false; + } else { + if (isIncludeFee && (amountValue.compareTo(coinData.getXlmBalance()) > 0 || amountValue.compareTo(feeValue) < 0)) + return false; + if (!isIncludeFee && amountValue.add(feeValue).compareTo(coinData.getXlmBalance()) > 0) + return false; + } + + return true; + } + + @Override + public boolean validateBalance(BalanceValidator balanceValidator) { + try { + if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { + if (coinData.isError404()) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_account); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account_instruction); + } else { + balanceValidator.setScore(0); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); + return false; + } + } + + // Workaround before new back-end +// if (card.getRemainingSignatures() == card.getMaxSignatures()) { +// firstLine = "Verified balance"; +// secondLine = "Balance confirmed in blockchain. "; +// secondLine += "Verified note identity. "; +// return; +// } + +// if (coinData.getBalanceUnconfirmed() != 0) { +// balanceValidator.setScore(0); +// balanceValidator.setFirstLine("Transaction in progress"); +// balanceValidator.setSecondLine("Wait for confirmation in blockchain"); +// return false; +// } + + if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); + if (coinData.getXlmBalance().isZero()) { + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); + } + } + + // rule 4 TODO: need to check SignedHashed against number of outputs in blockchain +// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0) +// { +// score = 80; +// firstLine = "Unguaranteed balance"; +// secondLine = "Potential unsent transaction. Redeem immediately if accept. "; +// return; +// } + + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getXlmBalance().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); + } + +// if(card.getFailedBalanceRequestCounter()!=0) { +// score -= 5 * card.getFailedBalanceRequestCounter(); +// secondLine += "Not all nodes have returned balance. Swipe down or tap again. "; +// if(score <= 0) +// return; +// } + + // +// if(card.isBalanceReceived() && !card.isBalanceEqual()) { +// score = 0; +// firstLine = "Disputed balance"; +// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later."; +// return; +// } + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public String evaluateFeeEquivalent(String fee) { + if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; + try { + Amount feeAmount = new Amount(fee, getFeeCurrency()); + return feeAmount.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + return ""; + } + } + + @Override + public String getBalanceEquivalent() { + if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public String calculateAddress(byte[] pkUncompressed) { + KeyPair kp = KeyPair.fromPublicKey(pkUncompressed); + return kp.getAccountId(); + } + + private static BigDecimal multiplier = new BigDecimal("10000000"); + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + BigDecimal d = internalAmount.divide(multiplier); + return new Amount(d, getBalanceCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) { + BigDecimal d = amount.multiply(multiplier); + return new InternalAmount(d, "stroops"); + } + + @Override + public InternalAmount convertToInternalAmount(byte[] bytes) { + if (bytes == null) return null; + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return new InternalAmount(Util.byteArrayToLong(reversed), "stroops"); + } + + @Override + public byte[] convertToByteArray(InternalAmount internalAmount) { + byte[] bytes = Util.longToByteArray(internalAmount.longValueExact()); + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return reversed; + } + + @Override + public CoinData createCoinData() { + return new XlmAssetData(); + } + + @Override + public String getUnspentInputsDescription() { + return ""; + } + + + @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 (coinData.isAssetBalanceZero() && IncFee) { + amountValue = new Amount(amountValue.subtract(feeValue), amountValue.getCurrency()); + } + + Operation operation; + if (coinData.getAssetBalance() == null) { + operation = new ChangeTrustOperation.Builder(Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), "900000000000.0000000").build(); + } else { + if (!coinData.isAssetBalanceZero()) { + operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), Asset.createNonNativeAsset(ctx.getCard().getTokenSymbol(), KeyPair.fromAccountId(ctx.getCard().getContractAddress())), amountValue.toValueString()).build(); + } else { + 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!"); + } + + return new SignTask.TransactionToSign() { + + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; + } + + @Override + public byte[][] getHashesToSign() throws Exception { + byte[][] dataForSign = new byte[1][]; + dataForSign[0] = transaction.hash(); + return dataForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + return transaction.signatureBase(); + } + + @Override + public String getHashAlgToSign() { + return "sha-256"; + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Issuer validation not supported!"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + // Sign the transaction to prove you are actually the person sending it. + transaction.setSign(signFromCard); + + byte[] txForSend = transaction.toEnvelopeXdrBase64().getBytes(); + notifyOnNeedSendTransaction(txForSend); + return txForSend; + } + }; + } + + + // network call inside, don't use on main thread + private boolean isAccountCreated(String address) { + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); + + 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(ctx.getBlockchain()); + + ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { + @Override + public void onSuccess(StellarRequest.Base request) { + Log.i(TAG, "onSuccess: " + request.getClass().getSimpleName()); + + if (request instanceof StellarRequest.Balance) { + StellarRequest.Balance balanceRequest = (StellarRequest.Balance) request; + + coinData.setAccountResponse(balanceRequest.accountResponse); + coinData.setValidationNodeDescription(serverApi.getCurrentURL()); + + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } else if (request instanceof StellarRequest.Ledgers) { + StellarRequest.Ledgers ledgersRequest = (StellarRequest.Ledgers) request; + + coinData.setLedgerResponse(ledgersRequest.ledgerResponse); + + if (serverApi.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } else { + ctx.setError("Invalid request logic"); + blockchainRequestsCallbacks.onComplete(false); + } + } + + + @Override + public void onFail(StellarRequest.Base request) { + Log.i(TAG, "onFail: " + request.getClass().getSimpleName() + " " + request.getError()); + + if (request.errorResponse.getCode() == 404) { + coinData.setError404(true); + } else { + ctx.setError(request.getError()); + } + + if (serverApi.isRequestsSequenceCompleted()) { + if (ctx.hasError()) { + blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onComplete(true); + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApi.setListener(listener); + + serverApi.requestData(ctx, new StellarRequest.Balance(coinData.getWallet())); + serverApi.requestData(ctx, new StellarRequest.Ledgers()); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { + // TODO: get fee stats? + coinData.minFee = coinData.normalFee = coinData.maxFee = coinData.getBaseFee(); + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException { + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); + + ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { + @Override + public void onSuccess(StellarRequest.Base request) { + try { + if (!StellarRequest.SubmitTransaction.class.isInstance(request)) + throw new Exception("Invalid request logic"); + StellarRequest.SubmitTransaction submitTransactionRequest = (StellarRequest.SubmitTransaction) request; + if (submitTransactionRequest.response.isSuccess()) { + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } else { + if (submitTransactionRequest.response.getExtras() != null && submitTransactionRequest.response.getExtras().getResultCodes() != null) { + String trResult = submitTransactionRequest.response.getExtras().getResultCodes().getTransactionResultCode(); + if (submitTransactionRequest.response.getExtras().getResultCodes().getOperationsResultCodes() != null && submitTransactionRequest.response.getExtras().getResultCodes().getOperationsResultCodes().size() > 0) { + trResult += "/" + submitTransactionRequest.response.getExtras().getResultCodes().getOperationsResultCodes().get(0); + } + ctx.setError(trResult); + } else { + ctx.setError("transaction failed"); + } + blockchainRequestsCallbacks.onComplete(false); + } + } 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(StellarRequest.Base request) { + ctx.setError(request.getError()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApi.setListener(listener); + + Transaction transaction = TransactionEx.fromEnvelopeXdr(new String(txForSend)); + coinData.incSequenceNumber(); + serverApi.requestData(ctx, new StellarRequest.SubmitTransaction(transaction)); + + } + + public boolean needMultipleLinesForBalance() { + return true; + } + + public boolean allowSelectFeeLevel() { + return false; + } + + public boolean allowSelectFeeInclusion() { + return coinData.isAssetBalanceZero(); + } + + public int pendingTransactionTimeoutInSeconds() { + return 10; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmData.java b/app/src/main/java/com/tangem/wallet/xlm/XlmData.java index b956759278..9eab0764f8 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmData.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmData.java @@ -30,13 +30,14 @@ public class XlmData extends CoinData { private Long sequenceNumber = 0L; private CoinEngine.Amount baseReserve = new CoinEngine.Amount("0.5", "XLM"); private CoinEngine.Amount baseFee = new CoinEngine.Amount("0.00001", "XLM"); - private boolean error404 = false; + private boolean error404, targetAccountCreated = false; @Override public void clearInfo() { super.clearInfo(); balance = null; error404 = false; + targetAccountCreated = false; } CoinEngine.Amount getBalance() { @@ -86,6 +87,14 @@ public class XlmData extends CoinData { this.error404 = error404; } + public boolean isTargetAccountCreated() { + return targetAccountCreated; + } + + public void setTargetAccountCreated(boolean targetAccountCreated) { + this.targetAccountCreated = targetAccountCreated; + } + @Override public void loadFromBundle(Bundle B) { super.loadFromBundle(B); @@ -116,6 +125,9 @@ public class XlmData extends CoinData { if (B.containsKey("Error404")) error404 = B.getBoolean("Error404"); else error404 = false; + + if (B.containsKey("TargetAccountCreated")) targetAccountCreated = B.getBoolean("TargetAccountCreated"); + else targetAccountCreated = false; } @Override @@ -143,6 +155,8 @@ public class XlmData extends CoinData { if (error404) B.putBoolean("Error404", true); + if (targetAccountCreated) B.putBoolean("TargetAccountCreated", true); + } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } 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 e8ad0ce1d2..717d3d85bf 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java @@ -200,12 +200,12 @@ public class XlmEngine extends CoinEngine { if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { if (coinData.isError404()) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("No account or network error"); - balanceValidator.setSecondLine("To create account send 1+ XLM to this address"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_no_account); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account_instruction); } else { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } } @@ -227,11 +227,11 @@ public class XlmEngine extends CoinEngine { if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalance().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } @@ -246,8 +246,8 @@ public class XlmEngine extends CoinEngine { if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalance().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } // if(card.getFailedBalanceRequestCounter()!=0) { @@ -361,7 +361,7 @@ public class XlmEngine extends CoinEngine { } Operation operation; - if (isAccountCreated(targetAddress)) + if (coinData.isTargetAccountCreated()) operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build(); else operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build(); @@ -414,29 +414,46 @@ public class XlmEngine extends CoinEngine { }; } + private void checkTargetAccountCreated(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); - // network call inside, don't use on main thread - private boolean isAccountCreated(String address) { - final ServerApiStellar serverApi = new ServerApiStellar(); + ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { + @Override + public void onSuccess(StellarRequest.Base request) { + coinData.setTargetAccountCreated(true); + blockchainRequestsCallbacks.onComplete(true); + } - 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 - } + @Override + public void onFail(StellarRequest.Base request) { + Log.i(TAG, "onFail: " + request.getClass().getSimpleName() + " " + request.getError()); + + if (request.errorResponse.getCode() == 404) { + coinData.setTargetAccountCreated(false); + + if (amount.compareTo(coinData.getReserve()) >= 0) { //TODO: take fee inclusion in account, now 1 XLM with fee included will fail after transaction is sent + blockchainRequestsCallbacks.onComplete(true); + } else { + ctx.setError(R.string.confirm_transaction_error_not_enough_xlm_for_create); + blockchainRequestsCallbacks.onComplete(false); + } + } else { // suppose account is created if anything goes wrong + coinData.setTargetAccountCreated(true); + blockchainRequestsCallbacks.onComplete(true); + } + } + }; + + serverApi.setListener(listener); + + serverApi.requestData(ctx, new StellarRequest.Balance(targetAddress)); - if (request.errorResponse != null && request.errorResponse.getCode() == 404) - return false; - else - return true; } @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - final ServerApiStellar serverApi = new ServerApiStellar(); + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { @Override @@ -447,6 +464,7 @@ public class XlmEngine extends CoinEngine { StellarRequest.Balance balanceRequest = (StellarRequest.Balance) request; coinData.setAccountResponse(balanceRequest.accountResponse); + coinData.setValidationNodeDescription(serverApi.getCurrentURL()); if (serverApi.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); @@ -500,14 +518,13 @@ public class XlmEngine extends CoinEngine { @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { - // TODO: get fee stats? coinData.minFee = coinData.normalFee = coinData.maxFee = coinData.getBaseFee(); - blockchainRequestsCallbacks.onComplete(true); + checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount); //TODO: move? } @Override public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException { - final ServerApiStellar serverApi = new ServerApiStellar(); + final ServerApiStellar serverApi = new ServerApiStellar(ctx.getBlockchain()); ServerApiStellar.Listener listener = new ServerApiStellar.Listener() { @Override 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 99a2474c86..3e6e0d1802 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -198,39 +198,39 @@ public class XrpEngine extends CoinEngine { try { if (coinData.isAccountNotFound()) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Account not found"); - balanceValidator.setSecondLine("Load 20+ XRP to create account"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_account_not_found); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account_xrp); return false; } if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unknown balance"); - balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance); return false; } if (coinData.hasUnconfirmed()) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Transaction in progress"); - balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation); return false; } if (coinData.isBalanceReceived()) { balanceValidator.setScore(100); - balanceValidator.setFirstLine("Verified balance"); - balanceValidator.setSecondLine("Balance confirmed in blockchain"); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain); if (coinData.getBalanceInInternalUnits().isZero()) { - balanceValidator.setFirstLine("Empty wallet"); - balanceValidator.setSecondLine(""); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet); + balanceValidator.setSecondLine(R.string.empty_string); } } if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { balanceValidator.setScore(80); - balanceValidator.setFirstLine("Verified offline balance"); - balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline); + balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance); } return true; @@ -497,7 +497,7 @@ public class XrpEngine extends CoinEngine { @Override public void onFail(String method, String message) { - Log.i(TAG, "onFail: " + method + " " + message); + Log.e(TAG, "onFail: " + method + " " + message); ctx.setError(message); if (serverApiRipple.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(false); diff --git a/app/src/main/res/layout/fr_verify_card.xml b/app/src/main/res/layout/fr_verify_card.xml index e139453c5e..9513a736bd 100644 --- a/app/src/main/res/layout/fr_verify_card.xml +++ b/app/src/main/res/layout/fr_verify_card.xml @@ -189,6 +189,35 @@ + + + + + + + + + + app:destination="@id/qrScanFragment" + app:enterAnim="@anim/nav_default_enter_anim" /> + app:destination="@id/qrScanFragment" + app:enterAnim="@anim/nav_default_enter_anim"/> + + + Annuler + Oui + Non + Erreur! + Pas de connexion avec les nœuds de la chaîne de blocage + Continuer + Le code PIN est vide + Blockchain + Réessayez de numériser + Impossible d\'effacer le porte-monnaie avec un solde différent de zéro + Envoyer le paiement + Depuis la carte + sur la carte + avec solde + Envoyer au porte-monnaie + Montant + BTC + Tangem AG + Scanner à nouveau pour vérifier la carte + READING… + Le porte-monnaie est vide + L\'utilisateur n\'a pas obtenu l\'autorisation d\'utiliser la caméra + + + Paramètres + Commun + Frais de modification manuelle + Modes de cryptage + + Aucun + Rapide + Fort + + + 0 + 1 + 2 + + Sélectionner des nœuds + + + Votre appareil Android est rooté. La sécurité en danger! + Je l\'ai + Avertissement + Délai de sécurité + Oups .. Il semble que votre smartphone ne supporte pas de tels paquets NFC. + Essayez d\'envoyer une quantité inférieure ou utilisez un smartphone avec prise en charge complète de NFC. + Votre argent est en jeu! + Cette carte a imposé un délai de sécurité + Veuillez tenir la carte fermement \ n jusqu\'à ce que l\'opération soit terminéev… + Vous devrez peut-être répéter cette opération plusieurs fois en fonction des performances NFC de votre smartphone. \ n Ceci est fait pour la sécurité de vos fonds. + Cette carte n\'est pas supportée + + + Gérer le code PIN de l\'utilisateur… + Gérer le code PIN de l\'utilisateur… + Envoyer les journaux… + À propos de… + + + Définir PIN1 + Définir le code PIN2 + >Réinitialiser PIN1 par défaut + Réinitialiser le code PIN2 par défaut + Réinitialiser les codes confidentiels par défaut + Effacer le porte-monnaie + + + ALPHA v.%1$s \n dev \n génération %2$s + v.%1$s + Cardano + + + Appuyez sur la carte Tangem avec votre smartphone + Scanner une carte avec votre \n %1$s \n comme indiqué ci-dessus + phone + Le porte-monnaie a été effacé + Non personnalisé + Il existe une nouvelle version de l\'application: %1$s + Mettre à jour + + + Aucun porte-monnaie compatible installé + Veuillez patienter pendant que la transaction précédente est confirmée dans la blockchain + Impossible d\'obtenir toutes les entrées. Balayez vers le bas pour actualiser. + La carte n\'a plus de signature! + Si vous oubliez votre nouveau code PIN, vous perdrez votre argent pour toujours! + Si vous utilisez un code PIN par défaut, une personne peut voler votre argent! + >Impossible d\'obtenir des données de la blockchain + Impossible d\'obtenir des données de la blockchain (connexion refusée) + Impossible d\'obtenir les données de la blockchain (réponse vide reçue) + Impossible d\'obtenir les données de la blockchain (erreur de communication) + Partager l\'adresse du porte-monnaie avec: + copié dans le presse-papiers + Retirer + Charger + Explorer + Copier + Détails + Scanner + Vérification dans la blockchain… + Avertissement: cette carte a déjà été complétée et des transactions signées dans le passé.Envisagez le retrait immédiat de tous les fonds si vous avez reçu cette carte d’une source non fiable. + VERIFYING… + via CRYPTONIT + via CRYPTONIT2 + via KRAKEN + Une autre application de porte-monnaie + Adresse de partage + Afficher le code QR + LOAD + + + Solde vérifié + Solde inconnu + Transaction en cours + Solde hors ligne vérifié + Portefeuille vide + La vérification a échoué + Transaction en attente… + Billet non authentique + Verrouillé avec code PIN2 + Identité de note vérifiée. + Aucun compte ni erreur réseau + Compte introuvable + Pas de connexion + Vérifié dans la blockchain + L\'authenticité n\'a pas été vérifiée + + Solde confirmé dans la blockchain + Solde ne peut pas être vérifié. Balayez vers le bas pour actualiser.. + Attendez la confirmation dans la chaîne de blocs + Restaurez la connexion Internet pour obtenir un solde sécurisé de la chaîne de blocs + Impossible d\'obtenir le + solde de la blockchain. Restaurez la connexion Internet pour être plus confiant. + Glissez vers le bas pour actualiser + La vérification du portefeuille a échoué. Touchez à nouveau. + Le service d\'attestation Tangem indique que le billet n\'est pas authentique. + La vérification du code binaire du micrologiciel a échoué + Demandez au titulaire de désactiver le code PIN2 avant d\'accepter + \'identité de la carte n\'a pas été vérifiée. Impossible d\'atteindre le service d\'attestation Tangem. + Pour créer un compte, envoyez des fonds à cette adresse + Pour créer un compte, envoyez 1+ XLM à cette adresse + Chargez plus de 20 XRP pour créer un compte + L\'authenticité ne peut pas être vérifiée. Balayez vers le bas pour actualiser. +      + + Maintenant, touchez la carte avec l\'ID + pour créer le porte-monnaie + pour effacer le porte-monnaie + pour signer le paiement + pour changer les codes PIN / PIN2 + En tapotant sur la carte, vous retirerez définitivement le porte-monnaie de la blockchain. Assurez-vous qu\'il n\'y aura pas d\autres transactions entrantes + Impossible d\'effacer le porte-monnaie. Veillez à entrer le code PIN2 correct! + Impossible de créer un porte-monnaie. Veillez à entrer le code PIN2 correct! + + + Entrez un nouveau code PIN ou utilisez un lecteur d\'empreintes digitales + Entrez le nouveau code PIN + Entrez un code PIN ou utilisez un lecteur d\'empreintes digitales + Entrez le code PIN2 ou utilisez un lecteur d\'empreintes digitales + Confirmer le nouveau code confidentiel + Confirmer le nouveau code PIN2 + Entrez le code confidentiel + Entrez le code PIN2 + Entrez un nouveau code PIN2 ou utilisez un lecteur d\'empreintes digitales + Entrez le nouveau code PIN2 + Veuillez saisir le code PIN pour confirmation! + + + Veuillez patienter pendant l\'envoi du paiement… + La transaction a été signée et envoyée avec succès au noeud de la blockchain. La balance de porte-monnaie sera mise à jour dans un moment + Réessayez. Échec d\'envoi de la transaction (%s) + Impossible de signer la transaction. Veillez à entrer le code PIN2 correct! + Le montant et les frais sont supérieurs au solde total du porte-monnaie! + + + entrez l\'adresse du porte-monnaie + entrer le montant + Vérifiez + Pas assez de fonds + Format de montant inconnu + Adresse du porte-monnaie de destination incorrecte + L\'adresse du porte-monnaie de destination est identique à l\'adresse source + Le montant est vide + + + adresse du porte-monnaie cible + montant des frais + Frais + Envoyer + Minimal + Normal + Priorité + Frais inclus + Frais non compris + (frais inclus) + (sans frais) + Les données obtenues sont obsolètes! Réessayez + Impossible d\'atteindre le nœud de la blockchain active en cours. Réessayez + Impossible de vérifier le solde! Pas de connexion avec les noeuds de la blockchain + Impossible de calculer les frais! Mauvaises données reçues du noeud + Veuillez attendre la confirmation de la transaction entrante + Pas assez de fonds ETH contre des taxes + Pas assez de fonds RBTC pour les frais + Un code PIN2 est requis pour signer le paiement + Service indisponible + Vous courez un risque de retarder la transaction + + + Le porte-monnaie n\'a pas encore été créé + Créer un porte-monnaie + + + ID de la carte + Méthode de signature + Cette carte est + Transactions signées + Signatures restantes + Heure de la dernière signature + Fonctionnalités + Fabricant + Porte-monnaie + Identité de la carte + Attested + Non confirmé + Réutilisable + None + Le dernier! + Unlimited + Carte ponctuelle + Micrologiciel + Date d\'enregistrement + Possession prouvée + Possession NON prouvée + non disponible + Émetteur + Clé privée + Noeud de validation + Unspents + Cette carte est protégé par le code PIN1 par défaut + Cette carte est protégé par le code PIN1 de l’utilisateur + Cette carte est protégé par le code PIN2 par défaut + Cette carte est protégé par le code PIN2 de l’utilisateur + Carte débloqué, uniquement à des fins de développement + Cette carte appliquera un délai de sécurité de %.0f secondes pour toutes lesopérations nécessitant un code PIN2 + Permet de changer les codes PIN1 et PIN2\n + Permet de changer PIN1\n + Permet de changer le code PIN2\n + PIN1 et PIN2 sont fixes\n + Requiert CVC\n + NDEF dynamique pour iOS\n + NDEF\n + Bloquable\n + Mode de commande atomique\n + La liaison au terminal est prise en charge\n + La carte est liée + à ce téléphone + + + Enregistrer + Supprimer + Protéger avec empreinte digitale + Appuyez sur le lecteur d\'empreintes digitales pour confirmer + L\'utilisateur n\'a pas activé l\'écran verrouillé + L\'utilisateur n\'a pas obtenu l\'autorisation d\'utiliser une empreinte digitale + L\'utilisateur n\'a enregistré aucune empreinte digitale + Échec de l\'enregistrement du code confidentiel + Entrez le code PIN et enregistrez-le + Entrez le code PIN2 et enregistrez-le + + + Paiement CRYPTONIT + À partir du compte CRYPTONIT: + ID utilisateur: + clé: + secret: + nonce: + nom d\'utilisateur: + mot_de_passe: + Veuillez saisir les données du compte + Obtenir le solde + Retrait + frais d\'opération + Retrait réussi! + + + Paiement KRAKEN + À partir du compte KRAKEN: + clé: + secret: + "Veuillez saisir les données du compte + Obtenir le solde + Retrait + Opération annulée! + Veuillez confirmer retirer + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 53129d77a4..53a6ee35ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,11 +16,13 @@ with balance Send to wallet Amount - BTC + BTC Tangem AG Scan again to verify the card READING… The wallet is empty + User hasn\'t granted permission to use camera + Settings @@ -69,7 +71,7 @@ ALPHA v.%1$s \n dev \n build %2$s v.%1$s Cardano - + This card is not supported Tap Tangem card with your smartphone @@ -111,6 +113,39 @@ Show QR-code LOAD + + Verified balance + Unknown balance + Transaction in progress + Verified offline balance + Empty wallet + Verification failed + Pending transaction… + Not genuine banknote + Locked with PIN2 + Verified note identity. + No account or network error + Account not found + No connection + Verified in blockchain + Authenticity was not verified + + Balance confirmed in blockchain + Balance cannot be verified. Swipe down to refresh. + Wait for confirmation in blockchain + Restore internet connection to obtain trusted balance from blockchain + Can\'t obtain balance from blockchain. Restore internet connection to be more confident. + Swipe down to refresh + Wallet verification failed. Tap again. + Tangem Attestation service says the banknote is not genuine. + Firmware binary code verification failed + Ask the holder to disable PIN2 before accepting + Card identity was not verified. Cannot reach Tangem attestation service. + To create account send funds to this address + To create account send 1+ XLM to this address + Load 20+ XRP to create account + Authenticity cannot be verified. Swipe down to refresh. + Now touch the banknote with ID to create the wallet @@ -134,7 +169,6 @@ Enter new PIN2 Please enter the PIN for confirmation! - Please wait while the payment is sent… Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while @@ -171,10 +205,12 @@ Please wait for confirmation of incoming transaction Not enough ETH funds for fee Not enough RBTC funds for fee + Target account is not created! Send 1+ XLM to create it PIN2 is required to sign the payment Service unavailable You have a risk of delaying transaction + Wallet hasn\'t been yet created Create Wallet @@ -212,6 +248,18 @@ This banknote is protected by user\'s PIN2 code Unlocked banknote, only for development use This banknote will enforce %.0f seconds security delay for all operations requiring PIN2 code + Allows to change PIN1 and PIN2\n + Allows to change PIN1\n + Allows to change PIN2\n + Fixed PIN1 and PIN2\n + Requires CVC\n + Dynamic NDEF for iOS\n + NDEF\n + Blockable\n + Atomic command mode\n + Linking to the terminal is supported\n + The card is linked + To this phone Save diff --git a/app/src/main/res/values/strings_untranslatable.xml b/app/src/main/res/values/strings_untranslatable.xml index 7783a7ef97..0b6a2bcf98 100644 --- a/app/src/main/res/values/strings_untranslatable.xml +++ b/app/src/main/res/values/strings_untranslatable.xml @@ -1,31 +1,32 @@ - <--  - -->  - **  - !!  - --  - -- -- -- - ---- ---- ---- ---- - < - -- - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 0 + <--  + -->  + **  + !!  + --  + -- -- -- + ---- ---- ---- ---- + < + -- + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 - com.tangem.data.LogFileProvider - Can\'t create CoinEngine! + com.tangem.data.LogFileProvider + Can\'t create CoinEngine! - category_common - manual_editing_fee - select_nodes - encryption_modes + category_common + manual_editing_fee + select_nodes + encryption_modes diff --git a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt index 295b10b8b9..e09a207c57 100644 --- a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt +++ b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt @@ -2,6 +2,7 @@ package com.tangem.ui import android.app.Activity import android.content.Context +import android.net.Uri import android.nfc.NfcAdapter import android.nfc.Tag import android.os.Build @@ -12,9 +13,9 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast import com.tangem.Constant -import com.tangem.data.Blockchain import com.tangem.ui.activity.MainActivity import com.tangem.ui.fragment.BaseFragment +import com.tangem.ui.fragment.qr.CameraPermissionManager import com.tangem.ui.navigation.NavigationResultListener import com.tangem.util.UtilHelper import com.tangem.wallet.CoinEngineFactory @@ -22,6 +23,7 @@ import com.tangem.wallet.R import com.tangem.wallet.TangemContext import kotlinx.android.synthetic.tangemAccess.fragment_prepare_transaction.* import java.io.IOException +import java.util.* class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback { companion object { @@ -31,6 +33,7 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc override val layoutId = R.layout.fragment_prepare_transaction private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) } + private val cameraPermissionManager: CameraPermissionManager by lazy { CameraPermissionManager(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -119,45 +122,43 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc } ivCamera.setOnClickListener { + if (cameraPermissionManager.isPermissionGranted()) { + navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment) + } else { + cameraPermissionManager.requirePermission() + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + cameraPermissionManager.handleRequestPermissionResult(requestCode, grantResults) { navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment) } } override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) { if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) { - var code = data.getString("QRCode") - when (ctx.blockchain) { - Blockchain.Bitcoin -> { - if (code.contains("bitcoin:")) { - val tmp = code.split("bitcoin:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - code = tmp[1] - } - } - Blockchain.Ethereum, Blockchain.Token -> { - if (code.contains("ethereum:")) { - val tmp = code.split("ethereum:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - code = tmp[1] - } else if (code.contains("blockchain:")) { //TODO: is this needed? - val tmp = code.split("blockchain:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - code = tmp[1] - } - } - Blockchain.Litecoin -> { - if (code.contains("litecoin:")) { - val tmp = code.split("litecoin:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - code = tmp[1] - } - } - Blockchain.Ripple -> { - if (code.contains("ripple:")) { - val tmp = code.split("ripple:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - code = tmp[1] + val code = data.getString("QRCode") + val schemeSplit = code!!.split(":") + when (schemeSplit.size) { + 2 -> { + if (ctx.blockchain.officialName.toLowerCase(Locale.ROOT).replace("\\s","") == schemeSplit[0]) { + val uri = Uri.parse(schemeSplit[1]) + etWallet?.setText(uri.path) +// val amount = uri.getQueryParameter("amount") //TODO: enable after redesign +// if (amount != null) { +// etAmount?.setText(amount) +// rgIncFee.check(R.id.rbFeeOut) +// } + } else { + etWallet?.setText(code) } } else -> { + etWallet?.setText(code) } } - etWallet?.setText(code) } else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) { navigateBackWithResult(resultCode, data) } @@ -170,5 +171,4 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc e.printStackTrace() } } - } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5c3241212c..dca2b516ad 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,15 @@ buildscript { - ext.kotlin_version = '1.3.41' + ext.kotlin_version = '1.3.50' repositories { google() jcenter() maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' - classpath 'io.fabric.tools:gradle:1.27.1' + classpath 'io.fabric.tools:gradle:1.31.0' } } diff --git a/server-android/build.gradle b/server-android/build.gradle index c194ede904..37632174bb 100644 --- a/server-android/build.gradle +++ b/server-android/build.gradle @@ -33,14 +33,14 @@ dependencies { implementation project(':tangem-card') // implementation 'com.github.TangemCash.card_android-common:card_android-android:0.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0-alpha04' + implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.squareup.retrofit2:converter-gson:2.5.0' - implementation 'com.squareup.retrofit2:retrofit:2.5.0' + implementation 'com.squareup.retrofit2:converter-gson:2.6.0' + implementation 'com.squareup.retrofit2:retrofit:2.6.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } repositories { diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/data/TangemCard.java b/tangem-card/src/main/java/com/tangem/tangem_card/data/TangemCard.java index 365848b417..4fd4cc1b0f 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/data/TangemCard.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/data/TangemCard.java @@ -26,6 +26,9 @@ public class TangemCard { private byte[] pbCardKey = null; private byte[] pbWalletKeyRar = null; private Date dtPersonalization = null; + private byte[] terminalPrivateKey; + private byte[] terminalPublicKey; + private boolean terminalIsLinked = false; public String getBlockchainID() { return blockchainID; @@ -156,6 +159,30 @@ public class TangemCard { this.dtPersonalization = dtPersonalization; } + public byte[] getTerminalPrivateKey() { + return terminalPrivateKey; + } + + public void setTerminalPrivateKey(byte[] terminalPrivateKey) { + this.terminalPrivateKey = terminalPrivateKey; + } + + public byte[] getTerminalPublicKey() { + return terminalPublicKey; + } + + public void setTerminalPublicKey(byte[] terminalPublicKey) { + this.terminalPublicKey = terminalPublicKey; + } + + public boolean getTerminalIsLinked() { + return terminalIsLinked; + } + + public void setTerminalIsLinked(boolean terminalIsLinked) { + this.terminalIsLinked = terminalIsLinked; + } + private int health = 0; public int getHealth() { @@ -350,6 +377,11 @@ public class TangemCard { return (settingsMask & SettingsMask.UseBlock) != 0; } + public Boolean supportLinkingTerminal() { + if (settingsMask == null) return null; + return (settingsMask & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0; + } + public int getMaxSignatures() { return maxSignatures; } diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/data/external/PINsProvider.java b/tangem-card/src/main/java/com/tangem/tangem_card/data/external/PINsProvider.java index 6652623017..49dbd1323e 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/data/external/PINsProvider.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/data/external/PINsProvider.java @@ -27,4 +27,7 @@ public interface PINsProvider { */ void setLastUsedPIN(String pin); + byte[] getTerminalPublicKey(); + byte[] getTerminalPrivateKey(); + } diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardCrypto.java b/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardCrypto.java index 3a01df636c..091a224401 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardCrypto.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardCrypto.java @@ -34,6 +34,8 @@ import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -150,7 +152,8 @@ public class CardCrypto { int rLength = enc[3]; if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3"); - if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3"); + if ((enc[5 + rLength] & 0x80) != 0) + throw new Exception("unsupported length encoding 3"); int sLength = enc[5 + rLength]; @@ -286,4 +289,15 @@ public class CardCrypto { return decryptedData; } } + + public static Map generateTerminalKeys() throws Exception { + byte[] privateKey = Util.generateRandomBytes(32); + byte[] publicKey = GeneratePublicKey(privateKey); + + Map keys = new HashMap<>(); + keys.put("terminalPrivateKey", privateKey); + keys.put("terminalPublicKey", publicKey); + return keys; + } + } diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java b/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java index f8d7641d56..94e1a7b799 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java @@ -120,6 +120,11 @@ public class CardProtocol { return (DefaultPIN2.equals(pin2)); } + private byte[] terminalPublicKey; + + public void setTerminalPublicKey(byte[] terminalPubKey) { + this.terminalPublicKey = terminalPubKey; + } private TangemCard mCard; private Exception mError; @@ -488,12 +493,22 @@ public class CardProtocol { CommandApdu Apdu = new CommandApdu(ins); byte[] baPIN = Util.calculateSHA256(mPIN); Apdu.addTLV(TLV.Tag.TAG_PIN, baPIN); - if (ins != INS.Read) { + if (ins == INS.Read) { + addTerminalPublicKeyToApdu(Apdu); + } else { Apdu.addTLV(TLV.Tag.TAG_CardID, mCard.getCID()); } return Apdu; } + private void addTerminalPublicKeyToApdu(CommandApdu apdu) { + if (terminalPublicKey != null) { + apdu.addTLV(TLV.Tag.TAG_Terminal_PublicKey, terminalPublicKey); + } else if (mCard.getTerminalPublicKey() != null) { + apdu.addTLV(TLV.Tag.TAG_Terminal_PublicKey, mCard.getTerminalPublicKey()); + } + } + // /** // * Run READ command and parse answer // * {@see run_Read(boolean parseResult) } @@ -633,7 +648,8 @@ public class CardProtocol { * @throws Exception - if something went wrong */ public void run_CreateWallet(String PIN2) throws Exception { - if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!"); + if (readResult == null) + throw new TangemException("Before run_VerifyCard execute run_Read card first!"); CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet); rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); @@ -835,7 +851,8 @@ public class CardProtocol { CommandApdu rqApdu = StartPrepareCommand(INS.Sign); rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); rqApdu.addTLV_U8(TLV.Tag.TAG_TrOut_HashSize, hashes[0].length); - rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, bs.toByteArray()); + byte[] hashesConcatenated = bs.toByteArray(); + rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, hashesConcatenated); if (issuerData != null) { if (!mCard.allowedSigningMethod.contains(TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer_And_WriteIssuerData)) throw new TangemException("Card don't support simultaneous sign with write issuer data!"); @@ -853,6 +870,8 @@ public class CardProtocol { throw new TangemException("Card require issuer validation before sign the transaction!"); } + prepareDataForLinkingTerminal(rqApdu, hashesConcatenated); + Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, false); @@ -915,6 +934,8 @@ public class CardProtocol { throw new TangemException("Card require issuer validation before sign the transaction!"); } + prepareDataForLinkingTerminal(rqApdu, bTxOutData); + ResponseApdu rspApdu = SendAndReceive(rqApdu, false); if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { @@ -935,6 +956,12 @@ public class CardProtocol { } } + private void prepareDataForLinkingTerminal(CommandApdu apdu, byte[] data) throws Exception { + byte[] transactionSignature = CardCrypto.Signature(mCard.getTerminalPrivateKey(), data); + apdu.addTLV(TLV.Tag.TAG_Terminal_TransactionSignature, transactionSignature); + addTerminalPublicKeyToApdu(apdu); + } + /** * VERIFY_CODE command * See [1] 8.8 @@ -952,7 +979,8 @@ public class CardProtocol { * @throws Exception - if something went wrong */ public byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception { - if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!"); + if (readResult == null) + throw new TangemException("Before run_VerifyCard execute run_Read card first!"); CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode); rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII")); rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress); @@ -988,7 +1016,8 @@ public class CardProtocol { * @throws Exception - if something went wrong */ private void run_ValidateCard(String PIN2) throws Exception { - if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!"); + if (readResult == null) + throw new TangemException("Before run_VerifyCard execute run_Read card first!"); CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard); rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); @@ -1022,7 +1051,8 @@ public class CardProtocol { * @throws Exception - if something went wrong */ public void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception { - if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!"); + if (readResult == null) + throw new TangemException("Before run_VerifyCard execute run_Read card first!"); CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/reader/CommandApdu.java b/tangem-card/src/main/java/com/tangem/tangem_card/reader/CommandApdu.java index 104138a6a9..0d6f910cd9 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/reader/CommandApdu.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/reader/CommandApdu.java @@ -159,8 +159,13 @@ public class CommandApdu { public static String toString(byte[] cmdApdu, int Lc) { String cmd = Util.bytesToHex(cmdApdu); if (Lc == 0) return cmd; - return cmd.substring(0, 8) + " " + cmd.substring(8, 10) + " " + - cmd.substring(10, 10 + Lc * 2) + " " + cmd.substring(10 + Lc * 2, cmd.length()); + if (cmd.substring(8, 10).equals("00")) { + return cmd.substring(0, 8) + " " + cmd.substring(8, 14) + " " + + cmd.substring(14, 14 + Lc * 2) + " " + cmd.substring(14 + Lc * 2, cmd.length()); + } else { + return cmd.substring(0, 8) + " " + cmd.substring(8, 10) + " " + + cmd.substring(10, 10 + Lc * 2) + " " + cmd.substring(10 + Lc * 2, cmd.length()); + } } @@ -196,6 +201,10 @@ public class CommandApdu { } public byte[] toBytes() { + return toBytes(true); + } + + public byte[] toBytes(boolean forceExtendedLength) { int length = 4; // CLA, INS, P1, P2 if (tlvList.size() != 0) { @@ -205,14 +214,14 @@ public class CommandApdu { if (mData.length != 0) { length += 1; // LC - if (mLc >= 256) - length += 2; + if (forceExtendedLength || mLc >= 256) + length += 2; length += mData.length; // DATA } if (mLeUsed) { length += 1; // LE - if (mLc >= 256) - length += 2; + if (forceExtendedLength || mLc >= 256) + length += 2; } byte[] apdu = new byte[length]; @@ -227,31 +236,31 @@ public class CommandApdu { apdu[index] = (byte) mP2; index++; if (mLc != 0) { - if (mLc < 256) { - apdu[index] = (byte) mLc; - index++; - } else { + if (forceExtendedLength || mLc >= 256) { apdu[index] = 0; index++; apdu[index] = (byte) (mLc >> 8); index++; apdu[index] = (byte) (mLc & 0xFF); index++; + } else { + apdu[index] = (byte) mLc; + index++; } System.arraycopy(mData, 0, apdu, index, mData.length); index += mData.length; } if (mLeUsed) { - if (mLc < 256) { - apdu[index] += (byte) mLe; // LE - } else { + if (forceExtendedLength || mLc >= 256) { apdu[index] = 0; index++; apdu[index] = (byte) (mLe >> 8); index++; apdu[index] = (byte) (mLe & 0xFF); index++; + } else { + apdu[index] += (byte) mLe; // LE } } diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java b/tangem-card/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java index 2b48a32d36..81470d9586 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java @@ -29,6 +29,8 @@ public class SettingsMask { public static final int DisablePrecomputedNDEF = 0x00010000; + public static final int SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000; + public static String getDescription(int iValue) { StringBuilder sb=new StringBuilder(); sb.append("["); @@ -58,6 +60,8 @@ public class SettingsMask { if ((iValue & SettingsMask.ForbidPurgeWallet) != 0) sb.append("ForbidPurgeWallet, "); if ((iValue & SettingsMask.AllowSelectBlockchain) != 0) sb.append("AllowSelectBlockchain, "); if ((iValue & SettingsMask.DisablePrecomputedNDEF) != 0) sb.append("DisablePrecomputedNDEF, "); + if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0) + sb.append("SkipSecurityDelayIfValidatedByLinkedTerminal, "); if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length()); sb.append("]"); diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/reader/TLV.java b/tangem-card/src/main/java/com/tangem/tangem_card/reader/TLV.java index ef071c1f7b..4188e0ca54 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/reader/TLV.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/reader/TLV.java @@ -85,8 +85,11 @@ public class TLV { TAG_Denomination(0xC0), TAG_ValidatedBalance(0xC1), TAG_LastSign_Date(0xC2), - TAG_DenominationText(0xC3); + TAG_DenominationText(0xC3), + TAG_Terminal_IsLinked(0x58), + TAG_Terminal_PublicKey(0x5C), + TAG_Terminal_TransactionSignature(0x57); Tag(int Code) { this.Code = Code; diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java b/tangem-card/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java index 94c5e3fcfe..453ecd2b28 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java @@ -235,6 +235,9 @@ public class CustomReadCardTask extends Thread { Log.e(TAG, "Can't get max signatures"); } + TLV terminalIsLinked = protocol.getReadResult().getTLV(TLV.Tag.TAG_Terminal_IsLinked); + mCard.setTerminalIsLinked(terminalIsLinked != null); + } catch (Exception e) { e.printStackTrace(); throw new CardProtocol.TangemException("Can't parse card data"); @@ -299,6 +302,9 @@ public class CustomReadCardTask extends Thread { if (isCancelled) return; protocol.setPIN(CardProtocol.DefaultPIN); + if (pinsProvider != null) { + protocol.setTerminalPublicKey(pinsProvider.getTerminalPublicKey()); + } protocol.clearReadResult(); if (lastRead_Encryption == null) { diff --git a/tangem-card/src/main/java/com/tangem/tangem_card/tasks/SignTask.java b/tangem-card/src/main/java/com/tangem/tangem_card/tasks/SignTask.java index 4aeb2a2659..92140edb11 100644 --- a/tangem-card/src/main/java/com/tangem/tangem_card/tasks/SignTask.java +++ b/tangem-card/src/main/java/com/tangem/tangem_card/tasks/SignTask.java @@ -47,7 +47,7 @@ public class SignTask extends CustomReadCardTask { mNotifications.onReadProgress(protocol, 30); if (isCancelled) return; - if (mCard.getPauseBeforePIN2() > 0) { + if (mCard.getPauseBeforePIN2() > 0 && !mCard.getTerminalIsLinked()) { mNotifications.onReadWait(mCard.getPauseBeforePIN2()); } diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index 2f7a51eb93..5471f31c0c 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -40,12 +40,12 @@ dependencies { implementation project(':tangem-card') // implementation 'com.github.TangemCash.card_android-common:card_android-android:0.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0-rc01' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' - implementation "androidx.lifecycle:lifecycle-runtime:2.0.0" - implementation "androidx.lifecycle:lifecycle-common-java8:2.0.0" - implementation 'androidx.appcompat:appcompat:1.1.0-rc01' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' + implementation "androidx.lifecycle:lifecycle-runtime:2.1.0" + implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0" + implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.scottyab:rootbeer-lib:0.0.7' testImplementation 'junit:junit:4.12' diff --git a/tangem-sdk/src/androidTest/java/com/tangem/tangem_sdk/ExampleInstrumentedTest.java b/tangem-sdk/src/androidTest/java/com/tangem/tangem_sdk/ExampleInstrumentedTest.java index 14e3fdcec3..41b93ed28b 100644 --- a/tangem-sdk/src/androidTest/java/com/tangem/tangem_sdk/ExampleInstrumentedTest.java +++ b/tangem-sdk/src/androidTest/java/com/tangem/tangem_sdk/ExampleInstrumentedTest.java @@ -8,7 +8,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk/android/data/PINStorage.java b/tangem-sdk/src/main/java/com/tangem/tangem_sdk/android/data/PINStorage.java index 4d0608bd18..a8cba9529c 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk/android/data/PINStorage.java +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk/android/data/PINStorage.java @@ -20,6 +20,7 @@ import javax.crypto.Cipher; public class PINStorage implements PINsProvider { private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2; + private static byte[] terminalPubicKey, terminalPrivateKey; private static SharedPreferences sharedPreferences = null; public static void init(Context context) { @@ -194,4 +195,19 @@ public class PINStorage implements PINsProvider { return sharedPreferences == null; } + public byte[] getTerminalPublicKey() { + return terminalPubicKey; + } + + public static void setTerminalPublicKey(byte[] terminalPubicKey) { + PINStorage.terminalPubicKey = terminalPubicKey; + } + + public byte[] getTerminalPrivateKey() { + return terminalPrivateKey; + } + + public static void setTerminalPrivateKey(byte[] terminalPrivateKey) { + PINStorage.terminalPrivateKey = terminalPrivateKey; + } } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt index cb697f95a2..f6e825319a 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt @@ -81,6 +81,10 @@ fun TangemCard.loadFromBundle(B: Bundle) { if (B.containsKey("onlineValidated")) isOnlineValidated = B.getBoolean("onlineValidated") + if (B.containsKey("terminalPrivateKey")) terminalPrivateKey = B.getByteArray("terminalPrivateKey") + if (B.containsKey("terminalPublicKey")) terminalPublicKey = B.getByteArray("terminalPublicKey") + terminalIsLinked = B.getBoolean("terminalIsLinked") + } val TangemCard.asBundle: Bundle @@ -152,6 +156,13 @@ fun TangemCard.saveToBundle(B: Bundle) { if (isOnlineValidated != null) B.putBoolean("onlineValidated", isOnlineValidated) + + if (terminalPrivateKey != null) + B.putByteArray("terminalPrivateKey", terminalPrivateKey) + if (terminalPublicKey != null) + B.putByteArray("terminalPublicKey", terminalPublicKey) + B.putBoolean("terminalIsLinked", terminalIsLinked) + } catch (e: Exception) { Log.e("Can't save to bundle ", e.message) } diff --git a/tangem-sdk/src/main/res/values-fr/strings.xml b/tangem-sdk/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..e546894281 --- /dev/null +++ b/tangem-sdk/src/main/res/values-fr/strings.xml @@ -0,0 +1,8 @@ + + + tangemcard + OK + Quitter + Activer NFC? + Cette application est inutile lorsque NFC est désactivé. Le mode lecteur en dépend. Appuyez sur OK pour accéder aux paramètres, où vous pouvez activer NFC. + \ No newline at end of file diff --git a/tangem-sdk/src/main/res/values/strings.xml b/tangem-sdk/src/main/res/values/strings.xml index b92c6c14f9..067b995daf 100644 --- a/tangem-sdk/src/main/res/values/strings.xml +++ b/tangem-sdk/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - tangemcard + tangemcard OK Quit Enable NFC?