diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5e1c94d31e..c154bdefa1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -132,6 +132,22 @@
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" />
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/data/network/Cryptonit.java b/app/src/main/java/com/tangem/data/network/Cryptonit.java
new file mode 100644
index 0000000000..de709d7289
--- /dev/null
+++ b/app/src/main/java/com/tangem/data/network/Cryptonit.java
@@ -0,0 +1,327 @@
+package com.tangem.data.network;
+
+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;
+import retrofit2.Retrofit;
+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
+ * Used in Cryptonit service
+ */
+public class Cryptonit {
+ private static final String SERVER_URL = "https://www.cryptonit.net:443/gateway/";
+
+ private static class Method {
+ public static final String AUTHENTICATE = SERVER_URL + "public/authenticate";
+ public static final String BALANCE = SERVER_URL + "private/balance";
+ public static final String WITHDRAW_COINS = SERVER_URL + "private/withdrawCoins";
+ }
+
+ public static class Model {
+
+ static class Authenticate
+ {
+ public static class Request
+ {
+ public String username;
+ public String password;
+ }
+ public static class Response
+ {
+ boolean success;
+ String[] missing_authenticators;
+ public Object[] infos;
+ public Object[] warnings;
+ public Object[] errors;
+ AuthenticationResult results;
+ }
+
+ static class AuthenticationResult {
+// token (string): authentication token,
+// nick (string),
+// stayLoggedIn (boolean),
+// integer lastLogin (integer): JavaScript time OR NEVER for first login,
+// preferredLanguage (string) = ['en' or 'de']
+
+ }
+
+ }
+
+ public static class Balance {
+ static class Request {
+ String[] currencies;
+ }
+ public static class Response {
+ public boolean success;
+ public Object[] infos;
+ public Object[] warnings;
+ public Object[] errors;
+ public BalanceResult[] results;
+
+ public static class BalanceResult {
+ public String currency;
+ public double balance;
+ public String receiveAddress; //the current address to receive funds for this account, if available,
+ public boolean fiat; // Is this a FIAT currency? If false, this is a CRYPTO currency.,
+ public Object[] unprocessedTransactions; // (array, optional): list of unprocessed transactions, if pass field withTransactions
+ }
+ }
+ }
+
+ public static class WithdrawCoins {
+ public static class Request {
+ public String currency;
+ public Double amount;
+ String toAddress;
+ Double includeMinerFee;
+ String password;
+ }
+ public static class Response {
+ public Boolean success;
+ public String[] missing_authenticators;
+ public Object[] infos;
+ public Object[] warnings;
+ public Object[] errors;
+ }
+ }
+ }
+
+ public interface Api {
+ @Headers("Content-Type: application/json")
+ @POST(Method.AUTHENTICATE)
+ Call authenticate(@Body Model.Authenticate.Request request);
+
+ @Headers("Content-Type: application/json")
+ @POST(Method.BALANCE)
+ Observable getBalance(@Header("Auth-Token") String authToken, @Body Model.Balance.Request request);
+
+ @Headers("Content-Type: application/json")
+ @POST(Method.WITHDRAW_COINS)
+ Observable withdrawCoins(@Header("Auth-Token") String authToken, @Body Model.WithdrawCoins.Request request);
+ }
+
+ private Api api = null;
+ private BalanceListener balanceListener;
+ private WithdrawalListener withdrawalListener;
+ private ErrorListener errorListener;
+ private Context context;
+ private String authToken;
+
+ public Cryptonit(Context context) {
+ this.context = context;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ username = sp.getString(context.getResources().getString(R.string.key_cryptonit_username), "");
+ password = sp.getString(context.getResources().getString(R.string.key_cryptonit_password), "");
+ fee = sp.getString(context.getResources().getString(R.string.key_cryptonit_fee), "0.0");
+ }
+
+ public String username;
+ public String password;
+ private String fee;
+
+ public String getFee() {
+ return fee;
+ }
+
+ public void setFee(String value)
+ {
+ fee=value;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ sp.edit()
+ .putString(context.getResources().getString(R.string.key_cryptonit_fee), fee)
+ .apply();
+ }
+
+ public Boolean haveAccountInfo() {
+ return !username.isEmpty() && !password.isEmpty();
+ }
+
+ public void saveAccountInfo() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ sp.edit()
+ .putString(context.getResources().getString(R.string.key_cryptonit_username), username)
+ .putString(context.getResources().getString(R.string.key_cryptonit_password), password)
+ .apply();
+
+ }
+
+ public interface BalanceListener {
+ void onBalanceData(Model.Balance.Response response);
+ }
+
+ public interface WithdrawalListener {
+ void onWithdrawalComplete(Model.WithdrawCoins.Response response);
+ }
+
+ public interface ErrorListener {
+ void onError(Throwable throwable);
+ }
+
+ public void setBalanceListener(BalanceListener listener) {
+ balanceListener = listener;
+ }
+
+ public void setWithdrawalListener(WithdrawalListener listener) {
+ withdrawalListener = listener;
+ }
+
+ public void setErrorListener(ErrorListener listener) {
+ errorListener = listener;
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestBalance(String currency) {
+
+ initApi();
+
+// Log.e("CRYPTONIT2", "username: " + username);
+// Log.e("CRYPTONIT2", "password: " + password);
+// Log.e("CRYPTONIT2", "auth-token: " + authToken);
+
+ Model.Balance.Request request=new Model.Balance.Request();
+ request.currencies=new String[] {currency};
+ api.getBalance(authToken, request)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(balanceModel -> balanceListener.onBalanceData(balanceModel),
+ // handle error
+ this::FireError
+ );
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestWithdrawCoins(String currency, Double amount, String address) {
+
+ initApi();
+
+// Log.e("CRYPTONIT2", "username: " + username);
+// Log.e("CRYPTONIT2", "password: " + password);
+// Log.e("CRYPTONIT2", "auth-token: " + authToken);
+
+ Model.WithdrawCoins.Request request=new Model.WithdrawCoins.Request();
+ request.currency=currency;
+ request.amount=amount;
+ request.toAddress=address;
+ request.includeMinerFee=Double.parseDouble(fee);
+ request.password=password;
+
+ api.withdrawCoins(authToken, request)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> {
+ if (response.success != null && response.success)
+ withdrawalListener.onWithdrawalComplete(response);
+ else {
+ errorListener.onError(new Exception(((LinkedTreeMap) response.errors[0]).entrySet().toArray()[0].toString()));
+ }
+ },
+ // handle error
+ this::FireError
+ );
+ }
+
+ private void FireError(Throwable e) throws IOException {
+ if (e.getClass() == HttpException.class && ((HttpException) e).code() == 500) {
+ JsonObject jsonObject = new JsonParser().parse(((HttpException) e).response().errorBody().string()).getAsJsonObject();
+ errorListener.onError(new Exception(e.getMessage() + ": " + jsonObject.get("errors").getAsString()));
+// if (jsonObject.get("reason").getAsString().equals("Invalid nonce")) nonce += 1000;
+ } else {
+ errorListener.onError(e);
+ }
+ }
+
+ private void initApi() {
+
+ if (api != null) return;
+
+// HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
+// logging.setLevel(HttpLoggingInterceptor.Level.BODY);
+
+ OkHttpClient httpClient = new OkHttpClient.Builder().
+// addInterceptor(logging).
+ addInterceptor(new AuthorizationInterceptor()).build();
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl(SERVER_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .client(httpClient)
+ .build();
+
+ api = retrofit.create(Api.class);
+ }
+
+ public class AuthorizationInterceptor implements Interceptor {
+
+ AuthorizationInterceptor() {
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request mainRequest=chain.request();
+
+ if( !mainRequest.url().toString().endsWith("authenticate")&& authToken==null )
+ {
+ Model.Authenticate.Request authRequest=new Model.Authenticate.Request();
+ authRequest.username=username;
+ authRequest.password=password;
+ retrofit2.Response authResponse=api.authenticate(authRequest).execute();
+ if( authResponse.isSuccessful() && authResponse.body().success)
+ {
+ String newToken = authResponse.headers().get("auth-token");
+ if (newToken != null) {
+ authToken = newToken;
+ }
+ }else{
+ throw new IOException("Authentication error: "+authResponse.message());
+ }
+ }
+
+ if( authToken!=null ) {
+ mainRequest = mainRequest.newBuilder().addHeader("auth-token", authToken).build();
+ }
+ Response mainResponse = chain.proceed(mainRequest);
+ if (!mainResponse.isSuccessful()) {
+ authToken=null;
+ }
+ return mainResponse;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/data/network/Cryptonit_OtherAPI.java b/app/src/main/java/com/tangem/data/network/Cryptonit_OtherAPI.java
new file mode 100644
index 0000000000..2f885a73ce
--- /dev/null
+++ b/app/src/main/java/com/tangem/data/network/Cryptonit_OtherAPI.java
@@ -0,0 +1,283 @@
+package com.tangem.data.network;
+
+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.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.wallet.R;
+
+import java.io.IOException;
+
+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.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.RequestBody;
+//import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+import retrofit2.http.Headers;
+import retrofit2.http.Multipart;
+import retrofit2.http.POST;
+import retrofit2.http.Part;
+import retrofit2.http.Path;
+
+/**
+ * HTTP
+ * Used in Cryptonit_OtherAPI service
+ */
+public class Cryptonit_OtherAPI {
+ private static final String SERVER_URL = "https://api.cryptonit.net/api/";
+
+ private static class Method {
+ public static final String BALANCE = SERVER_URL + "balance/{cryptoCurrency}%2F{fiatCurrency}";
+ public static final String CRYPTO_WITHDRAWAL = SERVER_URL + "crypto_withdrawal/";
+ }
+
+ public static class Response {
+ public static class Balance{
+ @SerializedName("btc_balance")
+ public String btc_balance;
+ @SerializedName("eur_balance")
+ public String eur_balance;
+ @SerializedName("eth_balance")
+ public String eth_balance;
+ @SerializedName("etn_balance")
+ public String etn_balance;
+ @SerializedName("btc_reserved")
+ public String btc_reserved;
+ @SerializedName("eur_reserved")
+ public String eur_reserved;
+ @SerializedName("eth_reserved")
+ public String eth_reserved;
+ @SerializedName("etn_reserved")
+ public String etn_reserved;
+ @SerializedName("btc_available")
+ public String btc_available;
+ @SerializedName("eur_available")
+ public String eur_available;
+ @SerializedName("eth_available")
+ public String eth_available;
+ @SerializedName("etn_available")
+ public String etn_available;
+ @SerializedName("btceur_fee")
+ public String btceur_fee;
+ @SerializedName("etheur_fee")
+ public String etheur_fee;
+ @SerializedName("etnbtc_fee")
+ public String etnbtc_fee;
+ @SerializedName("fee")
+ public String fee;
+ }
+
+ public static class CryptoWithdrawal {
+ @SerializedName("success")
+ public Boolean success;
+ @SerializedName("status")
+ public String status;
+ @SerializedName("reason")
+ public Object reason;
+ }
+ }
+
+ public interface Api {
+ @Multipart
+ @Headers("accept: multipart/form-data")
+ @POST(Method.BALANCE)
+ Observable getBalance(
+ @Path("cryptoCurrency") String cryptoCurrency, @Path("fiatCurrency") String fiatCurrency,
+ @Part(value = "key") RequestBody key, @Part("signature") RequestBody signature, @Part("nonce") RequestBody nonce);
+
+ @Multipart
+ @Headers("accept: multipart/form-data")
+ @POST(Method.CRYPTO_WITHDRAWAL)
+ Observable cryptoWithdrawal(
+ @Part(value = "currency") RequestBody currency, @Part("amount") RequestBody amount, @Part("address") RequestBody address,
+ @Part(value = "key") RequestBody key, @Part("signature") RequestBody signature, @Part("nonce") RequestBody nonce);
+ }
+
+ private Api api = null;
+ private BalanceListener balanceListener;
+ private WithdrawalListener withdrawalListener;
+ private ErrorListener errorListener;
+ private Context context;
+
+ public Cryptonit_OtherAPI(Context context) {
+ this.context = context;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ key = sp.getString(context.getResources().getString(R.string.key_cryptonit_key), "");
+ userId = sp.getString(context.getResources().getString(R.string.key_cryptonit_user_id), "");
+ secret = sp.getString(context.getResources().getString(R.string.key_cryptonit_secret), "");
+ nonce = sp.getInt(context.getResources().getString(R.string.key_cryptonit_nonce), 0);
+ }
+
+ public String key;
+ public String userId;
+ public String secret;
+ private Integer nonce;
+
+ public String getSecretDescription() {
+ if (secret == null || secret.isEmpty()) return "";
+ return secret.substring(0, 3) + "..." + secret.substring(secret.length() - 3, secret.length());
+ }
+
+ public Boolean havaAccountInfo() {
+ return (!userId.isEmpty() && !key.isEmpty() && !secret.isEmpty());
+ }
+
+ public void saveAccountInfo() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ nonce = 1;
+ sp.edit()
+ .putString(context.getResources().getString(R.string.key_cryptonit_key), key)
+ .putString(context.getResources().getString(R.string.key_cryptonit_user_id), userId)
+ .putString(context.getResources().getString(R.string.key_cryptonit_secret), secret)
+ .putInt(context.getResources().getString(R.string.key_cryptonit_nonce), nonce)
+ .apply();
+
+ }
+
+ private void incNonce() {
+ nonce++;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ sp.edit().putInt(context.getResources().getString(R.string.key_cryptonit_nonce), nonce).apply();
+ }
+
+ public interface BalanceListener {
+ void onBalanceData(Response.Balance response);
+ }
+
+ public interface WithdrawalListener {
+ void onWithdrawalComplete(Response.CryptoWithdrawal response);
+ }
+
+ public interface ErrorListener {
+ void onError(Throwable throwable);
+ }
+
+ public void setBalanceListener(BalanceListener listener) {
+ balanceListener = listener;
+ }
+
+ public void setWithdrawalListener(WithdrawalListener listener) {
+ withdrawalListener = listener;
+ }
+
+ public void setErrorListener(ErrorListener listener) {
+ errorListener = listener;
+ }
+
+ private String calcSignature() throws Exception {
+ incNonce();
+ Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+ SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
+ sha256_HMAC.init(secret_key);
+
+ String data = nonce.toString() + userId + key;
+ return Util.bytesToHex(sha256_HMAC.doFinal(data.getBytes("UTF-8")));
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestBalance(String cryptoCurrency, String fiatCurrency) throws Exception {
+
+ initApi();
+
+ String signature = calcSignature();
+
+ Log.e("CRYPTONIT", "user: " + userId);
+ Log.e("CRYPTONIT", "key: " + key);
+ Log.e("CRYPTONIT", "secret: " + secret);
+ Log.e("CRYPTONIT", "nonce: " + nonce);
+ Log.e("CRYPTONIT", "signature: " + signature);
+
+ api.getBalance(cryptoCurrency, fiatCurrency,
+ RequestBody.create(MediaType.parse("text/plain"), key),
+ RequestBody.create(MediaType.parse("text/plain"), signature),
+ RequestBody.create(MediaType.parse("text/plain"), nonce.toString()))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(balanceModel -> balanceListener.onBalanceData(balanceModel),
+ // handle error
+ this::FireError
+ );
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestCryptoWithdrawal(String currency, String amount, String address) throws Exception {
+
+ initApi();
+
+ String signature = calcSignature();
+
+ Log.e("CRYPTONIT", "user: " + userId);
+ Log.e("CRYPTONIT", "key: " + key);
+ Log.e("CRYPTONIT", "secret: " + secret);
+ Log.e("CRYPTONIT", "nonce: " + nonce);
+ Log.e("CRYPTONIT", "signature: " + signature);
+
+ api.cryptoWithdrawal(
+ RequestBody.create(MediaType.parse("text/plain"), currency),
+ RequestBody.create(MediaType.parse("text/plain"), amount),
+ RequestBody.create(MediaType.parse("text/plain"), address),
+ RequestBody.create(MediaType.parse("text/plain"), key),
+ RequestBody.create(MediaType.parse("text/plain"), signature),
+ RequestBody.create(MediaType.parse("text/plain"), nonce.toString()))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> {
+ if (response.success != null && response.success)
+ withdrawalListener.onWithdrawalComplete(response);
+ else {
+ LinkedTreeMap reason = (LinkedTreeMap) response.reason;
+ errorListener.onError(new Exception(reason.entrySet().toArray()[0].toString()));
+ }
+ },
+ // handle error
+ this::FireError
+ );
+ }
+
+ private void FireError(Throwable e) throws IOException {
+ if (e.getClass() == HttpException.class && ((HttpException) e).code() == 500) {
+ JsonObject jsonObject = new JsonParser().parse(((HttpException) e).response().errorBody().string()).getAsJsonObject();
+ errorListener.onError(new Exception(e.getMessage() + ": " + jsonObject.get("reason").getAsString()));
+ if (jsonObject.get("reason").getAsString().equals("Invalid nonce")) nonce += 1000;
+ } else {
+ errorListener.onError(e);
+ }
+ }
+
+ private void initApi() {
+
+ if (api != null) return;
+
+// HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
+// logging.setLevel(HttpLoggingInterceptor.Level.BODY);
+
+ OkHttpClient httpClient = new OkHttpClient.Builder().
+// addInterceptor(logging).
+ build();
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl(SERVER_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .client(httpClient)
+ .build();
+
+ api = retrofit.create(Api.class);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/data/network/Kraken.java b/app/src/main/java/com/tangem/data/network/Kraken.java
new file mode 100644
index 0000000000..a33b7cb5e6
--- /dev/null
+++ b/app/src/main/java/com/tangem/data/network/Kraken.java
@@ -0,0 +1,358 @@
+package com.tangem.data.network;
+
+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.wallet.R;
+
+import org.spongycastle.util.encoders.Base64;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+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.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+//import okhttp3.logging.HttpLoggingInterceptor;
+import okio.Buffer;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.POST;
+
+/**
+ * HTTP
+ * Used in Kraken service
+ */
+public class Kraken {
+ private static final String SERVER_URL = "https://api.kraken.com";
+
+ private static class Method {
+ public static final String BALANCE = SERVER_URL + "/0/private/Balance";
+ public static final String WITHDRAW_INFO = SERVER_URL + "/0/private/WithdrawInfo";
+ public static final String WITHDRAW = SERVER_URL + "/0/private/Withdraw";
+ }
+
+ public static class Model {
+
+ public static class Balance {
+ public static class Response {
+ public String[] error;
+ public Result result;
+
+ public static class Result {
+ public String XXBT;
+ public String XETH;
+ public String BCH;
+ }
+ }
+ }
+
+ public static class WithdrawInfo {
+ public static class Response {
+ public String[] error;
+ public Result result;
+
+ public static class Result {
+ public String fee;
+ public String amount;
+ }
+ }
+ }
+
+ public static class Withdraw {
+ public static class Response {
+ public String[] error;
+ public Result result;
+
+ public static class Result {
+ public String refid;
+ }
+ }
+ }
+ }
+
+ public interface Api {
+ @FormUrlEncoded
+ @POST(Method.BALANCE)
+ Observable getBalance(@Field("nonce") String nonce);
+
+ @FormUrlEncoded
+ @POST(Method.WITHDRAW_INFO)
+ Observable WithdrawInfo(@Field("nonce") String nonce, @Field("asset") String asset, @Field("key") String key, @Field("amount") String amount);
+
+ @FormUrlEncoded
+ @POST(Method.WITHDRAW)
+ Observable Withdraw(@Field("nonce") String nonce, @Field("asset") String asset, @Field("key") String key, @Field("amount") String amount);
+ }
+
+ private Api api = null;
+ private BalanceListener balanceListener;
+ private WithdrawalListener withdrawalListener;
+ private WithdrawalInfoListener withdrawalInfoListener;
+ private ErrorListener errorListener;
+ private Context context;
+
+ public Kraken(Context context) {
+ this.context = context;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ key = sp.getString(context.getResources().getString(R.string.key_kraken_key), "");
+ secret = sp.getString(context.getResources().getString(R.string.key_kraken_secret), "");
+ nonce = sp.getInt(context.getResources().getString(R.string.key_kraken_nonce), 0);
+ }
+
+ public String key;
+ public String secret;
+ private Integer nonce;
+
+ public String getSecretDescription() {
+ if (secret == null || secret.isEmpty()) return "";
+ return secret.substring(0, 3) + "..." + secret.substring(secret.length() - 3, secret.length());
+ }
+
+ public Boolean haveAccountInfo() {
+ return (!key.isEmpty() && !secret.isEmpty());
+ }
+
+ public void saveAccountInfo() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ sp.edit()
+ .putString(context.getResources().getString(R.string.key_kraken_key), key)
+ .putString(context.getResources().getString(R.string.key_kraken_secret), secret)
+ .putInt(context.getResources().getString(R.string.key_kraken_nonce), nonce)
+ .apply();
+
+ }
+
+ private void incNonce() {
+ nonce++;
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ sp.edit().putInt(context.getResources().getString(R.string.key_kraken_nonce), nonce).apply();
+ }
+
+ public interface BalanceListener {
+ void onBalanceData(Model.Balance.Response response);
+ }
+
+ public interface WithdrawalInfoListener {
+ void onWithdrawalInfoComplete(Model.WithdrawInfo.Response response);
+ }
+
+ public interface WithdrawalListener {
+ void onWithdrawalComplete(Model.Withdraw.Response response);
+ }
+
+ public interface ErrorListener {
+ void onError(Throwable throwable);
+ }
+
+ public void setBalanceListener(BalanceListener listener) {
+ balanceListener = listener;
+ }
+
+ public void setWithdrawalListener(WithdrawalListener listener) {
+ withdrawalListener = listener;
+ }
+
+ public void setWithdrawalInfoListener(WithdrawalInfoListener listener) {
+ withdrawalInfoListener = listener;
+ }
+
+ public void setErrorListener(ErrorListener listener) {
+ errorListener = listener;
+ }
+
+
+ private static final String HMAC_SHA512 = "HmacSHA512";
+
+
+ private String bodyToString(final RequestBody request){
+ try {
+ final RequestBody copy = request;
+ final Buffer buffer = new Buffer();
+ if(copy != null)
+ copy.writeTo(buffer);
+ else
+ return "";
+ return buffer.readUtf8();
+ }
+ catch (final IOException e) {
+ return "did not work";
+ }
+ }
+
+ private String calcSignature(String url, RequestBody requestBody) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
+
+ String postData=bodyToString(requestBody);
+
+ // create SHA-256 hash of the nonce and the POST data
+
+ String s=nonce+postData;
+ byte[] sha256 = Util.calculateSHA256(s);
+
+ // set the API method and retrieve the path
+ byte[] path = url.getBytes(StandardCharsets.UTF_8);
+
+ // decode the API secret, it's the HMAC key
+ byte[] hmacKey = Base64.decode(secret);
+
+ // create the HMAC message from the path and the previous hash
+ ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
+ outputStream.write(path);
+ outputStream.write(sha256);
+ byte[] hmacMessage = outputStream.toByteArray();//concatArrays(path, sha256);
+
+ Mac mac = Mac.getInstance(HMAC_SHA512);
+ mac.init(new SecretKeySpec(hmacKey, HMAC_SHA512));
+ byte[] hmacSignature=mac.doFinal(hmacMessage);
+
+ byte[] b64Signature = Base64.encode(hmacSignature);
+
+ return new String(b64Signature, StandardCharsets.UTF_8);
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestBalance() throws Exception {
+
+ initApi();
+
+ Log.e("kraken", "key: " + key);
+ Log.e("kraken", "secret: " + secret);
+ Log.e("kraken", "nonce: " + nonce);
+
+ api.getBalance(nonce.toString())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> balanceListener.onBalanceData(response),
+ // handle error
+ this::FireError
+ );
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestWithdrawInfo(String currency, String amount, String withdrawKey) throws Exception {
+
+ initApi();
+
+// Log.e("kraken", "key: " + key);
+// Log.e("kraken", "secret: " + secret);
+// Log.e("kraken", "nonce: " + nonce);
+
+ String asset=CurrencyToAsset(currency);
+
+// withdrawKey = "test";
+ api.WithdrawInfo(nonce.toString(), asset, withdrawKey, amount)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> withdrawalInfoListener.onWithdrawalInfoComplete(response),
+ // handle error
+ this::FireError
+ );
+ }
+
+ private static String CurrencyToAsset(String currency) throws Exception {
+ switch (currency)
+ {
+ case "BTC": return "XXBT";
+ case "BCH": return "BCH";
+ case "ETH": return "XETH";
+ default:
+ throw new Exception("Unsupported currency!");
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ public void requestWithdraw(String currency, String amount, String withdrawKey) throws Exception {
+ initApi();
+
+// Log.e("kraken", "key: " + key);
+// Log.e("kraken", "secret: " + secret);
+// Log.e("kraken", "nonce: " + nonce);
+
+ String asset=CurrencyToAsset(currency);
+
+// withdrawKey = "test";
+ api.Withdraw(nonce.toString(), asset, withdrawKey, amount)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> withdrawalListener.onWithdrawalComplete(response),
+ // handle error
+ this::FireError
+ );
+ }
+
+ private void FireError(Throwable e) throws IOException {
+// if (e.getClass() == HttpException.class && ((HttpException) e).code() == 500) {
+// JsonObject jsonObject = new JsonParser().parse(((HttpException) e).response().errorBody().string()).getAsJsonObject();
+// errorListener.onError(new Exception(e.getMessage() + ": " + jsonObject.get("reason").getAsString()));
+// if (jsonObject.get("reason").getAsString().equals("Invalid nonce")) nonce += 1000;
+// } else {
+ errorListener.onError(e);
+// }
+ }
+
+ private void initApi() {
+
+ if (api != null) return;
+
+// HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
+// logging.setLevel(HttpLoggingInterceptor.Level.BODY);
+
+ OkHttpClient httpClient = new OkHttpClient.Builder().
+ addInterceptor(new AuthorizationInterceptor()).
+// addInterceptor(logging).
+ build();
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl(SERVER_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .client(httpClient)
+ .build();
+
+ api = retrofit.create(Api.class);
+ }
+
+ public class AuthorizationInterceptor implements Interceptor {
+
+ AuthorizationInterceptor() {
+ }
+
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request mainRequest=chain.request();
+
+ try {
+ mainRequest = mainRequest.newBuilder().
+ addHeader("API-Key", key).
+ addHeader("API-Sign", calcSignature(mainRequest.url().toString().substring(SERVER_URL.length()), mainRequest.body())).
+ build();
+ incNonce();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new IOException("Can't calculate signature: "+e.getMessage());
+ }
+
+ return chain.proceed(mainRequest);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherAPIWithdrawalActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherAPIWithdrawalActivity.kt
new file mode 100644
index 0000000000..fc4ea333ef
--- /dev/null
+++ b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherAPIWithdrawalActivity.kt
@@ -0,0 +1,238 @@
+package com.tangem.presentation.activity
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.text.InputFilter
+import android.view.View
+import com.tangem.data.network.Cryptonit_OtherAPI
+import com.tangem.domain.cardReader.NfcManager
+import com.tangem.domain.wallet.Blockchain
+import com.tangem.domain.wallet.CoinEngineFactory
+import com.tangem.domain.wallet.TangemCard
+import com.tangem.util.DecimalDigitsInputFilter
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.activity_prepare_cryptonit_other_api_withdrawal.*
+import java.io.IOException
+
+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 var useCurrencyX1000: Boolean = false
+ private var card: TangemCard? = null
+ private var nfcManager: NfcManager? = null
+ private var cryptonit: Cryptonit_OtherAPI? = null
+
+
+ @SuppressLint("SetTextI18n")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_prepare_cryptonit_other_api_withdrawal)
+
+ MainActivity.commonInit(applicationContext)
+
+ nfcManager = NfcManager(this, this)
+
+ card = TangemCard(intent.getStringExtra("UID"))
+ card!!.loadFromBundle(intent.extras!!.getBundle("Card"))
+
+ cryptonit = Cryptonit_OtherAPI(this)
+
+ tvKey.text = cryptonit!!.key
+ tvUserID.text = cryptonit!!.userId
+ tvSecret.text = cryptonit!!.secretDescription
+
+ tvCardID.text = card!!.cidDescription
+ tvWallet.text = card!!.wallet
+ val engine = CoinEngineFactory.create(card!!.blockchain)
+
+ when (card!!.blockchain) {
+ Blockchain.Ethereum, Blockchain.EthereumTestNet -> {
+ tvCurrency.text = engine.getBalanceCurrency(card)
+ useCurrencyX1000 = false
+
+ }
+ Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash, Blockchain.BitcoinCashTestNet -> {
+ tvCurrency.text = "m" + card!!.blockchain.currency
+ useCurrencyX1000 = true
+ }
+ else -> {
+ tvCurrency.text = engine.getBalanceCurrency(card)
+ useCurrencyX1000 = false
+ }
+ }
+
+ etAmount.setText(engine.convertByteArrayToAmount(card!!, card!!.denomination))
+ when (card!!.blockchain) {
+ Blockchain.Bitcoin ->
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(5))
+ Blockchain.BitcoinCash ->
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(8))
+ Blockchain.Ethereum ->
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(18))
+ else -> {
+ }
+ }
+
+ // 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()
+ if (useCurrencyX1000) dblAmount /= 1000.0
+
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal)
+
+ cryptonit!!.requestCryptoWithdrawal(card!!.blockchain.currency, dblAmount.toString(), card!!.wallet)
+ } catch (e: Exception) {
+ etAmount.error = getString(R.string.unknown_amount_format)
+ }
+
+ //Toast.makeText(this, strAmount, Toast.LENGTH_LONG).show()
+// val balance = engine.getBalanceLong(card)!! / (card!!.blockchain.multiplier / 1000.0)
+// if (etAmount.text.toString().replace(",", ".").toDouble() > balance) {
+// etAmount.error = getString(R.string.not_enough_funds_on_your_account)
+// return@setOnClickListener
+// }
+
+ }
+ 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)
+ }
+
+ ivRefreshBalance.setOnClickListener { doRequestBalance() }
+
+ cryptonit!!.setBalanceListener { response ->
+ when (card!!.blockchain) {
+ Blockchain.Ethereum, Blockchain.EthereumTestNet -> {
+ tvBalance.text = response.eth_available
+ }
+ Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash, Blockchain.BitcoinCashTestNet -> {
+ tvBalance.text = response.btc_available
+ }
+ else -> {
+ }
+ }
+
+ tvBalanceCurrency.text = card!!.blockchain.currency
+ tvBalance.setTextColor(Color.BLACK)
+ rlProgressBar.visibility = View.INVISIBLE
+ btnLoad.isActivated = true
+ }
+ cryptonit!!.setErrorListener { throwable ->
+ throwable.printStackTrace()
+ rlProgressBar.visibility = View.INVISIBLE
+ tvError.visibility = View.VISIBLE
+ tvError.text = throwable.message
+ }
+ cryptonit!!.setWithdrawalListener { response ->
+ rlProgressBar.visibility = View.INVISIBLE
+ if (response.success != null && response.success!!) finish()
+ else {
+ tvError.visibility = View.VISIBLE
+ tvError.text = response.reason!!.toString()
+ }
+ }
+ btnLoad.isActivated = false
+ doRequestBalance()
+ }
+
+ private fun doRequestBalance() {
+ if (cryptonit!!.havaAccountInfo()) {
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.cryptonit_request_balance)
+ tvError.visibility = View.INVISIBLE
+ cryptonit!!.requestBalance(card!!.blockchain.currency, "USD")
+ } else {
+ tvError.visibility = View.VISIBLE
+ tvError.text = getString(R.string.cryptonit_not_enough_account_data)
+ }
+ }
+
+// private fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
+// this.addTextChangedListener(object : TextWatcher {
+// override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+// }
+//
+// override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
+// }
+//
+// override fun afterTextChanged(editable: Editable?) {
+// afterTextChanged.invoke(editable.toString())
+// }
+// })
+// }
+
+ public override fun onResume() {
+ super.onResume()
+ nfcManager!!.onResume()
+ }
+
+ public override fun onPause() {
+ super.onPause()
+ nfcManager!!.onPause()
+ }
+
+ public override fun onStop() {
+ super.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 -> {
+ cryptonit!!.key = data.getStringExtra("QRCode")
+ tvKey!!.text = cryptonit!!.key
+ }
+ REQUEST_CODE_SCAN_QR_SECRET -> {
+ cryptonit!!.secret = data.getStringExtra("QRCode")
+ tvSecret!!.text = cryptonit!!.secretDescription
+ }
+ REQUEST_CODE_SCAN_QR_USER_ID -> {
+ cryptonit!!.userId = data.getStringExtra("QRCode")
+ tvUserID!!.text = cryptonit!!.userId
+ }
+ }
+ cryptonit!!.saveAccountInfo()
+ doRequestBalance()
+ }
+ }
+
+ override fun onTagDiscovered(tag: Tag) {
+ try {
+// Log.w(javaClass.name, "Ignore discovered tag!")
+ nfcManager!!.ignoreTag(tag)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt
new file mode 100644
index 0000000000..155fe7b2bb
--- /dev/null
+++ b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt
@@ -0,0 +1,185 @@
+package com.tangem.presentation.activity
+
+import android.annotation.SuppressLint
+import android.graphics.Color
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.text.InputFilter
+import android.view.View
+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.domain.wallet.CoinEngineFactory
+import com.tangem.domain.wallet.TangemCard
+import com.tangem.util.DecimalDigitsInputFilter
+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 var useCurrencyX1000: Boolean = false
+ private var card: TangemCard? = null
+ private var nfcManager: NfcManager? = 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)
+
+ card = TangemCard(intent.getStringExtra("UID"))
+ card!!.loadFromBundle(intent.extras!!.getBundle("Card"))
+
+ cryptonit = Cryptonit(this)
+
+ etUsername.setText(cryptonit!!.username)
+ etPassword.setText(cryptonit!!.password)
+ etFee.setText(cryptonit!!.fee)
+
+ tvCardID.text = card!!.cidDescription
+ tvWallet.text = card!!.wallet
+ val engine = CoinEngineFactory.create(card!!.blockchain)
+
+ when (card!!.blockchain) {
+ Blockchain.Ethereum -> {
+ tvCurrency.text = engine.getBalanceCurrency(card)
+ useCurrencyX1000 = false
+
+ }
+ Blockchain.Bitcoin, Blockchain.BitcoinCash -> {
+ tvCurrency.text = "m" + card!!.blockchain.currency
+ useCurrencyX1000 = true
+ }
+ else -> {
+ tvCurrency.text = engine.getBalanceCurrency(card)
+ useCurrencyX1000 = false
+ }
+ }
+ tvFeeCurrency.text = tvCurrency.text
+
+ etAmount.setText(engine.convertByteArrayToAmount(card!!, card!!.denomination))
+ when (card!!.blockchain) {
+ Blockchain.Bitcoin -> {
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(5))
+ etFee.filters = arrayOf(DecimalDigitsInputFilter(5))
+ }
+ Blockchain.BitcoinCash -> {
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(8))
+ etFee.filters = arrayOf(DecimalDigitsInputFilter(8))
+ }
+ Blockchain.Ethereum -> {
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(18))
+ etFee.filters = arrayOf(DecimalDigitsInputFilter(18))
+ }
+ else -> {
+ }
+ }
+
+ // set listeners
+ btnLoad.setOnClickListener {
+
+ try {
+ val strAmount: String = etAmount.text.toString().replace(",", ".")
+ val strFee: String = etFee.text.toString().replace(",", ".")
+ var dblAmount: Double = strAmount.toDouble()
+ var dblFee: Double = strFee.toDouble()
+ if (useCurrencyX1000){
+ dblAmount /= 1000.0
+ dblFee /= 1000.0
+ }
+ cryptonit!!.fee=strFee
+
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal)
+
+ cryptonit!!.requestWithdrawCoins(card!!.blockchain.currency, dblAmount, card!!.wallet)
+ } catch (e: Exception) {
+ etAmount.error = getString(R.string.unknown_amount_format)
+ }
+ }
+
+ ivRefreshBalance.setOnClickListener {
+ cryptonit!!.username=etUsername.text.toString()
+ cryptonit!!.password=etPassword.text.toString()
+ cryptonit!!.saveAccountInfo()
+ doRequestBalance()
+ }
+
+ cryptonit!!.setBalanceListener { response ->
+ tvBalance.text = response.results[0].balance.toString()
+ tvBalanceCurrency.text = response.results[0].currency // card!!.blockchain.currency
+ tvBalance.setTextColor(Color.BLACK)
+ rlProgressBar.visibility = View.INVISIBLE
+ btnLoad.visibility = View.VISIBLE
+ }
+ cryptonit!!.setErrorListener { throwable ->
+ throwable.printStackTrace()
+ rlProgressBar.visibility = View.INVISIBLE
+ tvError.visibility = View.VISIBLE
+ tvError.text = throwable.message
+ }
+ cryptonit!!.setWithdrawalListener { response ->
+ rlProgressBar.visibility = View.INVISIBLE
+ if (response.success != null && response.success!!){
+ Toast.makeText(this,"Withdrawal successful!", Toast.LENGTH_LONG).show();
+ finish()
+ }
+ else {
+ tvError.visibility = View.VISIBLE
+ tvError.text = response.errors.toString()
+ }
+ }
+ btnLoad.visibility = View.INVISIBLE
+ doRequestBalance()
+ }
+
+ private fun doRequestBalance() {
+ if (cryptonit!!.haveAccountInfo()) {
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.cryptonit_request_balance)
+ tvError.visibility = View.INVISIBLE
+ cryptonit!!.requestBalance(card!!.blockchain.currency)
+ } else {
+ tvError.visibility = View.VISIBLE
+ tvError.text = getString(R.string.cryptonit_not_enough_account_data)
+ }
+ }
+
+ public override fun onResume() {
+ super.onResume()
+ nfcManager!!.onResume()
+ }
+
+ public override fun onPause() {
+ super.onPause()
+ nfcManager!!.onPause()
+ }
+
+ public override fun onStop() {
+ super.onStop()
+ nfcManager!!.onStop()
+ }
+
+ override fun onTagDiscovered(tag: Tag) {
+ try {
+ nfcManager!!.ignoreTag(tag)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/presentation/activity/PrepareKrakenWithdrawalActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PrepareKrakenWithdrawalActivity.kt
new file mode 100644
index 0000000000..0c625e39b3
--- /dev/null
+++ b/app/src/main/java/com/tangem/presentation/activity/PrepareKrakenWithdrawalActivity.kt
@@ -0,0 +1,286 @@
+package com.tangem.presentation.activity
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.content.Intent
+import android.graphics.Color
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.text.InputFilter
+import android.view.View
+import android.widget.Toast
+import com.tangem.data.network.Kraken
+import com.tangem.domain.cardReader.NfcManager
+import com.tangem.domain.wallet.Blockchain
+import com.tangem.domain.wallet.CoinEngineFactory
+import com.tangem.domain.wallet.TangemCard
+import com.tangem.util.DecimalDigitsInputFilter
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.activity_prepare_kraken_withdrawal.*
+import java.io.IOException
+import java.math.BigDecimal
+import java.net.URI
+import java.util.*
+
+class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
+
+ companion object {
+ val TAG: String = PrepareKrakenWithdrawalActivity::class.java.simpleName
+
+ private const val REQUEST_CODE_SCAN_QR = 1
+ }
+
+ private var useCurrencyX1000: Boolean = false
+ private var card: TangemCard? = null
+ private var nfcManager: NfcManager? = null
+ private var kraken: Kraken? = null
+ private var fee: BigDecimal? = null
+
+ @SuppressLint("SetTextI18n")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_prepare_kraken_withdrawal)
+
+ MainActivity.commonInit(applicationContext)
+
+ nfcManager = NfcManager(this, this)
+
+ card = TangemCard(intent.getStringExtra("UID"))
+ card!!.loadFromBundle(intent.extras!!.getBundle("Card"))
+
+ kraken = Kraken(this)
+
+ tvKey.text = kraken!!.key
+ tvSecret.text = kraken!!.secretDescription
+
+ tvCardID.text = card!!.cidDescription
+ tvWallet.text = card!!.wallet
+ val engine = CoinEngineFactory.create(card!!.blockchain)
+
+ when (card!!.blockchain) {
+ Blockchain.Ethereum, Blockchain.EthereumTestNet -> {
+ tvCurrency.text = engine.getBalanceCurrency(card)
+ useCurrencyX1000 = false
+
+ }
+ Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash, Blockchain.BitcoinCashTestNet -> {
+ tvCurrency.text = "m" + card!!.blockchain.currency
+ useCurrencyX1000 = true
+ }
+ else -> {
+ tvCurrency.text = engine.getBalanceCurrency(card)
+ useCurrencyX1000 = false
+ }
+ }
+
+ etAmount.setText(engine.convertByteArrayToAmount(card!!, card!!.denomination))
+ when (card!!.blockchain) {
+ Blockchain.Bitcoin ->
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(5))
+ Blockchain.BitcoinCash ->
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(8))
+ Blockchain.Ethereum ->
+ etAmount.filters = arrayOf(DecimalDigitsInputFilter(18))
+ else -> {
+ }
+ }
+
+ // set listeners
+ btnLoad.setOnClickListener {
+
+ try {
+ val strAmount: String = etAmount.text.toString().replace(",", ".")
+
+ var dblAmount: Double = strAmount.toDouble()
+ if (useCurrencyX1000) dblAmount /= 1000.0
+
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.kraken_request_withdrawal)
+
+ kraken!!.requestWithdrawInfo(card!!.blockchain.currency, dblAmount.toString(), card!!.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)
+ }
+
+ ivRefreshBalance.setOnClickListener { doRequestBalance() }
+
+ kraken!!.setBalanceListener { response ->
+ if (response.error != null && response.error.isNotEmpty()) {
+ tvError.visibility = View.VISIBLE
+ tvError.text = Arrays.toString(response.error)
+ } else {
+ when (card!!.blockchain) {
+ Blockchain.Ethereum -> {
+ tvBalance.text = response.result.XETH.trimEnd('0')
+ }
+ Blockchain.Bitcoin -> {
+ tvBalance.text = response.result.XXBT.trimEnd('0')
+ }
+ Blockchain.BitcoinCash -> {
+ tvBalance.text = response.result.BCH.trimEnd('0')
+ }
+ else -> {
+ tvBalance.text = "???"
+ }
+ }
+ tvBalanceCurrency.text = card!!.blockchain.currency
+ tvBalance.setTextColor(Color.BLACK)
+ btnLoad.visibility = View.VISIBLE
+ }
+ rlProgressBar.visibility = View.INVISIBLE
+ }
+ kraken!!.setErrorListener { throwable ->
+ throwable.printStackTrace()
+ rlProgressBar.visibility = View.INVISIBLE
+ tvError.visibility = View.VISIBLE
+ tvError.text = throwable.message
+ }
+ kraken!!.setWithdrawalListener { response ->
+ rlProgressBar.visibility = View.INVISIBLE
+ if (response.error != null && response.error.isNotEmpty()) {
+ tvError.visibility = View.VISIBLE
+ tvError.text = Arrays.toString(response.error)
+ } else {
+ Toast.makeText(this,"Withdrawal successful!", Toast.LENGTH_LONG).show()
+ finish()
+ }
+ }
+ kraken!!.setWithdrawalInfoListener { response ->
+ rlProgressBar.visibility = View.INVISIBLE
+ if (response.error != null && response.error.isNotEmpty()) {
+ tvError.visibility = View.VISIBLE
+ tvError.text = Arrays.toString(response.error)
+ } else {
+ fee = BigDecimal(response.result.fee)
+ showConfirmDialog()
+ }
+ }
+ btnLoad.visibility = View.INVISIBLE
+ doRequestBalance()
+ }
+
+ // Method to show an alert dialog with yes, no and cancel button
+ private fun showConfirmDialog() {
+ // Late initialize an alert dialog object
+ lateinit var dialog: AlertDialog
+
+
+ // Initialize a new instance of alert dialog builder object
+ val builder = AlertDialog.Builder(this)
+
+ // Set a title for alert dialog
+ builder.setTitle("Please confirm withdraw")
+
+ // Set a message for alert dialog
+ builder.setMessage(String.format("Continue with fee %s %s?", fee!!.toString().trimEnd('0'),card!!.blockchain.currency))
+
+ // On click listener for dialog buttons
+ val dialogClickListener = DialogInterface.OnClickListener { _, which ->
+ when (which) {
+ DialogInterface.BUTTON_POSITIVE -> {
+ try {
+ val strAmount: String = etAmount.text.toString().replace(",", ".")
+
+ var dblAmount: Double = strAmount.toDouble()
+ if (useCurrencyX1000) dblAmount /= 1000.0
+
+ dblAmount+=fee!!.toDouble()
+
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.kraken_request_withdrawal)
+
+ //Toast.makeText(this, String.format("Withdraw %s!",dblAmount.toString()), Toast.LENGTH_LONG).show()
+ kraken!!.requestWithdraw(card!!.blockchain.currency, dblAmount.toString(), card!!.wallet)
+ } catch (e: Exception) {
+ etAmount.error = getString(R.string.unknown_amount_format)
+ }
+ }
+ DialogInterface.BUTTON_NEGATIVE -> {
+ Toast.makeText(this, "Operation canceled!", Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+
+ // Set the alert dialog positive/yes button
+ builder.setPositiveButton("YES", dialogClickListener)
+
+ // Set the alert dialog negative/no button
+ builder.setNegativeButton("NO", dialogClickListener)
+
+
+ // Initialize the AlertDialog using builder object
+ dialog = builder.create()
+
+ // Finally, display the alert dialog
+ dialog.show()
+ }
+
+ private fun doRequestBalance() {
+ if (kraken!!.haveAccountInfo()) {
+ rlProgressBar.visibility = View.VISIBLE
+ tvProgressDescription.text = getString(R.string.kraken_request_balance)
+ tvError.visibility = View.INVISIBLE
+ kraken!!.requestBalance()
+ } else {
+ tvError.visibility = View.VISIBLE
+ tvError.text = getString(R.string.kraken_not_enough_account_data)
+ }
+ }
+
+ public override fun onResume() {
+ super.onResume()
+ nfcManager!!.onResume()
+ }
+
+ public override fun onPause() {
+ super.onPause()
+ nfcManager!!.onPause()
+ }
+
+ public override fun onStop() {
+ super.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 -> {
+ val uri = URI(data.getStringExtra("QRCode"))
+ var query = uri.query
+ var params = query.split("&")
+ for (param in params) {
+ if (param.startsWith("key=")) kraken!!.key = param.substring(4)
+ else if (param.startsWith("secret=")) kraken!!.secret = param.substring(7)
+ }
+ tvKey!!.text = kraken!!.key
+ tvSecret!!.text = kraken!!.secretDescription
+ }
+ }
+ kraken!!.saveAccountInfo()
+ doRequestBalance()
+ }
+ }
+
+ override fun onTagDiscovered(tag: Tag) {
+ try {
+// Log.w(javaClass.name, "Ignore discovered tag!")
+ nfcManager!!.ignoreTag(tag)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt
index 6064e59bc4..e78e9dfe37 100644
--- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt
+++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt
@@ -1,6 +1,7 @@
package com.tangem.presentation.fragment
import android.app.Activity
+import android.app.AlertDialog
import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.pm.PackageManager
@@ -36,6 +37,7 @@ import com.tangem.presentation.dialog.PINSwapWarningDialog
import com.tangem.presentation.dialog.WaitSecurityDelayDialog
import com.tangem.util.Util
import com.tangem.util.UtilHelper
+import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fr_loaded_wallet.*
import java.util.*
@@ -51,6 +53,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
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 const val REQUEST_CODE_RECEIVE_PAYMENT = 9
}
private var singleToast: Toast? = null
@@ -133,12 +136,53 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
tvWallet.setOnClickListener { doShareWallet(false) }
ivQR.setOnClickListener { doShareWallet(true) }
btnLoad.setOnClickListener {
- try {
- val intent = Intent(Intent.ACTION_VIEW, CoinEngineFactory.create(card!!.blockchain)!!.getShareWalletUri(card))
- intent.addCategory(Intent.CATEGORY_DEFAULT)
- startActivity(intent)
- } catch (e: ActivityNotFoundException) {
- showSingleToast(R.string.no_compatible_wallet)
+ if (BuildConfig.DEBUG) {
+ val items = arrayOf(getString(R.string.in_app), getString(R.string.via_cryptonit), getString(R.string.via_kraken))
+ val dialog = AlertDialog.Builder(activity).setTitle(getString(R.string.select_loading_method)).setItems(items
+ ) { _, which ->
+ when (items[which]) {
+ getString(R.string.in_app) -> {
+ try {
+ val intent = Intent(Intent.ACTION_VIEW, CoinEngineFactory.create(card!!.blockchain)!!.getShareWalletUri(card))
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ showSingleToast(R.string.no_compatible_wallet)
+ }
+
+ }
+// getString(R.string.via_cryptonit2) -> {
+// val intent = Intent(context, PrepareCryptonitOtherAPIWithdrawalActivity::class.java)
+// intent.putExtra("UID", card!!.uid)
+// intent.putExtra("Card", card!!.asBundle)
+// startActivityForResult(intent, REQUEST_CODE_RECEIVE_PAYMENT)
+// }
+ getString(R.string.via_cryptonit) -> {
+ val intent = Intent(context, PrepareCryptonitWithdrawalActivity::class.java)
+ intent.putExtra("UID", card!!.uid)
+ intent.putExtra("Card", card!!.asBundle)
+ startActivityForResult(intent, REQUEST_CODE_RECEIVE_PAYMENT)
+ }
+ getString(R.string.via_kraken) -> {
+ val intent = Intent(context, PrepareKrakenWithdrawalActivity::class.java)
+ intent.putExtra("UID", card!!.uid)
+ intent.putExtra("Card", card!!.asBundle)
+ startActivityForResult(intent, REQUEST_CODE_RECEIVE_PAYMENT)
+ }
+ else -> {
+ }
+ }
+
+ }
+ dialog.show()
+ } else {
+ try {
+ val intent = Intent(Intent.ACTION_VIEW, CoinEngineFactory.create(card!!.blockchain)!!.getShareWalletUri(card))
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ showSingleToast(R.string.no_compatible_wallet)
+ }
}
}
btnDetails.setOnClickListener {
@@ -351,7 +395,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific
}
updateViews()
}
- REQUEST_CODE_SEND_PAYMENT -> {
+ REQUEST_CODE_SEND_PAYMENT, REQUEST_CODE_RECEIVE_PAYMENT -> {
if (resultCode == Activity.RESULT_OK) {
srlLoadedWallet!!.postDelayed({ this.refresh() }, 10000)
srlLoadedWallet!!.isRefreshing = true
diff --git a/app/src/main/res/drawable-hdpi/ic_refresh.png b/app/src/main/res/drawable-hdpi/ic_refresh.png
new file mode 100644
index 0000000000..b787fdc3de
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_refresh.png b/app/src/main/res/drawable-mdpi/ic_refresh.png
new file mode 100644
index 0000000000..4174691cf3
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh.png b/app/src/main/res/drawable-xhdpi/ic_refresh.png
new file mode 100644
index 0000000000..b41425d9b8
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_refresh.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh.png b/app/src/main/res/drawable-xxhdpi/ic_refresh.png
new file mode 100644
index 0000000000..3a40ec9fb6
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_refresh.png differ
diff --git a/app/src/main/res/layout/activity_prepare_cryptonit_other_api_withdrawal.xml b/app/src/main/res/layout/activity_prepare_cryptonit_other_api_withdrawal.xml
new file mode 100644
index 0000000000..cac33ad2c1
--- /dev/null
+++ b/app/src/main/res/layout/activity_prepare_cryptonit_other_api_withdrawal.xml
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_prepare_cryptonit_withdrawal.xml b/app/src/main/res/layout/activity_prepare_cryptonit_withdrawal.xml
new file mode 100644
index 0000000000..305febc544
--- /dev/null
+++ b/app/src/main/res/layout/activity_prepare_cryptonit_withdrawal.xml
@@ -0,0 +1,418 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_prepare_kraken_withdrawal.xml b/app/src/main/res/layout/activity_prepare_kraken_withdrawal.xml
new file mode 100644
index 0000000000..0b9b081621
--- /dev/null
+++ b/app/src/main/res/layout/activity_prepare_kraken_withdrawal.xml
@@ -0,0 +1,388 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 22bb06168f..ace5d5454b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,6 +36,7 @@
Cannot erase wallet with non-zero balance
Send payment
From banknote
+ on banknote
with balance
Send to wallet
Amount
@@ -60,6 +61,11 @@
Please hold the banknote firmly\n until the operation is completed…
You may be required to repeat this operation a few times depending on the NFC performance of your smartphone.\n This is made for safety of your funds.
+ Select loading method
+ via CRYPTONIT
+ via CRYPTONIT2
+ via KRAKEN
+ In-App
Clean up
Manage user PIN1…
@@ -217,4 +223,26 @@
User hasn\'t granted permission to use Fingerprint
User hasn\'t registered any fingerprints
+
+ CRYPTONIT payment
+ From CRYPTONIT account:
+ user ID:
+ key:
+ secret:
+ nonce:
+ username:
+ password:
+ Please enter account data
+ Get balance
+ Withdrawal
+ operation fee
+
+
+ KRAKEN payment
+ From KRAKEN account:
+ key:
+ secret:
+ Please enter account data
+ Get balance
+ Withdrawal
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_key.xml b/app/src/main/res/values/strings_key.xml
new file mode 100644
index 0000000000..60e0badc83
--- /dev/null
+++ b/app/src/main/res/values/strings_key.xml
@@ -0,0 +1,17 @@
+
+
+ cryptonit_user_id
+ cryptonit_key
+ cryptonit_secret
+ cryptonit_nonce
+
+
+ cryptonit_username
+ cryptonit_password
+ cryptonit_fee
+
+
+ kraken_key
+ kraken_secret
+ kraken_nonce
+
\ No newline at end of file