Updated on 2026-08-14

This commit is contained in:
Tangem 2019-08-30 13:22:00 +03:00
commit af06bdca22
71 changed files with 1010 additions and 296 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@
.idea/runConfigurations.xml
.idea/encodings.xml
.idea/codeStyles/codeStyleConfig.xml
.idea/assetWizardSettings.xml
# fastlane files
**/fastlane/report.xml

View file

@ -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'
@ -119,6 +135,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'

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -2,5 +2,6 @@
<resources>
<string name="app_name">DEBUG</string>
<string name="log_file_provider_authorities">com.tangem.debug.data.LogFileProvider</string>
</resources>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.tangem.wallet">
<application
tools:ignore="GoogleAppIndexingWarning">
<meta-data
android:name="io.fabric.ApiKey"
android:value="${fabric_api_key}" />
<provider
android:name="com.tangem.data.LogFileProvider"
android:authorities="@string/log_file_provider_authorities"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true" />
</application>
</manifest>

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">BetaTangem</string>
</resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -2,16 +2,18 @@ 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.data.local.PendingTransactionsStorage
import com.tangem.card_android.android.data.Firmwares
import com.tangem.card_android.android.data.PINStorage
import com.tangem.card_common.data.Issuer
import com.tangem.data.dp.PrefsManager
import com.tangem.data.local.PendingTransactionsStorage
import com.tangem.di.*
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
@ -73,6 +75,7 @@ class App : Application() {
}
}
)
if (BuildConfig.CRASHLYTICS) Fabric.with(this, Crashlytics())
}
}

View file

@ -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> blockchainInfoAddress(@Path("address") String address);
@GET(Server.ApiBlockchainInfo.Method.UTXO)
Single<BlockchainInfoUnspents> blockchainInfoUnspents(@Query("active") String address);
@FormUrlEncoded
@POST(Server.ApiBlockchainInfo.Method.PUSH)
Single<ResponseBody> blockchainInfoPush(@Field("tx") String tx);
}

View file

@ -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;

View file

@ -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";
}
}
}

View file

@ -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<BlockchainInfoAddressAndUnspents> addressAndUnspentsObserver) {
Log.i(TAG, "new getAddressAndUnspents request");
BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class);
Single<BlockchainInfoAddress> addressObservable = api.blockchainInfoAddress(wallet);
Single<BlockchainInfoUnspents> 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<ResponseBody> sendObserver) {
Log.i(TAG, "new getAddress request");
BlockchainInfoApi api = App.Companion.getNetworkComponent().getRetrofitBlockchainInfo().create(BlockchainInfoApi.class);
Single<ResponseBody> sendObservable = api.blockchainInfoPush(tx)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
sendObservable.subscribe(sendObserver);
}
}

View file

@ -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";

View file

@ -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<Account> accountObserver) {
Log.i(TAG, "new getBalance request");

View file

@ -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<RippleResponse> call = rippleApi.ripple(rippleBody);
call.enqueue(new Callback<RippleResponse>() {
@Override
public void onResponse(@NonNull Call<RippleResponse> call, @NonNull Response<RippleResponse> 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<RippleResponse> 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<RippleResponse> call = rippleApi.ripple(rippleBody);
call.enqueue(new Callback<RippleResponse>() {
@Override

View file

@ -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/";
}

View file

@ -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<BlockchainInfoTransaction>? = 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<BlockchainInfoUtxo>
)
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
)

View file

@ -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

View file

@ -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()
}

View file

@ -34,7 +34,7 @@ class LogoActivity : AppCompatActivity() {
App.navigatorComponent.inject(this)
ivLogo.setOnClickListener { hide() }
clLogoContainer.setOnClickListener { hide() }
}
override fun onPostCreate(savedInstanceState: Bundle?) {

View file

@ -41,14 +41,17 @@ class MainActivity : AppCompatActivity() {
// 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<Tag>(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<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null) {
val activeFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
?.childFragmentManager?.primaryNavigationFragment
(activeFragment as? NfcAdapter.ReaderCallback)?.onTagDiscovered(tag)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -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;
}
}

View file

@ -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<BlockchainInfoAddressAndUnspents> addressAndUnspentsObserver = new DisposableSingleObserver<BlockchainInfoAddressAndUnspents>() {
@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<ResponseBody> responseObserver = new DisposableSingleObserver<ResponseBody>() {
@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();

View file

@ -2,11 +2,22 @@ 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;
@ -21,6 +32,8 @@ import com.tangem.wallet.UnspentOutputInfo;
import com.tangem.wallet.btc.BtcData;
import com.tangem.wallet.btc.BtcEngine;
import org.json.JSONException;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -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;

View file

@ -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())) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,4 +1,5 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/clLogoContainer"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -17,7 +18,7 @@
android:fontFamily="@font/maax"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@android:color/holo_red_light"
android:textColor="@android:color/black"
android:textSize="@dimen/text_size_small"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
@ -47,7 +48,7 @@
android:text="@string/splash_version_name_release"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@android:color/holo_red_light"
android:textColor="@android:color/black"
android:textSize="@dimen/text_size_small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -66,8 +66,8 @@
<string name="menu_loaded_wallet_erase_wallet">Erase wallet</string>
<!-- Splash Screen -->
<string name="splash_version_name_debug">BETA v.%1$s \n dev \n build %2$s</string>
<string name="splash_version_name_release">BETA v.%1$s</string>
<string name="splash_version_name_debug">ALPHA v.%1$s \n dev \n build %2$s</string>
<string name="splash_version_name_release">v.%1$s</string>
<string name="splash_cardano">Cardano</string>

View file

@ -3,11 +3,13 @@ buildscript {
repositories {
google()
jcenter()
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
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'
}
}

View file

@ -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'

View file

@ -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

View file

@ -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
----

View file

@ -1,6 +1,6 @@
#Thu Apr 18 12:21:12 MSK 2019
#Wed Aug 21 11:57:23 MSK 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

View file

@ -20,6 +20,10 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug_beta {
initWith debug
versionNameSuffix "-beta"
}
}
productFlavors {
}