From 8b5ed4f3e9f3abd4633239417853ca65505fea25 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 26 Dec 2018 15:22:56 +0300 Subject: [PATCH 001/228] Updated on 2026-08-14 --- .../presentation/activity/MainActivity.kt | 4 + .../dialog/WaitSecurityDelayDialogNew.kt | 117 +++++++++--------- 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt index dd8bc8b525..28c4231e80 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt @@ -38,6 +38,7 @@ import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog import com.tangem.presentation.dialog.RootFoundDialog import com.tangem.presentation.dialog.WaitSecurityDelayDialog +import com.tangem.presentation.dialog.WaitSecurityDelayDialogNew import com.tangem.tangemcard.android.reader.NfcReader import com.tangem.tangemcard.data.TangemCard import com.tangem.tangemcard.data.loadFromBundle @@ -84,6 +85,9 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) +// val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() +// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) + App.getNavigatorComponent().inject(this) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT diff --git a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt index 80173180d6..c400be56a8 100644 --- a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt +++ b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt @@ -1,10 +1,11 @@ package com.tangem.presentation.dialog -import android.app.Activity +import android.annotation.SuppressLint import android.app.AlertDialog import android.app.Dialog import android.os.Bundle import android.support.v7.app.AppCompatDialogFragment +import android.widget.ProgressBar import com.tangem.wallet.R import kotlinx.android.synthetic.main.dialog_wait_pin2.* import java.util.* @@ -18,27 +19,33 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { private const val DELAY_BEFORE_SHOW_DIALOG = 5000 } + private lateinit var pb: ProgressBar + private var msTimeout = 60000 private var msProgress = 0 private var timer: Timer? = null private var timerToShowDelayDialog: Timer? = null - private var instance: WaitSecurityDelayDialogNew? = null + @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val inflater = activity!!.layoutInflater val v = inflater.inflate(R.layout.dialog_wait_pin2, null) - progressBar.max = msTimeout - progressBar.progress = msProgress + isCancelable = false + + pb = v.findViewById(R.id.progressBar) + + + pb.max = msTimeout + pb.progress = msProgress timer = Timer() timer!!.scheduleAtFixedRate(object : TimerTask() { override fun run() { - progressBar.post { - val progress = this@WaitSecurityDelayDialogNew.progressBar.progress - if (progress < this@WaitSecurityDelayDialogNew.progressBar.max) { - this@WaitSecurityDelayDialogNew.progressBar.progress = progress + 1000 - } + pb.post { + val progress = pb.progress + if (progress < pb.max) + pb.progress = progress + 1000 } } }, 1000, 1000) @@ -51,59 +58,48 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { .create() } - fun onReadBeforeRequest(activity: Activity, timeout: Int) { - activity.runOnUiThread(Runnable { - if (timerToShowDelayDialog != null || timeout < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) - return@Runnable + fun onReadBeforeRequest(timeout: Int) { + if (timerToShowDelayDialog != null || timeout < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) + return + + timerToShowDelayDialog = Timer() + timerToShowDelayDialog!!.schedule(object : TimerTask() { + override fun run() { + setup(timeout, DELAY_BEFORE_SHOW_DIALOG) + isCancelable = false + show(activity?.supportFragmentManager, TAG) + } + }, DELAY_BEFORE_SHOW_DIALOG.toLong()) - timerToShowDelayDialog = Timer() - timerToShowDelayDialog!!.schedule(object : TimerTask() { - override fun run() { - if (instance != null) return - instance = WaitSecurityDelayDialogNew() - instance!!.setup(timeout, DELAY_BEFORE_SHOW_DIALOG) - instance!!.isCancelable = false -// instance.show(activity.fragmentManager, TAG) - } - }, DELAY_BEFORE_SHOW_DIALOG.toLong()) - }) } - fun onReadAfterRequest(activity: Activity) { - activity.runOnUiThread(Runnable { - if (timerToShowDelayDialog == null) return@Runnable + fun onReadAfterRequest() { + if (timerToShowDelayDialog == null) + return + + timerToShowDelayDialog!!.cancel() + timerToShowDelayDialog = null + } + + fun onReadWait(msec: Int) { + if (timerToShowDelayDialog != null) { timerToShowDelayDialog!!.cancel() timerToShowDelayDialog = null - }) - } + } - fun onReadWait(activity: Activity, msec: Int) { - activity.runOnUiThread(Runnable { - if (timerToShowDelayDialog != null) { - timerToShowDelayDialog!!.cancel() - timerToShowDelayDialog = null - } + if (msec == 0) { + dismiss() + return + } - if (msec == 0) { - if (instance != null) { - instance!!.dismiss() - instance = null - } - return@Runnable - } + if (msec > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { + // 1000ms - card delay notification interval + setup(msec + 1000, 1000) + isCancelable = false + show(activity?.supportFragmentManager, TAG) - if (instance == null) { - if (msec > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { - instance = WaitSecurityDelayDialogNew() - // 1000ms - card delay notification interval - instance!!.setup(msec + 1000, 1000) - instance!!.isCancelable = false -// instance.show(activity.fragmentManager, TAG) - } - } else { - instance!!.setRemainingTimeout(msec) - } - }) + } else + setRemainingTimeout(msec) } private fun setup(msTimeout: Int, msProgress: Int) { @@ -112,19 +108,20 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { } private fun setRemainingTimeout(msec: Int) { - progressBar.post { - val progress = this@WaitSecurityDelayDialogNew.progressBar.progress + pb.post { + val progress = pb.progress if (timer != null) { + // we get delay latency from card for first time - don't change progress by timer, only by card answer - progressBar.max = progress + msec + pb.max = progress + msec timer!!.cancel() timer = null } else { - val newProgress = progressBar.max - msec - if (newProgress > progress) + val newProgress = pb.max - msec + if (pb.max > progress) progressBar.progress = newProgress else - progressBar.max = progress + msec + pb.max = progress + msec } } } From f6186a521da767e6f60560b4b31526d50f2a3318 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 26 Dec 2018 18:08:15 +0300 Subject: [PATCH 002/228] Updated on 2026-08-14 --- .../com/tangem/data/network/InsightApi.java | 30 ++++++ .../java/com/tangem/data/network/Server.java | 1 - .../tangem/data/network/ServerApiInsight.java | 102 ++++++++++++++++++ .../data/network/model/InsightBody.java | 9 ++ .../data/network/model/InsightResponse.kt | 17 +++ .../java/com/tangem/di/NetworkComponent.java | 3 + .../java/com/tangem/di/NetworkModule.java | 10 ++ .../src/main/res/drawable/card_ru031.png | Bin 0 -> 87728 bytes .../src/main/res/drawable/card_ru032.png | Bin 0 -> 59393 bytes .../tangemserver/android/data/LocalStorage.kt | 4 + 10 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/tangem/data/network/InsightApi.java create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiInsight.java create mode 100644 app/src/main/java/com/tangem/data/network/model/InsightBody.java create mode 100644 app/src/main/java/com/tangem/data/network/model/InsightResponse.kt create mode 100644 tangemcard-android/src/main/res/drawable/card_ru031.png create mode 100644 tangemcard-android/src/main/res/drawable/card_ru032.png diff --git a/app/src/main/java/com/tangem/data/network/InsightApi.java b/app/src/main/java/com/tangem/data/network/InsightApi.java new file mode 100644 index 0000000000..458199262c --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/InsightApi.java @@ -0,0 +1,30 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.InsightBody; +import com.tangem.data.network.model.InsightResponse; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface InsightApi { + @GET(ServerApiInsight.INSIGHT_ADDRESS) + Call insightAddress(@Path("address") String address); + + @GET(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS) + Call insightUnspent(@Path("address") String address); + + @GET(ServerApiInsight.INSIGHT_TRANSACTION) + Call insightTransaction(@Path("transaction") String transaction); + + @GET(ServerApiInsight.INSIGHT_FEE) + Call insightFee(@Query("nbBlocks") int nbBlocks); + + @Headers("Content-Type: application/json") + @POST(ServerApiInsight.INSIGHT_SEND) + Call insightSend(@Body InsightBody body ); +} diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 7e8bc996aa..b6f74767ef 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -44,5 +44,4 @@ public class Server { static final String N_6 = URL_ESTIMATEFEE + "n/6"; } } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java new file mode 100644 index 0000000000..e43efdb6d9 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java @@ -0,0 +1,102 @@ +package com.tangem.data.network; + +import android.support.annotation.NonNull; +import android.util.Log; + +import com.tangem.App; +import com.tangem.data.network.model.InsightBody; +import com.tangem.data.network.model.InsightResponse; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ServerApiInsight { + private static String TAG = ServerApiInsight.class.getSimpleName(); + + public static final String INSIGHT_ADDRESS = "/addr/{address}"; + public static final String INSIGHT_UNSPENT_OUTPUTS = "/addr/{address}/utxo"; + public static final String INSIGHT_TRANSACTION = "/tx/{transaction}"; + public static final String INSIGHT_FEE = "/utils/estimatefee"; + public static final String INSIGHT_SEND = "/tx/send"; + + private int requestsCount=0; + + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; + } + + private InsightBodyListener insightBodyListener; + + public interface InsightBodyListener { + void onSuccess(String method, InsightResponse insightResponse); + + void onFail(String method, String message); + } + + public void setInsightResponse(InsightBodyListener listener) { + insightBodyListener = listener; + } + + public void insight(String method, String wallet, String tx, int nbBlocks) { + requestsCount++; + String insightURL = "http://130.185.109.17:3001/insight-api"; //TODO: make random selection + + Retrofit retrofitInsight = new Retrofit.Builder() //TODO: check + .baseUrl(insightURL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + +// InsightApi insightApi = App.getNetworkComponent().getRetrofitInsight(insightURL).create(InsightApi.class); + InsightApi insightApi = retrofitInsight.create(InsightApi.class); + Call call; + + switch (method) { + case INSIGHT_ADDRESS: + call = insightApi.insightAddress(wallet); + break; + + case INSIGHT_UNSPENT_OUTPUTS: + call = insightApi.insightUnspent(wallet); + break; + + case INSIGHT_TRANSACTION: + call = insightApi.insightTransaction(tx); + break; + + case INSIGHT_FEE: + call = insightApi.insightFee(nbBlocks); + break; + + case INSIGHT_SEND: + call = insightApi.insightSend(new InsightBody(tx)); + break; + + default: + call = insightApi.insightAddress(wallet); + break; + } + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + insightBodyListener.onSuccess(method, response.body()); + Log.i(TAG, "insight " + method + " onResponse " + response.code()); + } else { + insightBodyListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "insight " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + insightBodyListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "insight " + method + " onFailure " + t.getMessage()); + } + }); + } +} diff --git a/app/src/main/java/com/tangem/data/network/model/InsightBody.java b/app/src/main/java/com/tangem/data/network/model/InsightBody.java new file mode 100644 index 0000000000..4d8951adfa --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/InsightBody.java @@ -0,0 +1,9 @@ +package com.tangem.data.network.model; + +public class InsightBody { + private String rawtx; + + public InsightBody(String rawtx){ + this.rawtx = rawtx; + } +} diff --git a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt new file mode 100644 index 0000000000..8ac109c54a --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt @@ -0,0 +1,17 @@ +package com.tangem.data.network.model + +import com.google.gson.annotations.SerializedName + +data class InsightResponse( + @SerializedName("jsonrpc") + var jsonrpc: String = "", + + @SerializedName("id") + var id: Int? = null, + + @SerializedName("result") + var result: String = "", + + @SerializedName("error") + var error: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/di/NetworkComponent.java b/app/src/main/java/com/tangem/di/NetworkComponent.java index 4de9763426..2d269162e8 100644 --- a/app/src/main/java/com/tangem/di/NetworkComponent.java +++ b/app/src/main/java/com/tangem/di/NetworkComponent.java @@ -26,6 +26,9 @@ public interface NetworkComponent { @Named(Server.ApiUpdateVersion.URL_UPDATE_VERSION) Retrofit getRetrofitGithubusercontent(); +// @Named("Insight") //TODO:check +// Retrofit getRetrofitInsight(String insightURL); + @Named("socket") Socket getSocket(); diff --git a/app/src/main/java/com/tangem/di/NetworkModule.java b/app/src/main/java/com/tangem/di/NetworkModule.java index 86e669389e..b61754ffd6 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.java +++ b/app/src/main/java/com/tangem/di/NetworkModule.java @@ -31,6 +31,16 @@ class NetworkModule { .build(); } +// //@Singleton // TODO:check +// @Provides +// @Named("Insight") +// Retrofit provideRetrofitInsight(String insightURL) { +// return new Retrofit.Builder() +// .baseUrl(insightURL) +// .addConverterFactory(GsonConverterFactory.create()) +// .build(); +// } + @Singleton @Provides @Named(Server.ApiEstimatefee.URL_ESTIMATEFEE) diff --git a/tangemcard-android/src/main/res/drawable/card_ru031.png b/tangemcard-android/src/main/res/drawable/card_ru031.png new file mode 100644 index 0000000000000000000000000000000000000000..8533af69ab41ec9beefed68f4026a3e7e750b4c5 GIT binary patch literal 87728 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�EoQq z6HAh`5P?5_ebZ#Ma;!710lSKsW>TC{dG-gp0>(qNdQSqIx3~7>Eew!bXQ7|SK z4n88bYiXO!z(=+iV0bm&kno0rxIaK1s?~cIR&LkN9VY;J{T!@M4V`_W0AW=34-}=! z0f?#N%wB^N0+647^wADLj(m_V&d1sT$ai)CqNa}?RhDDDsAQPlS^6Ln7CvS_EX=Xg zbgD1ER0brCd-3eM<34YWrroBtIQR{c#c#NLDdPCYlAbNi1$5$EL;A)%FdDIPib!Q; z9j3P{aF@w4R4!!()^bDu&3Ppf-0dvxNvoPeupf_tVK7XjQ!tTAm)+x{SFc}<^sh}V z)Q@cR_b=2Bcm@Xt8$8#?A9x0)rs}6+(WKqrUP?|FS0W;Q1tI9fJhgm)JYPiHU3mz> zq7C2N0mx6E27stCQnv>Kq|$7X$YfEs2S7f}1IV5^76QoMfAXE5fApR2_=m`Q?L_2L&Cd?wyS6amC4;oX&VW?)KNCjL-jI zWc$I~oPbLvc676X$|oxpN5ezZ=~%SkS7&b$fE=&wec~w=^c~yH2jo~e05R})x~byN zE(VYvzL_lA$C3f^X#qg=2V%9L?pD_5E!`*tLdbHopqe;DZ)_Bl%6RTR140Ml%Tltz zY7AZ+GxjV78(LdiT`rel{7PAQ)AU5&)>hwiQ&X$MQ4<;*n%!PCn!6ol8}<*Oh%gEcShmWk72k_=}P_c*HT2oPFBj?O z04|3%fJ01id0iQf>FT=~>Vs0~pwi=bSvB;!QHSWjWA=BFvGJ8dv6F+*B(Jj5o z-?I|7SC+f%vXGsY350eG5CN&Y$7?fBy*Y;sQeb#8*+}K>gJ6I>fB4Zu$m`hcwz?MD zyx%70D^w?XT;*SGW9P$A^`b$E==`LjaxbV#!cAhd2eEx?RCSTC83W=r6RmqZ3FV_mGne&?K&afO=L z-rf?VjoVdKRZW!^n~BfpiK3U9%dTn6`N;yHYKQTtn65}Bg{p8b0tRK8^)Lh=Ir!)f zPksm+T+8*&g{tzJX;-C%kYBozc$#P+ZivvLn-18F{uXc2WFSfTN)o*ipYKsv6bxT~ zs4D$X-;Wv(9opDb!JmJ5?!=P@2z~Sb1DhRy*d&%3?Jk~?VSxO+03bhnb7rp;>pS21 z?B|65Q79nCDQfiUIusd5Gk1~$`9pCv6Wm34gb=dsgcXjo#F?x%bMv)J%SIFSyIC8j zu*X4WZY{>guCTmhoWI&vQ`1xqJL!~6COLFyU{jMBCx#n|yIL+bS%rOG5l;&HBG@!! za-8_IQ8`?)(=?~NDy>+%3fBtV|ewLV75dZ)n07*naRFJx}k8XYYXaLb?=MSX;MBBN? zvk5>x$p^@h4I7EqA-V+wnOOB18C)r%-d*wAv6}YRKd9A)h3*Hw&huGm) zsqC1yl>-nBp(~c$#$xcK&^G|U2zX`PMk-86kjFZ#xKE zOO_l0Xi>0AY&F`twjW><22E31qNsxba#*nj>4}p!^S07k->*IK8_}z{=EoCWZ|K@a{gxwaV0Y3l++t9)bqnH(==t_oJRW_+KUZIE64=aySM;X) zv+i=D*m}OtU^E(Q&KQ7tbR(7-m*8Os5q#8nX5u_HPVV^5$)^Puo{PxnRVrNrv zoqpoP$+NG_lON*qzpSl&vI|ofAZkdr_oeja0t9_)T=5(W0P@ji-}&^@Jb)DMV_|^& zeGz~tj0uzynRn=VF-r6p|7XhWl|zV3iJJHAE|sB9?+Gq!RGP6-G@qP8F=AwUb^@I- zq7#yJ6|9>Hmr3YGd?jQwnjOHlB0s5mCfzf)?hCu!BlDHRK6FCuL6eXtvQBp^qbt%! z1r^r5cHOP(iTeZLpl`(Eg0~wnzF_6Zx znj}MV2xncfxGCTJsW=oKLcrm~t+OXT{^iin&ptkR_Wj4QcKhf6(RZBQZxoUT5S2zZ zTGUby0|cd5g#cMBl4I=vKD&y6u z>lSN=346hYJd)LHo*%~UQiAN&NG@;dQ~(sFE>0Q@*3M2KlZewR8_k<{sf)EQ@uR-|nc3C4xTxi1*5+hHUVFAte=eccH>avV(lJdqzuk zedW2oI%8b5)ReX4^=`u4OzU0wSqT799ztpnDsG)Td+yxX+FOrlEB#Rcf^Mz*^ZxPy zqLU|!g{$cZAfFZi$PeEr(#IkIK{W`vHu3<%C?hN?+nT!Hu53+LU`LWt&Vsp`UN77I zeh@+?-V_X@Mo@AUBg?!2Oqem%N|ftjQfP!V+O=VW!LT{WNe26a`VD_@-J4jL*~T)U zq+Br$nbDtIL1VrklhUnQ6mg)cr_wPX^MX4Lbeik0UJ8u+Q|oODTQ=-!%cwl=@({~Q zoUMZuN0tP+n$N#msvXxjF=L)C&doNWXEfdT<;lYbSLq7PpkF`v@pCuN-FzDC4{`vK zX|L{eatZ*V7mC73{fkc#KtB8Q)6en%^31!F*MGPNKz_bEf+)295ERWem<~ns#?voS z{A6=Z2vO7ix+0}A3L$mpm!%g+jV6hu(ovQ}$p{%DP`NwAF~3GTfP#kQ~z{_6wyIA*any^G8xJ(vUxnF*MnXj=!~c zVLa#yhy3F!4n`H6V!S*8i1irzV6g(%^7-RLc(MNLTL>V87^9^;i}j+&2EKc%lzLoQ zoetWgJ->YYT<@djg~&f0Q`?*%31hD^Iw7)~2t{UNjZ+BH(7=s`J1pnAz-JOfUGlq@49&pvYBEYPUYvNI!+ zlnD8{R#wVQU}!_tELM{>SoC+$9Xc=o0C7Qzm8)2W;u)i;jrAe`Bp#*6iWDSAb4y+W zwz0z7y=6VIcq-uCNWGv9_Hhztx^KDqAWJRI!^a_=)7JuOEqL0AU^hJ*XW6RT`I?~5 zpDKPlmDuSVzh#B@8~x_@&pxL5VRb$kAwU$-71hVO zH4I9baHkpeu{`N$b=C)wR1+*k4ZPi^rF;tzyByp7xT%0?GWqwA!MWBHStZ6czbFWe zML1#m?6U{Av6O|7n};=-ej&=VPCi8d(cRgHCYTQpbWDn}x77dZr!qo5`RBa8)w{`E zIo4Oc@(Dx`0LU-oo%9Sq=ng>%!KzbHQU8LnM(@$n6koI1%Clom&*}r%o@huFV|=Ln zF%`AB(1}eSm$$3fj6A0%$AafjSC<|Egb(?-;Fe@C8e453a-DPwLMD8mqq*$X!kMlK zN+{LIW7Z#)6uDV5FKZgFRLIVU!bEhfo~(n&_>l9Zy0;yokm!PcO@?K&9cq`fOsdBa z!mGs4Hh}RW)p*`ue5E!oH0)W9E2-0KU$JK1wD)J9JNJ0%^v3}Zy1jev9BUUq^c_X| zSZd905j-$J-g`F}As;Lj=2%}j`~O1h_ymgdza^?Bf+j=kHXl_I6BLlxaVn`*q!rYT zzND@Q?~gL7hS``#qp=1B?QZ=YUBsrMdXm!?Fnw%ZsuS?GKkftNWGaC&1Q1cMryb5} z1Q46UZZw*L5$yko%MW02gnO~aa#e^gP@AExirQFeJ!`)*%#ud4DYY5LQjH|L!u!2M z)FgQxMX%-*_!`WxLXJmz?wjxdh6#?!yY@6N##(2q1>w2cywI>mm9CK0d-iib>&?9H z3qSq1_T2FkPXR!n+TAC|+655WT!g)))){__@qrNXgCpC~OI5B5*#0vIAj;^9 zC?&f-CF1D8#x}z?;+M-4duyzSg*`&Ag?Y{}956|-r86=uMq~=V%n`R<6}-pGyY}dA z%Ffn{!hHmgTuAJlN-3gbojiB*xnJ#!(tmvNnUlCiRP*Cf&hQI#a zZ@-KkroO$lx^rzWk6oEv%Kq!@=1+d}J#_u%x4-@B_;_3Y)O>Y?QPgDsqHrn1tY~XW z$><&M#8Lv^e+1MAxmoyX;sELRZcCS7M1mR^{F;32q^M(lgUQH<;C$t zUhgHIq1x3ZfiqP_O!L=_M~&DSN47)HeJOvdP8Dy-0z^&Ej5FxReXcfd3bS=kCiu3W_U~M_g1EwTUui1YT3E!regBdl6(E9 zpP&c&KO7vMC^u+z>W3q-A5+xmRme7JzprH-LrW{z6oVJmS8bjywov!|5Fe(n-=8xz z9dkOj({1Ra;lq^sVyf_cS=&VW;3|wJ4Bbb|F7^_;4S5Y#O^3^MSg*(4rD_}7ZdBp+ z8EG+V+7+oQGMD;6JJT_=*^k|*<;fsf@0AThb-iIx;0#tbN(;GTX6!~qK#&_AQ;XFW z?Z5^{)J)We)azap3Rwh@g6iVbUMF}wB*ypA%fAJxh4|IKqx0YDCAgMz*UfE+5S z(4(d`to1*BZ+K;(excy<%r7*ftMK8U`~>}DYVeEI4l~c}RHNMQO05KEWUnb1z2AR< z5mr6irUjR^f)JZYjR*qqzukG>RKK2a4a9t@H`n5E&sbg7qA>Tx2G%iK7oXL|E_}1@ zy}H0*fDo1xI+i_N`AMqi+R&LguiQ^kKLlNozPy?2krGROo;a~nvV*NA*H$$cpn!wi za=VV>+S?7#RV!3Yr9!J~a{+hyM*qDkLTzqK(iwo{FMK>sfZY7iiu?h7cJ}E2Nb%uD zae%04s>pqt)-!zT_qG~)RtC|vl6!p~J>)5TaOEdoMi0#oS2tA}s2s|yLPfX^tCE1D z#F^?4&3r?(($OxxA; z#W|FQNro#ksCd6zNj`RgyG84ASEq`r9$8I=!e00IJog}}CweE()#g_hdv~jp4GST~ z3R<#N$P*Qg%K^wf6S^V*(RaL1B*)TFmA`3i+Ujj=Z0yau&;t#_4TX<>`wQ^I2}hNc zRqeR1XB6Q|Rx42ya5|N-O}-YGK_|$vSxpX_$W~qpWR^tKV$;Bg&$m9a-Q45C9aMI; z&SpzZVUNOs6w4a^8cCi|exvy?8RsO<8|4f7MogH)X;^5@pMXt`(0Go-0^NODQ5)-6 zwKJFs2g~rHEU7%OI7CkVH2tOv%syLYV<@7P-2uNAiD@-o(E?RtBZ@r; zDRkpBoL@3=Z0kx}-0h9~M?6ickv4`X97y*El6K2_y&2{qaME&Ks?7~~3Hi$OPDip@ zN<^n$E9e{5LmH+XPkZf5iWhllFv9N;vJ}!44Ku6bys0LzpE&pf7q`eOo!zGJVosoel6KJZ2e(f;PwCbQjcx0sFA>}9l>TdOL;7#tm)orf*o z?z2}`*=+_P-)2CWBAhaLEJfNThtw0d%@piGS=T&RfP_I%;F9N2O10kAyk*eiC6CB7 zCK{s4RFz8EgY_1hkr+JZyi}l9uaQ?>XgbBrC*~Io);k%WPWey<=k=QT$z%hitqgIX z_cm0`XSKqWsVgoo1v^E~YBM(^Z!Abe3`<||Lw8?X9qgK0z0lP^Z7`T>%1j1sw*#C1 zsusM+to~)l!1%QdJa07gwm2`|Jay{S^E)AAkDV#>umXHMcRKUND}Qq9F;TJ}3qTI- zvo=)>Ao|fFO4Dju^_ySAFBbB3apz)%GnJSy$LBt5`?g&&Sq+7TBZ>)&%H*+1woFHt z{qFoXC`^W}Ta#rMfOXq-eS}Q)aI5rQST+>kc<$wDMKKYqNOyzMdPk5Hlh)KE?21);rIP`d#F^M_(3i<C#;?C%>ymgol-n0|FDS`=gzHF~+ypRJnp=Jwe0`o_oGyc+So{kGd^yKF(#%1+upu+e|RFT$*X ztqPjpHLHhq7+Gq4M~QUB)}dq_!ee_U0K{f9ni5N1R_=$X&AlUSp;XZK|C9H&F>R%J zzF>TOaLLc{vCpwxez$9E$_|$cm|$avI8-oTu-k7-G3&TR2nFWl=F&hg3&kQ80$RFg zYf2DA6s5FL&;lP;+9)fgRzy>oY1IZ*je4|FSCv{Vm9$b-RO;21v{LJi(jV^JFZ=wT z=hzr91Xovj(sR#;1k5>ygZ=!T|NHOPJUJr17;k9ZS*W++bFBK7dxTKFc>6jxiQ~6x zSq`{MYh&0%W)_H$2~39Z&ml;EyVQRwL(u!idmx^E?xpWap)aFNny9=05P`dHY8S?Of$-{Bu$wTcr4aq&x>#~< zw!e32*J)|-5NAvwIb)BuA|{4XS(`9Gp!AZOC{iIyQ?xGyh%7R{1QWH&+t;*Kn_dr# znXgH7v5M;9@kk_8Q&V?fAyp7Xrc0BEG^NwwHnU!T>Zpx4nrXVTw3?AARF5bRz3{d*M=^gg)wgIwMB6ZR?ogaCmu^C>=7S%459 z>x5KD72yI<9Is}{rDW_8_kbXTB|F>6K|$p!8WgN5DCxZX?0ihFmYHv8slsa}Ce_%W z1|ZlL(o(~2U5G^Jgd$P){H8Fmvx4_*{%AR&w@}}x%_pOc%HTpot6xnzuer?xAmmK{ z!e%?Lr<265DW(I3b8aBdF!pH&+5FGfuBe>Pu(YA@_AO};M5FO{q@UcKn;CBo`dDhy z!8v5}u6)YkbausZ>44Y8@It|Yh|&2*dAwPrQa49f9EE|)92G{MjnG(&3{E|lNE;4A zkE3ZU5`4UIUi0zp=NF;t>cPctzPWf9n7{k@;_DKmQ<<{zqv>S;a@ybqUfdWVC&Iy^ zM&Plm7Y0+ArCRBr;H`=}de7UN(v-u7kG*mayeA9<&HNx`(S<62DDa6#CSo-fhaqZ} zC=S@m96y!S>yKNIa3>g$`ami%U{b3U)6*J*IlS-jxq%yt07MsCoEnCFD&^a8#bV-o5_L zHxI7A3pK*)pS>#f=& zF$jx8bYC`^tZ65aeQ=xC>RryY7w)Gn3K03sd{~~fE9u}jzO7?XLn?c0xU0X38F&Q9 zR2j_YX;)S&wEM^f=nrf1ej8`&e%d4bFeN{4o9*`f#1IETw-H8mw3E!KjM zH9VBC6C0EbDP0>Y)IKWNgPjSY{>c+~C=^1`g8cn^CjbK8Rb|1)?1kztuS=v2@4x?m zpyJ|h{TCWPf9Ev;$ms=$@&Gx_$D&77)p4)sMD-8ABv+p}_efar(|7dZZQ!GGEHnF< zj{@QLWyyV#rMn~z9AL1*`)b#-Nsh6n`m-a2_AG2xZ;3s3TlIR~kt|k2tTvHeoTb$| zwl9$e0HjLJjn(!x z=i}-5aJZ|QCvEL?W%IZDr<vpX;L0Iv6SvuI@MJh2_1k7hHa2AuW#BR z6NpgjpPq)+=7PM4T3ujP#F0la_IU5d1|LKTWa)h{9iLxM_5E-i#(6)vSo_+vniRJA zkIHSX>z!fa{2QQnS5}x`Y^J9qkd{S(g++tw9Py;-{-g5Tn^7_ zt>ZF|=U380ZFt9C>=QLS#fMCx&oeS;cb8?KC}I20WiYl#y6)`o$oztXPAXYKZNhO~p`m%0Ac1w# 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

WFMhm7-O@nZOb6^>N+nfqcl# z5L&ghl*R1J)l&A|g2L_&65)bv1jf*T;hxpSMWyFQ0JRVPpz?0YNFHc7_8yP+@3XpF z7mHG<9GA67P31VR)KsTc*-qgKj`v=xl0iaB6p;T2Ao2%AnwO>Fq`WZlK(DwMpbJg7 zUxe2wIJbgW0p`(Xge0$`$=jw2xW_PhgNUY#P$wwqb;H*P9#*z*NhKrh%1Z&aLbdbf zP@@Ag%cwhG0KtjQeZm8Gv)X1YDCo}PPOYmkbkaWPFrXL)d4iw=P)$6C_JT?BNGH5@ zMj4M~Y+1pR3#U0E4V9BZOTn*Hu0W(Y-f;28Q4oYFlqewo89<;L#~bg2^|0i!dkS8K zE}%^Kz%_0}6Rfo@sU(|ADCb@BS{`{p5ur>dx*-b%Wm+yTLWthz?>U9U73s9>D?#>t zU;_?!9KXMOIIp(BQG=Th5CkO)Ez2e|YT25yc(;ipmE#MF2KC zqoC$pb3-=GeI4q(P+w7435x$_I--CCdPJ8}I2B0v7`?nP`5Lib1Wa=84g0+|$R{S9 zkKZQft=@Vq4|0O29!WnND&|CV&mwtPzd!eG$zdcG{K;*+C-He$O3YiUkbv%@Gl=ao zA2FR>wmB~$%8|AO)C#SS_w=lJ6VGr$p&~{9>j83cIwLJ#L({y#cVB@>5|VT9?LGH( z_eF^=Rt!=816LFfLO4z^DKy)1cHUc5agwA$r8JX z&{Rs8M7s05B%ZGpL}-&b;j=zsSyOyULx!JI(BufLKZ59bOiN76eiP!by1ol&TwJ%B zO>1r62Q1_B-oS`yY|_(;kCHSY{HhD0)Ec)yX}} z?Dis#IQ`$GktgiqCeqHc}ke7rewiIBcZLNWB%JF zVJME$I$736Qf}4W*Vjhc*7S`TJ^dxlRk99;oviq(ZFy>Ark%8u@!K>7bvZ!h z^|FB>87}o28tU*w49c_*zjL`~0CI^e{{4op+mB^H6p;TyK*YDI1r55e>xxvl8htsj z&lA?(`lZ!*%0HIwF2D=L1q7)(=k`I*%U&J#hR{s-wK!A=0;JuDu+%dBoNsj!-&mI{ zx|78xcUaH>2IF8CSs6*p=wX>-YjKurwY_T-!*I)G7`3ktDV0!%Zee4@yJQDM z>J+p*9XUWi7`IyeV2!;cMZ7Sy;xkOyX!dAOVfz;1I!ca(+O9(-z z7qUEO1T+%mBk47|5H2^ODi6OFnK0%TNc^OC8j!e9_D_Og-$|puz%BjwY0`lJo2>%i{r6TM; zuLDQxsnSBC&_tZ|3Qxo~z6ugpvQ1Ak1kG)e_hm1>PnfRRdgKc~#LHD%k{5nkhUm`G zIB!^^D^C_RWw}+n49IFVLjipluz9JSv^=qI8FL}M)LP2uEcl@`O#j5ez7;PclUnWcyoDI>zC`tPee(7uL&-rmeZ@BXM4^Hwe zj{!luXUA47X2MUzu+DXo?*1t2Tf%o1!X}&3a`deYVW#fMMO#=a(%Cj_#BpF?7`9Zl zHi{Y$<;Du?t?+*+q8>U=F7k%5oHz6l07L=#uLA^D-%%XT55EwyQu8(Men1kd>LGsn z^aaRn=Z63M--LKN`#y*nzWB119oDlM1NPOZ**o_C&Kd*TAf3wv$lcn`J?w<@Y zuFWjY*f$6KV{=jCa36Ukd(R|36$o6lm&KH|jC6obcgr#pfv=VYx9v3;ZQn5isKy*K zE!I|q}-9(08X@%MKtRsrN7 zp$wjS`Zegyd)5&`6P{~4Xi#V4=(F^&zrFs6ZDV;1~qLnEDKG41w#JcVnRv`46*l-WmJzaIt+j#xpVZoxKe0oAa~ zf?JjYXO0VcE6G(im^Y47PhSC}fc)11f>h>ds_%yb1e6tW((8!pEdXu75toW2$W}w4 z@Dm|~;55|2UO%m#5ThrbK|=T*fxRR_WQy^h5`DR!Tk)FHo^hyZY->h>BA{_%*52># zkuuM9wue96W46xDvKBWbeao(CyV!IKuC>a-8waf;5f#8K!%CNTYF!#1l}wF|FX}LW zVcoKC;R`@7H&B1NM{bWM`*kn1`aKi{* ziN)3(!`&!Fc@VuSJF9{qp3_4{~PJ~bL#y?VezbXF;hbsD% zMp4U*N(u~guh?d$EFcQGGY&#@Jwk@}+rkhogR%6_kJ;>PJ(D;T(4@U3rYvRz_pp$G z!N?(g%b?NiWLc)BOsK4TWYE57rnr}kz~{3C53iClAxH&BQ2&1*HWA9%`1PaRBvC+o z@%B9$r#S4y2D#}SH>HrM#1dzm!`&!euv!NDXtBQ;kVVh+F5*bbmHO-GiqEAXVsvpp zFG-LpI`6#~Rk`eOx}x}G90hZ%je;PGqLxkj_+q!G6&JD0wnd1_`<;G}S`C93IMBa5 zZ^6MsSDX%4i+vIgN6h{9$Re(?m=(9;1b|TE>i6-%MGNL#uK=owQw22%By;w?}pum6YF1D8|)c2OYq@AYh;qd zWo0W?cP?XkcoMZ*5zFLMxBH+2!m~8$!}FxJpuv}KQxDw`?;jv>Nf|G0Uf{(mr1+)K z^Iq0muT4q<7*Rle1Q15rIUeg?*rnUxS-R%Jm4LCll_K0-I&yG#qzwQcxI{p_;XgB{ zo{-%7`Bs`bged85gb?Ijnm@TFP4{gq0Vs3x<2}Pu-R2<-pB|@%w%Q^U1{V%n4cLyK zgMnHNmdQEDRd^gEQ)TRQnh=}^31qYmnJruMGhJP)9Sl#fR-Bstl+-X?0BYt^A0(?fIJeB3EDY2-$BlYiVWBE})egqH@s#CDS>PP|L1zmLUC}F^Yntu>4cBlLv zWJ4q@%O?Vo=gr}05BB=o%u^HvAr1{&R};1hqB(sq*5~;>6##$l;57SGN-INZ)u3rH$;0p~X*7dGFGCD52swi;@zi>M2GxO^k^D@Yr08HgI zk(%BBP|nd5_1OM2*8)uB`m^M8*3OEn=$HCmby{H*5b}T^wHKwV_l$6WNIua;pNXO? z5PHvZrJ_o9E7n2XO~POXTAf$U8vEdo9~^0KWeB%JTw51*AQrFAC*(bllg;Jc@=Qa8 zRjUqT$Nw90tc=YWTn1#dtux}Z-EZxK-xoX+N_Jmp_sSqCM7m}f?!5AnZDwWF>E?~v zw#@E(CqFM0G~CycIFV|~wfmFD;%A+py4(6&IU+@Td^}I#p849UNUtc-f>A(7 z1Hv|a7SUhB;Q*1{W2HYM&J6&;TR0%ni$PLRa$^N?c;X{(2T|G?5h00He=;t_lReoG zZY#)k@-^=OZ6RDB)m$j?`~={#ea5!7FfcUa?okC*-^p}7wY2s1jXA*J?w-tG9hjKz zimV21iJ5Sj5YVw|>@Z{4kgR8O$P0XSswkjYIdl;6AX|`kc0Ym;MpEf19)25JX^LMO zi<_Pvt2yNo=5?9dCW&y)p&q2FYsnXL+}`Zzq@mtZ(rpzqJWSCg#S8E@Ke}v0YcR|PRe8AgF~{O;DJK?2_Q(FFoRAggV4`=Y!jk7 zN02ZcIg=RN@Ao)wAuq+}qh8YkE|86_N~-p1)A$maMx> zhGBBuVlV_m$^eRv&PE`lrG3HWW5Jf?aoh0HsLJ3QM#8+p6P$iQrdB9aHKzo=i2l+g zL5?=zEzc~)%Ysa89Oy5Lh+_|2d85FqxYFk5NE8rqfM^R&Q|TFD0nxqV(C%piBI%3x z&5O?!?*2UJo%*HlaPV{{+gKB}SxR5wec}Lco+d3m>A?kBu`PC^ z7}PO8?6M&6Ko5C9Hhq&8KxjHs8#bzqb8KNH=235 zh%36AMhcKOPFFT@uYkza*AywvP=k6ZhtCfzdNA19%v?GioG9MN@y12eTq#wktmi9{0FTUd6JrFduBYZ@!|F1{I$#i=kcg}eMHwfMjdToT8j-bji9 zLJp8Z8n-Sp-0K81o)$d3&fE>i&kust39kkK;ZCy^$Z3DMDk_ZFBM@mdzT|5KA`*4x1tN7M*fH|UkJCEkf3-0jTbny zGoIHVvbZk6K&3Iu;`Wx*%5l;$ddsYTQB}ugm$&xU60@no2wv7gc-2YYCPB6WLw@rE zvOnoVXSH|UIzirR{+{>{YLDUd4^HvPP?iW%J>szFH0g=hC?I42Nqj8eHiZQQzF#fq zeIX72gy+)Lo(}?u>Br_VMeeqeFQltG?{&GL#qXL$?yU~Q4$;d!Pf<2ybHsnsxM z>UUcMc+^fh1ZiLKEm?$s=`KQvdjdp~Q27$y&0YNK}HDnRPxJ~Baw%O9Kg-$6ev{O6~o+y^9LZ#7s+r=EVWr#vr^n2S$s zrCpMRk&}R*e!@-2jkG8razACd?mJeb)R^NT!y~&iIK#E8J6_mtT4(9bZp(CET1dVdg~| zo)84tQ9%Ar01*Q3@qn9q=zcByi@C z;*Mu)Cjc$j2B%ln`!~idZl&U2J82JV%;G)E>W-BFE78*Ft%u{`Nq9gyzb#k+pDEBO zF^0cr^h$B$aV!aeAp2-4hnBw=KqP6`KXUQ)+PZLQNtkjsC0rmF1tb8FM-c&ekS%S_ zIY2zduCWZ7yee(5sv;4P+@OHOKZ1h4yoBI`aDb#7%Gdg&eV1683eW$OnXQh+jWL^j ze8K>@lD^}l0co4}^)q3z>At{@!#UD`#PrRBkYBkD`pZ0Q)iGZDCCY)w#~aFMNEO{o z+>?jp+PVJq10@`fYAEM8i_W2bNs9uq9~J~CJTAIO+;ZRnB>hbg1;q|PZhB-+5c3*A zighISa0u^MRv4}f!Z}^Q^oheOQfVjfNC5ai2fs#%nLAdy%|jiS(V{ZDTKS+k8`&gQ zXWu#s_(G8Jw!aKW*P1D~Rg^=8`q0|5KpER9T6}rh9sogPkMP_4F(-Vk7}7P-=-VNm zLKG0gK~dr<5H zq?mi?42}!dy)`gC!hk!5hMbTnU8Z22bP}tq%whJ0AnPqH0qo3Le4#dM80m|!l&#wk z{Px9tNZzsEF(e>$H2ssk0KzS{Q2dh(@kyQ`vrcs=BI>5(jm6ObnD zZbv96AfT?EAm-(s_XLZGteg}e0V9KUmqGlV9suWQid-Gs=6>6t-99+>eR*+d%AMs! z$S1Md#|FHI2{bSNFJTmKXM5k2*%#^}rs?nS3szKT$ehZ(n=k&3=thC~602uNmAI`<0&{t-arsxz99fMjM90V#lk0Mh#N ziN6Mj#-A&|ce@vTz#lMVT3s*uzOXz!JUBMH>N=$8Ch1jCU7NlT1ca7my1sR_wR_T1 zee(>|8%jv5qn#}gdO}EG{f=DFI6Ci)78ivk7DXc}CMeI=qLS zYwlC6IiU+Lv;x5MPC$6ivY1goNC9#^?DB)%fEb?&LIZL-A|R>h`DuR%5NUmu*VKVk zy_$#TaX^%i6k_g0@C?y-RzTvz^Z}Oh3{jwKKyE*U1y*SzlRke z1c@o@!)GZ{VCB?!nQd%rtgUQ%dU$wArlx2se-p7@kVsB}x?`!lb7J{k0Qrd;VlQ66 z6NL-7;mWm9Kz0C90RI>u?qmETfE=K_@&m%9eMtOEfQbIV_nv03^0p?gN={+G281F( zPz3jC%m&C6gBXU%Cb3##M*VTP1VXK-ZosiR>AdDF20tGX!$73GSm(D1ybuDYr+SuF zN9P=?wh^PnqQm{3s@v&@4w3;x^_Y|1WJ308LC6A9{5%TC|0y6%f(jAWgn9 z2v~Ak^D;+H!7M9NpmK6z-FZ#)d!$%~G15!d2ywWJ1Q0_FiZztN001BWNklDd!{3ApPc&D+*O3-<_XqFbu71s0=O*yteA0f+Qfh`vM5{sS%0*$n~fN**8Gc z>RftIK!Of`3js*n$$G_p0HO^5i>gnR^B#wJC!g;TxUdL;=|^Kmf`kR!~6Vj|FKc{JQ~BGjuAo z(qyq%vP_nNfgiOX-zKd7+ztrBL8do2|JX7g)6z)}5LmKgTU=OJT3U`-1VJw#Qc*$T z-v9y}uUOxvPruy^Sw7(A!I`@STx>0W$+mE_X5JAK8f1}h)er4w>dpMYa3g0dsyTF>GFplAT-jl zwm4}qDzSi;K(K$jg;ZJoo3~{OqsU|oyuF^{p7HJkL~!H_X+ZWKgU~sTWnfs@@N0S$ z5b}fLP85XGMmqs{wF3|UIs2PJ%KM`r2__B5^WuE~gnNk}3=ni~e0E`KYHDe4+O-0Q zG$5UQo4$2zcqOo`tfgmq!C?YX!uf*dW=Rp^1;jXDGCD6M-i!}pKNf?W;H1~7*n0+q z6~2cWW;|liqkxbEL?T4OKM{cPbVra*kuyGgZvN+cW{_lJC_r*9?uU!@P8$pm>*lc0 z06~zo-|kkKlLe%#Z_2Atrx+aWRR?A5Wy4bgAR)H}sg_BEAf9jr044{gm%lGhFOO`g zu%$_*`Ue0pzDm`ojVKYlwIj`okA7v88kuSdX+Xr8 zrwD0Y*8apG7jonji8E^GSlWe#~Q>gwcz&EsN`m6{Mk5SS=NZ0{PI zLm@9A5)4|7xCf!EWzr3s0T)eLtsNa^fMPUC@PRJ9=_CQ!t60%{s#&Qu>`*3z5m!KS zHFrh<`M(4tq??dg6j5qI;}wRIny7L$u4YOx)IeY#?{%)SVVVHk+0pKtGUStGU5G1)!9fUJinw#@EAVba;(rL?Q0 z0C}@Vxx4RFrwYD=5PvBXPTr<&M*;cI0djf|VnreRNfZ_k-%|&aFI{S0gJXGPeqnum zs()_O;V^mv?j=%cUS|k0#PEe6JuQLpdnZAJ0-{TiOh~)CDIHdg*v1COrstP>23H4M zgNb9$|Kc=uB&IgVv+7gQYxV_@Rx15|A{a#Ws7%ay*XV9Y5H(k6qk#O~fCzO>tVn=_ zmb*u2xfQ)byp98)ZqQVJ5mRKC)1$$K<+!}VJJAr^BGnH<3_%S2I|=Ap!~9;PIg*$+ zrfb;cuBjQ_TN4YL8?)o>_VEpTppYc-^^x>{)RK_9voj9uu_rzDV5|33e|>m0Nvz!I zUV&+)O$yHnN#_q0RrCJ+fT+re4O2Hf33-wYQ$na9U}ytZfnOZI-2Euz%dNfR(jSL- zz~E?jkEqNwuf4Nx);D_uRF3QvTeaEd{2fqeq=ld{T^l+#o~YT{F=W6j_LaFwys$46 zzmHFUcbOC*+j|5g?L)lJek(o}!f64cMQi4sWl-^9iR%Yz3myc_jR?k zf7`Uez7S-W0Jx=W%Sr$w!tBwQZzBfR7Xu7dvl&HDY|GMNzzh3w_=aBpN=Ksn2bI#f z$KNLH1&~|+{>BFK5axl1Y_JTbpH(r+=CQBMfq9@-#@JhTB29;6F;ug2{Kkheek z?{X`bSM=nADM`x|J1>5c{OrwtDnx#Oq8)|%1PEM^E%m>;BYkjSPkL}b^yMMO{!qlF zc^-#~TlD29GY)^W;sk!?`-gbuMR1`CAz-Vx2>VO<0E+q(o6t&Um>{HW)nG7FEZs8H z>!Sl$|E}}jW*hapSV$xb(%v)at`lzA*S=4W+XtuA9&gY!_U9gn>D2(``d%k1roI0; zumv-{`r@Vhg6q!f%dvmtNduM^MgiF`Kz0p|3-^>|kBNihd@~yZ0SRf`k&HTbAw=-G zQY`i;U>Go;T~h2Mgxh=77j*z1vWfE^Vbj{O(RT>JsW53Q_b^@%Lm^+G6O@hY^2z$v z0E~PL;+fe1tG>@0O_uJ-@15%gH?YjoW=-xE#oVXI2hDJv1j_-mb3?Kdr7aP2}dxthFmBZyYFWSz(NIsDUL;=|!KpHPR zNA?Iv!0_sj&7N}Q89a*Qd^{f)s7D3Z4~l51hw}Y$Uejqo(FY%AfN^PxpjTxKh6JQ# zd;r<)7;Y`k^z_8EwBVeSl{x?2vw#uTAFM~HgM3@;aHj_r25=b^WSCv?j6{jMua7Xq zkdOdOf5zOy9%#1kSUmYt6rGE;Pk;!k{BzVvIMM}l!uo@UURi1E`D*|D7QGBrHv9)Z>o1%ESPeMm74N6$zO1}!%2DUFBhvN6#x5Bt=KU9=nR@SdlVJNm^kc%3?`(zgaBuD<%ge8=Q;=PhGI~M4K%~ie*0HoC8V#Pft zu3!tj4!{}G3h2C7K_+zyRZX21|tSc;*QyEp?s{ep4pXthee4R zklkHH01QITYX?0b7D;K+H}6tzsg2{yOB>_%;VC7m_NE=01j%8KqVE?xS;VCm?q&M{ z|8f&~K)9^1I~k&Y`~V=m+~fIV)AQ_F7IBH^RqvMD%xC@*U~A}g07zDaf_~(NRvsjQ ze3K0#b@dF7I3WSeC`CfzcR~loBTbF- z^xJODstjhztl>u6lkm~^0%3MAD=PyCBq5$J9i&#fvAFbwoyqoCzK!a7cx-wR_=2CnB zz+_l8R#|&jpA(X;ZW)P~7(zzEFS+gPn|13CGQ|oHS&U|$-6@4?g2Lr z30{`$Wkq4qm1^>bnwnh@8wF%v08yRe5EgSpl%y8>0omTZt8@bb$6g|6P4tgKrQm_M z4@Xj@*As&~Z^SwpE)M5iO7y?w7sh(V=T|+GL_yPdB)wuwTi>^3S8~>ANy7AS!{*5d zU#hHY)7_Z}jo2qlW-B_h?ukh#pz7XN-ZL^n6Sev&T~w^v!(2s5?2A7*qz{p(Il3QY zzW{mV-zOUxkjy$l#+{Wba|0qde1edHABcFOy-rOJ@}ti(uYVIU7)jHS}a*Ei+3uGC;l{w|!_JqpN=0CJC&{)|o1_T#f_2$jMnod=MF>jeL} zu_)xUb4Z?>ocuV*<_e+gDXX!$%KuQ2%h{<4U$nnCZAHx;%JsDvw+I)KEk~T(;9f$g zRxh@6_4N(9)a^>tY`{Dc6A6r&I|fGVk-kn#=em{QDx)ti7!ME0=9Wgh8$E?^fmG96 zMdsMc&pxDP-`z`$4ng;l$sZ!#Iazc^;r;+Z`9K%l$sj3#fEjPO1V9?RYDMk+ysy4L zPEmx`UL+PIrw5(-tGd8Sf5-N}=GmqSuD+b@MhWg7pD^|>+d79A-QK?%nTrTW--ZOk z=PXP+V~noxZv6AG(e>3VRY!-_Y_<*=EdBE{U6DB(Mq#(pw`{k~kD7-%It-rdm|Rsu z(?4T^WRCZ(Y(c}>JBags!KfHrkwvynhGT766{7r*cma3=a&ySBRRc;ygLtDVwclY-Ch6WR|)91k*)o z;lTAFYFwp)#~5xmNz$=VW*eJbTbo~4*j$?)vv+kx>`iRxSvTVbgaH~TqYHyQ!&4@! z%$L(U&7q!2B;%eU*Jzq%${uF^P{bMM(#ap9MYyr7 z>Vuvs zYl)@w(NM25+7^)`Cn;s=;bYJaZNJyx9e=8<1Q3`DdKQ%zZMhIL7@CCPug(BU7=Bf z7&NeH8((oSAdim9Fxt}|J_K=XZpJps@O}>0vI6r0>E&%cK&C-&slR(;(HE~mJkS&BjnUDN;mc@GF zDWy}izgGL-Z`_ZsLw`N(^M5M1MIU5rk0c6^%MZLiO{K9;?0CV(m3-vr3lm^hKmw{p z?1M`t2pgT>a6mYfAiCHWcIwaK5C~HMz?bzu0Q9kT*kjF=YFy(*~ktrhp z!YYdnAK38ekf7lTsmxT|Qx3PGb`M&2ije>52a;G{^X_Y-fb0_>ndLt)5>u|bN$`X@ z7d(|h3jcK;;m?nJ2>t#75`X13rW`+!qXNYT*oauJ%(-?bg_iQlv_qeQ7_aeoZ}3%= zYGc1jwLYQ3u+cU9+VsqTdw`r}wkw?6WD(LaFatgUrL1fO2Lv;Y{V*UaZWD4i#u6p(!i(p9+}eMKBd z@EXzRCi3y)z%^ zsUPv7h2X@X19qAW%W~)N9E44;xHML52HTxDR1A>&i)Es}T-b)e-$*A}Nv3sONBvC+q01#Q> z5A3Y~#IFg-#?;dXJ;xf*o+3QI^m7?u4h|&0@tcCfO4X#)ziSp~QnypmvoD!;N3i_T zdPQ;#cz^mIcCW0=}JHSX@lfUwQ5XL9u}TR*Ze@8@C^h-0oflw@U_~a zi_F-SoJ74}t_W&PJLU>Wa$SXSxS;2J6uGz8^CnZ6j)*+Xoq( z?%nXx^q9|pWaVj6uaPn!>ZB)9T0<7N7eL~k|4Ck|NY`Hq-=h)*yZBevUmr>CU+RxtTS)&xXI2do|^aOMy3`FqGw{nR{qKT1TkfZeq*0r#Uj3P1?_ad3RRFNjgO^gDvPk=a&e72yvfOMWW^P1HEvkESH zkC6C&1L2k@=d;7UFB$Aa%GtX$&g*9WeRg69p$i+VIYNDzljMfS;IOX~E?YMe5H+Y#gHh#OuMW!UMYMCI!fr|IhiY!hgFV&J zK_LGJgeIKhZxf<`>=z&hGVX8{XGo{9D20R|g!=Nb2LvIbo-mnO@hU4)P{WQ(Jd9t7 zS}2qnmXY}44{mb4r+F4OOtsm1#NnfNK2oBD#B*Od5_$2wFB?FA6F?&c4~=+uUTaZy0_3lO&H3HQjg!(=@&{`n)V zgf!Mcw)ZffllljqaBt`AMcVDXF!rM(+<%q2A2tJnCYHCb4oIR#7Y0?Xm$woHr@MZ% z_(zJ5mi8qS^O38>41pL!>?Q@R-bl)8LX7WttUBGE%7jS98?PFamLC7}k$5R9%n{NQ zpA<&{*)Kpq)nkqR6+%`Ap{@Eo#qaDMD)dJ8%zHl(dZ~D?`Rcs`!brRN8#O*(?mk;e z&^pwCDe>ss(&KRdyh?**xPAW#=`8EpG@yPJB4im2kd!bI-+$z43P3Wyqa?AQn)~&w zWKT&fe$MZnyQ@gAyZJW%X`a3@bWEaX5`s}c_6rc9>2*Tl^%M(_72*9#zn&f}MP*re}1pTXLc_4~)0{AMo)VKnW(JW3gw1NQgM= zPCBbTlhjKENUYJb=sA07N2jR8uk|h-wt06RWPoY0vYhv)<3ka$9dQ6x6p$ZDV~Nl5 zG!6I3wxuJPmk2?KsMzQML6~`gaL=W$ve=aUFd^svq^94Xc)kZZFgm?T$6*mFB&E!K z0E7ZDU+HPTo%HB@A#iw*0*>?ycN@^T*)4ZtAF%I)IQalQd|>XqAdqF%0{H!@xXZ;n z?kRF-UXu3Z502tJyr(J%lz9n1(5=ajR7tSwy}*zso~%y~ZbLZLU-(f#_74zoZ9O&q z9@!|JlJFv)5RLn%GO}+RCk7!`5nw+|NW%SwubM}maUjm%W7al4zF;yyTlQIt2TcGQ zo!w7D%G#Dae5m2c@2+NcC1%D5nLPeYJ+1Mem{e53K5!|XR&-Llr`vGgXgwbIN&k7$ zH*Hb^CO-HWTmVn)J;;m#vVVZEO^+InJk^qo8RDKd_;rbz<{WPk13Y+|FwjABLbV@3 z2n9~45z*-9y|Y45%gDk2h{{Inn?_9S@q%DvV7O)9BK4M@1;~@Xf*td1(?)3b@yZrS z9~m)Hzd%xDo=AF4w>O9L!1IrI{Z*fzd9}i`V;(-h)tTi*`?P7tIBb5#8y>f)gj^mQjxE%)^jDdCGN-WmSNuFn^GX6@ zi?saolwc=F)D-DwivsdPw_jYK552QscM!tAmF)GUQaHK(9R=_s!dy~1^**#8J{E*C7f9#6@Fw5}L^x!0n!YgGfjaKI1@%N*G9c zSD(VW0g=m#Gqc|CH1(JF7Lb9u8r(R?zY5&l6);raf3Y}Eqjl$Dk#vueQPv0xz9 zKi=NHS3cT%zFWMrtq};DS-?QVGG-ek%$)|KKUK@+RJqcg@8(xO6#u>-lM{kMT97wR+kCw z3m_Esl?E>NT^8Ft*d0+Z~WU$89r5`XTTc#a=B6HphW; z`|5!xAo~djVtS-VzR4tgo*CM#6ZO5l4aHBN6N5paDqt>YYL$F{0Ff{YIh>SZV)sl% z%hZv_l|Q-3k92#g5>f!hCwy;_!ID`2e|v8N)W(@6 z{7c)tt#4c1E%m6U$z15LWFb+~N-YU#R4X)LW|?mVi2FA8WxO0UoaQnFY|LdC4iOj| zn_$NFIx+V6%HiD3ecO{bn~=%evzguO&bM1zwN?MG>Z^aZBy34wTOffU>8C0cVS$eB z_xC=>`y6wrdKQpy0;1PG4mLoTCRmI{8mO+aMnQ^z!op@7xavT*`%Z=KY9KaZ^yZjfb0!&M0(0839eTw8kw_~Cv8`U_WcFgDaLk{Hv2aP z2QBGqHX{{Zxo`Y{1!R3Yl)YUl?_ZxCfRZGs@CN|Yw*tN+3{S<&!UG5gn>i??a=U10fHG#k;NuT|@{6DSe4Tilc<=1dJIRSe{L3edG$B8Idwy{9|zO02gg5_ohWIJd|4yx8lG-c z^V_tsqlS+^eEgpu{hMGJ`6;bEXgv1EC|6$KvAC48KY59W+yCaH&+p%#gzYyNptuwW zrWgDmg^0#cMUY#Gr0xbTs;sq#`t?hrY0XUa=9B&|$OQ+mmXAJGt!DuVFCaYH5aYYhcJj<)N8fz(Sz2qn_lb|W!Y?lns+L;RhF}$Q?UK+@ z*{uoDbQRDqLt9R&LjTc^WbLW7x>+MY+5O8@SU}bpNLA0P#PV5nsLL4AmiiWk3Y#Qw zxCqfpinu1~W((seF$(}rN_zjpe?GYq<6Y$r?cbmF^s|f}d35b8t)Kji)*YAMyLc8p z8h868ptv~WC(oQYd->#n$5P&X{7fcC!1QW>001BWNklI0cIoV^&p-A02QObz7ZTJzykK;(WtBF>%jdd->WgcoZ?9@3bSDHtkNIX`t4?ubVIzd2(571fdF71z_yyCx_tWQ(x_5Iy+R4Y=T}~c* z^X0Q~G5%E=P!bUrC(y=c9=I)O@mee{E(!Df#TbDg?x~Y+UwZG_CC1Cy|L)6a&JW(Z z_Lf1QJ>Xt&sJ2;T0vZ$0VD$$FbLN_?hhzE5P`~!MHrfTjD~kNjsU-Q8Yb9dwkftDp zU$cO$SJb=Di>>ofAunQbrot!ZoxZE9ncMjJx`NXv1>UJ_CFm7^Wxn^eH^~+>zV|NE z`8qE1+UrS>>|&Y#3Rofn2%|mw6~VB8=LHbRIbhUYdmV~Pd**dUs~mmx5!Vd^p zcZHG;5FzK^qQ-Qe(_KW~6j*i#Ly!#GC?aI)9;+GebrS*`mAAbCt&w?`qi>x&laln@ zwKI^>NH2OQsDeMHqqH$?|I?R0zWT~11`PW6#2Jhuht5LUMxDIb8L|@sO3Eo8 zH{CYZLMj7x>z2{f4Id!#Q_HMEQ*Ub$tuO?4Fmn%`N;z=iqZBCa$q$Y3uRk9LrloQ2 zxa{{Z3I&3sUw)8;z4Bzr#gk9RL20ji7?S6(t=*u@iM#h5&2pc z7SG%m53zu30w7UG4%(#c{E&~nW#oWwa78|nZ}$uPSQhspqF}2j){14NPKj~6q8uaO zz(dNHUq1?^yz-$Ddg_^!Gbf&W>+;F>Qx2RxfF&_4N*UXZQ5cxOx4%dX{L+UQap3an zSK*gG+5gMS zyZYG0$6t+$fBEgWr%nj2rd<9Y<@uMMd@C~sN;_+a5zq$L{-l_=Bww>f@bR@YDCxZu zX;2tZ_-X2&wE7b=bZdO^~% zc5`0ga4gCKvKfFVE$7a2#V!1p(D%Ig7kwhVfOR0z7Z4@s?3HpuA`#Q{wW)-ng?x3S z!Pnb<_QWeM|KegC@xcfCpFVLhCGOIR=O0T-Ir`R10_da5N3VW(IVtVrwGW^9$p;=a z>F&B9(0=$yN>b+KcOSd@`n3c5GcTUm@5=`imER-*--#eLk^wOhm#u5ADd<`&#ro7U@bKGc`kPn6rWn!s{_QlN^^Zu8M zaQ3Xdz~%KpZE-9h8(4LcaR*+O5QY*C%XP}PZc238uV6^2&`(Q&vHVcp2-DP0OA&`W zJ>DX?ZTTpbaaRvqiorfQ5wCsygU6JQy?Z%6i4(*5=o2jSgV&Gl|M1DU=U%xK$9eBv z29UV3AHL2EPsa|Nc>C(r=l>tAY(9Q9;0#j}-x)18OAojDsj97%NX|5^DVBwm0iTRt zty**K2&+1PtytpGQ57`{g0mc;FtS3M~|lX9Gt+Y`be2bEEY}0szLa{@BypKGw6l9yer3F zvFinkYtNgaI)ZEA$O5tvbGJTsXlN9ag#oof-z*3~dd$D(R8LM)2(nurN=O-$5En8W z%hH<@nHsfD1bhG{F%32IeB(JB#4Q>_AznknYWL^&%Af_A_K@%A24}aSlXL! zyPJw)XP>$D%(Kcg0VnCzU)~8|DWjm$C6P&P>eFf-kourq^oTB4xlkU3<>IFcdyR|=Ms(7+I`osLDSl=!cQ4PLJ{WMTNiZ8@}X1b z6bfHH&cI0=0rd2HFFpBjiXoFYdhx`8l(WxV+|PODsjC++pN%s-bveoHgCr$+QXR2N zuO1-cFqjkf{K>OPE5YQb*=dy=kUvsF5b%TIgCohRWKLa?GXMNT5UpQ(;w`@9oyNri zvI&52)TL%&MK>0fSF-swY|@n~{IRnSo;RC1p^zeGu-+cuDO3USw&OE*g2vdT%NHR~ z38g>>G9g0y!L?6LysORp_}bI726^i0nUk;Xm%a3|XTIS|hfy#tg>aizg3PCl-ZqVr z?x~wrp$X_rC7}m~2PZ~d$S4p5De`wgAmhZ^%iCI$K(K&p8X#mzuFX6ehGteuGIgJC zWubg7%YS1-ji%RfuR1^uNXYhA|v((uU~oz?dTK+#FXZ zHdPfm1)ip%N^OjfVoHLH9E=nH{E6qz#y$6RT+-RsU;5~+%lDdFAb_yU(hUe;045c0x+D7vEAv?ez6jR+?fOE~l{=s}Lu~rI;0KE8Aioke) zNplrI(8UvH*Ca$KBM4*GKo!p<=HBe7kHlo02P+XJOpKQ&J|1v51=SX;4T$bSut}gS zAVJtCP-I$nY^1LdA>a%T@g_N8$kFCtvAs6mDh2LrK;;xvlwq*RB_#vX zaWg)l35%6a5aneBlpGLJSNdks6p}S6Ba;ridB)wH1j>BtKp;XgLc`Cjz0mWH&8)H( z?3q;%oChzG)*H+sUX?)#!ow!73*s`{?t}|nx-JxD0SOl%aKy|FoserS_dTz}W|nLj z17R7RCU741JIhjaqW-HEiml`hjm>Mqw3-y<<8zclC=^AHpKDFyN09+40EXf)&w-yH z#eWCD$S6rd*64VKh>KJ5+@^8`e;eSz^z~&@+Fa5;$bS>j*L%zUT|BEWw+fddD`}y@ zcUNWsx&QUo_c}NHJ=9Jh;a69n9oVGF@l!Q04 z{7zXpO8)pt4}IHQVM%>ymABE7XkTs6B`Yi-_mxO+lG&hueNNx~2_1+QA8ZZFWT+Tr zk1rt0+)3EqvOyyUrF+}z?O|F_FetG&k4?^rq$;jRXFAn&{YY)RI#Dg*uVHY(5W%-c zS)vTxM=s@#5O66iommGa+vh-YezLTR#w5ZkUD2ZD%^g~OcM(La>IoH|Pld)GviB&pX z#-73YNcA?+9*jGfI>MN!=JPEPehLJRS{X+Vh!zv|Fb9_Ox&MLt!ff#KD_B5+IPG@! zyrJ8>RPC2D7(+Axsw?4{z9|EnxW4CE!pekz*CpT?EkCT8fG}#eXj>L*MyS1_Au;9H z>i~Ke#=8a!+l$W4=eIIaUsN<&*qJxEr*ce`EVbHfR-LG7d|*D)5`l*CgAkU)P)t<; zc0t5=Ajvg`fcUubu*$1IPG!2Qjv*%#^>yj?XoI&_SwQYDhN#%m^oja_!(v%EPux}% zp7LvSCc`%z35dG_+tdJCYx5#iFlN=D{*%Yb9A zPAZbC#Bztu(Z4X!-fC2<6)|BmcDrFpd>s3RhFgfURfug&{69ECOKqCLS6L^|l1 z#O=pf}=7mB;e$*swZCG2WRX6x?7 zsXbM4ox_cZTr8&C!aTOATtLwLkB^pf`%kMHov62aALBlvZ_j`b9h8tm0Q^~nKMvSU)^owN7(t{|4eYi0GBH~ zzVRbhy>o;)-!N0{7XMW*IZ`m>E&UjmxGh#NvWNcFWocuCfYl(IoQvhYH8W<@`C@fg zHdKM4T;=P9m{fTh&ejPmBNU{@4r!gvy1Kg7daqR1Wz{*IMobbV;iH?9%nqwZvu(&o z>xOexK+bjd&f*B1|0$D0rpE|^&*zg8uaNW)1c5@E7qHUQ_ftSy`^K|XWX@hD4FTtl1{k~K zOqJKd5>3Z=?h(<2Fw;Y(IqSJ)EFc?SPl8dL+g216mKTfRxAyzph;Yn#^J=Fcn^SMS zfRg-u6d)|*Y+O6Z7I}zUff)TGVX~+^Z$At?%#PGEpB0yB;4B?az zt<7atuWIaw|Eh=W-B-LWx_B<1@ch#`CUa3dHm_g-*&;wVX#A9w+E?e_lwGMHoyF6m z3G0rD>LrB^-|AbQ=x}Yw=~w!?HhL!&N0&!zH$X^+59>&ND4rHsgEM+UG%4}lNkDa> z!<%J-cX%=wL33!DncU! z%{-`bxg5JnqP80!Qx2Eo=8QUoucuc`Q_J*KQgsEqiXdu0=kY)gVT~tgCq_LkqW_7z z&j_-BY#kt=hlu85UajBw$AZbUUJ+Bsh8O!gfgDIunCwkUFgK%Kr=*KfAi||s z0Sm|$0>Tj}=9_XnadyZ~lutW6CKAv)BI5E~fK)uyYLNuL2niOX>~HpH@4 zC?YXYtv75ZK2T1&6|ugsTMdPPEf|qwr5*8Q{BWB8Nt9BPf5Y1b1K3p$dH$)j-|FHm zo4VE=%wquwNnkx+Ns?M-Q^yX6FZo0U+Mb7D?(+kX&u5ybo+2mQ0lDmry6$sV+42xg!gZnmGz;8)!?Z7T*lp7Hsu8>1x3qi z-U9z+yaaV?7fE+7!;Em;2(f@{IUt~T$7fnTLplBLfC3SVE4p z`5Rf>v2ygQ0|+tGZ>?@|E2M$Y}Kh03~(3j5{x`iDv=XazM!1>DTa84U$&?;l!Y5Lg5l_ zMtCOtg$_746Bsgf00D&1CdpTZ5D=4viaVKA={^Y*j|WyJ>l3C|0HN|cjglbJqd?A= zC}LPZwi*ykNwjVC2fGc3yG*Y(y?`s^EmQkqgKSlbWtIgD4pWV^NWMZkB*eduguC45 zsrbQa!hg+!VBfO$9ErQ+&4aX8k1-irRAbsk3{@78PyuOMxqbl7UGc8nfPkn+2!fbj z$iSJ(onU+UqP3^|PD6Nzd?A+n>JSo|K>Q+4#LzmR575a_ZU2fomlh^@%v>;si3bC( zm2Bt&vVepO5V5#msY2yB3ULDhr)4HEl9@pJ=L1#9Isg%%B};ODLafuVc9sygmf*K8 z3EgL?)2nJpupFLek8)8X-YK1AD|0O3a!kc$Tbm}a zTo3_XKw__$cc~5~C%^H|%=!U=3B{m896-q7Y7O>vCnSd7lEJtcuH+tLKoS+XOY_7Nao@?(E=~fsZ+Q760cO9CLZ1Cis1tcth zaBC8X5~kpf z43M092k@pq)AYC%2A06!9v~p%u6VSZsok+aQ_?$t=sFU;H*fub(1fJ* zNd3eS38wt|;sI+eaLt}_eKcV1uffuA-s`miqE1VU=S=c~JEKurXxV54kOd?> zfTYKx4Ll)m`j#bK7bZYBa75a)BY+TV#sxd}_2q-#dhi|3M$sJ=Xkf(;TUP>pr_wy` ziJ-g6#3AU%tGM9;gabz&v31$c+z~X8{Q- zAU?TR3?S6P?$HaonY5FQ0m6Yv=N>$O5Fvg%^J{5k#Yp;uj7x#o_Gkl}P>`|1EA1}i zmE9f#3zRi8_vW3k*$|owv4CtQAiV5?ir1QB6&nVG!%@WUF$EA}Rt>ijg0CMRQ4Iy| z#8BHvZGeN$LHRi=@eEAb`9>6GHI`3rJW2K@h|j5Ep>sj_K4;0O6pi zs|W#9BjsUgD!+3Cl(c$s+=XLBfhkyEM8o+ZZ_*mK3HDf^M0YR4lf1W^53_)5Pe8aM zQ`bJrGiZV-}FG0&=QrYDDZC#j+^jjYqL=6S7Ba3i#>(HSXl2U!OBWm{d+#5qF{a zWLkj5q*QP^R%WZ1c&9ylC^h9w8OSp2uRtsy+vXgkFcEF^KPUvjm{0-Xps~$Dp)26a ziz<)s6<-@Z2r@_NR-?8nb%6^WBd@Dgj*H&e<7vJN^NsEZxo~Vgw80N7AmIeWRB8bm zrC~b@C@i&bHUkh?A;^tgRa6|n&R*PM(c&y_En3{&Ve!SGxVzi7*y64Q zij?9|+`YJa@!~D+E~VPT4v4;uuai7c>uofS+iK3@%daHgj&hn!R4dX zum>HW*4O&9CenoMl$%(WG);E?(aD3Bq@cpqnK|DN9(<7URv(vD%enhXrtbZt^p{3(>)DT|zgRX?<}j;#ystAC3l{dg7% z9$@FH<_E2=zQ@7g^2`rGv;Q3z!bOzesLo+YMt7Vh9W67n30cQ{GYWGv52179CDNe< zo5)G8sc}}Hr5DVauy$uE%MuIL>MB+VQ9|VieN`hc)1Ju2OaMrp)ZVLammwUG@TF;) zpn6W$wkpvl0JR3j`kkicZ@^}Eo}g#XXB0em7#DKRLA6^=E6rxqP@kN08QqxzQ>UJ1 zn_ZXesZ%JeLkMnTl$6g-Pfp;8b5lDKCRr<IWF3Yyjn*FVk4Henz0g4; zGcJJM&(t)p%`V#PrtidaPMH%OXnA~j{~rtBh9~@1a!DR9+x8pechg4=949zxKl8#7 zckzr4COz#riSM~d)rN3u{C_#Q)urNA=phl#jfeg3IX2Z}3 z_wi9HSs*le_SF>^aUY&7|92pDsA0C8n6=w6u{f8G5hfzIXs{4|;~HW26yeM1xc_7Z zd3JGauA|e$^$*4m0{GbC6c;XW1nD%Y1p$U!* z5NgL}SG|QzFi-GN^=*7mY5HcYPu$`1Gq|Q>ENhV~Z0c5|xP!lN@6M+#AOcX}!W#)M z3=a}A_i3-@q3o>F&mUt*PS72}8N5JJcl0f?jXj*?x!&(TxUO{x_>ei%4NFYB^O0Ie z4DXE0$}M{-YBVxz84pL8CTaQ5L#`g;CsMI1PPUVlcg)I1#ULCH=|>r0AJ8QSuFSL# zT>8>nW@cIEf&-i=VuRirY0T%TcS(qA@yNFIs@4V2+%|NWCX#2-#X{OniM&U zQ*Phy8hRrcYcJViRF7Gi+XQ&|!OhO7k!%Oqsooy9Sk5Nm@VwGoU5f|hB4_W4J<32%H|yScuuP072%Jjp-PdHvhLrv@Ye&y2xYI3UO8mKMSuI1QhUPS<7|lbrU_ z1ro8Ere=B(p;LKJJ^?ZhFiS(CO_1~q|Narn4QUg}UcR+Y;&k_6y;EjzC)H3ny^Z6>hAnZxvl zw-_5%coc6LH0H}{8LMW|@tVHh@rDUAD(rjx(&i=RkP7|bA~mPQ6aVfDfh5Rzo?Mfj zVA`14xaB}ga7V9zOSNX@iQxKs(qRokZR?K%UWZSWaPqwr;#%9PV~`j`6#3_0klm9X z`nH)~3y_E6Ltj20fn7ZmwfWn2ejP)7pRfhcZrC*;Qjt-?F*+>Q-D2Ty@v+3X&CSSW zXo5aW+uAcbc?V9Z55{9Ip3mXJpLD^=*2Kk{O+wA(W~E=WRT&0z+3|QUJ0gj0t9OAN zSy2y`1OS$?yjPfLxE3T5p-6`mG{q@P+k-N& zbR3sdc)Z`%PWDbv7NtwsoXBtor~sTTYb0cG1&zaDTeY=}!8rdGe{S$s!8tW%58M`0 zZeArcXO-9uXQP!&cUmXxDr@`WQhs(hLd_HJU( zzM5YU=dLP`r>i!F=`Y&=8-|+EfGIs;20||uR4M6&?A4ErLhaNH86Sx@fSdOHT*?<^ zhow&?;0Ht2T05efqoq1<8XqU8Ye##)j|CgXdufX)Y;5U^oZZ67PUD8TuMb%n(b~v;OSx$?$L+|tCRkhEsXOlUZJKY*Oll79Z0vee?eiiQQFZv-SnhY6G zhP4lw(04)vwH^^qR@ar}_Fd{+k1!Q&d7CYqXkY*U%RL>5IF?B-LDSyYXO+`UBpUYH zhcXE+Hp(SBhe#t|eEPaXjJS@~F8(u|GAvbP@ieGolU5a5(9@<#L_qNwmAsAj_vFZxnZa61Pf2tl|JZK31#ai+=kF$o^Tp%Cdc-8|RKVe)&PEoQs zmW=FHD%LVJS|B9rQ^sOx|0_d)Mp9HX!c=1;V1PQ*A+4KbgWFC1_(4@$Hc>i9KH4D& zGNegyqEmn+GbZ+3WqLycgw2NZhd>*k;P&=HTpWC23b1^2*9Me+QFxNaVr9yW6twzPOR$(`8axW}hC5Sq>td z+>#dx87>~oa_-o&#C&c1^0W)qha48M(9I%8S~;9%DzT~lJkLuMMkz$G2;U#`+6%U_ zVBs^+^~bIFzRtOJL+WFan*>bNXyn;pE^cZr9!#qI~w(r(Ggfz%o~S{r(Mtt3KVwJb#%UZs)5HDFk7gt~}d|HWtyn1LbF$=1iU zP{`F39&26t*!ohGr4RP_uPB_ZE-Yen{XP!8yIf zfCu`bn5maL7jfC^q!<36Dkn6z&j~?w=hOvlk z4Y!Rf!K?E}=Q#DjBu$SfGO)wKh20qJ7*WRBJYDoeryAk$2ro&dmyC_r1)RbWmre73 z{OMl0BU>0wmnYhycO_qzQ@2!rs6f-mvFvv{%hb()H<2^KtOf=?a6BAw0?Q)VCl5*B zD$4wQVtrPioJ`{47xLBffvwHTIaxK;W|;-^9ZmNTq7O-wr!2C^nOPk%IA5mH5VjO} zs0}ic18rL|vE+I5;C_Wto&3$P8;ewdw_bymtfaT(T90?X)UiIZm*4^Hg?q>SX8??k!KOij&Z1owwDyw|a zL4nE(Hn-^67@63{{RvOATY_O6jK=xz&{RJA7gU*47`X{MMZ|SVdg(-Ukn7Xdd_C$H z!R9_#KW98C{LXA?63;3_LDB6^J3%PvoIjVcxWaRW&`}s4LueZVg98-RJJoL1CNe_e z=jJ+eh}B*leE4GY`3M_iURjTP4v>6p&Y4}47ze1?WPL>b^#g4F&a=%FT`5a%%Z*#M zu(5%Pcq^rx6)~#9U$ptJGLD{VX-5P*C^iE) zBT?qbgN99AO&I)4#AOe_GDfyFLiRKIwxkM!{4#tox8q zjXCYocjEb_MQVQ8i7N|`5;zLe-Pi!nH4rvLxwkkl!mP-bGNCScao#SbG#q4LHA?f| z*sZHh%(^nJ!_P5{00oK1o$TLHDBcSg8Lm|jnWeIu45pe- zR~OXX522$cm(Ap&aL(r9856HR3p}?BF>96;;Ju(dWO(_p%g?~ClaLDB^#Hmh1l5bTy^@j?PE2$Kl z(|&OR!xd&_Zs#t$zoW?!lEWL}nOK}JK2NPxS$-{<_KW{5F-+t1>0uZ0b&GFb*vlMR z!ImLep-D6(F1>UNwB`YwzNb*IsHa2YY=HoHFl|j~qS8=R2)!fV;aQNiq3z)@S6y$= zkFmb7#IlCy9bvG<{7u@{Ki(YJ;WC%t7!Ab~w`*uM_IhLJF=QIX=(|Smr93HJkq9Z> zVO1P&@~_I0*&awGAQ*2Sc2qd<>wbg!!okrv(>d$CZ^|BXRLhnBPR01i8<6NN@1%ze z%oT2;SEtBtJW5+rtk_d8h8Z*|ruDB|!?|jHq37hBCdT1{qSjre5br8e(o`7 z!edP8M=^J%|H0-=E4}hR~=eC2b!zBooF@%1&;FSj%X|yL0vav;n>8 z3odn09p!|rw?54mDu2uF_n!Y!25-FtO4y1>4Y!>YWOA=~_n>l2;5z5V2}2BQ%ue+Ll+NSYiqo)m3OHYr1!o>X_YjvS;+Bp$Z932-Kl zkJ_f19d1Mqxyn^jZ?G^wYICHbZ8D6qOE_pnq5~27+}sV;|9x(6Or>fP z1A(Sn=?DnNN)YhF=dkuEXgyaxT_w}d&@`GASpNmmuUZIJ2%e{2O}8>d(=ga*%9X|> z5r|Otix?pl@l7o8gg11Ca^|!ey?%x<;I{{KA0%;xS6bRzR{AC1p|X6{%?_|RU6&H{ z=B*B3fA*<9iJRObE%@f?45x#_PC8xc5pg4EBgA<>Mg0)WI@tmrg%i?@>ZUa1F7i{~i@H{IMZ=Oi%OKi*^mx5L*=a)Tb8 z-1qZ#hMBSf)<1z2*{kpRiTc6$t_u6$Fr&l_ZS1ufpUr?WNtLSdBbnhy@O#nCGwEvJ zTd5q;PnE_p9GMLft@yF{JpF!?Vkrkn^aCG^8@0#mc~7?-RW9ui^@#ue%{3_Oxtv-R#F{4ovd0yKxJK9P@Xxzfdg-gyKKVlAND)0tzj_d_H@Lo zTGQNs-F;FlK?aoAr)qJ>@fez+N-@o{Ak6#uu86R4B>=F17I%A#cth*-8e&b#DrcEZ zfrAQisQ9X&d8QeUIT1FE9(1<0mWr5S&7;nf`IWpbX}C9a0P8><$qeQ_Kq+8ksI!zt zF>Zr!sTe+Qr{69QfDSuICKyeVu*WHT8QC0r4Y;EswdOMm!VkH8uTVKEg(4~w;@AAj zx_Lvyd9{04dqx$$qdv6^A6i3l!=b{O+6$j;vV*D`1P5zd?yk9hxNw!f>a3$)k zqdD6xS1FfcSz|O4%g5vSLB6H|+x+XfI~+D3S!5}mWMAGxrsjy!ov|5X zaD*>2{sRJHcNl+qG{$F_&c9xOQ1fr4-<9{}rd8V2AAa||HJUhN^4?80?&h?KmCeWm za82oPi*|x}wpg`O$78p7`U9gcul3Gl3yJo`Gbq3C9ucr2Arc!1%CgeNYjnG#%$&IG z(hOg$3JDBbmAM5K6;RH;qwrwYcajMSs*5*Y!ga_u>0RPYWo|NtbIN`BOYRn9z`Nfs z;{IJ&Bt5?kHYQ|&jwnUeh8*&prqLPu7JH~yo3Gezk#epgRNk@XY+~eBWnZP&sefUu zs5fq*l4l*Y+w3eML|oG36K?UOyGpTLWQGdD_c=eCk=~_E$!!V2QC8ioaCibyVRngA zJcKll_~;V|nW5cG3*H+lSM6U{bgRHjdtZv7 zMku<9zg<8ZmVICpdgLweVN0of+*jn2&^KDhu%+%V@76U)`rkdLVWRATx2V3EJ>AwV zL}ja8W%gFFL%&0ZXaE}z*0Y`BQDj>U5lm0EYZ*%^Ffozw5G zT#E7f2zsrB6Mvc9GnD&Wpk-65^Fx*d!Vs|7Mx(xA%MT9f2R<=K^ylOO@po2u+Jyo2RUM=Yln;xFgi zqzPgbI0;=ZH&#Z&L%)A`j}?&0_dHNjO3)HuQ9NpS|6tNs*t>CRdvTkZ>A(>gZeetOMeJEj;$?igwyfU+RI@KaVzbUUqCeo#&eb zZ&xqC4Q)L8Toyd1Ny?Xt=#9Xa73ZMs>JXHj;OBWh(G_6~qT2Y*vwyC|TlVr109vr4 z{_Wp84c^+M@d+sxSFe+ucJ;h2xoB?~$4Tzq{TzVy?mlc`Md8fP;|Gc>E3ILGuy)P- zFuKsYgsyX`S%|hI$iR|V^%YxKq=cwy6Bqu z{&7@r_ajeNMxExefb-v>H(tTsk}DirzB(_GHHomzpHRd~abk?oHCw zkVH*o?oxFb0(XWxE9WpD5UX!8d?UaWm|qa`l%DL;bxlp`+b>QdatXu;?C;H8B6LPd zOU%rH4pUqfG4evizLDvH1xDb?d_TZ@o46u34enc zJ~0bciVG7h=(7#2!R0$vIKP~mEX`9Sb!U8;E8z)0Iar$g7r{(Hj~wuCDw8GtTh*-m zNscNizTNK87xSV0s@9!_O}*ifIcokWrq+M9Q8L41-19hea%K_mNzXWxNWH&)zBmO! zr+62ty9V)UCAcg?B@5IUS@2qwM&7lBV~`KikZsMv;#g;}wBx=_)XJ?5=7e*4?l{oy z{#m;>Pv^L|Qq@mcl;+7JvW{C7;YO+H1P`SPX9!PGpbvEg5BPv6k!sVzJlVx<2rojM zYe+N?x8UhflaQryhx^7#*HAOokO{Nycj_B2p5$RuP=k%x}aOYuS!YEx9;X% zODK%U$~ivK9Hg~Y`Xh2mjCNW6=?=rg*yX>2GuRf(@OODV(-!UUPEF8QXMY=9peF?u zb2W!2?W@Y^w?7k_fh)85Pu1BC%Q#sNJ~z0nB|@9;?=GTGU(R)7;Pv=G!Pl{;uYYtm z9$tSpP!Uk!J%^E#MsG*3?t5)UBo$$g5b-Bq6_fiH`d`QzY<)NYJ+w)Ys(2cAP3%RZ zfOypAWGd+NrcV?Ozs%)){afG@c81_^+=>9_%6T*27xTHF9IvWU`~(RVf9PS)Ir#2u zqqbZOl{&_Eq2nVFc9e#zu4lgo^(VzL`@>-&-I-YZkLYKe)K2tF)ZWFD0$M@UwC_~X z&S-Tm0?dydX2=Aa>_hk|m&St={(Td|NjQ2v+}pnGx}V<1d46~dzSSiI>{Jue9$vEl zws8g)u&L=(u3=I3Xs4)lI@i{>x030|CH9bObi*$(J)d_gM*I0XNn}(8tK#Ub6B#aph)+44OY9 z6}Cw^004ibAT6O4y<%XW14+rDGK8moJ2r+(N!q&+D4#|tO6X6&?*q?6T2hA1jS0yS zs9YFhD3FJ`o;QMBAE`DT&rfYocB+sU0s&?*@A;@V9w)`>c2#a?LAiKcyStX&Cf@l# zT^U{8-5j>ee_o__UZS`TCvGvyNjm(ivP(~-7VH=+ZnZy!xLuK=XfYVnrazeGjOhyh zJ;=&I9Ad+v+<2Hs-*_C4?>yc#H;1k=?6h^g9*MoAVcD!nWqiJ2-Xp6aV)Gg(A^N|ZIVR)38}7J5`aFfbiIa<#Yc2K zJ-=fOSRLO@j6}BdA`8SSyZ9^V83~JD;9!gPzu?p3fuxqYSq@+nrD!;cMrY zKlxNaUuAa&O`dYYcScWt_Kv8gvUfdQoCd#kum?$PpWjypxrm8=0fa{0%qaef9g6RK zBqL2??;J#D71HXw+lr=oz2n<>UZb)R4thL%^AD6SzCWSOXs)yu<&#~{gJMsAy}KUQ zy!Fg1BSubxZVSbxJf6;|N#h?&<3VueEV!hJ8=_a`R4-?jlVY#DZw!O)pDy2~x;W1_ zD8|AkeR2uVsm@k|=zz<`u9qI1pvRR6h?(W!h;#5+hzYj`Vu;)n)CXureE;&M>?&FK zI*_GA$!cRitXU7lut7ni3mdqCDeh4L(Kl0uzah3j&u8p&+`5TpC9lx6l7GTt0Yao{2%a3 Bx{Ck+ literal 0 HcmV?d00001 diff --git a/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt b/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt index 426ac14e0e..c437f13f27 100644 --- a/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt +++ b/tangemserver-android/src/main/java/com/tangem/tangemserver/android/data/LocalStorage.kt @@ -67,6 +67,8 @@ class LocalStorage putResourceArtworkToCatalog(R.drawable.card_ru028, true) putResourceArtworkToCatalog(R.drawable.card_ru029, true) putResourceArtworkToCatalog(R.drawable.card_ru030, true) + putResourceArtworkToCatalog(R.drawable.card_ru031, true) + putResourceArtworkToCatalog(R.drawable.card_ru032, true) } if (batchesFile.exists()) { try { @@ -226,6 +228,8 @@ class LocalStorage card.batch == "001F" -> R.drawable.card_ru028 card.batch == "0018" -> R.drawable.card_ru029 card.batch == "0020" -> R.drawable.card_ru030 + card.batch == "0021" -> R.drawable.card_ru031 + card.batch == "0022" -> R.drawable.card_ru032 else -> null } From c01895f6abcdc51c2c732b3c453cb228acf5dd07 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 27 Dec 2018 14:15:54 +0300 Subject: [PATCH 003/228] Updated on 2026-08-14 --- .../presentation/activity/MainActivity.kt | 3 --- .../activity/SignPaymentActivity.kt | 21 ++++++++++++++++--- .../dialog/WaitSecurityDelayDialogNew.kt | 12 +++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt index 28c4231e80..2978ee8a4f 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt @@ -85,9 +85,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) -// val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() -// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) - App.getNavigatorComponent().inject(this) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT diff --git a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt index 0763437609..3cac8e6a19 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt @@ -20,12 +20,14 @@ import com.tangem.domain.wallet.CoinEngineFactory import com.tangem.domain.wallet.TangemContext import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog import com.tangem.presentation.dialog.WaitSecurityDelayDialog +import com.tangem.presentation.dialog.WaitSecurityDelayDialogNew import com.tangem.tangemcard.reader.CardProtocol import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.tangemcard.android.reader.NfcReader import com.tangem.tangemcard.data.asBundle import com.tangem.tangemcard.tasks.SignTask import com.tangem.tangemcard.util.Util +import com.tangem.util.LOG import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_sign_payment.* @@ -252,16 +254,29 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card }, 500) } - override fun onReadWait(msec: Int) { - WaitSecurityDelayDialog.onReadWait(this, msec) - } + private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() override fun onReadBeforeRequest(timeout: Int) { + LOG.i(TAG, "onReadBeforeRequest timeout $timeout") WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout) + } override fun onReadAfterRequest() { + LOG.i(TAG, "onReadAfterRequest") WaitSecurityDelayDialog.onReadAfterRequest(this) + } + override fun onReadWait(msec: Int) { + LOG.i(TAG, "onReadWait msec $msec") + WaitSecurityDelayDialog.onReadWait(this, msec) + + + } + + + + + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt index c400be56a8..0b08c8e5ea 100644 --- a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt +++ b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.support.v7.app.AppCompatDialogFragment import android.widget.ProgressBar import com.tangem.wallet.R -import kotlinx.android.synthetic.main.dialog_wait_pin2.* import java.util.* class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { @@ -31,11 +30,8 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { val inflater = activity!!.layoutInflater val v = inflater.inflate(R.layout.dialog_wait_pin2, null) - isCancelable = false - pb = v.findViewById(R.id.progressBar) - pb.max = msTimeout pb.progress = msProgress @@ -67,7 +63,8 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { override fun run() { setup(timeout, DELAY_BEFORE_SHOW_DIALOG) isCancelable = false - show(activity?.supportFragmentManager, TAG) +// if (!isVisible) + show(activity?.supportFragmentManager, TAG) } }, DELAY_BEFORE_SHOW_DIALOG.toLong()) @@ -96,7 +93,8 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { // 1000ms - card delay notification interval setup(msec + 1000, 1000) isCancelable = false - show(activity?.supportFragmentManager, TAG) +// if (!isVisible) + show(activity?.supportFragmentManager, TAG) } else setRemainingTimeout(msec) @@ -119,7 +117,7 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { } else { val newProgress = pb.max - msec if (pb.max > progress) - progressBar.progress = newProgress + pb.progress = newProgress else pb.max = progress + msec } From aac014439b50d9d4bf71c947facad283c092b663 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 27 Dec 2018 16:19:48 +0300 Subject: [PATCH 004/228] Updated on 2026-08-14 --- .../activity/SignPaymentActivity.kt | 35 +++++-- .../dialog/WaitSecurityDelayDialogNew.kt | 97 ++++++++++++++++--- .../presentation/event/ReadAfterRequest.kt | 4 + .../presentation/event/ReadBeforeRequest.kt | 5 + .../com/tangem/presentation/event/ReadWait.kt | 5 + 5 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/tangem/presentation/event/ReadAfterRequest.kt create mode 100644 app/src/main/java/com/tangem/presentation/event/ReadBeforeRequest.kt create mode 100644 app/src/main/java/com/tangem/presentation/event/ReadWait.kt diff --git a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt index 3cac8e6a19..6b43c44410 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt @@ -21,6 +21,10 @@ import com.tangem.domain.wallet.TangemContext import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog import com.tangem.presentation.dialog.WaitSecurityDelayDialog import com.tangem.presentation.dialog.WaitSecurityDelayDialogNew +import com.tangem.presentation.event.ReadAfterRequest +import com.tangem.presentation.event.ReadBeforeRequest +import com.tangem.presentation.event.ReadWait +import com.tangem.presentation.event.TransactionFinishWithError import com.tangem.tangemcard.reader.CardProtocol import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.tangemcard.android.reader.NfcReader @@ -30,6 +34,7 @@ import com.tangem.tangemcard.util.Util import com.tangem.util.LOG import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_sign_payment.* +import org.greenrobot.eventbus.EventBus class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { @@ -257,26 +262,38 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() override fun onReadBeforeRequest(timeout: Int) { - LOG.i(TAG, "onReadBeforeRequest timeout $timeout") - WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout) +// LOG.i(TAG, "onReadBeforeRequest timeout $timeout") +// WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout) + + if (!waitSecurityDelayDialogNew.isAdded) + waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) + + + val readBeforeRequest = ReadBeforeRequest() + readBeforeRequest.timeout = timeout + EventBus.getDefault().post(readBeforeRequest) } override fun onReadAfterRequest() { - LOG.i(TAG, "onReadAfterRequest") - WaitSecurityDelayDialog.onReadAfterRequest(this) +// LOG.i(TAG, "onReadAfterRequest") +// WaitSecurityDelayDialog.onReadAfterRequest(this) + + val readAfterRequest = ReadAfterRequest() + EventBus.getDefault().post(readAfterRequest) } override fun onReadWait(msec: Int) { - LOG.i(TAG, "onReadWait msec $msec") - WaitSecurityDelayDialog.onReadWait(this, msec) +// LOG.i(TAG, "onReadWait msec $msec") +// WaitSecurityDelayDialog.onReadWait(this, msec) + + val readWait = ReadWait() + readWait.msec = msec + EventBus.getDefault().post(readWait) } - - - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt index 0b08c8e5ea..4e44beb311 100644 --- a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt +++ b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt @@ -6,7 +6,15 @@ import android.app.Dialog import android.os.Bundle import android.support.v7.app.AppCompatDialogFragment import android.widget.ProgressBar +import com.tangem.presentation.activity.SignPaymentActivity +import com.tangem.presentation.event.ReadAfterRequest +import com.tangem.presentation.event.ReadBeforeRequest +import com.tangem.presentation.event.ReadWait +import com.tangem.presentation.event.TransactionFinishWithSuccess +import com.tangem.util.LOG import com.tangem.wallet.R +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe import java.util.* class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { @@ -54,23 +62,38 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { .create() } - fun onReadBeforeRequest(timeout: Int) { - if (timerToShowDelayDialog != null || timeout < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + super.onStop() + EventBus.getDefault().unregister(this) + } + + @Subscribe + fun readBeforeRequest(readBeforeRequest: ReadBeforeRequest) { + LOG.i(TAG, "readBeforeRequest 111") + + if (timerToShowDelayDialog != null || readBeforeRequest.timeout!! < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) return timerToShowDelayDialog = Timer() timerToShowDelayDialog!!.schedule(object : TimerTask() { override fun run() { - setup(timeout, DELAY_BEFORE_SHOW_DIALOG) + setup(readBeforeRequest.timeout!!, DELAY_BEFORE_SHOW_DIALOG) isCancelable = false -// if (!isVisible) + if (!isAdded) show(activity?.supportFragmentManager, TAG) } }, DELAY_BEFORE_SHOW_DIALOG.toLong()) - } - fun onReadAfterRequest() { + @Subscribe + fun readAfterRequest(readAfterRequest: ReadAfterRequest) { + LOG.i(TAG, "readAfterRequest 222") + if (timerToShowDelayDialog == null) return @@ -78,26 +101,74 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { timerToShowDelayDialog = null } - fun onReadWait(msec: Int) { + @Subscribe + fun readWait(readWait: ReadWait) { + LOG.i(TAG, "readWait 333") + if (timerToShowDelayDialog != null) { timerToShowDelayDialog!!.cancel() timerToShowDelayDialog = null } - if (msec == 0) { + if (readWait.msec == 0) { dismiss() return } - if (msec > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { + if (readWait.msec!! > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { // 1000ms - card delay notification interval - setup(msec + 1000, 1000) + setup(readWait.msec!! + 1000, 1000) isCancelable = false -// if (!isVisible) - show(activity?.supportFragmentManager, TAG) +// if (!isAdded) +// show(activity?.supportFragmentManager, TAG) } else - setRemainingTimeout(msec) + setRemainingTimeout(readWait.msec!!) + } + + fun onReadBeforeRequest(timeout: Int) { +// if (timerToShowDelayDialog != null || timeout < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) +// return +// +// timerToShowDelayDialog = Timer() +// timerToShowDelayDialog!!.schedule(object : TimerTask() { +// override fun run() { +// setup(timeout, DELAY_BEFORE_SHOW_DIALOG) +// isCancelable = false +// if (!isVisible) +// show(activity?.supportFragmentManager, TAG) +// } +// }, DELAY_BEFORE_SHOW_DIALOG.toLong()) + } + + fun onReadAfterRequest() { +// if (timerToShowDelayDialog == null) +// return +// +// timerToShowDelayDialog!!.cancel() +// timerToShowDelayDialog = null + } + + fun onReadWait(msec: Int) { +// if (timerToShowDelayDialog != null) { +// timerToShowDelayDialog!!.cancel() +// timerToShowDelayDialog = null +// } +// +// if (msec == 0) { +// dismiss() +// return +// } +// +// if (msec > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { +// // 1000ms - card delay notification interval +// setup(msec + 1000, 1000) +// isCancelable = false +//// if (!isVisible) +// show(activity?.supportFragmentManager, TAG) +// +// } else +// setRemainingTimeout(msec) } private fun setup(msTimeout: Int, msProgress: Int) { diff --git a/app/src/main/java/com/tangem/presentation/event/ReadAfterRequest.kt b/app/src/main/java/com/tangem/presentation/event/ReadAfterRequest.kt new file mode 100644 index 0000000000..a5b3398ec2 --- /dev/null +++ b/app/src/main/java/com/tangem/presentation/event/ReadAfterRequest.kt @@ -0,0 +1,4 @@ +package com.tangem.presentation.event + +class ReadAfterRequest { +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/event/ReadBeforeRequest.kt b/app/src/main/java/com/tangem/presentation/event/ReadBeforeRequest.kt new file mode 100644 index 0000000000..e9d1690bd5 --- /dev/null +++ b/app/src/main/java/com/tangem/presentation/event/ReadBeforeRequest.kt @@ -0,0 +1,5 @@ +package com.tangem.presentation.event + +class ReadBeforeRequest { + var timeout: Int? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/event/ReadWait.kt b/app/src/main/java/com/tangem/presentation/event/ReadWait.kt new file mode 100644 index 0000000000..3b35bf479b --- /dev/null +++ b/app/src/main/java/com/tangem/presentation/event/ReadWait.kt @@ -0,0 +1,5 @@ +package com.tangem.presentation.event + +class ReadWait { + var msec: Int? = null +} \ No newline at end of file From 07aaf3db477e609cb7f90c0321fc9f99fedf75d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 29 Dec 2018 13:47:59 +0300 Subject: [PATCH 005/228] Updated on 2026-08-14 --- .../tangem/data/network/ServerApiInsight.java | 6 +- .../data/network/model/InsightResponse.kt | 10 +- .../domain/wallet/ducatus/DucatusEngine.java | 714 ++++++++++++++++++ 3 files changed, 726 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java index e43efdb6d9..f7b54a8e52 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java @@ -3,7 +3,6 @@ package com.tangem.data.network; import android.support.annotation.NonNull; import android.util.Log; -import com.tangem.App; import com.tangem.data.network.model.InsightBody; import com.tangem.data.network.model.InsightResponse; @@ -24,6 +23,8 @@ public class ServerApiInsight { private int requestsCount=0; + public String lastNode; + public boolean isRequestsSequenceCompleted() { Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); return requestsCount <= 0; @@ -44,8 +45,9 @@ public class ServerApiInsight { public void insight(String method, String wallet, String tx, int nbBlocks) { requestsCount++; String insightURL = "http://130.185.109.17:3001/insight-api"; //TODO: make random selection + this.lastNode = insightURL; //TODO: show node instead of URL - Retrofit retrofitInsight = new Retrofit.Builder() //TODO: check + Retrofit retrofitInsight = new Retrofit.Builder() //TODO: move to NetworkModule+NetworkComponent if possible .baseUrl(insightURL) .addConverterFactory(GsonConverterFactory.create()) .build(); diff --git a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt index 8ac109c54a..3071e94c2d 100644 --- a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt @@ -9,8 +9,14 @@ data class InsightResponse( @SerializedName("id") var id: Int? = null, - @SerializedName("result") - var result: String = "", + @SerializedName("balanceSat") + var balanceSat: Long = null, + + @SerializedName("unconfirmedBalanceSat") + var unconfirmedBalanceSat: Long = null, + + @SerializedName("addrStr") + var addrStr: String = "", @SerializedName("error") var error: String = "" diff --git a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java new file mode 100644 index 0000000000..18262dcd2b --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java @@ -0,0 +1,714 @@ +package com.tangem.domain.wallet.ducatus; + +import android.net.Uri; +import android.text.InputFilter; +import android.util.Log; + +import com.tangem.data.network.ElectrumRequest; +import com.tangem.data.network.ServerApiInsight; +import com.tangem.data.network.model.InsightResponse; +import com.tangem.domain.wallet.BTCUtils; +import com.tangem.domain.wallet.BalanceValidator; +import com.tangem.domain.wallet.Base58; +import com.tangem.domain.wallet.CoinData; +import com.tangem.domain.wallet.TangemContext; +import com.tangem.domain.wallet.Transaction; +import com.tangem.domain.wallet.UnspentOutputInfo; +import com.tangem.domain.wallet.btc.BtcData; +import com.tangem.domain.wallet.btc.BtcEngine; +import com.tangem.tangemcard.data.TangemCard; +import com.tangem.tangemcard.reader.CardProtocol; +import com.tangem.tangemcard.tasks.SignTask; +import com.tangem.tangemcard.util.Util; +import com.tangem.util.CryptoUtil; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.util.DerEncodingUtil; +import com.tangem.wallet.R; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class DucatusEngine extends BtcEngine { + private static final String TAG = DucatusEngine.class.getSimpleName(); + public BtcData coinData = null; + + public DucatusEngine(TangemContext context) throws Exception { + super(context); + if (context.getCoinData() == null) { + coinData = new BtcData(); + context.setCoinData(coinData); + } else if (context.getCoinData() instanceof BtcData) { + coinData = (BtcData) context.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for LtcEngine"); + } + } + + public DucatusEngine() { + super(); + } + + private static int getDecimals() { + return 8; + } + + + private void checkBlockchainDataExists() throws Exception { + if (coinData == null) throw new Exception("No blockchain data"); + } + + @Override + public boolean awaitingConfirmation() { + if (coinData == null) return false; + return coinData.getBalanceUnconfirmed() != 0; + } + + @Override + public String getBalanceHTML() { + Amount balance = getBalance(); + if (balance != null) { + return balance.toDescriptionString(getDecimals()); + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + return "LTC"; + } + + @Override + public String getOfflineBalanceHTML() { + InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); + Amount offlineAmount = convertToAmount(offlineInternalAmount); + return offlineAmount.toDescriptionString(getDecimals()); + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getBalanceInInternalUnits() == null) return false; + return coinData.getBalanceInInternalUnits().notZero(); + } + + @Override + public boolean hasBalanceInfo() { + if (coinData == null) return false; + return coinData.hasBalanceInfo(); + } + + + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.cannot_obtain_data_from_blockchain); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.please_wait_while_previous); + } else if (coinData.getUnspentTransactions().size() == 0) { + ctx.setMessage(R.string.please_wait_for_confirmation); + } else { + return true; + } + return false; + } + + @Override + public String getFeeCurrency() { + return "LTC"; + } + + @Override + public boolean validateAddress(String address) { + if (address == null || address.isEmpty()) { + return false; + } + + if (address.length() < 25) { + return false; + } + + if (address.length() > 35) { + return false; + } + + if (!address.startsWith("L") && !address.startsWith("M")) { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if (decAddress == null || decAddress.length == 0) { + return false; + } + + byte[] rip = new byte[21]; + for (int i = 0; i < 21; ++i) { + rip[i] = decAddress[i]; + } + + byte[] kcv = CryptoUtil.doubleSha256(rip); + + for (int i = 0; i < 4; ++i) { + if (kcv[i] != decAddress[21 + i]) + return false; + } + + return true; + } + + + @Override + public boolean isNeedCheckNode() { + return true; + } + + @Override + public Uri getShareWalletUriExplorer() { + return Uri.parse("https://live.blockcypher.com/ltc/address/" + ctx.getCoinData().getWallet()); + } + + @Override + public Uri getShareWalletUri() { + if (ctx.getCard().getDenomination() != null) { + return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + } else { + return Uri.parse("litecoin:" + ctx.getCoinData().getWallet()); + } + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (coinData == null) return false; + if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { + InternalAmount fee; + InternalAmount amount; + + try { + checkBlockchainDataExists(); + amount = convertToInternalAmount(amountValue); + fee = convertToInternalAmount(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if (fee == null || amount == null) + return false; + + if (fee.isZero() || amount.isZero()) + return false; + + if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0)) + return false; + + if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0) + return false; + + return true; + } + + @Override + public boolean validateBalance(BalanceValidator balanceValidator) { + if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Unknown balance"); + balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + return false; + } + + // Workaround before new back-end +// if (card.getRemainingSignatures() == card.getMaxSignatures()) { +// firstLine = "Verified balance"; +// secondLine = "Balance confirmed in blockchain. "; +// secondLine += "Verified note identity. "; +// return; +// } + + if (coinData.getBalanceUnconfirmed() != 0) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Transaction in progress"); + balanceValidator.setSecondLine("Wait for confirmation in blockchain"); + return false; + } + + if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { + balanceValidator.setScore(100); + balanceValidator.setFirstLine("Verified balance"); + balanceValidator.setSecondLine("Balance confirmed in blockchain"); + if (coinData.getBalanceInInternalUnits().isZero()) { + balanceValidator.setFirstLine("Empty wallet"); + balanceValidator.setSecondLine(""); + } + } + + // rule 4 TODO: need to check SignedHashed against number of outputs in blockchain +// if((card.getRemainingSignatures() != card.getMaxSignatures()) && card.getBalance() != 0) +// { +// score = 80; +// firstLine = "Unguaranteed balance"; +// secondLine = "Potential unsent transaction. Redeem immediately if accept. "; +// return; +// } + + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine("Verified offline balance"); + balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + } + +// if(card.getFailedBalanceRequestCounter()!=0) { +// score -= 5 * card.getFailedBalanceRequestCounter(); +// secondLine += "Not all nodes have returned balance. Swipe down or tap again. "; +// if(score <= 0) +// return; +// } + + // +// if(card.isBalanceReceived() && !card.isBalanceEqual()) { +// score = 0; +// firstLine = "Disputed balance"; +// secondLine += " Cannot obtain trusted balance at the moment. Try to tap and check this banknote later."; +// return; +// } + + return true; + } + + @Override + public Amount getBalance() { + if (!hasBalanceInfo()) return null; + return convertToAmount(coinData.getBalanceInInternalUnits()); + } + + @Override + public String evaluateFeeEquivalent(String fee) { + if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; + try { + Amount feeAmount = new Amount(fee, getFeeCurrency()); + return feeAmount.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + return ""; + } + } + + @Override + public String getBalanceEquivalent() { + if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException { + byte netSelectionByte = (byte) 0x30; + + byte hash1[] = Util.calculateSHA256(pkUncompressed); + byte hash2[] = Util.calculateRIPEMD160(hash1); + + ByteBuffer BB = ByteBuffer.allocate(hash2.length + 1); + + BB.put(netSelectionByte); + BB.put(hash2); + + byte hash3[] = Util.calculateSHA256(BB.array()); + byte hash4[] = Util.calculateSHA256(hash3); + + BB = ByteBuffer.allocate(hash2.length + 5); + BB.put(netSelectionByte); //BB.put((byte) 0x6f); + BB.put(hash2); + BB.put(hash4[0]); + BB.put(hash4[1]); + BB.put(hash4[2]); + BB.put(hash4[3]); + + return org.bitcoinj.core.Base58.encode(BB.array()); + } + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + BigDecimal d = internalAmount.divide(new BigDecimal("100000000")); + return new Amount(d, getBalanceCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) { + BigDecimal d = amount.multiply(new BigDecimal("100000000")); + return new InternalAmount(d, "Satoshi"); + } + + @Override + public InternalAmount convertToInternalAmount(byte[] bytes) { + if (bytes == null) return null; + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return new InternalAmount(Util.byteArrayToLong(reversed), "Satoshi"); + } + + @Override + public byte[] convertToByteArray(InternalAmount internalAmount) { + byte[] bytes = Util.longToByteArray(internalAmount.longValueExact()); + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return reversed; + } + + @Override + public CoinData createCoinData() { + return new BtcData(); + } + + @Override + public String getUnspentInputsDescription() { + return coinData.getUnspentInputsDescription(); + } + + @Override + public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + final ArrayList unspentOutputs; + checkBlockchainDataExists(); + + String myAddress = ctx.getCoinData().getWallet(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + + // Build script for our address + List rawTxList = coinData.getUnspentTransactions(); + byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes; + + // Collect unspent + unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend); + + long fullAmount = 0; + for (int i = 0; i < unspentOutputs.size(); ++i) { + fullAmount += unspentOutputs.get(i).value; + } + + long fees = convertToInternalAmount(feeValue).longValueExact(); + long amount = convertToInternalAmount(amountValue).longValueExact(); + long change = fullAmount - amount; + if (IncFee) { + amount = amount - fees; + } else { + change = change - fees; + } + + final long amountFinal=amount; + final long changeFinal=change; + + if (amount + fees > fullAmount) { + throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); + } + + final byte[][] txForSign = new byte[unspentOutputs.size()][]; + final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][]; + final byte[][] bodyHash= new byte[unspentOutputs.size()][]; + + for (int i = 0; i < unspentOutputs.size(); ++i) { + txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change); + bodyHash[i] = Util.calculateSHA256(txForSign[i]); + bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); + } + + return new SignTask.PaymentToSign() { + + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw; + } + + @Override + public byte[][] getHashesToSign() throws Exception { + byte[][] dataForSign=new byte[unspentOutputs.size()][]; + if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!"); + for (int i = 0; i < unspentOutputs.size(); ++i) { + dataForSign[i] = bodyDoubleHash[i]; + } + return dataForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + for (int i = 0; i < txForSign.length; i++) { + if (i != 0 && txForSign[0].length != txForSign[i].length) + throw new Exception("Hashes length must be identical!"); + bs.write(txForSign[i]); + } + + return bs.toByteArray(); + } + + @Override + public String getHashAlgToSign() { + return "sha-256x2"; + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Issuer validation not supported!"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + for (int i = 0; i < unspentOutputs.size(); ++i) { + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + s = CryptoUtil.toCanonicalised(s); + + unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey); + } + + byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); + notifyOnNeedSendPayment(txForSend); + return txForSend; + } + }; + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiInsight serverApiInsight = new ServerApiInsight(); + + ServerApiInsight.InsightBodyListener insightBodyListenerListener = new ServerApiInsight.InsightBodyListener() { + public void onSuccess(String method, InsightResponse insightResponse) { + switch (method) { + case ServerApiInsight.INSIGHT_ADDRESS: { + try { + String walletAddress = insightResponse.getAddrStr(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(insightResponse.getBalanceSat()); + coinData.setBalanceUnconfirmed(insightResponse.getUnconfirmedBalanceSat()); + // Log.i("$TAG eth_get_balance", balanceCap) + } + catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL INSIGHT_ADDRESS Exception"); + } + } + break; + + case ServerApiInsight.INFURA_ETH_GET_TRANSACTION_COUNT: { + String nonce = insightResponse.getResult(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + coinData.setConfirmedTXCount(count); + + +// Log.i("$TAG eth_getTransCount", nonce) + } + break; + + case ServerApiInsight.INFURA_ETH_GET_PENDING_COUNT: { + String pending = insightResponse.getResult(); + pending = pending.substring(2); + BigInteger count = new BigInteger(pending, 16); + coinData.setUnconfirmedTXCount(count); + +// Log.i("$TAG eth_getPendingTxCount", pending) + } + break; + } + + if (serverApiInfura.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + Log.i(TAG, "onSuccess: "+electrumRequest.getMethod()); + if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) { + try { + String walletAddress = electrumRequest.getParams().getString(0); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + Long confBalance = electrumRequest.getResult().getLong("confirmed"); + Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed"); + coinData.setBalanceReceived(true); + coinData.setBalanceConfirmed(confBalance); + coinData.setBalanceUnconfirmed(unconfirmedBalance); + coinData.setValidationNodeDescription(serverApiInsight.lastNode); + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_GetBalance Exception"); + } + } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { + try { + String walletAddress = electrumRequest.getParams().getString(0); + JSONArray jsUnspentArray = electrumRequest.getResultArray(); + try { + coinData.getUnspentTransactions().clear(); + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = jsUnspent.getString("tx_hash"); + trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Height = jsUnspent.getInt("height"); + coinData.getUnspentTransactions().add(trUnspent); + } + } catch (JSONException e) { + e.printStackTrace(); + Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); + } + + for (int i = 0; i < jsUnspentArray.length(); i++) { + JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); + Integer height = jsUnspent.getInt("height"); + String hash = jsUnspent.getString("tx_hash"); + if (height != -1) { + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiInsight.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); + } else { + ctx.setError("Terminated by user"); + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { + try { + String txHash = electrumRequest.txHash; + String raw = electrumRequest.getResultString(); + for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { + if (tx.txID.equals(txHash)) + tx.Raw = raw; + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + if (serverApiInsight.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + }else{ + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError()); + ctx.setError(electrumRequest.getError()); + if (serverApiInsight.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false);//serverApiInsight// .isErrorOccurred(), serverApiInsight + //.getError()); + }else{ + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApiInsight.setElectrumRequestData(electrumListener); + + serverApiInsight.electrumRequestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); + serverApiInsight.electrumRequestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); + } + + private final static BigDecimal relayFee = new BigDecimal(0.00001); + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { + final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString()); + Log.e(TAG, String.format("Estimated tx size %d", calcSize)); + coinData.minFee=null; + coinData.maxFee=null; + coinData.normalFee=null; + + final ServerApiInsight serverApiInsight = new ServerApiInsight(); + + final ServerApiInsight.InsightBodyListener electrumListener = new ServerApiInsight.InsightBodyListener () { + @Override + public void onSuccess(ElectrumRequest electrumRequest) { + BigDecimal fee; + if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) { + try { + fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB + + if (fee.equals(BigDecimal.ZERO)) { + serverApiInsight.electrumRequestData(ctx, ElectrumRequest.getFee()); + } + +// if (calcSize != 0) { + fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size +// } else { +// serverApiInsight +//.electrumRequestData(ctx, ElectrumRequest.getFee()); +// } + + //compare fee to usual relay fee + if (fee.compareTo(relayFee) < 0) { + fee = relayFee; + } + fee = fee.setScale(8, RoundingMode.DOWN); + + Amount feeAmount = new Amount(fee, ctx.getBlockchain().getCurrency()); + coinData.minFee = feeAmount; + coinData.normalFee = feeAmount; + coinData.maxFee = feeAmount; +// if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) { + blockchainRequestsCallbacks.onComplete(true); +// } else { +// blockchainRequestsCallbacks.onProgress(); +// } + + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onFail(ElectrumRequest electrumRequest) { + ctx.setError(electrumRequest.getError()); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiInsight.setElectrumRequestData(electrumListener); + + serverApiInsight.electrumRequestData(ctx, ElectrumRequest.getFee()); + } +} \ No newline at end of file From 60fb1b56a33ae75450d2e418492b6ecd8378dc86 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 6 Jan 2019 20:40:20 +0300 Subject: [PATCH 006/228] Updated on 2026-08-14 --- .../com/tangem/data/network/InsightApi.java | 6 +- .../tangem/data/network/ServerApiInsight.java | 121 +++++---- .../data/network/model/InsightResponse.kt | 31 ++- .../domain/wallet/ducatus/DucatusEngine.java | 257 +++++++++--------- 4 files changed, 226 insertions(+), 189 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/InsightApi.java b/app/src/main/java/com/tangem/data/network/InsightApi.java index 458199262c..b75e92d06c 100644 --- a/app/src/main/java/com/tangem/data/network/InsightApi.java +++ b/app/src/main/java/com/tangem/data/network/InsightApi.java @@ -3,6 +3,8 @@ package com.tangem.data.network; import com.tangem.data.network.model.InsightBody; import com.tangem.data.network.model.InsightResponse; +import java.util.List; + import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; @@ -16,13 +18,13 @@ public interface InsightApi { Call insightAddress(@Path("address") String address); @GET(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS) - Call insightUnspent(@Path("address") String address); + Call> insightUnspent(@Path("address") String address); @GET(ServerApiInsight.INSIGHT_TRANSACTION) Call insightTransaction(@Path("transaction") String transaction); @GET(ServerApiInsight.INSIGHT_FEE) - Call insightFee(@Query("nbBlocks") int nbBlocks); + Call insightFee(); @Headers("Content-Type: application/json") @POST(ServerApiInsight.INSIGHT_SEND) diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java index f7b54a8e52..fc7de6214c 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java @@ -6,6 +6,8 @@ import android.util.Log; import com.tangem.data.network.model.InsightBody; import com.tangem.data.network.model.InsightResponse; +import java.util.List; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -17,13 +19,13 @@ public class ServerApiInsight { public static final String INSIGHT_ADDRESS = "/addr/{address}"; public static final String INSIGHT_UNSPENT_OUTPUTS = "/addr/{address}/utxo"; - public static final String INSIGHT_TRANSACTION = "/tx/{transaction}"; - public static final String INSIGHT_FEE = "/utils/estimatefee"; + public static final String INSIGHT_TRANSACTION = "/rawtx/{transaction}"; + public static final String INSIGHT_FEE = "/utils/estimatefee?nbBlocks=2,3,6"; public static final String INSIGHT_SEND = "/tx/send"; private int requestsCount=0; - public String lastNode; + public static String lastNode; public boolean isRequestsSequenceCompleted() { Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); @@ -34,7 +36,7 @@ public class ServerApiInsight { public interface InsightBodyListener { void onSuccess(String method, InsightResponse insightResponse); - + void onSuccess(String method, List utxoList); void onFail(String method, String message); } @@ -42,7 +44,7 @@ public class ServerApiInsight { insightBodyListener = listener; } - public void insight(String method, String wallet, String tx, int nbBlocks) { + public void insight(String method, String wallet, String tx) { requestsCount++; String insightURL = "http://130.185.109.17:3001/insight-api"; //TODO: make random selection this.lastNode = insightURL; //TODO: show node instead of URL @@ -54,51 +56,76 @@ public class ServerApiInsight { // InsightApi insightApi = App.getNetworkComponent().getRetrofitInsight(insightURL).create(InsightApi.class); InsightApi insightApi = retrofitInsight.create(InsightApi.class); - Call call; - switch (method) { - case INSIGHT_ADDRESS: - call = insightApi.insightAddress(wallet); - break; - - case INSIGHT_UNSPENT_OUTPUTS: - call = insightApi.insightUnspent(wallet); - break; - - case INSIGHT_TRANSACTION: - call = insightApi.insightTransaction(tx); - break; - - case INSIGHT_FEE: - call = insightApi.insightFee(nbBlocks); - break; - - case INSIGHT_SEND: - call = insightApi.insightSend(new InsightBody(tx)); - break; - - default: - call = insightApi.insightAddress(wallet); - break; - } - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.code() == 200) { - requestsCount--; - insightBodyListener.onSuccess(method, response.body()); - Log.i(TAG, "insight " + method + " onResponse " + response.code()); - } else { - insightBodyListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "insight " + method + " onResponse " + response.code()); + if (method.equals(INSIGHT_UNSPENT_OUTPUTS)) { + Call> call = insightApi.insightUnspent(wallet); + call.enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + if (response.code() == 200) { + requestsCount--; + insightBodyListener.onSuccess(method, response.body()); + Log.i(TAG, "insight " + method + " onResponse " + response.code()); + } else { + insightBodyListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "insight " + method + " onResponse " + response.code()); + } } - } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - insightBodyListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "insight " + method + " onFailure " + t.getMessage()); + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + insightBodyListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "insight " + method + " onFailure " + t.getMessage()); + } + }); + + } else { + Call call = null; + + switch (method) { + case INSIGHT_ADDRESS: + call = insightApi.insightAddress(wallet); + break; + + case INSIGHT_UNSPENT_OUTPUTS: + + break; + + case INSIGHT_TRANSACTION: + call = insightApi.insightTransaction(tx); + break; + + case INSIGHT_FEE: + call = insightApi.insightFee(); + break; + + case INSIGHT_SEND: + call = insightApi.insightSend(new InsightBody(tx)); + break; + + default: + call = insightApi.insightAddress(wallet); + break; } - }); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + insightBodyListener.onSuccess(method, response.body()); + Log.i(TAG, "insight " + method + " onResponse " + response.code()); + } else { + insightBodyListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "insight " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + insightBodyListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "insight " + method + " onFailure " + t.getMessage()); + } + }); + } } } diff --git a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt index 3071e94c2d..f9b86479b8 100644 --- a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt @@ -3,21 +3,36 @@ package com.tangem.data.network.model import com.google.gson.annotations.SerializedName data class InsightResponse( - @SerializedName("jsonrpc") - var jsonrpc: String = "", - - @SerializedName("id") - var id: Int? = null, - @SerializedName("balanceSat") - var balanceSat: Long = null, + var balanceSat: Long? = null, @SerializedName("unconfirmedBalanceSat") - var unconfirmedBalanceSat: Long = null, + var unconfirmedBalanceSat: Long? = null, @SerializedName("addrStr") var addrStr: String = "", + @SerializedName("txid") + var txid: String = "", + + @SerializedName("satoshis") + var satoshis: Int? = null, + + @SerializedName("height") + var height: Int? = null, + + @SerializedName("2") + var fee2: String = "", + + @SerializedName("3") + var fee3: String = "", + + @SerializedName("6") + var fee6: String = "", + + @SerializedName("rawtx") + var rawtx: String = "", + @SerializedName("error") var error: String = "" ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java index 18262dcd2b..682d62ac33 100644 --- a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java @@ -5,6 +5,7 @@ import android.text.InputFilter; import android.util.Log; import com.tangem.data.network.ElectrumRequest; +import com.tangem.data.network.InsightApi; import com.tangem.data.network.ServerApiInsight; import com.tangem.data.network.model.InsightResponse; import com.tangem.domain.wallet.BTCUtils; @@ -501,7 +502,8 @@ public class DucatusEngine extends BtcEngine { public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiInsight serverApiInsight = new ServerApiInsight(); - ServerApiInsight.InsightBodyListener insightBodyListenerListener = new ServerApiInsight.InsightBodyListener() { + ServerApiInsight.InsightBodyListener insightBodyListener = new ServerApiInsight.InsightBodyListener() { + @Override public void onSuccess(String method, InsightResponse insightResponse) { switch (method) { case ServerApiInsight.INSIGHT_ADDRESS: { @@ -514,7 +516,7 @@ public class DucatusEngine extends BtcEngine { coinData.setBalanceReceived(true); coinData.setBalanceConfirmed(insightResponse.getBalanceSat()); coinData.setBalanceUnconfirmed(insightResponse.getUnconfirmedBalanceSat()); - // Log.i("$TAG eth_get_balance", balanceCap) + coinData.setValidationNodeDescription(ServerApiInsight.lastNode); } catch (Exception e) { e.printStackTrace(); @@ -523,132 +525,70 @@ public class DucatusEngine extends BtcEngine { } break; - case ServerApiInsight.INFURA_ETH_GET_TRANSACTION_COUNT: { - String nonce = insightResponse.getResult(); - nonce = nonce.substring(2); - BigInteger count = new BigInteger(nonce, 16); - coinData.setConfirmedTXCount(count); - - -// Log.i("$TAG eth_getTransCount", nonce) - } - break; - - case ServerApiInsight.INFURA_ETH_GET_PENDING_COUNT: { - String pending = insightResponse.getResult(); - pending = pending.substring(2); - BigInteger count = new BigInteger(pending, 16); - coinData.setUnconfirmedTXCount(count); - -// Log.i("$TAG eth_getPendingTxCount", pending) + case ServerApiInsight.INSIGHT_TRANSACTION: { + try { + String raw = insightResponse.getRawtx(); + String txHash = new String(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(raw)))); //TODO: check + for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { + if (tx.txID.equals(txHash)) + tx.Raw = raw; + } + } catch (Exception e) { + e.printStackTrace(); + } } break; } - if (serverApiInfura.isRequestsSequenceCompleted()) { + if (serverApiInsight.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); } } - @Override - public void onSuccess(ElectrumRequest electrumRequest) { - Log.i(TAG, "onSuccess: "+electrumRequest.getMethod()); - if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) { + public void onSuccess(String method, List utxoList) { + // case ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS: TODO: check method try { - String walletAddress = electrumRequest.getParams().getString(0); - if (!walletAddress.equals(coinData.getWallet())) { - // todo - check - throw new Exception("Invalid wallet address in answer!"); + coinData.getUnspentTransactions().clear(); + for (InsightResponse utxo : utxoList) { + BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); + trUnspent.txID = utxo.getTxid(); + trUnspent.Amount = utxo.getSatoshis(); + trUnspent.Height = utxo.getHeight(); + coinData.getUnspentTransactions().add(trUnspent); + } + + for (InsightResponse utxo : utxoList) { + //if (height != -1) { TODO: check + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiInsight.insight(ServerApiInsight.INSIGHT_TRANSACTION, "", utxo.getTxid()); + } else { + ctx.setError("Terminated by user"); + } } - Long confBalance = electrumRequest.getResult().getLong("confirmed"); - Long unconfirmedBalance = electrumRequest.getResult().getLong("unconfirmed"); - coinData.setBalanceReceived(true); - coinData.setBalanceConfirmed(confBalance); - coinData.setBalanceUnconfirmed(unconfirmedBalance); - coinData.setValidationNodeDescription(serverApiInsight.lastNode); - } catch (JSONException e) { - e.printStackTrace(); - Log.e(TAG, "FAIL METHOD_GetBalance JSONException"); } catch (Exception e) { e.printStackTrace(); - Log.e(TAG, "FAIL METHOD_GetBalance Exception"); } - } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_ListUnspent)) { - try { - String walletAddress = electrumRequest.getParams().getString(0); - JSONArray jsUnspentArray = electrumRequest.getResultArray(); - try { - coinData.getUnspentTransactions().clear(); - for (int i = 0; i < jsUnspentArray.length(); i++) { - JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); - BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); - trUnspent.txID = jsUnspent.getString("tx_hash"); - trUnspent.Amount = jsUnspent.getInt("value"); - trUnspent.Height = jsUnspent.getInt("height"); - coinData.getUnspentTransactions().add(trUnspent); - } - } catch (JSONException e) { - e.printStackTrace(); - Log.e(TAG, "FAIL METHOD_ListUnspent JSONException"); - } - for (int i = 0; i < jsUnspentArray.length(); i++) { - JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); - Integer height = jsUnspent.getInt("height"); - String hash = jsUnspent.getString("tx_hash"); - if (height != -1) { - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiInsight.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); - } else { - ctx.setError("Terminated by user"); - } - } - } - } catch (JSONException e) { - e.printStackTrace(); - } - } else if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetTransaction)) { - try { - String txHash = electrumRequest.txHash; - String raw = electrumRequest.getResultString(); - for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) { - if (tx.txID.equals(txHash)) - tx.Raw = raw; - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - - if (serverApiInsight.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(!ctx.hasError()); - }else{ - blockchainRequestsCallbacks.onProgress(); - } } @Override public void onFail(String method, String message) { - Log.i(TAG, "onFail: "+electrumRequest.getMethod()+" "+electrumRequest.getError()); - ctx.setError(electrumRequest.getError()); - if (serverApiInsight.isRequestsSequenceCompleted()) { - blockchainRequestsCallbacks.onComplete(false);//serverApiInsight// .isErrorOccurred(), serverApiInsight - //.getError()); - }else{ - blockchainRequestsCallbacks.onProgress(); + if (!serverApiInsight.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); } } }; - serverApiInsight.setElectrumRequestData(electrumListener); + serverApiInsight.setInsightResponse(insightBodyListener); - serverApiInsight.electrumRequestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); - serverApiInsight.electrumRequestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); + serverApiInsight.insight(ServerApiInsight.INSIGHT_ADDRESS, coinData.getWallet(), ""); + serverApiInsight.insight(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS, coinData.getWallet(), ""); } - private final static BigDecimal relayFee = new BigDecimal(0.00001); +// private final static BigDecimal relayFee = new BigDecimal(0.00001); @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception { @@ -660,55 +600,108 @@ public class DucatusEngine extends BtcEngine { final ServerApiInsight serverApiInsight = new ServerApiInsight(); - final ServerApiInsight.InsightBodyListener electrumListener = new ServerApiInsight.InsightBodyListener () { + final ServerApiInsight.InsightBodyListener insightBodyListener = new ServerApiInsight.InsightBodyListener () { @Override - public void onSuccess(ElectrumRequest electrumRequest) { - BigDecimal fee; - if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetFee)) { + public void onSuccess(String method, InsightResponse insightResponse) { + if ( method.equals(ServerApiInsight.INSIGHT_FEE)) { try { - fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB + BigDecimal minFee = new BigDecimal(insightResponse.getFee2()); //fee per KB + BigDecimal normalFee = new BigDecimal(insightResponse.getFee3()); + BigDecimal maxFee = new BigDecimal(insightResponse.getFee6()); - if (fee.equals(BigDecimal.ZERO)) { - serverApiInsight.electrumRequestData(ctx, ElectrumRequest.getFee()); + if (minFee.equals(BigDecimal.ZERO) || normalFee.equals(BigDecimal.ZERO) || maxFee.equals(BigDecimal.ZERO)) { + serverApiInsight.insight(ServerApiInsight.INSIGHT_FEE, "",""); } -// if (calcSize != 0) { - fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size -// } else { -// serverApiInsight -//.electrumRequestData(ctx, ElectrumRequest.getFee()); + minFee = minFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size + normalFee = normalFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); + maxFee = maxFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); + +// //compare fee to usual relay fee TODO: check if needed after we get access to Ducatus network +// if (fee.compareTo(relayFee) < 0) { +// fee = relayFee; // } + minFee = minFee.setScale(8, RoundingMode.DOWN); + normalFee = normalFee.setScale(8, RoundingMode.DOWN); + maxFee = maxFee.setScale(8, RoundingMode.DOWN); - //compare fee to usual relay fee - if (fee.compareTo(relayFee) < 0) { - fee = relayFee; - } - fee = fee.setScale(8, RoundingMode.DOWN); + coinData.minFee = new Amount(minFee, ctx.getBlockchain().getCurrency()); + coinData.normalFee = new Amount(normalFee, ctx.getBlockchain().getCurrency()); + coinData.maxFee = new Amount(maxFee, ctx.getBlockchain().getCurrency()); - Amount feeAmount = new Amount(fee, ctx.getBlockchain().getCurrency()); - coinData.minFee = feeAmount; - coinData.normalFee = feeAmount; - coinData.maxFee = feeAmount; -// if (coinData.minFee != null && coinData.normalFee != null && coinData.maxFee != null) { blockchainRequestsCallbacks.onComplete(true); -// } else { -// blockchainRequestsCallbacks.onProgress(); -// } - } catch (JSONException e) { + } catch (Exception e) { e.printStackTrace(); } } } @Override - public void onFail(ElectrumRequest electrumRequest) { - ctx.setError(electrumRequest.getError()); - blockchainRequestsCallbacks.onComplete(false); + public void onSuccess (String method, List utxoList) { + Log.e(TAG, "Wrong response body, InsightResponse expected"); + } + + @Override + public void onFail(String method, String message) { + if (!serverApiInsight.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } } }; - serverApiInsight.setElectrumRequestData(electrumListener); + serverApiInsight.setInsightResponse(insightBodyListener); - serverApiInsight.electrumRequestData(ctx, ElectrumRequest.getFee()); + serverApiInsight.insight(ServerApiInsight.INSIGHT_FEE, "", ""); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { + final ServerApiInsight serverApiInsight = new ServerApiInsight(); + final String txStr = BTCUtils.toHex(txForSend); + + final ServerApiInsight.InsightBodyListener insightBodyListener = new ServerApiInsight.InsightBodyListener () { + @Override + public void onSuccess(String method, InsightResponse insightResponse) { + if (method.equals(ServerApiInsight.INSIGHT_SEND)) { + String resultString = insightResponse.toString(); + try { + if (resultString.isEmpty()) { + ctx.setError("No response from node"); + blockchainRequestsCallbacks.onComplete(false); + }else { // TODO: Make check for a valid send response + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + + Log.e(TAG, resultString); + } + } + } + } + + @Override + public void onSuccess (String method, List utxoList) { + Log.e(TAG, "Wrong response body, InsightResponse expected"); + } + + @Override + public void onFail(String method, String message) { + if (!serverApiInsight.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + serverApiInsight.setInsightResponse(insightBodyListener); + + serverApiInsight.insight(ServerApiInsight.INSIGHT_SEND, "", txStr); } } \ No newline at end of file From dfd7409c5dea57c48e4d652f217f235c85698783 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 10 Jan 2019 15:49:52 +0300 Subject: [PATCH 007/228] Updated on 2026-08-14 --- .../presentation/activity/MainActivity.kt | 9 ++-- .../activity/SignPaymentActivity.kt | 42 ++++++++--------- .../dialog/WaitSecurityDelayDialogNew.kt | 45 ------------------- 3 files changed, 22 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt index 2978ee8a4f..06cfd6347e 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt @@ -28,21 +28,20 @@ import com.tangem.App import com.tangem.Constant import com.tangem.data.Logger import com.tangem.data.network.ServerApiCommon -import com.tangem.tangemcard.android.nfc.DeviceNFCAntennaLocation -import com.tangem.tangemcard.tasks.ReadCardInfoTask import com.tangem.di.Navigator import com.tangem.domain.wallet.CoinEngineFactory import com.tangem.domain.wallet.TangemContext -import com.tangem.tangemcard.reader.CardProtocol -import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog import com.tangem.presentation.dialog.RootFoundDialog import com.tangem.presentation.dialog.WaitSecurityDelayDialog -import com.tangem.presentation.dialog.WaitSecurityDelayDialogNew +import com.tangem.tangemcard.android.nfc.DeviceNFCAntennaLocation +import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.tangemcard.android.reader.NfcReader import com.tangem.tangemcard.data.TangemCard import com.tangem.tangemcard.data.loadFromBundle import com.tangem.tangemcard.data.saveToBundle +import com.tangem.tangemcard.reader.CardProtocol +import com.tangem.tangemcard.tasks.ReadCardInfoTask import com.tangem.util.CommonUtil import com.tangem.util.LOG import com.tangem.util.PhoneUtility diff --git a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt index 6b43c44410..3258c8e3f4 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt @@ -20,21 +20,15 @@ import com.tangem.domain.wallet.CoinEngineFactory import com.tangem.domain.wallet.TangemContext import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog import com.tangem.presentation.dialog.WaitSecurityDelayDialog -import com.tangem.presentation.dialog.WaitSecurityDelayDialogNew -import com.tangem.presentation.event.ReadAfterRequest -import com.tangem.presentation.event.ReadBeforeRequest -import com.tangem.presentation.event.ReadWait -import com.tangem.presentation.event.TransactionFinishWithError -import com.tangem.tangemcard.reader.CardProtocol import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.tangemcard.android.reader.NfcReader import com.tangem.tangemcard.data.asBundle +import com.tangem.tangemcard.reader.CardProtocol import com.tangem.tangemcard.tasks.SignTask import com.tangem.tangemcard.util.Util import com.tangem.util.LOG import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_sign_payment.* -import org.greenrobot.eventbus.EventBus class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { @@ -259,38 +253,38 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card }, 500) } - private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() +// private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew() override fun onReadBeforeRequest(timeout: Int) { -// LOG.i(TAG, "onReadBeforeRequest timeout $timeout") -// WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout) + LOG.i(TAG, "onReadBeforeRequest timeout $timeout") + WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout) - if (!waitSecurityDelayDialogNew.isAdded) - waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) +// if (!waitSecurityDelayDialogNew.isAdded) +// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG) - val readBeforeRequest = ReadBeforeRequest() - readBeforeRequest.timeout = timeout - EventBus.getDefault().post(readBeforeRequest) +// val readBeforeRequest = ReadBeforeRequest() +// readBeforeRequest.timeout = timeout +// EventBus.getDefault().post(readBeforeRequest) } override fun onReadAfterRequest() { -// LOG.i(TAG, "onReadAfterRequest") -// WaitSecurityDelayDialog.onReadAfterRequest(this) + LOG.i(TAG, "onReadAfterRequest") + WaitSecurityDelayDialog.onReadAfterRequest(this) - val readAfterRequest = ReadAfterRequest() - EventBus.getDefault().post(readAfterRequest) +// val readAfterRequest = ReadAfterRequest() +// EventBus.getDefault().post(readAfterRequest) } override fun onReadWait(msec: Int) { -// LOG.i(TAG, "onReadWait msec $msec") -// WaitSecurityDelayDialog.onReadWait(this, msec) + LOG.i(TAG, "onReadWait msec $msec") + WaitSecurityDelayDialog.onReadWait(this, msec) - val readWait = ReadWait() - readWait.msec = msec - EventBus.getDefault().post(readWait) +// val readWait = ReadWait() +// readWait.msec = msec +// EventBus.getDefault().post(readWait) } diff --git a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt index 4e44beb311..7a29f8f39b 100644 --- a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt +++ b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt @@ -126,51 +126,6 @@ class WaitSecurityDelayDialogNew : AppCompatDialogFragment() { setRemainingTimeout(readWait.msec!!) } - fun onReadBeforeRequest(timeout: Int) { -// if (timerToShowDelayDialog != null || timeout < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG) -// return -// -// timerToShowDelayDialog = Timer() -// timerToShowDelayDialog!!.schedule(object : TimerTask() { -// override fun run() { -// setup(timeout, DELAY_BEFORE_SHOW_DIALOG) -// isCancelable = false -// if (!isVisible) -// show(activity?.supportFragmentManager, TAG) -// } -// }, DELAY_BEFORE_SHOW_DIALOG.toLong()) - } - - fun onReadAfterRequest() { -// if (timerToShowDelayDialog == null) -// return -// -// timerToShowDelayDialog!!.cancel() -// timerToShowDelayDialog = null - } - - fun onReadWait(msec: Int) { -// if (timerToShowDelayDialog != null) { -// timerToShowDelayDialog!!.cancel() -// timerToShowDelayDialog = null -// } -// -// if (msec == 0) { -// dismiss() -// return -// } -// -// if (msec > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) { -// // 1000ms - card delay notification interval -// setup(msec + 1000, 1000) -// isCancelable = false -//// if (!isVisible) -// show(activity?.supportFragmentManager, TAG) -// -// } else -// setRemainingTimeout(msec) - } - private fun setup(msTimeout: Int, msProgress: Int) { this.msTimeout = msTimeout this.msProgress = msProgress From 3da5e389731754d9085ad4946724956de28253c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 10 Jan 2019 16:46:15 +0300 Subject: [PATCH 008/228] Updated on 2026-08-14 --- .../java/com/tangem/data/network/model/InsightResponse.kt | 2 +- .../java/com/tangem/domain/wallet/bch/BtcCashEngine.java | 5 +---- app/src/main/java/com/tangem/domain/wallet/btc/BtcData.java | 6 +++--- .../main/java/com/tangem/domain/wallet/btc/BtcEngine.java | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt index f9b86479b8..4910b36695 100644 --- a/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/InsightResponse.kt @@ -16,7 +16,7 @@ data class InsightResponse( var txid: String = "", @SerializedName("satoshis") - var satoshis: Int? = null, + var satoshis: Long? = null, @SerializedName("height") var height: Int? = null, diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java index 61c55ba38f..bca4b4cb3d 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java @@ -5,13 +5,11 @@ import android.text.InputFilter; import android.util.Log; import com.tangem.data.network.ElectrumRequest; -import com.tangem.data.network.ServerApiCommon; import com.tangem.data.network.ServerApiElectrum; import com.tangem.domain.wallet.BCHUtils; import com.tangem.domain.wallet.BTCUtils; import com.tangem.tangemcard.reader.CardProtocol; import com.tangem.domain.wallet.BalanceValidator; -import com.tangem.data.Blockchain; import com.tangem.domain.wallet.btc.BtcData; import com.tangem.domain.wallet.CoinData; import com.tangem.domain.wallet.CoinEngine; @@ -24,7 +22,6 @@ import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.tangemcard.util.Util; -import com.tangem.util.FormatUtil; import com.tangem.wallet.R; import org.json.JSONArray; @@ -594,7 +591,7 @@ public class BtcCashEngine extends CoinEngine { JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); trUnspent.txID = jsUnspent.getString("tx_hash"); - trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Amount = jsUnspent.getLong("value"); trUnspent.Height = jsUnspent.getInt("height"); coinData.getUnspentTransactions().add(trUnspent); } diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BtcData.java b/app/src/main/java/com/tangem/domain/wallet/btc/BtcData.java index f036a85aa0..5a40f1ae6a 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BtcData.java +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BtcData.java @@ -35,14 +35,14 @@ public class BtcData extends CoinData { public static class UnspentTransaction { public String txID; - public Integer Amount; + public Long Amount; public Integer Height; public String Raw = ""; public Bundle getAsBundle() { Bundle B = new Bundle(); B.putString("txID", txID); - B.putInt("Amount", Amount); + B.putLong("Amount", Amount); B.putInt("Height", Height); B.putString("Raw", Raw); return B; @@ -50,7 +50,7 @@ public class BtcData extends CoinData { public void loadFromBundle(Bundle B) { txID = B.getString("txID"); - Amount = B.getInt("Amount"); + Amount = B.getLong("Amount"); Height = B.getInt("Height"); Raw = B.getString("Raw"); } diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java index 80f0e11d56..42b0073218 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java @@ -561,7 +561,7 @@ public class BtcEngine extends CoinEngine { JSONObject jsUnspent = jsUnspentArray.getJSONObject(i); BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction(); trUnspent.txID = jsUnspent.getString("tx_hash"); - trUnspent.Amount = jsUnspent.getInt("value"); + trUnspent.Amount = jsUnspent.getLong("value"); trUnspent.Height = jsUnspent.getInt("height"); coinData.getUnspentTransactions().add(trUnspent); } From 30c4c0b4254e75898edad886e3619057243d681c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 10 Jan 2019 16:46:56 +0300 Subject: [PATCH 009/228] Updated on 2026-08-14 --- .../StartFingerprintReaderTask.java | 32 +++++++---------- .../activity/ConfirmPaymentActivity.kt | 11 +++--- .../activity/CreateNewWalletActivity.kt | 3 +- .../activity/EmptyWalletActivity.kt | 9 +++-- .../presentation/activity/LogoActivity.kt | 6 ++-- .../presentation/activity/MainActivity.kt | 8 ++--- .../activity/PinRequestActivity.kt | 35 +++++++++---------- .../presentation/activity/PinSaveActivity.kt | 1 - .../presentation/activity/PinSwapActivity.kt | 3 +- ...pareCryptonitOtherApiWithdrawalActivity.kt | 12 +++---- .../PrepareCryptonitWithdrawalActivity.kt | 12 +++---- .../activity/PreparePaymentActivity.kt | 35 ++++++------------- .../presentation/activity/PurgeActivity.kt | 6 ++-- .../presentation/activity/QrScanActivity.kt | 1 - .../activity/SendTransactionActivity.kt | 5 +-- 15 files changed, 74 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java b/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java index 826c838d6b..91b13b925c 100644 --- a/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java +++ b/app/src/main/java/com/tangem/data/fingerprint/StartFingerprintReaderTask.java @@ -11,6 +11,7 @@ import android.security.keystore.KeyProperties; import com.tangem.Constant; import com.tangem.tangemcard.android.data.PINStorage; import com.tangem.presentation.activity.PinRequestActivity; +import com.tangem.util.LOG; import java.io.IOException; import java.lang.ref.WeakReference; @@ -26,6 +27,8 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; public class StartFingerprintReaderTask extends AsyncTask { + public static final String TAG = StartFingerprintReaderTask.class.getSimpleName(); + private WeakReference reference; private KeyStore keyStore; @@ -64,16 +67,14 @@ public class StartFingerprintReaderTask extends AsyncTask { @Override protected void onPostExecute(final Boolean success) { - PinRequestActivity pinRequestActivity = reference.get(); onCancelled(); if (!success) { - pinRequestActivity.doLog("Authentication failed!"); + LOG.i(TAG, "Authentication failed!"); } else { fingerprintHelper.startAuth(fingerprintManager, cryptoObject); - pinRequestActivity.doLog("Authenticate using fingerprint!"); - + LOG.i(TAG, "Authenticate using fingerprint!"); } } @@ -85,9 +86,7 @@ public class StartFingerprintReaderTask extends AsyncTask { } private boolean getKeyStore() { - PinRequestActivity pinRequestActivity = reference.get(); - - pinRequestActivity.doLog("Getting keystore..."); + LOG.i(TAG, "Getting keystore..."); try { keyStore = KeyStore.getInstance(Constant.KEYSTORE); keyStore.load(null); // Create empty keystore @@ -101,9 +100,7 @@ public class StartFingerprintReaderTask extends AsyncTask { @TargetApi(Build.VERSION_CODES.M) public boolean createNewKey(boolean forceCreate) { - PinRequestActivity pinRequestActivity = reference.get(); - - pinRequestActivity.doLog("Creating new key..."); + LOG.i(TAG, "Creating new key..."); try { if (forceCreate) keyStore.deleteEntry(Constant.KEY_ALIAS); @@ -120,9 +117,9 @@ public class StartFingerprintReaderTask extends AsyncTask { ); generator.generateKey(); - pinRequestActivity.doLog("Key created."); + LOG.i(TAG, "Key created."); } else - pinRequestActivity.doLog("Key exists."); + LOG.i(TAG, "Key exists."); return true; } catch (Exception e) { @@ -133,9 +130,7 @@ public class StartFingerprintReaderTask extends AsyncTask { } private boolean getCipher() { - PinRequestActivity pinRequestActivity = reference.get(); - - pinRequestActivity.doLog("Getting cipher..."); + LOG.i(TAG, "Getting cipher..."); try { cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); return true; @@ -150,7 +145,7 @@ public class StartFingerprintReaderTask extends AsyncTask { private boolean initCipher(int mode) { PinRequestActivity pinRequestActivity = reference.get(); - pinRequestActivity.doLog("Initializing cipher..."); + LOG.i(TAG, "Initializing cipher..."); try { keyStore.load(null); SecretKey keyspec = (SecretKey) keyStore.getKey(Constant.KEY_ALIAS, null); @@ -181,9 +176,7 @@ public class StartFingerprintReaderTask extends AsyncTask { @TargetApi(Build.VERSION_CODES.M) private boolean initCryptObject() { - PinRequestActivity pinRequestActivity = reference.get(); - - pinRequestActivity.doLog("Initializing crypt object..."); + LOG.i(TAG, "Initializing crypt object..."); try { cryptoObject = new FingerprintManager.CryptoObject(cipher); return true; @@ -193,5 +186,4 @@ public class StartFingerprintReaderTask extends AsyncTask { return false; } - } diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt index 5ab55e0c3c..d6329df62b 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt @@ -30,8 +30,7 @@ import java.util.* class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { - private var nfcManager: NfcManager? = null - + private lateinit var nfcManager: NfcManager private lateinit var ctx: TangemContext private lateinit var amount: CoinEngine.Amount @@ -183,17 +182,17 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { public override fun onResume() { super.onResume() - nfcManager!!.onResume() + nfcManager.onResume() } public override fun onPause() { super.onPause() - nfcManager!!.onPause() + nfcManager.onPause() } public override fun onStop() { super.onStop() - nfcManager!!.onStop() + nfcManager.onStop() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -249,7 +248,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { override fun onTagDiscovered(tag: Tag) { try { - nfcManager!!.ignoreTag(tag) + nfcManager.ignoreTag(tag) } catch (e: IOException) { e.printStackTrace() } diff --git a/app/src/main/java/com/tangem/presentation/activity/CreateNewWalletActivity.kt b/app/src/main/java/com/tangem/presentation/activity/CreateNewWalletActivity.kt index f7f06b0b4a..cfdd251dad 100644 --- a/app/src/main/java/com/tangem/presentation/activity/CreateNewWalletActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/CreateNewWalletActivity.kt @@ -28,7 +28,6 @@ import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_create_new_wallet.* class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { - companion object { fun callingIntent(context: Context, ctx: TangemContext): Intent { val intent = Intent(context, CreateNewWalletActivity::class.java) @@ -38,8 +37,8 @@ class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, } } - private lateinit var ctx: TangemContext private lateinit var nfcManager: NfcManager + private lateinit var ctx: TangemContext private var createNewWalletTask: CreateNewWalletTask? = null private var lastReadSuccess = true diff --git a/app/src/main/java/com/tangem/presentation/activity/EmptyWalletActivity.kt b/app/src/main/java/com/tangem/presentation/activity/EmptyWalletActivity.kt index d74f7d1cbe..cc7646b7db 100644 --- a/app/src/main/java/com/tangem/presentation/activity/EmptyWalletActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/EmptyWalletActivity.kt @@ -33,10 +33,8 @@ import kotlinx.android.synthetic.main.activity_empty_wallet.* import javax.inject.Inject class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { - companion object { val TAG: String = EmptyWalletActivity::class.java.simpleName - fun callingIntent(context: Context, ctx: TangemContext): Intent { val intent = Intent(context, EmptyWalletActivity::class.java) ctx.saveToIntent(intent) @@ -44,15 +42,16 @@ class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card } } + @Inject + internal lateinit var navigator: Navigator + private lateinit var nfcManager: NfcManager private lateinit var ctx: TangemContext + private var lastReadSuccess = true private var verifyCardTask: VerifyCardTask? = null private var requestPIN2Count = 0 - @Inject - internal lateinit var navigator: Navigator - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_empty_wallet) diff --git a/app/src/main/java/com/tangem/presentation/activity/LogoActivity.kt b/app/src/main/java/com/tangem/presentation/activity/LogoActivity.kt index 51e36440be..76e05876d3 100644 --- a/app/src/main/java/com/tangem/presentation/activity/LogoActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/LogoActivity.kt @@ -14,9 +14,7 @@ import kotlinx.android.synthetic.main.activity_logo.* import javax.inject.Inject class LogoActivity : AppCompatActivity() { - companion object { - fun callingIntent(context: Context, autoHide: Boolean): Intent { val intent = Intent(context, LogoActivity::class.java) intent.putExtra(Constant.EXTRA_AUTO_HIDE, autoHide) @@ -24,11 +22,11 @@ class LogoActivity : AppCompatActivity() { } } - private val hideRunnable = Runnable { this.hide() } - @Inject internal lateinit var navigator: Navigator + private val hideRunnable = Runnable { this.hide() } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_logo) diff --git a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt index 06cfd6347e..79d8594bfd 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.kt @@ -56,11 +56,14 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco companion object { val TAG: String = MainActivity::class.java.simpleName - fun callingIntent(context: Context) = Intent(context, MainActivity::class.java) } + @Inject + internal lateinit var navigator: Navigator + private lateinit var nfcManager: NfcManager + private var zipFile: File? = null private var antenna: DeviceNFCAntennaLocation? = null private var unsuccessReadCount = 0 @@ -68,9 +71,6 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco private var readCardInfoTask: ReadCardInfoTask? = null private var onNfcReaderCallback: NfcAdapter.ReaderCallback? = null - @Inject - internal lateinit var navigator: Navigator - override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) { diff --git a/app/src/main/java/com/tangem/presentation/activity/PinRequestActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PinRequestActivity.kt index 0fa74bd5c3..e8128a0825 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PinRequestActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PinRequestActivity.kt @@ -29,14 +29,16 @@ import com.tangem.tangemcard.data.TangemCard import com.tangem.tangemcard.data.loadFromBundle import com.tangem.tangemcard.data.EXTRA_TANGEM_CARD import com.tangem.tangemcard.data.EXTRA_TANGEM_CARD_UID +import com.tangem.util.LOG import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_pin_request.* import kotlinx.android.synthetic.main.layout_pin_buttons.* import java.io.IOException class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener { - companion object { + val TAG: String = PinRequestActivity::class.java.simpleName + fun callingIntent(context: Activity, mode: String): Intent { val intent = Intent(context, PinRequestActivity::class.java) intent.putExtra(Constant.EXTRA_MODE, mode) @@ -81,12 +83,11 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge } } + private lateinit var nfcManager: NfcManager + lateinit var mode: Mode private var allowFingerprint = false - private var nfcManager: NfcManager? = null - var startFingerprintReaderTask: StartFingerprintReaderTask? = null - private var fingerprintManager: FingerprintManager? = null private var fingerprintHelper: FingerprintHelper? = null @@ -179,7 +180,7 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge startFingerprintReaderTask = null } - nfcManager!!.onPause() + nfcManager.onPause() } override fun onStop() { @@ -192,12 +193,12 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge startFingerprintReaderTask = null } - nfcManager!!.onStop() + nfcManager.onStop() } override fun onResume() { super.onResume() - nfcManager!!.onResume() + nfcManager.onResume() if (allowFingerprint) startFingerprintReader() } @@ -205,19 +206,19 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge override fun onTagDiscovered(tag: Tag) { try { Log.w(javaClass.name, "Ignore discovered tag!") - nfcManager!!.ignoreTag(tag) + nfcManager.ignoreTag(tag) } catch (e: IOException) { e.printStackTrace() } } override fun authenticationFailed(error: String) { - doLog(error) + LOG.w(TAG, error) } @TargetApi(Build.VERSION_CODES.M) override fun authenticationSucceeded(result: FingerprintManager.AuthenticationResult) { - doLog("Authentication succeeded!") + LOG.i(TAG,"Authentication succeeded!") val cipher = result.cryptoObject.cipher if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) { @@ -245,10 +246,6 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge finish() } - fun doLog(text: String) { - // Log.e("FP", text); - } - @SuppressLint("SetTextI18n") private fun buttonClick(button: Button) { tvPin!!.text = tvPin!!.text.toString() + button.text as String @@ -256,27 +253,27 @@ class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Finge @SuppressLint("NewApi") private fun testFingerPrintSettings(): Boolean { - doLog("Testing Fingerprint Settings") + LOG.i(TAG,"Testing Fingerprint Settings") val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager if (!keyguardManager.isKeyguardSecure) { - doLog("User hasn't enabled Lock Screen") + LOG.i(TAG,"User hasn't enabled Lock Screen") return false } if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { - doLog("User hasn't granted permission to use Fingerprint") + LOG.i(TAG,"User hasn't granted permission to use Fingerprint") return false } if (!fingerprintManager!!.hasEnrolledFingerprints()) { - doLog("User hasn't registered any fingerprints") + LOG.i(TAG,"User hasn't registered any fingerprints") return false } - doLog("Fingerprint authentication is set.\n") + LOG.i(TAG,"Fingerprint authentication is set.\n") return true } diff --git a/app/src/main/java/com/tangem/presentation/activity/PinSaveActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PinSaveActivity.kt index 5a2c5b140d..b67ad84f01 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PinSaveActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PinSaveActivity.kt @@ -39,7 +39,6 @@ import javax.crypto.SecretKey import javax.crypto.spec.IvParameterSpec class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelperListener { - companion object { val TAG: String = PinSaveActivity::class.java.simpleName diff --git a/app/src/main/java/com/tangem/presentation/activity/PinSwapActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PinSwapActivity.kt index 8a38bae83c..38584e3d12 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PinSwapActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PinSwapActivity.kt @@ -28,7 +28,6 @@ import com.tangem.wallet.R import kotlinx.android.synthetic.main.activity_pin_swap.* class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { - companion object { val TAG: String = PinSwapActivity::class.java.simpleName @@ -43,8 +42,8 @@ class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProt } private lateinit var nfcManager: NfcManager - private var card: TangemCard? = null + private var card: TangemCard? = null private var newPIN: String? = null private var newPIN2: String? = null diff --git a/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherApiWithdrawalActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherApiWithdrawalActivity.kt index fe4deaf57d..88c5e358c0 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherApiWithdrawalActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitOtherApiWithdrawalActivity.kt @@ -28,8 +28,9 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt val TAG: String = PrepareCryptonitOtherApiWithdrawalActivity::class.java.simpleName } + private lateinit var nfcManager: NfcManager private lateinit var ctx: TangemContext - private var nfcManager: NfcManager? = null + private var cryptonit: CryptonitOtherApi? = null @Inject @@ -156,17 +157,17 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt public override fun onResume() { super.onResume() - nfcManager!!.onResume() + nfcManager.onResume() } public override fun onPause() { super.onPause() - nfcManager!!.onPause() + nfcManager.onPause() } public override fun onStop() { super.onStop() - nfcManager!!.onStop() + nfcManager.onStop() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -193,8 +194,7 @@ class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapt override fun onTagDiscovered(tag: Tag) { try { -// Log.w(javaClass.name, "Ignore discovered tag!") - nfcManager!!.ignoreTag(tag) + nfcManager.ignoreTag(tag) } catch (e: IOException) { e.printStackTrace() } diff --git a/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt index 9a166fea3e..a3ae2fc75d 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PrepareCryptonitWithdrawalActivity.kt @@ -28,10 +28,10 @@ class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.Reade val TAG: String = PrepareCryptonitWithdrawalActivity::class.java.simpleName } + private lateinit var nfcManager: NfcManager private lateinit var ctx: TangemContext - private var nfcManager: NfcManager? = null - private var cryptonit: Cryptonit? = null + private var cryptonit: Cryptonit? = null @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { @@ -150,22 +150,22 @@ class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.Reade public override fun onResume() { super.onResume() - nfcManager!!.onResume() + nfcManager.onResume() } public override fun onPause() { super.onPause() - nfcManager!!.onPause() + nfcManager.onPause() } public override fun onStop() { super.onStop() - nfcManager!!.onStop() + nfcManager.onStop() } override fun onTagDiscovered(tag: Tag) { try { - nfcManager!!.ignoreTag(tag) + nfcManager.ignoreTag(tag) } catch (e: IOException) { e.printStackTrace() } diff --git a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt index faeffdc9a7..fa94fbac01 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt @@ -25,10 +25,8 @@ import java.io.IOException import javax.inject.Inject class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { - companion object { val TAG: String = PreparePaymentActivity::class.java.simpleName - fun callingIntent(context: Context, ctx: TangemContext): Intent { val intent = Intent(context, PreparePaymentActivity::class.java) ctx.saveToIntent(intent) @@ -36,12 +34,12 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } } - private lateinit var ctx: TangemContext - private var nfcManager: NfcManager? = null - @Inject internal lateinit var navigator: Navigator + private lateinit var ctx: TangemContext + private lateinit var nfcManager: NfcManager + @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -60,7 +58,7 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { tvBalance.text = html //TODO - to engine - if (ctx.blockchain == Blockchain.Token && engine.balance.currency!=Blockchain.Ethereum.currency) { + if (ctx.blockchain == Blockchain.Token && engine.balance.currency != Blockchain.Ethereum.currency) { rgIncFee!!.visibility = View.INVISIBLE } else { rgIncFee!!.visibility = View.VISIBLE @@ -87,7 +85,6 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } } btnVerify.setOnClickListener { - val engine1 = CoinEngineFactory.create(ctx) val strAmount: String = etAmount.text.toString().replace(",", ".") @@ -102,29 +99,18 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { etAmount.error = getString(R.string.unknown_amount_format) } - var checkAddress = false -// if (engine1 != null) - checkAddress = engine1.validateAddress(etWallet.text.toString()) - // check wallet address - if (!checkAddress) { + if (!engine1.validateAddress(etWallet.text.toString())) { etWallet.error = getString(R.string.incorrect_destination_wallet_address) return@setOnClickListener - } else { + } else etWallet.error = null - } if (etWallet.text.toString() == ctx.coinData!!.wallet) { etWallet.error = getString(R.string.destination_wallet_address_equal_source_address) return@setOnClickListener } - // check enough funds - // TODO - double with engin.checkAmount -// if (etAmount.text.toString().replace(",", ".").toDouble() > engine.getBalanceValue(card).replace(",", ".").toDouble()) { -// etAmount.error = getString(R.string.not_enough_funds_on_your_card) -// return@setOnClickListener -// } if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) { return@setOnClickListener } @@ -143,17 +129,17 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { public override fun onResume() { super.onResume() - nfcManager!!.onResume() + nfcManager.onResume() } public override fun onPause() { super.onPause() - nfcManager!!.onPause() + nfcManager.onPause() } public override fun onStop() { super.onStop() - nfcManager!!.onStop() + nfcManager.onStop() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -188,11 +174,10 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { override fun onTagDiscovered(tag: Tag) { try { - nfcManager!!.ignoreTag(tag) + nfcManager.ignoreTag(tag) } catch (e: IOException) { e.printStackTrace() } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/presentation/activity/PurgeActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PurgeActivity.kt index 7930a88062..facf49ee7f 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PurgeActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PurgeActivity.kt @@ -31,17 +31,19 @@ class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoc companion object { val TAG: String = PurgeActivity::class.java.simpleName - const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER fun callingIntent(context: Context, ctx: TangemContext): Intent { val intent = Intent(context, PurgeActivity::class.java) ctx.saveToIntent(intent) return intent } + + const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER } - private lateinit var ctx: TangemContext private lateinit var nfcManager: NfcManager + private lateinit var ctx: TangemContext + private var purgeTask: PurgeTask? = null override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/tangem/presentation/activity/QrScanActivity.kt b/app/src/main/java/com/tangem/presentation/activity/QrScanActivity.kt index 9d4e4d0585..1dcf4b0edb 100644 --- a/app/src/main/java/com/tangem/presentation/activity/QrScanActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/QrScanActivity.kt @@ -13,7 +13,6 @@ import com.tangem.Constant import me.dm7.barcodescanner.zxing.ZXingScannerView class QrScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { - companion object { fun callingIntent(context: Context): Intent { return Intent(context, QrScanActivity::class.java) diff --git a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt index 3152227a80..d3465e1d28 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt @@ -21,9 +21,10 @@ import java.io.IOException class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { - private lateinit var ctx: TangemContext - private var tx: ByteArray? = null private lateinit var nfcManager: NfcManager + private lateinit var ctx: TangemContext + + private var tx: ByteArray? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) From 6d8eeaf6ac249a6b7082cf918c2b8dfcc7771b3a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 10 Jan 2019 16:58:30 +0300 Subject: [PATCH 010/228] Updated on 2026-08-14 --- app/src/main/res/drawable/ic_content_copy_black_24dp.xml | 0 app/src/main/res/drawable/ic_nfc_white_24dp.xml | 0 app/src/main/res/drawable/ic_wrench_white_24dp.xml | 0 gradlew | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 app/src/main/res/drawable/ic_content_copy_black_24dp.xml mode change 100755 => 100644 app/src/main/res/drawable/ic_nfc_white_24dp.xml mode change 100755 => 100644 app/src/main/res/drawable/ic_wrench_white_24dp.xml mode change 100755 => 100644 gradlew diff --git a/app/src/main/res/drawable/ic_content_copy_black_24dp.xml b/app/src/main/res/drawable/ic_content_copy_black_24dp.xml old mode 100755 new mode 100644 diff --git a/app/src/main/res/drawable/ic_nfc_white_24dp.xml b/app/src/main/res/drawable/ic_nfc_white_24dp.xml old mode 100755 new mode 100644 diff --git a/app/src/main/res/drawable/ic_wrench_white_24dp.xml b/app/src/main/res/drawable/ic_wrench_white_24dp.xml old mode 100755 new mode 100644 diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 From 9d899467d108800e830a9795a36fbbf9b642327d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 14 Jan 2019 15:35:59 +0300 Subject: [PATCH 011/228] Updated on 2026-08-14 --- .../main/java/com/tangem/data/Blockchain.java | 3 +- .../java/com/tangem/data/network/Server.java | 10 +- .../data/network/ServerApiRootstock.java | 105 +++++++ .../com/tangem/data/network/ServerURL.java | 3 +- .../tangem/domain/wallet/CoinEngineFactory.kt | 3 + .../tangem/domain/wallet/rsk/RskEngine.java | 295 ++++++++++++++++++ 6 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/ServerApiRootstock.java create mode 100644 app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 5ad40fedc9..cf900eb9c4 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -13,7 +13,8 @@ public enum Blockchain { EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"), Token("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"), BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"), - Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"); + Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"), + Rootstock("RSK", "RBTC", 1.0, R.drawable.ic_logo_bitcoin, "Rootstock"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index b6f74767ef..9be4f58449 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -28,7 +28,15 @@ public class Server { public static final String URL_INFURA = ServerURL.API_INFURA; public static class Method { - static final String MAIN = URL_INFURA + "613a0b14833145968b1f656240c7d245"; + static final String MAIN = URL_INFURA + "v3/613a0b14833145968b1f656240c7d245"; + } + } + + public static class ApiRootstock { + public static final String URL_ROOTSTOCK = ServerURL.API_ROOTSTOCK; + + public static class Method { + static final String MAIN = URL_ROOTSTOCK; } } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java new file mode 100644 index 0000000000..5b3cbdcc9c --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java @@ -0,0 +1,105 @@ +package com.tangem.data.network; + +import android.support.annotation.NonNull; +import android.util.Log; + +import com.tangem.App; +import com.tangem.data.network.model.InfuraBody; +import com.tangem.data.network.model.InfuraResponse; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class ServerApiRootstock { + private static String TAG = ServerApiRootstock.class.getSimpleName(); + + /** + * HTTP + * Infura + *

+ * eth_getBalance + * eth_getTransactionCount + * eth_call + * eth_sendRawTransaction + * eth_gasPrice + */ + public static final String ROOTSTOCK_ETH_GET_BALANCE = "eth_getBalance"; + public static final String ROOTSTOCK_ETH_GET_TRANSACTION_COUNT = "eth_getTransactionCount"; + public static final String ROOTSTOCK_ETH_GET_PENDING_COUNT = "eth_getPendingCount"; + public static final String ROOTSTOCK_ETH_CALL = "eth_call"; + public static final String ROOTSTOCK_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction"; + public static final String ROOTSTOCK_ETH_GAS_PRICE = "eth_gasPrice"; + + private int requestsCount=0; + + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; + } + + private RootstockBodyListener rootstockBodyListener; + + public interface RootstockBodyListener { + void onSuccess(String method, InfuraResponse infuraResponse); + + void onFail(String method, String message); + } + + public void setRootstockResponse(RootstockBodyListener listener) { + rootstockBodyListener = listener; + } + + public void rootstock(String method, int id, String wallet, String contract, String tx) { + requestsCount++; + InfuraApi infuraApi = App.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class); + + InfuraBody infuraBody; + switch (method) { + case ROOTSTOCK_ETH_GET_BALANCE: + case ROOTSTOCK_ETH_GET_TRANSACTION_COUNT: + infuraBody = new InfuraBody(method, new String[]{wallet, "latest"}, id); + break; + case ROOTSTOCK_ETH_GET_PENDING_COUNT: + infuraBody = new InfuraBody(ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, new String[]{wallet, "pending"}, id); + break; + case ROOTSTOCK_ETH_CALL: + String address = wallet.substring(2); + infuraBody = new InfuraBody(method, new Object[]{new InfuraBody.EthCallParams("0x70a08231000000000000000000000000" + address, contract), "latest"}, id); + break; + + case ROOTSTOCK_ETH_SEND_RAW_TRANSACTION: + infuraBody = new InfuraBody(method, new String[]{tx}, id); + break; + + case ROOTSTOCK_ETH_GAS_PRICE: + infuraBody = new InfuraBody(method, id); + break; + + default: + infuraBody = new InfuraBody(); + } + + Call call = infuraApi.infura(infuraBody); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + rootstockBodyListener.onSuccess(method, response.body()); + Log.i(TAG, "rootstock " + method + " onResponse " + response.code()); + } else { + rootstockBodyListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "rootstock " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + rootstockBodyListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "rootstock " + method + " onFailure " + t.getMessage()); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index 297b3ee60c..b33d4c4ba4 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -3,7 +3,8 @@ package com.tangem.data.network; class ServerURL { static final String API_TANGEM = "https://verify.tangem.com/"; static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/"; - static final String API_INFURA = "https://mainnet.infura.io/v3/"; + static final String API_INFURA = "https://mainnet.infura.io/"; static final String API_ESTIMATEFEE = "https://estimatefee.com/"; static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/"; + static final String API_ROOTSTOCK = "https://public-node.rsk.co/"; } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt index 951b7eb457..7bba67ec11 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt @@ -8,6 +8,7 @@ import com.tangem.domain.wallet.token.TokenEngine import com.tangem.domain.wallet.bch.BtcCashEngine import com.tangem.data.Blockchain import com.tangem.domain.wallet.ltc.LtcEngine +import com.tangem.domain.wallet.rsk.RskEngine /** * Factory for create specific engine @@ -44,6 +45,8 @@ object CoinEngineFactory { TokenEngine(context) else if (Blockchain.Litecoin == context.blockchain) LtcEngine(context) + else if (Blockchain.Rootstock == context.blockchain) + RskEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java new file mode 100644 index 0000000000..e2c1c83e17 --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java @@ -0,0 +1,295 @@ +package com.tangem.domain.wallet.rsk; + +import android.net.Uri; +import android.util.Log; + +import com.tangem.data.Blockchain; +import com.tangem.data.network.ServerApiRootstock; +import com.tangem.data.network.model.InfuraResponse; +import com.tangem.domain.wallet.BTCUtils; +import com.tangem.domain.wallet.CoinEngine; +import com.tangem.domain.wallet.ECDSASignatureETH; +import com.tangem.domain.wallet.EthTransaction; +import com.tangem.domain.wallet.TangemContext; +import com.tangem.domain.wallet.eth.EthData; +import com.tangem.domain.wallet.eth.EthEngine; +import com.tangem.tangemcard.data.TangemCard; +import com.tangem.tangemcard.tasks.SignTask; +import com.tangem.util.CryptoUtil; +import com.tangem.wallet.R; + +import org.bitcoinj.core.ECKey; + +import java.math.BigInteger; +import java.util.Arrays; + +public class RskEngine extends EthEngine { + + private static final String TAG = RskEngine.class.getSimpleName(); + + public RskEngine(TangemContext ctx) throws Exception { + super(ctx); + if (ctx.getCoinData() == null) { + coinData = new EthData(); + ctx.setCoinData(coinData); + } else if (ctx.getCoinData() instanceof EthData) { + coinData = (EthData) ctx.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for RskEngine"); + } + } + + private static int getDecimals() { + return 18; + } + + @Override + public String getBalanceCurrency() { + return Blockchain.Rootstock.getCurrency(); + } + + @Override + public String getFeeCurrency() { + return Blockchain.Rootstock.getCurrency(); + } + + @Override + public Uri getShareWalletUri() { return Uri.parse(ctx.getCoinData().getWallet()); } + + @Override + public Uri getShareWalletUriExplorer() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } + + @Override + public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { + + Log.e(TAG, "Construct payment " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding")); + + BigInteger nonceValue = coinData.getConfirmedTXCount(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + + BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact(); + BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact(); + + if (IncFee) { + weiAmount = weiAmount.subtract(weiFee); + } + + BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(21000); + Integer chainId = EthTransaction.ChainEnum.Rootstock_mainnet.getValue(); + + String to = targetAddress; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); + + return new SignTask.PaymentToSign() { + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash; + } + + @Override + public byte[][] getHashesToSign() { + byte[][] hashesForSign = new byte[1][]; + hashesForSign[0] = tx.getRawHash(); + return hashesForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for RSK"); + } + + @Override + public String getHashAlgToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for RSK"); + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Transaction validation by issuer not supported in this version"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + byte[] for_hash = tx.getRawHash(); + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if (!f) { + Log.e("RSK-CHECK", "sign Failed."); + } + + tx.signature = new ECDSASignatureETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e(TAG, "invalid v"); + throw new Exception("Error in RskEngine - invalid v"); + } + tx.signature.v = (byte) v; + Log.e(TAG, "RSK_v: " +String.valueOf(v)); + + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendPayment(txForSend); + return txForSend; + } + }; + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); + // request infura listener + ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse rootstockResponse) { + switch (method) { + case ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE: { + String balanceCap = rootstockResponse.getResult(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + coinData.setBalanceReceived(true); + coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); + +// Log.i("$TAG eth_get_balance", balanceCap) + } + break; + + case ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT: { + String nonce = rootstockResponse.getResult(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + coinData.setConfirmedTXCount(count); + + +// Log.i("$TAG eth_getTransCount", nonce) + } + break; + + case ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT: { + String pending = rootstockResponse.getResult(); + pending = pending.substring(2); + BigInteger count = new BigInteger(pending, 16); + coinData.setUnconfirmedTXCount(count); + +// Log.i("$TAG eth_getPendingTxCount", pending) + } + break; + } + + if (serverApiRootstock.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String method, String message) { + if (!serverApiRootstock.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + serverApiRootstock.setRootstockResponse(rootstockBodyListener); + + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); + // request infura eth gasPrice listener + ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse rootstockResponse) { + String gasPrice = rootstockResponse.getResult(); + gasPrice = gasPrice.substring(2); + // rounding gas price to integer gwei + BigInteger l = new BigInteger(gasPrice, 16);//.divide(BigInteger.valueOf(1000000000L)).multiply(BigInteger.valueOf(1000000000L)); + + Log.i(TAG, "Rootstock gas price: " + gasPrice + " (" + l.toString() + ")"); + BigInteger m = BigInteger.valueOf(21000); + + Log.e(TAG, "fee multiplier: " + m.toString()); + + CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei"); + CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(12)).divide(BigInteger.valueOf(10)).multiply(m), "wei"); + CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(15)).divide(BigInteger.valueOf(10)).multiply(m), "wei"); + + Log.i(TAG, "min fee : " + weiMinFee.toValueString() + " wei"); + Log.i(TAG, "normal fee: " + weiNormalFee.toValueString() + " wei"); + Log.i(TAG, "max fee : " + weiMaxFee.toValueString() + " wei"); + + coinData.minFee = convertToAmount(weiMinFee); + coinData.normalFee = convertToAmount(weiNormalFee); + coinData.maxFee = convertToAmount(weiMaxFee); + + Log.i(TAG, "min fee : " + coinData.minFee.toString()); + Log.i(TAG, "normal fee: " + coinData.normalFee.toString()); + Log.i(TAG, "max fee : " + coinData.maxFee.toString()); + + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void onFail(String method, String message) { + ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiRootstock.setRootstockResponse(rootstockBodyListener); + + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { + + String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); + + ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); + ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse rootstockResponse) { + if (method.equals(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION)) { + if (rootstockResponse.getResult().isEmpty()) { + ctx.setError("Rejected by node: " + rootstockResponse.getError()); + blockchainRequestsCallbacks.onComplete(false); + } else { + BigInteger nonce = coinData.getConfirmedTXCount(); + nonce=nonce.add(BigInteger.valueOf(1)); + coinData.setConfirmedTXCount(nonce); + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } + } + + @Override + public void onFail(String method, String message) { + if (method.equals(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION)) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + serverApiRootstock.setRootstockResponse(rootstockBodyListener); + + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + + } + +} + From ec3fe160098ca3aeffe3e4ae8f205743b42a0ec0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Jan 2019 16:03:38 +0300 Subject: [PATCH 012/228] Updated on 2026-08-14 --- .../main/java/com/tangem/data/Blockchain.java | 3 +- .../com/tangem/data/network/RootstockApi.java | 15 + .../data/network/ServerApiRootstock.java | 4 +- .../java/com/tangem/di/NetworkComponent.java | 3 + .../java/com/tangem/di/NetworkModule.java | 10 + .../tangem/domain/wallet/CoinEngineFactory.kt | 6 + .../tangem/domain/wallet/TangemContext.java | 8 +- .../tangem/domain/wallet/rsk/RskEngine.java | 10 +- .../domain/wallet/rsk/RskTokenEngine.java | 546 ++++++++++++++++++ .../domain/wallet/token/TokenEngine.java | 10 +- .../presentation/fragment/LoadedWallet.kt | 4 +- app/src/main/res/values/strings.xml | 2 +- 12 files changed, 606 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/RootstockApi.java create mode 100644 app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index cf900eb9c4..81229b390b 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -14,7 +14,8 @@ public enum Blockchain { Token("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"), BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"), Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"), - Rootstock("RSK", "RBTC", 1.0, R.drawable.ic_logo_bitcoin, "Rootstock"); + Rootstock("RSK", "RBTC", 1.0, R.drawable.ic_logo_bitcoin, "Rootstock"), + RootstockToken("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Rootstock"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/RootstockApi.java b/app/src/main/java/com/tangem/data/network/RootstockApi.java new file mode 100644 index 0000000000..7faadfb4c0 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/RootstockApi.java @@ -0,0 +1,15 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.InfuraBody; +import com.tangem.data.network.model.InfuraResponse; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +public interface RootstockApi { + @Headers("Content-Type: application/json") + @POST(Server.ApiRootstock.Method.MAIN) + Call rootstock(@Body InfuraBody body); +} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java index 5b3cbdcc9c..988c383639 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java @@ -52,7 +52,7 @@ public class ServerApiRootstock { public void rootstock(String method, int id, String wallet, String contract, String tx) { requestsCount++; - InfuraApi infuraApi = App.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class); + RootstockApi rootstockApi = App.getNetworkComponent().getRetrofitRootstock().create(RootstockApi.class); InfuraBody infuraBody; switch (method) { @@ -80,7 +80,7 @@ public class ServerApiRootstock { infuraBody = new InfuraBody(); } - Call call = infuraApi.infura(infuraBody); + Call call = rootstockApi.rootstock(infuraBody); call.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { diff --git a/app/src/main/java/com/tangem/di/NetworkComponent.java b/app/src/main/java/com/tangem/di/NetworkComponent.java index 2d269162e8..f7367bdeb0 100644 --- a/app/src/main/java/com/tangem/di/NetworkComponent.java +++ b/app/src/main/java/com/tangem/di/NetworkComponent.java @@ -26,6 +26,9 @@ public interface NetworkComponent { @Named(Server.ApiUpdateVersion.URL_UPDATE_VERSION) Retrofit getRetrofitGithubusercontent(); + @Named(Server.ApiRootstock.URL_ROOTSTOCK) + Retrofit getRetrofitRootstock(); + // @Named("Insight") //TODO:check // Retrofit getRetrofitInsight(String insightURL); diff --git a/app/src/main/java/com/tangem/di/NetworkModule.java b/app/src/main/java/com/tangem/di/NetworkModule.java index b61754ffd6..e4ce02ddc8 100644 --- a/app/src/main/java/com/tangem/di/NetworkModule.java +++ b/app/src/main/java/com/tangem/di/NetworkModule.java @@ -31,6 +31,16 @@ class NetworkModule { .build(); } + @Singleton + @Provides + @Named(Server.ApiRootstock.URL_ROOTSTOCK) + Retrofit provideRetrofitRootstock() { + return new Retrofit.Builder() + .baseUrl(Server.ApiRootstock.URL_ROOTSTOCK) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + // //@Singleton // TODO:check // @Provides // @Named("Insight") diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt index 7bba67ec11..adf693ade8 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt @@ -9,6 +9,7 @@ import com.tangem.domain.wallet.bch.BtcCashEngine import com.tangem.data.Blockchain import com.tangem.domain.wallet.ltc.LtcEngine import com.tangem.domain.wallet.rsk.RskEngine +import com.tangem.domain.wallet.rsk.RskTokenEngine /** * Factory for create specific engine @@ -28,11 +29,14 @@ object CoinEngineFactory { Blockchain.Ethereum, Blockchain.EthereumTestNet -> EthEngine() Blockchain.Token -> TokenEngine() Blockchain.Litecoin -> LtcEngine() + Blockchain.Rootstock -> RskEngine() + Blockchain.RootstockToken -> RskTokenEngine() else -> null } } fun create(context: TangemContext): CoinEngine? { + var result: CoinEngine? try { result = if (Blockchain.BitcoinCash == context.blockchain) @@ -47,6 +51,8 @@ object CoinEngineFactory { LtcEngine(context) else if (Blockchain.Rootstock == context.blockchain) RskEngine(context) + else if (Blockchain.RootstockToken == context.blockchain) + RskTokenEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/domain/wallet/TangemContext.java b/app/src/main/java/com/tangem/domain/wallet/TangemContext.java index 5802887489..8a837bb611 100644 --- a/app/src/main/java/com/tangem/domain/wallet/TangemContext.java +++ b/app/src/main/java/com/tangem/domain/wallet/TangemContext.java @@ -33,6 +33,10 @@ public class TangemContext { { return Blockchain.Token; } + if( (blockchain==Blockchain.Rootstock)&& card.isToken() ) + { + return Blockchain.RootstockToken; + } return blockchain; } @@ -45,9 +49,9 @@ public class TangemContext { public String getBlockchainName() { Blockchain blockchain=getBlockchain(); - if( (blockchain==Blockchain.Ethereum || blockchain==Blockchain.EthereumTestNet)&& card.isToken() ) { + if( blockchain==Blockchain.Token || blockchain==Blockchain.RootstockToken ) { String token = card.getTokenSymbol(); - return token + "
" + getBlockchain().getOfficialName() + " ERC20 token"; + return token + "
" + getBlockchain().getOfficialName() + " smart contract token"; }else { return blockchain.getOfficialName(); } diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java index e2c1c83e17..7d34f079cb 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java @@ -28,7 +28,7 @@ public class RskEngine extends EthEngine { private static final String TAG = RskEngine.class.getSimpleName(); public RskEngine(TangemContext ctx) throws Exception { - super(ctx); + this.ctx = ctx; if (ctx.getCoinData() == null) { coinData = new EthData(); ctx.setCoinData(coinData); @@ -39,6 +39,10 @@ public class RskEngine extends EthEngine { } } + public RskEngine() { + super(); + } + private static int getDecimals() { return 18; } @@ -146,7 +150,7 @@ public class RskEngine extends EthEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request infura listener + // request rootstock listener ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { @@ -209,7 +213,7 @@ public class RskEngine extends EthEngine { @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request infura eth gasPrice listener + // request rootstock gasPrice listener ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java new file mode 100644 index 0000000000..7898475ada --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java @@ -0,0 +1,546 @@ +package com.tangem.domain.wallet.rsk; + +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +import com.google.common.base.Strings; +import com.tangem.data.Blockchain; +import com.tangem.data.network.ServerApiRootstock; +import com.tangem.data.network.model.InfuraResponse; +import com.tangem.domain.wallet.BTCUtils; +import com.tangem.domain.wallet.CoinEngine; +import com.tangem.domain.wallet.ECDSASignatureETH; +import com.tangem.domain.wallet.EthTransaction; +import com.tangem.domain.wallet.TangemContext; +import com.tangem.domain.wallet.eth.EthData; +import com.tangem.domain.wallet.token.TokenData; +import com.tangem.domain.wallet.token.TokenEngine; +import com.tangem.tangemcard.data.TangemCard; +import com.tangem.tangemcard.tasks.SignTask; +import com.tangem.util.CryptoUtil; + +import org.bitcoinj.core.ECKey; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.Arrays; + +public class RskTokenEngine extends TokenEngine { + + private static final String TAG = RskTokenEngine.class.getSimpleName(); + + public RskTokenEngine(TangemContext ctx) throws Exception { + super(ctx); + if (ctx.getCoinData() == null) { + coinData = new TokenData(); + ctx.setCoinData(coinData); + } else if (ctx.getCoinData() instanceof TokenData) { + coinData = (TokenData) ctx.getCoinData(); + } else if (ctx.getCoinData() instanceof EthData) { + // special case with receive card data substitution from server at the moment + Bundle B=new Bundle(); + ctx.getCoinData().saveToBundle(B); + coinData = new TokenData(); + coinData.loadFromBundle(B); + ctx.setCoinData(coinData); + } else { + throw new Exception("Invalid type of Blockchain data for RskTokenEngine"); + } + } + + public RskTokenEngine() { + super(); + } + + @Override + public String getBalanceCurrency() { + String currency = ctx.getCard().getTokenSymbol(); + if (Strings.isNullOrEmpty(currency)) + return "NoN"; + if (hasBalanceInfo()) { + if (coinData.getBalanceInInternalUnits().notZero()) { + return currency; + } else { + return Blockchain.Rootstock.getCurrency(); + } + } else { + return currency; + } + } + + @Override + public String getFeeCurrency() { + return Blockchain.Rootstock.getCurrency(); + } + + @Override + public Amount convertToAmount(InternalAmount internalAmount) throws Exception { + if (internalAmount.getCurrency().equals("wei")) { + BigDecimal d = internalAmount.divide(new BigDecimal("1000000000000000000"), getEthDecimals(), RoundingMode.DOWN); + return new Amount(d, Blockchain.Rootstock.getCurrency()); + } else if (internalAmount.getCurrency().equals(ctx.getCard().getTokenSymbol())) { + BigDecimal p = new BigDecimal(10); + p = p.pow(getTokenDecimals()); + BigDecimal d = internalAmount.divide(p); + return new Amount(d, ctx.getCard().getTokenSymbol()); + } + throw new Exception(String.format("Can't convert '%s' to '%s'", internalAmount.getCurrency(), ctx.getCard().getTokenSymbol())); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) throws Exception { + if (amount.getCurrency().equals(Blockchain.Rootstock.getCurrency())) { + BigDecimal d = amount.multiply(new BigDecimal("1000000000000000000")); + return new InternalAmount(d, "wei"); + } else if (amount.getCurrency().equals(ctx.getCard().getTokenSymbol())) { + BigDecimal p = new BigDecimal(10); + p = p.pow(getTokenDecimals()); + BigDecimal d = amount.multiply(p); + return new InternalAmount(d, ctx.getCard().getTokenSymbol()); + } + throw new Exception(String.format("Can't convert '%s' to '%s'", amount.getCurrency(), ctx.getCard().getTokenSymbol())); + } + + @Override + public Uri getShareWalletUriExplorer() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } // Only RSK explorer for now + + @Override + public Uri getShareWalletUri() { return Uri.parse(ctx.getCoinData().getWallet()); } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (!hasBalanceInfo()) return false; + Amount balance; + try { + if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) { + balance = convertToAmount(coinData.getBalanceInInternalUnits()); + } else if (amount.getCurrency().equals(Blockchain.Rootstock.getCurrency()) && coinData.getBalanceInInternalUnits().isZero()) { + balance = convertToAmount(coinData.getBalanceAlterInInternalUnits()); + } else { + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return amount.compareTo(balance) <= 0; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) { + if (!hasBalanceInfo()) return false; + + try { + Amount balanceRBTC = convertToAmount(coinData.getBalanceAlterInInternalUnits()); + + if (fee == null || amount == null || fee.isZero() || amount.isZero()) + return false; + + + if (amount.getCurrency().equals(ctx.getCard().tokenSymbol)) { + // token transaction + if (fee.compareTo(balanceRBTC) > 0) + return false; + } else if (amount.getCurrency().equals(Blockchain.Rootstock.getCurrency()) && coinData.getBalanceInInternalUnits().isZero()) { + // standard RBTC transaction +// try { + if (isFeeIncluded && (amount.compareTo(balanceRBTC) > 0 || fee.compareTo(balanceRBTC) > 0)) + return false; + + if (!isFeeIncluded && amount.add(fee).compareTo(balanceRBTC) > 0) + return false; + +// } catch (NumberFormatException e) { +// e.printStackTrace(); +// } + } else + + { + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + return true; + } + + @Override + public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + if (amountValue.getCurrency().equals(Blockchain.Rootstock.getCurrency())) { + return constructPaymentRBTC(feeValue, amountValue, IncFee, targetAddress); + } else { + return constructPaymentToken(feeValue, amountValue, IncFee, targetAddress); + } + } + + private SignTask.PaymentToSign constructPaymentRBTC(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { + Log.e(TAG, "Construct RBTC payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); + + BigInteger nonceValue = coinData.getConfirmedTXCount(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + + BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact(); + BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact(); + + if (IncFee) { + weiAmount = weiAmount.subtract(weiFee); + } + + BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000)); + BigInteger gasLimit = BigInteger.valueOf(21000); + Integer chainId = EthTransaction.ChainEnum.Rootstock_mainnet.getValue(); + + String to = targetAddress; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); + + return new SignTask.PaymentToSign() { + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash; + } + + @Override + public byte[][] getHashesToSign() { + byte[][] hashesForSign = new byte[1][]; + hashesForSign[0] = tx.getRawHash(); + return hashesForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for RSK"); + } + + @Override + public String getHashAlgToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for RSK"); + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Transaction validation by issuer not supported in this version"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + byte[] for_hash = tx.getRawHash(); + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if (!f) { + Log.e("RSK-CHECK", "sign Failed."); + } + + tx.signature = new ECDSASignatureETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e(TAG, "invalid v"); + throw new Exception("Error in RskTokenEngine - invalid v"); + } + tx.signature.v = (byte) v; + Log.e(TAG,"RSK_v "+ String.valueOf(v)); + + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendPayment(txForSend); + return txForSend; + } + }; + } + + private SignTask.PaymentToSign constructPaymentToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { + Log.e(TAG, "Construct TOKEN payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); + + BigInteger nonceValue = coinData.getConfirmedTXCount(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); +// boolean flag = (ctx.getCard().getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer); +// Issuer issuer = ctx.getCard().getIssuer(); + + +// BigInteger gigaK = BigInteger.valueOf(1000000000L); + + BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact(); + + InternalAmount amountDec = convertToInternalAmount(amountValue); + BigInteger amount = amountDec.toBigInteger(); //new BigInteger(amountValue, 10); + + + //amount = amount.subtract(fee); + + BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(60000)); + BigInteger gasLimit = BigInteger.valueOf(60000); + Integer chainId = EthTransaction.ChainEnum.Rootstock_mainnet.getValue(); + BigInteger amountZero = BigInteger.ZERO; + + String to = targetAddress; + + if (to.startsWith("0x") || to.startsWith("0X")) { + to = to.substring(2); + } + + String contractAddress = getContractAddress(ctx.getCard()); + + if (contractAddress.startsWith("0x") || contractAddress.startsWith("0X")) { + contractAddress = contractAddress.substring(2); + } + + String amountLeadZero = amount.toString(16); + if (amountLeadZero.startsWith("0x") || amountLeadZero.startsWith("0X")) { + amountLeadZero = amountLeadZero.substring(2); + } + + while (amountLeadZero.length() < 64) { + amountLeadZero = "0" + amountLeadZero; + } + + String cmd = "a9059cbb000000000000000000000000" + to + amountLeadZero; //TODO only for BAT + + + byte[] data = BTCUtils.fromHex(cmd); + EthTransaction tx = EthTransaction.create(contractAddress, amountZero, nonceValue, gasPrice, gasLimit, chainId, data); + + return new SignTask.PaymentToSign() { + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash; + } + + @Override + public byte[][] getHashesToSign() { + byte[][] hashesForSign = new byte[1][]; + hashesForSign[0] = tx.getRawHash(); + return hashesForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for RSK"); + } + + @Override + public String getHashAlgToSign() throws Exception { + throw new Exception("Signing of raw transaction not supported for RSK"); + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Transaction validation by issuer not supported in this version"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + byte[] for_hash = tx.getRawHash(); + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64)); + s = CryptoUtil.toCanonicalised(s); + + boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), pbKey); + + if (!f) { + Log.e("RSK-CHECK", "sign Failed."); + } + + tx.signature = new ECDSASignatureETH(r, s); + int v = tx.BruteRecoveryID2(tx.signature, for_hash, pbKey); + if (v != 27 && v != 28) { + Log.e(TAG, "invalid v"); + throw new Exception("Error in RskTokenEngine - invalid v"); + } + tx.signature.v = (byte) v; + Log.e(TAG,"RSK_v: "+ String.valueOf(v)); + + byte[] txForSend = tx.getEncoded(); + notifyOnNeedSendPayment(txForSend); + return txForSend; + + } + }; + + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); + // request rootstock listener + ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse rootstockResponse) { + switch (method) { + case ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE: { + String balanceCap = rootstockResponse.getResult(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + coinData.setBalanceReceived(true); + coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); + +// Log.i("$TAG eth_get_balance", balanceCap) + } + break; + + case ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT: { + String nonce = rootstockResponse.getResult(); + nonce = nonce.substring(2); + BigInteger count = new BigInteger(nonce, 16); + coinData.setConfirmedTXCount(count); + + +// Log.i("$TAG eth_getTransCount", nonce) + } + break; + + case ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT: { + String pending = rootstockResponse.getResult(); + pending = pending.substring(2); + BigInteger count = new BigInteger(pending, 16); + coinData.setUnconfirmedTXCount(count); + +// Log.i("$TAG eth_getPendingTxCount", pending) + } + break; +// + case ServerApiRootstock.ROOTSTOCK_ETH_CALL: { + try { + String balanceCap = rootstockResponse.getResult(); + balanceCap = balanceCap.substring(2); + BigInteger l = new BigInteger(balanceCap, 16); + coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); + +// Log.i("$TAG eth_call", balanceCap) + + if (blockchainRequestsCallbacks.allowAdvance()) { + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + } else { + ctx.setError("Terminated by user"); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + break; + + } + if (serverApiRootstock.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + @Override + public void onFail(String method, String message) { + if (!serverApiRootstock.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + serverApiRootstock.setRootstockResponse(rootstockBodyListener); + + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); + // request rootstock gasPrice listener + ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse rootstockResponse) { + String gasPrice = rootstockResponse.getResult(); + gasPrice = gasPrice.substring(2); + + // rounding gas price to integer gwei + BigInteger l = new BigInteger(gasPrice, 16); + + Log.i(TAG, "Rootstock gas price: "+gasPrice+" ("+l.toString()+")"); + BigInteger m; + if (!amount.getCurrency().equals(Blockchain.Rootstock.getCurrency())) m = BigInteger.valueOf(60000); + else m = BigInteger.valueOf(21000); + + Log.i(TAG, "fee multiplier: "+m.toString()); + + CoinEngine.InternalAmount weiMinFee = new CoinEngine.InternalAmount(l.multiply(m), "wei"); + CoinEngine.InternalAmount weiNormalFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(12)).divide(BigInteger.valueOf(10)).multiply(m), "wei"); + CoinEngine.InternalAmount weiMaxFee = new CoinEngine.InternalAmount(l.multiply(BigInteger.valueOf(15)).divide(BigInteger.valueOf(10)).multiply(m), "wei"); + Log.i(TAG, "min fee : "+weiMinFee.toValueString()+" wei"); + Log.i(TAG, "normal fee: "+weiNormalFee.toValueString()+" wei"); + Log.i(TAG, "max fee : "+weiMaxFee.toValueString()+" wei"); + + try { + coinData.minFee = convertToAmount(weiMinFee); + coinData.normalFee = convertToAmount(weiNormalFee); + coinData.maxFee = convertToAmount(weiMaxFee); + Log.i(TAG, "min fee : "+coinData.minFee.toString()); + Log.i(TAG, "normal fee: "+coinData.normalFee.toString()); + Log.i(TAG, "max fee : "+coinData.maxFee.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void onFail(String method, String message) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + }; + serverApiRootstock.setRootstockResponse(rootstockBodyListener); + + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { + + String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); + + ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); + // request rootstock listener + ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + @Override + public void onSuccess(String method, InfuraResponse infuraResponse) { + if (method.equals(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION)) { + if (infuraResponse.getResult().isEmpty()) { + ctx.setError("Rejected by node: " + infuraResponse.getError()); + blockchainRequestsCallbacks.onComplete(false); + } else { + BigInteger nonce = coinData.getConfirmedTXCount(); + nonce=nonce.add(BigInteger.valueOf(1)); + coinData.setConfirmedTXCount(nonce); + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } + } + + @Override + public void onFail(String method, String message) { + if (method.equals(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION)) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + serverApiRootstock.setRootstockResponse(rootstockBodyListener); + + serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + + } + +} diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java index 23bdd7d2e5..dbba036f60 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java @@ -90,7 +90,7 @@ public class TokenEngine extends CoinEngine { public String getBalanceHTML() { if (hasBalanceInfo()) { try { - return " " + convertToAmount(coinData.getBalanceInInternalUnits()).toDescriptionString(getTokenDecimals()) + "
+ " + convertToAmount(coinData.getBalanceAlterInInternalUnits()).toDescriptionString(getEthDecimals()) + " for gas"; + return " " + convertToAmount(coinData.getBalanceInInternalUnits()).toDescriptionString(getTokenDecimals()) + "
+ " + convertToAmount(coinData.getBalanceAlterInInternalUnits()).toDescriptionString(getEthDecimals()) + " for fee"; } catch (Exception e) { e.printStackTrace(); return ""; @@ -136,15 +136,15 @@ public class TokenEngine extends CoinEngine { return ctx.getString(R.string.not_implemented); } - private static int getEthDecimals() { + protected static int getEthDecimals() { return 18; } - private int getTokenDecimals() { + protected int getTokenDecimals() { return ctx.getCard().getTokensDecimal(); } - private String getContractAddress(TangemCard card) { + protected String getContractAddress(TangemCard card) { return card.getContractAddress(); } @@ -311,7 +311,7 @@ public class TokenEngine extends CoinEngine { } else if (awaitingConfirmation()) { ctx.setMessage(R.string.please_wait_while_previous); } else if (!isBalanceAlterNotZero()) { - ctx.setMessage(ctx.getString(R.string.not_enough_eth_for_gas)); + ctx.setMessage(ctx.getString(R.string.not_enough_eth_for_fee)); } else { return true; } 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 ca23b0aa1d..5f1dcb1c92 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -106,7 +106,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (ctx.blockchain == Blockchain.Token) + if (ctx.blockchain == Blockchain.Token || ctx.blockchain == Blockchain.RootstockToken) tvBalance.setSingleLine(false) ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card)) @@ -705,6 +705,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific Blockchain.Token -> "ethereum" Blockchain.BitcoinCash -> "bitcoin-cash" Blockchain.Litecoin -> "litecoin" + Blockchain.Rootstock -> "bitcoin" + Blockchain.RootstockToken ->"bitcoin" else -> { throw Exception("Can''t get rate for blockchain " + ctx.blockchainName) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3c8bf2fa60..78c21d84cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,7 +189,7 @@ Cannot calculate fee! Wrong data received from the node The wallet is empty Please wait for confirmation of incoming transaction - Not enough ETH funds for gas + Not enough ETH funds for fee PIN2 is required to sign the payment Not enough funds From a2e21a5d11c8868298980e3d535d27501b69acaa Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Jan 2019 18:59:18 +0300 Subject: [PATCH 013/228] Updated on 2026-08-14 --- .../domain/wallet/rsk/RskTokenEngine.java | 17 +++++++++++++++++ .../tangem/domain/wallet/token/TokenEngine.java | 2 +- .../activity/PreparePaymentActivity.kt | 3 ++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java index 7898475ada..f571505f4b 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java @@ -19,6 +19,7 @@ import com.tangem.domain.wallet.token.TokenEngine; import com.tangem.tangemcard.data.TangemCard; import com.tangem.tangemcard.tasks.SignTask; import com.tangem.util.CryptoUtil; +import com.tangem.wallet.R; import org.bitcoinj.core.ECKey; @@ -109,6 +110,22 @@ public class RskTokenEngine extends TokenEngine { @Override public Uri getShareWalletUri() { return Uri.parse(ctx.getCoinData().getWallet()); } + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.cannot_obtain_data_from_blockchain); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.please_wait_while_previous); + } else if (!isBalanceAlterNotZero()) { + ctx.setMessage(ctx.getString(R.string.not_enough_rbtc_for_fee)); + } else { + return true; + } + return false; + } + @Override public boolean checkNewTransactionAmount(Amount amount) { if (!hasBalanceInfo()) return false; diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java index dbba036f60..dd6e5fb76f 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java @@ -169,7 +169,7 @@ public class TokenEngine extends CoinEngine { return true; } - private boolean isBalanceAlterNotZero() { + protected boolean isBalanceAlterNotZero() { if (coinData == null) return false; if (coinData.getBalanceAlterInInternalUnits() == null) return false; return coinData.getBalanceAlterInInternalUnits().notZero(); diff --git a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt index faeffdc9a7..a0adf06876 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt @@ -60,7 +60,8 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { tvBalance.text = html //TODO - to engine - if (ctx.blockchain == Blockchain.Token && engine.balance.currency!=Blockchain.Ethereum.currency) { + if ((ctx.blockchain == Blockchain.Token && engine.balance.currency!=Blockchain.Ethereum.currency) || + ((ctx.blockchain == Blockchain.RootstockToken && engine.balance.currency!=Blockchain.Rootstock.currency))){ rgIncFee!!.visibility = View.INVISIBLE } else { rgIncFee!!.visibility = View.VISIBLE diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 78c21d84cb..0249a2a10d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,6 +190,7 @@ The wallet is empty Please wait for confirmation of incoming transaction Not enough ETH funds for fee + Not enough RBTC funds for fee PIN2 is required to sign the payment Not enough funds From 6a14bb202fa875f165c0d9be5596379dc119973b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Jan 2019 12:55:20 +0300 Subject: [PATCH 014/228] Updated on 2026-08-14 --- .../main/java/com/tangem/data/Blockchain.java | 4 +- .../tangem/data/network/CoinmarketApi.java | 11 +-- .../java/com/tangem/data/network/Server.java | 2 +- .../tangem/data/network/ServerApiCommon.java | 55 ++++++++---- .../com/tangem/data/network/ServerURL.java | 2 +- .../data/network/model/RateInfoResponse.kt | 83 ++++--------------- .../presentation/fragment/LoadedWallet.kt | 41 +++++---- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 9 files changed, 85 insertions(+), 119 deletions(-) diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 81229b390b..f3472beb19 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -11,11 +11,11 @@ public enum Blockchain { BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_testnet, "Bitcoin Testnet"), Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"), - Token("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"), + Token("Token", "ETH", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"), BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"), Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"), Rootstock("RSK", "RBTC", 1.0, R.drawable.ic_logo_bitcoin, "Rootstock"), - RootstockToken("Token", "ERC20", 1.0, R.drawable.ic_logo_bat_token, "Rootstock"); + RootstockToken("RSKToken", "RBTC", 1.0, R.drawable.ic_logo_bat_token, "Rootstock"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/CoinmarketApi.java b/app/src/main/java/com/tangem/data/network/CoinmarketApi.java index 9a101000fa..d14422cc88 100644 --- a/app/src/main/java/com/tangem/data/network/CoinmarketApi.java +++ b/app/src/main/java/com/tangem/data/network/CoinmarketApi.java @@ -2,12 +2,13 @@ package com.tangem.data.network; import com.tangem.data.network.model.RateInfoResponse; -import java.util.List; - -import io.reactivex.Observable; +import retrofit2.Call; import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.Query; public interface CoinmarketApi { - @GET(Server.ApiCoinmarket.Method.V1_TICKER_CONVERT) - Observable> getRateInfoList(); + @Headers("X-CMC_PRO_API_KEY: 7850b957-6943-42eb-af73-8d536dd8f73e") + @GET(Server.ApiCoinmarket.Method.PRICE_CONVERSION) + Call getRateInfo(@Query("amount") int amount, @Query("symbol") String cryptoId); } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 9be4f58449..85db1a8643 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -17,7 +17,7 @@ public class Server { public static final String URL_COINMARKET = ServerURL.API_COINMARKETCAP; public static class Method { - static final String V1_TICKER_CONVERT = URL_COINMARKET + "v1/ticker/?convert=USD&lmit=10"; + static final String PRICE_CONVERSION = URL_COINMARKET + "v1/tools/price-conversion"; } } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java index 7ab8fe42e0..43112f0219 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java @@ -83,32 +83,53 @@ public class ServerApiCommon { public interface RateInfoDataListener { void onSuccess(RateInfoResponse rateInfoResponse); + + void onFail(String message); } public void setRateInfoData(RateInfoDataListener listener) { rateInfoDataListener = listener; } - @SuppressLint("CheckResult") public void rateInfoData(String cryptoId) { CoinmarketApi coinmarketApi = App.getNetworkComponent().getRetrofitCoinmarketcap().create(CoinmarketApi.class); - coinmarketApi.getRateInfoList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(rateInfoModelList -> { - if (!rateInfoModelList.isEmpty()) { - for (RateInfoResponse rateInfoMode : rateInfoModelList) { - if (rateInfoMode.getId().equals(cryptoId)) { - rateInfoDataListener.onSuccess(rateInfoMode); - Log.i(TAG, "rateInfoData " + cryptoId + " onResponse " + "200"); - } - } - } else - Log.e(TAG, "rateInfoData " + cryptoId + " onResponse " + "Empty"); - }, - // handle error - Throwable::printStackTrace); + Call call = coinmarketApi.getRateInfo(1, cryptoId); + + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + rateInfoDataListener.onSuccess(response.body()); + Log.i(TAG, "coinmarketcap onResponse " + response.code()); + } else { + rateInfoDataListener.onFail(String.valueOf(response.code())); + Log.e(TAG, "coinmarketcap onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + rateInfoDataListener.onFail(String.valueOf(t.getMessage())); + Log.e(TAG, "coinmarketcap onFailure " + t.getMessage()); + } + }); +// coinmarketApi.getRateInfoList() +// .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe(rateInfoModelList -> { +// if (!rateInfoModelList.isEmpty()) { +// for (RateInfoResponse rateInfoMode : rateInfoModelList) { +// if (rateInfoMode.getId().equals(cryptoId)) { +// rateInfoDataListener.onSuccess(rateInfoMode); +// Log.i(TAG, "rateInfoData " + cryptoId + " onResponse " + "200"); +// } +// } +// } else +// Log.e(TAG, "rateInfoData " + cryptoId + " onResponse " + "Empty"); +// }, +// // handle error +// Throwable::printStackTrace); } /** diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index b33d4c4ba4..3dc1bf5064 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -2,7 +2,7 @@ package com.tangem.data.network; class ServerURL { static final String API_TANGEM = "https://verify.tangem.com/"; - static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/"; + static final String API_COINMARKETCAP = "https://sandbox-api.coinmarketcap.com/"; //TODO: change to "https://pro-api.coinmarketcap.com/" for production environment static final String API_INFURA = "https://mainnet.infura.io/"; static final String API_ESTIMATEFEE = "https://estimatefee.com/"; static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/"; diff --git a/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt b/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt index 359c450fec..6fd76e8581 100644 --- a/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt +++ b/app/src/main/java/com/tangem/data/network/model/RateInfoResponse.kt @@ -1,76 +1,23 @@ package com.tangem.data.network.model -import android.os.Parcelable import com.google.gson.annotations.SerializedName -import kotlinx.android.parcel.Parcelize -@Parcelize data class RateInfoResponse( - @SerializedName(ID) - var id: String = "", + @SerializedName("data") + var data: RateData? = null +) - @SerializedName(NAME) - var name: String = "", +data class RateData( + @SerializedName("quote") + var quote: Quote? = null +) - @SerializedName(SYMBOL) - var symbol: String = "", +data class Quote( + @SerializedName("USD") + var usd: CurrencyRate? = null +) - @SerializedName(RANK) - var rank: String = "", - - @SerializedName(PRICE_USD) - var priceUsd: String = "", - - @SerializedName(PRICE_BTC) - var priceBtc: String = "", - - @SerializedName(VOLUME_USD_24H) - var volumeUsd24h: String = "", - - @SerializedName(MARKET_CAP_USD) - var marketCapUsd: String = "", - - @SerializedName(AVAILABLE_SUPPLY) - var availableSupply: String = "", - - @SerializedName(TOTAL_SUPPLY) - var totalSupply: String = "", - - @SerializedName(MAX_SUPPLY) - var maxSupply: String = "", - - @SerializedName(PERCENT_CHANGE_1H) - var percentChange1h: String = "", - - @SerializedName(PERCENT_CHANGE_24H) - var percentChange24h: String = "", - - @SerializedName(PERCENT_CHANGE_7H) - var percentChange7h: String = "", - - @SerializedName(LAST_UPDATED) - var lastUpdated: String = "" - -) : Parcelable { - - companion object { - val TAG: String = RateInfoResponse::class.java.simpleName - - const val ID = "id" - const val NAME = "name" - const val SYMBOL = "symbol" - const val RANK = "rank" - const val PRICE_USD = "price_usd" - const val PRICE_BTC = "price_btc" - const val VOLUME_USD_24H = "24h_volume_usd" - const val MARKET_CAP_USD = "market_cap_usd" - const val AVAILABLE_SUPPLY = "available_supply" - const val TOTAL_SUPPLY = "total_supply" - const val MAX_SUPPLY = "max_supply" - const val PERCENT_CHANGE_1H = "percent_change_1h" - const val PERCENT_CHANGE_24H = "percent_change_24h" - const val PERCENT_CHANGE_7H = "percent_change_7d" - const val LAST_UPDATED = "last_updated" - } - -} \ No newline at end of file +data class CurrencyRate( + @SerializedName("price") + var price: Float? = null +) \ 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 5f1dcb1c92..99e9d96208 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -22,6 +22,7 @@ import com.tangem.App import com.tangem.Constant import com.tangem.data.Blockchain import com.tangem.data.network.ServerApiCommon +import com.tangem.data.network.model.RateInfoResponse import com.tangem.domain.wallet.BalanceValidator import com.tangem.domain.wallet.CoinEngine import com.tangem.domain.wallet.CoinEngineFactory @@ -258,12 +259,24 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific serverApiTangem.setArtworkListener(artworkListener) // request rate info listener - serverApiCommon.setRateInfoData { - if (activity == null || !UtilHelper.isOnline(activity!!)) return@setRateInfoData - val rate = it.priceUsd.toFloat() - ctx.coinData!!.rate = rate - ctx.coinData!!.rateAlter = rate + val rateInfoDataListener: ServerApiCommon.RateInfoDataListener = object : ServerApiCommon.RateInfoDataListener { + + override fun onSuccess(rateInfoResponse: RateInfoResponse?){ + ctx.coinData!!.rate = rateInfoResponse!!.data!!.quote!!.usd!!.price!! + } + + override fun onFail(message: String?){ + ctx.error = message + } } + serverApiCommon.setRateInfoData(rateInfoDataListener) + +// serverApiCommon.setRateInfoData { +// if (activity == null || !UtilHelper.isOnline(activity!!)) return@setRateInfoData +// val rate = it.priceUsd.toFloat() +// ctx.coinData!!.rate = rate +// ctx.coinData!!.rateAlter = rate +// } refresh() @@ -695,23 +708,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific private fun requestRateInfo() { if (UtilHelper.isOnline(context as Activity)) { LOG.i(TAG, "requestRateInfo") - - // TODO - move requestRateInfo to CoinEngine - val cryptoId: String = when (ctx.blockchain) { - Blockchain.Bitcoin -> "bitcoin" - Blockchain.BitcoinTestNet -> "bitcoin" - Blockchain.Ethereum -> "ethereum" - Blockchain.EthereumTestNet -> "ethereum" - Blockchain.Token -> "ethereum" - Blockchain.BitcoinCash -> "bitcoin-cash" - Blockchain.Litecoin -> "litecoin" - Blockchain.Rootstock -> "bitcoin" - Blockchain.RootstockToken ->"bitcoin" - else -> { - throw Exception("Can''t get rate for blockchain " + ctx.blockchainName) - } - } - serverApiCommon.rateInfoData(cryptoId) + serverApiCommon.rateInfoData(ctx.blockchain.currency) } else { ctx.error = getString(R.string.no_connection) updateViews() diff --git a/build.gradle b/build.gradle index 3942b0608f..03239d7756 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7368b742cc..227eb11196 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 25 21:50:16 MSK 2018 +#Wed Jan 16 11:40:34 MSK 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip From 7eae28caf02592451ff3e64b167aabbb38c98139 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Jan 2019 13:58:20 +0300 Subject: [PATCH 015/228] Updated on 2026-08-14 --- .../domain/wallet/bch/BitcoinCashNode.kt | 30 ++-- .../tangem/domain/wallet/btc/BitcoinNode.kt | 129 ++++++++---------- .../tangem/domain/wallet/ltc/LitecoinNode.kt | 7 +- 3 files changed, 70 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt b/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt index e08f74eacf..40a32ea6e1 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt @@ -4,21 +4,17 @@ enum class BitcoinCashNode(val host: String, val port: Int, val proto: String) { N_001("electrumx.hillsideinternet.com", 50002, "ssl"), N_002("bch0.kister.net", 50002, "ssl"), N_003("abc1.hsmiths.com", 60002, "ssl"), - N_004("bch.curalle.ovh", 50002, "ssl"), - N_005("207.180.215.112", 52002, "ssl"), - N_006("bch.imaginary.cash", 50002, "ssl"), - N_007("dedi.jochen-hoenicke.de", 51002, "ssl"), - N_008("crypto.mldlabs.com", 50002, "ssl"), - N_009("bch.electrumx.cash", 50002, "ssl"), - N_010("electroncash.cascharia.com", 50002, "ssl"), - N_011("bch.crypto.mldlabs.com", 50002, "ssl"), - N_012("electron-cash.dragon.zone", 50002, "ssl"), - N_013("electron.coinucopia.io", 50002, "ssl"), - N_014("blackie.c3-soft.com", 50002, "ssl"), - N_015("electroncash.ueo.ch", 51002, "ssl"), - N_016("electrum.imaginary.cash", 50002, "ssl"), - N_017("35.157.238.5", 51002, "ssl"), - N_018("bitcoincash.quangld.com", 50002, "ssl"), - N_019("bch.stitthappens.com", 50002, "ssl"), - N_020("electroncash.dk", 50002, "ssl"), + N_004("bch.imaginary.cash", 50002, "ssl"), + N_005("dedi.jochen-hoenicke.de", 51002, "ssl"), + N_006("crypto.mldlabs.com", 50002, "ssl"), + N_007("electroncash.cascharia.com", 50002, "ssl"), + N_008("bch.crypto.mldlabs.com", 50002, "ssl"), + N_009("electron-cash.dragon.zone", 50002, "ssl"), + N_010("electron.coinucopia.io", 50002, "ssl"), + N_011("blackie.c3-soft.com", 50002, "ssl"), + N_012("electroncash.ueo.ch", 51002, "ssl"), + N_013("electrum.imaginary.cash", 50002, "ssl"), + N_014("bitcoincash.quangld.com", 50002, "ssl"), + N_015("bch.stitthappens.com", 50002, "ssl"), + N_016("electroncash.dk", 50002, "ssl"), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt b/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt index 0d5d7cb997..a8bfc5b3ee 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt @@ -7,79 +7,58 @@ enum class BitcoinNode(val host: String, val port: Int, val proto: String) { N_004("ip239.ip-54-36-234.eu", 50001, "tcp"), N_005("electrum-server.ninja", 50001, "tcp"), N_006("174.138.11.174", 50001, "tcp"), - N_007("ndnd.selfhost.eu", 50001, "tcp"), - N_008("btc.cihar.com", 50001, "tcp"), - N_009("vps.hsmiths.com", 8080, "tcp"), - N_010("electrum.hsmiths.com", 8080, "tcp"), - N_011("ip120.ip-54-37-91.eu", 50001, "tcp"), - N_012("vps.hsmiths.com", 50001, "tcp"), - N_013("orannis.com", 50001, "tcp"), - N_014("ip101.ip-54-37-91.eu", 50001, "tcp"), - N_015("e-x.not.fyi", 50001, "tcp"), - N_016("electrum.hsmiths.com", 50001, "tcp"), - N_017("electrum.vom-stausee.de", 50001, "tcp"), - N_018("bitcoin.corgi.party", 50001, "tcp"), - N_019("electrum2.eff.ro", 50001, "tcp"), - N_020("electrum.coinucopia.io", 50001, "tcp"), - N_021("electrum.eff.ro", 50001, "tcp"), - N_022("btc.xskyx.net", 50001, "tcp"), - N_023("kirsche.emzy.de", 50001, "tcp"), - N_024("electrum.petrkr.net", 50001, "tcp"), - N_025("btc.knas.systems", 50001, "tcp"), - N_026("b.ooze.cc", 50002, "ssl"), - N_027("electrum.nute.net", 50002, "ssl"), - N_028("ndnd.selfhost.eu", 50002, "ssl"), - N_029("electrum.coinop.cc", 50002, "ssl"), - N_030("orannis.com", 50002, "ssl"), - N_031("electrum.vom-stausee.de", 50002, "ssl"), - N_032("ip119.ip-54-37-91.eu", 50002, "ssl"), - N_033("ip101.ip-54-37-91.eu", 50002, "ssl"), - N_034("electrum2.villocq.com", 50002, "ssl"), - N_035("dedi.jochen-hoenicke.de", 50002, "ssl"), - N_036("174.138.11.174", 50002, "ssl"), - N_037("tomscryptos.com", 50002, "ssl"), - N_038("elec.luggs.co", 443, "ssl"), - N_039("ip239.ip-54-36-234.eu", 50002, "ssl"), - N_040("bitcoins.sk", 50002, "ssl"), - N_041("btc.cihar.com", 50002, "ssl"), - N_042("e-x.not.fyi", 50002, "ssl"), - N_043("ip120.ip-54-37-91.eu", 50002, "ssl"), - N_044("electrum.villocq.com", 50002, "ssl"), - N_045("electrum.anduck.net", 50012, "ssl"), - N_046("technetium.network", 50002, "ssl"), - N_047("electrum.coinucopia.io", 50002, "ssl"), - N_048("currentlane.lovebitco.in", 50002, "ssl"), - N_049("dimon.trimon.de", 50002, "ssl"), - N_050("rbx.curalle.ovh", 50002, "ssl"), - N_051("btc.gravitech.net", 50002, "ssl"), - N_052("hetzner01.fischl-online.de", 50002, "ssl"), - N_053("fn.48.org", 50002, "ssl"), - N_054("185.64.116.15", 50002, "ssl"), - N_055("kirsche.emzy.de", 50002, "ssl"), - N_056("109.192.105.174", 50002, "ssl"), - N_057("fedaykin.goip.de", 50002, "ssl"), - N_058("vps.hsmiths.com", 50002, "ssl"), - N_059("104.250.141.242", 50002, "ssl"), - N_060("electrum.qtornado.com", 50002, "ssl"), - N_061("electrum-server.ninja", 50002, "ssl"), - N_062("electrum2.eff.ro", 50002, "ssl"), - N_063("electrum.hsmiths.com", 995, "ssl"), - N_064("electrum.hsmiths.com", 50002, "ssl"), - N_065("139.162.14.142", 50002, "ssl"), - N_066("electrum.eff.ro", 50002, "ssl"), - N_067("electrum.taborsky.cz", 50002, "ssl"), - N_068("electrum.festivaldelhumor.org", 50002, "ssl"), - N_069("electrum.petrkr.net", 50002, "ssl"), - N_070("us.electrum.be", 50002, "ssl"), - N_071("bitcoin-node.org", 50002, "ssl"), - N_072("vmd27610.contaboserver.net", 50002, "ssl"), - N_073("electrumx.soon.it", 50002, "ssl"), - N_074("vmd30612.contaboserver.net", 50002, "ssl"), - N_075("enode.duckdns.org", 50002, "ssl"), - N_076("81-7-13-84.blue.kundencontroller.de", 50002, "ssl"), - N_077("electrum.scumm.it", 50002, "ssl"), - N_078("helicarrier.bauerj.eu", 50002, "ssl"), - N_079("tardis.bauerj.eu", 50002, "ssl"), - N_080("such.ninja", 50002, "ssl"), - N_081("electrum.be", 50002, "ssl"), + N_007("vps.hsmiths.com", 8080, "tcp"), + N_008("ip120.ip-54-37-91.eu", 50001, "tcp"), + N_009("ip101.ip-54-37-91.eu", 50001, "tcp"), + N_010("e-x.not.fyi", 50001, "tcp"), + N_011("electrum.vom-stausee.de", 50001, "tcp"), + N_012("electrum2.eff.ro", 50001, "tcp"), + N_013("electrum.coinucopia.io", 50001, "tcp"), + N_014("kirsche.emzy.de", 50001, "tcp"), + N_015("electrum.petrkr.net", 50001, "tcp"), + N_016("btc.knas.systems", 50001, "tcp"), + N_017("b.ooze.cc", 50002, "ssl"), + N_018("electrum.nute.net", 50002, "ssl"), + N_019("electrum.coinop.cc", 50002, "ssl"), + N_020("electrum.vom-stausee.de", 50002, "ssl"), + N_021("ip119.ip-54-37-91.eu", 50002, "ssl"), + N_022("ip101.ip-54-37-91.eu", 50002, "ssl"), + N_023("dedi.jochen-hoenicke.de", 50002, "ssl"), + N_024("174.138.11.174", 50002, "ssl"), + N_025("ip239.ip-54-36-234.eu", 50002, "ssl"), + N_026("bitcoins.sk", 50002, "ssl"), + N_027("e-x.not.fyi", 50002, "ssl"), + N_028("ip120.ip-54-37-91.eu", 50002, "ssl"), + N_029("electrum.villocq.com", 50002, "ssl"), + N_030("electrum.anduck.net", 50012, "ssl"), + N_031("technetium.network", 50002, "ssl"), + N_032("electrum.coinucopia.io", 50002, "ssl"), + N_033("dimon.trimon.de", 50002, "ssl"), + N_034("rbx.curalle.ovh", 50002, "ssl"), + N_035("btc.gravitech.net", 50002, "ssl"), + N_036("hetzner01.fischl-online.de", 50002, "ssl"), + N_037("fn.48.org", 50002, "ssl"), + N_038("185.64.116.15", 50002, "ssl"), + N_039("kirsche.emzy.de", 50002, "ssl"), + N_040("109.192.105.174", 50002, "ssl"), + N_041("fedaykin.goip.de", 50002, "ssl"), + N_042("vps.hsmiths.com", 50002, "ssl"), + N_043("104.250.141.242", 50002, "ssl"), + N_044("electrum.qtornado.com", 50002, "ssl"), + N_045("electrum-server.ninja", 50002, "ssl"), + N_046("electrum2.eff.ro", 50002, "ssl"), + N_047("electrum.hsmiths.com", 995, "ssl"), + N_048("electrum.hsmiths.com", 50002, "ssl"), + N_049("139.162.14.142", 50002, "ssl"), + N_050("electrum.petrkr.net", 50002, "ssl"), + N_051("us.electrum.be", 50002, "ssl"), + N_052("bitcoin-node.org", 50002, "ssl"), + N_053("electrumx.soon.it", 50002, "ssl"), + N_054("vmd30612.contaboserver.net", 50002, "ssl"), + N_055("81-7-13-84.blue.kundencontroller.de", 50002, "ssl"), + N_056("electrum.scumm.it", 50002, "ssl"), + N_057("helicarrier.bauerj.eu", 50002, "ssl"), + N_058("tardis.bauerj.eu", 50002, "ssl"), + N_059("such.ninja", 50002, "ssl"), + N_060("electrum.be", 50002, "ssl"), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/ltc/LitecoinNode.kt b/app/src/main/java/com/tangem/domain/wallet/ltc/LitecoinNode.kt index 0d7ff8c172..4ce198c857 100644 --- a/app/src/main/java/com/tangem/domain/wallet/ltc/LitecoinNode.kt +++ b/app/src/main/java/com/tangem/domain/wallet/ltc/LitecoinNode.kt @@ -10,8 +10,7 @@ enum class LitecoinNode(val host: String, val port: Int, val proto: String) { N_007("electrum-ltc.bysh.me", 50002, "ssl"), N_008("e-3.claudioboxx.com", 50004, "ssl"), N_009("electrum-ltc.wilv.in", 50002, "ssl"), - N_010("ltc.rentonisk.com", 50002, "ssl"), - N_011("e-1.claudioboxx.com", 50004, "ssl"), - N_012("electrum.ltc.xurious.com", 50002, "ssl"), - N_013("ltc01.knas.systems", 50004, "ssl"), + N_010("e-1.claudioboxx.com", 50004, "ssl"), + N_011("electrum.ltc.xurious.com", 50002, "ssl"), + N_012("ltc01.knas.systems", 50004, "ssl"), } \ No newline at end of file From 886885e82658240dc0a081a1c0776797d4e8396f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Jan 2019 12:16:26 +0300 Subject: [PATCH 016/228] Updated on 2026-08-14 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- .../.idea/caches/build_file_checksums.ser | Bin 0 -> 852 bytes .../.idea/codeStyles/Project.xml | 29 + tangemcard-android/.idea/gradle.xml | 21 + ...le__android_arch_core_common_1_1_1_jar.xml | 11 + ...radle__android_arch_core_runtime_1_1_1.xml | 12 + ...ndroid_arch_lifecycle_common_1_1_1_jar.xml | 11 + ..._android_arch_lifecycle_livedata_1_1_1.xml | 12 + ...oid_arch_lifecycle_livedata_core_1_1_1.xml | 12 + ...__android_arch_lifecycle_runtime_1_1_1.xml | 12 + ...android_arch_lifecycle_viewmodel_1_1_1.xml | 12 + ...upport_animated_vector_drawable_28_0_0.xml | 10 + ...om_android_support_appcompat_v7_28_0_0.xml | 13 + ...oid_support_asynclayoutinflater_28_0_0.xml | 10 + ...com_android_support_cardview_v7_28_0_0.xml | 10 + ...android_support_collections_28_0_0_jar.xml | 9 + ...ort_constraint_constraint_layout_1_1_3.xml | 10 + ...int_constraint_layout_solver_1_1_3_jar.xml | 9 + ...droid_support_coordinatorlayout_28_0_0.xml | 13 + ...m_android_support_cursoradapter_28_0_0.xml | 10 + ..._com_android_support_customview_28_0_0.xml | 10 + ...dle__com_android_support_design_28_0_0.xml | 13 + ...om_android_support_documentfile_28_0_0.xml | 10 + ...om_android_support_drawerlayout_28_0_0.xml | 13 + ...om_android_support_interpolator_28_0_0.xml | 10 + ...dle__com_android_support_loader_28_0_0.xml | 10 + ...d_support_localbroadcastmanager_28_0_0.xml | 10 + ...adle__com_android_support_print_28_0_0.xml | 13 + ...android_support_recyclerview_v7_28_0_0.xml | 13 + ...droid_support_slidingpanelayout_28_0_0.xml | 10 + ...support_support_annotations_28_0_0_jar.xml | 9 + ..._android_support_support_compat_28_0_0.xml | 13 + ...android_support_support_core_ui_28_0_0.xml | 10 + ...roid_support_support_core_utils_28_0_0.xml | 10 + ...ndroid_support_support_fragment_28_0_0.xml | 13 + ...id_support_support_media_compat_28_0_0.xml | 13 + ..._com_android_support_support_v4_28_0_0.xml | 10 + ...support_support_vector_drawable_28_0_0.xml | 10 + ...roid_support_swiperefreshlayout_28_0_0.xml | 13 + ...port_test_espresso_espresso_core_3_0_2.xml | 12 + ...spresso_espresso_idling_resource_3_0_2.xml | 12 + ...com_android_support_test_monitor_1_0_2.xml | 12 + ..._com_android_support_test_runner_1_0_2.xml | 12 + ..._com_android_support_transition_28_0_0.xml | 13 + ...oid_support_versionedparcelable_28_0_0.xml | 10 + ...__com_android_support_viewpager_28_0_0.xml | 10 + ..._google_code_findbugs_jsr305_2_0_1_jar.xml | 9 + ...e__com_google_code_gson_gson_2_8_5_jar.xml | 11 + ...dle__com_google_dagger_dagger_2_16_jar.xml | 11 + ...radle__com_google_guava_guava_18_0_jar.xml | 11 + ...oogle_protobuf_protobuf_java_2_6_1_jar.xml | 11 + ...radle__com_google_zxing_core_3_3_3_jar.xml | 11 + ...it_retrofit2_rxjava2_adapter_1_0_0_jar.xml | 11 + ...adle__com_lambdaworks_scrypt_1_4_0_jar.xml | 11 + ..._madgag_spongycastle_core_1_56_0_0_jar.xml | 11 + ..._madgag_spongycastle_prov_1_56_0_0_jar.xml | 11 + ...radle__com_scottyab_rootbeer_lib_0_0_7.xml | 12 + ...yfishjy_ripplebackground_library_1_0_1.xml | 12 + ...dle__com_squareup_javawriter_2_1_1_jar.xml | 11 + ...okhttp3_logging_interceptor_3_10_0_jar.xml | 11 + ...okhttp3_logging_interceptor_3_11_0_jar.xml | 11 + ...com_squareup_okhttp3_okhttp_3_12_0_jar.xml | 11 + ...__com_squareup_okhttp_okhttp_2_7_2_jar.xml | 11 + ...dle__com_squareup_okio_okio_1_15_0_jar.xml | 11 + ...eup_retrofit2_converter_gson_2_5_0_jar.xml | 11 + ..._squareup_retrofit2_retrofit_2_5_0_jar.xml | 11 + .../.idea/libraries/Gradle__core_1_56_0_0.xml | 11 + ...__info_hoang8f_android_segmented_1_0_6.xml | 12 + ...__io_reactivex_rxjava2_rxandroid_2_0_1.xml | 12 + ..._io_reactivex_rxjava2_rxjava_2_2_0_jar.xml | 11 + ...o_reactivex_rxjava2_rxkotlin_2_3_0_jar.xml | 11 + ...radle__javax_inject_javax_inject_1_jar.xml | 11 + .../Gradle__junit_junit_4_12_jar.xml | 11 + ...adle__me_dm7_barcodescanner_core_1_9_8.xml | 10 + ...dle__me_dm7_barcodescanner_zxing_1_9_8.xml | 10 + ...dle__net_jcip_jcip_annotations_1_0_jar.xml | 11 + .../Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml | 11 + ..._org_bitcoinj_bitcoinj_core_0_14_7_jar.xml | 11 + .../Gradle__org_bitcoinj_orchid_1_2_1_jar.xml | 11 + ...dle__org_greenrobot_eventbus_3_1_1_jar.xml | 11 + ...le__org_hamcrest_hamcrest_core_1_3_jar.xml | 11 + ..._hamcrest_hamcrest_integration_1_3_jar.xml | 11 + ..._org_hamcrest_hamcrest_library_1_3_jar.xml | 11 + ...le__org_jetbrains_annotations_13_0_jar.xml | 11 + ..._android_extensions_runtime_1_3_11_jar.xml | 11 + ...brains_kotlin_kotlin_stdlib_1_3_11_jar.xml | 11 + ...kotlin_kotlin_stdlib_common_1_3_11_jar.xml | 11 + ...s_kotlin_kotlin_stdlib_jdk7_1_3_11_jar.xml | 11 + ...tivestreams_reactive_streams_1_0_2_jar.xml | 11 + ...Gradle__org_slf4j_slf4j_api_1_7_20_jar.xml | 11 + .../.idea/libraries/Gradle__prov_1_56_0_0.xml | 11 + tangemcard-android/.idea/misc.xml | 38 + tangemcard-android/.idea/modules.xml | 12 + .../.idea/runConfigurations.xml | 12 + tangemcard-android/.idea/workspace.xml | 5040 +++++++++++++++++ tangemcard-android/build.gradle | 20 - .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + tangemcard-android/gradlew | 172 + tangemcard-android/gradlew.bat | 84 + tangemcard-android/local.properties | 8 + tangemcard-common/build.gradle | 2 +- tangemserver-android/build.gradle | 8 +- 104 files changed, 6391 insertions(+), 31 deletions(-) create mode 100644 tangemcard-android/.idea/caches/build_file_checksums.ser create mode 100644 tangemcard-android/.idea/codeStyles/Project.xml create mode 100644 tangemcard-android/.idea/gradle.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_core_common_1_1_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_core_runtime_1_1_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_common_1_1_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_1_1_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_core_1_1_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_runtime_1_1_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_viewmodel_1_1_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_animated_vector_drawable_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_appcompat_v7_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_asynclayoutinflater_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_cardview_v7_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_collections_28_0_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_1_1_3.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_solver_1_1_3_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_coordinatorlayout_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_cursoradapter_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_customview_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_design_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_documentfile_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_drawerlayout_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_interpolator_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_loader_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_localbroadcastmanager_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_print_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_recyclerview_v7_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_slidingpanelayout_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_annotations_28_0_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_compat_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_ui_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_utils_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_fragment_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_media_compat_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_v4_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_support_vector_drawable_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_swiperefreshlayout_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_core_3_0_2.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_test_monitor_1_0_2.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_test_runner_1_0_2.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_transition_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_versionedparcelable_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_android_support_viewpager_28_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_google_dagger_dagger_2_16_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_google_guava_guava_18_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_google_protobuf_protobuf_java_2_6_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_google_zxing_core_3_3_3_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_jakewharton_retrofit_retrofit2_rxjava2_adapter_1_0_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_lambdaworks_scrypt_1_4_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_core_1_56_0_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_prov_1_56_0_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_scottyab_rootbeer_lib_0_0_7.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_skyfishjy_ripplebackground_library_1_0_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_10_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_11_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_okhttp_3_12_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_2_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_okio_okio_1_15_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_converter_gson_2_5_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_retrofit_2_5_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__core_1_56_0_0.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__info_hoang8f_android_segmented_1_0_6.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxandroid_2_0_1.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxjava_2_2_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxkotlin_2_3_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__javax_inject_javax_inject_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__junit_junit_4_12_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_core_1_9_8.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_zxing_1_9_8.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__net_jcip_jcip_annotations_1_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_bitcoinj_core_0_14_7_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_orchid_1_2_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_greenrobot_eventbus_3_1_1_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_android_extensions_runtime_1_3_11_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_3_11_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_3_11_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_3_11_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_reactivestreams_reactive_streams_1_0_2_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_20_jar.xml create mode 100644 tangemcard-android/.idea/libraries/Gradle__prov_1_56_0_0.xml create mode 100644 tangemcard-android/.idea/misc.xml create mode 100644 tangemcard-android/.idea/modules.xml create mode 100644 tangemcard-android/.idea/runConfigurations.xml create mode 100644 tangemcard-android/.idea/workspace.xml create mode 100644 tangemcard-android/gradle/wrapper/gradle-wrapper.jar create mode 100644 tangemcard-android/gradle/wrapper/gradle-wrapper.properties create mode 100644 tangemcard-android/gradlew create mode 100644 tangemcard-android/gradlew.bat create mode 100644 tangemcard-android/local.properties diff --git a/build.gradle b/build.gradle index 3942b0608f..03239d7756 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7368b742cc..6fea634781 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 25 21:50:16 MSK 2018 +#Fri Jan 18 12:06:47 MSK 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/tangemcard-android/.idea/caches/build_file_checksums.ser b/tangemcard-android/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..d50a885154f3e2a103b3f4715e441821ba5d521f GIT binary patch literal 852 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}uGBYr_F>vMNC#JY1CYR(Fc`|U8WE7nf0pv~wfj8aj&Dj|@-g!H1TBU;6`Kl5IN0O~cEGR&=s*s_MfemE3 ze$Mp9Uv=vaIr^^YS$_W3+7gB!l1+zrsW`Q$EVW1%8pSxQdl;)#wMb#ZS&oOc7Tuw) z{3Q%LC_w;s7fbo=8Kz~LZ_?g=F+aAr<#h>zKu&&gVh$*w6r>iFWTqCE!WCCbo60QU zOSz%=FpqV|pN6y&22G$RLV+$Sj&UtbPt8k>frfDmBDrChaz(c1WMKe@zp&WFQlq72JSzn3rwlHjrm03EP4vH$=8 literal 0 HcmV?d00001 diff --git a/tangemcard-android/.idea/codeStyles/Project.xml b/tangemcard-android/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..30aa626c23 --- /dev/null +++ b/tangemcard-android/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/gradle.xml b/tangemcard-android/.idea/gradle.xml new file mode 100644 index 0000000000..88d3a36dfc --- /dev/null +++ b/tangemcard-android/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_core_common_1_1_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_core_common_1_1_1_jar.xml new file mode 100644 index 0000000000..bf16fff60d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_core_common_1_1_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_core_runtime_1_1_1.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_core_runtime_1_1_1.xml new file mode 100644 index 0000000000..3672191097 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_core_runtime_1_1_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_common_1_1_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_common_1_1_1_jar.xml new file mode 100644 index 0000000000..27b1867567 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_common_1_1_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_1_1_1.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_1_1_1.xml new file mode 100644 index 0000000000..e5c527903a --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_1_1_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_core_1_1_1.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_core_1_1_1.xml new file mode 100644 index 0000000000..3c68888267 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_livedata_core_1_1_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_runtime_1_1_1.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_runtime_1_1_1.xml new file mode 100644 index 0000000000..ba470532d5 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_runtime_1_1_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_viewmodel_1_1_1.xml b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_viewmodel_1_1_1.xml new file mode 100644 index 0000000000..91aae22d5a --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__android_arch_lifecycle_viewmodel_1_1_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_animated_vector_drawable_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_animated_vector_drawable_28_0_0.xml new file mode 100644 index 0000000000..d7b54e8e49 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_animated_vector_drawable_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_appcompat_v7_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_appcompat_v7_28_0_0.xml new file mode 100644 index 0000000000..d661b92d7d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_appcompat_v7_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_asynclayoutinflater_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_asynclayoutinflater_28_0_0.xml new file mode 100644 index 0000000000..3adbd26dbe --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_asynclayoutinflater_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_cardview_v7_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_cardview_v7_28_0_0.xml new file mode 100644 index 0000000000..bb09bc80ce --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_cardview_v7_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_collections_28_0_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_collections_28_0_0_jar.xml new file mode 100644 index 0000000000..80e4d731eb --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_collections_28_0_0_jar.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_1_1_3.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_1_1_3.xml new file mode 100644 index 0000000000..bcb4ff48a7 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_1_1_3.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_solver_1_1_3_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_solver_1_1_3_jar.xml new file mode 100644 index 0000000000..c0adf88fae --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_constraint_constraint_layout_solver_1_1_3_jar.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_coordinatorlayout_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_coordinatorlayout_28_0_0.xml new file mode 100644 index 0000000000..97836ea7b8 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_coordinatorlayout_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_cursoradapter_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_cursoradapter_28_0_0.xml new file mode 100644 index 0000000000..830cd238b4 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_cursoradapter_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_customview_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_customview_28_0_0.xml new file mode 100644 index 0000000000..9fec3518d8 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_customview_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_design_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_design_28_0_0.xml new file mode 100644 index 0000000000..97fe427240 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_design_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_documentfile_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_documentfile_28_0_0.xml new file mode 100644 index 0000000000..524d01ecbc --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_documentfile_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_drawerlayout_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_drawerlayout_28_0_0.xml new file mode 100644 index 0000000000..38b44b30aa --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_drawerlayout_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_interpolator_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_interpolator_28_0_0.xml new file mode 100644 index 0000000000..9aab62b216 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_interpolator_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_loader_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_loader_28_0_0.xml new file mode 100644 index 0000000000..9dd42c1961 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_loader_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_localbroadcastmanager_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_localbroadcastmanager_28_0_0.xml new file mode 100644 index 0000000000..ecfc3b503b --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_localbroadcastmanager_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_print_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_print_28_0_0.xml new file mode 100644 index 0000000000..628832daaa --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_print_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_recyclerview_v7_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_recyclerview_v7_28_0_0.xml new file mode 100644 index 0000000000..26e2936c1f --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_recyclerview_v7_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_slidingpanelayout_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_slidingpanelayout_28_0_0.xml new file mode 100644 index 0000000000..437f7fac8d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_slidingpanelayout_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_annotations_28_0_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_annotations_28_0_0_jar.xml new file mode 100644 index 0000000000..0fdecce0d6 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_annotations_28_0_0_jar.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_compat_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_compat_28_0_0.xml new file mode 100644 index 0000000000..129fc06474 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_compat_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_ui_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_ui_28_0_0.xml new file mode 100644 index 0000000000..5ca9644a03 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_ui_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_utils_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_utils_28_0_0.xml new file mode 100644 index 0000000000..c6570f7435 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_core_utils_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_fragment_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_fragment_28_0_0.xml new file mode 100644 index 0000000000..398f8ec322 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_fragment_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_media_compat_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_media_compat_28_0_0.xml new file mode 100644 index 0000000000..1f47f18005 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_media_compat_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_v4_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_v4_28_0_0.xml new file mode 100644 index 0000000000..0fb0ac4602 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_v4_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_vector_drawable_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_vector_drawable_28_0_0.xml new file mode 100644 index 0000000000..78bf0d8291 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_support_vector_drawable_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_swiperefreshlayout_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_swiperefreshlayout_28_0_0.xml new file mode 100644 index 0000000000..5749719e9d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_swiperefreshlayout_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_core_3_0_2.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_core_3_0_2.xml new file mode 100644 index 0000000000..4912074536 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_core_3_0_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2.xml new file mode 100644 index 0000000000..041cbb2726 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_espresso_espresso_idling_resource_3_0_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_monitor_1_0_2.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_monitor_1_0_2.xml new file mode 100644 index 0000000000..03a762f327 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_monitor_1_0_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_runner_1_0_2.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_runner_1_0_2.xml new file mode 100644 index 0000000000..8408d30c6f --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_test_runner_1_0_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_transition_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_transition_28_0_0.xml new file mode 100644 index 0000000000..84a20b7c0b --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_transition_28_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_versionedparcelable_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_versionedparcelable_28_0_0.xml new file mode 100644 index 0000000000..d2745eedab --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_versionedparcelable_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_android_support_viewpager_28_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__com_android_support_viewpager_28_0_0.xml new file mode 100644 index 0000000000..8704d75527 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_android_support_viewpager_28_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml new file mode 100644 index 0000000000..947e2512f6 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1_jar.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5_jar.xml new file mode 100644 index 0000000000..bc71380739 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_google_code_gson_gson_2_8_5_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_google_dagger_dagger_2_16_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_google_dagger_dagger_2_16_jar.xml new file mode 100644 index 0000000000..284170c712 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_google_dagger_dagger_2_16_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_google_guava_guava_18_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_google_guava_guava_18_0_jar.xml new file mode 100644 index 0000000000..861033c37b --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_google_guava_guava_18_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_google_protobuf_protobuf_java_2_6_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_google_protobuf_protobuf_java_2_6_1_jar.xml new file mode 100644 index 0000000000..82830f553a --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_google_protobuf_protobuf_java_2_6_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_google_zxing_core_3_3_3_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_google_zxing_core_3_3_3_jar.xml new file mode 100644 index 0000000000..b13d935230 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_google_zxing_core_3_3_3_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_jakewharton_retrofit_retrofit2_rxjava2_adapter_1_0_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_jakewharton_retrofit_retrofit2_rxjava2_adapter_1_0_0_jar.xml new file mode 100644 index 0000000000..056c316f46 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_jakewharton_retrofit_retrofit2_rxjava2_adapter_1_0_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_lambdaworks_scrypt_1_4_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_lambdaworks_scrypt_1_4_0_jar.xml new file mode 100644 index 0000000000..aaedebc619 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_lambdaworks_scrypt_1_4_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_core_1_56_0_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_core_1_56_0_0_jar.xml new file mode 100644 index 0000000000..5ed9e68b19 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_core_1_56_0_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_prov_1_56_0_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_prov_1_56_0_0_jar.xml new file mode 100644 index 0000000000..7182375e3e --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_madgag_spongycastle_prov_1_56_0_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_scottyab_rootbeer_lib_0_0_7.xml b/tangemcard-android/.idea/libraries/Gradle__com_scottyab_rootbeer_lib_0_0_7.xml new file mode 100644 index 0000000000..32b901fcad --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_scottyab_rootbeer_lib_0_0_7.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_skyfishjy_ripplebackground_library_1_0_1.xml b/tangemcard-android/.idea/libraries/Gradle__com_skyfishjy_ripplebackground_library_1_0_1.xml new file mode 100644 index 0000000000..7f33fc2e11 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_skyfishjy_ripplebackground_library_1_0_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1_jar.xml new file mode 100644 index 0000000000..5c4dd4e872 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_10_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_10_0_jar.xml new file mode 100644 index 0000000000..70c9278186 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_10_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_11_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_11_0_jar.xml new file mode 100644 index 0000000000..ce168f985d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_logging_interceptor_3_11_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_okhttp_3_12_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_okhttp_3_12_0_jar.xml new file mode 100644 index 0000000000..29cd9ae00a --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp3_okhttp_3_12_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_2_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_2_jar.xml new file mode 100644 index 0000000000..3e162ea028 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_2_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_okio_okio_1_15_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okio_okio_1_15_0_jar.xml new file mode 100644 index 0000000000..5321c1a14d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_okio_okio_1_15_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_converter_gson_2_5_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_converter_gson_2_5_0_jar.xml new file mode 100644 index 0000000000..7acf4c40f1 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_converter_gson_2_5_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_retrofit_2_5_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_retrofit_2_5_0_jar.xml new file mode 100644 index 0000000000..7abd896c6f --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__com_squareup_retrofit2_retrofit_2_5_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__core_1_56_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__core_1_56_0_0.xml new file mode 100644 index 0000000000..4c8e2e21c7 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__core_1_56_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__info_hoang8f_android_segmented_1_0_6.xml b/tangemcard-android/.idea/libraries/Gradle__info_hoang8f_android_segmented_1_0_6.xml new file mode 100644 index 0000000000..31d200c58b --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__info_hoang8f_android_segmented_1_0_6.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxandroid_2_0_1.xml b/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxandroid_2_0_1.xml new file mode 100644 index 0000000000..5789daef55 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxandroid_2_0_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxjava_2_2_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxjava_2_2_0_jar.xml new file mode 100644 index 0000000000..05f01ea85c --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxjava_2_2_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxkotlin_2_3_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxkotlin_2_3_0_jar.xml new file mode 100644 index 0000000000..087cc1b89a --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__io_reactivex_rxjava2_rxkotlin_2_3_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__javax_inject_javax_inject_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__javax_inject_javax_inject_1_jar.xml new file mode 100644 index 0000000000..f9c9a30640 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__javax_inject_javax_inject_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__junit_junit_4_12_jar.xml b/tangemcard-android/.idea/libraries/Gradle__junit_junit_4_12_jar.xml new file mode 100644 index 0000000000..f4f25a81d9 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__junit_junit_4_12_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_core_1_9_8.xml b/tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_core_1_9_8.xml new file mode 100644 index 0000000000..1d3db747c4 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_core_1_9_8.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_zxing_1_9_8.xml b/tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_zxing_1_9_8.xml new file mode 100644 index 0000000000..bbe3bbb46c --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__me_dm7_barcodescanner_zxing_1_9_8.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__net_jcip_jcip_annotations_1_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__net_jcip_jcip_annotations_1_0_jar.xml new file mode 100644 index 0000000000..d7e0096e76 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__net_jcip_jcip_annotations_1_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml new file mode 100644 index 0000000000..5855a297fe --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__net_sf_kxml_kxml2_2_3_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_bitcoinj_core_0_14_7_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_bitcoinj_core_0_14_7_jar.xml new file mode 100644 index 0000000000..c87fd0cb85 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_bitcoinj_core_0_14_7_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_orchid_1_2_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_orchid_1_2_1_jar.xml new file mode 100644 index 0000000000..5805d028ee --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_bitcoinj_orchid_1_2_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_greenrobot_eventbus_3_1_1_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_greenrobot_eventbus_3_1_1_jar.xml new file mode 100644 index 0000000000..e29d235a26 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_greenrobot_eventbus_3_1_1_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml new file mode 100644 index 0000000000..50cf2b92b8 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml new file mode 100644 index 0000000000..a5eaca5bef --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml new file mode 100644 index 0000000000..b4dabdc1dd --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0_jar.xml new file mode 100644 index 0000000000..93524e8896 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_android_extensions_runtime_1_3_11_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_android_extensions_runtime_1_3_11_jar.xml new file mode 100644 index 0000000000..3668e68cce --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_android_extensions_runtime_1_3_11_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_3_11_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_3_11_jar.xml new file mode 100644 index 0000000000..8762d3d930 --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_3_11_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_3_11_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_3_11_jar.xml new file mode 100644 index 0000000000..7eba057aeb --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_3_11_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_3_11_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_3_11_jar.xml new file mode 100644 index 0000000000..ad4fd5145d --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_3_11_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_reactivestreams_reactive_streams_1_0_2_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_reactivestreams_reactive_streams_1_0_2_jar.xml new file mode 100644 index 0000000000..761660632c --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_reactivestreams_reactive_streams_1_0_2_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_20_jar.xml b/tangemcard-android/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_20_jar.xml new file mode 100644 index 0000000000..33c4534e6c --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_20_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/libraries/Gradle__prov_1_56_0_0.xml b/tangemcard-android/.idea/libraries/Gradle__prov_1_56_0_0.xml new file mode 100644 index 0000000000..a67dc23eca --- /dev/null +++ b/tangemcard-android/.idea/libraries/Gradle__prov_1_56_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/misc.xml b/tangemcard-android/.idea/misc.xml new file mode 100644 index 0000000000..b0c7b20c87 --- /dev/null +++ b/tangemcard-android/.idea/misc.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/modules.xml b/tangemcard-android/.idea/modules.xml new file mode 100644 index 0000000000..505e4aaf98 --- /dev/null +++ b/tangemcard-android/.idea/modules.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/runConfigurations.xml b/tangemcard-android/.idea/runConfigurations.xml new file mode 100644 index 0000000000..7f68460d8b --- /dev/null +++ b/tangemcard-android/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/tangemcard-android/.idea/workspace.xml b/tangemcard-android/.idea/workspace.xml new file mode 100644 index 0000000000..46a99e117c --- /dev/null +++ b/tangemcard-android/.idea/workspace.xml @@ -0,0 +1,5040 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1547496657083 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangemserver-android/gradle/wrapper/gradle-wrapper.jar b/tangemserver-android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/tangemserver-android/gradle/wrapper/gradle-wrapper.properties b/tangemserver-android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..9a4163a4f5 --- /dev/null +++ b/tangemserver-android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tangemserver-android/gradlew b/tangemserver-android/gradlew new file mode 100644 index 0000000000..cccdd3d517 --- /dev/null +++ b/tangemserver-android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/tangemserver-android/gradlew.bat b/tangemserver-android/gradlew.bat new file mode 100644 index 0000000000..f9553162f1 --- /dev/null +++ b/tangemserver-android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tangemserver-android/local.properties b/tangemserver-android/local.properties new file mode 100644 index 0000000000..93d4f44737 --- /dev/null +++ b/tangemserver-android/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Mon Jan 14 23:10:57 MSK 2019 +sdk.dir=C\:\\Users\\Eugene\\AppData\\Local\\Android\\Sdk From 3f743ec35356acf446f4fcddc8517eaf8b584585 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Jan 2019 16:38:17 +0300 Subject: [PATCH 021/228] Updated on 2026-08-14 --- .../tangem/domain/wallet/eth/EthEngine.java | 2 +- .../tangem/domain/wallet/rsk/RskEngine.java | 22 ++++++++----------- .../domain/wallet/rsk/RskTokenEngine.java | 15 ------------- .../domain/wallet/token/TokenEngine.java | 2 +- .../presentation/fragment/LoadedWallet.kt | 9 +++++++- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java index 876bc8bd17..b6635e395a 100644 --- a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java @@ -45,7 +45,7 @@ public class EthEngine extends CoinEngine { } else if (ctx.getCoinData() instanceof EthData) { coinData = (EthData) ctx.getCoinData(); } else { - throw new Exception("Invalid type of Blockchain data for EthEngine"); + throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName()); } } diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java index 7d34f079cb..b1a83967a1 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java @@ -28,15 +28,7 @@ public class RskEngine extends EthEngine { private static final String TAG = RskEngine.class.getSimpleName(); public RskEngine(TangemContext ctx) throws Exception { - this.ctx = ctx; - if (ctx.getCoinData() == null) { - coinData = new EthData(); - ctx.setCoinData(coinData); - } else if (ctx.getCoinData() instanceof EthData) { - coinData = (EthData) ctx.getCoinData(); - } else { - throw new Exception("Invalid type of Blockchain data for RskEngine"); - } + super(ctx); } public RskEngine() { @@ -58,10 +50,14 @@ public class RskEngine extends EthEngine { } @Override - public Uri getShareWalletUri() { return Uri.parse(ctx.getCoinData().getWallet()); } + public Uri getShareWalletUri() { + return Uri.parse(ctx.getCoinData().getWallet()); + } @Override - public Uri getShareWalletUriExplorer() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } + public Uri getShareWalletUriExplorer() { + return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); + } @Override public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { @@ -138,7 +134,7 @@ public class RskEngine extends EthEngine { throw new Exception("Error in RskEngine - invalid v"); } tx.signature.v = (byte) v; - Log.e(TAG, "RSK_v: " +String.valueOf(v)); + Log.e(TAG, "RSK_v: " + String.valueOf(v)); byte[] txForSend = tx.getEncoded(); notifyOnNeedSendPayment(txForSend); @@ -272,7 +268,7 @@ public class RskEngine extends EthEngine { blockchainRequestsCallbacks.onComplete(false); } else { BigInteger nonce = coinData.getConfirmedTXCount(); - nonce=nonce.add(BigInteger.valueOf(1)); + nonce = nonce.add(BigInteger.valueOf(1)); coinData.setConfirmedTXCount(nonce); ctx.setError(null); blockchainRequestsCallbacks.onComplete(true); diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java index f571505f4b..e1064e9e74 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java @@ -34,21 +34,6 @@ public class RskTokenEngine extends TokenEngine { public RskTokenEngine(TangemContext ctx) throws Exception { super(ctx); - if (ctx.getCoinData() == null) { - coinData = new TokenData(); - ctx.setCoinData(coinData); - } else if (ctx.getCoinData() instanceof TokenData) { - coinData = (TokenData) ctx.getCoinData(); - } else if (ctx.getCoinData() instanceof EthData) { - // special case with receive card data substitution from server at the moment - Bundle B=new Bundle(); - ctx.getCoinData().saveToBundle(B); - coinData = new TokenData(); - coinData.loadFromBundle(B); - ctx.setCoinData(coinData); - } else { - throw new Exception("Invalid type of Blockchain data for RskTokenEngine"); - } } public RskTokenEngine() { diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java index dd6e5fb76f..08a1398551 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java @@ -55,7 +55,7 @@ public class TokenEngine extends CoinEngine { coinData.loadFromBundle(B); ctx.setCoinData(coinData); } else { - throw new Exception("Invalid type of Blockchain data for TokenEngine"); + throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName()); } } 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 99e9d96208..30565a6493 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -262,7 +262,14 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific val rateInfoDataListener: ServerApiCommon.RateInfoDataListener = object : ServerApiCommon.RateInfoDataListener { override fun onSuccess(rateInfoResponse: RateInfoResponse?){ - ctx.coinData!!.rate = rateInfoResponse!!.data!!.quote!!.usd!!.price!! + try { + ctx.coinData!!.rate = rateInfoResponse!!.data!!.quote!!.usd!!.price!! + } + catch (e : java.lang.Exception) { + e.printStackTrace() + ctx.error = "Unknown response from CoinMarketCap" + } + } override fun onFail(message: String?){ From 6ed28b0c0c4291096b6a407888dc7a2779d3e6f9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Jan 2019 14:10:22 +0300 Subject: [PATCH 022/228] Updated on 2026-08-14 --- .../main/java/com/tangem/domain/wallet/eth/EthEngine.java | 7 +++++-- .../main/java/com/tangem/domain/wallet/rsk/RskEngine.java | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java index 876bc8bd17..c885e8fb85 100644 --- a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java @@ -57,6 +57,10 @@ public class EthEngine extends CoinEngine { return 18; } + protected int getChainId() { + return ctx.getBlockchain() == Blockchain.Ethereum ? EthTransaction.ChainEnum.Mainnet.getValue() : EthTransaction.ChainEnum.Rinkeby.getValue(); + } + @Override public boolean awaitingConfirmation() { return false; @@ -391,7 +395,6 @@ public class EthEngine extends CoinEngine { BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000)); BigInteger gasLimit = BigInteger.valueOf(21000); - Integer chainId = ctx.getBlockchain() == Blockchain.Ethereum ? EthTransaction.ChainEnum.Mainnet.getValue() : EthTransaction.ChainEnum.Rinkeby.getValue(); String to = targetAddress; @@ -399,7 +402,7 @@ public class EthEngine extends CoinEngine { to = to.substring(2); } - final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); + final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, this.getChainId()); return new SignTask.PaymentToSign() { @Override diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java index 7d34f079cb..3d381f5b5f 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java @@ -47,6 +47,11 @@ public class RskEngine extends EthEngine { return 18; } + @Override + protected int getChainId() { + return EthTransaction.ChainEnum.Rootstock_mainnet.getValue(); + } + @Override public String getBalanceCurrency() { return Blockchain.Rootstock.getCurrency(); From efcbb71398a0ba9340989a4c18d9c32245503172 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Jan 2019 14:38:12 +0300 Subject: [PATCH 023/228] Updated on 2026-08-14 --- .../com/tangem/presentation/activity/PreparePaymentActivity.kt | 1 - app/src/main/res/layout/activity_confirm_payment.xml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt index 2bb08c71c5..23808e6232 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt @@ -5,7 +5,6 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.nfc.NfcAdapter -import android.nfc.NfcManager import android.nfc.Tag import android.os.Bundle import android.support.v7.app.AppCompatActivity diff --git a/app/src/main/res/layout/activity_confirm_payment.xml b/app/src/main/res/layout/activity_confirm_payment.xml index 6f6f56cea5..4549cd4dd4 100644 --- a/app/src/main/res/layout/activity_confirm_payment.xml +++ b/app/src/main/res/layout/activity_confirm_payment.xml @@ -269,7 +269,6 @@ android:enabled="false" android:fontFamily="@font/maax" android:inputType="numberDecimal" - android:padding="12dp" android:textColor="@color/black" android:textColorLink="@android:color/holo_blue_dark" android:textSize="@dimen/text_size_medium" @@ -277,7 +276,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintWidth_min="150dp" /> + app:layout_constraintWidth_min="150dp"/> Date: Tue, 22 Jan 2019 15:09:28 +0300 Subject: [PATCH 024/228] Updated on 2026-08-14 --- app/src/main/res/layout/activity_confirm_payment.xml | 6 +++--- app/src/main/res/layout/activity_prepare_payment.xml | 10 +++++----- app/src/main/res/values/dimens.xml | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/layout/activity_confirm_payment.xml b/app/src/main/res/layout/activity_confirm_payment.xml index 4549cd4dd4..b5304e28e4 100644 --- a/app/src/main/res/layout/activity_confirm_payment.xml +++ b/app/src/main/res/layout/activity_confirm_payment.xml @@ -161,11 +161,11 @@ android:padding="12dp" android:textColor="@color/black" android:textColorLink="@color/colorPrimary" - android:textSize="@dimen/text_size_large" + android:textSize="@dimen/text_size_1_large" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent"/> + android:layout_marginStart="8dp"> + app:layout_constraintWidth_min="150dp"/> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4f658b4efc..8464ee95a5 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -21,6 +21,7 @@ 13sp 15sp 19sp + 23sp 26sp 34sp From 4b325b213b46a9fa7d07b8457bd02273ba2a9556 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Jan 2019 00:38:27 +0300 Subject: [PATCH 025/228] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 6 +- app/src/main/java/com/tangem/Constant.kt | 16 +- .../tangem/data/network/ServerApiCommon.java | 38 +- .../data/network/ServerApiElectrum.java | 42 +- .../tangem/data/network/ServerApiInfura.java | 22 +- .../tangem/data/network/ServerApiInsight.java | 36 +- .../data/network/ServerApiRootstock.java | 22 +- .../com/tangem/data/network/ServerURL.java | 2 +- app/src/main/java/com/tangem/di/Navigator.kt | 4 +- .../com/tangem/di/NavigatorComponent.java | 4 +- .../com/tangem/domain/wallet/CoinEngine.java | 36 +- .../domain/wallet/bch/BtcCashEngine.java | 42 +- .../tangem/domain/wallet/btc/BtcEngine.java | 50 +- .../domain/wallet/ducatus/DucatusEngine.java | 38 +- .../tangem/domain/wallet/eth/EthEngine.java | 38 +- .../tangem/domain/wallet/ltc/LtcEngine.java | 18 +- .../tangem/domain/wallet/rsk/RskEngine.java | 36 +- .../domain/wallet/rsk/RskTokenEngine.java | 54 +- .../domain/wallet/token/TokenEngine.java | 54 +- ...ivity.kt => ConfirmTransactionActivity.kt} | 20 +- ...ivity.kt => PrepareTransactionActivity.kt} | 16 +- .../activity/SendTransactionActivity.kt | 2 +- ...Activity.kt => SignTransactionActivity.kt} | 34 +- .../dialog/WaitSecurityDelayDialogNew.kt | 2 - .../presentation/fragment/LoadedWallet.kt | 16 +- ...t.xml => activity_confirm_transaction.xml} | 674 +++++++++--------- ...t.xml => activity_prepare_transaction.xml} | 522 +++++++------- ...ment.xml => activity_sign_transaction.xml} | 90 +-- app/src/main/res/values/strings.xml | 16 +- tangemcard-common/build.gradle | 2 +- .../tangem/tangemcard/reader/CardCrypto.java | 291 +++++--- .../tangemcard/reader/CardProtocol.java | 80 ++- .../tangemcard/tasks/CustomReadCardTask.java | 40 +- .../com/tangem/tangemcard/tasks/SignTask.java | 26 +- 34 files changed, 1257 insertions(+), 1132 deletions(-) rename app/src/main/java/com/tangem/presentation/activity/{ConfirmPaymentActivity.kt => ConfirmTransactionActivity.kt} (93%) rename app/src/main/java/com/tangem/presentation/activity/{PreparePaymentActivity.kt => PrepareTransactionActivity.kt} (92%) rename app/src/main/java/com/tangem/presentation/activity/{SignPaymentActivity.kt => SignTransactionActivity.kt} (90%) rename app/src/main/res/layout/{activity_confirm_payment.xml => activity_confirm_transaction.xml} (95%) rename app/src/main/res/layout/{activity_prepare_payment.xml => activity_prepare_transaction.xml} (96%) rename app/src/main/res/layout/{activity_sign_payment.xml => activity_sign_transaction.xml} (90%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 033a88b840..b24b163742 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -99,17 +99,17 @@ android:theme="@style/AppTheme.NoActionBar" /> diff --git a/app/src/main/java/com/tangem/Constant.kt b/app/src/main/java/com/tangem/Constant.kt index 57bb685c8c..433d912aa0 100644 --- a/app/src/main/java/com/tangem/Constant.kt +++ b/app/src/main/java/com/tangem/Constant.kt @@ -13,7 +13,7 @@ object Constant { const val EXTRA_MODIFICATION_UPDATE = "update" // LoadedWallet, VerifyCard - const val REQUEST_CODE_SEND_PAYMENT = 1 + const val REQUEST_CODE_SEND_TRANSACTION = 1 const val REQUEST_CODE_PURGE = 2 const val REQUEST_CODE_REQUEST_PIN2_FOR_PURGE = 3 const val REQUEST_CODE_VERIFY_CARD = 4 @@ -21,7 +21,7 @@ object Constant { const val REQUEST_CODE_ENTER_NEW_PIN2 = 6 const val REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN = 7 const val REQUEST_CODE_SWAP_PIN = 8 - const val REQUEST_CODE_RECEIVE_PAYMENT = 9 + const val REQUEST_CODE_RECEIVE_TRANSACTION = 9 // MainActivity const val REQUEST_CODE_SHOW_CARD_ACTIVITY = 1 @@ -57,26 +57,26 @@ object Constant { const val REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY = 2 const val REQUEST_CODE_REQUEST_PIN2 = 3 - // ConfirmPaymentActivity - const val REQUEST_CODE_SIGN_PAYMENT = 1 + // ConfirmTransactionActivity + const val REQUEST_CODE_SIGN_TRANSACTION = 1 const val REQUEST_CODE_REQUEST_PIN2_ = 2 // SendTransactionActivity const val EXTRA_TX: String = "TX" - // SignPaymentActivity + // SignTransactionActivity const val EXTRA_AMOUNT = "Amount" const val EXTRA_AMOUNT_CURRENCY = "AmountCurrency" const val EXTRA_FEE = "Fee" const val EXTRA_FEE_CURRENCY = "FeeCurrency" const val EXTRA_FEE_INCLUDED = "FeeIncluded" const val EXTRA_TARGET_ADDRESS = "TargetAddress" - const val REQUEST_CODE_SEND_PAYMENT_ = 1 + const val REQUEST_CODE_SEND_TRANSACTION_ = 1 const val RESULT_INVALID_PIN_ = Activity.RESULT_FIRST_USER - // PreparePaymentActivity + // PrepareTransactionActivity const val REQUEST_CODE_SCAN_QR = 1 - const val REQUEST_CODE_SEND_PAYMENT__ = 2 + const val REQUEST_CODE_SEND_TRANSACTION__ = 2 // PrepareCryptonitOtherApiWithdrawalActivity const val REQUEST_CODE_SCAN_QR_KEY = 1 diff --git a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java index 7ab8fe42e0..c10d274cbc 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiCommon.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiCommon.java @@ -24,18 +24,18 @@ public class ServerApiCommon { public static final int ESTIMATE_FEE_PRIORITY = 2; public static final int ESTIMATE_FEE_NORMAL = 3; public static final int ESTIMATE_FEE_MINIMAL = 6; - private EstimateFeeListener estimateFeeListener; + private EstimatedFeeListener estimatedFeeListener; - public interface EstimateFeeListener { + public interface EstimatedFeeListener { void onSuccess(int blockCount, String estimateFeeResponse); void onFail(int blockCount, String message); } - public void setEstimateFee(EstimateFeeListener listener) { - estimateFeeListener = listener; + public void setBtcEstimatedFeeListener(EstimatedFeeListener listener) { + estimatedFeeListener = listener; } - public void estimateFee(int blockCount) { + public void requestBtcEstimatedFee(int blockCount) { EstimatefeeApi estimatefeeApi = App.getNetworkComponent().getRetrofitEstimatefee().create(EstimatefeeApi.class); Call call; @@ -60,17 +60,17 @@ public class ServerApiCommon { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.code() == 200) { - estimateFeeListener.onSuccess(blockCount, response.body()); - Log.i(TAG, "estimateFee onResponse " + response.code() + " " + response.body()); + estimatedFeeListener.onSuccess(blockCount, response.body()); + Log.i(TAG, "requestBtcEstimatedFee onResponse " + response.code() + " " + response.body()); } else - estimateFeeListener.onFail(blockCount, response.body()); - Log.e(TAG, "estimateFee onResponse " + response.code()); + estimatedFeeListener.onFail(blockCount, response.body()); + Log.e(TAG, "requestBtcEstimatedFee onResponse " + response.code()); } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - estimateFeeListener.onFail(blockCount, t.getMessage()); - Log.e(TAG, "estimateFee onFailure " + t.getMessage()); + estimatedFeeListener.onFail(blockCount, t.getMessage()); + Log.e(TAG, "requestBtcEstimatedFee onFailure " + t.getMessage()); } }); } @@ -79,18 +79,18 @@ public class ServerApiCommon { * HTTP * Used in Crypto-currency course */ - private RateInfoDataListener rateInfoDataListener; + private RateInfoListener rateInfoListener; - public interface RateInfoDataListener { + public interface RateInfoListener { void onSuccess(RateInfoResponse rateInfoResponse); } - public void setRateInfoData(RateInfoDataListener listener) { - rateInfoDataListener = listener; + public void setRateInfoListener(RateInfoListener listener) { + rateInfoListener = listener; } @SuppressLint("CheckResult") - public void rateInfoData(String cryptoId) { + public void requestRateInfo(String cryptoId) { CoinmarketApi coinmarketApi = App.getNetworkComponent().getRetrofitCoinmarketcap().create(CoinmarketApi.class); coinmarketApi.getRateInfoList() @@ -100,12 +100,12 @@ public class ServerApiCommon { if (!rateInfoModelList.isEmpty()) { for (RateInfoResponse rateInfoMode : rateInfoModelList) { if (rateInfoMode.getId().equals(cryptoId)) { - rateInfoDataListener.onSuccess(rateInfoMode); - Log.i(TAG, "rateInfoData " + cryptoId + " onResponse " + "200"); + rateInfoListener.onSuccess(rateInfoMode); + Log.i(TAG, "requestRateInfo " + cryptoId + " onResponse " + "200"); } } } else - Log.e(TAG, "rateInfoData " + cryptoId + " onResponse " + "Empty"); + Log.e(TAG, "requestRateInfo " + cryptoId + " onResponse " + "Empty"); }, // handle error Throwable::printStackTrace); diff --git a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java index 53b250d78b..deb6e49560 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiElectrum.java @@ -9,7 +9,6 @@ import com.tangem.domain.wallet.bch.BitcoinCashNode; import com.tangem.domain.wallet.btc.BitcoinNode; import com.tangem.domain.wallet.btc.BitcoinNodeTestNet; import com.tangem.domain.wallet.ltc.LitecoinNode; -import com.tangem.tangemcard.reader.CardProtocol; import com.tangem.wallet.R; import java.io.BufferedReader; @@ -24,7 +23,6 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -47,13 +45,13 @@ import io.reactivex.schedulers.Schedulers; /** * Request processor for Electrum Api * Every request live cycle: - * 1. In application create request and call {@link ServerApiElectrum}.electrumRequestData(..) + * 1. In application create request and call {@link ServerApiElectrum}.requestData(..) * 2. Try send every request for max 4 times, - * 3. If all 4 times fail call DefaultObserver.onError (defined in .electrumRequestData(..)) and than - * {@link ElectrumRequestDataListener}.onFail(...) callback + * 3. If all 4 times fail call DefaultObserver.onError (defined in .requestData(..)) and than + * {@link ResponseListener}.onFail(...) callback * Error can be acquired with {@link ElectrumRequest}.getError() method - * 4. If request network communication finished successfully then call DefaultObserver.onComplete (defined in .electrumRequestData) and than - * {@link ElectrumRequestDataListener}.onSuccess(...) callback + * 4. If request network communication finished successfully then call DefaultObserver.onComplete (defined in .requestData) and than + * {@link ResponseListener}.onSuccess(...) callback */ public class ServerApiElectrum { private static String TAG = ServerApiElectrum.class.getSimpleName(); @@ -62,7 +60,7 @@ public class ServerApiElectrum { * TCP, SSL * Used in BTC, BCH */ - private ElectrumRequestDataListener electrumRequestDataListener; + private ResponseListener responseListener; private String host; private int port; @@ -76,7 +74,7 @@ public class ServerApiElectrum { /** * Interface for notification every request result */ - public interface ElectrumRequestDataListener { + public interface ResponseListener { /** * Notify that request processing was successful @@ -94,8 +92,8 @@ public class ServerApiElectrum { * Set notificaion listener * @param listener */ - public void setElectrumRequestData(ElectrumRequestDataListener listener) { - electrumRequestDataListener = listener; + public void setResponseListener(ResponseListener listener) { + responseListener = listener; } @@ -104,7 +102,7 @@ public class ServerApiElectrum { * @param ctx * @param electrumRequest */ - public void electrumRequestData(TangemContext ctx, ElectrumRequest electrumRequest) { + public void requestData(TangemContext ctx, ElectrumRequest electrumRequest) { requestsCount++; Log.i(TAG, String.format("New request[%d]: %s", requestsCount,electrumRequest.getMethod())); Observable checkElectrumDataObserver = Observable.just(electrumRequest) @@ -129,20 +127,20 @@ public class ServerApiElectrum { @Override public void onNext(ElectrumRequest v) { if (electrumRequest.answerData != null) { - Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null"); + Log.i(TAG, "requestData " + electrumRequest.getMethod() + " onNext != null"); } else { - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext == null"); + Log.e(TAG, "requestData " + electrumRequest.getMethod() + " onNext == null"); } } @Override public void onError(Throwable e) { requestsCount--; - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onError " + e.getMessage()); + Log.e(TAG, "requestData " + electrumRequest.getMethod() + " onError " + e.getMessage()); Log.e(TAG, String.format("%d requests left in processing",requestsCount)); electrumRequest.setError(ctx.getString(R.string.cannot_obtain_data_from_blockchain)); //setErrorOccurred(e.getMessage());//; - electrumRequestDataListener.onFail(electrumRequest); + responseListener.onFail(electrumRequest); } /** @@ -152,16 +150,16 @@ public class ServerApiElectrum { public void onComplete() { requestsCount--; if (electrumRequest.answerData != null) { - Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData!=null"); + Log.i(TAG, "requestData " + electrumRequest.getMethod() + " onComplete, answerData!=null"); } else { - Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData==null"); + Log.e(TAG, "requestData " + electrumRequest.getMethod() + " onComplete, answerData==null"); } Log.e(TAG, String.format("%d requests left in processing",requestsCount)); if (electrumRequest.answerData != null && electrumRequest.getError()==null) { - electrumRequestDataListener.onSuccess(electrumRequest); + responseListener.onSuccess(electrumRequest); } else { // if( error==null || error.isEmpty() ) setErrorOccurred(ctx.getString(R.string.cannot_obtain_data_from_blockchain)); - electrumRequestDataListener.onFail(electrumRequest); + responseListener.onFail(electrumRequest); } } @@ -257,7 +255,7 @@ public class ServerApiElectrum { } catch (ConnectException e) { //e.printStackTrace(); - //electrumRequestDataListener.onFail(e.getMessage()); + //responseListener.onFail(e.getMessage()); electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection)); Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage()); } finally { @@ -274,7 +272,7 @@ public class ServerApiElectrum { } } catch (IOException e) { //e.printStackTrace(); - //electrumRequestDataListener.onFail(e.getMessage()); + //responseListener.onFail(e.getMessage()); electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error)); Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage()); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java index 34b2f33f3f..977518fd29 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInfura.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInfura.java @@ -38,19 +38,19 @@ public class ServerApiInfura { return requestsCount <= 0; } - private InfuraBodyListener infuraBodyListener; + private ResponseListener responseListener; - public interface InfuraBodyListener { + public interface ResponseListener { void onSuccess(String method, InfuraResponse infuraResponse); void onFail(String method, String message); } - public void setInfuraResponse(InfuraBodyListener listener) { - infuraBodyListener = listener; + public void setResponseListener(ResponseListener listener) { + responseListener = listener; } - public void infura(String method, int id, String wallet, String contract, String tx) { + public void requestData(String method, int id, String wallet, String contract, String tx) { requestsCount++; InfuraApi infuraApi = App.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class); @@ -86,18 +86,18 @@ public class ServerApiInfura { public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.code() == 200) { requestsCount--; - infuraBodyListener.onSuccess(method, response.body()); - Log.i(TAG, "infura " + method + " onResponse " + response.code()); + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { - infuraBodyListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "infura " + method + " onResponse " + response.code()); + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - infuraBodyListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "infura " + method + " onFailure " + t.getMessage()); + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } }); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java index fc7de6214c..ac24f5b532 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiInsight.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiInsight.java @@ -32,21 +32,21 @@ public class ServerApiInsight { return requestsCount <= 0; } - private InsightBodyListener insightBodyListener; + private ResponseListener responseListener; - public interface InsightBodyListener { + public interface ResponseListener { void onSuccess(String method, InsightResponse insightResponse); void onSuccess(String method, List utxoList); void onFail(String method, String message); } - public void setInsightResponse(InsightBodyListener listener) { - insightBodyListener = listener; + public void setResponseListener(ResponseListener listener) { + responseListener = listener; } - public void insight(String method, String wallet, String tx) { + public void requestData(String method, String wallet, String tx) { requestsCount++; - String insightURL = "http://130.185.109.17:3001/insight-api"; //TODO: make random selection + String insightURL = "http://130.185.109.17:3001/requestData-api"; //TODO: make random selection this.lastNode = insightURL; //TODO: show node instead of URL Retrofit retrofitInsight = new Retrofit.Builder() //TODO: move to NetworkModule+NetworkComponent if possible @@ -64,18 +64,18 @@ public class ServerApiInsight { public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.code() == 200) { requestsCount--; - insightBodyListener.onSuccess(method, response.body()); - Log.i(TAG, "insight " + method + " onResponse " + response.code()); + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { - insightBodyListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "insight " + method + " onResponse " + response.code()); + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); } } @Override public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - insightBodyListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "insight " + method + " onFailure " + t.getMessage()); + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } }); @@ -112,18 +112,18 @@ public class ServerApiInsight { public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.code() == 200) { requestsCount--; - insightBodyListener.onSuccess(method, response.body()); - Log.i(TAG, "insight " + method + " onResponse " + response.code()); + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { - insightBodyListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "insight " + method + " onResponse " + response.code()); + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - insightBodyListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "insight " + method + " onFailure " + t.getMessage()); + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } }); } diff --git a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java index 988c383639..5d19e3225c 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiRootstock.java @@ -38,19 +38,19 @@ public class ServerApiRootstock { return requestsCount <= 0; } - private RootstockBodyListener rootstockBodyListener; + private ResponseListener responseListener; - public interface RootstockBodyListener { + public interface ResponseListener { void onSuccess(String method, InfuraResponse infuraResponse); void onFail(String method, String message); } - public void setRootstockResponse(RootstockBodyListener listener) { - rootstockBodyListener = listener; + public void setResponseListener(ResponseListener listener) { + responseListener = listener; } - public void rootstock(String method, int id, String wallet, String contract, String tx) { + public void requestData(String method, int id, String wallet, String contract, String tx) { requestsCount++; RootstockApi rootstockApi = App.getNetworkComponent().getRetrofitRootstock().create(RootstockApi.class); @@ -86,18 +86,18 @@ public class ServerApiRootstock { public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.code() == 200) { requestsCount--; - rootstockBodyListener.onSuccess(method, response.body()); - Log.i(TAG, "rootstock " + method + " onResponse " + response.code()); + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); } else { - rootstockBodyListener.onFail(method, String.valueOf(response.code())); - Log.e(TAG, "rootstock " + method + " onResponse " + response.code()); + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - rootstockBodyListener.onFail(method, String.valueOf(t.getMessage())); - Log.e(TAG, "rootstock " + method + " onFailure " + t.getMessage()); + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); } }); } diff --git a/app/src/main/java/com/tangem/data/network/ServerURL.java b/app/src/main/java/com/tangem/data/network/ServerURL.java index b33d4c4ba4..45cad24be7 100644 --- a/app/src/main/java/com/tangem/data/network/ServerURL.java +++ b/app/src/main/java/com/tangem/data/network/ServerURL.java @@ -3,7 +3,7 @@ package com.tangem.data.network; class ServerURL { static final String API_TANGEM = "https://verify.tangem.com/"; static final String API_COINMARKETCAP = "https://api.coinmarketcap.com/"; - static final String API_INFURA = "https://mainnet.infura.io/"; + static final String API_INFURA = "https://mainnet.requestData.io/"; static final String API_ESTIMATEFEE = "https://estimatefee.com/"; static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/"; static final String API_ROOTSTOCK = "https://public-node.rsk.co/"; diff --git a/app/src/main/java/com/tangem/di/Navigator.kt b/app/src/main/java/com/tangem/di/Navigator.kt index 3a620229c4..bc486fbfb5 100644 --- a/app/src/main/java/com/tangem/di/Navigator.kt +++ b/app/src/main/java/com/tangem/di/Navigator.kt @@ -68,8 +68,8 @@ class Navigator { context.startActivityForResult(PurgeActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_PURGE) } - fun showPreparePayment(context: Activity, ctx: TangemContext) { - context.startActivityForResult(PreparePaymentActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_SEND_PAYMENT) + fun showPrepareTransaction(context: Activity, ctx: TangemContext) { + context.startActivityForResult(PrepareTransactionActivity.callingIntent(context, ctx), Constant.REQUEST_CODE_SEND_TRANSACTION) } fun showCreateNewWallet(context: Activity, ctx: TangemContext) { diff --git a/app/src/main/java/com/tangem/di/NavigatorComponent.java b/app/src/main/java/com/tangem/di/NavigatorComponent.java index 23bd390f21..3c2e043645 100644 --- a/app/src/main/java/com/tangem/di/NavigatorComponent.java +++ b/app/src/main/java/com/tangem/di/NavigatorComponent.java @@ -6,7 +6,7 @@ import com.tangem.presentation.activity.LogoActivity; import com.tangem.presentation.activity.MainActivity; import com.tangem.presentation.activity.PrepareCryptonitOtherApiWithdrawalActivity; import com.tangem.presentation.activity.PrepareKrakenWithdrawalActivity; -import com.tangem.presentation.activity.PreparePaymentActivity; +import com.tangem.presentation.activity.PrepareTransactionActivity; import com.tangem.presentation.activity.VerifyCardActivity; import javax.inject.Singleton; @@ -23,7 +23,7 @@ public interface NavigatorComponent { void inject(MainActivity activity); - void inject(PreparePaymentActivity activity); + void inject(PrepareTransactionActivity activity); void inject(PrepareCryptonitOtherApiWithdrawalActivity activity); diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java index 83b0f987bf..c09c9043ee 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java @@ -181,12 +181,10 @@ public abstract class CoinEngine { public abstract boolean isBalanceNotZero(); -// public abstract byte[] sign(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress, CardProtocol protocol) throws Exception; - - // TODO - change isExtractPossible to isExtractPossible and if not - return string message + // TODO - return string message public abstract boolean isExtractPossible(); - public abstract Uri getShareWalletUriExplorer(); + public abstract Uri getWalletExplorerUri(); public abstract Uri getShareWalletUri(); @@ -245,14 +243,14 @@ public abstract class CoinEngine { } /** - * Create instance of {@link SignTask.PaymentToSign} used for transaction signing and sending + * Create instance of {@link SignTask.TransactionToSign} used for transaction signing and sending * * Transaction processing sequence: * 1. User enter transaction attributes - * 2. Application create instance of {@link SignTask.PaymentToSign} by call {@see constructPayment} - * 3. Application set notification when transaction were prepared {@see setOnNeedSendPayment} and start {@link SignTask} + * 2. Application create instance of {@link SignTask.TransactionToSign} by call {@see constructTransaction} + * 3. Application set notification when transaction were prepared {@see setOnNeedSendTransaction} and start {@link SignTask} * 4. User tap card and card sign transaction - * 5. Application receive {@link CoinEngine.OnNeedSendPayment} notification with prepared raw transaction + * 5. Application receive {@link OnNeedSendTransaction} notification with prepared raw transaction * 6. Application show user information that transaction ready for sending and start sending procedure by call {@see requestSendTransaction} * 7. Application receive notification of sending result through {@link CoinEngine.BlockchainRequestsCallbacks} and show result to user * @@ -260,32 +258,32 @@ public abstract class CoinEngine { * @param feeValue - fee amount of desired transaction * @param IncFee - true if fee amount is included in amountValue (amountValue is total amount of transaction) * @param targetAddress - target address of transaction - * @return instance of {@link SignTask.PaymentToSign} + * @return instance of {@link SignTask.TransactionToSign} * @throws Exception if something goes wrong */ - public abstract SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception; + public abstract SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception; /** * Interface used to notify main application when new transaction is prepared to send */ - public interface OnNeedSendPayment { - void onPaymentPrepared(byte[] txForSend); + public interface OnNeedSendTransaction { + void onTransactionPrepared(byte[] txForSend); } - protected OnNeedSendPayment onNeedSendPayment; + protected OnNeedSendTransaction onNeedSendTransaction; /** * Set notification callback when new transaction is prepared to send */ - public void setOnNeedSendPayment(OnNeedSendPayment onNeedSendPayment) { - this.onNeedSendPayment = onNeedSendPayment; + public void setOnNeedSendTransaction(OnNeedSendTransaction onNeedSendTransaction) { + this.onNeedSendTransaction = onNeedSendTransaction; } - protected void notifyOnNeedSendPayment(byte[] txForSend) throws Exception { - if (onNeedSendPayment == null) - throw new Exception("Payment signed but no callback defined to send!"); - onNeedSendPayment.onPaymentPrepared(txForSend); + protected void notifyOnNeedSendTransaction(byte[] txForSend) throws Exception { + if (onNeedSendTransaction == null) + throw new Exception("Transaction was signed but no callback defined to send!"); + onNeedSendTransaction.onTransactionPrepared(txForSend); } /** diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java index bca4b4cb3d..146145b2a5 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BtcCashEngine.java @@ -185,7 +185,7 @@ public class BtcCashEngine extends CoinEngine { } @Override - public Uri getShareWalletUriExplorer() { + public Uri getWalletExplorerUri() { return Uri.parse("https://bch.btc.com/" + ctx.getCoinData().getWallet()); } @@ -454,7 +454,7 @@ public class BtcCashEngine extends CoinEngine { @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); String srcLegacyAddress = convertToLegacyAddress(ctx.getCoinData().getWallet()); @@ -500,7 +500,7 @@ public class BtcCashEngine extends CoinEngine { bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); } - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { @@ -551,7 +551,7 @@ public class BtcCashEngine extends CoinEngine { byte[] txForSend = BCHUtils.buildTXForSend(destLegacyAddress, srcLegacyAddress, unspentOutputs, amountFinal, changeFinal); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; @@ -561,7 +561,7 @@ public class BtcCashEngine extends CoinEngine { public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) throws Exception { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() { + ServerApiElectrum.ResponseListener electrumBodyListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { if (electrumRequest.isMethod(ElectrumRequest.METHOD_GetBalance)) { @@ -606,7 +606,7 @@ public class BtcCashEngine extends CoinEngine { String hash = jsUnspent.getString("tx_hash"); if (height != -1) { if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); + serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); } else { ctx.setError("Terminated by user"); } @@ -649,22 +649,22 @@ public class BtcCashEngine extends CoinEngine { } }; - serverApiElectrum.setElectrumRequestData(electrumBodyListener); + serverApiElectrum.setResponseListener(electrumBodyListener); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.checkBalance(convertToLegacyAddress(coinData.getWallet()))); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(convertToLegacyAddress(coinData.getWallet()))); + serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(convertToLegacyAddress(coinData.getWallet()))); + serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(convertToLegacyAddress(coinData.getWallet()))); } private Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { try { - SignTask.PaymentToSign ps=constructPayment(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress ); - OnNeedSendPayment onNeedSendPaymentBackup=onNeedSendPayment; - onNeedSendPayment=(tx)->{}; // empty function to bypass exception + SignTask.TransactionToSign ps= constructTransaction(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress ); + OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction; + onNeedSendTransaction =(tx)->{}; // empty function to bypass exception byte[][] hashesToSign=ps.getHashesToSign(); byte[] signFromCard = new byte[64 * hashesToSign.length]; byte[] txForSend=ps.onSignCompleted(signFromCard); - onNeedSendPayment=onNeedSendPaymentBackup; + onNeedSendTransaction = onNeedSendTransactionBackup; Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length)); return txForSend.length; } catch (Exception e) { @@ -686,7 +686,7 @@ public class BtcCashEngine extends CoinEngine { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - final ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener () { + final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { BigDecimal fee; @@ -695,13 +695,13 @@ public class BtcCashEngine extends CoinEngine { fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB if (fee.equals(BigDecimal.ZERO)) { - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee()); + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); } // if (calcSize != 0) { fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size // } else { -// serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee()); +// serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); // } //compare fee to usual relay fee @@ -732,9 +732,9 @@ public class BtcCashEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setElectrumRequestData(electrumListener); + serverApiElectrum.setResponseListener(electrumListener); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee()); + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); } @Override @@ -742,7 +742,7 @@ public class BtcCashEngine extends CoinEngine { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); final String txStr = BTCUtils.toHex(txForSend); - ServerApiElectrum.ElectrumRequestDataListener electrumBodyListener = new ServerApiElectrum.ElectrumRequestDataListener() { + ServerApiElectrum.ResponseListener electrumBodyListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { @@ -773,10 +773,10 @@ public class BtcCashEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setElectrumRequestData(electrumBodyListener); + serverApiElectrum.setResponseListener(electrumBodyListener); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); } diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java index 42b0073218..8cab4364e7 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java @@ -189,7 +189,7 @@ public class BtcEngine extends CoinEngine { } @Override - public Uri getShareWalletUriExplorer() { + public Uri getWalletExplorerUri() { return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://blockchain.info/address/" : "https://testnet.blockchain.info/address/") + ctx.getCoinData().getWallet()); } @@ -422,7 +422,7 @@ public class BtcEngine extends CoinEngine { } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { final ArrayList unspentOutputs; checkBlockchainDataExists(); @@ -467,7 +467,7 @@ public class BtcEngine extends CoinEngine { bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); } - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { @@ -517,7 +517,7 @@ public class BtcEngine extends CoinEngine { } byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; @@ -527,7 +527,7 @@ public class BtcEngine extends CoinEngine { public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener() { + ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { Log.i(TAG, "onSuccess: "+electrumRequest.getMethod()); @@ -576,7 +576,7 @@ public class BtcEngine extends CoinEngine { String hash = jsUnspent.getString("tx_hash"); if (height != -1) { if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); + serverApiElectrum.requestData(ctx, ElectrumRequest.getTransaction(walletAddress, hash)); } else { ctx.setError("Terminated by user"); } @@ -617,14 +617,14 @@ public class BtcEngine extends CoinEngine { } }; - serverApiElectrum.setElectrumRequestData(electrumListener); + serverApiElectrum.setResponseListener(electrumListener); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); + serverApiElectrum.requestData(ctx, ElectrumRequest.checkBalance(coinData.getWallet())); + serverApiElectrum.requestData(ctx, ElectrumRequest.listUnspent(coinData.getWallet())); } protected Integer calculateEstimatedTransactionSize(String outputAddress, String outAmount) { - //todo - правильней было бы использовать constructPayment + //todo - правильней было бы использовать constructTransaction try { // String myAddress = coinData.getWallet(); // byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -685,14 +685,14 @@ public class BtcEngine extends CoinEngine { // // byte[] realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change); - SignTask.PaymentToSign ps=constructPayment(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress ); - OnNeedSendPayment onNeedSendPaymentBackup=onNeedSendPayment; - onNeedSendPayment=(tx)->{}; // empty function to bypass exception + SignTask.TransactionToSign ps= constructTransaction(new Amount(outAmount, getBalanceCurrency()),new Amount("0.00",getFeeCurrency()), true, outputAddress ); + OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction; + onNeedSendTransaction =(tx)->{}; // empty function to bypass exception byte[][] hashesToSign=ps.getHashesToSign(); byte[] signFromCard = new byte[64 * hashesToSign.length]; byte[] txForSend=ps.onSignCompleted(signFromCard); - onNeedSendPayment=onNeedSendPaymentBackup; + onNeedSendTransaction = onNeedSendTransactionBackup; Log.e(TAG,"txForSend.length="+String.valueOf(txForSend.length)); return txForSend.length; @@ -717,14 +717,14 @@ public class BtcEngine extends CoinEngine { final ServerApiCommon serverApiCommon = new ServerApiCommon(); - final ServerApiCommon.EstimateFeeListener estimateFeeListener = new ServerApiCommon.EstimateFeeListener() { + final ServerApiCommon.EstimatedFeeListener estimatedFeeListener = new ServerApiCommon.EstimatedFeeListener() { @Override public void onSuccess(int blockCount, String estimateFeeResponse) { BigDecimal fee = new BigDecimal(estimateFeeResponse); // BTC per 1 kb if (fee.equals(BigDecimal.ZERO)) { if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiCommon.estimateFee(blockCount); + serverApiCommon.requestBtcEstimatedFee(blockCount); } return; } @@ -733,7 +733,7 @@ public class BtcEngine extends CoinEngine { fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024), BigDecimal.ROUND_DOWN); // per Kb -> per byte } else { if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiCommon.estimateFee(blockCount); + serverApiCommon.requestBtcEstimatedFee(blockCount); } return; } @@ -763,18 +763,18 @@ public class BtcEngine extends CoinEngine { public void onFail(int blockCount, String message) { // TODO - add fail counter to terminate after NNN tries if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiCommon.estimateFee(blockCount); + serverApiCommon.requestBtcEstimatedFee(blockCount); return; } ctx.setError(ctx.getContext().getString(R.string.cannot_calculate_fee_wrong_data_received_from_node)); blockchainRequestsCallbacks.onComplete(false); } }; - serverApiCommon.setEstimateFee(estimateFeeListener); + serverApiCommon.setBtcEstimatedFeeListener(estimatedFeeListener); - serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY); - serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_NORMAL); - serverApiCommon.estimateFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL); + serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_PRIORITY); + serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_NORMAL); + serverApiCommon.requestBtcEstimatedFee(ServerApiCommon.ESTIMATE_FEE_MINIMAL); } @@ -783,7 +783,7 @@ public class BtcEngine extends CoinEngine { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); final String txStr = BTCUtils.toHex(txForSend); - ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener() { + ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { if (electrumRequest.isMethod(ElectrumRequest.METHOD_SendTransaction)) { @@ -814,10 +814,10 @@ public class BtcEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setElectrumRequestData(electrumListener); + serverApiElectrum.setResponseListener(electrumListener); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); + serverApiElectrum.requestData(ctx, ElectrumRequest.broadcast(ctx.getCoinData().getWallet(), txStr)); } diff --git a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java index 682d62ac33..32261ff2c9 100644 --- a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java @@ -4,8 +4,6 @@ import android.net.Uri; import android.text.InputFilter; import android.util.Log; -import com.tangem.data.network.ElectrumRequest; -import com.tangem.data.network.InsightApi; import com.tangem.data.network.ServerApiInsight; import com.tangem.data.network.model.InsightResponse; import com.tangem.domain.wallet.BTCUtils; @@ -26,10 +24,6 @@ import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.util.DerEncodingUtil; import com.tangem.wallet.R; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.math.BigInteger; @@ -179,7 +173,7 @@ public class DucatusEngine extends BtcEngine { } @Override - public Uri getShareWalletUriExplorer() { + public Uri getWalletExplorerUri() { return Uri.parse("https://live.blockcypher.com/ltc/address/" + ctx.getCoinData().getWallet()); } @@ -397,7 +391,7 @@ public class DucatusEngine extends BtcEngine { } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { final ArrayList unspentOutputs; checkBlockchainDataExists(); @@ -442,7 +436,7 @@ public class DucatusEngine extends BtcEngine { bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); } - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { @@ -492,7 +486,7 @@ public class DucatusEngine extends BtcEngine { } byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; @@ -502,7 +496,7 @@ public class DucatusEngine extends BtcEngine { public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiInsight serverApiInsight = new ServerApiInsight(); - ServerApiInsight.InsightBodyListener insightBodyListener = new ServerApiInsight.InsightBodyListener() { + ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() { @Override public void onSuccess(String method, InsightResponse insightResponse) { switch (method) { @@ -562,7 +556,7 @@ public class DucatusEngine extends BtcEngine { for (InsightResponse utxo : utxoList) { //if (height != -1) { TODO: check if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiInsight.insight(ServerApiInsight.INSIGHT_TRANSACTION, "", utxo.getTxid()); + serverApiInsight.requestData(ServerApiInsight.INSIGHT_TRANSACTION, "", utxo.getTxid()); } else { ctx.setError("Terminated by user"); } @@ -582,10 +576,10 @@ public class DucatusEngine extends BtcEngine { } }; - serverApiInsight.setInsightResponse(insightBodyListener); + serverApiInsight.setResponseListener(responseListener); - serverApiInsight.insight(ServerApiInsight.INSIGHT_ADDRESS, coinData.getWallet(), ""); - serverApiInsight.insight(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS, coinData.getWallet(), ""); + serverApiInsight.requestData(ServerApiInsight.INSIGHT_ADDRESS, coinData.getWallet(), ""); + serverApiInsight.requestData(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS, coinData.getWallet(), ""); } // private final static BigDecimal relayFee = new BigDecimal(0.00001); @@ -600,7 +594,7 @@ public class DucatusEngine extends BtcEngine { final ServerApiInsight serverApiInsight = new ServerApiInsight(); - final ServerApiInsight.InsightBodyListener insightBodyListener = new ServerApiInsight.InsightBodyListener () { + final ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() { @Override public void onSuccess(String method, InsightResponse insightResponse) { if ( method.equals(ServerApiInsight.INSIGHT_FEE)) { @@ -610,7 +604,7 @@ public class DucatusEngine extends BtcEngine { BigDecimal maxFee = new BigDecimal(insightResponse.getFee6()); if (minFee.equals(BigDecimal.ZERO) || normalFee.equals(BigDecimal.ZERO) || maxFee.equals(BigDecimal.ZERO)) { - serverApiInsight.insight(ServerApiInsight.INSIGHT_FEE, "",""); + serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "",""); } minFee = minFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size @@ -650,9 +644,9 @@ public class DucatusEngine extends BtcEngine { } } }; - serverApiInsight.setInsightResponse(insightBodyListener); + serverApiInsight.setResponseListener(responseListener); - serverApiInsight.insight(ServerApiInsight.INSIGHT_FEE, "", ""); + serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "", ""); } @Override @@ -660,7 +654,7 @@ public class DucatusEngine extends BtcEngine { final ServerApiInsight serverApiInsight = new ServerApiInsight(); final String txStr = BTCUtils.toHex(txForSend); - final ServerApiInsight.InsightBodyListener insightBodyListener = new ServerApiInsight.InsightBodyListener () { + final ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() { @Override public void onSuccess(String method, InsightResponse insightResponse) { if (method.equals(ServerApiInsight.INSIGHT_SEND)) { @@ -700,8 +694,8 @@ public class DucatusEngine extends BtcEngine { } } }; - serverApiInsight.setInsightResponse(insightBodyListener); + serverApiInsight.setResponseListener(responseListener); - serverApiInsight.insight(ServerApiInsight.INSIGHT_SEND, "", txStr); + serverApiInsight.requestData(ServerApiInsight.INSIGHT_SEND, "", txStr); } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java index c885e8fb85..19e5c3d1fc 100644 --- a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java @@ -236,7 +236,7 @@ public class EthEngine extends CoinEngine { } @Override - public Uri getShareWalletUriExplorer() { + public Uri getWalletExplorerUri() { if (ctx.getBlockchain() == Blockchain.EthereumTestNet) return Uri.parse("https://rinkeby.etherscan.io/address/" + ctx.getCoinData().getWallet()); else @@ -379,9 +379,9 @@ public class EthEngine extends CoinEngine { } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { - Log.e(TAG, "Construct payment " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding")); + Log.e(TAG, "Construct transaction " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding")); BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -404,7 +404,7 @@ public class EthEngine extends CoinEngine { final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, this.getChainId()); - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { return signingMethod == TangemCard.SigningMethod.Sign_Hash; @@ -455,7 +455,7 @@ public class EthEngine extends CoinEngine { Log.e(TAG, "ETH_v: " +String.valueOf(v)); byte[] txForSend = tx.getEncoded(); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; @@ -464,8 +464,8 @@ public class EthEngine extends CoinEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiInfura serverApiInfura = new ServerApiInfura(); - // request infura listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + // request requestData listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { switch (method) { @@ -517,18 +517,18 @@ public class EthEngine extends CoinEngine { } } }; - serverApiInfura.setInfuraResponse(infuraBodyListener); + serverApiInfura.setResponseListener(responseListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { ServerApiInfura serverApiInfura = new ServerApiInfura(); - // request infura eth gasPrice listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + // request requestData eth gasPrice listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { String gasPrice = infuraResponse.getResult(); @@ -566,9 +566,9 @@ public class EthEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiInfura.setInfuraResponse(infuraBodyListener); + serverApiInfura.setResponseListener(responseListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); } @Override @@ -577,8 +577,8 @@ public class EthEngine extends CoinEngine { String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); ServerApiInfura serverApiInfura = new ServerApiInfura(); - // request infura eth gasPrice listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + // request requestData eth gasPrice listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { @@ -604,9 +604,9 @@ public class EthEngine extends CoinEngine { } }; - serverApiInfura.setInfuraResponse(infuraBodyListener); + serverApiInfura.setResponseListener(responseListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); } } diff --git a/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java index 4ab77928ef..a0c3eb7dc3 100644 --- a/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/ltc/LtcEngine.java @@ -176,7 +176,7 @@ public class LtcEngine extends BtcEngine { } @Override - public Uri getShareWalletUriExplorer() { + public Uri getWalletExplorerUri() { return Uri.parse("https://live.blockcypher.com/ltc/address/" + ctx.getCoinData().getWallet()); } @@ -394,7 +394,7 @@ public class LtcEngine extends BtcEngine { } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { final ArrayList unspentOutputs; checkBlockchainDataExists(); @@ -439,7 +439,7 @@ public class LtcEngine extends BtcEngine { bodyDoubleHash[i] = Util.calculateSHA256(bodyHash[i]); } - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { @@ -489,7 +489,7 @@ public class LtcEngine extends BtcEngine { } byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; @@ -507,7 +507,7 @@ public class LtcEngine extends BtcEngine { final ServerApiElectrum serverApiElectrum = new ServerApiElectrum(); - final ServerApiElectrum.ElectrumRequestDataListener electrumListener = new ServerApiElectrum.ElectrumRequestDataListener () { + final ServerApiElectrum.ResponseListener electrumListener = new ServerApiElectrum.ResponseListener() { @Override public void onSuccess(ElectrumRequest electrumRequest) { BigDecimal fee; @@ -516,13 +516,13 @@ public class LtcEngine extends BtcEngine { fee = new BigDecimal(electrumRequest.getResultString()); //fee per KB if (fee.equals(BigDecimal.ZERO)) { - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee()); + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); } // if (calcSize != 0) { fee = fee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size // } else { -// serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee()); +// serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); // } //compare fee to usual relay fee @@ -553,8 +553,8 @@ public class LtcEngine extends BtcEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiElectrum.setElectrumRequestData(electrumListener); + serverApiElectrum.setResponseListener(electrumListener); - serverApiElectrum.electrumRequestData(ctx, ElectrumRequest.getFee()); + serverApiElectrum.requestData(ctx, ElectrumRequest.getFee()); } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java index 3d381f5b5f..e63e41a529 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskEngine.java @@ -66,12 +66,12 @@ public class RskEngine extends EthEngine { public Uri getShareWalletUri() { return Uri.parse(ctx.getCoinData().getWallet()); } @Override - public Uri getShareWalletUriExplorer() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } + public Uri getWalletExplorerUri() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) { - Log.e(TAG, "Construct payment " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding")); + Log.e(TAG, "Construct transaction " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding")); BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -95,7 +95,7 @@ public class RskEngine extends EthEngine { final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { return signingMethod == TangemCard.SigningMethod.Sign_Hash; @@ -146,7 +146,7 @@ public class RskEngine extends EthEngine { Log.e(TAG, "RSK_v: " +String.valueOf(v)); byte[] txForSend = tx.getEncoded(); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; @@ -155,8 +155,8 @@ public class RskEngine extends EthEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request rootstock listener - ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + // request requestData listener + ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { switch (method) { @@ -208,18 +208,18 @@ public class RskEngine extends EthEngine { } } }; - serverApiRootstock.setRootstockResponse(rootstockBodyListener); + serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request rootstock gasPrice listener - ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + // request requestData gasPrice listener + ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { String gasPrice = rootstockResponse.getResult(); @@ -257,9 +257,9 @@ public class RskEngine extends EthEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiRootstock.setRootstockResponse(rootstockBodyListener); + serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); } @Override @@ -268,7 +268,7 @@ public class RskEngine extends EthEngine { String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { if (method.equals(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION)) { @@ -294,9 +294,9 @@ public class RskEngine extends EthEngine { } }; - serverApiRootstock.setRootstockResponse(rootstockBodyListener); + serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); } diff --git a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java index f571505f4b..89e6e362ce 100644 --- a/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/rsk/RskTokenEngine.java @@ -105,7 +105,7 @@ public class RskTokenEngine extends TokenEngine { } @Override - public Uri getShareWalletUriExplorer() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } // Only RSK explorer for now + public Uri getWalletExplorerUri() { return Uri.parse("https://explorer.rsk.co/address/" + ctx.getCoinData().getWallet()); } // Only RSK explorer for now @Override public Uri getShareWalletUri() { return Uri.parse(ctx.getCoinData().getWallet()); } @@ -186,16 +186,16 @@ public class RskTokenEngine extends TokenEngine { } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { if (amountValue.getCurrency().equals(Blockchain.Rootstock.getCurrency())) { - return constructPaymentRBTC(feeValue, amountValue, IncFee, targetAddress); + return constructTransactionRBTC(feeValue, amountValue, IncFee, targetAddress); } else { - return constructPaymentToken(feeValue, amountValue, IncFee, targetAddress); + return constructTransactionToken(feeValue, amountValue, IncFee, targetAddress); } } - private SignTask.PaymentToSign constructPaymentRBTC(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { - Log.e(TAG, "Construct RBTC payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); + private SignTask.TransactionToSign constructTransactionRBTC(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { + Log.e(TAG, "Construct RBTC transaction "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -219,7 +219,7 @@ public class RskTokenEngine extends TokenEngine { final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { return signingMethod == TangemCard.SigningMethod.Sign_Hash; @@ -270,14 +270,14 @@ public class RskTokenEngine extends TokenEngine { Log.e(TAG,"RSK_v "+ String.valueOf(v)); byte[] txForSend = tx.getEncoded(); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; } - private SignTask.PaymentToSign constructPaymentToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { - Log.e(TAG, "Construct TOKEN payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); + private SignTask.TransactionToSign constructTransactionToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { + Log.e(TAG, "Construct TOKEN transaction "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -327,7 +327,7 @@ public class RskTokenEngine extends TokenEngine { byte[] data = BTCUtils.fromHex(cmd); EthTransaction tx = EthTransaction.create(contractAddress, amountZero, nonceValue, gasPrice, gasLimit, chainId, data); - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { return signingMethod == TangemCard.SigningMethod.Sign_Hash; @@ -378,7 +378,7 @@ public class RskTokenEngine extends TokenEngine { Log.e(TAG,"RSK_v: "+ String.valueOf(v)); byte[] txForSend = tx.getEncoded(); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } @@ -389,8 +389,8 @@ public class RskTokenEngine extends TokenEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request rootstock listener - ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + // request requestData listener + ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { switch (method) { @@ -436,9 +436,9 @@ public class RskTokenEngine extends TokenEngine { // Log.i("$TAG eth_call", balanceCap) if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { ctx.setError("Terminated by user"); } @@ -465,16 +465,16 @@ public class RskTokenEngine extends TokenEngine { } } }; - serverApiRootstock.setRootstockResponse(rootstockBodyListener); + serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); } @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request rootstock gasPrice listener - ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + // request requestData gasPrice listener + ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse rootstockResponse) { String gasPrice = rootstockResponse.getResult(); @@ -516,9 +516,9 @@ public class RskTokenEngine extends TokenEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiRootstock.setRootstockResponse(rootstockBodyListener); + serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); } @Override @@ -527,8 +527,8 @@ public class RskTokenEngine extends TokenEngine { String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); ServerApiRootstock serverApiRootstock = new ServerApiRootstock(); - // request rootstock listener - ServerApiRootstock.RootstockBodyListener rootstockBodyListener = new ServerApiRootstock.RootstockBodyListener() { + // request requestData listener + ServerApiRootstock.ResponseListener responseListener = new ServerApiRootstock.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { if (method.equals(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION)) { @@ -554,9 +554,9 @@ public class RskTokenEngine extends TokenEngine { } }; - serverApiRootstock.setRootstockResponse(rootstockBodyListener); + serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.rootstock(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); } diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java index dd6e5fb76f..24d6604619 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java @@ -289,7 +289,7 @@ public class TokenEngine extends CoinEngine { @Override - public Uri getShareWalletUriExplorer() { + public Uri getWalletExplorerUri() { return Uri.parse("https://etherscan.io/token/" + getContractAddress(ctx.getCard()) + "?a=" + ctx.getCoinData().getWallet()); } @@ -431,16 +431,16 @@ public class TokenEngine extends CoinEngine { } @Override - public SignTask.PaymentToSign constructPayment(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { if (amountValue.getCurrency().equals(Blockchain.Ethereum.getCurrency())) { - return constructPaymentETH(feeValue, amountValue, IncFee, targetAddress); + return constructTransactionETH(feeValue, amountValue, IncFee, targetAddress); } else { - return constructPaymentToken(feeValue, amountValue, IncFee, targetAddress); + return constructTransactionToken(feeValue, amountValue, IncFee, targetAddress); } } - private SignTask.PaymentToSign constructPaymentETH(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { - Log.e(TAG, "Construct ETH payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); + private SignTask.TransactionToSign constructTransactionETH(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { + Log.e(TAG, "Construct ETH transaction "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -465,7 +465,7 @@ public class TokenEngine extends CoinEngine { final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId); - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { return signingMethod == TangemCard.SigningMethod.Sign_Hash; @@ -516,14 +516,14 @@ public class TokenEngine extends CoinEngine { Log.e(TAG,"ETH_v "+ String.valueOf(v)); byte[] txForSend = tx.getEncoded(); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } }; } - private SignTask.PaymentToSign constructPaymentToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { - Log.e(TAG, "Construct TOKEN payment "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); + private SignTask.TransactionToSign constructTransactionToken(Amount feeValue, Amount amountValue, boolean IncFee, String targetAddress) throws Exception { + Log.e(TAG, "Construct TOKEN transaction "+amountValue.toString()+" with fee "+feeValue.toString()+(IncFee?" including":" excluding")); BigInteger nonceValue = coinData.getConfirmedTXCount(); byte[] pbKey = ctx.getCard().getWalletPublicKey(); @@ -573,7 +573,7 @@ public class TokenEngine extends CoinEngine { byte[] data = BTCUtils.fromHex(cmd); EthTransaction tx = EthTransaction.create(contractAddress, amountZero, nonceValue, gasPrice, gasLimit, chainId, data); - return new SignTask.PaymentToSign() { + return new SignTask.TransactionToSign() { @Override public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { return signingMethod == TangemCard.SigningMethod.Sign_Hash; @@ -624,7 +624,7 @@ public class TokenEngine extends CoinEngine { Log.e(TAG,"ETH_v: "+ String.valueOf(v)); byte[] txForSend = tx.getEncoded(); - notifyOnNeedSendPayment(txForSend); + notifyOnNeedSendTransaction(txForSend); return txForSend; } @@ -635,8 +635,8 @@ public class TokenEngine extends CoinEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { final ServerApiInfura serverApiInfura = new ServerApiInfura(); - // request infura listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + // request requestData listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { switch (method) { @@ -682,9 +682,9 @@ public class TokenEngine extends CoinEngine { // Log.i("$TAG eth_call", balanceCap) if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { ctx.setError("Terminated by user"); } @@ -711,16 +711,16 @@ public class TokenEngine extends CoinEngine { } } }; - serverApiInfura.setInfuraResponse(infuraBodyListener); + serverApiInfura.setResponseListener(responseListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); } @Override public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { ServerApiInfura serverApiInfura = new ServerApiInfura(); - // request infura eth gasPrice listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + // request requestData eth gasPrice listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { String gasPrice = infuraResponse.getResult(); @@ -762,9 +762,9 @@ public class TokenEngine extends CoinEngine { blockchainRequestsCallbacks.onComplete(false); } }; - serverApiInfura.setInfuraResponse(infuraBodyListener); + serverApiInfura.setResponseListener(responseListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GAS_PRICE, 67, coinData.getWallet(), "", ""); } @Override @@ -773,8 +773,8 @@ public class TokenEngine extends CoinEngine { String txStr = String.format("0x%s", BTCUtils.toHex(txForSend)); ServerApiInfura serverApiInfura = new ServerApiInfura(); - // request infura eth gasPrice listener - ServerApiInfura.InfuraBodyListener infuraBodyListener = new ServerApiInfura.InfuraBodyListener() { + // request requestData eth gasPrice listener + ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() { @Override public void onSuccess(String method, InfuraResponse infuraResponse) { if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) { @@ -800,9 +800,9 @@ public class TokenEngine extends CoinEngine { } }; - serverApiInfura.setInfuraResponse(infuraBodyListener); + serverApiInfura.setResponseListener(responseListener); - serverApiInfura.infura(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr); } diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt similarity index 93% rename from app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt rename to app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt index 03d704f3ef..8c66aee396 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt @@ -23,12 +23,12 @@ import com.tangem.tangemcard.data.TangemCard import com.tangem.tangemcard.data.loadFromBundle import com.tangem.util.UtilHelper import com.tangem.wallet.R -import kotlinx.android.synthetic.main.activity_confirm_payment.* +import kotlinx.android.synthetic.main.activity_confirm_transaction.* import org.greenrobot.eventbus.EventBus import java.io.IOException import java.util.* -class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { +class ConfirmTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { private lateinit var nfcManager: NfcManager private lateinit var ctx: TangemContext @@ -41,7 +41,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_confirm_payment) + setContentView(R.layout.activity_confirm_transaction) nfcManager = NfcManager(this, this) @@ -74,7 +74,9 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { btnSend.visibility = View.INVISIBLE - rgFee.isEnabled = !(ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token || ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Litecoin) + for (lol in rgFee.touchables) { + lol.isEnabled = !(ctx.blockchain == Blockchain.Ethereum || ctx.blockchain == Blockchain.EthereumTestNet || ctx.blockchain == Blockchain.Token || ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Litecoin) + } // set listeners rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) } @@ -175,7 +177,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } override fun allowAdvance(): Boolean { - return UtilHelper.isOnline(this@ConfirmPaymentActivity) + return UtilHelper.isOnline(this@ConfirmTransactionActivity) } }, etWallet.text.toString(), @@ -199,7 +201,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constant.REQUEST_CODE_SIGN_PAYMENT) { + if (requestCode == Constant.REQUEST_CODE_SIGN_TRANSACTION) { if (data != null && data.extras != null) { if (data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) { val updatedCard = TangemCard(data.getStringExtra("UID")) @@ -222,7 +224,7 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { finish() } else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) { if (resultCode == Activity.RESULT_OK) { - val intent = Intent(baseContext, SignPaymentActivity::class.java) + val intent = Intent(baseContext, SignTransactionActivity::class.java) ctx.saveToIntent(intent) intent.putExtra(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString()) intent.putExtra(Constant.EXTRA_AMOUNT, etAmount.text.toString()) @@ -230,9 +232,9 @@ class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { intent.putExtra(Constant.EXTRA_FEE, etFee.text.toString()) intent.putExtra(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString()) intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee) - startActivityForResult(intent, Constant.REQUEST_CODE_SIGN_PAYMENT) + startActivityForResult(intent, Constant.REQUEST_CODE_SIGN_TRANSACTION) } else - Toast.makeText(baseContext, R.string.pin_2_is_required_to_sign_the_payment, Toast.LENGTH_LONG).show() + Toast.makeText(baseContext, R.string.pin_2_is_required_to_sign_the_transaction, Toast.LENGTH_LONG).show() } } diff --git a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/PrepareTransactionActivity.kt similarity index 92% rename from app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt rename to app/src/main/java/com/tangem/presentation/activity/PrepareTransactionActivity.kt index 23808e6232..aa73d48659 100644 --- a/app/src/main/java/com/tangem/presentation/activity/PreparePaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/PrepareTransactionActivity.kt @@ -20,16 +20,16 @@ import com.tangem.domain.wallet.CoinEngineFactory import com.tangem.domain.wallet.TangemContext import com.tangem.tangemcard.android.reader.NfcManager import com.tangem.wallet.R -import kotlinx.android.synthetic.main.activity_prepare_payment.* +import kotlinx.android.synthetic.main.activity_prepare_transaction.* import java.io.IOException import javax.inject.Inject -class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { +class PrepareTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { companion object { - val TAG: String = PreparePaymentActivity::class.java.simpleName + val TAG: String = PrepareTransactionActivity::class.java.simpleName fun callingIntent(context: Context, ctx: TangemContext): Intent { - val intent = Intent(context, PreparePaymentActivity::class.java) + val intent = Intent(context, PrepareTransactionActivity::class.java) ctx.saveToIntent(intent) return intent } @@ -45,7 +45,7 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_prepare_payment) + setContentView(R.layout.activity_prepare_transaction) App.getNavigatorComponent().inject(this) @@ -118,13 +118,13 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { return@setOnClickListener } - val intent = Intent(baseContext, ConfirmPaymentActivity::class.java) + val intent = Intent(baseContext, ConfirmTransactionActivity::class.java) ctx.saveToIntent(intent) intent.putExtra(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString()) intent.putExtra(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn)) intent.putExtra(Constant.EXTRA_AMOUNT, strAmount) intent.putExtra(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString()) - startActivityForResult(intent, Constant.REQUEST_CODE_SEND_PAYMENT__) + startActivityForResult(intent, Constant.REQUEST_CODE_SEND_TRANSACTION__) } ivCamera.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR) } @@ -169,7 +169,7 @@ class PreparePaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { } } etWallet!!.setText(code) - } else if (requestCode == Constant.REQUEST_CODE_SEND_PAYMENT__) { + } else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) { setResult(resultCode, data) finish() } diff --git a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt index d3465e1d28..f2de42ee6b 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SendTransactionActivity.kt @@ -43,7 +43,7 @@ class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback { if (success) finishWithSuccess() else - finishWithError(this@SendTransactionActivity.getString(R.string.try_again_failed_to_send_transaction)) + finishWithError(ctx.error) } override fun onProgress() { diff --git a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt b/app/src/main/java/com/tangem/presentation/activity/SignTransactionActivity.kt similarity index 90% rename from app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt rename to app/src/main/java/com/tangem/presentation/activity/SignTransactionActivity.kt index 3258c8e3f4..9591aa7857 100644 --- a/app/src/main/java/com/tangem/presentation/activity/SignPaymentActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/SignTransactionActivity.kt @@ -28,18 +28,18 @@ import com.tangem.tangemcard.tasks.SignTask import com.tangem.tangemcard.util.Util import com.tangem.util.LOG import com.tangem.wallet.R -import kotlinx.android.synthetic.main.activity_sign_payment.* +import kotlinx.android.synthetic.main.activity_sign_transaction.* -class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { +class SignTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications { companion object { - val TAG: String = SignPaymentActivity::class.java.simpleName + val TAG: String = SignTransactionActivity::class.java.simpleName } private lateinit var nfcManager: NfcManager private lateinit var ctx: TangemContext - private var signPaymentTask: SignTask? = null + private var signTransactionTask: SignTask? = null private lateinit var amount: CoinEngine.Amount private lateinit var fee: CoinEngine.Amount @@ -51,7 +51,7 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_sign_payment) + setContentView(R.layout.activity_sign_transaction) nfcManager = NfcManager(this, this) @@ -76,21 +76,21 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card public override fun onPause() { nfcManager.onPause() - if (signPaymentTask != null) - signPaymentTask!!.cancel(true) + if (signTransactionTask != null) + signTransactionTask!!.cancel(true) super.onPause() } public override fun onStop() { // dismiss enable NFC dialog nfcManager.onStop() - if (signPaymentTask != null) - signPaymentTask!!.cancel(true) + if (signTransactionTask != null) + signTransactionTask!!.cancel(true) super.onStop() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == Constant.REQUEST_CODE_SEND_PAYMENT_) { + if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) { setResult(resultCode, data) finish() return @@ -127,18 +127,18 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card val coinEngine = CoinEngineFactory.create(ctx) ?: throw CardProtocol.TangemException("Can't create CoinEngine!") - coinEngine.setOnNeedSendPayment { tx -> + coinEngine.setOnNeedSendTransaction { tx -> if (tx != null) { val intent = Intent(this, SendTransactionActivity::class.java) ctx.saveToIntent(intent) intent.putExtra(Constant.EXTRA_TX, tx) - startActivityForResult(intent, Constant.REQUEST_CODE_SEND_PAYMENT_) + startActivityForResult(intent, Constant.REQUEST_CODE_SEND_TRANSACTION_) } } - val paymentToSign = coinEngine.constructPayment(amount, fee, isIncludeFee, outAddressStr) + val transactionToSign = coinEngine.constructTransaction(amount, fee, isIncludeFee, outAddressStr) - signPaymentTask = SignTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, paymentToSign) - signPaymentTask!!.start() + signTransactionTask = SignTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, transactionToSign) + signTransactionTask!!.start() } else nfcManager.ignoreTag(isoDep.tag) @@ -170,7 +170,7 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card } override fun onReadFinish(cardProtocol: CardProtocol?) { - signPaymentTask = null + signTransactionTask = null if (cardProtocol != null) { if (cardProtocol.error == null) { progressBar!!.post { @@ -240,7 +240,7 @@ class SignPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, Card } override fun onReadCancel() { - signPaymentTask = null + signTransactionTask = null progressBar!!.postDelayed({ try { diff --git a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt index 7a29f8f39b..b9429449fb 100644 --- a/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt +++ b/app/src/main/java/com/tangem/presentation/dialog/WaitSecurityDelayDialogNew.kt @@ -6,11 +6,9 @@ import android.app.Dialog import android.os.Bundle import android.support.v7.app.AppCompatDialogFragment import android.widget.ProgressBar -import com.tangem.presentation.activity.SignPaymentActivity import com.tangem.presentation.event.ReadAfterRequest import com.tangem.presentation.event.ReadBeforeRequest import com.tangem.presentation.event.ReadWait -import com.tangem.presentation.event.TransactionFinishWithSuccess import com.tangem.util.LOG import com.tangem.wallet.R import org.greenrobot.eventbus.EventBus 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 b8fd021fe8..a11fcf6d77 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -124,7 +124,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific btnExplore.setOnClickListener { val engine = CoinEngineFactory.create(ctx) - startActivity(Intent(Intent.ACTION_VIEW, engine?.shareWalletUriExplorer)) + startActivity(Intent(Intent.ACTION_VIEW, engine?.walletExplorerUri)) } btnCopy.setOnClickListener { doShareWallet(false) } @@ -157,13 +157,13 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific getString(R.string.via_cryptonit) -> { val intent = Intent(activity, PrepareCryptonitWithdrawalActivity::class.java) ctx.saveToIntent(intent) - startActivityForResult(intent, Constant.REQUEST_CODE_RECEIVE_PAYMENT) + startActivityForResult(intent, Constant.REQUEST_CODE_RECEIVE_TRANSACTION) } getString(R.string.via_kraken) -> { val intent = Intent(activity, PrepareKrakenWithdrawalActivity::class.java) ctx.saveToIntent(intent) - startActivityForResult(intent, Constant.REQUEST_CODE_RECEIVE_PAYMENT) + startActivityForResult(intent, Constant.REQUEST_CODE_RECEIVE_TRANSACTION) } else -> { } @@ -183,7 +183,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific else if (ctx.card!!.remainingSignatures == 0) UtilHelper.showSingleToast(context, getString(R.string.card_has_no_remaining_signature)) else - (activity as LoadedWalletActivity).navigator.showPreparePayment(context as Activity, ctx) + (activity as LoadedWalletActivity).navigator.showPrepareTransaction(context as Activity, ctx) else Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show() } @@ -259,8 +259,8 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific serverApiTangem.setArtworkListener(artworkListener) // request rate info listener - serverApiCommon.setRateInfoData { - if (activity == null || !UtilHelper.isOnline(activity!!)) return@setRateInfoData + serverApiCommon.setRateInfoListener { + if (activity == null || !UtilHelper.isOnline(activity!!)) return@setRateInfoListener val rate = it.priceUsd.toFloat() ctx.coinData!!.rate = rate ctx.coinData!!.rateAlter = rate @@ -417,7 +417,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific updateViews() } - Constant.REQUEST_CODE_SEND_PAYMENT, Constant.REQUEST_CODE_RECEIVE_PAYMENT -> { + Constant.REQUEST_CODE_SEND_TRANSACTION, Constant.REQUEST_CODE_RECEIVE_TRANSACTION -> { if (resultCode == Activity.RESULT_OK) { ctx.coinData?.clearInfo() srl?.postDelayed({ this.refresh() }, 5000) @@ -712,7 +712,7 @@ class LoadedWallet : Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notific throw Exception("Can''t get rate for blockchain " + ctx.blockchainName) } } - serverApiCommon.rateInfoData(cryptoId) + serverApiCommon.requestRateInfo(cryptoId) } else { ctx.error = getString(R.string.no_connection) updateViews() diff --git a/app/src/main/res/layout/activity_confirm_payment.xml b/app/src/main/res/layout/activity_confirm_transaction.xml similarity index 95% rename from app/src/main/res/layout/activity_confirm_payment.xml rename to app/src/main/res/layout/activity_confirm_transaction.xml index b5304e28e4..0004113e61 100644 --- a/app/src/main/res/layout/activity_confirm_payment.xml +++ b/app/src/main/res/layout/activity_confirm_transaction.xml @@ -1,338 +1,338 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -