Updated on 2026-08-14

This commit is contained in:
Tangem 2019-01-10 16:58:52 +03:00
commit 8c0b3d30d0
180 changed files with 6215 additions and 3816 deletions

4
.idea/gradle.xml generated
View file

@ -9,7 +9,9 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/tangemcard" />
<option value="$PROJECT_DIR$/tangemcard-android" />
<option value="$PROJECT_DIR$/tangemcard-common" />
<option value="$PROJECT_DIR$/tangemserver-android" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />

6
.idea/kotlinc.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JsCompilerArguments">
<option name="sourceMapEmbedSources" />
</component>
</project>

5
.idea/misc.xml generated
View file

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" />
</list>
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />

View file

@ -15,7 +15,7 @@ android {
applicationId "com.tangem.wallet"
minSdkVersion 21
targetSdkVersion 28
versionCode 100
versionCode 103
versionName "0.811.1." + generateVersionName()
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@ -44,13 +44,14 @@ android {
}
dependencies {
implementation project(':tangemcard')
implementation project(':tangemcard-common')
implementation project(':tangemcard-android')
implementation project(':tangemserver-android')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-compat:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.dagger:dagger:2.16'
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.madgag.spongycastle:core:1.56.0.0'
@ -58,17 +59,18 @@ dependencies {
implementation 'com.scottyab:rootbeer-lib:0.0.7'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
implementation 'com.skyfishjy.ripplebackground:library:1.0.1'
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
implementation 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.bitcoinj:bitcoinj-parent:0.14.7'
implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
implementation 'org.greenrobot:eventbus:3.1.1'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'info.hoang8f:android-segmented:1.0.6'
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
}

View file

@ -1,5 +1,6 @@
<?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">
<uses-feature
@ -7,11 +8,12 @@
android:required="true" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
@ -20,11 +22,13 @@
<application
android:name="com.tangem.App"
android:allowBackup="true"
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<provider
android:name="com.tangem.data.LogFileProvider"
android:authorities="@string/log_file_provider_authorities"

View file

@ -3,10 +3,25 @@ package com.tangem;
import android.app.Application;
import android.support.v7.app.AppCompatDelegate;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.tangem.tangemserver.android.data.LocalStorage;
import com.tangem.di.DaggerNavigatorComponent;
import com.tangem.di.DaggerNetworkComponent;
import com.tangem.di.NavigatorComponent;
import com.tangem.di.NetworkComponent;
import com.tangem.tangemcard.data.Issuer;
import com.tangem.tangemcard.android.data.Firmwares;
import com.tangem.tangemcard.android.data.PINStorage;
import com.tangem.tangemcard.data.external.FirmwaresDigestsProvider;
import com.tangem.tangemcard.data.external.PINsProvider;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class App extends Application {
@ -30,6 +45,10 @@ public class App extends Application {
return navigatorComponent;
}
public static LocalStorage localStorage;
public static PINsProvider pinStorage;
public static FirmwaresDigestsProvider firmwaresStorage;
@Override
public void onCreate() {
super.onCreate();
@ -38,6 +57,18 @@ public class App extends Application {
networkComponent = DaggerNetworkComponent.create();
navigatorComponent = buildNavigatorComponent();
// common init
if (PINStorage.needInit())
PINStorage.init(getApplicationContext());
initIssuers();
firmwaresStorage = new Firmwares(getApplicationContext());
localStorage = new LocalStorage(getApplicationContext());
pinStorage = new PINStorage();
}
/**
@ -56,4 +87,21 @@ public class App extends Application {
.build();
}
public void initIssuers() {
try {
try (InputStream is = getApplicationContext().getAssets().open("issuers.json")) {
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
Type listType = new TypeToken<List<Issuer>>() {
}.getType();
Issuer.fillIssuers(new Gson().fromJson(reader, listType));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,13 +1,86 @@
package com.tangem
import android.app.Activity
object Constant {
const val EXTRA_BLOCKCHAIN_DATA = "BLOCKCHAIN_DATA"
const val MESSAGE = "Message"
const val ERROR = "Error"
const val EXTRA_MESSAGE = "message"
const val EXTRA_MODIFICATION = "modification"
const val EXTRA_MODIFICATION_DELETE = "delete"
const val EXTRA_MODIFICATION_UPDATE = "update"
// LoadedWallet, VerifyCard
const val REQUEST_CODE_SEND_PAYMENT = 1
const val REQUEST_CODE_PURGE = 2
const val REQUEST_CODE_REQUEST_PIN2_FOR_PURGE = 3
const val REQUEST_CODE_VERIFY_CARD = 4
const val REQUEST_CODE_ENTER_NEW_PIN = 5
const val REQUEST_CODE_ENTER_NEW_PIN2 = 6
const val REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN = 7
const val REQUEST_CODE_SWAP_PIN = 8
const val REQUEST_CODE_RECEIVE_PAYMENT = 9
// MainActivity
const val REQUEST_CODE_SHOW_CARD_ACTIVITY = 1
const val REQUEST_CODE_ENTER_PIN_ACTIVITY = 2
const val REQUEST_CODE_SEND_EMAIL = 3
const val REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS = 3
const val EXTRA_LAST_DISCOVERED_TAG = "extra_last_tag"
const val EXTRA_PIN2 = "PIN2"
// LogoActivity
const val EXTRA_AUTO_HIDE = "extra_auto_hide"
const val MILLIS_AUTO_HIDE = 1000
// PinRequestActivity
const val EXTRA_MODE = "mode"
const val KEY_ALIAS = "pinKey"
const val KEYSTORE = "AndroidKeyStore"
// QrScanActivity
const val EXTRA_QR_CODE = "QRCode"
// PinSwapActivity
const val EXTRA_CONFIRM_PIN = "confirmPIN"
const val EXTRA_CONFIRM_PIN_2 = "confirmPIN2"
const val EXTRA_NEW_PIN = "newPIN"
const val EXTRA_NEW_PIN_2 = "newPIN2"
// CreateNewWalletActivity
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
// EmptyWalletActivity
const val REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = 2
const val REQUEST_CODE_REQUEST_PIN2 = 3
// ConfirmPaymentActivity
const val REQUEST_CODE_SIGN_PAYMENT = 1
const val REQUEST_CODE_REQUEST_PIN2_ = 2
// SendTransactionActivity
const val EXTRA_TX: String = "TX"
// SignPaymentActivity
const val EXTRA_AMOUNT = "Amount"
const val EXTRA_AMOUNT_CURRENCY = "AmountCurrency"
const val EXTRA_FEE = "Fee"
const val EXTRA_FEE_CURRENCY = "FeeCurrency"
const val EXTRA_FEE_INCLUDED = "FeeIncluded"
const val EXTRA_TARGET_ADDRESS = "TargetAddress"
const val REQUEST_CODE_SEND_PAYMENT_ = 1
const val RESULT_INVALID_PIN_ = Activity.RESULT_FIRST_USER
// PreparePaymentActivity
const val REQUEST_CODE_SCAN_QR = 1
const val REQUEST_CODE_SEND_PAYMENT__ = 2
// PrepareCryptonitOtherApiWithdrawalActivity
const val REQUEST_CODE_SCAN_QR_KEY = 1
const val REQUEST_CODE_SCAN_QR_SECRET = 2
const val REQUEST_CODE_SCAN_QR_USER_ID = 3
}

View file

@ -1,7 +1,6 @@
package com.tangem.domain.wallet;
package com.tangem.data;
import com.google.common.base.Strings;
import com.tangem.wallet.R;
import com.tangem.tangemcard.R;
/**
* Created by dvol on 06.08.2017.
@ -13,7 +12,8 @@ public enum Blockchain {
Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"),
Token("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"),
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash");
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"),
Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin");
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
mID = ID;
@ -71,7 +71,7 @@ public enum Blockchain {
}
public int getImageResource(android.content.Context context, String name) {
if (Strings.isNullOrEmpty(name))
if (name==null || name.isEmpty())
return getImageResource();
name = name.toLowerCase();

View file

@ -68,8 +68,7 @@ public class LogFileProvider extends ContentProvider {
// Create & return a ParcelFileDescriptor pointing to the file
// Note: I don't care what mode they ask for - they're only getting
// read only
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(
fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
// Otherwise unrecognised Uri

View file

@ -3,7 +3,7 @@ package com.tangem.data;
import android.content.Context;
import android.util.Log;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import java.io.BufferedReader;
import java.io.BufferedWriter;

View file

@ -8,8 +8,10 @@ import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import com.tangem.data.db.PINStorage;
import com.tangem.Constant;
import com.tangem.tangemcard.android.data.PINStorage;
import com.tangem.presentation.activity.PinRequestActivity;
import com.tangem.util.LOG;
import java.io.IOException;
import java.lang.ref.WeakReference;
@ -25,6 +27,8 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
public static final String TAG = StartFingerprintReaderTask.class.getSimpleName();
private WeakReference<PinRequestActivity> reference;
private KeyStore keyStore;
@ -63,16 +67,14 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
@Override
protected void onPostExecute(final Boolean success) {
PinRequestActivity pinRequestActivity = reference.get();
onCancelled();
if (!success) {
pinRequestActivity.doLog("Authentication failed!");
LOG.i(TAG, "Authentication failed!");
} else {
fingerprintHelper.startAuth(fingerprintManager, cryptoObject);
pinRequestActivity.doLog("Authenticate using fingerprint!");
LOG.i(TAG, "Authenticate using fingerprint!");
}
}
@ -84,11 +86,9 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
}
private boolean getKeyStore() {
PinRequestActivity pinRequestActivity = reference.get();
pinRequestActivity.doLog("Getting keystore...");
LOG.i(TAG, "Getting keystore...");
try {
keyStore = KeyStore.getInstance(PinRequestActivity.KEYSTORE);
keyStore = KeyStore.getInstance(Constant.KEYSTORE);
keyStore.load(null); // Create empty keystore
return true;
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
@ -100,17 +100,15 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
@TargetApi(Build.VERSION_CODES.M)
public boolean createNewKey(boolean forceCreate) {
PinRequestActivity pinRequestActivity = reference.get();
pinRequestActivity.doLog("Creating new key...");
LOG.i(TAG, "Creating new key...");
try {
if (forceCreate)
keyStore.deleteEntry(PinRequestActivity.KEY_ALIAS);
keyStore.deleteEntry(Constant.KEY_ALIAS);
if (!keyStore.containsAlias(PinRequestActivity.KEY_ALIAS)) {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, PinRequestActivity.KEYSTORE);
if (!keyStore.containsAlias(Constant.KEY_ALIAS)) {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, Constant.KEYSTORE);
generator.init(new KeyGenParameterSpec.Builder(PinRequestActivity.KEY_ALIAS,
generator.init(new KeyGenParameterSpec.Builder(Constant.KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
@ -119,9 +117,9 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
);
generator.generateKey();
pinRequestActivity.doLog("Key created.");
LOG.i(TAG, "Key created.");
} else
pinRequestActivity.doLog("Key exists.");
LOG.i(TAG, "Key exists.");
return true;
} catch (Exception e) {
@ -132,9 +130,7 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
}
private boolean getCipher() {
PinRequestActivity pinRequestActivity = reference.get();
pinRequestActivity.doLog("Getting cipher...");
LOG.i(TAG, "Getting cipher...");
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
return true;
@ -149,10 +145,10 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
private boolean initCipher(int mode) {
PinRequestActivity pinRequestActivity = reference.get();
pinRequestActivity.doLog("Initializing cipher...");
LOG.i(TAG, "Initializing cipher...");
try {
keyStore.load(null);
SecretKey keyspec = (SecretKey) keyStore.getKey(PinRequestActivity.KEY_ALIAS, null);
SecretKey keyspec = (SecretKey) keyStore.getKey(Constant.KEY_ALIAS, null);
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(mode, keyspec);
@ -180,9 +176,7 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
@TargetApi(Build.VERSION_CODES.M)
private boolean initCryptObject() {
PinRequestActivity pinRequestActivity = reference.get();
pinRequestActivity.doLog("Initializing crypt object...");
LOG.i(TAG, "Initializing crypt object...");
try {
cryptoObject = new FingerprintManager.CryptoObject(cipher);
return true;
@ -192,5 +186,4 @@ public class StartFingerprintReaderTask extends AsyncTask<Void, Void, Boolean> {
return false;
}
}

View file

@ -4,30 +4,22 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.util.Util;
import com.tangem.wallet.R;
import java.io.IOException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
//import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
@ -36,9 +28,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
/**
* HTTP

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -12,7 +11,7 @@ import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.LinkedTreeMap;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import java.io.IOException;

View file

@ -17,14 +17,14 @@ public class ElectrumRequest {
public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast";
public static final String METHOD_GetFee = "blockchain.estimatefee";
public JSONObject jsRequestData;
public String answerData;
public String error;
public String walletAddress;
private JSONObject jsRequestData;
String answerData;
private String error = null;
private String walletAddress;
public String txHash;
public String TX;
public String host;
public int port;
private String TX;
String host;
int port;
private ElectrumRequest() {
}
@ -84,10 +84,9 @@ public class ElectrumRequest {
}
public static ElectrumRequest getFee(String wallet) {
public static ElectrumRequest getFee() {
ElectrumRequest request = new ElectrumRequest();
try {
request.walletAddress = wallet; //METHOD_GetFee
request.jsRequestData = new JSONObject("{ \"method\":\"" + METHOD_GetFee + "\", \"params\":[\"" + 6 + "\"] }");
} catch (JSONException e) {
e.printStackTrace();
@ -143,8 +142,8 @@ public class ElectrumRequest {
return "";
}
public boolean isMethod(String methodName) throws JSONException {
return jsRequestData.getString("method").equals(methodName);
public boolean isMethod(String methodName) {
return getMethod().equals(methodName);
}
public JSONArray getParams() throws JSONException {
@ -155,12 +154,36 @@ public class ElectrumRequest {
return getAnswer().getJSONObject("result");
}
public JSONObject getError() throws JSONException {
return getAnswer().getJSONObject("error");
public String getError() {
if( answerData!=null ) {
// answer received - return error from it
JSONObject answer = getAnswer();
if (answer.has("error")) {
try {
return getAnswer().getJSONObject("error").toString();
} catch (JSONException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}else{
// no answer received - return saved error reason
return error;
}
}
public void setError(String error) {
this.error = error;
}
public String getResultString() throws JSONException {
return getAnswer().getString("result");
if (getAnswer().has("result")) {
return getAnswer().getString("result");
} else {
return null;
}
}
public JSONArray getResultArray() throws JSONException {

View file

@ -4,10 +4,9 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import org.spongycastle.util.encoders.Base64;

View file

@ -2,20 +2,6 @@ package com.tangem.data.network;
public class Server {
/**
* https://tangem-webapp.appspot.com/
* https://tangem-services.appspot.com/
*/
public static class ApiTangem {
public static final String URL_TANGEM = ServerURL.API_TANGEM;
public static class Method {
static final String VERIFY = URL_TANGEM + "verify";
static final String VERIFY_AND_GET_INFO = URL_TANGEM + "card/verify-and-get-info";
static final String ARTWORK = URL_TANGEM + "card/artwork";
}
}
public static class ApiUpdateVersion {
public static final String URL_UPDATE_VERSION = ServerURL.API_UPDATE_VERSION;
@ -42,7 +28,7 @@ public class Server {
public static final String URL_INFURA = ServerURL.API_INFURA;
public static class Method {
static final String MAIN = URL_INFURA + "AfWg0tmYEX5Kukn2UkKV";
static final String MAIN = URL_INFURA + "613a0b14833145968b1f656240c7d245";
}
}

View file

@ -5,15 +5,7 @@ import android.support.annotation.NonNull;
import android.util.Log;
import com.tangem.App;
import com.tangem.data.network.model.CardVerifyAndGetInfo;
import com.tangem.data.network.model.RateInfoResponse;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.util.Util;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -36,7 +28,7 @@ public class ServerApiCommon {
public interface EstimateFeeListener {
void onSuccess(int blockCount, String estimateFeeResponse);
void onFail(String message);
void onFail(int blockCount, String message);
}
public void setEstimateFee(EstimateFeeListener listener) {
@ -71,106 +63,18 @@ public class ServerApiCommon {
estimateFeeListener.onSuccess(blockCount, response.body());
Log.i(TAG, "estimateFee onResponse " + response.code() + " " + response.body());
} else
estimateFeeListener.onFail(response.body());
estimateFeeListener.onFail(blockCount, response.body());
Log.e(TAG, "estimateFee onResponse " + response.code());
}
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
estimateFeeListener.onFail(t.getMessage());
estimateFeeListener.onFail(blockCount, t.getMessage());
Log.e(TAG, "estimateFee onFailure " + t.getMessage());
}
});
}
/**
* HTTP
* Card verify
*/
private CardVerifyAndGetInfoListener cardVerifyAndGetInfoListener;
public interface CardVerifyAndGetInfoListener {
void onSuccess(CardVerifyAndGetInfo.Response cardVerifyAndGetArtworkResponse);
void onFail(String message);
}
public void setCardVerifyAndGetInfoListener(CardVerifyAndGetInfoListener listener) {
cardVerifyAndGetInfoListener = listener;
}
public void cardVerifyAndGetInfo(TangemCard card) {
TangemApi tangemApi = App.getNetworkComponent().getRetrofitTangem().create(TangemApi.class);
List<CardVerifyAndGetInfo.Request.Item> requests = new ArrayList<>();
requests.add(new CardVerifyAndGetInfo.Request.Item(Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey())));
CardVerifyAndGetInfo.Request requestBody = new CardVerifyAndGetInfo.Request(requests);
Call<CardVerifyAndGetInfo.Response> call = tangemApi.getCardVerifyAndGetInfo(requestBody);
call.enqueue(new Callback<CardVerifyAndGetInfo.Response>() {
@Override
public void onResponse(@NonNull Call<CardVerifyAndGetInfo.Response> call, @NonNull Response<CardVerifyAndGetInfo.Response> response) {
if (response.code() == 200) {
CardVerifyAndGetInfo.Response cardVerifyAndGetArtworkResponse = response.body();
cardVerifyAndGetInfoListener.onSuccess(cardVerifyAndGetArtworkResponse);
Log.i(TAG, "cardVerifyAndGeInfo onResponse " + response.code());
} else {
cardVerifyAndGetInfoListener.onFail(String.valueOf(response.code()));
Log.e(TAG, "cardVerifyAndGetInfo onResponse " + response.code());
}
}
@Override
public void onFailure(@NonNull Call<CardVerifyAndGetInfo.Response> call, @NonNull Throwable t) {
cardVerifyAndGetInfoListener.onFail(t.getMessage());
Log.e(TAG, "cardVerifyAndGetInfo onFailure " + t.getMessage());
}
});
}
/**
* HTTP
* Last version request from GitHub
*/
private ArtworkListener artworkListener;
public interface ArtworkListener {
void onSuccess(String artworkId, InputStream inputStream, Date updateDate);
void onFail(String message);
}
public void setArtworkListener(ArtworkListener listener) {
artworkListener = listener;
}
public void requestArtwork(String artworkId, Date updateDate, TangemCard card) {
TangemApi tangemApi = App.getNetworkComponent().getRetrofitTangem().create(TangemApi.class);
Call<ResponseBody> call = tangemApi.getArtwork(artworkId, Util.bytesToHex(card.getCID()), Util.bytesToHex(card.getCardPublicKey()));
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
Log.i(TAG, "getArtwork onResponse " + response.code());
if (response.code() == 200) {
try {
ResponseBody body = response.body();
if (body != null) {
artworkListener.onSuccess(artworkId, body.byteStream(), updateDate);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
artworkListener.onFail(t.getMessage());
Log.e(TAG, "getArtwork onFailure " + t.getMessage());
}
});
}
/**
* HTTP
* Used in Crypto-currency course

View file

@ -3,11 +3,14 @@ package com.tangem.data.network;
import android.util.Log;
import com.tangem.App;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.data.Blockchain;
import com.tangem.domain.wallet.bch.BitcoinCashNode;
import com.tangem.domain.wallet.btc.BitcoinNode;
import com.tangem.domain.wallet.btc.BitcoinNodeTestNet;
import com.tangem.domain.wallet.ltc.LitecoinNode;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.wallet.R;
import java.io.BufferedReader;
import java.io.IOException;
@ -41,6 +44,17 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DefaultObserver;
import io.reactivex.schedulers.Schedulers;
/**
* Request processor for Electrum Api
* Every request live cycle:
* 1. In application create request and call {@link ServerApiElectrum}.electrumRequestData(..)
* 2. Try send every request for max 4 times,
* 3. If all 4 times fail call DefaultObserver<ElectrumRequest>.onError (defined in .electrumRequestData(..)) and than
* {@link ElectrumRequestDataListener}.onFail(...) callback
* Error can be acquired with {@link ElectrumRequest}.getError() method
* 4. If request network communication finished successfully then call DefaultObserver<ElectrumRequest>.onComplete (defined in .electrumRequestData) and than
* {@link ElectrumRequestDataListener}.onSuccess(...) callback
*/
public class ServerApiElectrum {
private static String TAG = ServerApiElectrum.class.getSimpleName();
@ -52,19 +66,49 @@ public class ServerApiElectrum {
private String host;
private int port;
public interface ElectrumRequestDataListener {
void onSuccess(ElectrumRequest electrumRequest);
private int requestsCount=0;
void onFail(String method);
public boolean isRequestsSequenceCompleted() {
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
return requestsCount <= 0;
}
/**
* Interface for notification every request result
*/
public interface ElectrumRequestDataListener {
/**
* Notify that request processing was successful
* @param electrumRequest - processed request containing received answer {@see electrumRequest.getAnswer() method}
*/
void onSuccess(ElectrumRequest electrumRequest);
/**
* Notify that request processing was successful
* @param electrumRequest - processed request containing occurred error {@see electrumRequest.getError() method}
*/
void onFail(ElectrumRequest electrumRequest);
}
/**
* Set notificaion listener
* @param listener
*/
public void setElectrumRequestData(ElectrumRequestDataListener listener) {
electrumRequestDataListener = listener;
}
public void electrumRequestData(TangemCard card, ElectrumRequest electrumRequest) {
/**
* Start process request
* @param ctx
* @param electrumRequest
*/
public void electrumRequestData(TangemContext ctx, ElectrumRequest electrumRequest) {
requestsCount++;
Log.i(TAG, String.format("New request[%d]: %s", requestsCount,electrumRequest.getMethod()));
Observable<ElectrumRequest> checkElectrumDataObserver = Observable.just(electrumRequest)
.doOnNext(electrumRequest1 -> doElectrumRequest(card, electrumRequest))
.doOnNext(electrumRequest1 -> doElectrumRequest(ctx, electrumRequest))
.flatMap(electrumRequest1 -> {
if (electrumRequest1.answerData == null) {
@ -81,36 +125,56 @@ public class ServerApiElectrum {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
checkElectrumDataObserver.subscribe(new DefaultObserver<ElectrumRequest>() {
//TODO remove onNext
@Override
public void onNext(ElectrumRequest v) {
if (electrumRequest.answerData != null) {
electrumRequestDataListener.onSuccess(electrumRequest);
// Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null");
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null");
} else {
electrumRequestDataListener.onFail(electrumRequest.getMethod());
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext == null");
}
}
@Override
public void onError(Throwable e) {
electrumRequestDataListener.onFail(electrumRequest.getMethod());
requestsCount--;
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onError " + e.getMessage());
Log.e(TAG, String.format("%d requests left in processing",requestsCount));
electrumRequest.setError(ctx.getString(R.string.cannot_obtain_data_from_blockchain));
//setErrorOccurred(e.getMessage());//;
electrumRequestDataListener.onFail(electrumRequest);
}
/**
* Called after completion request processing
*/
@Override
public void onComplete() {
// Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete");
requestsCount--;
if (electrumRequest.answerData != null) {
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData!=null");
} else {
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData==null");
}
Log.e(TAG, String.format("%d requests left in processing",requestsCount));
if (electrumRequest.answerData != null && electrumRequest.getError()==null) {
electrumRequestDataListener.onSuccess(electrumRequest);
} else {
// if( error==null || error.isEmpty() ) setErrorOccurred(ctx.getString(R.string.cannot_obtain_data_from_blockchain));
electrumRequestDataListener.onFail(electrumRequest);
}
}
});
}
private List<ElectrumRequest> doElectrumRequest(TangemCard card, ElectrumRequest electrumRequest) {
private void doElectrumRequest(TangemContext ctx, ElectrumRequest electrumRequest) {
String host;
int port;
String proto;
if (card.getBlockchain() == Blockchain.BitcoinTestNet) {
electrumRequest.setError(null);
// todo - get available URL list from coinEngine, remove if( ctx.getBlockchain()==...)
if (ctx.getBlockchain() == Blockchain.BitcoinTestNet) {
BitcoinNodeTestNet bitcoinNodeTestNet = BitcoinNodeTestNet.values()[new Random().nextInt(BitcoinNodeTestNet.values().length)];
host = bitcoinNodeTestNet.getHost();
port = bitcoinNodeTestNet.getPort();
@ -118,48 +182,59 @@ public class ServerApiElectrum {
this.host = host;
this.port = port;
return doElectrumRequestTcp(electrumRequest, host, port);
} else if (card.getBlockchain() == Blockchain.BitcoinCash) {
doElectrumRequestTcp(electrumRequest, host, port);
} else if (ctx.getBlockchain() == Blockchain.BitcoinCash) {
BitcoinCashNode bitcoinCashNode = BitcoinCashNode.values()[new Random().nextInt(BitcoinCashNode.values().length)];
host = bitcoinCashNode.getHost();
port = bitcoinCashNode.getPort();
proto = bitcoinCashNode.getProto();
this.host = host;
this.port = port;
return doElectrumRequestTcp(electrumRequest, host, port);
if (proto.equals("tcp")) {
doElectrumRequestTcp(electrumRequest, host, port);
} else {
doElectrumRequestSsl(electrumRequest, host, port);
}
} else if (card.getBlockchain() == Blockchain.Bitcoin) {
} else if (ctx.getBlockchain() == Blockchain.Bitcoin) {
BitcoinNode bitcoinNode = BitcoinNode.values()[new Random().nextInt(BitcoinNode.values().length)];
host = bitcoinNode.getHost();
port = bitcoinNode.getPort();
proto = bitcoinNode.getProto();
this.host = host;
this.port = port;
if (proto.equals("tcp")) {
this.host = host;
this.port = port;
return doElectrumRequestTcp(electrumRequest, host, port);
doElectrumRequestTcp(electrumRequest, host, port);
} else {
this.host = host;
this.port = port;
doElectrumRequestSsl(electrumRequest, host, port);
}
} else if (ctx.getBlockchain() == Blockchain.Litecoin) {
LitecoinNode litecoinNode = LitecoinNode.values()[new Random().nextInt(LitecoinNode.values().length)];
host = litecoinNode.getHost();
port = litecoinNode.getPort();
proto = litecoinNode.getProto();
return doElectrumRequestSsl(electrumRequest, host, port);
this.host = host;
this.port = port;
if (proto.equals("tcp")) {
doElectrumRequestTcp(electrumRequest, host, port);
} else {
doElectrumRequestSsl(electrumRequest, host, port);
}
}
return null;
}
private List<ElectrumRequest> doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) {
List<ElectrumRequest> result = new ArrayList<>();
Collections.addAll(result, electrumRequest);
private void doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) {
try {
Socket socket = App.getNetworkComponent().getSocket();
socket.setSoTimeout(3000);
Log.i(TAG, "Start process "+electrumRequest.getMethod()+" @ "+host + ":" + port);
socket.connect(new InetSocketAddress(InetAddress.getByName(host), port));
Log.i(TAG, host + " " + port);
try {
OutputStream os = socket.getOutputStream();
OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8");
@ -176,37 +251,46 @@ public class ServerApiElectrum {
if (electrumRequest.answerData != null) {
Log.i(TAG, ">> " + electrumRequest.answerData);
} else {
electrumRequest.error = "No answer from server";
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_answer));
Log.i(TAG, ">> <NULL>");
}
} catch (ConnectException e) {
e.printStackTrace();
electrumRequestDataListener.onFail(e.getMessage());
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
//e.printStackTrace();
//electrumRequestDataListener.onFail(e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection));
Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
} finally {
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " CLOSE");
socket.close();
Log.i(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " socket.close");
try {
if( socket.isConnected() ) socket.close();
}
catch (Exception e)
{
e.printStackTrace();
Log.e(TAG,"Can't close socket");
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error));
}
}
} catch (IOException e) {
e.printStackTrace();
electrumRequestDataListener.onFail(e.getMessage());
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " IOException " + e.getMessage());
//e.printStackTrace();
//electrumRequestDataListener.onFail(e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error));
Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage());
}
return result;
}
private List<ElectrumRequest> doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) {
private void doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) {
try {
// create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@ -234,8 +318,8 @@ public class ServerApiElectrum {
Collections.addAll(result, electrumRequest);
try {
sslSocket = (SSLSocket) sf.createSocket(host, port);
Log.i(TAG, host + " " + port);
sslSocket = (SSLSocket) sf.createSocket(host, port);
try {
OutputStream os = sslSocket.getOutputStream();
OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8");
@ -250,34 +334,41 @@ public class ServerApiElectrum {
if (electrumRequest.answerData != null) {
Log.i(TAG, ">> " + electrumRequest.answerData);
} else {
electrumRequest.error = "No answer from server";
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_answer));
Log.i(TAG, ">> <NULL>");
}
} catch (ConnectException e) {
e.printStackTrace();
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection));
Log.e(TAG, "doElectrumRequestSsl " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
} finally {
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " CLOSE");
sslSocket.close();
Log.i(TAG, "doElectrumRequestSsl " + electrumRequest.getMethod() + " socket.close");
try {
if( sslSocket.isConnected() ) sslSocket.close();
}
catch (Exception e)
{
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error));
e.printStackTrace();
Log.e(TAG, "Can't close ssl socket");
}
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " IOException " + e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error));
Log.e(TAG, "doElectrumRequestSsl " + electrumRequest.getMethod() + " IOException " + e.getMessage());
}
return result;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain));
Log.e(TAG, e.getMessage());
}
return null;
}
public String getValidationNodeDescription() {
return "Electrum, " + host + ":" + String.valueOf(port);
}
}

View file

@ -31,6 +31,13 @@ public class ServerApiInfura {
public static final String INFURA_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction";
public static final String INFURA_ETH_GAS_PRICE = "eth_gasPrice";
private int requestsCount=0;
public boolean isRequestsSequenceCompleted() {
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
return requestsCount <= 0;
}
private InfuraBodyListener infuraBodyListener;
public interface InfuraBodyListener {
@ -44,6 +51,7 @@ public class ServerApiInfura {
}
public void infura(String method, int id, String wallet, String contract, String tx) {
requestsCount++;
InfuraApi infuraApi = App.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class);
InfuraBody infuraBody;
@ -77,6 +85,7 @@ public class ServerApiInfura {
@Override
public void onResponse(@NonNull Call<InfuraResponse> call, @NonNull Response<InfuraResponse> response) {
if (response.code() == 200) {
requestsCount--;
infuraBodyListener.onSuccess(method, response.body());
Log.i(TAG, "infura " + method + " onResponse " + response.code());
} else {

View file

@ -3,7 +3,7 @@ package com.tangem.data.network;
class ServerURL {
static final String API_TANGEM = "https://verify.tangem.com/";
static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/";
static final String API_INFURA = "https://mainnet.infura.io/";
static final String API_ESTIMATEFEE = " https://estimatefee.com/";
static final String API_INFURA = "https://mainnet.infura.io/v3/";
static final String API_ESTIMATEFEE = "https://estimatefee.com/";
static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/";
}

View file

@ -1,100 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
public class CreateNewWalletTask extends Thread {
public static final String TAG = CreateNewWalletTask.class.getSimpleName();
private Context mContext;
private TangemCard mCard;
private NfcManager mNfcManager;
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public CreateNewWalletTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mCard = card;
mContext = context;
mNfcManager = nfcManager;
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start create new wallet --]");
if (isCancelled) return;
protocol.run_VerifyCard();
Log.i(TAG, "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName());
mNotifications.onReadProgress(protocol, 30);
if (isCancelled) return;
// if (mCard.getPauseBeforePIN2() > 0) {
// mNotifications.onReadWait(mCard.getPauseBeforePIN2());
// }
// try {
protocol.run_CreateWallet(PINStorage.getPIN2());
// } finally {
// mNotifications.onReadWait(0);
// }
mNotifications.onReadProgress(protocol, 60);
if (isCancelled) return;
protocol.run_Read();
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish create new wallet --]");
mNotifications.onReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,102 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
public class PurgeTask extends Thread {
public static final String TAG = PurgeTask.class.getSimpleName();
private String txOutAddress;
private Context mContext;
private TangemCard mCard;
private NfcManager mNfcManager;
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public PurgeTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mCard = card;
mContext = context;
mNfcManager = nfcManager;
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
// for Samsung's bugs - Workaround for the Samsung Galaxy S5 (since the first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start purge --]");
if (isCancelled) return;
if (mCard.getPauseBeforePIN2() > 0) {
mNotifications.onReadWait(mCard.getPauseBeforePIN2());
}
// try {
protocol.run_PurgeWallet(PINStorage.getPIN2());
// } finally {
// mNotifications.onReadWait(0);
// }
mNotifications.onReadProgress(protocol, 50);
protocol.run_Read();
mNotifications.onReadProgress(protocol, 100);
if (isCancelled)
return;
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
mNotifications.onReadWait(0);
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish purge --]");
mNotifications.onReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,173 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.util.Util;
import java.util.ArrayList;
public class ReadCardInfoTask extends Thread {
public static final String TAG = ReadCardInfoTask.class.getSimpleName();
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
private Context mContext;
private NfcManager mNfcManager;
// this fields are static to optimize process when need enter pin and scan card again
private static ArrayList<String> lastRead_UnsuccessfullPINs = new ArrayList<>();
private static TangemCard.EncryptionMode lastRead_Encryption = null;
private static String lastRead_UID;
public static void resetLastReadInfo() {
lastRead_UID="";
lastRead_Encryption=null;
lastRead_UnsuccessfullPINs.clear();
}
public ReadCardInfoTask(Context context, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mNfcManager = nfcManager;
// ReadCardInfoTask.lastRead_UID = lastRead_UID;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mNotifications);
mNotifications.onReadStart(protocol);
try {
mNotifications.onReadProgress(protocol, 5);
byte[] UID = mIsoDep.getTag().getId();
String sUID = Util.byteArrayToHexString(UID);
if (!lastRead_UID.equals(sUID)) {
resetLastReadInfo();
}
Log.i(TAG, "[-- Start read card info --]");
if (isCancelled) return;
protocol.setPIN(PINStorage.getDefaultPIN());
protocol.clearReadResult();
if (lastRead_Encryption == null) {
Log.i(TAG, "Try get supported encryption mode");
protocol.run_GetSupportedEncryption();
} else {
Log.i(TAG, "Use already defined encryption mode: " + lastRead_Encryption.name());
protocol.getCard().encryptionMode = lastRead_Encryption;
}
if (protocol.haveReadResult()) {
//already have read result (obtained while get supported encryption), only read issuer data and define offline balance
protocol.parseReadResult();
protocol.run_ReadWriteIssuerData();
mNotifications.onReadProgress(protocol, 60);
PINStorage.setLastUsedPIN(protocol.getCard().getPIN());
} else {
//don't have read result - may be don't get supported encryption on this try, need encryption or need another PIN
if (lastRead_Encryption == null) {
// we try get supported encryption on this time
lastRead_Encryption = protocol.getCard().encryptionMode;
if (protocol.getCard().encryptionMode == TangemCard.EncryptionMode.None) {
// default pin not accepted
lastRead_UnsuccessfullPINs.add(PINStorage.getDefaultPIN());
}
}
boolean pinFound = false;
for (String PIN : PINStorage.getPINs()) {
Log.e(TAG, "PIN: " + PIN);
boolean skipPin = false;
for (int i = 0; i < lastRead_UnsuccessfullPINs.size(); i++) {
if (lastRead_UnsuccessfullPINs.get(i).equals(PIN)) {
skipPin = true;
break;
}
}
if (skipPin) {
Log.e(TAG, "Skip PIN - already checked before");
continue;
}
try {
protocol.setPIN(PIN);
if (protocol.getCard().encryptionMode != TangemCard.EncryptionMode.None) {
protocol.CreateProtocolKey();
}
protocol.run_Read();
mNotifications.onReadProgress(protocol, 60);
PINStorage.setLastUsedPIN(PIN);
pinFound = true;
protocol.getCard().setPIN(PIN);
break;
} catch (CardProtocol.TangemException_InvalidPIN e) {
Log.e(TAG, e.getMessage());
lastRead_UnsuccessfullPINs.add(PIN);
}
}
if (!pinFound) {
throw new CardProtocol.TangemException_InvalidPIN("No valid PIN found!");
}
}
protocol.run_CheckPIN2isDefault();
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish read card info --]");
mNotifications.onReadFinish(protocol);
}
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
mNfcManager.notifyReadResult(false);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,159 +0,0 @@
package com.tangem.data.nfc;
import android.app.Activity;
import android.content.Intent;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.CoinEngineFactory;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.presentation.activity.SendTransactionActivity;
import com.tangem.presentation.activity.SignPaymentActivity;
import com.tangem.domain.wallet.BTCUtils;
import java.io.IOException;
public class SignPaymentTask extends Thread {
public static final String TAG = SignPaymentTask.class.getSimpleName();
private CoinEngine.Amount txAmount;
private CoinEngine.Amount txFee;
private Boolean txIncFee = true;
public void SetTransactionValue(CoinEngine.Amount amount, CoinEngine.Amount fee, Boolean incfee) {
txAmount = amount;
txFee = fee;
txIncFee = incfee;
}
private String txOutAddress;
private Activity mContext;
private TangemContext mCtx;
private NfcManager mNfcManager;
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public SignPaymentTask(Activity context, TangemContext ctx, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications, CoinEngine.Amount amount, CoinEngine.Amount fee, Boolean IncFee, String outAddress) {
mCtx=ctx;
mContext = context;
mNfcManager = nfcManager;
mIsoDep = isoDep;
mNotifications = notifications;
txOutAddress = outAddress;
SetTransactionValue(amount, fee, IncFee);
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCtx.getCard(), mNotifications);
mNotifications.onReadStart(protocol);
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start sign payment --]");
if (isCancelled) return;
protocol.run_Read(false);
protocol.run_VerifyCard();
Log.i(TAG, "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName());
mNotifications.onReadProgress(protocol, 30);
if (isCancelled) return;
//
// if (mCard.getBlockchain() == Blockchain.Ethereum) {
// SignETH_TX(protocol);
// } else {
// SignBTC_TX(protocol);
// }
CoinEngine engine = CoinEngineFactory.INSTANCE.create(mCtx);
if (engine != null) {
if (mCtx.getCard().getPauseBeforePIN2() > 0) {
mNotifications.onReadWait(mCtx.getCard().getPauseBeforePIN2());
}
byte[] tx = null;
try {
tx = engine.sign(txFee, txAmount, txIncFee, txOutAddress, protocol);
}
catch (IOException e) {
e.printStackTrace();
protocol.setError(e);
} finally {
mNotifications.onReadWait(0);
}
if (tx != null) {
// TODO - move to engine!!!
String txStr = BTCUtils.toHex(tx);
if (mCtx.getBlockchain() == Blockchain.Ethereum || mCtx.getBlockchain() == Blockchain.EthereumTestNet || mCtx.getBlockchain() == Blockchain.Token) {
txStr = String.format("0x%s", txStr);
}
Intent intent = new Intent(mContext, SendTransactionActivity.class);
mCtx.saveToIntent(intent);
intent.putExtra(SendTransactionActivity.EXTRA_TX, txStr);
mContext.startActivityForResult(intent, SignPaymentActivity.REQUEST_CODE_SEND_PAYMENT);
}
}
mNotifications.onReadProgress(protocol, 100);
if (isCancelled) return;
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
mNotifications.onReadWait(0);
}
} catch (CardProtocol.TangemException_InvalidPIN e) {
e.printStackTrace();
protocol.setError(e);
} catch (CardProtocol.TangemException_WrongAmount e) {
e.printStackTrace();
protocol.setError(e);
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish sign payment --]");
mNotifications.onReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,106 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
public class SwapPINTask extends Thread {
public static final String TAG = SwapPINTask.class.getSimpleName();
private Context mContext;
private TangemCard mCard;
private NfcManager mNfcManager;
private String newPIN, newPIN2;
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
public SwapPINTask(Context context, TangemCard card, NfcManager nfcManager, String newPIN, String newPIN2, IsoDep isoDep, CardProtocol.Notifications notifications) {
this.newPIN = newPIN;
this.newPIN2 = newPIN2;
mCard = card;
mContext = context;
mNfcManager = nfcManager;
mIsoDep = isoDep;
mNotifications = notifications;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start swap pin --]");
if (isCancelled) return;
if (mCard.getPauseBeforePIN2() > 0) {
mNotifications.onReadWait(mCard.getPauseBeforePIN2());
}
// try {
protocol.run_SetPIN(PINStorage.getPIN2(), newPIN, newPIN2, false);
protocol.setPIN(newPIN);
mCard.setPIN(newPIN);
// } finally {
// mNotifications.onReadWait(0);
// }
mNotifications.onReadProgress(protocol, 50);
protocol.run_Read();
mNotifications.onReadProgress(protocol, 100);
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
mNotifications.onReadWait(0);
}
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish purge --]");
mNotifications.onReadFinish(protocol);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,116 +0,0 @@
package com.tangem.data.nfc;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.Firmwares;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
import java.util.Arrays;
/**
* Created by dvol on 04.02.2018.
*/
public class VerifyCardTask extends Thread {
public static final String TAG = VerifyCardTask.class.getSimpleName();
private IsoDep mIsoDep;
private CardProtocol.Notifications mNotifications;
private boolean isCancelled = false;
private Context mContext;
private TangemCard mCard;
private NfcManager mNfcManager;
public VerifyCardTask(Context context, TangemCard card, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) {
mCard = card;
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mNfcManager = nfcManager;
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
int timeout = mIsoDep.getTimeout();
mIsoDep.connect();
mIsoDep.close();
mIsoDep.connect();
mIsoDep.setTimeout(timeout);
try {
CardProtocol protocol = new CardProtocol(mContext, mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
mNotifications.onReadProgress(protocol, 5);
Log.i(TAG, "[-- Start verify card --]");
if (isCancelled) return;
String PIN = mCard.getPIN();
protocol.setPIN(PIN);
protocol.run_Read(false);
PINStorage.setLastUsedPIN(PIN);
mNotifications.onReadProgress(protocol, 20);
if (isCancelled) return;
protocol.run_VerifyCard();
mNotifications.onReadProgress(protocol, 50);
Log.i(TAG, "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName());
if (isCancelled) return;
if (protocol.getCard().getStatus() == TangemCard.Status.Loaded) {
protocol.run_CheckWalletWithSignatureVerify();
mNotifications.onReadProgress(protocol, 80);
}
if (isCancelled) return;
Firmwares.VerifyCodeRecord record=Firmwares.selectRandomVerifyCodeBlock(mCard.getFirmwareVersion());
if (isCancelled) return;
if( record!=null ) {
byte[] returnedDigest = protocol.run_VerifyCode(record.hashAlg, record.blockIndex, record.blockCount, record.challenge);
mCard.setCodeConfirmed(Arrays.equals(returnedDigest, record.digest));
}else{
mCard.setCodeConfirmed(null);
}
mNotifications.onReadProgress(protocol, 90);
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, "[-- Finish verify card --]");
mNotifications.onReadFinish(protocol);
}
} finally {
mNfcManager.ignoreTag(mIsoDep.getTag());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -2,14 +2,9 @@ package com.tangem.di
import android.app.Activity
import android.nfc.Tag
import android.os.Bundle
import com.tangem.domain.wallet.CoinData
import com.tangem.domain.wallet.TangemCard
import com.tangem.presentation.activity.EmptyWalletActivity
import com.tangem.presentation.activity.LoadedWalletActivity
import com.tangem.presentation.activity.MainActivity
import com.tangem.presentation.activity.VerifyCardActivity
import com.tangem.presentation.fragment.LoadedWallet
import com.tangem.Constant
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.activity.*
class Navigator {
@ -17,28 +12,68 @@ class Navigator {
context.startActivity(MainActivity.callingIntent(context))
}
fun showLoadedWallet(context: Activity, lastTag: Tag, cardInfo: Bundle) {
context.startActivityForResult(LoadedWalletActivity.callingIntent(context, lastTag, cardInfo), MainActivity.REQUEST_CODE_SHOW_CARD_ACTIVITY)
fun showLogo(context: Activity, autoHide: Boolean) {
context.startActivity(LogoActivity.callingIntent(context, autoHide))
}
fun showEmptyWallet(context: Activity) {
context.startActivity(EmptyWalletActivity.callingIntent(context))
fun showPinSave(context: Activity, hasPin2: Boolean) {
context.startActivity(PinSaveActivity.callingIntent(context, hasPin2))
}
fun showVerifyCard(context: Activity, card: TangemCard, coinData: CoinData, message: String, error: String) {
context.startActivityForResult(VerifyCardActivity.callingIntent(context, card, coinData, message, error), LoadedWallet.REQUEST_CODE_VERIFY_CARD)
fun showPinRequest(context: Activity, mode: String) {
context.startActivityForResult(PinRequestActivity.callingIntent(context, mode), Constant.REQUEST_CODE_ENTER_PIN_ACTIVITY)
}
fun showPinSwap(context: Activity) {
fun showQrScanActivity(context: Activity, requestCode: Int) {
context.startActivityForResult(QrScanActivity.callingIntent(context), requestCode)
}
// fun showPreparePayment(context: Activity) {
//
// }
//
// fun showCreateNewWallet(context: Activity) {
//
// }
fun showPinRequestRequestPin(context: Activity, mode: String, ctx: TangemContext, newPin: String) {
context.startActivityForResult(PinRequestActivity.callingIntentRequestPin(context, mode, ctx, newPin), Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
fun showPinRequestRequestPin2(context: Activity, mode: String, ctx: TangemContext, newPin2: String) {
context.startActivityForResult(PinRequestActivity.callingIntentRequestPin2(context, mode, ctx, newPin2), Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
fun showPinRequestRequestPin2(context: Activity, mode: String, ctx: TangemContext) {
context.startActivityForResult(PinRequestActivity.callingIntentRequestPin2(context, mode, ctx), Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
fun showPinRequestConfirmNewPin(context: Activity, mode: String, newPin: String) {
context.startActivityForResult(PinRequestActivity.callingIntentConfirmPin(context, mode, newPin), Constant.REQUEST_CODE_ENTER_NEW_PIN)
}
fun showPinRequestConfirmNewPin2(context: Activity, mode: String, newPin2: String) {
context.startActivityForResult(PinRequestActivity.callingIntentConfirmPin2(context, mode, newPin2), Constant.REQUEST_CODE_ENTER_NEW_PIN2)
}
fun showLoadedWallet(context: Activity, lastTag: Tag, ctx: TangemContext) {
context.startActivityForResult(LoadedWalletActivity.callingIntent(context, lastTag, ctx), Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY)
}
fun showEmptyWallet(context: Activity, ctx: TangemContext) {
context.startActivity(EmptyWalletActivity.callingIntent(context, ctx))
}
fun showVerifyCard(context: Activity, ctx: TangemContext) {
context.startActivityForResult(VerifyCardActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_VERIFY_CARD)
}
fun showPinSwap(context: Activity, newPIN: String, newPIN2: String) {
context.startActivityForResult(PinSwapActivity.callingIntent(context, newPIN, newPIN2), Constant.REQUEST_CODE_SWAP_PIN)
}
fun showPurge(context: Activity, ctx: TangemContext) {
context.startActivityForResult(PurgeActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_PURGE)
}
fun showPreparePayment(context: Activity, ctx: TangemContext) {
context.startActivityForResult(PreparePaymentActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_SEND_PAYMENT)
}
fun showCreateNewWallet(context: Activity, ctx: TangemContext) {
context.startActivityForResult(CreateNewWalletActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY)
}
}

View file

@ -4,6 +4,9 @@ import com.tangem.presentation.activity.EmptyWalletActivity;
import com.tangem.presentation.activity.LoadedWalletActivity;
import com.tangem.presentation.activity.LogoActivity;
import com.tangem.presentation.activity.MainActivity;
import com.tangem.presentation.activity.PrepareCryptonitOtherApiWithdrawalActivity;
import com.tangem.presentation.activity.PrepareKrakenWithdrawalActivity;
import com.tangem.presentation.activity.PreparePaymentActivity;
import com.tangem.presentation.activity.VerifyCardActivity;
import javax.inject.Singleton;
@ -20,6 +23,12 @@ public interface NavigatorComponent {
void inject(MainActivity activity);
void inject(PreparePaymentActivity activity);
void inject(PrepareCryptonitOtherApiWithdrawalActivity activity);
void inject(PrepareKrakenWithdrawalActivity activity);
void inject(LoadedWalletActivity activity);
void inject(VerifyCardActivity activity);

View file

@ -20,9 +20,6 @@ public interface NetworkComponent {
@Named(Server.ApiEstimatefee.URL_ESTIMATEFEE)
Retrofit getRetrofitEstimatefee();
@Named(Server.ApiTangem.URL_TANGEM)
Retrofit getRetrofitTangem();
@Named(Server.ApiCoinmarket.URL_COINMARKET)
Retrofit getRetrofitCoinmarketcap();

View file

@ -41,17 +41,6 @@ class NetworkModule {
.build();
}
@Singleton
@Provides
@Named(Server.ApiTangem.URL_TANGEM)
Retrofit provideRetrofitTangem() {
return new Retrofit.Builder()
.baseUrl(Server.ApiTangem.URL_TANGEM)
.addConverterFactory(GsonConverterFactory.create())
.client(createOkHttpClient())
.build();
}
@Singleton
@Provides
@Named(Server.ApiUpdateVersion.URL_UPDATE_VERSION)

View file

@ -1,13 +0,0 @@
package com.tangem.domain
enum class LitecoinNode(val host: String, val port: Int) {
n1("ltc.rentonisk.com", 50001),
n2("backup.electrum-ltc.org", 50001),
n3("node.ispol.sk", 50003),
n4("electrum-ltc.wilv.in", 50001),
n5("ltc01.knas.systems", 50003),
n6("electrumx.nmdps.net", 9433),
n7("e-3.claudioboxx.com", 50003),
n8("electrum.ltc.xurious.com", 50001),
n9("e-1.claudioboxx.com", 50003),
}

View file

@ -1,27 +0,0 @@
package com.tangem.domain.cardReader;
/**
* Created by dvol on 07.03.2018.
*/
public class SettingsMask {
public static final int IsReusable = 0x0001;
public static final int UseActivation = 0x0002;
public static final int UseBlock = 0x0008;
public static final int AllowSwapPIN = 0x0010;
public static final int AllowSwapPIN2 = 0x0020;
public static final int UseCVC = 0x0040;
public static final int ForbidDefaultPIN = 0x0080;
public static final int UseOneCommandAtTime = 0x0100;
public static final int UseNDEF = 0x0200;
public static final int UseDynamicNDEF = 0x0400;
public static final int SmartSecurityDelay = 0x0800;
public static final int Protocol_AllowUnencrypted = 0x1000;
public static final int Protocol_AllowStaticEncryption = 0x2000;
public static final int ProtectIssuerDataAgainstReplay = 0x4000;
}

View file

@ -0,0 +1,345 @@
package com.tangem.domain.wallet;
/**
* Created by Ilia on 29.09.2017.
*/
import android.util.Log;
import com.tangem.domain.wallet.btc.BitcoinException;
import com.tangem.domain.wallet.btc.BitcoinOutputStream;
import com.tangem.domain.wallet.btc.BtcData;
import com.tangem.util.CryptoUtil;
import com.tangem.util.FormatUtil;
import com.tangem.tangemcard.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
public final class BCHUtils {
static final BigInteger LARGEST_PRIVATE_KEY = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);//SECP256K1_N
public static final long MIN_FEE_PER_KB = 10000;
public static final long MAX_ALLOWED_FEE = FormatUtil.parseValue("0.1");
public static final long MIN_PRIORITY_FOR_NO_FEE = 57600000;
public static final long MIN_MIN_OUTPUT_VALUE_FOR_NO_FEE = 10000000L;
public static final int MAX_TX_LEN_FOR_NO_FEE = 10000;
public static final float EXPECTED_BLOCKS_PER_DAY = 144.0f;//(expected confirmations per day)
public static String toHex(byte[] bytes) {
if (bytes == null) {
return "";
}
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] buildTXForSign(String myAddress, String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int currentInputPos, long amount, long change) throws BitcoinException, IOException {
int inputPos = currentInputPos;
byte[] tx = buildPreimage(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
return tx;
}
public static byte[] buildTXForSend(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, long amount, long change) throws BitcoinException, IOException {
int inputPos = -1;
byte[] tx = buildBodyTX(outputAddress, changeAddress, unspentOutputs, inputPos, amount, change);
return tx;
}
//BIP 143 as reference + script length added
public static byte[] buildPreimage(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
//nVersion of the transaction (4-byte little endian)
BitcoinOutputStream forSign = new BitcoinOutputStream();
//forSign.writeInt32(0x01);
forSign.write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//inputCount
byte inputCount = (byte) unspentOutputs.size();
//hashPrevouts (32-byte hash)
ByteArrayOutputStream prevouts = new ByteArrayOutputStream();
for (int i = 0; i < inputCount; ++i) {
UnspentOutputInfo outPut = unspentOutputs.get(i);
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Little-endian txID
byte[] txIndex = BCHUtils.reverse(Util.intToByteArray4(outPut.outputIndex));//Little-endian outputIndex
prevouts.write(txHash);
prevouts.write(txIndex);
}
byte[] hashPrevouts = CryptoUtil.doubleSha256(prevouts.toByteArray());
forSign.write(hashPrevouts);
//hashSequence (32-byte hash), ffffffff only
ByteArrayOutputStream sequences = new ByteArrayOutputStream();
for (int i = 0; i < inputCount; ++i) {
sequences.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
}
byte[] hashSequence = CryptoUtil.doubleSha256(sequences.toByteArray());
forSign.write(hashSequence);
//outpoint (32-byte hash + 4-byte little endian)
UnspentOutputInfo outPut = unspentOutputs.get(inputPos);
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Little-endian txID
byte[] txIndex = BCHUtils.reverse(Util.intToByteArray4(outPut.outputIndex));//Little-endian outputIndex
forSign.write(txHash);
forSign.write(txIndex);
//scriptCode of the input (serialized as scripts inside CTxOuts)
byte[] scriptCode = Transaction.Script.buildOutput(changeAddress).bytes; //build change out
byte[] scriptLength = Util.intToByteArray(scriptCode.length);
forSign.write(scriptLength);
forSign.write(scriptCode);
//value of the output spent by this input (8-byte little endian)
byte[] outValue = BCHUtils.reverse(Util.longToByteArray8(outPut.value));
forSign.write(outValue);
//nSequence of the input (4-byte little endian), ffffffff only
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff});
//hashOutputs (32-byte hash)
ByteArrayOutputStream outputs = new ByteArrayOutputStream();
byte[] sendAmount = BCHUtils.reverse(Util.longToByteArray8(amount));
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
byte[] sendLength = Util.intToByteArray(sendScript.length);
outputs.write(sendAmount);
outputs.write(sendLength);
outputs.write(sendScript);
//output for change (if any)
if (change != 0) {
byte[] changeAmount = BCHUtils.reverse(Util.longToByteArray8(change));
byte[] changeScript = Transaction.Script.buildOutput(changeAddress).bytes; //build change out
byte[] changeLength = Util.intToByteArray(changeScript.length);
outputs.write(changeAmount);
outputs.write(changeLength);
outputs.write(changeScript);
}
byte[] hashOutputs = CryptoUtil.doubleSha256(outputs.toByteArray());
forSign.write(hashOutputs);
//nLocktime of the transaction (4-byte little endian)
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
//sighash type of the signature (4-byte little endian)
forSign.write(new byte[]{0x41, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
Log.e("Sign_TX_Body", BCHUtils.toHex(rawData));
return rawData;
}
public static byte[] buildBodyTX(String outputAddress, String changeAddress, ArrayList<UnspentOutputInfo> unspentOutputs, int inputPos, long amount, long change) throws BitcoinException, IOException {
//0200000000
BitcoinOutputStream forSign = new BitcoinOutputStream();
//forSign.writeInt32(0x01);
forSign.write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//01
byte inputCount = (byte) unspentOutputs.size();
forSign.write(inputCount); // input count
//hex str hash prev btc
for (int i = 0; i < inputCount; ++i) {
UnspentOutputInfo outPut = unspentOutputs.get(i);
int outputIndex = outPut.outputIndex;
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(outPut.txHashForBuild));//Sha256Hash.hash(rawTxByte);
forSign.write(txHash);
forSign.writeInt32(outputIndex); //output index in prev tx
if (inputPos == -1 || i == inputPos) {
// hex str 1976a914....88ac
forSign.write((byte) outPut.scriptForBuild.length);
forSign.write(outPut.scriptForBuild);
} else {
forSign.write(0x00);
}
//ffffffff
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence
}
//02
byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount
forSign.write(outputCount);
//8 bytes
forSign.writeInt64(amount); //amount
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
//hex str 1976a914....88ac
forSign.write((byte) sendScript.length);
forSign.write(sendScript);
if (change != 0) {
//8 bytes
forSign.writeInt64(change); // change
//hex str 1976a914....88ac
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
forSign.write((byte) chancheScript.length);
forSign.write(chancheScript);
}
//00000000
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
//forSign.write(new byte[]{0x01, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
Log.e("Sign_TX_Body", BCHUtils.toHex(rawData));
return rawData;
}
public static byte[] buildBodyTX(String outputAddress, String changeAddress, int outputIndex, String prevID, long amount, long change, byte[] script) throws BitcoinException, IOException {
//0200000000
BitcoinOutputStream forSign = new BitcoinOutputStream();
forSign.writeInt32(0x01);//write(new byte[]{0x02, 0x00, 0x00, 0x00}); // version
//01
byte inputCount = 1;
forSign.write(inputCount); // input count
//hex str hash prev btc
byte[] txHash = BCHUtils.reverse(Util.hexToBytes(prevID));//Sha256Hash.hash(rawTxByte);
forSign.write(txHash); //previos tx hash
//00000000
//byte indexOutput = outputIndex;
forSign.writeInt32(outputIndex/*indexOutput*/); //output index in prev tx
//forSign.write(0x00);
// hex str 1976a914....88ac
forSign.write((byte) script.length);
forSign.write(script);
//ffffffff
forSign.write(new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); // sequence
//02
byte outputCount = (byte) ((change == 0) ? 1 : 2); // outputCount
forSign.write(outputCount);
//8 bytes
forSign.writeInt64(amount); //amount
byte[] sendScript = Transaction.Script.buildOutput(outputAddress).bytes; // build out
//hex str 1976a914....88ac
forSign.write((byte) sendScript.length);
forSign.write(sendScript);
if (change != 0) {
//8 bytes
forSign.writeInt64(change); // change
//hex str 1976a914....88ac
byte[] chancheScript = Transaction.Script.buildOutput(changeAddress).bytes; //build out
forSign.write((byte) chancheScript.length);
forSign.write(chancheScript);
}
//00000000
forSign.write(new byte[]{0x00, 0x00, 0x00, 0x00});
byte[] rawData = forSign.toByteArray();
//Log.e("Sign_TX_Body", BCHUtils.toHex(rawData));
return rawData;
}
public static ArrayList<UnspentOutputInfo> getOutputs(List<BtcData.UnspentTransaction> rawTxList, byte[] outputScriptWeAreAbleToSpend) throws BitcoinException {
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
for (BtcData.UnspentTransaction current : rawTxList) {
byte[] rawTxByte = BCHUtils.fromHex(current.Raw);
if (rawTxByte == null || current.Raw.isEmpty()) {
continue;
}
Transaction baseTx = new Transaction(rawTxByte);
if (baseTx.inputs.length == 0 || baseTx.outputs.length == 0)
throw new IllegalArgumentException("Unable to decode given transaction");
byte[] txHash = BCHUtils.reverse(CryptoUtil.doubleSha256(rawTxByte));
String txHashForBuild = current.txID;
byte[] sign = null;
for (int outputIndex = 0; outputIndex < baseTx.outputs.length; outputIndex++) {
Transaction.Output output = baseTx.outputs[outputIndex];
// find outputs
if (Arrays.equals(outputScriptWeAreAbleToSpend, output.script.bytes)) {
unspentOutputs.add(new UnspentOutputInfo(txHash, output.script, output.value, outputIndex, -1, txHashForBuild, sign));
}
}
}
return unspentOutputs;
}
public static byte[] fromHex(String s) {
if (s != null) {
try {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (!Character.isWhitespace(ch)) {
sb.append(ch);
}
}
s = sb.toString();
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int hi = (Character.digit(s.charAt(i), 16) << 4);
int low = Character.digit(s.charAt(i + 1), 16);
if (hi >= 256 || low < 0 || low >= 16) {
return null;
}
data[i / 2] = (byte) (hi | low);
}
return data;
} catch (Exception ignored) {
}
}
return null;
}
public static byte[] reverse(byte[] bytes) {
byte[] result = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
result[i] = bytes[bytes.length - i - 1];
}
return result;
}
public static byte[] reverseInPlace(byte[] bytes) {
int len = bytes.length / 2;
for (int i = 0; i < len; i++) {
byte t = bytes[i];
bytes[i] = bytes[bytes.length - i - 1];
bytes[bytes.length - i - 1] = t;
}
return bytes;
}
}

View file

@ -11,21 +11,14 @@ import com.tangem.domain.wallet.btc.BitcoinOutputStream;
import com.tangem.domain.wallet.btc.BtcData;
import com.tangem.util.CryptoUtil;
import com.tangem.util.FormatUtil;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
@SuppressWarnings({"WeakerAccess", "TryWithIdenticalCatches", "unused"})
public final class BTCUtils {

View file

@ -1,5 +1,6 @@
package com.tangem.domain.wallet;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.wallet.R;
public class BalanceValidator {

View file

@ -3,8 +3,8 @@ package com.tangem.domain.wallet;
import android.os.Bundle;
import android.util.Log;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.tangem.data.Blockchain;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class CoinData {
@ -12,6 +12,24 @@ public abstract class CoinData {
public CoinData() {
}
private String wallet;
public void setWallet(String wallet) {
this.wallet = wallet;
}
public String getWallet() {
return wallet;
}
public String getShortWalletString() {
if (wallet.length() < 22) {
return wallet;
} else {
return wallet.substring(0, 10) + "......" + wallet.substring(wallet.length() - 10, wallet.length());
}
}
public boolean isBalanceReceived() {
return balanceReceived;
}
@ -23,12 +41,14 @@ public abstract class CoinData {
}
public void loadFromBundle(Bundle B) {
wallet = B.getString("Wallet");
if (B.containsKey("balanceReceived")) setBalanceReceived(B.getBoolean("balanceReceived"));
validationNodeDescription = B.getString("validationNodeDescription");
if (B.containsKey("FailedBalance"))
failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance"));
// if (B.containsKey("FailedBalance"))
// failedBalanceRequestCounter = new AtomicInteger(B.getInt("FailedBalance"));
if (B.containsKey("isBalanceEqual")) setIsBalanceEqual(B.getBoolean("isBalanceEqual"));
@ -40,10 +60,12 @@ public abstract class CoinData {
public void saveToBundle(Bundle B) {
try {
B.putString("Wallet", wallet);
if (balanceEqual != null) B.putBoolean("isBalanceEqual", balanceEqual);
if (failedBalanceRequestCounter != null)
B.putInt("FailedBalance", failedBalanceRequestCounter.get());
// if (failedBalanceRequestCounter != null)
// B.putInt("FailedBalance", failedBalanceRequestCounter.get());
B.putFloat("rate", rate);
B.putFloat("rateAlter", rateAlter);
@ -121,25 +143,32 @@ public abstract class CoinData {
public void clearInfo() {
setIsBalanceEqual(false);
setBalanceReceived(false); // TODO check
setValidationNodeDescription("");
minFee=null;
maxFee=null;
normalFee=null;
rate=0f;
rateAlter=0f;
}
private AtomicInteger failedBalanceRequestCounter;
public int incFailedBalanceRequestCounter() {
if (failedBalanceRequestCounter == null)
failedBalanceRequestCounter = new AtomicInteger(0);
return failedBalanceRequestCounter.incrementAndGet();
}
public void resetFailedBalanceRequestCounter() {
failedBalanceRequestCounter = new AtomicInteger(0);
}
public int getFailedBalanceRequestCounter() {
if (failedBalanceRequestCounter == null)
return 0;
return failedBalanceRequestCounter.get();
}
// private AtomicInteger failedBalanceRequestCounter;
//
// public int incFailedBalanceRequestCounter() {
// if (failedBalanceRequestCounter == null)
// failedBalanceRequestCounter = new AtomicInteger(0);
// return failedBalanceRequestCounter.incrementAndGet();
// }
//
// public void resetFailedBalanceRequestCounter() {
// failedBalanceRequestCounter = new AtomicInteger(0);
// }
//
// public int getFailedBalanceRequestCounter() {
// if (failedBalanceRequestCounter == null)
// return 0;
// return failedBalanceRequestCounter.get();
// }
private Boolean balanceEqual;
@ -161,7 +190,7 @@ public abstract class CoinData {
this.validationNodeDescription = validationNodeDescription;
}
public CoinEngine.Amount minFee = null;
public CoinEngine.Amount normalFee = null;
public CoinEngine.Amount maxFee = null;
}

View file

@ -3,7 +3,8 @@ package com.tangem.domain.wallet;
import android.net.Uri;
import android.text.InputFilter;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.tasks.SignTask;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -20,41 +21,41 @@ import java.util.Locale;
public abstract class CoinEngine {
public static class InternalAmount extends BigDecimal {
private String currency;
public InternalAmount() {
super(0);
currency="";
currency = "";
}
public InternalAmount(String amountString, String currency) {
super(amountString.replace(',','.'));
this.currency=currency;
super(amountString.replace(',', '.'));
this.currency = currency;
}
public InternalAmount(long amount, String currency) {
super(amount);
this.currency=currency;
this.currency = currency;
}
public InternalAmount(BigDecimal amount, String currency) {
super(amount.unscaledValue(), amount.scale());
this.currency=currency;
this.currency = currency;
}
public InternalAmount(BigInteger amount, String currency) {
super(new BigDecimal(amount).unscaledValue(), new BigDecimal(amount).scale());
this.currency=currency;
this.currency = currency;
}
public boolean notZero()
{
return compareTo(BigDecimal.ZERO)>0;
public boolean notZero() {
return compareTo(BigDecimal.ZERO) > 0;
}
public boolean isZero() {
return compareTo(BigDecimal.ZERO)==0;
return compareTo(BigDecimal.ZERO) == 0;
}
public String getCurrency() {
@ -72,7 +73,7 @@ public abstract class CoinEngine {
df.setGroupingUsed(false);
BigDecimal bd=new BigDecimal(unscaledValue(), scale());
BigDecimal bd = new BigDecimal(unscaledValue(), scale());
bd.setScale(decimals, ROUND_DOWN);
return df.format(bd);
}
@ -93,11 +94,11 @@ public abstract class CoinEngine {
public Amount() {
super(0);
currency="";
currency = "";
}
public Amount(String amountString, String currency) {
super(amountString.replace(',','.'));
super(amountString.replace(',', '.'));
this.currency = currency;
}
@ -120,13 +121,12 @@ public abstract class CoinEngine {
return super.toString() + " " + currency;
}
public boolean notZero()
{
return compareTo(BigDecimal.ZERO)>0;
public boolean notZero() {
return compareTo(BigDecimal.ZERO) > 0;
}
public String toDescriptionString(int decimals) {
return toValueString(decimals)+ " " + currency;
return toValueString(decimals) + " " + currency;
}
public String toValueString(int decimals) {
@ -140,7 +140,7 @@ public abstract class CoinEngine {
df.setGroupingUsed(false);
BigDecimal bd=new BigDecimal(unscaledValue(), scale());
BigDecimal bd = new BigDecimal(unscaledValue(), scale());
bd.setScale(decimals, ROUND_DOWN);
return df.format(bd);
}
@ -161,7 +161,7 @@ public abstract class CoinEngine {
}
public boolean isZero() {
return compareTo(BigDecimal.ZERO)==0;
return compareTo(BigDecimal.ZERO) == 0;
}
}
@ -181,7 +181,7 @@ public abstract class CoinEngine {
public abstract boolean isBalanceNotZero();
public abstract byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception;
// public abstract byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception;
// TODO - change isExtractPossible to isExtractPossible and if not - return string message
public abstract boolean isExtractPossible();
@ -220,9 +220,11 @@ public abstract class CoinEngine {
public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException;
public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception;
public abstract Amount convertToAmount(String strAmount, String currency);
public abstract InternalAmount convertToInternalAmount(Amount amount) throws Exception;
public abstract InternalAmount convertToInternalAmount(byte[] bytes) throws Exception;
public abstract byte[] convertToByteArray(InternalAmount internalAmount) throws Exception;
@ -231,4 +233,112 @@ public abstract class CoinEngine {
public abstract String getUnspentInputsDescription();
public void defineWallet() throws CardProtocol.TangemException {
try {
String wallet = calculateAddress(ctx.getCard().getWalletPublicKey());
ctx.getCoinData().setWallet(wallet);
} catch (Exception e) {
ctx.getCoinData().setWallet("ERROR");
throw new CardProtocol.TangemException("Can't define wallet address");
}
}
/**
* Create instance of {@link SignTask.PaymentToSign} used for transaction signing and sending
*
* Transaction processing sequence:
* 1. User enter transaction attributes
* 2. Application create instance of {@link SignTask.PaymentToSign} by call {@see constructPayment}
* 3. Application set notification when transaction were prepared {@see setOnNeedSendPayment} and start {@link SignTask}
* 4. User tap card and card sign transaction
* 5. Application receive {@link CoinEngine.OnNeedSendPayment} notification with prepared raw transaction
* 6. Application show user information that transaction ready for sending and start sending procedure by call {@see requestSendTransaction}
* 7. Application receive notification of sending result through {@link CoinEngine.BlockchainRequestsCallbacks} and show result to user
*
* @param amountValue - amount of desired transaction
* @param feeValue - fee amount of desired transaction
* @param IncFee - true if fee amount is included in amountValue (amountValue is total amount of transaction)
* @param targetAddress - target address of transaction
* @return instance of {@link SignTask.PaymentToSign}
* @throws Exception if something goes wrong
*/
public abstract SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception;
/**
* Interface used to notify main application when new transaction is prepared to send
*/
public interface OnNeedSendPayment {
void onPaymentPrepared(byte[] txForSend);
}
protected OnNeedSendPayment onNeedSendPayment;
/**
* Set notification callback when new transaction is prepared to send
*/
public void setOnNeedSendPayment(OnNeedSendPayment onNeedSendPayment) {
this.onNeedSendPayment = onNeedSendPayment;
}
protected void notifyOnNeedSendPayment(byte[] txForSend) throws Exception {
if (onNeedSendPayment == null)
throw new Exception("Payment signed but no callback defined to send!");
onNeedSendPayment.onPaymentPrepared(txForSend);
}
/**
* Interface used to notify/querying application during processing sequence of request to blockchain nodes/servers
*/
public interface BlockchainRequestsCallbacks {
/**
* Notification that the all requests in sequence completed
* Call after a last request completed
* If occurred error return in {@link TangemContext} {@see TangemContext.getError()}
*
* @param success -*
*/
void onComplete(Boolean success);
/**
* Notification that a new part of data received and it's possible to update view
* May call when some request in the sequence completed but there are still a few requests left
*/
void onProgress();
/**
* Return flag that allow to add new or re-requests in the sequence
* Call between requests or when request fail and before re-request
*
* @return true if not need terminate (e.g. activity is online)
*/
boolean allowAdvance();
}
/**
* Start sequence of request to blockchain nodes needed to get balance and other information (for example unspent transaction) needed to
* show current state of wallet and prepare new withdrawal transaction
* Save result in {@link CoinData}
* If occurred error can be get at onComplete callback in {@link TangemContext}.getError()
* @param blockchainRequestsCallbacks - notifications
* @throws Exception if something goes wrong
*/
public abstract void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception;
/**
* Start sequence of request to blockchain nodes needed to get fee amount for a new transaction
* Save result in {@link CoinData} minFee, maxFee, normalFee
* @param blockchainRequestsCallbacks - notifications
* @throws Exception if something goes wrong
*/
public abstract void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception;
/**
* Start sequence of request to blockchain nodes needed to send new transaction
* If occurred error can be get at onComplete callback in {@link TangemContext}.getError()
* @param blockchainRequestsCallbacks - notifications
* @throws Exception if something goes wrong
*/
public abstract void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception;
}

View file

@ -6,6 +6,8 @@ import com.tangem.domain.wallet.btc.BtcEngine
import com.tangem.domain.wallet.eth.EthEngine
import com.tangem.domain.wallet.token.TokenEngine
import com.tangem.domain.wallet.bch.BtcCashEngine
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.ltc.LtcEngine
/**
* Factory for create specific engine
@ -24,6 +26,7 @@ object CoinEngineFactory {
Blockchain.BitcoinCash -> BtcCashEngine()
Blockchain.Ethereum, Blockchain.EthereumTestNet -> EthEngine()
Blockchain.Token -> TokenEngine()
Blockchain.Litecoin -> LtcEngine()
else -> null
}
}
@ -39,6 +42,8 @@ object CoinEngineFactory {
EthEngine(context)
else if (Blockchain.Token == context.blockchain)
TokenEngine(context)
else if (Blockchain.Litecoin == context.blockchain)
LtcEngine(context)
else
return null
} catch (e: Exception) {

View file

@ -5,6 +5,9 @@ import android.content.Intent;
import android.os.Bundle;
import com.tangem.Constant;
import com.tangem.data.Blockchain;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.data.TangemCardExtensionsKt;
public class TangemContext {
@ -15,7 +18,6 @@ public class TangemContext {
private String error;
private String message;
public TangemContext() {
}
@ -26,12 +28,29 @@ public class TangemContext {
public Blockchain getBlockchain() {
if (card == null) return Blockchain.Unknown;
return card.getBlockchain();
Blockchain blockchain=Blockchain.fromId(card.getBlockchainID());
if( (blockchain==Blockchain.Ethereum || blockchain==Blockchain.EthereumTestNet)&& card.isToken() )
{
return Blockchain.Token;
}
return blockchain;
}
public void setBlockchain(Blockchain blockchain) {
if (card == null) return;
card.setBlockchain(blockchain);
// public void setBlockchain(Blockchain blockchain) {
// if (card == null) return;
// card.setBlockchainID(blockchain.getID());
// }
// private String blockchainName = "";
public String getBlockchainName() {
Blockchain blockchain=getBlockchain();
if( (blockchain==Blockchain.Ethereum || blockchain==Blockchain.EthereumTestNet)&& card.isToken() ) {
String token = card.getTokenSymbol();
return token + " <br><small><small> " + getBlockchain().getOfficialName() + " ERC20 token</small></small>";
}else {
return blockchain.getOfficialName();
}
}
public Context getContext() {
@ -66,6 +85,10 @@ public class TangemContext {
return error;
}
public boolean hasError() {
return error!=null && !error.isEmpty();
}
public void setMessage(String value) {
this.message = value;
}
@ -83,9 +106,9 @@ public class TangemContext {
TangemContext tangemContext = new TangemContext();
tangemContext.setContext(context);
if (bundle.containsKey(TangemCard.EXTRA_UID)) {
tangemContext.card = new TangemCard(bundle.getString(TangemCard.EXTRA_UID));
tangemContext.card.loadFromBundle(bundle.getBundle(TangemCard.EXTRA_CARD));
if (bundle.containsKey(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID)) {
tangemContext.card = new TangemCard(bundle.getString(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID));
TangemCardExtensionsKt.loadFromBundle(tangemContext.card, bundle.getBundle(TangemCardExtensionsKt.EXTRA_TANGEM_CARD));
}
if (tangemContext.getBlockchain() != null) {
@ -104,8 +127,8 @@ public class TangemContext {
public void saveToIntent(Intent intent) {
if (card != null) {
intent.putExtra(TangemCard.EXTRA_UID, card.getUID());
intent.putExtra(TangemCard.EXTRA_CARD, card.getAsBundle());
intent.putExtra(TangemCardExtensionsKt.EXTRA_TANGEM_CARD_UID, card.getUID());
intent.putExtra(TangemCardExtensionsKt.EXTRA_TANGEM_CARD, TangemCardExtensionsKt.getAsBundle(card));
}
if (coinData != null) {
@ -121,4 +144,17 @@ public class TangemContext {
if (context != null) return getContext().getResources().getString(stringId);
return "context.resources.string[" + stringId + "]";
}
public void setDenomination(byte[] denomination) {
try {
CoinEngine engine= CoinEngineFactory.INSTANCE.create(getBlockchain());
CoinEngine.InternalAmount internalAmount=engine.convertToInternalAmount(denomination);
CoinEngine.Amount amount=engine.convertToAmount(internalAmount);
card.setDenomination(denomination,amount.toString());
} catch (Exception e) {
e.printStackTrace();
card.setDenomination(denomination,"N/A");
}
}
}

View file

@ -566,11 +566,11 @@ public final class Transaction {
public static Script buildOutput(String address) throws BitcoinException {
//noinspection TryWithIdenticalCatches
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111) {
if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111 || addressWithCheckSumAndNetworkCode[0] == 48) { //0 for BTC/BCH 1 address | 48 for LTC L address
return buildOutputP2H(address);
}
if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4) {
if(addressWithCheckSumAndNetworkCode[0] == 5 || addressWithCheckSumAndNetworkCode[0] == (byte)0xc4 || addressWithCheckSumAndNetworkCode[0] == 50) { //5 for BTC/BCH/LTC 3 address | 50 for LTC M address
return buildOutputP2SH(address);
}
@ -579,7 +579,7 @@ public final class Transaction {
public static Script buildOutputP2SH(String address) throws BitcoinException {
try {
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4) {
if (addressWithCheckSumAndNetworkCode[0] != 5 && addressWithCheckSumAndNetworkCode[0] != (byte)0xc4 && addressWithCheckSumAndNetworkCode[0] != 50) {
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
}
@ -601,7 +601,7 @@ public final class Transaction {
//noinspection TryWithIdenticalCatches
try {
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111) {
if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111 && addressWithCheckSumAndNetworkCode[0] != 48) {
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
}

View file

@ -13,8 +13,6 @@ public class UnspentOutputInfo {
public final long confirmations;
public String txHashForBuild;
public byte[] scriptForBuild;
public byte[] bodyDoubleHash;
public byte[] bodyHash;
public UnspentOutputInfo(byte[] txHash, Transaction.Script script, long value, int outputIndex, long confirmations, String hashForBuild, byte[] sign) {
this.txHash = txHash;

View file

@ -1,8 +1,24 @@
package com.tangem.domain.wallet.bch
enum class BitcoinCashNode(val host: String, val port: Int) {
n1("electrumx-bch.cryptonermal.net", 50001),
n2("abc1.hsmiths.com", 60001),
n3("electrum.imaginary.cash", 50001),
n4("35.157.238.5", 51001),
enum class BitcoinCashNode(val host: String, val port: Int, val proto: String) {
N_001("electrumx.hillsideinternet.com", 50002, "ssl"),
N_002("bch0.kister.net", 50002, "ssl"),
N_003("abc1.hsmiths.com", 60002, "ssl"),
N_004("bch.curalle.ovh", 50002, "ssl"),
N_005("207.180.215.112", 52002, "ssl"),
N_006("bch.imaginary.cash", 50002, "ssl"),
N_007("dedi.jochen-hoenicke.de", 51002, "ssl"),
N_008("crypto.mldlabs.com", 50002, "ssl"),
N_009("bch.electrumx.cash", 50002, "ssl"),
N_010("electroncash.cascharia.com", 50002, "ssl"),
N_011("bch.crypto.mldlabs.com", 50002, "ssl"),
N_012("electron-cash.dragon.zone", 50002, "ssl"),
N_013("electron.coinucopia.io", 50002, "ssl"),
N_014("blackie.c3-soft.com", 50002, "ssl"),
N_015("electroncash.ueo.ch", 51002, "ssl"),
N_016("electrum.imaginary.cash", 50002, "ssl"),
N_017("35.157.238.5", 51002, "ssl"),
N_018("bitcoincash.quangld.com", 50002, "ssl"),
N_019("bch.stitthappens.com", 50002, "ssl"),
N_020("electroncash.dk", 50002, "ssl"),
}

View file

@ -2,30 +2,39 @@ package com.tangem.domain.wallet.bch;
import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.data.network.ElectrumRequest;
import com.tangem.data.network.ServerApiCommon;
import com.tangem.data.network.ServerApiElectrum;
import com.tangem.domain.wallet.BCHUtils;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.domain.wallet.CoinEngineFactory;
import com.tangem.data.Blockchain;
import com.tangem.domain.wallet.btc.BtcData;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.Transaction;
import com.tangem.domain.wallet.UnspentOutputInfo;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.tangemcard.tasks.SignTask;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.util.DerEncodingUtil;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.util.FormatUtil;
import com.tangem.wallet.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -35,6 +44,7 @@ import java.util.List;
public class BtcCashEngine extends CoinEngine {
private static final String TAG = BtcCashEngine.class.getSimpleName();
public BtcData coinData = null;
public BtcCashEngine(TangemContext context) throws Exception {
@ -169,9 +179,7 @@ public class BtcCashEngine extends CoinEngine {
//
// return true;
if(CashAddr.isValidCashAddress(address))
return true;
return false;
return CashAddr.isValidCashAddress(address);
}
@Override
@ -181,12 +189,12 @@ public class BtcCashEngine extends CoinEngine {
@Override
public Uri getShareWalletUriExplorer() {
return Uri.parse((ctx.getBlockchain() == Blockchain.BitcoinCash ? "https://bitcoincash.blockexplorer.com/address/" : "https://testnet.blockexplorer.com/address/") + ctx.getCard().getWallet());
return Uri.parse("https://bch.btc.com/" + ctx.getCoinData().getWallet());
}
@Override
public Uri getShareWalletUri() {
return Uri.parse(ctx.getCard().getWallet());
return Uri.parse(ctx.getCoinData().getWallet());
}
@Override
@ -223,7 +231,7 @@ public class BtcCashEngine extends CoinEngine {
if (fee.isZero() || amount.isZero())
return false;
if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee)<0))
if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0))
return false;
if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0)
@ -318,16 +326,16 @@ public class BtcCashEngine extends CoinEngine {
@Override
public String getBalanceEquivalent() {
if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return "";
Amount balance=getBalance();
if( balance==null ) return "";
Amount balance = getBalance();
if (balance == null) return "";
return balance.toEquivalentString(coinData.getRate());
}
@Override
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
public String calculateAddress(byte[] pubKey) throws NoSuchProviderException, NoSuchAlgorithmException {
// CashAddr format
byte hash1[] = Util.calculateSHA256(pkUncompressed);
byte hash1[] = Util.calculateSHA256(pubKey);
byte hash2[] = Util.calculateRIPEMD160(hash1);
return CashAddr.toCashAddress(BitcoinCashAddressType.P2PKH, hash2);
@ -428,20 +436,32 @@ public class BtcCashEngine extends CoinEngine {
return coinData.getUnspentInputsDescription();
}
@Override
public void defineWallet() throws CardProtocol.TangemException {
try {
String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar());
ctx.getCoinData().setWallet(wallet);
}
catch (Exception e)
{
ctx.getCoinData().setWallet("ERROR");
throw new CardProtocol.TangemException("Can't define wallet address");
}
}
// @Override
// public String getAmountDescription(TangemCard mCard, String amount) throws Exception {
// return mCard.getAmountDescription(Double.parseDouble(amount));
// }
@Override
public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String destAddress, CardProtocol protocol) throws Exception {
@Override
public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
checkBlockchainDataExists();
CoinEngine engine = CoinEngineFactory.INSTANCE.create(ctx);
String srcLegacyAddress = ((BtcCashEngine)engine).convertToLegacyAddress(ctx.getCard().getWallet());
String destLegacyAddress = ((BtcCashEngine)engine).convertToLegacyAddress(destAddress);
String srcLegacyAddress = convertToLegacyAddress(ctx.getCoinData().getWallet());
String destLegacyAddress = convertToLegacyAddress(targetAddress);
byte[] pbKey = ctx.getCard().getWalletPublicKeyRar(); //ALWAYS USING COMPRESS KEY
// Build script for our address
@ -449,7 +469,7 @@ public class BtcCashEngine extends CoinEngine {
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(srcLegacyAddress).bytes;
// Collect unspent
ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
ArrayList<UnspentOutputInfo> unspentOutputs = BCHUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
@ -470,48 +490,297 @@ public class BtcCashEngine extends CoinEngine {
throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount));
}
byte[][] dataForSign = new byte[unspentOutputs.size()][];
final long amountFinal = amount;
final long changeFinal = change;
byte[][] txForSign = new byte[unspentOutputs.size()][];
byte[][] bodyHash = new byte[unspentOutputs.size()][];
byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][];
for (int i = 0; i < unspentOutputs.size(); ++i) {
byte[] newTX = BTCUtils.buildTXForSign(srcLegacyAddress, destLegacyAddress, srcLegacyAddress, unspentOutputs, i, amount, change);
txForSign[i] = BCHUtils.buildTXForSign(srcLegacyAddress, destLegacyAddress, srcLegacyAddress, unspentOutputs, i, amount, change);
bodyHash[i] = Util.calculateSHA256(txForSign[i]);
bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]);
}
byte[] hashData = Util.calculateSHA256(newTX);
byte[] doubleHashData = Util.calculateSHA256(hashData);
return new SignTask.PaymentToSign() {
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
dataForSign[i] = newTX;
} else {
dataForSign[i] = doubleHashData;
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
}
}
byte[] signFromCard;
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < dataForSign.length; i++) {
if (i != 0 && dataForSign[0].length != dataForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(dataForSign[i]);
@Override
public byte[][] getHashesToSign() throws Exception {
byte[][] dataForSign = new byte[unspentOutputs.size()][];
if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < unspentOutputs.size(); ++i) {
dataForSign[i] = bodyDoubleHash[i];
}
return dataForSign;
}
signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value;
} else {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, ctx.getCard().getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
}
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
@Override
public byte[] getRawDataToSign() throws Exception {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
for (int i = 0; i < txForSign.length; i++) {
if (i != 0 && txForSign[0].length != txForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(txForSign[i]);
}
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
}
return bs.toByteArray();
}
return BTCUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amount, change);
@Override
public String getHashAlgToSign() {
return "sha-256x2";
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Transaction validation by issuer not supported in this version!");
}
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDerBitcoinCash(r, s, pbKey);
}
byte[] txForSend = BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal);
notifyOnNeedSendPayment(txForSend);
return txForSend;
}
};
}
@Override
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception {
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) {
try {
Long confBalance = electrumRequest.getResult().getLong("confirmed");
Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed");
coinData.setBalanceReceived(true);
coinData.setBalanceConfirmed(confBalance);
coinData.setBalanceUnconfirmed(unconfirmedBalance);
coinData.setValidationNodeDescription(serverApiElectrum.getValidationNodeDescription());
} catch (JSONException e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
}
}
if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) {
try {
String walletAddress = electrumRequest.getParams().getString(0);
JSONArray jsUnspentArray = electrumRequest.getResultArray();
try {
coinData.getUnspentTransactions().clear();
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
trUnspent.txID = jsUnspent.getString("tx_hash");
trUnspent.Amount = jsUnspent.getInt("value");
trUnspent.Height = jsUnspent.getInt("height");
coinData.getUnspentTransactions().add(trUnspent);
}
} catch (JSONException e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
}
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
Integer height = jsUnspent.getInt("height");
String hash = jsUnspent.getString("tx_hash");
if (height != -1) {
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
} else {
ctx.setError("Terminated by user");
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) {
try {
String txHash = electrumRequest.txHash;
String raw = electrumRequest.getResultString();
for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) {
if (tx.txID.equals(txHash))
tx.Raw = raw;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
if (serverApiElectrum.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
}else{
blockchainRequestsCallbacks.onProgress();
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError());
ctx.setError(electrumRequest.getError());
if (serverApiElectrum.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
}else{
blockchainRequestsCallbacks.onProgress();
}
}
};
serverApiElectrum.setElectrumRequestData(electrumBodyListener);
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.checkBalance(convertToLegacyAddress(coinData.getWallet())));
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(convertToLegacyAddress(coinData.getWallet())));
}
private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) {
try {
SignTask.PaymentToSign ps=constructPayment(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress );
OnNeedSendPayment onNeedSendPaymentBackup=onNeedSendPayment;
onNeedSendPayment=(tx)->{}; // empty function to bypass exception
byte[][] hashesToSign=ps.getHashesToSign();
byte[] signFromCard = new byte[64 * hashesToSign.length];
byte[] txForSend=ps.onSignCompleted(signFromCard);
onNeedSendPayment=onNeedSendPaymentBackup;
Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length));
return txForSend.length;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't calculate transaction size -> use default!");
return 256;
}
}
private final static BigDecimal relayFee = new BigDecimal(0.00001);
@Override
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
coinData.minFee=null;
coinData.maxFee=null;
coinData.normalFee=null;
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener () {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
BigDecimal fee;
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) {
try {
fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB
if (fee.equals(BigDecimal.ZERO)) {
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee());
}
// if (calcSize != 0) {
fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size
// } else {
// serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee());
// }
//compare fee to usual relay fee
if (fee.compareTo(relayFee) < 0) {
fee = relayFee;
}
fee = fee.setScale(8, RoundingMode.DOWN);
CoinEngine.Amount feeAmount = new CoinEngine.Amount(fee, ctx.getBlockchain().getCurrency());
coinData.minFee = feeAmount;
coinData.normalFee = feeAmount;
coinData.maxFee = feeAmount;
// if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) {
blockchainRequestsCallbacks.onComplete(true);
// } else {
// blockchainRequestsCallbacks.onProgress();
// }
} catch (JSONException e) {
e.printStackTrace();
}
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
ctx.setError(electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setElectrumRequestData(electrumListener);
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee());
}
@Override
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws Exception {
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final String txStr = BTCUtils.toHex(txForSend);
ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) {
try {
String resultString = electrumRequest.getResultString();
if (resultString == null || resultString.isEmpty()) {
ctx.setError("Rejected by node: " + electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}else {
ctx.setError(null);
blockchainRequestsCallbacks.onComplete(true);
}
} catch (Exception e) {
if (e.getMessage() != null) {
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
} else {
ctx.setError(e.getClass().getName());
blockchainRequestsCallbacks.onComplete(false);
}
}
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
ctx.setError(electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setElectrumRequestData(electrumBodyListener);
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr));
}
}

View file

@ -56,9 +56,11 @@ public class CashAddr {
String[] addressParts = bitcoinCashAddress.split(SEPARATOR);
if (addressParts.length == 2) {
decoded.setPrefix(addressParts[0]);
} else {
decoded.setPrefix(MAIN_NET_PREFIX);
}
byte[] addressData = BitcoinCashBase32.decode(addressParts[1]);
byte[] addressData = BitcoinCashBase32.decode(addressParts[addressParts.length - 1]);
addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8);
addressData = BitcoinCashBitArrayConverter.convertBits(addressData, 5, 8, true);
byte versionByte = addressData[0];
@ -84,19 +86,21 @@ public class CashAddr {
public static boolean isValidCashAddress(String bitcoinCashAddress ) {
try {
String prefix;
if (bitcoinCashAddress.contains(SEPARATOR)) {
String[] split = bitcoinCashAddress.split(SEPARATOR);
prefix = split[0];
bitcoinCashAddress = split[1];
} else {
prefix =MAIN_NET_PREFIX;
}
if (!isSingleCase(bitcoinCashAddress))
return false;
bitcoinCashAddress = bitcoinCashAddress.toLowerCase();
String prefix;
if (bitcoinCashAddress.contains(SEPARATOR)) {
String[] split = bitcoinCashAddress.split(SEPARATOR);
prefix = split[0];
if (!prefix.equals(MAIN_NET_PREFIX)) {return false;} //for now we use main net only
bitcoinCashAddress = split[1];
} else {
prefix = MAIN_NET_PREFIX;
}
if (!bitcoinCashAddress.startsWith("q")) {return false;} //for now we use P2PKH addresses only
byte[] checksumData = concatenateByteArrays(
concatenateByteArrays(getPrefixBytes(prefix ), new byte[] { 0x00 }),

View file

@ -2,18 +2,84 @@ package com.tangem.domain.wallet.btc
enum class BitcoinNode(val host: String, val port: Int, val proto: String) {
N_001("electrum.anduck.net", 50001, "tcp"),
N_002("electrum-server.ninja", 50001, "tcp"),
N_003("btc.cihar.com", 50001, "tcp"),
N_004("vps.hsmiths.com", 50001, "tcp"),
N_005("electrum.hsmiths.com", 50001, "tcp"),
N_006("electrum.vom-stausee.de", 50001, "tcp"),
N_007("node.ispol.sk", 50001, "tcp"),
N_008("electrum2.eff.ro", 50001, "tcp"),
N_009("electrumx.nmdps.net", 50001, "tcp"),
N_010("kirsche.emzy.de", 50001, "tcp"),
N_011("electrum.petrkr.net", 50001, "tcp"),
N_012("electrum.dk", 50001, "tcp"),
N_013("electrum.anduck.net", 50012, "ssl"),
N_014("electrum.eff.ro", 50002, "ssl"),
N_015("vps.hsmiths.com", 50002, "ssl"),
N_002("ip119.ip-54-37-91.eu", 50001, "tcp"),
N_003("electrum.qtornado.com", 50001, "tcp"),
N_004("ip239.ip-54-36-234.eu", 50001, "tcp"),
N_005("electrum-server.ninja", 50001, "tcp"),
N_006("174.138.11.174", 50001, "tcp"),
N_007("ndnd.selfhost.eu", 50001, "tcp"),
N_008("btc.cihar.com", 50001, "tcp"),
N_009("vps.hsmiths.com", 8080, "tcp"),
N_010("electrum.hsmiths.com", 8080, "tcp"),
N_011("ip120.ip-54-37-91.eu", 50001, "tcp"),
N_012("vps.hsmiths.com", 50001, "tcp"),
N_013("orannis.com", 50001, "tcp"),
N_014("ip101.ip-54-37-91.eu", 50001, "tcp"),
N_015("e-x.not.fyi", 50001, "tcp"),
N_016("electrum.hsmiths.com", 50001, "tcp"),
N_017("electrum.vom-stausee.de", 50001, "tcp"),
N_018("bitcoin.corgi.party", 50001, "tcp"),
N_019("electrum2.eff.ro", 50001, "tcp"),
N_020("electrum.coinucopia.io", 50001, "tcp"),
N_021("electrum.eff.ro", 50001, "tcp"),
N_022("btc.xskyx.net", 50001, "tcp"),
N_023("kirsche.emzy.de", 50001, "tcp"),
N_024("electrum.petrkr.net", 50001, "tcp"),
N_025("btc.knas.systems", 50001, "tcp"),
N_026("b.ooze.cc", 50002, "ssl"),
N_027("electrum.nute.net", 50002, "ssl"),
N_028("ndnd.selfhost.eu", 50002, "ssl"),
N_029("electrum.coinop.cc", 50002, "ssl"),
N_030("orannis.com", 50002, "ssl"),
N_031("electrum.vom-stausee.de", 50002, "ssl"),
N_032("ip119.ip-54-37-91.eu", 50002, "ssl"),
N_033("ip101.ip-54-37-91.eu", 50002, "ssl"),
N_034("electrum2.villocq.com", 50002, "ssl"),
N_035("dedi.jochen-hoenicke.de", 50002, "ssl"),
N_036("174.138.11.174", 50002, "ssl"),
N_037("tomscryptos.com", 50002, "ssl"),
N_038("elec.luggs.co", 443, "ssl"),
N_039("ip239.ip-54-36-234.eu", 50002, "ssl"),
N_040("bitcoins.sk", 50002, "ssl"),
N_041("btc.cihar.com", 50002, "ssl"),
N_042("e-x.not.fyi", 50002, "ssl"),
N_043("ip120.ip-54-37-91.eu", 50002, "ssl"),
N_044("electrum.villocq.com", 50002, "ssl"),
N_045("electrum.anduck.net", 50012, "ssl"),
N_046("technetium.network", 50002, "ssl"),
N_047("electrum.coinucopia.io", 50002, "ssl"),
N_048("currentlane.lovebitco.in", 50002, "ssl"),
N_049("dimon.trimon.de", 50002, "ssl"),
N_050("rbx.curalle.ovh", 50002, "ssl"),
N_051("btc.gravitech.net", 50002, "ssl"),
N_052("hetzner01.fischl-online.de", 50002, "ssl"),
N_053("fn.48.org", 50002, "ssl"),
N_054("185.64.116.15", 50002, "ssl"),
N_055("kirsche.emzy.de", 50002, "ssl"),
N_056("109.192.105.174", 50002, "ssl"),
N_057("fedaykin.goip.de", 50002, "ssl"),
N_058("vps.hsmiths.com", 50002, "ssl"),
N_059("104.250.141.242", 50002, "ssl"),
N_060("electrum.qtornado.com", 50002, "ssl"),
N_061("electrum-server.ninja", 50002, "ssl"),
N_062("electrum2.eff.ro", 50002, "ssl"),
N_063("electrum.hsmiths.com", 995, "ssl"),
N_064("electrum.hsmiths.com", 50002, "ssl"),
N_065("139.162.14.142", 50002, "ssl"),
N_066("electrum.eff.ro", 50002, "ssl"),
N_067("electrum.taborsky.cz", 50002, "ssl"),
N_068("electrum.festivaldelhumor.org", 50002, "ssl"),
N_069("electrum.petrkr.net", 50002, "ssl"),
N_070("us.electrum.be", 50002, "ssl"),
N_071("bitcoin-node.org", 50002, "ssl"),
N_072("vmd27610.contaboserver.net", 50002, "ssl"),
N_073("electrumx.soon.it", 50002, "ssl"),
N_074("vmd30612.contaboserver.net", 50002, "ssl"),
N_075("enode.duckdns.org", 50002, "ssl"),
N_076("81-7-13-84.blue.kundencontroller.de", 50002, "ssl"),
N_077("electrum.scumm.it", 50002, "ssl"),
N_078("helicarrier.bauerj.eu", 50002, "ssl"),
N_079("tardis.bauerj.eu", 50002, "ssl"),
N_080("such.ninja", 50002, "ssl"),
N_081("electrum.be", 50002, "ssl"),
}

View file

@ -4,6 +4,8 @@ package com.tangem.domain.wallet.btc;
* Created by Ilia on 29.09.2017.
*/
import com.tangem.domain.wallet.Transaction;
import java.io.ByteArrayOutputStream;
@SuppressWarnings("WeakerAccess")
@ -40,4 +42,7 @@ public final class BitcoinOutputStream extends ByteArrayOutputStream {
writeInt64(value);
}
}
public void write(Transaction.Script script) {
}
}

View file

@ -18,11 +18,19 @@ public class BtcData extends CoinData {
private Long balanceConfirmed, balanceUnconfirmed;
public String getUnspentInputsDescription() {
int gatheredUnspents = 0;
for (int i=0; i<unspentTransactions.size(); i++) {
if (unspentTransactions.get(i).Raw.length() > 1) gatheredUnspents++;
try {
int gatheredUnspents = 0;
if( unspentTransactions==null ) return "";
for (int i = 0; i < unspentTransactions.size(); i++) {
if (unspentTransactions.get(i).Raw != null && unspentTransactions.get(i).Raw.length() > 1) gatheredUnspents++;
}
return String.valueOf(unspentTransactions.size()) + " unspents (" + String.valueOf(gatheredUnspents) + " received)";
}
catch (Exception e)
{
e.printStackTrace();
return "";
}
return String.valueOf(unspentTransactions.size()) + " unspents (" + String.valueOf(gatheredUnspents) + " received)";
}
public static class UnspentTransaction {

View file

@ -2,29 +2,38 @@ package com.tangem.domain.wallet.btc;
import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.data.network.ServerApiCommon;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Base58;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.data.Blockchain;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.Transaction;
import com.tangem.domain.wallet.UnspentOutputInfo;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.tangemcard.tasks.SignTask;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.util.DerEncodingUtil;
import com.tangem.util.Util;
import com.tangem.tangemcard.util.Util;
import com.tangem.wallet.R;
import com.tangem.data.network.ElectrumRequest;
import com.tangem.data.network.ServerApiElectrum;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -34,6 +43,8 @@ import java.util.List;
public class BtcEngine extends CoinEngine {
private static final String TAG = BtcEngine.class.getSimpleName();
public BtcData coinData = null;
public BtcEngine(TangemContext context) throws Exception {
@ -179,15 +190,15 @@ public class BtcEngine extends CoinEngine {
@Override
public Uri getShareWalletUriExplorer() {
return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + ctx.getCard().getWallet());
return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + ctx.getCoinData().getWallet());
}
@Override
public Uri getShareWalletUri() {
if (ctx.getCard().getDenomination() != null) {
return Uri.parse("bitcoin:" + ctx.getCard().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8));
return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8));
} else {
return Uri.parse("bitcoin:" + ctx.getCard().getWallet());
return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet());
}
}
@ -236,14 +247,15 @@ public class BtcEngine extends CoinEngine {
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
try {
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
// Workaround before new back-end
// Workaround before new back-end
// if (card.getRemainingSignatures() == card.getMaxSignatures()) {
// firstLine = "Verified balance";
// secondLine = "Balance confirmed in blockchain. ";
@ -251,24 +263,24 @@ public class BtcEngine extends CoinEngine {
// return;
// }
if (coinData.getBalanceUnconfirmed() != 0) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Transaction in progress");
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
return false;
}
if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) {
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (coinData.getBalanceInInternalUnits().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
if (coinData.getBalanceUnconfirmed() != 0) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Transaction in progress");
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
return false;
}
}
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) {
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (coinData.getBalanceInInternalUnits().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
}
}
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0)
// {
// score = 80;
@ -277,11 +289,11 @@ public class BtcEngine extends CoinEngine {
// return;
// }
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
}
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
}
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
@ -290,7 +302,7 @@ public class BtcEngine extends CoinEngine {
// return;
// }
//
//
// if(card.isBalanceReceived() && !card.isBalanceEqual()) {
// score = 0;
// firstLine = "Disputed balance";
@ -298,7 +310,13 @@ public class BtcEngine extends CoinEngine {
// return;
// }
return true;
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
}
@Override
@ -372,7 +390,7 @@ public class BtcEngine extends CoinEngine {
}
@Override
public InternalAmount convertToInternalAmount(Amount amount) throws Exception {
public InternalAmount convertToInternalAmount(Amount amount) {
BigDecimal d = amount.multiply(new BigDecimal("100000000"));
return new InternalAmount(d, "Satoshi");
}
@ -386,7 +404,7 @@ public class BtcEngine extends CoinEngine {
}
@Override
public byte[] convertToByteArray(InternalAmount internalAmount) throws Exception {
public byte[] convertToByteArray(InternalAmount internalAmount) {
byte[] bytes = Util.longToByteArray(internalAmount.longValueExact());
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
@ -404,11 +422,11 @@ public class BtcEngine extends CoinEngine {
}
@Override
public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception {
public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
final ArrayList<UnspentOutputInfo> unspentOutputs;
checkBlockchainDataExists();
String myAddress = ctx.getCard().getWallet();
String myAddress = ctx.getCoinData().getWallet();
byte[] pbKey = ctx.getCard().getWalletPublicKey();
// Build script for our address
@ -416,14 +434,13 @@ public class BtcEngine extends CoinEngine {
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
fullAmount += unspentOutputs.get(i).value;
}
long fees = convertToInternalAmount(feeValue).longValueExact();
long amount = convertToInternalAmount(amountValue).longValueExact();
long change = fullAmount - amount;
@ -433,52 +450,375 @@ public class BtcEngine extends CoinEngine {
change = change - fees;
}
final long amountFinal = amount;
final long changeFinal = change;
if (amount + fees > fullAmount) {
throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount));
}
byte[][] dataForSign = new byte[unspentOutputs.size()][];
final byte[][] txForSign = new byte[unspentOutputs.size()][];
final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][];
final byte[][] bodyHash = new byte[unspentOutputs.size()][];
for (int i = 0; i < unspentOutputs.size(); ++i) {
byte[] newTX = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
bodyHash[i] = Util.calculateSHA256(txForSign[i]);
bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]);
}
byte[] hashData = Util.calculateSHA256(newTX);
byte[] doubleHashData = Util.calculateSHA256(hashData);
return new SignTask.PaymentToSign() {
unspentOutputs.get(i).bodyDoubleHash = doubleHashData;
unspentOutputs.get(i).bodyHash = hashData;
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
dataForSign[i] = newTX;
} else {
dataForSign[i] = doubleHashData;
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
}
}
byte[] signFromCard;
if (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw || ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Raw_Validated_By_Issuer) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (dataForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < dataForSign.length; i++) {
if (i != 0 && dataForSign[0].length != dataForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(dataForSign[i]);
@Override
public byte[][] getHashesToSign() throws Exception {
byte[][] dataForSign = new byte[unspentOutputs.size()][];
if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < unspentOutputs.size(); ++i) {
dataForSign[i] = bodyDoubleHash[i];
}
return dataForSign;
}
signFromCard = protocol.run_SignRaw(PINStorage.getPIN2(), bs.toByteArray()).getTLV(TLV.Tag.TAG_Signature).Value;
} else {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), dataForSign, ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer, null, ctx.getCard().getIssuer()).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
@Override
public byte[] getRawDataToSign() throws Exception {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
for (int i = 0; i < txForSign.length; i++) {
if (i != 0 && txForSign[0].length != txForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(txForSign[i]);
}
return bs.toByteArray();
}
@Override
public String getHashAlgToSign() {
return "sha-256x2";
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Issuer validation not supported!");
}
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
}
byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal);
notifyOnNeedSendPayment(txForSend);
return txForSend;
}
};
}
@Override
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
Log.i(TAG, "onSuccess: "+electrumRequest.getMethod());
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) {
try {
String walletAddress = electrumRequest.getParams().getString(0);
if (!walletAddress.equals(coinData.getWallet())) {
// todo - check
throw new Exception("Invalid wallet address in answer!");
}
Long confBalance = electrumRequest.getResult().getLong("confirmed");
Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed");
coinData.setBalanceReceived(true);
coinData.setBalanceConfirmed(confBalance);
coinData.setBalanceUnconfirmed(unconfirmedBalance);
coinData.setValidationNodeDescription(serverApiElectrum.getValidationNodeDescription());
} catch (JSONException e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_GetBalance JSONException");
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_GetBalance Exception");
}
} else if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) {
try {
String walletAddress = electrumRequest.getParams().getString(0);
JSONArray jsUnspentArray = electrumRequest.getResultArray();
try {
coinData.getUnspentTransactions().clear();
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
trUnspent.txID = jsUnspent.getString("tx_hash");
trUnspent.Amount = jsUnspent.getInt("value");
trUnspent.Height = jsUnspent.getInt("height");
coinData.getUnspentTransactions().add(trUnspent);
}
} catch (JSONException e) {
e.printStackTrace();
Log.e(TAG, "FAIL METHOD_ListUnspent JSONException");
}
for (int i = 0; i < jsUnspentArray.length(); i++) {
JSONObject jsUnspent = jsUnspentArray.getJSONObject(i);
Integer height = jsUnspent.getInt("height");
String hash = jsUnspent.getString("tx_hash");
if (height != -1) {
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash));
} else {
ctx.setError("Terminated by user");
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
} else if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) {
try {
String txHash = electrumRequest.txHash;
String raw = electrumRequest.getResultString();
for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) {
if (tx.txID.equals(txHash))
tx.Raw = raw;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
if (serverApiElectrum.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
}else{
blockchainRequestsCallbacks.onProgress();
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError());
ctx.setError(electrumRequest.getError());
if (serverApiElectrum.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(false);//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
}else{
blockchainRequestsCallbacks.onProgress();
}
}
};
serverApiElectrum.setElectrumRequestData(electrumListener);
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet()));
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet()));
}
protected Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) {
//todo - правильней было бы использовать constructPayment
try {
// String myAddress = coinData.getWallet();
// byte[] pbKey = ctx.getCard().getWalletPublicKey();
// byte[] pbComprKey = ctx.getCard().getWalletPublicKeyRar();
//
// // build script for our address
// List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
//
// // collect unspent
// ArrayList<UnspentOutputInfo> unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
//
// Long fullAmount = 0L;
// for (int i = 0; i < unspentOutputs.size(); i++) {
// fullAmount += unspentOutputs.get(i).value;
// }
//
// // get first unspent
//// val outPut = unspentOutputs[0]
//// val outPutIndex = outPut.outputIndex
//
// // get prev TX id;
//// val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2";
//
// Long fees = FormatUtil.ConvertStringToLong("0.00");
// Long amount = FormatUtil.ConvertStringToLong(outAmount);
// amount -= fees;
//
// Long change = fullAmount - fees - amount;
//
// if (amount + fees > fullAmount) {
// throw new Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount));
// }
//
// byte[][] hashesForSign = new byte[unspentOutputs.size()][];
//
// for (int i = 0; i < unspentOutputs.size(); i++) {
// byte[] newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change);
// byte[] hashData = Util.calculateSHA256(newTX);
// byte[] doubleHashData = Util.calculateSHA256(hashData);
//// Log.e("TX_BODY_1", BTCUtils.toHex(newTX))
//// Log.e("TX_HASH_1", BTCUtils.toHex(hashData))
//// Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData))
//
//// unspentOutputs[i].bodyDoubleHash = doubleHashData
//// unspentOutputs[i].bodyHash = hashData
// hashesForSign[i] = doubleHashData;
// }
//
// byte[] signFromCard = new byte[64 * unspentOutputs.size()];
//
// for (int i = 0; i < unspentOutputs.size(); i++) {
// BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
// BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
// byte[] encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey);
// unspentOutputs.get(i).scriptForBuild = encodingSign;
// }
//
// byte[] realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change);
SignTask.PaymentToSign ps=constructPayment(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress );
OnNeedSendPayment onNeedSendPaymentBackup=onNeedSendPayment;
onNeedSendPayment=(tx)->{}; // empty function to bypass exception
byte[][] hashesToSign=ps.getHashesToSign();
byte[] signFromCard = new byte[64 * hashesToSign.length];
byte[] txForSend=ps.onSignCompleted(signFromCard);
onNeedSendPayment=onNeedSendPaymentBackup;
Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length));
return txForSend.length;
// Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length)+" realTX.length="+String.valueOf(realTX.length));
//
// return realTX.length;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't calculate transaction size -> use default!");
return 256;
}
}
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
@Override
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
coinData.minFee = null;
coinData.maxFee = null;
coinData.normalFee = null;
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
}
final ServerApiCommon serverApiCommon = new ServerApiCommon();
final ServerApiCommon.EstimateFeeListener estimateFeeListener = new ServerApiCommon.EstimateFeeListener() {
@Override
public void onSuccess(int blockCount, String estimateFeeResponse) {
BigDecimal fee = new BigDecimal(estimateFeeResponse); // BTC per 1 kb
if (fee.equals(BigDecimal.ZERO)) {
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiCommon.estimateFee(blockCount);
}
return;
}
if (calcSize != 0) {
fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024), BigDecimal.ROUND_DOWN); // per Kb -> per byte
} else {
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiCommon.estimateFee(blockCount);
}
return;
}
fee = fee.setScale(8, RoundingMode.DOWN);
switch (blockCount) {
case ServerApiCommon.ESTIMATE_FEE_MINIMAL:
coinData.minFee = new CoinEngine.Amount(fee, getFeeCurrency());
break;
case ServerApiCommon.ESTIMATE_FEE_NORMAL:
coinData.normalFee = new CoinEngine.Amount(fee, getFeeCurrency());
break;
case ServerApiCommon.ESTIMATE_FEE_PRIORITY:
coinData.maxFee = new CoinEngine.Amount(fee, getFeeCurrency());
break;
}
if(coinData.minFee!=null && coinData.normalFee!=null && coinData.maxFee!=null ) {
blockchainRequestsCallbacks.onComplete(true);
}else{
blockchainRequestsCallbacks.onProgress();
}
}
@Override
public void onFail(int blockCount, String message) {
// TODO - add fail counter to terminate after NNN tries
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiCommon.estimateFee(blockCount);
return;
}
ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node));
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiCommon.setEstimateFee(estimateFeeListener);
serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY);
serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_NORMAL);
serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL);
}
@Override
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final String txStr = BTCUtils.toHex(txForSend);
ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener() {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) {
try {
String resultString = electrumRequest.getResultString();
if (resultString == null || resultString.isEmpty()) {
ctx.setError("Rejected by node: " + electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}else {
ctx.setError(null);
blockchainRequestsCallbacks.onComplete(true);
}
} catch (Exception e) {
if (e.getMessage() != null) {
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
} else {
ctx.setError(e.getClass().getName());
blockchainRequestsCallbacks.onComplete(false);
}
}
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
ctx.setError(electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setElectrumRequestData(electrumListener);
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr));
return BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amount, change);
}
}

