diff --git a/.gitignore b/.gitignore index 61cfae2fdc..16e0697372 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ .idea/runConfigurations.xml .idea/encodings.xml .idea/codeStyles/codeStyleConfig.xml +.idea/assetWizardSettings.xml # fastlane files **/fastlane/report.xml diff --git a/app/build.gradle b/app/build.gradle index 06dbb7a80f..460e200818 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' +apply plugin: 'io.fabric' static def generateVersionName() { def gitCommitsCount = "git rev-list HEAD --count".execute().text.trim() @@ -9,6 +10,9 @@ static def generateVersionName() { return gitCommitsCount.toInteger() + 831// + "\\n" + gitCurrentBranch } +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + android { compileSdkVersion 28 defaultConfig { @@ -25,19 +29,30 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.debug + buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("false")' + ext.enableCrashlytics = false } debug { debuggable true minifyEnabled false applicationIdSuffix ".debug" signingConfig signingConfigs.debug + buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("false")' + ext.enableCrashlytics = false + } + debug_beta { + initWith debug + versionNameSuffix "-beta" + applicationIdSuffix ".debug_beta" + buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("true")' + manifestPlaceholders = [fabric_api_key: properties.getProperty('fabric_api_key')] } } flavorDimensions "" productFlavors { tangemAccess { - versionCode 114 - versionName "0.908.1." + generateVersionName() + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 116 + versionName project.hasProperty('versionName') ? project.property('versionName') : "0.1.0.-SNAPSHOT" applicationId "com.tangem.wallet" signingConfig signingConfigs.debug } @@ -65,6 +80,7 @@ android { repositories { maven { url "https://jitpack.io" } + maven { url 'https://maven.fabric.io/public' } } configurations { all*.exclude group: 'com.google.guava', module: 'listenablefuture' @@ -123,6 +139,9 @@ dependencies { // implementation 'com.github.stellar:java-stellar-sdk:0.4.1' implementation 'net.i2p.crypto:eddsa:0.3.0' + implementation('com.crashlytics.sdk.android:crashlytics:2.9.9') { transitive = true } + implementation('io.fabric.sdk.android:fabric:1.4.0@aar') { transitive = true } + implementation files('libs/ripple-core-0.0.1.jar') //4 dependencies for ripple-core TODO: move to module? implementation 'net.i2p.crypto:eddsa:0.3.0' diff --git a/app/src/debug/ic_launcher-web.png b/app/src/debug/ic_launcher-web.png new file mode 100644 index 0000000000..6ea2968959 Binary files /dev/null and b/app/src/debug/ic_launcher-web.png differ diff --git a/app/src/debug/res/drawable/ic_launcher_background.xml b/app/src/debug/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..2408e30d17 --- /dev/null +++ b/app/src/debug/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..c4a603d4cc --- /dev/null +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..c4a603d4cc --- /dev/null +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..32c50f2332 Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..d5b16e9389 Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..27632f83cf Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..5b7966cdd4 Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..626b524a19 Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..2d7e470a28 Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..54a64c94f6 Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..a88c8e24f4 Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..c87f5f573d Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..9847879c7c Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..40b595210b Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..be2d53828c Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..dab3199415 Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..92e1668b94 Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..d4c5bcabcb Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index 9ef8838437..1c13a1dbc0 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -2,5 +2,6 @@ DEBUG + com.tangem.debug.data.LogFileProvider \ No newline at end of file diff --git a/app/src/debug_beta/AndroidManifest.xml b/app/src/debug_beta/AndroidManifest.xml new file mode 100644 index 0000000000..9f07ca1097 --- /dev/null +++ b/app/src/debug_beta/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/debug_beta/res/drawable/ic_launcher_background.xml b/app/src/debug_beta/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..2408e30d17 --- /dev/null +++ b/app/src/debug_beta/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..c4a603d4cc --- /dev/null +++ b/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..c4a603d4cc --- /dev/null +++ b/app/src/debug_beta/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..2789587d99 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..33ac538a5c Binary files /dev/null and b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..db6d60f2c8 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..807a7a3be8 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..c91c20c714 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..8c87ad2ac3 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..d525ea41b9 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..578f07c490 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..fb770fede5 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5e640bd233 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..0489350858 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..31e3b01ea5 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..f1730013fc Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..e1c04f31c9 Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..1847464cbd Binary files /dev/null and b/app/src/debug_beta/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/debug_beta/res/values/strings.xml b/app/src/debug_beta/res/values/strings.xml new file mode 100644 index 0000000000..3e2d147b31 --- /dev/null +++ b/app/src/debug_beta/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + BetaTangem + + \ No newline at end of file diff --git a/app/src/main/ic_launcher_2-web.png b/app/src/main/ic_launcher_2-web.png new file mode 100644 index 0000000000..8639ebe991 Binary files /dev/null and b/app/src/main/ic_launcher_2-web.png differ diff --git a/app/src/main/java/com/tangem/App.kt b/app/src/main/java/com/tangem/App.kt index 04bbe17063..0a295e366a 100644 --- a/app/src/main/java/com/tangem/App.kt +++ b/app/src/main/java/com/tangem/App.kt @@ -2,6 +2,7 @@ package com.tangem import android.app.Application import androidx.appcompat.app.AppCompatDelegate +import com.crashlytics.android.Crashlytics import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.tangem.card_android.android.data.Firmwares @@ -15,6 +16,7 @@ import com.tangem.di.NetworkComponent import com.tangem.di.ToastHelperComponent import com.tangem.server_android.data.LocalStorage import com.tangem.wallet.BuildConfig +import io.fabric.sdk.android.Fabric import java.io.InputStreamReader import java.nio.charset.StandardCharsets @@ -74,6 +76,7 @@ class App : Application() { } } ) + if (BuildConfig.CRASHLYTICS) Fabric.with(this, Crashlytics()) } } diff --git a/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java b/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java new file mode 100644 index 0000000000..70f4aae91d --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/BlockchainInfoApi.java @@ -0,0 +1,25 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.BlockchainInfoAddress; +import com.tangem.data.network.model.BlockchainInfoUnspents; + +import io.reactivex.Single; +import okhttp3.ResponseBody; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface BlockchainInfoApi { + @GET(Server.ApiBlockchainInfo.Method.ADDRESS) + Single blockchainInfoAddress(@Path("address") String address); + + @GET(Server.ApiBlockchainInfo.Method.UTXO) + Single blockchainInfoUnspents(@Query("active") String address); + + @FormUrlEncoded + @POST(Server.ApiBlockchainInfo.Method.PUSH) + Single blockchainInfoPush(@Field("tx") String tx); +} diff --git a/app/src/main/java/com/tangem/data/network/CoinmarketApi.java b/app/src/main/java/com/tangem/data/network/CoinmarketApi.java index 0411ca2100..fd3699ba08 100644 --- a/app/src/main/java/com/tangem/data/network/CoinmarketApi.java +++ b/app/src/main/java/com/tangem/data/network/CoinmarketApi.java @@ -3,7 +3,6 @@ package com.tangem.data.network; import com.tangem.data.network.model.RateInfoResponse; import io.reactivex.Observable; -import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Headers; import retrofit2.http.Query; diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 943488dc02..1b0570150a 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -118,4 +118,15 @@ public class Server { static final String PUSH = MAIN + "/txs/push"; } } + + public static class ApiBlockchainInfo { + public static final String URL_BLOCKCHAININFO = ServerURL.API_BLOCKCHAIN_INFO; + + public static class Method { + static final String ADDRESS = URL_BLOCKCHAININFO + "rawaddr/{address}?limit=5"; + static final String UTXO = URL_BLOCKCHAININFO + "unspent"; +// static final String TX = URL_BLOCKCHAININFO + "rawtx/{txHash}"; + static final String PUSH = URL_BLOCKCHAININFO + "pushtx"; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java new file mode 100644 index 0000000000..3697332b26 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiBlockchainInfo.java @@ -0,0 +1,46 @@ +package com.tangem.data.network; + +import android.util.Log; + +import com.tangem.App; +import com.tangem.data.network.model.BlockchainInfoAddress; +import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents; +import com.tangem.data.network.model.BlockchainInfoUnspents; + +import java.util.ArrayList; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import okhttp3.ResponseBody; + +public class ServerApiBlockchainInfo { + private static String TAG = ServerApiBlockchainInfo.class.getSimpleName(); + + public void getAddressAndUnspents(String wallet, SingleObserver addressAndUnspentsObserver) { + Log.i(TAG, "new getAddressAndUnspents request"); + BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); + + Single addressObservable = api.blockchainInfoAddress(wallet); + + Single unspentsObservable = api.blockchainInfoUnspents(wallet) + .onErrorReturnItem(new BlockchainInfoUnspents(new ArrayList<>())); + + Single.zip(addressObservable, unspentsObservable, BlockchainInfoAddressAndUnspents::new) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(addressAndUnspentsObserver); + } + + public void sendTransaction(String tx, SingleObserver sendObserver) { + Log.i(TAG, "new getAddress request"); + BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class); + + Single sendObservable = api.blockchainInfoPush(tx) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + + sendObservable.subscribe(sendObserver); + } +} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java index f593b1545c..6b8bc2a6f1 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java @@ -2,6 +2,8 @@ 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.BlockcypherBody; @@ -11,13 +13,12 @@ import com.tangem.data.network.model.BlockcypherTx; import java.util.Random; -import androidx.annotation.NonNull; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class ServerApiBlockcypher { - private static String TAG = ServerApiRipple.class.getSimpleName(); + private static String TAG = ServerApiBlockcypher.class.getSimpleName(); public static final String BLOCKCYPHER_ADDRESS = "blockcypher_address"; public static final String BLOCKCYPHER_FEE = "blockcypher_fee"; diff --git a/app/src/main/java/com/tangem/data/network/ServerApiEos.java b/app/src/main/java/com/tangem/data/network/ServerApiEos.java index 81f666b50b..99bc68b9cb 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiEos.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiEos.java @@ -7,18 +7,16 @@ import com.tangem.wallet.eos.EosPushTransactionRequest; import io.jafka.jeos.EosApi; import io.jafka.jeos.EosApiFactory; -import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest; import io.jafka.jeos.core.response.chain.account.Account; import io.jafka.jeos.core.response.chain.transaction.PushedTransaction; import io.jafka.jeos.impl.EosApiServiceGenerator; -import io.jafka.jeos.impl.EosChainApiService; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; public class ServerApiEos { - private static String TAG = ServerApiBinance.class.getSimpleName(); + private static String TAG = ServerApiEos.class.getSimpleName(); public static void getBalance(String wallet, Observer accountObserver) { Log.i(TAG, "new getBalance request"); 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 ddbe82e090..e4c82722fb 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiRipple.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiRipple.java @@ -2,12 +2,13 @@ package com.tangem.data.network; import android.util.Log; +import androidx.annotation.NonNull; + import com.tangem.data.network.model.RippleBody; import com.tangem.data.network.model.RippleResponse; import java.util.HashMap; -import androidx.annotation.NonNull; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -25,7 +26,14 @@ public class ServerApiRipple { private int requestsCount = 0; - public static String lastNode; + private final String rippleURL1 = "https://s1.ripple.com:51234"; //TODO: make random selection, add more?, move + private final String rippleURL2 = "https://s2.ripple.com:51234"; + + private String currentURL = rippleURL1; + + public String getCurrentURL() { + return currentURL; + } public boolean isRequestsSequenceCompleted() { Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); @@ -46,11 +54,9 @@ public class ServerApiRipple { public void requestData(String method, String wallet, String tx) { requestsCount++; - String rippleURL = "https://s1.ripple.com:51234"; //TODO: make random selection - lastNode = rippleURL; //TODO: show node instead of URL Retrofit retrofitRipple = new Retrofit.Builder() - .baseUrl(rippleURL) + .baseUrl(currentURL) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -93,6 +99,38 @@ public class ServerApiRipple { rippleBody = new RippleBody(); } + Call call = rippleApi.ripple(rippleBody); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + retryRequest(method, rippleBody); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + retryRequest(method, rippleBody); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + } + + private void retryRequest(String method, RippleBody rippleBody) { + currentURL = rippleURL2; + + Retrofit retrofitRipple = new Retrofit.Builder() + .baseUrl(currentURL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + RippleApi rippleApi = retrofitRipple.create(RippleApi.class); + Call call = rippleApi.ripple(rippleBody); call.enqueue(new Callback() { @Override 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 362e064d50..45515132d2 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -14,4 +14,5 @@ class ServerURL { 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_BLOCKCHAIN_INFO = "https://blockchain.info/"; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt new file mode 100644 index 0000000000..fab1d0c88f --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/BlockchainInfoResponse.kt @@ -0,0 +1,43 @@ +package com.tangem.data.network.model + +import com.google.gson.annotations.SerializedName + +data class BlockchainInfoAddress( + @SerializedName("final_balance") + var final_balance: Long? = null, + + @SerializedName("txs") + var txs: List? = null +) + +data class BlockchainInfoTransaction( + @SerializedName("hash") + var hash: String? = null, + + @SerializedName("block_height") + var block_height: Long? = null +) + +data class BlockchainInfoUnspents( + @SerializedName("unspent_outputs") + var unspent_outputs: List +) + +data class BlockchainInfoUtxo( + @SerializedName("tx_hash_big_endian") + var tx_hash_big_endian: String? = null, + + @SerializedName("tx_output_n") + var tx_output_n: Int? = null, + + @SerializedName("value") + var value: Long? = null, + + @SerializedName("script") + var script: String? = null +) + +data class BlockchainInfoAddressAndUnspents( + var address: BlockchainInfoAddress, + var unspents: BlockchainInfoUnspents +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/NetworkComponent.kt b/app/src/main/java/com/tangem/di/NetworkComponent.kt index 06182cb4a8..e877710081 100644 --- a/app/src/main/java/com/tangem/di/NetworkComponent.kt +++ b/app/src/main/java/com/tangem/di/NetworkComponent.kt @@ -1,14 +1,11 @@ package com.tangem.di import com.tangem.data.network.Server - -import java.net.Socket - -import javax.inject.Named -import javax.inject.Singleton - import dagger.Component import retrofit2.Retrofit +import java.net.Socket +import javax.inject.Named +import javax.inject.Singleton @Singleton @Component(modules = [NetworkModule::class]) @@ -38,6 +35,9 @@ interface NetworkComponent { @get:Named(Server.ApiSoChain.URL) val retrofitSoChain: Retrofit + @get:Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) + val retrofitBlockchainInfo: Retrofit + @get:Named("socket") val socket: Socket diff --git a/app/src/main/java/com/tangem/di/NetworkModule.kt b/app/src/main/java/com/tangem/di/NetworkModule.kt index 4f1da7af02..93e10159e6 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.kt +++ b/app/src/main/java/com/tangem/di/NetworkModule.kt @@ -3,21 +3,19 @@ package com.tangem.di import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import com.tangem.data.network.Server import com.tangem.wallet.BuildConfig - -import java.io.IOException -import java.net.InetSocketAddress -import java.net.Socket -import java.net.SocketException - -import javax.inject.Named -import javax.inject.Singleton - import dagger.Module import dagger.Provides import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import java.io.IOException +import java.net.InetSocketAddress +import java.net.Socket +import java.net.SocketException +import javax.inject.Named +import javax.inject.Singleton @Module internal class NetworkModule { @@ -121,6 +119,20 @@ internal class NetworkModule { return builder.build() } + @Singleton + @Provides + @Named(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) + fun provideRetrofitBlockchainInfo(): Retrofit { + val builder = Retrofit.Builder() + .baseUrl(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO) + .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(ScalarsConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + if (BuildConfig.DEBUG) + builder.client(createOkHttpClient()) + return builder.build() + } + private fun createOkHttpClient(): OkHttpClient { return OkHttpClient.Builder().addInterceptor(createHttpLoggingInterceptor()).build() } diff --git a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt index 71be563abc..bd14533708 100644 --- a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt @@ -39,14 +39,15 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { // private var onNfcReaderCallback: NfcAdapter.ReaderCallback? = null -// override fun onNewIntent(intent: Intent?) { -// super.onNewIntent(intent) -// 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 && onNfcReaderCallback != null) -// onNfcReaderCallback?.onTagDiscovered(tag) -// } -// } + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + 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) { + onTagDiscovered(tag) + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt b/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt index c893fcb397..0ee9038dfb 100644 --- a/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/LogoFragment.kt @@ -26,7 +26,7 @@ class LogoFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - ivLogo.setOnClickListener { hide() } + clLogoContainer.setOnClickListener { hide() } // set beta version name if (BuildConfig.DEBUG) tvAppVersion.text = String.format(getString(R.string.version_name_debug), BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt index 9ea9ca608c..eacbc94863 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt @@ -142,7 +142,7 @@ class EmptyWalletFragment : BaseFragment(), NavigationResultListener, val isoDep = IsoDep.get(tag) val uid = tag.id val sUID = Util.byteArrayToHexString(uid) - if (ctx.card.uid != sUID) { + if (ctx.card.uid != sUID || cardProtocol != null) { (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag) return } diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcData.java b/app/src/main/java/com/tangem/wallet/btc/BtcData.java index 24d82d0ed0..ba4a6da025 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcData.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcData.java @@ -19,17 +19,19 @@ public class BtcData extends CoinData { private boolean useBlockcypher = false; + //for blockchain.info + private boolean hasUnconfirmed = false; + public String getUnspentInputsDescription() { try { int gatheredUnspents = 0; - if( unspentTransactions==null ) return ""; + if (unspentTransactions == null) return ""; for (int i = 0; i < unspentTransactions.size(); i++) { - if (unspentTransactions.get(i).script != null && unspentTransactions.get(i).script.length() > 1) gatheredUnspents++; + if (unspentTransactions.get(i).script != null && unspentTransactions.get(i).script.length() > 1) + gatheredUnspents++; } return unspentTransactions.size() + " unspents (" + gatheredUnspents + " received)"; - } - catch (Exception e) - { + } catch (Exception e) { e.printStackTrace(); return ""; } @@ -86,6 +88,7 @@ public class BtcData extends CoinData { } } if (B.containsKey("UseBlockcypher")) useBlockcypher = B.getBoolean("UseBlockcypher"); + if (B.containsKey("HasUnconfirmed")) useBlockcypher = B.getBoolean("HasUnconfirmed"); } @Override @@ -102,6 +105,7 @@ public class BtcData extends CoinData { if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed); if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed); if (useBlockcypher) B.putBoolean("UseBlockcypher", true); + if (hasUnconfirmed) B.putBoolean("HasUnconfirmed", true); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -116,7 +120,11 @@ public class BtcData extends CoinData { } public CoinEngine.InternalAmount getBalanceInInternalUnits() { - return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceConfirmed).add(BigDecimal.valueOf(balanceUnconfirmed)),"Satoshi"); + if (balanceConfirmed != null && balanceUnconfirmed != null) { + return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceConfirmed).add(BigDecimal.valueOf(balanceUnconfirmed)), "Satoshi"); + } else { + return null; + } } public Long getBalanceUnconfirmed() { @@ -142,4 +150,12 @@ public class BtcData extends CoinData { public void setUseBlockcypher(boolean useBlockcypher) { this.useBlockcypher = useBlockcypher; } + + public boolean isHasUnconfirmed() { + return hasUnconfirmed; + } + + public void setHasUnconfirmed(boolean hasUnconfirmed) { + this.hasUnconfirmed = hasUnconfirmed; + } } 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 22d1075ccc..3c2c9b1608 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -12,14 +12,18 @@ import com.tangem.card_common.util.Util; import com.tangem.data.Blockchain; import com.tangem.data.local.PendingTransactionsStorage; import com.tangem.data.network.Server; +import com.tangem.data.network.ServerApiBlockchainInfo; import com.tangem.data.network.ServerApiBlockcypher; import com.tangem.data.network.ServerApiCommon; -import com.tangem.data.network.ServerApiSoChain; +import com.tangem.data.network.model.BlockchainInfoAddress; +import com.tangem.data.network.model.BlockchainInfoAddressAndUnspents; +import com.tangem.data.network.model.BlockchainInfoTransaction; +import com.tangem.data.network.model.BlockchainInfoUnspents; +import com.tangem.data.network.model.BlockchainInfoUtxo; import com.tangem.data.network.model.BlockcypherFee; import com.tangem.data.network.model.BlockcypherResponse; import com.tangem.data.network.model.BlockcypherTx; import com.tangem.data.network.model.BlockcypherTxref; -import com.tangem.data.network.model.SoChain; import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; @@ -33,9 +37,8 @@ import com.tangem.wallet.TangemContext; import com.tangem.wallet.Transaction; import com.tangem.wallet.UnspentOutputInfo; -import org.json.JSONException; - import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; @@ -46,6 +49,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import io.reactivex.SingleObserver; +import io.reactivex.observers.DisposableSingleObserver; +import okhttp3.ResponseBody; + public class BtcEngine extends CoinEngine { private static final String TAG = BtcEngine.class.getSimpleName(); @@ -263,7 +270,7 @@ public class BtcEngine extends CoinEngine { // return; // } - if (coinData.getBalanceUnconfirmed() != 0) { + if (coinData.getBalanceUnconfirmed() != 0 || coinData.isHasUnconfirmed()) { balanceValidator.setScore(0); balanceValidator.setFirstLine("Transaction in progress"); balanceValidator.setSecondLine("Wait for confirmation in blockchain"); @@ -537,120 +544,67 @@ public class BtcEngine extends CoinEngine { public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { ctx.setError(null); - if (!coinData.isUseBlockcypher()) { - final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + //SoChain request can be found at LtcEngine + if (!coinData.isUseBlockcypher() && ctx.getBlockchain() != Blockchain.BitcoinTestNet) { + final ServerApiBlockchainInfo serverApiBlockchainInfo = new ServerApiBlockchainInfo(); - ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() { + SingleObserver addressAndUnspentsObserver = new DisposableSingleObserver() { @Override - public void onSuccess(SoChain.Response.AddressBalance response) { - try { - String walletAddress = response.getData().getAddress(); - if (!walletAddress.equals(coinData.getWallet())) { - // todo - check - throw new Exception("Invalid wallet address in answer!"); - } - Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact(); - Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact(); + public void onSuccess(BlockchainInfoAddressAndUnspents blockchainInfoAddressAndUnspents) { + BlockchainInfoAddress blockchainInfoAddress = blockchainInfoAddressAndUnspents.getAddress(); + BlockchainInfoUnspents blockchainInfoUnspents = blockchainInfoAddressAndUnspents.getUnspents(); + + if (blockchainInfoAddress.getFinal_balance() != null) { coinData.setBalanceReceived(true); - coinData.setBalanceConfirmed(confBalance); - coinData.setBalanceUnconfirmed(unconfirmedBalance); - coinData.setValidationNodeDescription(Server.ApiSoChain.URL); - } catch (JSONException e) { - e.printStackTrace(); - Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); - ctx.setError("FAIL METHOD_GetBalance JSONException"); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL METHOD_GetBalance Exception"); - ctx.setError("FAIL METHOD_GetBalance Exception"); - } - if (serverApiSoChain.isRequestsSequenceCompleted()) { - if (!coinData.isUseBlockcypher()) { - checkPending(blockchainRequestsCallbacks); - } else { - try { - requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); - } catch (Exception e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); + coinData.setBalanceConfirmed(blockchainInfoAddress.getFinal_balance()); + coinData.setBalanceUnconfirmed(0L); + coinData.setValidationNodeDescription(Server.ApiBlockchainInfo.URL_BLOCKCHAININFO); + + for (BlockchainInfoTransaction tx : blockchainInfoAddress.getTxs()) { + if (tx.getBlock_height() == null) { + coinData.setHasUnconfirmed(true); + } + } + + if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String pendingTxId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + for (BlockchainInfoTransaction responseTx : blockchainInfoAddress.getTxs()) { + if (responseTx.getHash().equals(pendingTxId)) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), pendingTx.getTx()); + } + } } } - } else { - blockchainRequestsCallbacks.onProgress(); } + for (BlockchainInfoUtxo utxo : blockchainInfoUnspents.getUnspent_outputs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = utxo.getTx_hash_big_endian(); + trUnspent.amount = utxo.getValue(); + trUnspent.outputN = utxo.getTx_output_n(); + trUnspent.script = utxo.getScript(); + coinData.getUnspentTransactions().add(trUnspent); + } + + blockchainRequestsCallbacks.onComplete(true); } @Override - public void onSuccess(SoChain.Response.TxUnspent response) { - String walletAddress = response.getData().getAddress(); - try { - if (!walletAddress.equals(coinData.getWallet())) { - // todo - check - throw new Exception("Invalid wallet address in answer!"); - } - coinData.getUnspentTransactions().clear(); - if (response.getData().getTxs() != null) - for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) { - BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); - trUnspent.txID = tx.getTxid(); - trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact(); - trUnspent.outputN = tx.getOutput_no(); - trUnspent.script = tx.getScript_hex(); - coinData.getUnspentTransactions().add(trUnspent); - -// if (blockchainRequestsCallbacks.allowAdvance()) { -// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); -// } else { -// ctx.setError("Terminated by user"); -// } - } - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); - } - - if (serverApiSoChain.isRequestsSequenceCompleted()) { - if (!coinData.isUseBlockcypher()) { - checkPending(blockchainRequestsCallbacks); - } else { - try { - requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); - } catch (Exception e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } - } else { - blockchainRequestsCallbacks.onProgress(); - } - - } - - @Override - public void onFail(String message) { - Log.i(TAG, "onFail: " + message); + public void onError(Throwable e) { + Log.i(TAG, "onError: getAddress" + e.getMessage()); coinData.setUseBlockcypher(true); -// ctx.setError(R.string.cannot_obtain_data_from_blockchain); - if (serverApiSoChain.isRequestsSequenceCompleted()) { -// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); - try { - requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); - } catch (Exception e) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } - } else { - blockchainRequestsCallbacks.onProgress(); + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception ex) { + ctx.setError(ex.getMessage()); + blockchainRequestsCallbacks.onComplete(false); } } }; + serverApiBlockchainInfo.getAddressAndUnspents(coinData.getWallet(), addressAndUnspentsObserver); - serverApiSoChain.setAddressInfoListener(addressInfoListener); - - serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); - serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); } else { final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); @@ -716,93 +670,48 @@ public class BtcEngine extends CoinEngine { private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { + ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); - if (!coinData.isUseBlockcypher()) { - ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); - - ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() { - @Override - public void onSuccess(SoChain.Response.GetTx response) { - Log.i(TAG, "onSuccess: GetTx"); - try { - if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) { - App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex()); - } - } catch (Exception e) { - Log.e(TAG, "onFail: GetTx" + response); - } - if (serverApiSoChain.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } - } - - @Override - public void onFail(String message) { - Log.i(TAG, "onFail: GetTx " + message); - if (serverApiSoChain.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } - } - }; - - serverApiSoChain.setTransactionInfoListener(listener); - for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { - String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() { + @Override + public void onSuccess(BlockcypherTx response) { + Log.i(TAG, "onSuccess: BlockcypherTx"); try { - serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId); + if (response.getHex() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex()); + } } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "onFail: BlockcypherTx" + response); + } + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); } } - } else { - ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); - ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() { - - @Override - public void onSuccess(BlockcypherTx response) { - Log.i(TAG, "onSuccess: BlockcypherTx"); - try { - if (response.getHex() != null) { - App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex()); - } - } catch (Exception e) { - Log.e(TAG, "onFail: BlockcypherTx" + response); - } - if (serverApiBlockcypher.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: BlockcypherTx " + message); + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); } + } + }; + serverApiBlockcypher.setTxResponseListener(listener); - @Override - public void onFail(String message) { - Log.i(TAG, "onFail: BlockcypherTx " + message); - if (serverApiBlockcypher.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); - } else { - blockchainRequestsCallbacks.onProgress(); - } - } - }; - serverApiBlockcypher.setTxResponseListener(listener); - - for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { - String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); - try { - serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId); - } catch (Exception e) { - e.printStackTrace(); - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - } + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + try { + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } } + } else { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } @@ -1026,42 +935,40 @@ public class BtcEngine extends CoinEngine { @Override public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { final String txStr = BTCUtils.toHex(txForSend); - if (!coinData.isUseBlockcypher()) { - final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); - ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() { + //SoChain request can be found at LtcEngine + if (!coinData.isUseBlockcypher() && ctx.getBlockchain() != Blockchain.BitcoinTestNet) { + final ServerApiBlockchainInfo serverApiBlockchainInfo = new ServerApiBlockchainInfo(); + + SingleObserver responseObserver = new DisposableSingleObserver() { @Override - public void onSuccess(SoChain.Response.SendTx response) { + public void onSuccess(ResponseBody response) { try { - if (response.getStatus() == null || response.getData() == null) { - ctx.setError("Rejected by node: invalid answer received"); - blockchainRequestsCallbacks.onComplete(false); - } else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) { - ctx.setError("Rejected by node: " + response.getData()); - blockchainRequestsCallbacks.onComplete(false); - } else { - ctx.setError(null); - blockchainRequestsCallbacks.onComplete(true); - } - } catch (Exception e) { - if (e.getMessage() != null) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } else { - ctx.setError(e.getClass().getName()); - blockchainRequestsCallbacks.onComplete(false); + Log.i(TAG, response.string()); + if (!response.string().equals("Transaction Submitted")) { + ctx.setError(response.string()); } + } catch (IOException e) { + e.printStackTrace(); + ctx.setError(e.getMessage()); + } + + if (ctx.hasError()) { + blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onComplete(true); } } @Override - public void onFail(String message) { - ctx.setError(message); + public void onError(Throwable e) { + Log.e(TAG, e.getMessage()); + ctx.setError(e.getMessage()); blockchainRequestsCallbacks.onComplete(false); } }; - serverApiSoChain.setSendTxListener(listener); - serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr); + + serverApiBlockchainInfo.sendTransaction(txStr, responseObserver); } else { final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); 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 680c112119..4758a9ab04 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -2,24 +2,37 @@ package com.tangem.wallet.ltc; import android.net.Uri; import android.text.InputFilter; +import android.util.Log; +import com.tangem.App; +import com.tangem.card_common.data.TangemCard; +import com.tangem.card_common.reader.CardProtocol; +import com.tangem.card_common.tasks.SignTask; +import com.tangem.card_common.util.Util; +import com.tangem.data.local.PendingTransactionsStorage; +import com.tangem.data.network.Server; +import com.tangem.data.network.ServerApiBlockcypher; +import com.tangem.data.network.ServerApiSoChain; +import com.tangem.data.network.model.BlockcypherFee; +import com.tangem.data.network.model.BlockcypherResponse; +import com.tangem.data.network.model.BlockcypherTx; +import com.tangem.data.network.model.BlockcypherTxref; +import com.tangem.data.network.model.SoChain; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.util.DerEncodingUtil; import com.tangem.wallet.BTCUtils; import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.Base58; import com.tangem.wallet.CoinData; +import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; import com.tangem.wallet.Transaction; import com.tangem.wallet.UnspentOutputInfo; import com.tangem.wallet.btc.BtcData; import com.tangem.wallet.btc.BtcEngine; -import com.tangem.card_common.data.TangemCard; -import com.tangem.card_common.reader.CardProtocol; -import com.tangem.card_common.tasks.SignTask; -import com.tangem.card_common.util.Util; -import com.tangem.util.CryptoUtil; -import com.tangem.util.DecimalDigitsInputFilter; -import com.tangem.util.DerEncodingUtil; -import com.tangem.wallet.R; + +import org.json.JSONException; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; @@ -478,6 +491,281 @@ public class LtcEngine extends BtcEngine { }; } + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { + ctx.setError(null); + + if (!coinData.isUseBlockcypher()) { + final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + + ServerApiSoChain.AddressInfoListener addressInfoListener = new ServerApiSoChain.AddressInfoListener() { + @Override + public void onSuccess(SoChain.Response.AddressBalance response) { + try { + String walletAddress = response.getData().getAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = convertToInternalAmount(convertToAmount(response.getData().getConfirmed_balance(), getBalanceCurrency())).longValueExact(); + Long unconfirmedBalance = convertToInternalAmount(convertToAmount(response.getData().getUnconfirmed_balance(), getBalanceCurrency())).longValueExact(); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(Server.ApiSoChain.URL); + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); + ctx.setError("FAIL METHOD_GetBalance JSONException"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance Exception"); + ctx.setError("FAIL METHOD_GetBalance Exception"); + } + if (serverApiSoChain.isRequestsSequenceCompleted()) { + if (!coinData.isUseBlockcypher()) { + checkPending(blockchainRequestsCallbacks); + } else { + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + + } + + @Override + public void onSuccess(SoChain.Response.TxUnspent response) { + String walletAddress = response.getData().getAddress(); + try { + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + coinData.getUnspentTransactions().clear(); + if (response.getData().getTxs() != null) + for (SoChain.Response.TxUnspent.Data.Tx tx : response.getData().getTxs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = tx.getTxid(); + trUnspent.amount = convertToInternalAmount(convertToAmount(tx.getValue(), getBalanceCurrency())).longValueExact(); + trUnspent.outputN = tx.getOutput_no(); + trUnspent.script = tx.getScript_hex(); + coinData.getUnspentTransactions().add(trUnspent); + +// if (blockchainRequestsCallbacks.allowAdvance()) { +// //serverApiSoChain.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); +// } else { +// ctx.setError("Terminated by user"); +// } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } + + if (serverApiSoChain.isRequestsSequenceCompleted()) { + if (!coinData.isUseBlockcypher()) { + checkPending(blockchainRequestsCallbacks); + } else { + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: " + message); + coinData.setUseBlockcypher(true); +// ctx.setError(R.string.cannot_obtain_data_from_blockchain); + if (serverApiSoChain.isRequestsSequenceCompleted()) { +// blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + try { + requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks); + } catch (Exception e) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + + serverApiSoChain.setAddressInfoListener(addressInfoListener); + + serverApiSoChain.requestAddressBalance(ctx.getBlockchain(), coinData.getWallet()); + serverApiSoChain.requestUnspentTx(ctx.getBlockchain(), coinData.getWallet()); + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { + @Override + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + Log.i(TAG, "onSuccess: " + method); + try { + String walletAddress = blockcypherResponse.getAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = blockcypherResponse.getBalance(); + Long unconfirmedBalance = blockcypherResponse.getUnconfirmed_balance(); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(Server.ApiBlockcypher.URL_BLOCKCYPHER); + + coinData.getUnspentTransactions().clear(); + if (blockcypherResponse.getTxrefs() != null) { + for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = txref.getTx_hash(); + trUnspent.amount = txref.getValue(); + trUnspent.outputN = txref.getTx_output_n(); + trUnspent.script = txref.getScript(); + coinData.getUnspentTransactions().add(trUnspent); + } + } + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception"); + } + + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + checkPending(blockchainRequestsCallbacks); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions"); + ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions"); + blockchainRequestsCallbacks.onComplete(false); + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + + serverApiBlockcypher.setResponseListener(blockcypherListener); + + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); + } + } + + private void checkPending(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { + + if (!coinData.isUseBlockcypher()) { + ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + + ServerApiSoChain.TransactionInfoListener listener = new ServerApiSoChain.TransactionInfoListener() { + @Override + public void onSuccess(SoChain.Response.GetTx response) { + Log.i(TAG, "onSuccess: GetTx"); + try { + if (response.getData() != null && response.getData().getTx_hex() != null && response.getData().getTxid() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getData().getTx_hex()); + } + } catch (Exception e) { + Log.e(TAG, "onFail: GetTx" + response); + } + if (serverApiSoChain.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: GetTx " + message); + if (serverApiSoChain.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApiSoChain.setTransactionInfoListener(listener); + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + try { + serverApiSoChain.requestTransactionInfo(ctx.getBlockchain(), txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + } else { + ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.TxResponseListener listener = new ServerApiBlockcypher.TxResponseListener() { + + @Override + public void onSuccess(BlockcypherTx response) { + Log.i(TAG, "onSuccess: BlockcypherTx"); + try { + if (response.getHex() != null) { + App.pendingTransactionsStorage.removeTransaction(ctx.getCard(), response.getHex()); + } + } catch (Exception e) { + Log.e(TAG, "onFail: BlockcypherTx" + response); + } + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String message) { + Log.i(TAG, "onFail: BlockcypherTx " + message); + if (serverApiBlockcypher.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + serverApiBlockcypher.setTxResponseListener(listener); + + for (PendingTransactionsStorage.TransactionInfo pendingTx : App.pendingTransactionsStorage.getTransactions(ctx.getCard()).getTransactions()) { + String txId = BTCUtils.toHex(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(pendingTx.getTx())))); + try { + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txId); + } catch (Exception e) { + e.printStackTrace(); + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + } + } else { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } + } + private final static BigDecimal relayFee = new BigDecimal(0.00001).setScale(8, RoundingMode.DOWN); @Override @@ -486,6 +774,92 @@ public class LtcEngine extends BtcEngine { blockchainRequestsCallbacks.onComplete(true); } + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception { + final String txStr = BTCUtils.toHex(txForSend); + if (!coinData.isUseBlockcypher()) { + final ServerApiSoChain serverApiSoChain = new ServerApiSoChain(); + + ServerApiSoChain.SendTxListener listener = new ServerApiSoChain.SendTxListener() { + @Override + public void onSuccess(SoChain.Response.SendTx response) { + try { + if (response.getStatus() == null || response.getData() == null) { + ctx.setError("Rejected by node: invalid answer received"); + blockchainRequestsCallbacks.onComplete(false); + } else if (response.getStatus().equals("fail") || response.getData().getTxid() == null) { + ctx.setError("Rejected by node: " + response.getData()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + } + } + } + + @Override + public void onFail(String message) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiSoChain.setSendTxListener(listener); + serverApiSoChain.requestSendTransaction(ctx.getBlockchain(), txStr); + + } else { + final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher(); + + ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() { + @Override + public void onSuccess(String method, BlockcypherResponse blockcypherResponse) { + String resultString = blockcypherResponse.toString(); + try { + if (resultString.isEmpty()) { + ctx.setError("No response from node"); + blockchainRequestsCallbacks.onComplete(false); + } else { // TODO: Make check for a valid send response + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + Log.e(TAG, resultString); + } + } + } + + public void onSuccess(String method, BlockcypherFee blockcypherFee) { + Log.e(TAG, "Wrong response type for requestSendTransaction"); + ctx.setError("Wrong response type for requestSendTransaction"); + blockchainRequestsCallbacks.onComplete(false); + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiBlockcypher.setResponseListener(blockcypherListener); + + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_SEND, "", txStr); + } + } + @Override public boolean allowSelectFeeLevel() { return false; 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 9a7bb12594..95275bb0a5 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -15,14 +15,14 @@ import com.tangem.card_common.tasks.SignTask; import com.tangem.card_common.util.Util; import com.tangem.data.network.ServerApiRipple; import com.tangem.data.network.model.RippleResponse; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.wallet.BTCUtils; import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.CoinData; import com.tangem.wallet.CoinEngine; -import com.tangem.wallet.TangemContext; -import com.tangem.util.CryptoUtil; -import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.wallet.R; +import com.tangem.wallet.TangemContext; import java.math.BigDecimal; import java.math.BigInteger; @@ -455,7 +455,7 @@ public class XrpEngine extends CoinEngine { coinData.setBalanceReceived(true); coinData.setBalanceUnconfirmed(Long.parseLong(rippleResponse.getResult().getAccount_data().getBalance())); coinData.setSequence(rippleResponse.getResult().getAccount_data().getSequence()); - coinData.setValidationNodeDescription(ServerApiRipple.lastNode); + coinData.setValidationNodeDescription(serverApiRipple.getCurrentURL()); // //check pending // if (App.pendingTransactionsStorage.hasTransactions(ctx.getCard())) { diff --git a/app/src/main/res/drawable/ic_launcher_background.png b/app/src/main/res/drawable/ic_launcher_background.png new file mode 100644 index 0000000000..095189cdaf Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_background.png differ diff --git a/app/src/main/res/layout/fragment_logo.xml b/app/src/main/res/layout/fragment_logo.xml index 33e8e5751d..b6efa61fed 100644 --- a/app/src/main/res/layout/fragment_logo.xml +++ b/app/src/main/res/layout/fragment_logo.xml @@ -1,4 +1,5 @@ Erase wallet - BETA v.%1$s \n dev \n build %2$s - BETA v.%1$s + ALPHA v.%1$s \n dev build %2$s + v.%1$s Hello, %1$s! You have %2$d new messages. diff --git a/build.gradle b/build.gradle index cdf9111476..5c3241212c 100644 --- a/build.gradle +++ b/build.gradle @@ -3,11 +3,13 @@ buildscript { repositories { google() jcenter() + maven { url 'https://maven.fabric.io/public' } } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' 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' } } diff --git a/card-android/build.gradle b/card-android/build.gradle index e30a1701c6..2a324d24e4 100644 --- a/card-android/build.gradle +++ b/card-android/build.gradle @@ -20,6 +20,10 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug_beta { + initWith debug + versionNameSuffix "-beta" + } } buildToolsVersion '28.0.3' compileOptions { diff --git a/fastlane/Fastfile b/fastlane/Fastfile index bf598fdf5b..b70e5bad19 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,46 +16,59 @@ default_platform(:android) platform :android do + desc "Runs all the tests" lane :test do gradle( - task: "test", - flavor: "tangemAccess", - build_type: "Debug") + task: "test", + flavor: "tangemAccess", + build_type: "DebugUnitTest") end - desc "Build a release APK" - lane :release do - gradle( - task: "clean assemble", - flavor: "tangemAccess", - build_type: "Release") + desc "Build a signed release APK" + lane :release do |options| + gradle( + task: "clean assemble", + flavor: "tangemAccess", + build_type: "Release", + properties: { + "android.injected.signing.store.file" => options[:keystore], + "android.injected.signing.store.password" => options[:store_password], + "android.injected.signing.key.alias" => options[:key_alias], + "android.injected.signing.key.password" => options[:key_password], + }) end - desc "Build a debug APK" - lane :debug do - gradle(task: "clean assemble", - flavor: "tangemAccess", - build_type: "Debug") - end + desc "Build debug and release APKs" + lane :build do |options| + gradle( + task: "clean assemble", + flavor: "tangemAccess", + build_type: "Debug_beta", + properties: { + 'versionCode' => options[:versionCode], + 'versionName' => options[:versionName], + }) + gradle( + task: "assemble", + flavor: "tangemAccess", + build_type: "Release", + properties: { + 'versionCode' => options[:versionCode], + 'versionName' => options[:versionName], + "android.injected.signing.store.file" => options[:keystore], + "android.injected.signing.store.password" => options[:store_password], + "android.injected.signing.key.alias" => options[:key_alias], + "android.injected.signing.key.password" => options[:key_password], + }) + end desc "Submit a new Beta Build to Crashlytics Beta" - lane :beta do - gradle(task: "clean assemble", - flavor: "tangemAccess", - build_type: "Debug") - crashlytics - - # sh "your_script.sh" - # You can also use other beta testing services here + lane :beta do |options| + crashlytics( + api_token: options[:api_token], + build_secret: options[:build_secret], + apk_path: options[:apk_path]) end - desc "Deploy a new version to the Google Play" - lane :deploy do - gradle( - task: "clean assemble", - flavor: "tangemAccess", - build_type: "Release") - upload_to_play_store - end end diff --git a/fastlane/README.md b/fastlane/README.md index 61b4a5d99a..7c3f972135 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -25,22 +25,17 @@ Runs all the tests ``` fastlane android release ``` -Build a release APK -### android debug +Build a signed release APK +### android build ``` -fastlane android debug +fastlane android build ``` -Build a debug APK +Build debug and release APKs ### android beta ``` fastlane android beta ``` Submit a new Beta Build to Crashlytics Beta -### android deploy -``` -fastlane android deploy -``` -Deploy a new version to the Google Play ---- diff --git a/server-android/build.gradle b/server-android/build.gradle index 7aaa4af308..c6e7c5bfed 100644 --- a/server-android/build.gradle +++ b/server-android/build.gradle @@ -20,6 +20,10 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug_beta { + initWith debug + versionNameSuffix "-beta" + } } productFlavors { }