From b1134eb19e5710483107abfe3faddb5f15adda65 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 5 Sep 2018 01:06:51 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 16 + .../com/tangem/data/network/Cryptonit.java | 327 +++++++++++++ .../data/network/Cryptonit_OtherAPI.java | 283 ++++++++++++ .../java/com/tangem/data/network/Kraken.java | 358 +++++++++++++++ ...pareCryptonitOtherAPIWithdrawalActivity.kt | 238 ++++++++++ .../PrepareCryptonitWithdrawalActivity.kt | 185 ++++++++ .../PrepareKrakenWithdrawalActivity.kt | 286 ++++++++++++ .../presentation/fragment/LoadedWallet.kt | 58 ++- app/src/main/res/drawable-hdpi/ic_refresh.png | Bin 0 -> 1090 bytes app/src/main/res/drawable-mdpi/ic_refresh.png | Bin 0 -> 740 bytes .../main/res/drawable-xhdpi/ic_refresh.png | Bin 0 -> 1419 bytes .../main/res/drawable-xxhdpi/ic_refresh.png | Bin 0 -> 2230 bytes ...prepare_cryptonit_other_api_withdrawal.xml | 434 ++++++++++++++++++ .../activity_prepare_cryptonit_withdrawal.xml | 418 +++++++++++++++++ .../activity_prepare_kraken_withdrawal.xml | 388 ++++++++++++++++ app/src/main/res/values/strings.xml | 28 ++ app/src/main/res/values/strings_key.xml | 17 + 17 files changed, 3029 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/Cryptonit.java create mode 100644 app/src/main/java/com/tangem/data/network/Cryptonit_OtherAPI.java create mode 100644 app/src/main/java/com/tangem/data/network/Kraken.java create mode 100644 app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherAPIWithdrawalActivity.kt create mode 100644 app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt create mode 100644 app/src/main/java/com/tangem/presentation/activity/PrepareKrakenWithdrawalActivity.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_refresh.png create mode 100644 app/src/main/res/drawable-mdpi/ic_refresh.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_refresh.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_refresh.png create mode 100644 app/src/main/res/layout/activity_prepare_cryptonit_other_api_withdrawal.xml create mode 100644 app/src/main/res/layout/activity_prepare_cryptonit_withdrawal.xml create mode 100644 app/src/main/res/layout/activity_prepare_kraken_withdrawal.xml create mode 100644 app/src/main/res/values/strings_key.xml 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 0000000000000000000000000000000000000000..b787fdc3de284282dd7c431b24a2bbb6fe477492 GIT binary patch literal 1090 zcmV-I1ikx-P)q`_-6yF}0*^{6Ly(xR(g6+=iYO4fRDAfmpL;@iQdO=Y^lpmrO5f&r`1%4`zw34dwK7azmcYbCKM0*#>8t_0qw~iSPk_{VRQ(~y=KJKJnG5c7tKpIv1zxU> zEcMFdES@MA=)|kXt*c+QPhIulGmiiHd$nQ1ox< zz~SH1-XoJIqC|`G3ce~7nh)Tc(g2r`oJZ2iO54U)VL~Un4^-%xGc>$7zhQGLI#*Qn z>nT*F1LUz9Lo2h4>Mx2Rck-|2vR)nvZzYCgehZqvl6Ol%2_F|!dM8I(A1=XA zF)9dcMm6Fj&|cmfSnj5r~Y-C6fxaIEiXxt7Qa&S}|MvOxf=VpJOm!ZCDSi zv?!SHhC`F+DKicEePabR8IxRlGR0C2-!ns?)j&WdC#)s_l6{O3jpt?vv@w> zq1G~z{l-K)NIQ&4Ob=N91hhJafZt3%`_h&R`5YW%$EQAEcLI8nAs|kSe-CzjZ zH9>%!V`Q||QpXMw4SCiTzo%EGr?Y>YDyd{0Xt}Ddw~9cSqHkvl88$~*{~%gg97Zuu z;k;$>uaZVaG(J{^Mhi{K+O(O&(?ONsj%Vff8t!|$$p#47FGd37run4m`sIvuUa?23 zHE$w5z&GLWEU)0PRzUf0Nezk) z@{A!doOlNXIUF^P7DDlPWM;q%p%hFt)w6tgdm~%29j#-Ph0hB!GO06GTirDYhU2tp zNKnaRL8eDU|IcNP*|OZqm?vTUG94l{UO7_XY7cKV?Q5)Df~xPv0+dlkXY<0B*{mUR z*lppPNqjSJTLKe}IjN5QkAz(b#7IctLs+03c3$}I*y=t00T;>|(#coa2mk;807*qo IM6N<$g0-vw`v3p{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4174691cf39c058be692528bc64e044251f35649 GIT binary patch literal 740 zcmVD7y? ziFk;)2819%x4UQk6>HV(>`sr#j6L?mY3S4R)K^tq-&fVSoW?SVd&;bS-YzfZc#qkb->F6AI-mS zZcH8YWWB!Hs_}k<@;8>lzsjJCHsMb%(D`-gMNEI~2g9#oK|@>I&6Qg#(*buHB``WA z13)Hf1{H$_5myYy9SMCS>|Sc%7WsJ;(m5>y7YOe!O8}uFB>aAQk=tSe-1THoT&z-{ z?(QfSz}a#+1{`ZBS@e|Olu|n$rFQ5*k+9)`YHL-bEno|tQwBdc@oh6i^vRD5D!$PZ zb+$y^!*Ss7?^VArKVVjs96*i-sTYs5>InYt+9KkdqPnr+K;8IREv2ln(;ZtyEK)V< zwKKc0rkE9Y(AM?qpP3+l?72$KK{;kU`m(Q1J${;z^E_2 zJzMn(Ui;ji=^bC#rsF_Cv+SD7M+(m<%55qp*x~QL&_d^Ghn+H z6AKXvNz^%%Ua0azks>wG$ah*dWa49@VFjfaB{U8kQyL|)DBYff2O4lA8GmEw=S5E0 zP}}fg2e4@Y<(>z{2Te^?hX1+%9PV{6h)i<8%L?n0bOc-UG`98Bz#+0be@s9Raa04K z+aX|IM{uA?2fWo+0bD)g$98_R9an literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b41425d9b803d6861d6404f38ed4ec6681ed287d GIT binary patch literal 1419 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 literal 0 HcmV?d00001 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