View file

@ -62,8 +62,12 @@ public class EthData extends CoinData {
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
String currency = B.getString("BalanceCurrency");
balance = new CoinEngine.InternalAmount(B.getString("BalanceDecimal"), currency);
if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) {
String currency = B.getString("BalanceCurrency");
balance = new CoinEngine.InternalAmount(B.getString("BalanceDecimal"), currency);
} else {
balance = null;
}
if (B.containsKey("confirmTx"))
countConfirmedTX = new BigInteger(B.getString("confirmTx"), 16);
@ -75,8 +79,10 @@ public class EthData extends CoinData {
public void saveToBundle(Bundle B) {
super.saveToBundle(B);
try {
B.putString("BalanceCurrency", balance.getCurrency());
B.putString("BalanceDecimal", balance.toString());
if (balance != null) {
B.putString("BalanceCurrency", balance.getCurrency());
B.putString("BalanceDecimal", balance.toString());
}
B.putString("confirmTx", getConfirmedTXCount().toString(16));
B.putString("unconfirmTx", getUnconfirmedTXCount().toString(16));

View file

@ -4,20 +4,19 @@ import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.data.Blockchain;
import com.tangem.data.network.ServerApiInfura;
import com.tangem.data.network.model.InfuraResponse;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Blockchain;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.ECDSASignatureETH;
import com.tangem.domain.wallet.EthTransaction;
import com.tangem.domain.wallet.Issuer;
import com.tangem.domain.wallet.Keccak256;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.tasks.SignTask;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.wallet.R;
@ -27,8 +26,6 @@ import org.bitcoinj.core.ECKey;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
/**
@ -37,6 +34,7 @@ import java.util.Arrays;
public class EthEngine extends CoinEngine {
private static final String TAG = EthEngine.class.getSimpleName();
public EthData coinData = null;
public EthEngine(TangemContext ctx) throws Exception {
@ -60,7 +58,7 @@ public class EthEngine extends CoinEngine {
}
@Override
public boolean awaitingConfirmation(){
public boolean awaitingConfirmation() {
return false;
}
@ -74,17 +72,17 @@ public class EthEngine extends CoinEngine {
@Override
public String getBalanceHTML() {
Amount balance=getBalance();
if( balance!=null ) {
Amount balance = getBalance();
if (balance != null) {
return balance.toDescriptionString(getDecimals());
}else{
} else {
return "";
}
}
@Override
public String getBalanceCurrency() {
return "ETH";
return Blockchain.Ethereum.getCurrency();
}
@Override
@ -96,14 +94,14 @@ public class EthEngine extends CoinEngine {
@Override
public boolean isBalanceNotZero() {
if( coinData ==null ) return false;
if (coinData == null) return false;
if (coinData.getBalanceInInternalUnits() == null) return false;
return coinData.getBalanceInInternalUnits().notZero();
}
@Override
public String getFeeCurrency() {
return "ETH";
return Blockchain.Ethereum.getCurrency();
}
public boolean isNeedCheckNode() {
@ -186,8 +184,8 @@ public class EthEngine extends CoinEngine {
@Override
public String getBalanceEquivalent() {
Amount balance=getBalance();
if( balance==null ) return "";
Amount balance = getBalance();
if (balance == null) return "";
return balance.toEquivalentString(coinData.getRate());
}
@ -203,8 +201,8 @@ public class EthEngine extends CoinEngine {
}
@Override
public InternalAmount convertToInternalAmount(Amount amount){
return new InternalAmount(amount.multiply(new BigDecimal("1000000000000000000")),"wei");
public InternalAmount convertToInternalAmount(Amount amount) {
return new InternalAmount(amount.multiply(new BigDecimal("1000000000000000000")), "wei");
}
@Override
@ -221,24 +219,24 @@ public class EthEngine extends CoinEngine {
@Override
public boolean hasBalanceInfo() {
return coinData.getBalanceInInternalUnits()!=null;
return coinData.getBalanceInInternalUnits() != null;
}
@Override
public Uri getShareWalletUri() {
if (ctx.getCard().getDenomination() != null) {
return Uri.parse("ethereum:" + ctx.getCard().getWallet());// + "?value=" + mCard.getDenomination() +"e18");
return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18");
} else {
return Uri.parse("ethereum:" + ctx.getCard().getWallet());
return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());
}
}
@Override
public Uri getShareWalletUriExplorer() {
if (ctx.getCard().getBlockchain() == Blockchain.EthereumTestNet)
return Uri.parse("https://rinkeby.etherscan.io/address/" + ctx.getCard().getWallet());
if (ctx.getBlockchain() == Blockchain.EthereumTestNet)
return Uri.parse("https://rinkeby.etherscan.io/address/" + ctx.getCoinData().getWallet());
else
return Uri.parse("https://etherscan.io/address/" + ctx.getCard().getWallet());
return Uri.parse("https://etherscan.io/address/" + ctx.getCoinData().getWallet());
}
@Override
@ -257,14 +255,14 @@ public class EthEngine extends CoinEngine {
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[] { new DecimalDigitsInputFilter(getDecimals()) };
return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
}
@Override
public boolean checkNewTransactionAmount(Amount amount){
if( coinData ==null ) return false;
Amount balance=getBalance();
if (balance==null || amount.compareTo(balance) > 0) {
public boolean checkNewTransactionAmount(Amount amount) {
if (coinData == null) return false;
Amount balance = getBalance();
if (balance == null || amount.compareTo(balance) > 0) {
return false;
}
return true;
@ -295,7 +293,7 @@ public class EthEngine extends CoinEngine {
try {
BigDecimal cardBalance = getBalance();
if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee)<0))
if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee) < 0))
return false;
if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0)
@ -349,20 +347,18 @@ public class EthEngine extends CoinEngine {
try {
Amount feeValue = new Amount(fee, ctx.getBlockchain().getCurrency());
return feeValue.toEquivalentString(coinData.getRate());
}
catch (Exception e)
{
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
@Override
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
public String calculateAddress(byte[] pkUncompressed) {
Keccak256 kec = new Keccak256();
int lenPk = pkUncompressed.length;
if (lenPk < 2) {
throw new IllegalArgumentException("Uncompress public key length is invald");
throw new IllegalArgumentException("Uncompress public key length is invalid");
}
byte[] cleanKey = new byte[lenPk - 1];
for (int i = 0; i < cleanKey.length; ++i) {
@ -379,21 +375,20 @@ public class EthEngine extends CoinEngine {
}
@Override
public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception {
public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) {
Log.e(TAG, "Construct payment " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding"));
BigInteger nonceValue = coinData.getConfirmedTXCount();
byte[] pbKey = ctx.getCard().getWalletPublicKey();
boolean flag = (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = ctx.getCard().getIssuer();
BigInteger weiFee=convertToInternalAmount(feeValue).toBigIntegerExact();
BigInteger weiAmount=convertToInternalAmount(amountValue).toBigIntegerExact();
BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact();
BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact();
if (IncFee) {
weiAmount = weiAmount.subtract(weiFee);
}
BigInteger nonce = nonceValue;
BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000));
BigInteger gasLimit = BigInteger.valueOf(21000);
Integer chainId = ctx.getBlockchain() == Blockchain.Ethereum ? EthTransaction.ChainEnum.Mainnet.getValue() : EthTransaction.ChainEnum.Rinkeby.getValue();
@ -404,41 +399,211 @@ public class EthEngine extends CoinEngine {
to = to.substring(2);
}
EthTransaction tx = EthTransaction.create(to, weiAmount, nonce, gasPrice, gasLimit, chainId);
final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
return new SignTask.PaymentToSign() {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash;
}
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
@Override
public byte[][] getHashesToSign() {
byte[][] hashesForSign = new byte[1][];
hashesForSign[0] = tx.getRawHash();
return hashesForSign;
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
@Override
public byte[] getRawDataToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for ETH");
}
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
@Override
public String getHashAlgToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for ETH");
}
if (!f) {
Log.e("ETH-CHECK", "sign Failed.");
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Transaction validation by issuer not supported in this version");
}
tx.signature = new ECDSASignatureETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
byte[] for_hash = tx.getRawHash();
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if (!f) {
Log.e("ETH-CHECK", "sign Failed.");
}
tx.signature = new ECDSASignatureETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e(TAG, "invalid v");
throw new Exception("Error in EthEngine - invalid v");
}
tx.signature.v = (byte) v;
Log.e(TAG, "ETH_v: " +String.valueOf(v));
byte[] txForSend = tx.getEncoded();
notifyOnNeedSendPayment(txForSend);
return txForSend;
}
};
}
@Override
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
final ServerApiInfura serverApiInfura = new ServerApiInfura();
// request infura listener
ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() {
@Override
public void onSuccess(String method, InfuraResponse infuraResponse) {
switch (method) {
case ServerApiInfura.INFURA_ETH_GET_BALANCE: {
String balanceCap = infuraResponse.getResult();
balanceCap = balanceCap.substring(2);
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceReceived(true);
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
// Log.i("$TAG eth_get_balance", balanceCap)
}
break;
case ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT: {
String nonce = infuraResponse.getResult();
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
case ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT: {
String pending = infuraResponse.getResult();
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
}
if (serverApiInfura.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
} else {
blockchainRequestsCallbacks.onProgress();
}
}
@Override
public void onFail(String method, String message) {
if (!serverApiInfura.isRequestsSequenceCompleted()) {
ctx.setError(message);
blockchainRequestsCallbacks.onComplete(false);
}
}
};
serverApiInfura.setInfuraResponse(infuraBodyListener);
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
}
@Override
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
ServerApiInfura serverApiInfura = new ServerApiInfura();
// request infura eth gasPrice listener
ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() {
@Override
public void onSuccess(String method, InfuraResponse infuraResponse) {
String gasPrice = infuraResponse.getResult();
gasPrice = gasPrice.substring(2);
// rounding gas price to integer gwei
BigInteger l = new BigInteger(gasPrice, 16);//.divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L));
Log.i(TAG, "Infura gas price: " + gasPrice + " (" + l.toString() + ")");
BigInteger m = BigInteger.valueOf(21000);
Log.e(TAG, "fee multiplier: " + m.toString());
CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei");
CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(12)).divide(BigInteger.valueOf(10)).multiply(m), "wei");
CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(15)).divide(BigInteger.valueOf(10)).multiply(m), "wei");
Log.i(TAG, "min fee : " + weiMinFee.toValueString() + " wei");
Log.i(TAG, "normal fee: " + weiNormalFee.toValueString() + " wei");
Log.i(TAG, "max fee : " + weiMaxFee.toValueString() + " wei");
coinData.minFee = convertToAmount(weiMinFee);
coinData.normalFee = convertToAmount(weiNormalFee);
coinData.maxFee = convertToAmount(weiMaxFee);
Log.i(TAG, "min fee : " + coinData.minFee.toString());
Log.i(TAG, "normal fee: " + coinData.normalFee.toString());
Log.i(TAG, "max fee : " + coinData.maxFee.toString());
blockchainRequestsCallbacks.onComplete(true);
}
@Override
public void onFail(String method, String message) {
ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node));
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiInfura.setInfuraResponse(infuraBodyListener);
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", "");
}
@Override
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
String txStr = String.format("0x%s", BTCUtils.toHex(txForSend));
ServerApiInfura serverApiInfura = new ServerApiInfura();
// request infura eth gasPrice listener
ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() {
@Override
public void onSuccess(String method, InfuraResponse infuraResponse) {
if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
if (infuraResponse.getResult().isEmpty()) {
ctx.setError("Rejected by node: " + infuraResponse.getError());
blockchainRequestsCallbacks.onComplete(false);
} else {
BigInteger nonce = coinData.getConfirmedTXCount();
nonce=nonce.add(BigInteger.valueOf(1));
coinData.setConfirmedTXCount(nonce);
ctx.setError(null);
blockchainRequestsCallbacks.onComplete(true);
}
}
}
@Override
public void onFail(String method, String message) {
if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
ctx.setError(message);
blockchainRequestsCallbacks.onComplete(false);
}
}
};
serverApiInfura.setInfuraResponse(infuraBodyListener);
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr);
byte[] realTX = tx.getEncoded();
return realTX;
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.wallet.ltc
enum class LitecoinNode(val host: String, val port: Int, val proto: String) {
N_001("node.ispol.sk", 50004, "ssl"),
N_002("electrum-ltc.klippb.org", 50002, "ssl"),
N_003("backup.electrum-ltc.org", 443, "ssl"),
N_004("electrum-ltc.petrkr.net", 60002, "ssl"),
N_005("technetium.network", 50003, "ssl"),
N_006("167.99.146.166", 50002, "ssl"),
N_007("electrum-ltc.bysh.me", 50002, "ssl"),
N_008("e-3.claudioboxx.com", 50004, "ssl"),
N_009("electrum-ltc.wilv.in", 50002, "ssl"),
N_010("ltc.rentonisk.com", 50002, "ssl"),
N_011("e-1.claudioboxx.com", 50004, "ssl"),
N_012("electrum.ltc.xurious.com", 50002, "ssl"),
N_013("ltc01.knas.systems", 50004, "ssl"),
}

View file

@ -0,0 +1,560 @@
package com.tangem.domain.wallet.ltc;
import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.tangem.data.network.ElectrumRequest;
import com.tangem.data.network.ServerApiElectrum;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.Base58;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.Transaction;
import com.tangem.domain.wallet.UnspentOutputInfo;
import com.tangem.domain.wallet.btc.BtcData;
import com.tangem.domain.wallet.btc.BtcEngine;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.tasks.SignTask;
import com.tangem.tangemcard.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;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LtcEngine extends BtcEngine {
private static final String TAG = LtcEngine.class.getSimpleName();
public BtcData coinData = null;
public LtcEngine(TangemContext context) throws Exception {
super(context);
if (context.getCoinData() == null) {
coinData = new BtcData();
context.setCoinData(coinData);
} else if (context.getCoinData() instanceof BtcData) {
coinData = (BtcData) context.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for LtcEngine");
}
}
public LtcEngine() {
super();
}
private static int getDecimals() {
return 8;
}
private void checkBlockchainDataExists() throws Exception {
if (coinData == null) throw new Exception("No blockchain data");
}
@Override
public boolean awaitingConfirmation() {
if (coinData == null) return false;
return coinData.getBalanceUnconfirmed() != 0;
}
@Override
public String getBalanceHTML() {
Amount balance = getBalance();
if (balance != null) {
return balance.toDescriptionString(getDecimals());
} else {
return "";
}
}
@Override
public String getBalanceCurrency() {
return "LTC";
}
@Override
public String getOfflineBalanceHTML() {
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
Amount offlineAmount = convertToAmount(offlineInternalAmount);
return offlineAmount.toDescriptionString(getDecimals());
}
@Override
public boolean isBalanceNotZero() {
if (coinData == null) return false;
if (coinData.getBalanceInInternalUnits() == null) return false;
return coinData.getBalanceInInternalUnits().notZero();
}
@Override
public boolean hasBalanceInfo() {
if (coinData == null) return false;
return coinData.hasBalanceInfo();
}
@Override
public boolean isExtractPossible() {
if (!hasBalanceInfo()) {
ctx.setMessage(R.string.cannot_obtain_data_from_blockchain);
} else if (!isBalanceNotZero()) {
ctx.setMessage(R.string.wallet_empty);
} else if (awaitingConfirmation()) {
ctx.setMessage(R.string.please_wait_while_previous);
} else if (coinData.getUnspentTransactions().size() == 0) {
ctx.setMessage(R.string.please_wait_for_confirmation);
} else {
return true;
}
return false;
}
@Override
public String getFeeCurrency() {
return "LTC";
}
@Override
public boolean validateAddress(String address) {
if (address == null || address.isEmpty()) {
return false;
}
if (address.length() < 25) {
return false;
}
if (address.length() > 35) {
return false;
}
if (!address.startsWith("L") && !address.startsWith("M")) {
return false;
}
byte[] decAddress = Base58.decodeBase58(address);
if (decAddress == null || decAddress.length == 0) {
return false;
}
byte[] rip = new byte[21];
for (int i = 0; i < 21; ++i) {
rip[i] = decAddress[i];
}
byte[] kcv = CryptoUtil.doubleSha256(rip);
for (int i = 0; i < 4; ++i) {
if (kcv[i] != decAddress[21 + i])
return false;
}
return true;
}
@Override
public boolean isNeedCheckNode() {
return true;
}
@Override
public Uri getShareWalletUriExplorer() {
return Uri.parse("https://live.blockcypher.com/ltc/address/" + ctx.getCoinData().getWallet());
}
@Override
public Uri getShareWalletUri() {
if (ctx.getCard().getDenomination() != null) {
return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8));
} else {
return Uri.parse("litecoin:" + ctx.getCoinData().getWallet());
}
}
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
}
@Override
public boolean checkNewTransactionAmount(Amount amount) {
if (coinData == null) return false;
if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) {
return false;
}
return true;
}
@Override
public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) {
InternalAmount fee;
InternalAmount amount;
try {
checkBlockchainDataExists();
amount = convertToInternalAmount(amountValue);
fee = convertToInternalAmount(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if (fee == null || amount == null)
return false;
if (fee.isZero() || amount.isZero())
return false;
if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0))
return false;
if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0)
return false;
return true;
}
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
// Workaround before new back-end
// if (card.getRemainingSignatures() == card.getMaxSignatures()) {
// firstLine = "Verified balance";
// secondLine = "Balance confirmed in blockchain. ";
// secondLine += "Verified note identity. ";
// return;
// }
if (coinData.getBalanceUnconfirmed() != 0) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Transaction in progress");
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
return false;
}
if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) {
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (coinData.getBalanceInInternalUnits().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
}
}
// rule 4 TODO: need to check SignedHashed against number of outputs in blockchain
// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0)
// {
// score = 80;
// firstLine = "Unguaranteed balance";
// secondLine = "Potential unsent transaction. Redeem immediately if accept. ";
// return;
// }
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
}
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
// secondLine += "Not all nodes have returned balance. Swipe down or tap again. ";
// if(score <= 0)
// return;
// }
//
// if(card.isBalanceReceived() && !card.isBalanceEqual()) {
// score = 0;
// firstLine = "Disputed balance";
// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later.";
// return;
// }
return true;
}
@Override
public Amount getBalance() {
if (!hasBalanceInfo()) return null;
return convertToAmount(coinData.getBalanceInInternalUnits());
}
@Override
public String evaluateFeeEquivalent(String fee) {
if (!coinData.getAmountEquivalentDescriptionAvailable()) return "";
try {
Amount feeAmount = new Amount(fee, getFeeCurrency());
return feeAmount.toEquivalentString(coinData.getRate());
} catch (Exception e) {
return "";
}
}
@Override
public String getBalanceEquivalent() {
if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return "";
Amount balance = getBalance();
if (balance == null) return "";
return balance.toEquivalentString(coinData.getRate());
}
@Override
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
byte netSelectionByte = (byte) 0x30;
byte hash1[] = Util.calculateSHA256(pkUncompressed);
byte hash2[] = Util.calculateRIPEMD160(hash1);
ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1);
BB.put(netSelectionByte);
BB.put(hash2);
byte hash3[] = Util.calculateSHA256(BB.array());
byte hash4[] = Util.calculateSHA256(hash3);
BB = ByteBuffer.allocate(hash2.length + 5);
BB.put(netSelectionByte); //BB.put((byte) 0x6f);
BB.put(hash2);
BB.put(hash4[0]);
BB.put(hash4[1]);
BB.put(hash4[2]);
BB.put(hash4[3]);
return org.bitcoinj.core.Base58.encode(BB.array());
}
@Override
public Amount convertToAmount(InternalAmount internalAmount) {
BigDecimal d = internalAmount.divide(new BigDecimal("100000000"));
return new Amount(d, getBalanceCurrency());
}
@Override
public Amount convertToAmount(String strAmount, String currency) {
return new Amount(strAmount, currency);
}
@Override
public InternalAmount convertToInternalAmount(Amount amount) {
BigDecimal d = amount.multiply(new BigDecimal("100000000"));
return new InternalAmount(d, "Satoshi");
}
@Override
public InternalAmount convertToInternalAmount(byte[] bytes) {
if (bytes == null) return null;
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return new InternalAmount(Util.byteArrayToLong(reversed), "Satoshi");
}
@Override
public byte[] convertToByteArray(InternalAmount internalAmount) {
byte[] bytes = Util.longToByteArray(internalAmount.longValueExact());
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return reversed;
}
@Override
public CoinData createCoinData() {
return new BtcData();
}
@Override
public String getUnspentInputsDescription() {
return coinData.getUnspentInputsDescription();
}
@Override
public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
final ArrayList<UnspentOutputInfo> unspentOutputs;
checkBlockchainDataExists();
String myAddress = ctx.getCoinData().getWallet();
byte[] pbKey = ctx.getCard().getWalletPublicKey();
// Build script for our address
List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
// Collect unspent
unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
long fullAmount = 0;
for (int i = 0; i < unspentOutputs.size(); ++i) {
fullAmount += unspentOutputs.get(i).value;
}
long fees = convertToInternalAmount(feeValue).longValueExact();
long amount = convertToInternalAmount(amountValue).longValueExact();
long change = fullAmount - amount;
if (IncFee) {
amount = amount - fees;
} else {
change = change - fees;
}
final long amountFinal=amount;
final long changeFinal=change;
if (amount + fees > fullAmount) {
throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount));
}
final byte[][] txForSign = new byte[unspentOutputs.size()][];
final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][];
final byte[][] bodyHash= new byte[unspentOutputs.size()][];
for (int i = 0; i < unspentOutputs.size(); ++i) {
txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
bodyHash[i] = Util.calculateSHA256(txForSign[i]);
bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]);
}
return new SignTask.PaymentToSign() {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw;
}
@Override
public byte[][] getHashesToSign() throws Exception {
byte[][] dataForSign=new byte[unspentOutputs.size()][];
if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < unspentOutputs.size(); ++i) {
dataForSign[i] = bodyDoubleHash[i];
}
return dataForSign;
}
@Override
public byte[] getRawDataToSign() throws Exception {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
for (int i = 0; i < txForSign.length; i++) {
if (i != 0 && txForSign[0].length != txForSign[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(txForSign[i]);
}
return bs.toByteArray();
}
@Override
public String getHashAlgToSign() {
return "sha-256x2";
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Issuer validation not supported!");
}
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
for (int i = 0; i < unspentOutputs.size(); ++i) {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64));
s = CryptoUtil.toCanonicalised(s);
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
}
byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal);
notifyOnNeedSendPayment(txForSend);
return txForSend;
}
};
}
private final static BigDecimal relayFee = new BigDecimal(0.00001);
@Override
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
coinData.minFee=null;
coinData.maxFee=null;
coinData.normalFee=null;
final ServerApiElectrum serverApiElectrum = new ServerApiElectrum();
final ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener () {
@Override
public void onSuccess(ElectrumRequest electrumRequest) {
BigDecimal fee;
if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) {
try {
fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB
if (fee.equals(BigDecimal.ZERO)) {
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee());
}
// if (calcSize != 0) {
fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size
// } else {
// serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee());
// }
//compare fee to usual relay fee
if (fee.compareTo(relayFee) < 0) {
fee = relayFee;
}
fee = fee.setScale(8, RoundingMode.DOWN);
CoinEngine.Amount feeAmount = new CoinEngine.Amount(fee, ctx.getBlockchain().getCurrency());
coinData.minFee = feeAmount;
coinData.normalFee = feeAmount;
coinData.maxFee = feeAmount;
// if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) {
blockchainRequestsCallbacks.onComplete(true);
// } else {
// blockchainRequestsCallbacks.onProgress();
// }
} catch (JSONException e) {
e.printStackTrace();
}
}
}
@Override
public void onFail(ElectrumRequest electrumRequest) {
ctx.setError(electrumRequest.getError());
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiElectrum.setElectrumRequestData(electrumListener);
serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee());
}
}

View file

@ -28,7 +28,11 @@ public class TokenData extends EthData {
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"),"wei");
if( B.containsKey("BalanceDecimalAlter" )) {
balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei");
}else{
balanceAlter=null;
}
}
@Override
@ -36,7 +40,9 @@ public class TokenData extends EthData {
super.saveToBundle(B);
try {
B.putString("BalanceDecimalAlter", balanceAlter.toString());
if( balanceAlter!=null ) {
B.putString("BalanceDecimalAlter", balanceAlter.toString());
}
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}

View file

@ -1,23 +1,25 @@
package com.tangem.domain.wallet.token;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputFilter;
import android.util.Log;
import com.google.common.base.Strings;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.TLV;
import com.tangem.data.Blockchain;
import com.tangem.data.network.ServerApiInfura;
import com.tangem.data.network.model.InfuraResponse;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.ECDSASignatureETH;
import com.tangem.domain.wallet.EthTransaction;
import com.tangem.domain.wallet.Issuer;
import com.tangem.domain.wallet.Keccak256;
import com.tangem.domain.wallet.TangemCard;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.domain.wallet.eth.EthData;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.tasks.SignTask;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.wallet.R;
@ -27,8 +29,6 @@ import org.bitcoinj.core.ECKey;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
/**
@ -37,6 +37,7 @@ import java.util.Arrays;
public class TokenEngine extends CoinEngine {
private static final String TAG = TokenEngine.class.getSimpleName();
public TokenData coinData = null;
public TokenEngine(TangemContext ctx) throws Exception {
@ -46,6 +47,13 @@ public class TokenEngine extends CoinEngine {
ctx.setCoinData(coinData);
} else if (ctx.getCoinData() instanceof TokenData) {
coinData = (TokenData) ctx.getCoinData();
} else if (ctx.getCoinData() instanceof EthData) {
// special case with receive card data substitution from server at the moment
Bundle B=new Bundle();
ctx.getCoinData().saveToBundle(B);
coinData = new TokenData();
coinData.loadFromBundle(B);
ctx.setCoinData(coinData);
} else {
throw new Exception("Invalid type of Blockchain data for TokenEngine");
}
@ -101,7 +109,7 @@ public class TokenEngine extends CoinEngine {
if (coinData.getBalanceInInternalUnits().notZero()) {
return currency;
} else {
return "ETH";
return Blockchain.Ethereum.getCurrency();
}
} else {
return currency;
@ -120,7 +128,7 @@ public class TokenEngine extends CoinEngine {
@Override
public String getFeeCurrency() {
return "ETH";
return Blockchain.Ethereum.getCurrency();
}
@Override
@ -128,15 +136,15 @@ public class TokenEngine extends CoinEngine {
return ctx.getString(R.string.not_implemented);
}
public static int getEthDecimals() {
private static int getEthDecimals() {
return 18;
}
public int getTokenDecimals() {
private int getTokenDecimals() {
return ctx.getCard().getTokensDecimal();
}
public String getContractAddress(TangemCard card) {
private String getContractAddress(TangemCard card) {
return card.getContractAddress();
}
@ -161,7 +169,7 @@ public class TokenEngine extends CoinEngine {
return true;
}
public boolean isBalanceAlterNotZero() {
private boolean isBalanceAlterNotZero() {
if (coinData == null) return false;
if (coinData.getBalanceAlterInInternalUnits() == null) return false;
return coinData.getBalanceAlterInInternalUnits().notZero();
@ -170,7 +178,7 @@ public class TokenEngine extends CoinEngine {
@Override
public boolean isBalanceNotZero() {
if (coinData == null) return false;
if (coinData.getBalanceInInternalUnits() == null && coinData.getBalanceAlterInInternalUnits() == null ) return false;
if (coinData.getBalanceInInternalUnits() == null && coinData.getBalanceAlterInInternalUnits() == null) return false;
return coinData.getBalanceInInternalUnits().notZero() || coinData.getBalanceAlterInInternalUnits().notZero();
}
@ -185,7 +193,7 @@ public class TokenEngine extends CoinEngine {
// TODO: check why Rate=EthRate
return "";//convertToAmount(coinData.getBalanceInInternalUnits()).toEquivalentString(coinData.getRate());
} else {
if( coinData.getBalanceAlterInInternalUnits()==null ) return "";
if (coinData.getBalanceAlterInInternalUnits() == null) return "";
return convertToAmount(coinData.getBalanceAlterInInternalUnits()).toEquivalentString(coinData.getRateAlter());
}
} catch (Exception e) {
@ -195,11 +203,11 @@ public class TokenEngine extends CoinEngine {
}
@Override
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
public String calculateAddress(byte[] pkUncompressed) {
Keccak256 kec = new Keccak256();
int lenPk = pkUncompressed.length;
if (lenPk < 2) {
throw new IllegalArgumentException("Uncompress public key length is invald");
throw new IllegalArgumentException("Uncompress public key length is invalid");
}
byte[] cleanKey = new byte[lenPk - 1];
for (int i = 0; i < cleanKey.length; ++i) {
@ -219,7 +227,7 @@ public class TokenEngine extends CoinEngine {
public Amount convertToAmount(InternalAmount internalAmount) throws Exception {
if (internalAmount.getCurrency().equals("wei")) {
BigDecimal d = internalAmount.divide(new BigDecimal("1000000000000000000"), getEthDecimals(), RoundingMode.DOWN);
return new Amount(d, "ETH");
return new Amount(d, Blockchain.Ethereum.getCurrency());
} else if (internalAmount.getCurrency().equals(ctx.getCard().getTokenSymbol())) {
BigDecimal p = new BigDecimal(10);
p = p.pow(getTokenDecimals());
@ -236,7 +244,7 @@ public class TokenEngine extends CoinEngine {
@Override
public InternalAmount convertToInternalAmount(Amount amount) throws Exception {
if (amount.getCurrency().equals("ETH")) {
if (amount.getCurrency().equals(Blockchain.Ethereum.getCurrency())) {
BigDecimal d = amount.multiply(new BigDecimal("1000000000000000000"));
return new InternalAmount(d, "wei");
} else if (amount.getCurrency().equals(ctx.getCard().getTokenSymbol())) {
@ -282,15 +290,15 @@ public class TokenEngine extends CoinEngine {
@Override
public Uri getShareWalletUriExplorer() {
return Uri.parse("https://etherscan.io/token/" + getContractAddress(ctx.getCard()) + "?a=" + ctx.getCard().getWallet());
return Uri.parse("https://etherscan.io/token/" + getContractAddress(ctx.getCard()) + "?a=" + ctx.getCoinData().getWallet());
}
@Override
public Uri getShareWalletUri() {
if (ctx.getCard().getDenomination() != null) {
return Uri.parse("ethereum:" + ctx.getCard().getWallet());// + "?value=" + mCard.getDenomination() +"e18");
return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18");
} else {
return Uri.parse("ethereum:" + ctx.getCard().getWallet());
return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());
}
}
@ -317,7 +325,7 @@ public class TokenEngine extends CoinEngine {
try {
if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) {
balance = convertToAmount(coinData.getBalanceInInternalUnits());
} else if (amount.getCurrency().equals("ETH") && coinData.getBalanceInInternalUnits().isZero()) {
} else if (amount.getCurrency().equals(Blockchain.Ethereum.getCurrency()) && coinData.getBalanceInInternalUnits().isZero()) {
balance = convertToAmount(coinData.getBalanceAlterInInternalUnits());
} else {
return false;
@ -334,7 +342,7 @@ public class TokenEngine extends CoinEngine {
if (!hasBalanceInfo()) return false;
try {
Amount balance = convertToAmount(coinData.getBalanceAlterInInternalUnits());
Amount balanceETH = convertToAmount(coinData.getBalanceAlterInInternalUnits());
if (fee == null || amount == null || fee.isZero() || amount.isZero())
return false;
@ -342,22 +350,20 @@ public class TokenEngine extends CoinEngine {
if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) {
// token transaction
if( fee.compareTo(balance)>0 )
if (fee.compareTo(balanceETH) > 0)
return false;
} else if (amount.getCurrency().equals("ETH") && coinData.getBalanceInInternalUnits().isZero()) {
// standart ETH transaction
try {
BigDecimal cardBalance = getBalance();
if (isFeeIncluded && amount.compareTo(cardBalance) > 0)
} else if (amount.getCurrency().equals(Blockchain.Ethereum.getCurrency()) && coinData.getBalanceInInternalUnits().isZero()) {
// standard ETH transaction
// try {
if (isFeeIncluded && (amount.compareTo(balanceETH) > 0 || fee.compareTo(balanceETH) > 0))
return false;
if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0)
if (!isFeeIncluded && amount.add(fee).compareTo(balanceETH) > 0)
return false;
} catch (NumberFormatException e) {
e.printStackTrace();
}
// } catch (NumberFormatException e) {
// e.printStackTrace();
// }
} else
{
@ -425,19 +431,19 @@ public class TokenEngine extends CoinEngine {
}
@Override
public byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception {
if (amountValue.getCurrency().equals("ETH")) {
return signETH(feeValue, amountValue, IncFee, targetAddress, protocol);
public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
if (amountValue.getCurrency().equals(Blockchain.Ethereum.getCurrency())) {
return constructPaymentETH(feeValue, amountValue, IncFee, targetAddress);
} else {
return signToken(feeValue, amountValue, IncFee, targetAddress, protocol);
return constructPaymentToken(feeValue, amountValue, IncFee, targetAddress);
}
}
public byte[] signETH(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception {
private SignTask.PaymentToSign constructPaymentETH(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception {
Log.e(TAG, "Construct ETH payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding"));
BigInteger nonceValue = coinData.getConfirmedTXCount();
byte[] pbKey = ctx.getCard().getWalletPublicKey();
boolean flag = (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = ctx.getCard().getIssuer();
BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact();
BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact();
@ -446,10 +452,8 @@ public class TokenEngine extends CoinEngine {
weiAmount = weiAmount.subtract(weiFee);
}
BigInteger nonce = nonceValue;
BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000));
BigInteger gasLimit = BigInteger.valueOf(21000);
// Integer chainId = ctx.getBlockchain() == Blockchain.Ethereum ? EthTransaction.ChainEnum.Mainnet.getValue() : EthTransaction.ChainEnum.Rinkeby.getValue();
Integer chainId = EthTransaction.ChainEnum.Mainnet.getValue(); // Token support on main net only!!!
@ -459,62 +463,84 @@ public class TokenEngine extends CoinEngine {
to = to.substring(2);
}
EthTransaction tx = EthTransaction.create(to, weiAmount, nonce, gasPrice, gasLimit, chainId);
final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
return new SignTask.PaymentToSign() {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash;
}
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
@Override
public byte[][] getHashesToSign() {
byte[][] hashesForSign = new byte[1][];
hashesForSign[0] = tx.getRawHash();
return hashesForSign;
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
@Override
public byte[] getRawDataToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for ETH");
}
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
@Override
public String getHashAlgToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for ETH");
}
if (!f) {
Log.e("ETH-CHECK", "sign Failed.");
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Transaction validation by issuer not supported in this version");
}
tx.signature = new ECDSASignatureETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
byte[] for_hash = tx.getRawHash();
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
byte[] realTX = tx.getEncoded();
return realTX;
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if (!f) {
Log.e("ETH-CHECK", "sign Failed.");
}
tx.signature = new ECDSASignatureETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e(TAG, "invalid v");
throw new Exception("Error in EthEngine - invalid v");
}
tx.signature.v = (byte) v;
Log.e(TAG,"ETH_v "+ String.valueOf(v));
byte[] txForSend = tx.getEncoded();
notifyOnNeedSendPayment(txForSend);
return txForSend;
}
};
}
public byte[] signToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception {
private SignTask.PaymentToSign constructPaymentToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception {
Log.e(TAG, "Construct TOKEN payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding"));
BigInteger nonceValue = coinData.getConfirmedTXCount();
byte[] pbKey = ctx.getCard().getWalletPublicKey();
boolean flag = (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
Issuer issuer = ctx.getCard().getIssuer();
// boolean flag = (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer);
// Issuer issuer = ctx.getCard().getIssuer();
BigInteger gigaK = BigInteger.valueOf(1000000000L);
// BigInteger gigaK = BigInteger.valueOf(1000000000L);
BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact();
InternalAmount amountDec=convertToInternalAmount(amountValue);
InternalAmount amountDec = convertToInternalAmount(amountValue);
BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10);
//amount = amount.subtract(fee);
BigInteger nonce = nonceValue;
BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(60000));
BigInteger gasLimit = BigInteger.valueOf(60000);
Integer chainId = EthTransaction.ChainEnum.Mainnet.getValue();
@ -545,41 +571,239 @@ public class TokenEngine extends CoinEngine {
byte[] data = BTCUtils.fromHex(cmd);
EthTransaction tx = EthTransaction.create(contractAddress, amountZero, nonce, gasPrice, gasLimit, chainId, data);
EthTransaction tx = EthTransaction.create(contractAddress, amountZero, nonceValue, gasPrice, gasLimit, chainId, data);
byte[][] hashesForSign = new byte[1][];
byte[] for_hash = tx.getRawHash();
hashesForSign[0] = for_hash;
return new SignTask.PaymentToSign() {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash;
}
byte[] signFromCard = null;
try {
signFromCard = protocol.run_SignHashes(PINStorage.getPIN2(), hashesForSign, flag, null, issuer).getTLV(TLV.Tag.TAG_Signature).Value;
// TODO slice signFromCard to hashes.length parts
} catch (Exception ex) {
Log.e("ETH", ex.getMessage());
return null;
}
@Override
public byte[][] getHashesToSign() {
byte[][] hashesForSign = new byte[1][];
hashesForSign[0] = tx.getRawHash();
return hashesForSign;
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
@Override
public byte[] getRawDataToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for ETH");
}
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
@Override
public String getHashAlgToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for ETH");
}
if (!f) {
Log.e("ETH-CHECK", "sign Failed.");
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Transaction validation by issuer not supported in this version");
}
tx.signature = new ECDSASignatureETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e("ETH", "invalid v");
return null;
}
tx.signature.v = (byte) v;
Log.e("ETH_v", String.valueOf(v));
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
byte[] for_hash = tx.getRawHash();
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey);
if (!f) {
Log.e("ETH-CHECK", "sign Failed.");
}
tx.signature = new ECDSASignatureETH(r, s);
int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey);
if (v != 27 && v != 28) {
Log.e(TAG, "invalid v");
throw new Exception("Error in EthEngine - invalid v");
}
tx.signature.v = (byte) v;
Log.e(TAG,"ETH_v: "+ String.valueOf(v));
byte[] txForSend = tx.getEncoded();
notifyOnNeedSendPayment(txForSend);
return txForSend;
}
};
byte[] realTX = tx.getEncoded();
return realTX;
}
@Override
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
final ServerApiInfura serverApiInfura = new ServerApiInfura();
// request infura listener
ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() {
@Override
public void onSuccess(String method, InfuraResponse infuraResponse) {
switch (method) {
case ServerApiInfura.INFURA_ETH_GET_BALANCE: {
String balanceCap = infuraResponse.getResult();
balanceCap = balanceCap.substring(2);
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceReceived(true);
coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
// Log.i("$TAG eth_get_balance", balanceCap)
}
break;
case ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT: {
String nonce = infuraResponse.getResult();
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
case ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT: {
String pending = infuraResponse.getResult();
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
//
case ServerApiInfura.INFURA_ETH_CALL: {
try {
String balanceCap = infuraResponse.getResult();
balanceCap = balanceCap.substring(2);
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol));
// Log.i("$TAG eth_call", balanceCap)
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
} else {
ctx.setError("Terminated by user");
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
if (serverApiInfura.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
} else {
blockchainRequestsCallbacks.onProgress();
}
}
@Override
public void onFail(String method, String message) {
if (!serverApiInfura.isRequestsSequenceCompleted()) {
ctx.setError(message);
blockchainRequestsCallbacks.onComplete(false);
}
}
};
serverApiInfura.setInfuraResponse(infuraBodyListener);
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
}
@Override
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
ServerApiInfura serverApiInfura = new ServerApiInfura();
// request infura eth gasPrice listener
ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() {
@Override
public void onSuccess(String method, InfuraResponse infuraResponse) {
String gasPrice = infuraResponse.getResult();
gasPrice = gasPrice.substring(2);
// rounding gas price to integer gwei
BigInteger l = new BigInteger(gasPrice, 16);
Log.i(TAG, "Infura gas price: "+gasPrice+" ("+l.toString()+")");
BigInteger m;
if (!amount.getCurrency().equals(Blockchain.Ethereum.getCurrency())) m = BigInteger.valueOf(60000);
else m = BigInteger.valueOf(21000);
Log.i(TAG, "fee multiplier: "+m.toString());
CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei");
CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(12)).divide(BigInteger.valueOf(10)).multiply(m), "wei");
CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(15)).divide(BigInteger.valueOf(10)).multiply(m), "wei");
Log.i(TAG, "min fee : "+weiMinFee.toValueString()+" wei");
Log.i(TAG, "normal fee: "+weiNormalFee.toValueString()+" wei");
Log.i(TAG, "max fee : "+weiMaxFee.toValueString()+" wei");
try {
coinData.minFee = convertToAmount(weiMinFee);
coinData.normalFee = convertToAmount(weiNormalFee);
coinData.maxFee = convertToAmount(weiMaxFee);
Log.i(TAG, "min fee : "+coinData.minFee.toString());
Log.i(TAG, "normal fee: "+coinData.normalFee.toString());
Log.i(TAG, "max fee : "+coinData.maxFee.toString());
} catch (Exception e) {
e.printStackTrace();
}
blockchainRequestsCallbacks.onComplete(true);
}
@Override
public void onFail(String method, String message) {
ctx.setError(message);
blockchainRequestsCallbacks.onComplete(false);
}
};
serverApiInfura.setInfuraResponse(infuraBodyListener);
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", "");
}
@Override
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
String txStr = String.format("0x%s", BTCUtils.toHex(txForSend));
ServerApiInfura serverApiInfura = new ServerApiInfura();
// request infura eth gasPrice listener
ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() {
@Override
public void onSuccess(String method, InfuraResponse infuraResponse) {
if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
if (infuraResponse.getResult().isEmpty()) {
ctx.setError("Rejected by node: " + infuraResponse.getError());
blockchainRequestsCallbacks.onComplete(false);
} else {
BigInteger nonce = coinData.getConfirmedTXCount();
nonce=nonce.add(BigInteger.valueOf(1));
coinData.setConfirmedTXCount(nonce);
ctx.setError(null);
blockchainRequestsCallbacks.onComplete(true);
}
}
}
@Override
public void onFail(String method, String message) {
if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
ctx.setError(message);
blockchainRequestsCallbacks.onComplete(false);
}
}
};
serverApiInfura.setInfuraResponse(infuraBodyListener);
serverApiInfura.infura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr);
}
}

View file

@ -9,60 +9,40 @@ import android.support.v7.app.AppCompatActivity
import android.text.Editable
import android.text.Html
import android.text.TextWatcher
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.Toast
import com.tangem.data.network.ElectrumRequest
import com.tangem.data.network.ServerApiCommon
import com.tangem.data.network.ServerApiElectrum
import com.tangem.data.network.ServerApiInfura
import com.tangem.data.network.model.InfuraResponse
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.domain.wallet.btc.BtcData
import com.tangem.util.*
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.event.TransactionFinishWithError
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.data.loadFromBundle
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_confirm_payment.*
import org.json.JSONException
import org.greenrobot.eventbus.EventBus
import java.io.IOException
import java.math.BigDecimal
import java.math.BigInteger
import java.math.RoundingMode
import java.util.*
class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
private const val REQUEST_CODE_SIGN_PAYMENT = 1
private const val REQUEST_CODE_REQUEST_PIN2 = 2
}
private var nfcManager: NfcManager? = null
private var serverApiCommon: ServerApiCommon = ServerApiCommon()
private var serverApiInfura: ServerApiInfura = ServerApiInfura()
private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum()
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private lateinit var amount: CoinEngine.Amount
private var feeRequestSuccess = false
// private var balanceRequestSuccess = false
private var minFee: CoinEngine.Amount? = null
private var maxFee: CoinEngine.Amount? = null
private var normalFee: CoinEngine.Amount? = null
private var isIncludeFee: Boolean = true
private var requestPIN2Count = 0
private var nodeCheck = true
private var dtVerified: Date? = null
private var calcSize: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_confirm_payment)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
ctx = TangemContext.loadFromBundle(this, intent.extras)
@ -72,16 +52,16 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
val html = Html.fromHtml(engine!!.balanceHTML)
tvBalance.text = html
isIncludeFee = intent.getBooleanExtra(SignPaymentActivity.EXTRA_FEE_INCLUDED, true)
isIncludeFee = intent.getBooleanExtra(Constant.EXTRA_FEE_INCLUDED, true)
if (isIncludeFee)
tvIncFee.setText(R.string.including_fee)
else
tvIncFee.setText(R.string.not_including_fee)
amount = CoinEngine.Amount(intent.getStringExtra(SignPaymentActivity.EXTRA_AMOUNT), intent.getStringExtra(SignPaymentActivity.EXTRA_AMOUNT_CURRENCY))
amount = CoinEngine.Amount(intent.getStringExtra(Constant.EXTRA_AMOUNT), intent.getStringExtra(Constant.EXTRA_AMOUNT_CURRENCY))
if (ctx.blockchain == Blockchain.Token && amount.currency!="ETH")
if (ctx.blockchain == Blockchain.Token && amount.currency != Blockchain.Ethereum.currency)
tvIncFee.visibility = View.INVISIBLE
else
tvIncFee.visibility = View.VISIBLE
@ -90,36 +70,12 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
tvCurrency.text = engine.balanceCurrency
tvCurrency2.text = engine.feeCurrency
tvCardID.text = ctx.card!!.cidDescription
etWallet.setText(intent.getStringExtra(SignPaymentActivity.EXTRA_TARGET_ADDRESS))
etWallet.setText(intent.getStringExtra(Constant.EXTRA_TARGET_ADDRESS))
etFee.setText("")
btnSend.visibility = View.INVISIBLE
feeRequestSuccess = false
// balanceRequestSuccess = false
if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token) {
rgFee.isEnabled = false
requestInfura(ServerApiInfura.INFURA_ETH_GAS_PRICE)
} else {
rgFee.isEnabled = true
// requestElectrum(ctx.card, ElectrumRequest.checkBalance(ctx.card!!.wallet))
calcSize = 256
try {
calcSize = buildSize(etWallet!!.text.toString(), "0.00", etAmount.text.toString())
} catch (ex: Exception) {
Log.e("Build Fee error", ex.message)
}
ctx.coinData!!.resetFailedBalanceRequestCounter()
progressBar.visibility = View.VISIBLE
requestEstimateFee()
}
rgFee.isEnabled = !(ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token || ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Litecoin)
// set listeners
rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) }
@ -190,158 +146,58 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
requestPIN2Count = 0
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
intent.putExtra(SignPaymentActivity.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2)
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_)
}
// request electrum listener
// val electrumBodyListener: ServerApiHelperElectrum.ElectrumRequestDataListener = object : ServerApiHelperElectrum.ElectrumRequestDataListener {
// override fun onSuccess(electrumRequest: ElectrumRequest?) {
// if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_GetBalance)) {
// try {
// if (etFee.text.toString().isEmpty()) etFee.setText(getString(R.string.empty))
// val engine = CoinEngineFactory.create(ctx)
// val balance = engine.convertToAmount(CoinEngine.InternalAmount(electrumRequest.result.getLong("confirmed") + electrumRequest.result.getLong("unconfirmed"), "Satoshi"))
// val amount = CoinEngine.Amount(etAmount.text.toString(), ctx.blockchain.currency)
// if (balance < amount) {
// etFee.error = getString(R.string.not_enough_funds)
// } else {
// etFee.error = null
// balanceRequestSuccess = true
// if (feeRequestSuccess && balanceRequestSuccess) {
// btnSend.visibility = View.VISIBLE
// }
// dtVerified = Date()
// nodeCheck = true
// }
// } catch (e: JSONException) {
// e.printStackTrace()
//// requestElectrum(ctx.card!!, ElectrumRequest.checkBalance(ctx.card!!.wallet))
// }
// }
// }
//
// override fun onFail(message: String?) {
// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes))
// }
//
// }
// serverApiHelperElectrum.setElectrumRequestData(electrumBodyListener)
val coinEngine = CoinEngineFactory.create(ctx)
// request infura eth gasPrice listener
val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener {
override fun onSuccess(method: String, infuraResponse: InfuraResponse) {
when (method) {
ServerApiInfura.INFURA_ETH_GAS_PRICE -> {
var gasPrice = infuraResponse.result
gasPrice = gasPrice.substring(2)
//TODO - remove Gwei
// rounding gas price to integer gwei
val l = BigInteger(gasPrice, 16).divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L))
progressBar.visibility = View.VISIBLE
//val m = if (ctx.blockchain==Blockchain.Token) BigInteger.valueOf(60000) else BigInteger.valueOf(21000)
val m = if (amount.currency != "ETH") BigInteger.valueOf(60000) else BigInteger.valueOf(21000)
val weiMinFee = CoinEngine.InternalAmount(l.multiply(m), "wei")
val weiNormalFee = CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(12)).divide(BigDecimal.valueOf(10)), "wei")
val weiMaxFee = CoinEngine.InternalAmount(weiMinFee.multiply(BigDecimal.valueOf(15)).divide(BigDecimal.valueOf(10)), "wei")
coinEngine!!.requestFee(
object : CoinEngine.BlockchainRequestsCallbacks {
override fun onComplete(success: Boolean) {
if (success) {
onProgress()
progressBar.visibility = View.INVISIBLE
dtVerified = Date()
} else {
finishWithError(Activity.RESULT_CANCELED, ctx.error)
}
}
minFee = engine.convertToAmount(weiMinFee)
normalFee = engine.convertToAmount(weiNormalFee)
maxFee = engine.convertToAmount(weiMaxFee)
override fun onProgress() {
doSetFee(rgFee.checkedRadioButtonId)
//etFee.setText(weiNormalFee.toValueString())
etFee.error = null
btnSend.visibility = View.VISIBLE
feeRequestSuccess = true
// balanceRequestSuccess = true
dtVerified = Date()
}
}
}
override fun onFail(method: String, message: String) {
when (method) {
ServerApiInfura.INFURA_ETH_GAS_PRICE -> {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain))
}
}
}
}
serverApiInfura.setInfuraResponse(infuraBodyListener)
// request estimate fee listener
val estimateFeeListener: ServerApiCommon.EstimateFeeListener = object : ServerApiCommon.EstimateFeeListener {
override fun onSuccess(blockCount: Int, estimateFeeResponse: String?) {
var fee: BigDecimal?
fee = BigDecimal(estimateFeeResponse) // BTC per 1 kb
if (fee == BigDecimal.ZERO) {
progressBar.visibility = View.INVISIBLE
requestEstimateFee()
}
if (calcSize.toLong() != 0L) {
fee = fee.multiply(BigDecimal(calcSize.toLong())).divide(BigDecimal(1024)) // per Kb -> per byte
} else {
requestEstimateFee()
}
progressBar.visibility = View.INVISIBLE
fee = fee!!.setScale(8, RoundingMode.DOWN)
when (blockCount) {
ServerApiCommon.ESTIMATE_FEE_MINIMAL -> {
minFee = CoinEngine.Amount(fee, engine.feeCurrency)
if (rgFee.checkedRadioButtonId == R.id.rbMinimalFee) doSetFee(rgFee.checkedRadioButtonId)
}
ServerApiCommon.ESTIMATE_FEE_NORMAL -> {
normalFee = CoinEngine.Amount(fee, engine.feeCurrency)
if (rgFee.checkedRadioButtonId == R.id.rbNormalFee) doSetFee(rgFee.checkedRadioButtonId)
override fun allowAdvance(): Boolean {
return UtilHelper.isOnline(this@ConfirmPaymentActivity)
}
ServerApiCommon.ESTIMATE_FEE_PRIORITY -> {
maxFee = CoinEngine.Amount(fee, engine.feeCurrency)
if (rgFee.checkedRadioButtonId == R.id.rbMaximumFee) doSetFee(rgFee.checkedRadioButtonId)
}
}
etFee.error = null
feeRequestSuccess = true
if (feeRequestSuccess)
// if (feeRequestSuccess && balanceRequestSuccess)
btnSend.visibility = View.VISIBLE
dtVerified = Date()
}
override fun onFail(message: String?) {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_calculate_fee_wrong_data_received_from_node))
}
}
serverApiCommon.setEstimateFee(estimateFeeListener)
},
etWallet.text.toString(),
amount)
}
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
nfcManager.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_SIGN_PAYMENT) {
if (requestCode == Constant.REQUEST_CODE_SIGN_PAYMENT) {
if (data != null && data.extras != null) {
if (data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) {
val updatedCard = TangemCard(data.getStringExtra("UID"))
@ -349,28 +205,30 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
ctx.card = updatedCard
}
}
if (resultCode == SignPaymentActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
if (resultCode == Constant.RESULT_INVALID_PIN_ && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
intent.putExtra(SignPaymentActivity.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2)
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_)
return
}
setResult(resultCode, data)
finish()
} else if (requestCode == REQUEST_CODE_REQUEST_PIN2) {
} else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) {
if (resultCode == Activity.RESULT_OK) {
val intent = Intent(baseContext, SignPaymentActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra(SignPaymentActivity.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_AMOUNT, etAmount.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_FEE, etFee.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, REQUEST_CODE_SIGN_PAYMENT)
intent.putExtra(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
intent.putExtra(Constant.EXTRA_AMOUNT, etAmount.text.toString())
intent.putExtra(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
intent.putExtra(Constant.EXTRA_FEE, etFee.text.toString())
intent.putExtra(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, Constant.REQUEST_CODE_SIGN_PAYMENT)
} else
Toast.makeText(baseContext, R.string.pin_2_is_required_to_sign_the_payment, Toast.LENGTH_LONG).show()
}
@ -390,120 +248,44 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager!!.ignoreTag(tag)
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
// TODO - move to BtcEngine
@Throws(Exception::class)
internal fun buildSize(outputAddress: String, outFee: String, outAmount: String): Int {
val myAddress = ctx.card!!.wallet
val pbKey = ctx.card!!.walletPublicKey
val pbComprKey = ctx.card!!.walletPublicKeyRar
// build script for our address
val rawTxList = (ctx.coinData!! as BtcData).unspentTransactions
val outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes
// collect unspent
val unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend)
var fullAmount: Long = 0
for (i in unspentOutputs.indices) {
fullAmount += unspentOutputs[i].value
}
// get first unspent
// val outPut = unspentOutputs[0]
// val outPutIndex = outPut.outputIndex
// get prev TX id;
// val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2";
val fees = FormatUtil.ConvertStringToLong(outFee)
var amount = FormatUtil.ConvertStringToLong(outAmount)
amount -= fees
val change = fullAmount - fees - amount
if (amount + fees > fullAmount) {
throw Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount))
}
val hashesForSign = arrayOfNulls<ByteArray>(unspentOutputs.size)
for (i in unspentOutputs.indices) {
val newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change)
val hashData = Util.calculateSHA256(newTX)
val doubleHashData = Util.calculateSHA256(hashData)
// Log.e("TX_BODY_1", BTCUtils.toHex(newTX))
// Log.e("TX_HASH_1", BTCUtils.toHex(hashData))
// Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData))
unspentOutputs[i].bodyDoubleHash = doubleHashData
unspentOutputs[i].bodyHash = hashData
hashesForSign[i] = doubleHashData
}
val signFromCard = ByteArray(64 * unspentOutputs.size)
for (i in unspentOutputs.indices) {
val r = BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64))
val s = BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64))
val encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey)
unspentOutputs[i].scriptForBuild = encodingSign
}
val realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change)
return realTX.size
}
private fun requestElectrum(card: TangemCard, electrumRequest: ElectrumRequest) {
if (UtilHelper.isOnline(this)) {
serverApiElectrum.electrumRequestData(card, electrumRequest)
} else
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain))
}
private fun requestInfura(method: String) {
if (UtilHelper.isOnline(this)) {
serverApiInfura.infura(method, 67, ctx.card!!.wallet, "", "")
} else
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain))
}
private fun requestEstimateFee() {
serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY)
serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_NORMAL)
serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL)
}
private fun doSetFee(checkedRadioButtonId: Int) {
var txtFee = ""
when (checkedRadioButtonId) {
R.id.rbMinimalFee ->
if (minFee != null)
txtFee = minFee!!.toValueString()
else
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain))
if (ctx.coinData.minFee != null) {
txtFee = ctx.coinData.minFee!!.toValueString()
btnSend.visibility = View.VISIBLE
} else
btnSend.visibility = View.INVISIBLE
R.id.rbNormalFee ->
if (normalFee != null)
txtFee = normalFee!!.toValueString()
else
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain))
if (ctx.coinData.normalFee != null) {
txtFee = ctx.coinData.normalFee!!.toValueString()
btnSend.visibility = View.VISIBLE
} else
btnSend.visibility = View.INVISIBLE
R.id.rbMaximumFee ->
if (maxFee != null)
txtFee = maxFee!!.toValueString()
else
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_obtain_data_from_blockchain))
if (ctx.coinData.maxFee != null) {
txtFee = ctx.coinData.maxFee!!.toValueString()
btnSend.visibility = View.VISIBLE
} else
btnSend.visibility = View.INVISIBLE
}
etFee.setText(txtFee.replace(',', '.'))
}
private fun finishWithError(errorCode: Int, message: String) {
val transactionFinishWithError = TransactionFinishWithError()
transactionFinishWithError.message = message
EventBus.getDefault().post(transactionFinishWithError)
val intent = Intent()
intent.putExtra("message", message)
setResult(errorCode, intent)

View file

@ -1,6 +1,7 @@
package com.tangem.presentation.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
@ -12,27 +13,32 @@ import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.data.nfc.CreateNewWalletTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.App
import com.tangem.Constant
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.android.reader.NfcReader
import com.tangem.tangemcard.data.asBundle
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.tasks.CreateNewWalletTask
import com.tangem.tangemcard.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_create_new_wallet.*
class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = CreateNewWalletActivity::class.java.simpleName
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, CreateNewWalletActivity::class.java)
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
return intent
}
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var nfcManager: NfcManager? = null
private var createNewWalletTask: CreateNewWalletTask? = null
private var lastReadSuccess = true
@ -43,8 +49,6 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_create_new_wallet)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
ctx = TangemContext.loadFromBundle(this, intent.extras)
@ -71,11 +75,11 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
} else {
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 65000
}
createNewWalletTask = CreateNewWalletTask(this, ctx.card, nfcManager, isoDep, this)
createNewWalletTask = CreateNewWalletTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this)
createNewWalletTask!!.start()
} else {
// Log.d(TAG, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")");
nfcManager!!.ignoreTag(isoDep.tag)
nfcManager.ignoreTag(isoDep.tag)
}
} catch (e: Exception) {
@ -85,11 +89,11 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
nfcManager!!.onPause()
nfcManager.onPause()
if (createNewWalletTask != null)
createNewWalletTask!!.cancel(true)
super.onPause()
@ -97,7 +101,7 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
public override fun onStop() {
// dismiss enable NFC dialog
nfcManager!!.onStop()
nfcManager.onStop()
if (createNewWalletTask != null)
createNewWalletTask!!.cancel(true)
super.onStop()
@ -138,8 +142,8 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
val intent = Intent()
intent.putExtra("message", "Cannot create wallet. Make sure you enter correct PIN2!")
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(RESULT_INVALID_PIN, intent)
intent.putExtra("Card", cardProtocol.card!!.asBundle)
setResult(Constant.RESULT_INVALID_PIN, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
@ -177,7 +181,6 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
}
override fun onReadCancel() {
createNewWalletTask = null
progressBar!!.postDelayed({
@ -192,7 +195,7 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback,
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.OnReadWait(this, msec)
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {

View file

@ -13,31 +13,41 @@ import android.support.v7.app.AppCompatActivity
import android.text.Html
import android.view.View
import android.widget.Toast
import com.tangem.data.nfc.VerifyCardTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.android.reader.NfcReader
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.data.asBundle
import com.tangem.tangemcard.data.loadFromBundle
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.tasks.VerifyCardTask
import com.tangem.tangemcard.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_empty_wallet.*
import javax.inject.Inject
class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = EmptyWalletActivity::class.java.simpleName
private const val REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = 2
private const val REQUEST_CODE_REQUEST_PIN2 = 3
private const val REQUEST_CODE_VERIFY_CARD = 4
fun callingIntent(context: Context) = Intent(context, EmptyWalletActivity::class.java)
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, EmptyWalletActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
}
private var nfcManager: NfcManager? = null
@Inject
internal lateinit var navigator: Navigator
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var lastReadSuccess = true
private var verifyCardTask: VerifyCardTask? = null
private var requestPIN2Count = 0
@ -46,24 +56,24 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_empty_wallet)
MainActivity.commonInit(applicationContext)
App.getNavigatorComponent().inject(this)
nfcManager = NfcManager(this, this)
ctx = TangemContext.loadFromBundle(this, intent.extras)
tvIssuer.text = ctx.card!!.issuerDescription
//tvBlockchain.text = ctx.card!!.blockchainName
if (ctx.card!!.tokenSymbol.length > 1) {
val html = Html.fromHtml(ctx.card!!.blockchainName)
val html = Html.fromHtml(ctx.blockchainName)
tvBlockchain.text = html
} else
tvBlockchain.text = ctx.card!!.blockchainName
tvBlockchain.text = ctx.blockchainName
tvCardID.text = ctx.card!!.cidDescription
imgBlockchain.setImageResource(ctx.card!!.blockchain.getImageResource(this, ctx.card!!.tokenSymbol))
imgBlockchain.setImageResource(ctx.blockchain.getImageResource(this, ctx.card!!.tokenSymbol))
if (ctx.card!!.useDefaultPIN1()!!) {
if (ctx.card!!.useDefaultPIN1()) {
imgPIN.setImageResource(R.drawable.unlock_pin1)
imgPIN.setOnClickListener { Toast.makeText(this@EmptyWalletActivity, R.string.this_banknote_protected_default_PIN1_code, Toast.LENGTH_LONG).show() }
} else {
@ -97,27 +107,27 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2)
}
}
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
nfcManager.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) {
if (requestCode == Constant.REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
@ -132,21 +142,21 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2)
return
}
}
setResult(resultCode, data)
finish()
} else if (requestCode == REQUEST_CODE_REQUEST_PIN2) {
} else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2) {
if (resultCode == Activity.RESULT_OK) {
doCreateNewWallet()
navigator.showCreateNewWallet(this, ctx)
}
}
}
@ -158,11 +168,11 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
if (ctx.card!!.uid != sUID) {
// Log.d(TAG, "Invalid UID: " + sUID);
nfcManager!!.ignoreTag(isoDep.tag)
LOG.d(TAG, "Invalid UID: $sUID")
nfcManager.ignoreTag(isoDep.tag)
return
} else {
// Log.v(TAG, "UID: " + sUID);
LOG.d(TAG, "UID: $sUID")
}
if (lastReadSuccess) {
@ -171,7 +181,7 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
isoDep.timeout = 65000
}
//lastTag = tag;
verifyCardTask = VerifyCardTask(this, ctx.card, nfcManager, isoDep, this)
verifyCardTask = VerifyCardTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, App.firmwaresStorage, this)
verifyCardTask!!.start()
} catch (e: Exception) {
e.printStackTrace()
@ -244,7 +254,7 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.OnReadWait(this, msec)
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {
@ -255,11 +265,4 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
WaitSecurityDelayDialog.onReadAfterRequest(this)
}
private fun doCreateNewWallet() {
val intent = Intent(this, CreateNewWalletActivity::class.java)
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
startActivityForResult(intent, REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY)
}
}

View file

@ -7,7 +7,9 @@ import android.nfc.Tag
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.fragment.LoadedWallet
import com.tangem.wallet.R
import javax.inject.Inject
@ -18,10 +20,10 @@ class LoadedWalletActivity : AppCompatActivity() {
lateinit var navigator: Navigator
companion object {
fun callingIntent(context: Context, lastTag: Tag, cardInfo: Bundle): Intent {
fun callingIntent(context: Context, lastTag: Tag, ctx: TangemContext): Intent {
val intent = Intent(context, LoadedWalletActivity::class.java)
intent.putExtra(MainActivity.EXTRA_LAST_DISCOVERED_TAG, lastTag)
intent.putExtras(cardInfo)
intent.putExtra(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
ctx.saveToIntent(intent)
return intent
}
}
@ -32,8 +34,6 @@ class LoadedWalletActivity : AppCompatActivity() {
App.getNavigatorComponent().inject(this)
MainActivity.commonInit(applicationContext)
if (intent.extras!!.containsKey(NfcAdapter.EXTRA_TAG)) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null) {

View file

@ -1,9 +1,12 @@
package com.tangem.presentation.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
@ -11,19 +14,19 @@ import kotlinx.android.synthetic.main.activity_logo.*
import javax.inject.Inject
class LogoActivity : AppCompatActivity() {
companion object {
val TAG: String = LogoActivity::class.java.simpleName
const val EXTRA_AUTO_HIDE = "extra_auto_hide"
const val MILLIS_AUTO_HIDE = 1000
fun callingIntent(context: Context, autoHide: Boolean): Intent {
val intent = Intent(context, LogoActivity::class.java)
intent.putExtra(Constant.EXTRA_AUTO_HIDE, autoHide)
return intent
}
}
private val hideRunnable = Runnable { this.hide() }
@Inject
internal lateinit var navigator: Navigator
private val hideRunnable = Runnable { this.hide() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_logo)
@ -42,8 +45,8 @@ class LogoActivity : AppCompatActivity() {
else
tvAppVersion.text = "BETA v." + BuildConfig.VERSION_NAME
if (intent.getBooleanExtra(EXTRA_AUTO_HIDE, true))
ivLogo.postDelayed(hideRunnable, MILLIS_AUTO_HIDE.toLong())
if (intent.getBooleanExtra(Constant.EXTRA_AUTO_HIDE, true))
ivLogo.postDelayed(hideRunnable, Constant.MILLIS_AUTO_HIDE.toLong())
}
private fun hide() {

View file

@ -25,20 +25,25 @@ import android.widget.RelativeLayout
import android.widget.Toast
import com.scottyab.rootbeer.RootBeer
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Logger
import com.tangem.data.db.PINStorage
import com.tangem.data.network.ServerApiCommon
import com.tangem.data.nfc.DeviceNFCAntennaLocation
import com.tangem.data.nfc.ReadCardInfoTask
import com.tangem.di.Navigator
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.Firmwares
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.RootFoundDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.tangemcard.android.nfc.DeviceNFCAntennaLocation
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.android.reader.NfcReader
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.data.loadFromBundle
import com.tangem.tangemcard.data.saveToBundle
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.tasks.ReadCardInfoTask
import com.tangem.util.CommonUtil
import com.tangem.util.LOG
import com.tangem.util.PhoneUtility
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
@ -51,29 +56,14 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
companion object {
val TAG: String = MainActivity::class.java.simpleName
private const val REQUEST_CODE_SEND_EMAIL = 3
private const val REQUEST_CODE_ENTER_PIN_ACTIVITY = 2
const val REQUEST_CODE_SHOW_CARD_ACTIVITY = 1
private const val REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS = 3
const val EXTRA_LAST_DISCOVERED_TAG = "extra_last_tag"
fun callingIntent(context: Context) = Intent(context, MainActivity::class.java)
fun commonInit(context: Context) {
if (PINStorage.needInit())
PINStorage.init(context)
if (Issuer.needInit())
Issuer.init(context)
if (Firmwares.needInit())
Firmwares.init(context)
}
}
private var nfcManager: NfcManager? = null
@Inject
internal lateinit var navigator: Navigator
private lateinit var nfcManager: NfcManager
private var zipFile: File? = null
private var antenna: DeviceNFCAntennaLocation? = null
private var unsuccessReadCount = 0
@ -81,9 +71,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
private var readCardInfoTask: ReadCardInfoTask? = null
private var onNfcReaderCallback: NfcAdapter.ReaderCallback? = null
@Inject
internal lateinit var navigator: Navigator
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) {
@ -107,8 +94,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
commonInit(applicationContext)
setNfcAdapterReaderCallback(this)
rippleBackgroundNfc.startRippleAnimation()
@ -155,12 +140,11 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
// check if root device
val rootBeer = RootBeer(this)
if (rootBeer.isRootedWithoutBusyBoxCheck && !BuildConfig.DEBUG)
RootFoundDialog().show(fragmentManager, RootFoundDialog.TAG)
RootFoundDialog().show(supportFragmentManager, RootFoundDialog.TAG)
// set listeners
fab.setOnClickListener { showMenu(it) }
val apiHelper = ServerApiCommon()
apiHelper.setLastVersionListener { response ->
try {
@ -180,19 +164,19 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
NfcManager.verifyPermissions(this)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
Log.e("QRScanActivity", "User hasn't granted permission to use camera")
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS)
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), Constant.REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CODE_SEND_EMAIL -> {
Constant.REQUEST_CODE_SEND_EMAIL -> {
if (zipFile != null) {
zipFile!!.delete()
zipFile = null
}
}
REQUEST_CODE_ENTER_PIN_ACTIVITY -> {
Constant.REQUEST_CODE_ENTER_PIN_ACTIVITY -> {
if (resultCode == Activity.RESULT_OK && lastTag != null)
onTagDiscovered(lastTag!!)
else
@ -216,17 +200,16 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
R.id.sendLogs -> {
var f: File? = null
try {
f = Logger.collectLogs(this)
if (f != null) {
// Log.e(TAG, String.format("Collect %d log bytes", f.length()));
LOG.e(TAG, String.format("Collect %d log bytes", f.length()))
CommonUtil.sendEmail(this, zipFile, TAG, "Logs", PhoneUtility.getDeviceInfo(), arrayOf(f))
} else {
// Log.e(TAG, "Can't create temporaly log file");
LOG.e(TAG, "Can't create temporarily log file")
}
} catch (e: Exception) {
e.printStackTrace()
@ -237,17 +220,17 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
return true
}
R.id.managePIN -> {
showSavePinActivity()
navigator.showPinSave(this, false)
return true
}
R.id.managePIN2 -> {
showSavePin2Activity()
navigator.showPinSave(this, true)
return true
}
R.id.about -> {
showLogoActivity()
navigator.showLogo(this, false)
return true
}
}
@ -260,7 +243,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
// Log.e(TAG, "setTimeout(" + String.valueOf(1000 + 3000 * unsuccessReadCount) + ")");
LOG.e(TAG, "setTimeout(" + (1000 + 3000 * unsuccessReadCount) + ")")
if (unsuccessReadCount < 2) {
isoDep.timeout = 2000 + 5000 * unsuccessReadCount
} else {
@ -268,14 +251,14 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
}
lastTag = tag
readCardInfoTask = ReadCardInfoTask(this, nfcManager, isoDep, this)
readCardInfoTask = ReadCardInfoTask(NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this)
readCardInfoTask!!.start()
// Log.i(TAG, "onTagDiscovered " + Arrays.toString(tag.getId()));
LOG.i(TAG, "onTagDiscovered " + Arrays.toString(tag.id))
} catch (e: Exception) {
e.printStackTrace()
nfcManager!!.notifyReadResult(false)
nfcManager.notifyReadResult(false)
}
}
@ -283,23 +266,19 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
super.onResume()
animate()
ReadCardInfoTask.resetLastReadInfo()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
nfcManager!!.onPause()
if (readCardInfoTask != null) {
readCardInfoTask!!.cancel(true)
}
nfcManager.onPause()
readCardInfoTask?.cancel(true)
super.onPause()
}
public override fun onStop() {
// dismiss enable NFC dialog
nfcManager!!.onStop()
if (readCardInfoTask != null) {
readCardInfoTask!!.cancel(true)
}
nfcManager.onStop()
readCardInfoTask?.cancel(true)
super.onStop()
}
@ -315,7 +294,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
readCardInfoTask = null
if (cardProtocol != null) {
if (cardProtocol.error == null) {
nfcManager!!.notifyReadResult(true)
nfcManager.notifyReadResult(true)
rlProgressBar.post {
rlProgressBar.visibility = View.GONE
@ -330,12 +309,20 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
val card = TangemCard(uid)
card.loadFromBundle(cardInfo.getBundle("Card"))
val ctx = TangemContext(card)
when {
card.status == TangemCard.Status.Loaded -> lastTag?.let { navigator.showLoadedWallet(this, it, cardInfo) }
card.status == TangemCard.Status.Empty -> navigator.showEmptyWallet(this)
card.status == TangemCard.Status.Loaded -> lastTag?.let {
val engineCoin = CoinEngineFactory.create(ctx)
?: throw CardProtocol.TangemException("Can't create CoinEngine!")
engineCoin.defineWallet()
//mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed));
navigator.showLoadedWallet(this, it, ctx)
}
card.status == TangemCard.Status.Empty -> navigator.showEmptyWallet(this, ctx)
card.status == TangemCard.Status.Purged -> Toast.makeText(this, R.string.erased_wallet, Toast.LENGTH_SHORT).show()
card.status == TangemCard.Status.NotPersonalized -> Toast.makeText(this, R.string.not_personalized, Toast.LENGTH_SHORT).show()
else -> lastTag?.let { navigator.showLoadedWallet(this, it, cardInfo) }
else -> lastTag?.let { navigator.showLoadedWallet(this, it, ctx) }
}
}
@ -346,7 +333,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
unsuccessReadCount++
if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN)
doEnterPIN()
navigator.showPinRequest(this, PinRequestActivity.Mode.RequestPIN.toString())
else {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported)
if (!NoExtendedLengthSupportDialog.allReadyShowed)
@ -354,7 +341,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
lastTag = null
ReadCardInfoTask.resetLastReadInfo()
nfcManager!!.notifyReadResult(false)
nfcManager.notifyReadResult(false)
}
}
}
@ -382,7 +369,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.OnReadWait(Objects.requireNonNull(this), msec)
WaitSecurityDelayDialog.onReadWait(Objects.requireNonNull(this), msec)
}
override fun onReadBeforeRequest(timeout: Int) {
@ -397,13 +384,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
onNfcReaderCallback = callback
}
private fun showLogoActivity() {
val intent = Intent(baseContext, LogoActivity::class.java)
intent.putExtra(LogoActivity.TAG, true)
intent.putExtra(LogoActivity.EXTRA_AUTO_HIDE, false)
startActivity(intent)
}
private fun animate() {
val lp = llHand.layoutParams as RelativeLayout.LayoutParams
val lp2 = llNfc.layoutParams as RelativeLayout.LayoutParams
@ -424,18 +404,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
llHand.startAnimation(a)
}
private fun showSavePinActivity() {
val intent = Intent(baseContext, PinSaveActivity::class.java)
intent.putExtra("PIN2", false)
startActivity(intent)
}
private fun showSavePin2Activity() {
val intent = Intent(baseContext, PinSaveActivity::class.java)
intent.putExtra("PIN2", true)
startActivity(intent)
}
private fun showMenu(v: View) {
val popup = PopupMenu(this, v)
val inflater = popup.menuInflater
@ -451,10 +419,4 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
popup.show()
}
private fun doEnterPIN() {
val intent = Intent(this, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN.toString())
startActivityForResult(intent, REQUEST_CODE_ENTER_PIN_ACTIVITY)
}
}

View file

@ -19,29 +19,75 @@ import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Button
import com.tangem.Constant
import com.tangem.data.fingerprint.StartFingerprintReaderTask
import com.tangem.domain.cardReader.NfcManager
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.data.fingerprint.FingerprintHelper
import com.tangem.data.db.PINStorage
import com.tangem.domain.wallet.TangemCard
import com.tangem.domain.wallet.TangemContext
import com.tangem.tangemcard.android.data.PINStorage
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.data.loadFromBundle
import com.tangem.tangemcard.data.EXTRA_TANGEM_CARD
import com.tangem.tangemcard.data.EXTRA_TANGEM_CARD_UID
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_request.*
import kotlinx.android.synthetic.main.layout_pin_buttons.*
import java.io.IOException
class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener {
companion object {
const val KEY_ALIAS = "pinKey"
const val KEYSTORE = "AndroidKeyStore"
val TAG: String = PinRequestActivity::class.java.simpleName
fun callingIntent(context: Activity, mode: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
return intent
}
fun callingIntentRequestPin(context: Activity, mode: String, ctx: TangemContext, newPIN: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN, newPIN)
ctx.saveToIntent(intent)
return intent
}
fun callingIntentRequestPin2(context: Activity, mode: String, ctx: TangemContext, newPIN2: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN_2, newPIN2)
ctx.saveToIntent(intent)
return intent
}
fun callingIntentRequestPin2(context: Activity, mode: String, ctx: TangemContext): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
ctx.saveToIntent(intent)
return intent
}
fun callingIntentConfirmPin(context: Activity, mode: String, newPIN: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN, newPIN)
return intent
}
fun callingIntentConfirmPin2(context: Activity, mode: String, newPIN2: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN_2, newPIN2)
return intent
}
}
private lateinit var nfcManager: NfcManager
lateinit var mode: Mode
private var allowFingerprint = false
private var nfcManager: NfcManager? = null
var startFingerprintReaderTask: StartFingerprintReaderTask? = null
private var fingerprintManager: FingerprintManager? = null
private var fingerprintHelper: FingerprintHelper? = null
@ -53,11 +99,9 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pin_request)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
mode = Mode.valueOf(intent.getStringExtra("mode"))
mode = Mode.valueOf(intent.getStringExtra(Constant.EXTRA_MODE))
if (mode == Mode.RequestNewPIN)
if (PINStorage.haveEncryptedPIN()) {
@ -82,9 +126,9 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
else if (mode == Mode.ConfirmNewPIN2)
tvPinPrompt.setText(R.string.confirm_new_pin_2)
else if (mode == Mode.RequestPIN2) {
val uid = intent.getStringExtra(TangemCard.EXTRA_UID)
val uid = intent.getStringExtra(EXTRA_TANGEM_CARD_UID)
val card = TangemCard(uid)
card.loadFromBundle(intent.getBundleExtra(TangemCard.EXTRA_CARD))
card.loadFromBundle(intent.getBundleExtra(EXTRA_TANGEM_CARD))
if (card.PIN2 == TangemCard.PIN2_Mode.DefaultPIN2 || card.PIN2 == TangemCard.PIN2_Mode.Unchecked) {
// if we know PIN2 or not try default previously - use it
@ -136,7 +180,7 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
startFingerprintReaderTask = null
}
nfcManager!!.onPause()
nfcManager.onPause()
}
override fun onStop() {
@ -149,12 +193,12 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
startFingerprintReaderTask = null
}
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
if (allowFingerprint)
startFingerprintReader()
}
@ -162,20 +206,19 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
override fun onTagDiscovered(tag: Tag) {
try {
Log.w(javaClass.name, "Ignore discovered tag!")
nfcManager!!.ignoreTag(tag)
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun authenticationFailed(error: String) {
doLog(error)
LOG.w(TAG, error)
}
@TargetApi(Build.VERSION_CODES.M)
override fun authenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
doLog("Authentication succeeded!")
LOG.i(TAG,"Authentication succeeded!")
val cipher = result.cryptoObject.cipher
if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) {
@ -203,10 +246,6 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
finish()
}
fun doLog(text: String) {
// Log.e("FP", text);
}
@SuppressLint("SetTextI18n")
private fun buttonClick(button: Button) {
tvPin!!.text = tvPin!!.text.toString() + button.text as String
@ -214,27 +253,27 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge
@SuppressLint("NewApi")
private fun testFingerPrintSettings(): Boolean {
doLog("Testing Fingerprint Settings")
LOG.i(TAG,"Testing Fingerprint Settings")
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
if (!keyguardManager.isKeyguardSecure) {
doLog("User hasn't enabled Lock Screen")
LOG.i(TAG,"User hasn't enabled Lock Screen")
return false
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
doLog("User hasn't granted permission to use Fingerprint")
LOG.i(TAG,"User hasn't granted permission to use Fingerprint")
return false
}
if (!fingerprintManager!!.hasEnrolledFingerprints()) {
doLog("User hasn't registered any fingerprints")
LOG.i(TAG,"User hasn't registered any fingerprints")
return false
}
doLog("Fingerprint authentication is set.\n")
LOG.i(TAG,"Fingerprint authentication is set.\n")
return true
}

View file

@ -6,6 +6,7 @@ import android.annotation.TargetApi
import android.app.Dialog
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
@ -19,9 +20,10 @@ import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.Toast
import com.tangem.Constant
import com.tangem.data.fingerprint.ConfirmWithFingerprintTask
import com.tangem.data.fingerprint.FingerprintHelper
import com.tangem.data.db.PINStorage
import com.tangem.tangemcard.android.data.PINStorage
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_save.*
import kotlinx.android.synthetic.main.layout_pin_buttons.*
@ -37,6 +39,15 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelperListener {
companion object {
val TAG: String = PinSaveActivity::class.java.simpleName
fun callingIntent(context: Context, hasPin2: Boolean): Intent {
val intent = Intent(context, PinSaveActivity::class.java)
intent.putExtra(Constant.EXTRA_PIN2, hasPin2)
return intent
}
}
private var confirmWithFingerprintTask: ConfirmWithFingerprintTask? = null
private var keyStore: KeyStore? = null
@ -56,9 +67,7 @@ class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelper
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pin_save)
MainActivity.commonInit(applicationContext)
usePIN2 = intent.getBooleanExtra("PIN2", false)
usePIN2 = intent.getBooleanExtra(Constant.EXTRA_PIN2, false)
if (usePIN2)
tvPinPrompt.text = getString(R.string.enter_pin2_and_use_fingerprint_to_save_it)
@ -155,7 +164,7 @@ class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelper
fun getKeyStore(): Boolean {
try {
keyStore = KeyStore.getInstance(PinRequestActivity.KEYSTORE)
keyStore = KeyStore.getInstance(Constant.KEYSTORE)
// create empty keystore
keyStore!!.load(null)
return true
@ -176,12 +185,12 @@ class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelper
fun createNewKey(forceCreate: Boolean): Boolean {
try {
if (forceCreate)
keyStore!!.deleteEntry(PinRequestActivity.KEY_ALIAS)
keyStore!!.deleteEntry(Constant.KEY_ALIAS)
if (!keyStore!!.containsAlias(PinRequestActivity.KEY_ALIAS)) {
val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, PinRequestActivity.KEYSTORE)
if (!keyStore!!.containsAlias(Constant.KEY_ALIAS)) {
val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, Constant.KEYSTORE)
generator.init(KeyGenParameterSpec.Builder(PinRequestActivity.KEY_ALIAS,
generator.init(KeyGenParameterSpec.Builder(Constant.KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
@ -217,14 +226,14 @@ class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelper
fun initCipher(mode: Int): Boolean {
try {
keyStore!!.load(null)
val keyspec = keyStore!!.getKey(PinRequestActivity.KEY_ALIAS, null) as SecretKey
val keySpec = keyStore!!.getKey(Constant.KEY_ALIAS, null) as SecretKey
if (mode == Cipher.ENCRYPT_MODE) {
cipher!!.init(mode, keyspec)
cipher!!.init(mode, keySpec)
} else {
val iv = PINStorage.loadEncryptedIV()
val ivspec = IvParameterSpec(iv)
cipher!!.init(mode, keyspec, ivspec)
val ivSpec = IvParameterSpec(iv)
cipher!!.init(mode, keySpec, ivSpec)
}
return true
@ -302,7 +311,6 @@ class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelper
}
// show a progress spinner, and kick off a background task to perform the user login attempt
//showProgress(true);
confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveActivity)
confirmWithFingerprintTask!!.execute(null as Void?)
} else {

View file

@ -1,6 +1,7 @@
package com.tangem.presentation.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
@ -12,27 +13,37 @@ import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.data.nfc.SwapPINTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.App
import com.tangem.Constant
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.android.reader.NfcReader
import com.tangem.tangemcard.data.*
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.tasks.SwapPINTask
import com.tangem.tangemcard.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_swap.*
class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = PinSwapActivity::class.java.simpleName
fun callingIntent(context: Context, newPIN: String, newPIN2: String): Intent {
val intent = Intent(context, PinSwapActivity::class.java)
intent.putExtra(Constant.EXTRA_NEW_PIN, newPIN)
intent.putExtra(Constant.EXTRA_NEW_PIN_2, newPIN2)
return intent
}
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
}
private var nfcManager: NfcManager? = null
private var card: TangemCard? = null
private lateinit var nfcManager: NfcManager
private var card: TangemCard? = null
private var newPIN: String? = null
private var newPIN2: String? = null
@ -44,15 +55,13 @@ class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProt
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pin_swap)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
card = TangemCard(intent.getStringExtra(TangemCard.EXTRA_UID))
card!!.loadFromBundle(intent.extras!!.getBundle(TangemCard.EXTRA_CARD))
card = TangemCard(intent.getStringExtra(EXTRA_TANGEM_CARD_UID))
card!!.loadFromBundle(intent.extras!!.getBundle(EXTRA_TANGEM_CARD))
newPIN = intent.getStringExtra("newPIN")
newPIN2 = intent.getStringExtra("newPIN2")
newPIN = intent.getStringExtra(Constant.EXTRA_NEW_PIN)
newPIN2 = intent.getStringExtra(Constant.EXTRA_NEW_PIN_2)
tvCardID.text = card!!.cidDescription
@ -68,17 +77,16 @@ class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProt
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
// Log.v(TAG, "UID: $sUID")
LOG.d(TAG, "UID: $sUID")
if (sUID == card!!.uid) {
isoDep.timeout = card!!.pauseBeforePIN2 + 65000
swapPinTask = SwapPINTask(this, card, nfcManager, newPIN, newPIN2, isoDep, this)
swapPinTask = SwapPINTask(card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, newPIN, newPIN2)
swapPinTask!!.start()
} else {
// Log.d(TAG, "Mismatch card UID (" + sUID + " instead of " + mCard!!.uid + ")")
nfcManager!!.ignoreTag(isoDep.tag)
LOG.d(TAG, "Mismatch card UID (" + sUID + " instead of " + card!!.uid + ")")
nfcManager.ignoreTag(isoDep.tag)
}
} catch (e: Exception) {
e.printStackTrace()
}
@ -86,11 +94,11 @@ class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProt
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
nfcManager!!.onPause()
nfcManager.onPause()
if (swapPinTask != null)
swapPinTask!!.cancel(true)
super.onPause()
@ -98,7 +106,7 @@ class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProt
public override fun onStop() {
// dismiss enable NFC dialog
nfcManager!!.onStop()
nfcManager.onStop()
if (swapPinTask != null)
swapPinTask!!.cancel(true)
super.onStop()
@ -190,7 +198,7 @@ class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProt
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.OnReadWait(this, msec)
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {

View file

@ -9,36 +9,39 @@ import android.nfc.Tag
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.data.network.CryptonitOtherApi
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_cryptonit_other_api_withdrawal.*
import java.io.IOException
import javax.inject.Inject
class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareCryptonitOtherApiWithdrawalActivity::class.java.simpleName
private const val REQUEST_CODE_SCAN_QR_KEY = 1
private const val REQUEST_CODE_SCAN_QR_SECRET = 2
private const val REQUEST_CODE_SCAN_QR_USER_ID = 3
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var nfcManager: NfcManager? = null
private var cryptonit: CryptonitOtherApi? = null
@Inject
internal lateinit var navigator: Navigator
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_cryptonit_other_api_withdrawal)
MainActivity.commonInit(applicationContext)
App.getNavigatorComponent().inject(this)
nfcManager = NfcManager(this, this)
@ -51,27 +54,26 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt
tvSecret.text = cryptonit!!.secretDescription
tvCardID.text = ctx.card!!.cidDescription
tvWallet.text = ctx.card!!.wallet
tvWallet.text = ctx.coinData!!.wallet
val engine = CoinEngineFactory.create(ctx)
tvCurrency.text = engine!!.balanceCurrency
etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString())
etAmount.filters=engine.amountInputFilters
etAmount.filters = engine.amountInputFilters
// set listeners
btnLoad.setOnClickListener {
try {
val strAmount: String = etAmount.text.toString().replace(",", ".")
// if (!engine.checkAmount(card, strAmount))
// etAmount.error = getString(R.string.unknown_amount_format)
var dblAmount: Double = strAmount.toDouble()
val dblAmount: Double = strAmount.toDouble()
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal)
cryptonit!!.requestCryptoWithdrawal(ctx.blockchain.currency, dblAmount.toString(), ctx.card!!.wallet)
cryptonit!!.requestCryptoWithdrawal(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
@ -84,18 +86,11 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt
// }
}
ivCameraKey.setOnClickListener {
val intent = Intent(baseContext, QrScanActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SCAN_QR_KEY)
}
ivCameraSecret.setOnClickListener {
val intent = Intent(baseContext, QrScanActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SCAN_QR_SECRET)
}
ivCameraUserId.setOnClickListener {
val intent = Intent(baseContext, QrScanActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SCAN_QR_USER_ID)
}
ivCameraKey.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR_KEY) }
ivCameraSecret.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR_SECRET) }
ivCameraUserId.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR_USER_ID) }
ivRefreshBalance.setOnClickListener { doRequestBalance() }
@ -162,32 +157,32 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
nfcManager.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if( resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode") ) {
when(requestCode) {
REQUEST_CODE_SCAN_QR_KEY -> {
if (resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
when (requestCode) {
Constant.REQUEST_CODE_SCAN_QR_KEY -> {
cryptonit!!.key = data.getStringExtra("QRCode")
tvKey!!.text = cryptonit!!.key
}
REQUEST_CODE_SCAN_QR_SECRET -> {
Constant.REQUEST_CODE_SCAN_QR_SECRET -> {
cryptonit!!.secret = data.getStringExtra("QRCode")
tvSecret!!.text = cryptonit!!.secretDescription
}
REQUEST_CODE_SCAN_QR_USER_ID -> {
Constant.REQUEST_CODE_SCAN_QR_USER_ID -> {
cryptonit!!.userId = data.getStringExtra("QRCode")
tvUserID!!.text = cryptonit!!.userId
}
@ -199,8 +194,7 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt
override fun onTagDiscovered(tag: Tag) {
try {
// Log.w(javaClass.name, "Ignore discovered tag!")
nfcManager!!.ignoreTag(tag)
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}

View file

@ -13,8 +13,8 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.tangem.data.network.Cryptonit
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.util.DecimalDigitsInputFilter
@ -22,25 +22,22 @@ import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_cryptonit_withdrawal.*
import java.io.IOException
class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareCryptonitWithdrawalActivity::class.java.simpleName
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var nfcManager: NfcManager? = null
private var cryptonit: Cryptonit? = null
private var cryptonit: Cryptonit? = null
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_cryptonit_withdrawal)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
ctx = TangemContext.loadFromBundle(this, intent.extras)
@ -52,7 +49,7 @@ class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.Reade
etFee.setText(cryptonit!!.fee)
tvCardID.text = ctx.card!!.cidDescription
tvWallet.text = ctx.card!!.wallet
tvWallet.text = ctx.coinData!!.wallet
val engine = CoinEngineFactory.create(ctx)
tvCurrency.text = engine!!.balanceCurrency
@ -89,18 +86,17 @@ class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.Reade
// set listeners
btnLoad.setOnClickListener {
try {
val strAmount: String = etAmount.text.toString().replace(",", ".")
val strFee: String = etFee.text.toString().replace(",", ".")
var dblAmount: Double = strAmount.toDouble()
val dblAmount: Double = strAmount.toDouble()
var dblFee: Double = strFee.toDouble()
cryptonit!!.fee = strFee
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal)
cryptonit!!.requestWithdrawCoins(ctx.blockchain.currency, dblAmount, ctx.card!!.wallet)
cryptonit!!.requestWithdrawCoins(ctx.blockchain.currency, dblAmount, ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
@ -145,7 +141,7 @@ class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.Reade
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_balance)
tvError.visibility = View.INVISIBLE
cryptonit!!.requestBalance(ctx.card!!.blockchain.currency)
cryptonit!!.requestBalance(ctx.blockchain.currency)
} else {
tvError.visibility = View.VISIBLE
tvError.text = getString(R.string.cryptonit_not_enough_account_data)
@ -154,22 +150,22 @@ class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.Reade
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
nfcManager.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager!!.ignoreTag(tag)
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}

View file

@ -15,9 +15,12 @@ import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.network.Kraken
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.data.Blockchain
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.wallet.R
@ -26,13 +29,12 @@ import java.io.IOException
import java.math.BigDecimal
import java.net.URI
import java.util.*
import javax.inject.Inject
class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareKrakenWithdrawalActivity::class.java.simpleName
private const val REQUEST_CODE_SCAN_QR = 1
}
private lateinit var ctx: TangemContext
@ -40,12 +42,15 @@ class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCa
private var kraken: Kraken? = null
private var fee: BigDecimal? = null
@Inject
internal lateinit var navigator: Navigator
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_kraken_withdrawal)
MainActivity.commonInit(applicationContext)
App.getNavigatorComponent().inject(this)
nfcManager = NfcManager(this, this)
@ -57,7 +62,7 @@ class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCa
tvSecret.text = kraken!!.secretDescription
tvCardID.text = ctx.card!!.cidDescription
tvWallet.text = ctx.card!!.wallet
tvWallet.text = ctx.coinData!!.wallet
val engine = CoinEngineFactory.create(ctx)
tvCurrency.text = engine!!.balanceCurrency
@ -85,16 +90,14 @@ class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCa
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.kraken_request_withdrawal)
kraken!!.requestWithdrawInfo(ctx.blockchain.currency, dblAmount.toString(), ctx.card!!.wallet)
kraken!!.requestWithdrawInfo(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
}
ivCamera.setOnClickListener {
val intent = Intent(baseContext, QrScanActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SCAN_QR)
}
ivCamera.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR) }
ivRefreshBalance.setOnClickListener { doRequestBalance() }
@ -184,7 +187,7 @@ class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCa
tvProgressDescription.text = getString(R.string.kraken_request_withdrawal)
//Toast.makeText(this, String.format("Withdraw %s!",dblAmount.toString()), Toast.LENGTH_LONG).show()
kraken!!.requestWithdraw(ctx.blockchain.currency, dblAmount.toString(), ctx.card!!.wallet)
kraken!!.requestWithdraw(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
@ -240,7 +243,7 @@ class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCa
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
when (requestCode) {
REQUEST_CODE_SCAN_QR -> {
Constant.REQUEST_CODE_SCAN_QR -> {
val uri = URI(data.getStringExtra("QRCode"))
val query = uri.query
val params = query.split("&")

View file

@ -12,32 +12,40 @@ import android.text.Html
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.Blockchain
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_payment.*
import java.io.IOException
import javax.inject.Inject
class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PreparePaymentActivity::class.java.simpleName
private const val REQUEST_CODE_SCAN_QR = 1
private const val REQUEST_CODE_SEND_PAYMENT = 2
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, PreparePaymentActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
}
@Inject
internal lateinit var navigator: Navigator
private lateinit var ctx: TangemContext
private var nfcManager: NfcManager? = null
private lateinit var nfcManager: NfcManager
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_payment)
MainActivity.commonInit(applicationContext)
App.getNavigatorComponent().inject(this)
nfcManager = NfcManager(this, this)
@ -50,7 +58,7 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
tvBalance.text = html
//TODO - to engine
if (ctx.card!!.blockchain == Blockchain.Token && engine.balance.currency!="ETH") {
if (ctx.blockchain == Blockchain.Token && engine.balance.currency != Blockchain.Ethereum.currency) {
rgIncFee!!.visibility = View.INVISIBLE
} else {
rgIncFee!!.visibility = View.VISIBLE
@ -77,7 +85,6 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
}
}
btnVerify.setOnClickListener {
val engine1 = CoinEngineFactory.create(ctx)
val strAmount: String = etAmount.text.toString().replace(",", ".")
@ -92,67 +99,54 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
etAmount.error = getString(R.string.unknown_amount_format)
}
var checkAddress = false
if (engine1 != null)
checkAddress = engine1.validateAddress(etWallet.text.toString())
// check wallet address
if (!checkAddress) {
if (!engine1.validateAddress(etWallet.text.toString())) {
etWallet.error = getString(R.string.incorrect_destination_wallet_address)
return@setOnClickListener
} else {
} else
etWallet.error = null
}
if (etWallet.text.toString() == ctx.card!!.wallet) {
if (etWallet.text.toString() == ctx.coinData!!.wallet) {
etWallet.error = getString(R.string.destination_wallet_address_equal_source_address)
return@setOnClickListener
}
// check enough funds
// TODO - double with engin.checkAmount
// if (etAmount.text.toString().replace(",", ".").toDouble() > engine.getBalanceValue(card).replace(",", ".").toDouble()) {
// etAmount.error = getString(R.string.not_enough_funds_on_your_card)
// return@setOnClickListener
// }
if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) {
return@setOnClickListener
}
val intent = Intent(baseContext, ConfirmPaymentActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra(SignPaymentActivity.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn))
intent.putExtra(SignPaymentActivity.EXTRA_AMOUNT, strAmount)
intent.putExtra(SignPaymentActivity.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
startActivityForResult(intent, REQUEST_CODE_SEND_PAYMENT)
}
ivCamera.setOnClickListener {
val intent = Intent(baseContext, QrScanActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SCAN_QR)
intent.putExtra(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn))
intent.putExtra(Constant.EXTRA_AMOUNT, strAmount)
intent.putExtra(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
startActivityForResult(intent, Constant.REQUEST_CODE_SEND_PAYMENT__)
}
ivCamera.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR) }
}
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
nfcManager.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
var code = data.getStringExtra("QRCode")
when (ctx.card!!.blockchain) {
when (ctx.blockchain) {
Blockchain.Bitcoin -> {
if (code.contains("bitcoin:")) {
val tmp = code.split("bitcoin:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
@ -172,8 +166,7 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
}
}
etWallet!!.setText(code)
} else if (requestCode == REQUEST_CODE_SEND_PAYMENT) {
} else if (requestCode == Constant.REQUEST_CODE_SEND_PAYMENT__) {
setResult(resultCode, data)
finish()
}
@ -181,11 +174,10 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager!!.ignoreTag(tag)
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.presentation.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.ColorStateList
@ -12,14 +13,17 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Toast
import com.tangem.data.nfc.PurgeTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.TangemCard
import com.tangem.App
import com.tangem.tangemcard.tasks.PurgeTask
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.android.reader.NfcReader
import com.tangem.tangemcard.data.asBundle
import com.tangem.tangemcard.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_purge.*
@ -27,11 +31,19 @@ class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoc
companion object {
val TAG: String = PurgeActivity::class.java.simpleName
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, PurgeActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var nfcManager: NfcManager? = null
private var purgeTask: PurgeTask? = null
override fun onCreate(savedInstanceState: Bundle?) {
@ -42,8 +54,6 @@ class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoc
nfcManager = NfcManager(this, this)
MainActivity.commonInit(applicationContext)
ctx = TangemContext.loadFromBundle(this, intent.extras)
tvCardID.text = ctx.card!!.cidDescription
@ -53,18 +63,18 @@ class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoc
public override fun onResume() {
super.onResume()
nfcManager?.onResume()
nfcManager.onResume()
}
public override fun onPause() {
nfcManager?.onPause()
nfcManager.onPause()
purgeTask?.cancel(true)
super.onPause()
}
public override fun onStop() {
// dismiss enable NFC dialog
nfcManager?.onStop()
nfcManager.onStop()
purgeTask?.cancel(true)
super.onStop()
}
@ -72,19 +82,18 @@ class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoc
override fun onTagDiscovered(tag: Tag) {
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val isoDep = IsoDep.get(tag) ?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
// Log.v(TAG, "UID: $sUID")
LOG.d(TAG, "UID: $sUID")
if (sUID == ctx.card!!.uid) {
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 65000
purgeTask = PurgeTask(this, ctx.card, nfcManager, isoDep, this)
purgeTask = PurgeTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this)
purgeTask!!.start()
} else {
// this Log.d(TAG, "Mismatch card UID (" + sUID + " instead of " + card.getUID() + ")");
nfcManager?.ignoreTag(isoDep.tag)
LOG.d(TAG, "Mismatch card UID (" + sUID + " instead of " + ctx.card.uid + ")")
nfcManager.ignoreTag(isoDep.tag)
}
} catch (e: Exception) {
@ -93,7 +102,7 @@ class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoc
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.OnReadWait(this, msec)
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {

View file

@ -2,17 +2,22 @@ package com.tangem.presentation.activity
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v7.app.AppCompatActivity
import com.google.zxing.Result
import com.tangem.Constant
import me.dm7.barcodescanner.zxing.ZXingScannerView
class QrScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
companion object {
fun callingIntent(context: Context): Intent {
return Intent(context, QrScanActivity::class.java)
}
}
private var scannerView: ZXingScannerView? = null
@ -26,14 +31,12 @@ class QrScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
override fun onPause() {
super.onPause()
if (scannerView != null)
scannerView!!.stopCamera()
scannerView?.stopCamera()
}
override fun onResume() {
super.onResume()
if (scannerView != null)
scannerView!!.startCamera()
scannerView?.startCamera()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
@ -51,7 +54,7 @@ class QrScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
override fun handleResult(result: Result) {
val data = Intent()
data.putExtra("QRCode", result.text)
data.putExtra(Constant.EXTRA_QR_CODE, result.text)
setResult(Activity.RESULT_OK, data)
finish()
}
@ -60,9 +63,10 @@ class QrScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
// programmatically initialize the scanner view
scannerView = ZXingScannerView(this)
setContentView(scannerView)
// register ourselves as a handler for scan results.
scannerView!!.setResultHandler(this)
scannerView!!.startCamera()
scannerView?.setResultHandler(this)
scannerView?.startCamera()
}
}

View file

@ -7,94 +7,54 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.KeyEvent
import android.widget.Toast
import com.tangem.data.network.ElectrumRequest
import com.tangem.data.network.ServerApiElectrum
import com.tangem.data.network.ServerApiInfura
import com.tangem.data.network.model.InfuraResponse
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.domain.wallet.eth.EthData
import com.tangem.Constant
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.event.TransactionFinishWithError
import com.tangem.presentation.event.TransactionFinishWithSuccess
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import org.greenrobot.eventbus.EventBus
import java.io.IOException
import java.math.BigInteger
class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
const val EXTRA_TX: String = "TX"
}
private var serverApiInfura: ServerApiInfura = ServerApiInfura()
private var serverApiElectrum: ServerApiElectrum = ServerApiElectrum()
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var tx: String? = null
private var nfcManager: NfcManager? = null
private var tx: ByteArray? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_send_transaction)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
ctx = TangemContext.loadFromBundle(this, intent.extras)
tx = intent.getStringExtra(EXTRA_TX)
tx = intent.getByteArrayExtra(Constant.EXTRA_TX)
val engine = CoinEngineFactory.create(ctx)
if (ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token)
requestInfura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, "")
else if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet)
requestElectrum(ctx.card!!, ElectrumRequest.broadcast(ctx.card!!.wallet, tx))
else if (ctx.blockchain == Blockchain.BitcoinCash)
requestElectrum(ctx.card!!, ElectrumRequest.broadcast(ctx.card!!.wallet, tx))
// request electrum listener
val electrumBodyListener: ServerApiElectrum.ElectrumRequestDataListener = object : ServerApiElectrum.ElectrumRequestDataListener {
override fun onSuccess(electrumRequest: ElectrumRequest?) {
if (electrumRequest!!.isMethod(ElectrumRequest.METHOD_SendTransaction)) {
if (electrumRequest.resultString.isEmpty())
finishWithError("Rejected by node: " + electrumRequest.getError())
else
finishWithSuccess()
}
}
override fun onFail(message: String?) {
finishWithError(message!!)
}
}
serverApiElectrum.setElectrumRequestData(electrumBodyListener)
// request infura listener
val infuraBodyListener: ServerApiInfura.InfuraBodyListener = object : ServerApiInfura.InfuraBodyListener {
override fun onSuccess(method: String, infuraResponse: InfuraResponse) {
when (method) {
ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> {
if (infuraResponse.result.isEmpty())
finishWithError("Rejected by node: " + infuraResponse.error)
else {
val nonce = (ctx.coinData!! as EthData).confirmedTXCount
nonce.add(BigInteger.valueOf(1))
(ctx.coinData!! as EthData).confirmedTXCount = nonce
engine!!.requestSendTransaction(
object : CoinEngine.BlockchainRequestsCallbacks {
override fun onComplete(success: Boolean) {
if (success)
finishWithSuccess()
}
else
finishWithError(this@SendTransactionActivity.getString(R.string.try_again_failed_to_send_transaction))
}
}
}
override fun onFail(method: String, message: String) {
when (method) {
ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION -> {
finishWithError(message)
override fun onProgress() {
}
}
}
}
serverApiInfura.setInfuraResponse(infuraBodyListener)
override fun allowAdvance(): Boolean {
return UtilHelper.isOnline(this@SendTransactionActivity)
}
},
tx
)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
@ -109,51 +69,45 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
nfcManager.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
nfcManager.onStop()
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager!!.ignoreTag(tag)
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun requestInfura(method: String, contract: String) {
if (UtilHelper.isOnline(this)) {
serverApiInfura.infura(method, 67, ctx.card!!.wallet, contract, tx)
} else
finishWithError(getString(R.string.no_connection))
}
private fun requestElectrum(card: TangemCard, electrumRequest: ElectrumRequest) {
if (UtilHelper.isOnline(this)) {
serverApiElectrum.electrumRequestData(card, electrumRequest)
} else
finishWithError(getString(R.string.no_connection))
}
private fun finishWithSuccess() {
val transactionFinishWithSuccess = TransactionFinishWithSuccess()
transactionFinishWithSuccess.message = getString(R.string.transaction_has_been_successfully_signed)
EventBus.getDefault().post(transactionFinishWithSuccess)
val intent = Intent()
intent.putExtra("message", getString(R.string.transaction_has_been_successfully_signed))
intent.putExtra(Constant.EXTRA_MESSAGE, getString(R.string.transaction_has_been_successfully_signed))
setResult(RESULT_OK, intent)
finish()
}
private fun finishWithError(message: String) {
val transactionFinishWithError = TransactionFinishWithError()
transactionFinishWithError.message = String.format(getString(R.string.try_again_failed_to_send_transaction), message)
EventBus.getDefault().post(transactionFinishWithError)
val intent = Intent()
intent.putExtra("message", String.format(getString(R.string.try_again_failed_to_send_transaction), message))
intent.putExtra(Constant.EXTRA_MESSAGE, String.format(getString(R.string.try_again_failed_to_send_transaction), message))
setResult(RESULT_CANCELED, intent)
finish()
}

View file

@ -13,14 +13,20 @@ import android.view.KeyEvent
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.data.nfc.SignPaymentTask
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.NfcManager
import com.tangem.App
import com.tangem.Constant
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.android.reader.NfcReader
import com.tangem.tangemcard.data.asBundle
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.tangemcard.tasks.SignTask
import com.tangem.tangemcard.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_sign_payment.*
@ -28,24 +34,15 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
companion object {
val TAG: String = SignPaymentActivity::class.java.simpleName
const val EXTRA_AMOUNT = "Amount"
const val EXTRA_AMOUNT_CURRENCY = "AmountCurrency"
const val EXTRA_FEE = "Fee"
const val EXTRA_FEE_CURRENCY = "FeeCurrency"
const val EXTRA_FEE_INCLUDED = "FeeIncluded"
const val EXTRA_TARGET_ADDRESS = "TargetAddress"
const val REQUEST_CODE_SEND_PAYMENT = 1
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
}
private var nfcManager: NfcManager? = null
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var signPaymentTask: SignPaymentTask? = null
private var signPaymentTask: SignTask? = null
private var amount: CoinEngine.Amount? = null
private var fee: CoinEngine.Amount? = null
private lateinit var amount: CoinEngine.Amount
private lateinit var fee: CoinEngine.Amount
private var isIncludeFee = true
private var outAddressStr: String? = null
private var lastReadSuccess = true
@ -56,16 +53,14 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sign_payment)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
ctx=TangemContext.loadFromBundle(this, intent.extras)
ctx = TangemContext.loadFromBundle(this, intent.extras)
amount = CoinEngine.Amount(intent.getStringExtra(EXTRA_AMOUNT), intent.getStringExtra(EXTRA_AMOUNT_CURRENCY))
fee = CoinEngine.Amount(intent.getStringExtra(EXTRA_FEE), intent.getStringExtra(EXTRA_FEE_CURRENCY))
isIncludeFee = intent.getBooleanExtra(EXTRA_FEE_INCLUDED, true)
outAddressStr = intent.getStringExtra(EXTRA_TARGET_ADDRESS)
amount = CoinEngine.Amount(intent.getStringExtra(Constant.EXTRA_AMOUNT), intent.getStringExtra(Constant.EXTRA_AMOUNT_CURRENCY))
fee = CoinEngine.Amount(intent.getStringExtra(Constant.EXTRA_FEE), intent.getStringExtra(Constant.EXTRA_FEE_CURRENCY))
isIncludeFee = intent.getBooleanExtra(Constant.EXTRA_FEE_INCLUDED, true)
outAddressStr = intent.getStringExtra(Constant.EXTRA_TARGET_ADDRESS)
tvCardID.text = ctx.card!!.cidDescription
@ -76,11 +71,11 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
nfcManager.onResume()
}
public override fun onPause() {
nfcManager!!.onPause()
nfcManager.onPause()
if (signPaymentTask != null)
signPaymentTask!!.cancel(true)
super.onPause()
@ -88,14 +83,14 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
public override fun onStop() {
// dismiss enable NFC dialog
nfcManager!!.onStop()
nfcManager.onStop()
if (signPaymentTask != null)
signPaymentTask!!.cancel(true)
super.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SEND_PAYMENT) {
if (requestCode == Constant.REQUEST_CODE_SEND_PAYMENT_) {
setResult(resultCode, data)
finish()
return
@ -122,7 +117,6 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
// Log.v(TAG, "UID: $sUID")
if (sUID == ctx.card!!.uid) {
if (lastReadSuccess) {
@ -130,13 +124,35 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
} else {
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 65000
}
signPaymentTask = SignPaymentTask(this, ctx, nfcManager, isoDep, this, amount, fee, isIncludeFee, outAddressStr)
signPaymentTask!!.start()
} else {
// Log.d(TAG, "Mismatch card UID (" + sUID + " instead of " + card!!.uid + ")")
nfcManager!!.ignoreTag(isoDep.tag)
}
val coinEngine = CoinEngineFactory.create(ctx)
?: throw CardProtocol.TangemException("Can't create CoinEngine!")
coinEngine.setOnNeedSendPayment { tx ->
if (tx != null) {
val intent = Intent(this, SendTransactionActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra(Constant.EXTRA_TX, tx)
startActivityForResult(intent, Constant.REQUEST_CODE_SEND_PAYMENT_)
}
}
val paymentToSign = coinEngine.constructPayment(amount, fee, isIncludeFee, outAddressStr)
signPaymentTask = SignTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, paymentToSign)
signPaymentTask!!.start()
} else
nfcManager.ignoreTag(isoDep.tag)
} catch (e: CardProtocol.TangemException_WrongAmount) {
try {
val intent = Intent()
intent.putExtra("message", getString(R.string.cannot_sign_transaction_wrong_amount))
intent.putExtra("UID", ctx.card.uid)
intent.putExtra("Card", ctx.card.asBundle)
setResult(Activity.RESULT_CANCELED, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
}
@ -174,10 +190,10 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
progressBar!!.visibility = View.INVISIBLE
val intent = Intent()
intent.putExtra("message", getString(R.string.cannot_sign_transaction__make_sure_you_enter_correct_pin_2))
intent.putExtra("message", getString(R.string.cannot_sign_transaction_make_sure_you_enter_correct_pin_2))
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(RESULT_INVALID_PIN, intent)
setResult(Constant.RESULT_INVALID_PIN_, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
@ -237,16 +253,41 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card
}, 500)
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.OnReadWait(this, msec)
}
// private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew()
override fun onReadBeforeRequest(timeout: Int) {
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout)
// if (!waitSecurityDelayDialogNew.isAdded)
// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
// val readBeforeRequest = ReadBeforeRequest()
// readBeforeRequest.timeout = timeout
// EventBus.getDefault().post(readBeforeRequest)
}
override fun onReadAfterRequest() {
LOG.i(TAG, "onReadAfterRequest")
WaitSecurityDelayDialog.onReadAfterRequest(this)
// val readAfterRequest = ReadAfterRequest()
// EventBus.getDefault().post(readAfterRequest)
}
override fun onReadWait(msec: Int) {
LOG.i(TAG, "onReadWait msec $msec")
WaitSecurityDelayDialog.onReadWait(this, msec)
// val readWait = ReadWait()
// readWait.msec = msec
// EventBus.getDefault().post(readWait)
}
}

View file

@ -7,8 +7,7 @@ import android.support.v7.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinData
import com.tangem.domain.wallet.TangemCard
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.fragment.VerifyCard
import com.tangem.wallet.R
import javax.inject.Inject
@ -19,13 +18,9 @@ class VerifyCardActivity : AppCompatActivity() {
lateinit var navigator: Navigator
companion object {
fun callingIntent(context: Context, card: TangemCard, coinData: CoinData, message: String, error: String): Intent {
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, VerifyCardActivity::class.java)
intent.putExtra(TangemCard.EXTRA_UID, card.uid)
intent.putExtra(TangemCard.EXTRA_CARD, card.asBundle)
intent.putExtra(Constant.EXTRA_BLOCKCHAIN_DATA, coinData.asBundle())
intent.putExtra(Constant.MESSAGE, message)
intent.putExtra(Constant.ERROR, error)
ctx.saveToIntent(intent)
return intent
}
}
@ -35,15 +30,13 @@ class VerifyCardActivity : AppCompatActivity() {
setContentView(R.layout.activity_verify_card)
App.getNavigatorComponent().inject(this)
MainActivity.commonInit(applicationContext)
}
override fun onBackPressed() {
super.onBackPressed()
val verifyCard = supportFragmentManager.findFragmentById(R.id.verify_card_fragment) as VerifyCard
val data = verifyCard.prepareResultIntent()
data.putExtra(Constant.EXTRA_MODIFICATION, "update")
data.putExtra(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE)
finish()
}

View file

@ -2,8 +2,8 @@ package com.tangem.presentation.dialog
import android.app.AlertDialog
import android.app.Dialog
import android.app.DialogFragment
import android.os.Bundle
import android.support.v4.app.DialogFragment
import com.tangem.wallet.R

View file

@ -11,15 +11,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.tangem.util.UtilHelper;
import com.tangem.wallet.R;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by dvol on 06.03.2018.
*/

View file

@ -3,12 +3,11 @@ package com.tangem.presentation.dialog;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ProgressBar;
import com.tangem.wallet.R;
@ -20,24 +19,16 @@ import java.util.TimerTask;
* Created by dvol on 06.03.2018.
*/
public class WaitSecurityDelayDialog extends DialogFragment {
private static final String TAG = WaitSecurityDelayDialog.class.getSimpleName();
private ProgressBar progressBar;
private int msTimeout = 60000, msProgress = 0;
private Timer timer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
ProgressBar progressBar;
int msTimeout = 60000, msProgress = 0;
Timer timer;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
// Inflate and set t he layout for the dialog
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
View v = inflater.inflate(R.layout.dialog_wait_pin2, null);
@ -49,17 +40,20 @@ public class WaitSecurityDelayDialog extends DialogFragment {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
progressBar.post(() -> {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) {
WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000);
progressBar.post(new Runnable() {
@Override
public void run() {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) {
WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000);
}
}
});
}
}, 1000, 1000);
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.security_delay)
.setTitle("Security delay")
.setView(v)
.setCancelable(false)
.create();
@ -76,19 +70,22 @@ public class WaitSecurityDelayDialog extends DialogFragment {
}
public void setRemainingTimeout(final int msec) {
progressBar.post(() -> {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
progressBar.setMax(progress + msec);
timer.cancel();
timer = null;
} else {
int newProgress = progressBar.getMax() - msec;
if (newProgress > progress) {
progressBar.setProgress(newProgress);
} else {
progressBar.post(new Runnable() {
@Override
public void run() {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
progressBar.setMax(progress + msec);
timer.cancel();
timer = null;
} else {
int newProgress = progressBar.getMax() - msec;
if (newProgress > progress) {
progressBar.setProgress(newProgress);
} else {
progressBar.setMax(progress + msec);
}
}
}
});
@ -97,70 +94,75 @@ public class WaitSecurityDelayDialog extends DialogFragment {
static Timer timerToShowDelayDialog = null;
static WaitSecurityDelayDialog instance = null;
public static WaitSecurityDelayDialog getInstance() {
if (instance == null) {
instance = new WaitSecurityDelayDialog();
}
return instance;
}
// public static WaitSecurityDelayDialog getInstance() {
// if (instance == null) {
// instance = new WaitSecurityDelayDialog();
// }
// return instance;
// }
private final static int MinRemainingDelayToShowDialog = 1000;
private final static int DelayBeforeShowDialog = 5000;
private final static int MinRemainingDelayToShowDialog=1000;
private final static int DelayBeforeShowDialog=5000;
public static void onReadBeforeRequest(final Activity activity, final int timeout) {
activity.runOnUiThread(() -> {
if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog + MinRemainingDelayToShowDialog)
return;
timerToShowDelayDialog = new Timer();
timerToShowDelayDialog.schedule(new TimerTask() {
@Override
public void run() {
if (WaitSecurityDelayDialog.instance != null) return;
instance = new WaitSecurityDelayDialog();
instance.setup(timeout, DelayBeforeShowDialog);
instance.setCancelable(false);
if (instance.getFragmentManager() != null)
instance.show(instance.getFragmentManager(), TAG);
}
}, DelayBeforeShowDialog);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (timerToShowDelayDialog != null || timeout < DelayBeforeShowDialog+MinRemainingDelayToShowDialog) return;
timerToShowDelayDialog = new Timer();
timerToShowDelayDialog.schedule(new TimerTask() {
@Override
public void run() {
if (WaitSecurityDelayDialog.instance != null) return;
instance = new WaitSecurityDelayDialog();
instance.setup(timeout, DelayBeforeShowDialog);
instance.setCancelable(false);
instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog");
}
}, DelayBeforeShowDialog);
}
});
}
public static void onReadAfterRequest(final Activity activity) {
activity.runOnUiThread(() -> {
if (timerToShowDelayDialog == null) return;
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
});
}
public static void OnReadWait(final Activity activity, final int msec) {
activity.runOnUiThread(() -> {
if (timerToShowDelayDialog != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (timerToShowDelayDialog == null) return;
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
}
if (msec == 0) {
if (instance != null) {
instance.dismiss();
instance = null;
}
return;
}
if (instance == null) {
if (msec > MinRemainingDelayToShowDialog) {
instance = new WaitSecurityDelayDialog();
// 1000ms - card delay notification interval
instance.setup(msec + 1000, 1000);
instance.setCancelable(false);
if (instance.getFragmentManager() != null)
instance.show(instance.getFragmentManager(), TAG);
}
} else
instance.setRemainingTimeout(msec);
});
}
}
public static void onReadWait(final Activity activity, final int msec) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (timerToShowDelayDialog != null) {
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
}
if (msec == 0) {
if (instance != null) {
instance.dismiss();
instance = null;
}
return;
}
if (instance == null) {
if( msec>MinRemainingDelayToShowDialog ) {
instance = new WaitSecurityDelayDialog();
// 1000ms - card delay notification interval
instance.setup(msec + 1000, 1000);
instance.setCancelable(false);
instance.show(activity.getFragmentManager(), "WaitSecurityDelayDialog");
}
} else {
instance.setRemainingTimeout(msec);
}
}
});
}
}

View file

@ -0,0 +1,153 @@
package com.tangem.presentation.dialog
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import android.support.v7.app.AppCompatDialogFragment
import android.widget.ProgressBar
import com.tangem.presentation.activity.SignPaymentActivity
import com.tangem.presentation.event.ReadAfterRequest
import com.tangem.presentation.event.ReadBeforeRequest
import com.tangem.presentation.event.ReadWait
import com.tangem.presentation.event.TransactionFinishWithSuccess
import com.tangem.util.LOG
import com.tangem.wallet.R
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.util.*
class WaitSecurityDelayDialogNew : AppCompatDialogFragment() {
companion object {
val TAG: String = WaitSecurityDelayDialogNew::class.java.simpleName
private const val MIN_REMAINING_DELAY_TO_SHOW_DIALOG = 1000
private const val DELAY_BEFORE_SHOW_DIALOG = 5000
}
private lateinit var pb: ProgressBar
private var msTimeout = 60000
private var msProgress = 0
private var timer: Timer? = null
private var timerToShowDelayDialog: Timer? = null
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = activity!!.layoutInflater
val v = inflater.inflate(R.layout.dialog_wait_pin2, null)
pb = v.findViewById(R.id.progressBar)
pb.max = msTimeout
pb.progress = msProgress
timer = Timer()
timer!!.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
pb.post {
val progress = pb.progress
if (progress < pb.max)
pb.progress = progress + 1000
}
}
}, 1000, 1000)
return AlertDialog.Builder(activity)
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.security_delay)
.setView(v)
.setCancelable(false)
.create()
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
@Subscribe
fun readBeforeRequest(readBeforeRequest: ReadBeforeRequest) {
LOG.i(TAG, "readBeforeRequest 111")
if (timerToShowDelayDialog != null || readBeforeRequest.timeout!! < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG)
return
timerToShowDelayDialog = Timer()
timerToShowDelayDialog!!.schedule(object : TimerTask() {
override fun run() {
setup(readBeforeRequest.timeout!!, DELAY_BEFORE_SHOW_DIALOG)
isCancelable = false
if (!isAdded)
show(activity?.supportFragmentManager, TAG)
}
}, DELAY_BEFORE_SHOW_DIALOG.toLong())
}
@Subscribe
fun readAfterRequest(readAfterRequest: ReadAfterRequest) {
LOG.i(TAG, "readAfterRequest 222")
if (timerToShowDelayDialog == null)
return
timerToShowDelayDialog!!.cancel()
timerToShowDelayDialog = null
}
@Subscribe
fun readWait(readWait: ReadWait) {
LOG.i(TAG, "readWait 333")
if (timerToShowDelayDialog != null) {
timerToShowDelayDialog!!.cancel()
timerToShowDelayDialog = null
}
if (readWait.msec == 0) {
dismiss()
return
}
if (readWait.msec!! > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) {
// 1000ms - card delay notification interval
setup(readWait.msec!! + 1000, 1000)
isCancelable = false
// if (!isAdded)
// show(activity?.supportFragmentManager, TAG)
} else
setRemainingTimeout(readWait.msec!!)
}
private fun setup(msTimeout: Int, msProgress: Int) {
this.msTimeout = msTimeout
this.msProgress = msProgress
}
private fun setRemainingTimeout(msec: Int) {
pb.post {
val progress = pb.progress
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
pb.max = progress + msec
timer!!.cancel()
timer = null
} else {
val newProgress = pb.max - msec
if (pb.max > progress)
pb.progress = newProgress
else
pb.max = progress + msec
}
}
}
}

View file

@ -0,0 +1,4 @@
package com.tangem.presentation.event
class ReadAfterRequest {
}

View file

@ -0,0 +1,5 @@
package com.tangem.presentation.event
class ReadBeforeRequest {
var timeout: Int? = null
}

View file

@ -0,0 +1,5 @@
package com.tangem.presentation.event
class ReadWait {
var msec: Int? = null
}

View file

@ -0,0 +1,5 @@
package com.tangem.presentation.event
class TransactionFinishWithError {
var message: String? = null
}

View file

@ -0,0 +1,5 @@
package com.tangem.presentation.event
class TransactionFinishWithSuccess {
var message: String? = null
}

View file

@ -8,19 +8,24 @@ import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat
import android.text.Html
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.Toast
import com.tangem.data.db.PINStorage
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.presentation.activity.CreateNewWalletActivity
import com.tangem.presentation.activity.PurgeActivity
import com.tangem.presentation.activity.PinRequestActivity
import com.tangem.presentation.activity.PinSwapActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.presentation.activity.*
import com.tangem.presentation.dialog.PINSwapWarningDialog
import com.tangem.tangemcard.android.data.PINStorage
import com.tangem.tangemcard.android.reader.NfcManager
import com.tangem.tangemcard.data.TangemCard
import com.tangem.tangemcard.data.loadFromBundle
import com.tangem.tangemcard.reader.CardProtocol
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fr_verify_card.*
@ -31,15 +36,6 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = VerifyCard::class.java.simpleName
private const val REQUEST_CODE_SEND_PAYMENT = 1
private const val REQUEST_CODE_PURGE = 2
private const val REQUEST_CODE_REQUEST_PIN2_FOR_PURGE = 3
private const val REQUEST_CODE_VERIFY_CARD = 4
private const val REQUEST_CODE_ENTER_NEW_PIN = 5
private const val REQUEST_CODE_ENTER_NEW_PIN2 = 6
private const val REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN = 7
private const val REQUEST_CODE_SWAP_PIN = 8
}
private var nfcManager: NfcManager? = null
@ -70,9 +66,10 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
// set listeners
fabMenu.setOnClickListener { showMenu(fabMenu) }
btnOk.setOnClickListener {
val data = prepareResultIntent()
data.putExtra("modification", "update")
data.putExtra(Constant.EXTRA_MODIFICATION, "update")
activity?.finish()
}
}
@ -93,50 +90,50 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
var data = data
// var data = data
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_ENTER_NEW_PIN -> if (resultCode == Activity.RESULT_OK) {
Constant.REQUEST_CODE_ENTER_NEW_PIN -> if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (data.extras!!.containsKey("confirmPIN")) {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN = data.getStringExtra("newPIN")
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
} else {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("newPIN", data.getStringExtra("newPIN"))
intent.putExtra("mode", PinRequestActivity.Mode.ConfirmNewPIN.toString())
startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN)
}
}
}
REQUEST_CODE_ENTER_NEW_PIN2 -> if (resultCode == Activity.RESULT_OK) {
Constant.REQUEST_CODE_ENTER_NEW_PIN2 -> if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (data.extras!!.containsKey("confirmPIN2")) {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN2 = data.getStringExtra("newPIN2")
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
} else {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("newPIN2", data.getStringExtra("newPIN2"))
intent.putExtra("mode", PinRequestActivity.Mode.ConfirmNewPIN2.toString())
startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN2)
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN2)
}
}
}
REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
if (newPIN == "") newPIN = ctx.card!!.pin
if (newPIN2 == "") newPIN2 = PINStorage.getPIN2()
if (newPIN2 == "") newPIN2 = App.pinStorage.getPIN2()
val pinSwapWarningDialog = PINSwapWarningDialog()
pinSwapWarningDialog.setOnRefreshPage { startSwapPINActivity() }
pinSwapWarningDialog.setOnRefreshPage { (activity as VerifyCardActivity).navigator.showPinSwap(context as Activity, newPIN, newPIN2) }
val bundle = Bundle()
if (!PINStorage.isDefaultPIN(newPIN) || !PINStorage.isDefaultPIN2(newPIN2))
if (!CardProtocol.isDefaultPIN(newPIN) || !CardProtocol.isDefaultPIN2(newPIN2))
bundle.putString(PINSwapWarningDialog.EXTRA_MESSAGE, getString(R.string.if_you_forget))
else
bundle.putString(PINSwapWarningDialog.EXTRA_MESSAGE, getString(R.string.if_you_use_default))
@ -144,11 +141,11 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
pinSwapWarningDialog.show(activity?.supportFragmentManager, PINSwapWarningDialog.TAG)
}
REQUEST_CODE_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
Constant.REQUEST_CODE_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
if (data == null) {
data = Intent()
// data = Intent()
ctx.saveToIntent(data)
data.putExtra("modification", "delete")
data?.putExtra("modification", "delete")
} else
data.putExtra("modification", "update")
activity!!.setResult(Activity.RESULT_OK, data)
@ -159,12 +156,12 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
return
} else {
if (data != null && data.extras!!.containsKey("message")) {
@ -172,18 +169,16 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
}
}
}
REQUEST_CODE_REQUEST_PIN2_FOR_PURGE -> if (resultCode == Activity.RESULT_OK) {
val intent = Intent(context, PurgeActivity::class.java)
ctx.saveToIntent(intent)
startActivityForResult(intent, REQUEST_CODE_PURGE)
}
REQUEST_CODE_PURGE -> if (resultCode == Activity.RESULT_OK) {
Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE -> if (resultCode == Activity.RESULT_OK)
(activity as VerifyCardActivity).navigator.showPurge(context as Activity, ctx)
Constant.REQUEST_CODE_PURGE -> if (resultCode == Activity.RESULT_OK) {
if (data == null) {
data = Intent()
ctx.saveToIntent(data)
data.putExtra("modification", "delete")
data?.putExtra(Constant.EXTRA_MODIFICATION, "delete")
} else {
data.putExtra("modification", "update")
data.putExtra(Constant.EXTRA_MODIFICATION, "update")
}
activity!!.setResult(Activity.RESULT_OK, data)
activity!!.finish()
@ -193,12 +188,12 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (resultCode == CreateNewWalletActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
return
} else {
if (data != null && data.extras!!.containsKey("message")) {
@ -207,35 +202,11 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
}
updateViews()
}
REQUEST_CODE_SEND_PAYMENT -> {
if (resultCode == Activity.RESULT_OK) {
srlVerifyCard.isRefreshing = true
ctx.coinData!!.clearInfo()
ctx.card!!.switchToInitialBlockchain()
updateViews()
}
if (data != null) {
if (data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) {
val updatedCard = TangemCard(data.getStringExtra("UID"))
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (data.extras!!.containsKey("message")) {
if (resultCode == Activity.RESULT_OK)
ctx.message = data.getStringExtra("message")
else
ctx.error = data.getStringExtra("message")
}
updateViews()
}
}
}
}
override fun onTagDiscovered(tag: Tag) {
try {
// Log.w(getClass().getName(), "Ignore discovered tag!");
nfcManager!!.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
@ -277,8 +248,9 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
}
tvIssuer.text = ctx.card!!.issuerDescription
tvCardRegistredDate.text = ctx.card!!.personalizationDateTimeDescription
val html = Html.fromHtml(ctx.card!!.blockchainName)
tvCardRegistredDate.text = DateUtils.formatDateTime(null, ctx.card!!.personalizationDateTime.time, DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NUMERIC_DATE or DateUtils.FORMAT_SHOW_YEAR)
val html = Html.fromHtml(ctx.blockchainName)
tvBlockchain.text = html
tvValidationNode.text = ctx.coinData!!.validationNodeDescription
@ -358,7 +330,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
tvFeatures.text = features
if (ctx.card!!.useDefaultPIN1()!!) {
if (ctx.card!!.useDefaultPIN1()) {
imgPIN.setImageResource(R.drawable.unlock_pin1)
imgPIN.setOnClickListener { Toast.makeText(context, R.string.this_banknote_protected_default_PIN1_code, Toast.LENGTH_LONG).show() }
} else {
@ -385,7 +357,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
imgDeveloperVersion.visibility = View.INVISIBLE
if (ctx.card!!.status == TangemCard.Status.Loaded) {
tvWallet.text = ctx.card!!.shortWalletString
tvWallet.text = ctx.coinData!!.shortWalletString
if (ctx.card!!.isWalletPublicKeyValid) {
tvWalletIdentity.setText(R.string.possession_proved)
tvWalletIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed))
@ -422,7 +394,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
intent.putExtra("mode", PinRequestActivity.Mode.RequestNewPIN.toString())
newPIN = ""
newPIN2 = ""
startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN)
}
private fun doResetPin() {
@ -432,7 +404,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
ctx.saveToIntent(intent)
newPIN = PINStorage.getDefaultPIN()
newPIN2 = ""
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
private fun doResetPin2() {
@ -442,7 +414,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
ctx.saveToIntent(intent)
newPIN = ""
newPIN2 = PINStorage.getDefaultPIN2()
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
private fun doResetPins() {
@ -452,7 +424,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
ctx.saveToIntent(intent)
newPIN = PINStorage.getDefaultPIN()
newPIN2 = PINStorage.getDefaultPIN2()
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
private fun doSetPin2() {
@ -461,7 +433,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
intent.putExtra("mode", PinRequestActivity.Mode.RequestNewPIN2.toString())
newPIN = ""
newPIN2 = ""
startActivityForResult(intent, REQUEST_CODE_ENTER_NEW_PIN2)
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN2)
}
private fun doPurge() {
@ -477,15 +449,7 @@ class VerifyCard : Fragment(), NfcAdapter.ReaderCallback {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
}
private fun startSwapPINActivity() {
val intent = Intent(context, PinSwapActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra("newPIN", newPIN)
intent.putExtra("newPIN2", newPIN2)
startActivityForResult(intent, REQUEST_CODE_SWAP_PIN)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
}
fun prepareResultIntent(): Intent {

View file

@ -3,6 +3,7 @@ package com.tangem.util;
import android.util.Log;
import com.tangem.domain.wallet.ECDSASignatureETH;
import com.tangem.tangemcard.util.Util;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;

View file

@ -0,0 +1,58 @@
package com.tangem.util;
import android.util.Log;
import com.tangem.wallet.BuildConfig;
public final class LOG {
public static String TAG = "com.tangem.wallet";
public static void d(String msg) {
d(null, msg);
}
public static void e(String msg) {
e(null, msg);
}
public static void d(String tag, String msg) {
if (BuildConfig.DEBUG)
Log.d(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg);
}
public static void d(String tag, int msg) {
if (BuildConfig.DEBUG)
Log.d(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg));
}
public static void i(String tag, String msg) {
if (BuildConfig.DEBUG)
Log.i(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg);
}
public static void i(String tag, int msg) {
if (BuildConfig.DEBUG)
Log.i(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg));
}
public static void w(String tag, String msg) {
if (BuildConfig.DEBUG)
Log.w(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg);
}
public static void w(String tag, int msg) {
if (BuildConfig.DEBUG)
Log.w(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg));
}
public static void e(String tag, String msg) {
if (BuildConfig.DEBUG)
Log.e(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), msg);
}
public static void e(String tag, int msg) {
if (BuildConfig.DEBUG)
Log.d(TAG + (tag != null && !tag.equals("") ? "." + tag : ""), String.valueOf(msg));
}
}

View file

@ -6,6 +6,7 @@ import android.graphics.Bitmap
import android.graphics.Color
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.widget.Toast
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.WriterException
@ -48,4 +49,19 @@ object UtilHelper {
}
}
private var singleToast: Toast? = null
private var showTime: Date = Date()
fun showSingleToast(context: Context?, text: String) {
if (singleToast == null || !singleToast!!.view.isShown || showTime.time + 2000 < Date().time) {
if (singleToast != null)
singleToast!!.cancel()
if (context != null) {
singleToast = Toast.makeText(context, text, Toast.LENGTH_LONG)
singleToast!!.show()
showTime = Date()
}
}
}
}

View file

@ -213,7 +213,7 @@
android:orientation="horizontal">
<android.support.v7.widget.AppCompatButton
android:id="@+id/btnLookup"
android:id="@+id/btnExplore"
style="@style/AppTheme.RoundedCornerMaterialButton"
android:layout_width="0dp"
android:layout_height="34dp"
@ -363,7 +363,7 @@
android:textColor="@color/primary_dark" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/btnScanAgain"
android:id="@+id/btnNewScan"
style="@style/AppTheme.RoundedCornerMaterialButton"
android:layout_width="0dp"
android:layout_height="34dp"

View file

@ -107,6 +107,9 @@
<string name="if_you_forget">If you forget your new PIN you will lose your money forever!</string>
<string name="if_you_use_default">If you use default PIN someone can steal your money!</string>
<string name="cannot_obtain_data_from_blockchain">Cannot obtain data from blockchain</string>
<string name="cannot_obtain_data_from_blockchain_no_connection">Cannot obtain data from blockchain (connection refused)</string>
<string name="cannot_obtain_data_from_blockchain_no_answer">Cannot obtain data from blockchain (empty answer received)</string>
<string name="cannot_obtain_data_from_blockchain_communication_error">Cannot obtain data from blockchain (communication error)</string>
<string name="sending_cached_transaction">Sending cached transaction…</string>
<string name="not_implemented">NOT IMPLEMENTED</string>
<string name="this_banknote_protected_default_PIN1_code">This banknote is protected by default PIN1 code</string>
@ -158,7 +161,7 @@
<string name="please_wait">Please wait while the payment is sent…</string>
<string name="transaction_has_been_successfully_signed">Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while</string>
<string name="try_again_failed_to_send_transaction">Try again. Failed to send transaction (%1$s)</string>
<string name="cannot_sign_transaction._make_sure_you_enter_correct_pin_2">Cannot sign transaction. Make sure you enter correct PIN2!</string>
<string name="cannot_sign_transaction_make_sure_you_enter_correct_pin_2">Cannot sign transaction. Make sure you enter correct PIN2!</string>
<string name="cannot_sign_transaction_wrong_amount">Amount and fee exceed total wallet balance!</string>
<!-- PreparePaymentActivity -->

View file

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.10'
ext.kotlin_version = '1.3.11'
repositories {
google()
jcenter()

View file

@ -1 +1 @@
include ':app', ':tangemcard'
include ':app', ':tangemcard-android', ':tangemserver-android', ':tangemcard-common'

View file

@ -0,0 +1,59 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildToolsVersion '28.0.3'
}
dependencies {
implementation project(':tangemcard-common')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
// implementation 'com.android.support:design:28.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.scottyab:rootbeer-lib:0.0.7'
// implementation 'com.android.support:support-compat:28.0.0'
// implementation 'com.android.support:support-v4:28.0.0'
// implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
// implementation 'com.squareup.retrofit2:retrofit:2.5.0'
// implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
// implementation 'com.skyfishjy.ripplebackground:library:1.0.1'
// implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
// implementation 'com.google.dagger:dagger:2.16'
// implementation 'org.bitcoinj:bitcoinj-parent:0.14.7'
// implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
// annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
// implementation 'info.hoang8f:android-segmented:1.0.6'
// implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
// implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
// implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}

View file

@ -1,4 +1,4 @@
package com.tangem.tangemcard;
package com.tangem.tangemcard.android;
import android.content.Context;
import android.support.test.InstrumentationRegistry;

View file

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tangem.tangemcard" >
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<uses-permission android:name="android.permission.NFC" />
</manifest>

View file

@ -1,25 +1,22 @@
package com.tangem.domain.cardReader;
package com.tangem.tangemcard.android.data;
import android.content.Context;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.tangem.util.Util;
import com.tangem.tangemcard.data.external.FirmwaresDigestsProvider;
import com.tangem.tangemcard.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class Firmwares {
public class Firmwares implements FirmwaresDigestsProvider {
private static JsonArray jaFirmwares = null;
public static boolean needInit() {
return jaFirmwares == null;
}
public static void init(Context context) {
public Firmwares(Context context) {
try (InputStream is = context.getAssets().open("fw_hashes.json")) {
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
JsonParser parser = new JsonParser();
@ -30,21 +27,14 @@ public class Firmwares {
}
}
public static class VerifyCodeRecord {
public String hashAlg;
public int blockIndex;
public int blockCount;
public byte[] challenge;
public byte[] digest;
}
public static VerifyCodeRecord selectRandomVerifyCodeBlock(String firmwareVersion) throws IOException {
@Override
public FirmwaresDigestsProvider.VerifyCodeRecord selectRandomVerifyCodeBlock(String firmwareVersion) {
try {
for (int i = 0; i < jaFirmwares.size(); i++) {
JsonObject jsVersion = jaFirmwares.get(i).getAsJsonObject();
if (jsVersion.get("fw").getAsString().equals(firmwareVersion)) {
VerifyCodeRecord result = new VerifyCodeRecord();
FirmwaresDigestsProvider.VerifyCodeRecord result = new FirmwaresDigestsProvider.VerifyCodeRecord();
result.hashAlg = "sha-256";
JsonArray jsHashes = jsVersion.get(result.hashAlg).getAsJsonArray();
result.challenge = Util.hexToBytes(jsVersion.get("challenge").getAsString());

View file

@ -1,11 +1,12 @@
package com.tangem.data.db;
package com.tangem.tangemcard.android.data;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.tangemcard.data.external.PINsProvider;
import com.tangem.tangemcard.reader.CardProtocol;
import java.util.ArrayList;
import java.util.List;
@ -17,7 +18,7 @@ import javax.crypto.Cipher;
* Global PIN Storage
*/
public class PINStorage {
public class PINStorage implements PINsProvider {
private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2;
private static SharedPreferences sharedPreferences = null;
@ -30,8 +31,8 @@ public class PINStorage {
mPIN2 = null;
}
public static List<String> getPINs() {
@Override
public List<String> getPINs() {
ArrayList<String> result = new ArrayList<>();
if (mLastUsedPIN != null) result.add(mLastUsedPIN);
if (mEncryptedPIN != null && !result.contains(mEncryptedPIN)) result.add(mEncryptedPIN);
@ -41,7 +42,7 @@ public class PINStorage {
return result;
}
public static void setLastUsedPIN(String PIN) {
public void setLastUsedPIN(String PIN) {
mLastUsedPIN = PIN;
}
@ -177,18 +178,10 @@ public class PINStorage {
editor.apply();
}
public static String getPIN2() {
public String getPIN2() {
return mPIN2;
}
public static boolean isDefaultPIN(String pin) {
return (CardProtocol.DefaultPIN.equals(pin));
}
public static boolean isDefaultPIN2(String pin2) {
return (CardProtocol.DefaultPIN2.equals(pin2));
}
public static String getDefaultPIN() {
return CardProtocol.DefaultPIN;
}

View file

@ -1,7 +1,6 @@
package com.tangem.data.nfc
package com.tangem.tangemcard.android.nfc
import android.os.Build
import com.tangem.tangemcard.nfc.NFCLocation
class DeviceNFCAntennaLocation {
companion object {

View file

@ -1,4 +1,4 @@
package com.tangem.tangemcard.nfc
package com.tangem.tangemcard.android.nfc
enum class NFCLocation(val codename: String, val fullName: String, val orientation: Int, val x: Int, val y: Int, val z: Int) {
model1("sailfish", "Google Pixel", 0, 65, 25, 0),

View file

@ -1,4 +1,4 @@
package com.tangem.presentation.dialog
package com.tangem.tangemcard.android.presentation.dialog
import android.app.Dialog
import android.content.Intent
@ -8,7 +8,7 @@ import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import com.tangem.wallet.R
import com.tangem.tangemcard.R
class NfcEnableDialog : DialogFragment() {

Some files were not shown because too many files have changed in this diff Show more