From f6186a521da767e6f60560b4b31526d50f2a3318 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 26 Dec 2018 18:08:15 +0300 Subject: [PATCH 1/9] 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 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%0Ac1w# zyfzLiMiPO?2XhFJLQ9qur9<#hiTMC1{CoWHjil`H{wV(Pe}13L7Xo{A>NNt0y!>eT zDF8Vok42A@vlV3mobu;Z%CK0eM|Pf;_goJpIutqUQ%)2~dRh_6GE(;-akG@7ejx!p zPU2)J9i4=7xLozIGU~2N>kXPhls=LT<(_F|;K@VY3r>%0yR0^g#WKC#p|zQ!(@TVP z5u~7?g^>`(8S+Yn3muN*umoY;y61wX1F(R&U#&sbhkBEZY}DaT0l8_Oe?M7a(*YQi*~S}?#2 zx!`P0B=hwcL~2dE+qy7prDA4|=4hg3Bsf6uA&r6KrMSFV4Hb%Z)M}rZi#o`L-WMyu zhg=sX0I8qi+$jOp=*Zd&k-7RJWh^g%*A)rX1R7_3GBxoFV=3q;zNaG@IQmOPk)tTl z<(DJSc#^8=1ga9!WCqThQtX@_t%sVAqd74eo7D)eY;L{F0P^Wv{gguyp8!4F0^fEi zKnike5QJ3hT~UBE76I~315DTufIPVOx&fp-Nl00M6!KW--sUSgTQ2!Q=PjmJ(xZ#- z&Ah~w9~dnPo3m`o6|!yPAQ|M==(|H4Niy10Gqyqo0y)v2P495v8 zFY&~hiDQ=M(~D&hvd}C-$^&8tpb+AR!UluEr`M4bHAD5FVI+i1sQc6Nv&x#3EU$&6 z8L19p_}lw8C$$DEP)X=?gPCMB)St5EQhJ+BuQg89s}z||o){Ba_b&j1MCMX>{`SwY zw^cbjjsk?RSHG?Rk+QLiJPzFZ!iN zK^y`ALO6etCmAA1VqFUW5vJ>8zg7$8N>twVr7+(D~_U_C1JuKtn$0_BRbd?lvC7w!C}B9EZZ@DitqzWa@(4TA?>ePQ&<>u^Os!4jELgZt?DDr_G%6X@k>BSd}t1 zP*cPQOD$2jLoiN9wc6T%*=n&6gk;08xzkB@0ubzCrL!#DKPfp%`Ak;;FX-A_ZUO>z ze{LWa31tVmyetlW76EwDSQL;mjI7P-o;!3TMNJh#<*_(_6sfK#EsQv}F+5;We1wyPhZZ2Ol5kh1hYa1 zlpB~@JxW_WzvmP*nSsKLZGCREL$3J9qS1nf1!yD>5#FC_s#vjsR6IbYm zKMD|t)1e;n^Pm442Z;P~86WFZfH0NKgI8n}Dl4W}qCbe?#GL^rE~cgdNI^}U(lL@% zrt&TbhcSTEP9_S;A%jGtBXXDWUR!<5=td+;xOqi!HX+a`g(;{n%Oieh--wLa52AXP z!-WA-pHfP)g!E6)=GC1G(d@B1zBXb;F_N zWjz6?5OS9cQx=2t_X-@m&{y)_ziogV3i+we1rT*bUQ>}cc1mb$G>fAGvIi!)3O zhy)>Q`3iK5U@^jh5*2J3s^eWgGUtU1F6`Ym6ZJNW%QEm}D$(Nv&nVt35S0)z$WCVi zBYx6z)w$f2ZUKPkG?5u9kygN-p|N|~3W+Ctn37gnxoC=yw&`)qV$f>r$#krbxKw03 z83+F9hS~9OERl~l*R9B29!CHGAOJ~3K~!kLaB(%yJ|qB9ln$nlR)_T(5r-Rz4q({k z!t+@**cD|<3$!5;pG;~;QMNAex`iheD-pOA@?CfrV2=XiNcH?jX`>$kWagLGuV26T zKB$BI6MXspb@%HBka9lODFA_uyCNdp3XDQ!th<%z4;GuZ%Ac)F@YH}%ikB)me_0A} zVD~l5`$|d?QmD>|eN(d>$$2R?xj{YnP2sKPP%iN`uSq*4m5?H1OjX29#LM{?qq`jd z5G~(tCu~25&`aJOQBu}7Q4CFjZDH9V@%E0_Vj)U`;)}7ZcHr@$UKJz8JE7;++%Va^ z7>O=smL~kdXdiZ=gyTcBRaKFs3|lZz<}~Pf<`X6yD#34eweSU(jBuWSkkqi?ySuEH zsOqx=CDOYm0rJKheCvmGOww^ZN9P| z-RI}#!~sIMGlGz+Oqd!F=~v?*q|o63efv$RLaO($;}W0VFf~p9!iv!YgAGUC-a9=u zZ#J2F6v2!57|(9REhDR6qhF6&TrRyf7==Pa$)Bs(m|$t=JKnGALWFRkH^(sJb50n? z0*}WsmrmT4(q|0A&8A2fI%yj-H}utnqk-6`@oKM@Iz=UaY1f7fW}xa~dP8k)p_Z^@ zfm7zX0iGaD6Od3ztqM7Ga|Z))s^~6uX7DdM0g$6k)q8LJc!0=hIvVVqntoaJx&fp- z>CWi@DaVg>j=_yrK*ms6zTm96+bYe_5?dm|Ttz|_x^{`&+KVGIEF@IN7RRwn`r&2D zrLVPC8$L##m@!W3GhWadKSHHd74d=9f%3NY$&|&S*J5``#+JU5%X13ig^?PbkaEG# z!Wo9WH?sf$f!g0}4^>{P@@W_%m*5c ziRl@Ec!nh4u-t$Sd&%ICh+ZF&r88RNbUO^$bISI3$ukxJ^4@WR-X8~$|9tAF)&H6S zM19i7It?Hd66wk->9B%gdL=!&AGcJJ=)H8tgH*{4c&(QLgq%&wAJ|^J7&@viQ>?0| za~#a%wg!0}GAXZK(Jw@?izVPiJ(1_N)uYRG6cS1%kl_oO3zP?oUqBct>O z@Bh0~KdtmHUoU`2KGqSbgx5&R0anaDM&4lCYvIK-7x#s8H0%fRLqd zHZdWoo}*)s#j1LK&WN|}kel>xXmvG78w3pCzKcix4hbNz&2zPx;8cBggl3Kk?|O4m zo)L&*4e^iE^-=szbcI-@aLa0&YXy2YmYoR0sCeD{=n5&%D_8mjX;gy(MC3GA44re} zkcqbX;cO%ps;LRKjc%#PSk~mjg%OF?k#zU6EZ?F$MyuNUFwSCt=of~8riGQos}J{; zB0&BWinv40>c2Vd!?OPD`s)OcvQi;u07QM`Y9-^Yx0Mwbg~}$=D+pOg5W=3yM2rIs zS!B*9bzMOZz+n&qSgkG6Xhs_IP7)PYhtbPpu|fRTEw-A1L@juGHPMR3x@9IJ?HI9@BMu*ubl5v4NJsAM#^4SM%f7im9li*N*qSvm;CDidu2 z*aI3<;5S1QZd}txhiq0IZMISvYnx|c>rcKOsI&FP67yP0^@%dMXzI3aYC>6RB+pvALCsftAzI zVS1>8s+r5OqdSs$+byH4NByk?lg%@ShPMEa;}+w`0p#5W7vC>`Tz_pwZcq2IP6tQ{ zceMPJ*7c)nUd0yc3x#vdk7BR}j$aGKbn%FFI_vJ0=?_ZeKc}fZkUB{3R1d8V;)lC@ zD4Kx3Qwj4U%*pX#5~|zhvg7ry9gEOR7M*a||G4a$haGF}66hltlxeoIv`p z#JOI%+8ozuiVTiba({~LiQ}kVW3=GqS~q3JCZR0?U5TX0r6E1k&9csZEr}sW+ZbeN z*~^!(e>6Qqb5OKo5~<;um&2_-Z}z~TBkUx!7X{lX*=5BMZd2vh8j0p{?C)K6L7z-r z#G}O(2`iwzjrr zy~T5D(H^7}&W1}RJ0~_4js3?= zNTI5ZL-o*lmqtgzXqFwjPa4>jvLpC71PByXg1Zyr^^O?&+NKi-b^d%OzLN{b&wQcC zhi@JA1!|#L>vjvwRJ9Ia&|!cuQl*4p8W&PtCO%nE4W(IH#kCfJH@Me=6d(|pc+=c$ zeDkiy;j&=wkHA-CCBk0SW#~dV+!>RhMpj9Ul#q$RG-OAdJr0oLLHUoZrN7GKrv}K0 zJl5#|!Cuo<_Q72RF`4DouB@b^ zAhlh|uUy6+lA-rmhgNE|X9G3yN0XMuXPqX8%MAV>{&wIudB{G}^2eP2_;!-th;pd5ZCx5%wEV$QfV_PodW`DBS)eU6@iQY5E*o#4T^He>)qe|7HEwN5uLZUNFnQ?q_Rd~?-Y{>?TT_GEf z<;zHJ6am5w^~+iRRHMo0^BHGQ4bL^sF+@hz8Pk>aJ;~M28VO_8wWrYY%L#_D2}hn3 zAW2M4D&a!I-L+o6q_w09;xddYzGi`Dsv6g$&jkz+r)h95<$^Z*($7nNiBlA1--6Su zJTM6ZRYcoXAsg)iGu2t_hoJ!Z`ALAh_irB{$9=3b0D^t2|IqZyU6a}2bUKDJ?c^O- z_Do`rwlSIR(PKMrYPQI$WBq-kIh->Oef7-ecH2F*;aq=@4!_rJ=!r&WwRUC(74qsx zF5Cc~d0(SkJw6|*7p3H}&IPz#9IA&Cm7DQ)U zThiNmp~|_QfwZrNe@cX+L~dlcr$?Z45)hLs$efgWGu7`u^yqL@0bIg3P>IGt(Ki2N zlw~wJy~X8lxJKt4hNF#IRhO=kIe4|)*yYT*AS8hH<-JcJ(%7w&$ucOGg|D~pE-Y$p z<$ihBJ2*HpO(Kzt>0(o&kCcA=mYj^J4`R=<`o^HTxXce2801aTG zQv7o4K`PN7EKYfxW^W2CXYS&RGi%{-hB8Nk3 zu?DT8yG=11mt*f)YhC{8?np=V6hi$y<68(daNTI%=87WJsa-}-IvWO7Q9q~OT}3F; zv;L2F{8lZH*SRhXBTAMv0`CN5PL#beE^W%X?(r=3uMGN8nwwz1!hookd(*g1trm=o{fE<&& zJqsZC?CMYdasSEw{!YJd10}Fu>qUr7qfwwdpGFJX8ibPg%Q)I0Ec=mPKJ#`oBQ$Bp zYhn}K)pesMF1+-cEJ3tu#*5$pA&Yhg?Yp%G*DlIf^q?Pt{V>k3ibC+orqJFE{zMmd zrQIf%UdE+W4An!+X7R&YM?4;B`nJr_Rh2yK)nBsOYy;cN%_7$n!prf5z(d5ZkVO}) zFvp?f7=Lg*k#6VCYYyN7bsBq=DDU*e(mr(D@t&$=}$%Q{eGr{Wa&5DumW4kh03WBtPi*ko)n|rS_&6h>YL& zyRJY6wRkq^*QzULLh$Ne5wGg>1JUWRdX2`FHd+l@PIfnmTTG=axSs{;Aluk_b5WEH|GaM@ zD+=-M0>L!^q+CS)8b;|qH9$`EyuSn>2X}U>R~mYMG2-tuV;u{zz}6cN%rl~GFm{^1nJ;6mA~ zc2&4u$<8j%2lXps0i7W_peIh^5r8x`3?=DcU$|%QK2Y zfM`;TLYWCbTgWx~*w{I18yDspwMCW@k~@$!=Lx@%KEGT8oh;UmWGt87oCKni$()r= zoRiAFiX_XJ5D4iyCWbSSczkjFIq6p1S;jcgXbl53srg|YEyh$eL!<-bz_fw+U?)KHJ_DLUr%{0526Z4%<;;S*9FMYsSULHplfgo6WS` zub=HN1CY?eE~Us(E>ebj&QBykoK9DrNe9;FNzchqfb@ms3gt)=9^n?10LCl)AS`;H z_3{$Yp@a}iN=r-MEBy;~GMVaQc3^)L=&&?~|RCF)z#QA40f!ntWmb_>2!x%xE-W&r2ac z7KBvN3u(S~j-;1_5K%}@X^)f`@M1JP@{~*UHjehEeFm!Sl$y8X+pQ%UIppZuUy;#q zL8wkBFWusE6F`uw)9MZaTho(HLFuZImBb*S1{mQ;bz^{N03al{+Z$kni8Z@GOi017 zsbY14MAOwZlpDF(cK=Qdj4txpKtc~{I)nxbKeEgn>OdGBCZ!uz^Rdgt3v+OvE&ldPPnEQZ68W{i5`r93aPitd{}g&Z@~hxH356 zwh!Jx2lr8r8~a+nsBUOzuU_fE0J$##q}S&&I)?V1Iek-TY-Ggi^Yoxy<1pH<&LR|P zpF)oYSE{R5@HO?TC^*=G0rJ%n28dJ7&!Q*REZTt^*?p^M(&#g~44mwkk9Db%fvmII zv5qFQVDJ2B{*kLRD+*Q5r9_!jZ|&y1$)%J4qR*t0jl_>Hd8YhOoT1}_q)$-R@}aPp z*Asw1=j`TYB-F+9cN`8-857ufVz_}gEY7D_rQ&IQfaNsJ(^`%xcCz4ys*8z2frMy0 zIl8q~PZ)W%?rFbOD&?Ab`?7&hAm99TyA30vVXldc?Xt?I3`Ww_Bv6z>tRK=rH={0a z|9(b<4tgLHaz%9nAZh}TH_EXn{9o@xL3_hE>hP?iAx|gTZ*Q1JcYe`?R)b)5s(ZnL z1Ups+{c~T1?e6602&B4fIEqPBuZ-ApXy0VScd`oXF@RLk@_u$Z*70N(^ZZ8h-FCA3 zRu%mj#n8d=IDMo)k+v{`&q_$stp>yF_L$*_PN{k{As-kds!e3RRjs=Ap*WZ(-Vc#C zvy9gDrDFRVkwU)cLTjsBfdN9;O~f0wBtoM408EzW(?UtNNR^yPxkQ1JTC~LVzv`3N zL_?i;wc96pW@6!JINRB7R;wsWsfPE?<%!ARdZRr5D5oJb*E;P$fQYR%#24ll+bIJP zb_zr7g~y@*dE+l2jrA%3a)jsoB>-vrbZ0sD=+UG3C8w(y#}M;_gDV3F-3>Pf=2u50 z3*xUvr~xl)=fYR>4*TFX+Di>Y*3oW+P}1vc!&+f9=0HZBfhb( z5}t$aP(t5@_AT}Wgr>7KV+i#Tfao>H0iviZ2ocU@ZwNwI{l|RIvpVWz$@>Or{9c*7R7a%Yp-( zw)??sK9tHY_H_9f1r=eFNdVF6hlrk>VC~lgkck)^{C%K@<21g+@(j;6FZcV1N62xJ zNrnzp$uU66>ps7BQTk60kYhaWF9AsV-lt#v>JARD*BlndDngSujA+mA(%f&>A3~cD zIdmY@ZZaAjkG|S;8NI*QBtKnmGkLJ#sBZ5K;xMFri(a$lA4I6zJL31OeHC*xe03*e zu0yLX-yki&@79>gT98Ux-Y;D{XXPEh@NtWvJs7JY{!G_gsDHo=gXqc5(h@`mTL_WZc>QZ1-@}KIG;7R zqYsy7z^SVbQKoruI4Uxt>v9nw3hZNPw9`!@TY`|@1SQ>;$(Yf8&81NA?sT>%zB!bs z>8p1Y=z$e7iN;qX3f8*NoEwZtW(KuFv5M4U7AKTUU@lJavN)F~7LOU9k$MWw0?2u( z8s2&Q`1J}N}OgbdW9wiQiCwC{7@Ox?Q8++LAl?8ez>#maPH!|Y27ARR_*9-u{ zV4LGxdvQn;iA_k%OGxh&wxRgQaArFfp57Q+7{ca^svn9H>50VbK(%%FfwAT*f-Se2 zv+DJF^5{a1Z1_=fkc{Q(42Hq}?|Ni&C z|Ni@LzI^wc>(}3T@RtJ+d5fXwW4#0*a#j#-xV>Jt(P6fk9d_>^#L|8@q@3`J^l$(Z zj@g0Fu$eDC_4)l?e1_d_HyVBTtP$)_jlg9cI70XO?c@@S0etcm6Ata2PKV23g!|#p z-UwG2o#a_g%zDXw@*qTVT`pgOQS;=2R=JV@59MZGA{}qf&PYiePybLiQTSCxhIyUS zM@npv8)C(HN9)uean25=%#ajg@+*|EM4I-v{EI}=07ORUcpKJ?5~TzMNUM_3bTo`VeApSv_B4SkI7@g>)VV2MM&-7|Mc#AMxPJ=&}YX{yV35Z-}u}ZH8|GCz%hDE_!^T- zZ?Nj`-7wmXAjIl0;g1^b2p4&r7M;MVf~9n=36aJfscGIXYVjiItLZ z4@8qYlYkH|KR+(j@ibf;NQ4za$ljVjJ zB76E3$F-| zk-)}-?FGID<`uMBm)Gg>V1!vLF8r^PUS)Dv3>xLha7fK;34jS7+j(4cWMvTS~=xloYt?o#SZKm5!Z~o ze^;KfqBiNKNiDft9m#Xf?K%n&gLW`6G%RI=6)7LXUE)X{N!3dLLQFwTj%3sPef{hE zW8-zr_0~X21Ja#Zv(NNw0`FEZ9c3W+khmL&Vu(tmCM=_5Rv6onv6RYqNX&Qnc z*9R|I@KP~Ytp-S+;&aUUW3`1MRgk%YHUIo_1?|7;7LgYBCijA{R)7_kxbHR5vE8uL z+yvbtfqFrs{gBrb0HTs*L7FKS;k;SO@ySJ2@U_L}VEN6=|ZOved~eDQ}bFMj^+>kbeYuPBmvz62mua+T8Tvr!&@vZjGj7B(>2I!a^! z7%4O)PVYUNjzWSYOi22WBNBSNR(g#JUXqyg_6;kE8{w|_GbbUVj^~so@o|0;bSZW^ zgB`03@s^gdNzaU@qEfM15|8Cy#MALvqVIu?OEmLhwzgw7SljlZx4yoip}se;P(RyS zyR~qTtZr+Ym}r|A8liCn1xOXN$Z)->LK|SJQK2-(qFc4y!QgByK33Y6x1%fW`rh8) z=G^<(fgb<>AOJ~3K~&aqGThx(?>Bv&CzHw~C#8J2y*Ab-!dr~-;hqCZA5v7gea+%{ z)CfZ8gja}aEf%p+nB62fYng4KKJ>z2ayI1VyJU)31GL6#DX1h*48T1dhffzjCGkdOOdcPLS&djSUQ(sL%(9}8s;5KfZE3lR)wE_8p5zzfi*Wj4ejqx%{jhl^5ZOs2 z?%p4Y&u{H-Y{nhVK7__mJ#Muk z(Phpxx_pVbj7DS)Aw3ZlC8E^C@uMyz*FH_V(p2K9068s6fA{g@-+ys7JS2#G``e$= zh`b_z$Xj@+-Bo@zKp=G@yJ6;~@nuzUkAk<95D2PNrS@E|dVT|z8sWQ?74^a766HQy zyoTOHd@2e>^?EfC;**`mo>Jl5ZAJa@kQK&$Mq`ZAX!BzoWNW3B1cDL%MI_mu_0kHR z;Npqkpp=$s?a_g@S@+#dV*RM@Y{`J)<*OKb*|0!@@1bfKP38^ySXinx>I_!Bp*oQm z=t)3>tH7>P8GSkk!;nx^8Lrn5#}N zBmMG@F;+Ry+%wnjg|X1~n4qDeyDTLIl0HwyGVP#*U6ljm;=4b5_QlKKK_K$kXTN{^ z_}$kUAf=LNF9Qf5#M)9|K0e%`NqU zKD7Q)1wqKIOBFa||Ih-{63S2)xE{@G-IULhie}PmC?AZCCYpF4{nT9IDPN8fnwYBl zb)>%F-`>Y7S4Z9YlJP6^a~Z?=lf*DKM@jFOMr*Jx)DUk|@;h}+%i(NOSZ^`Ah^!&D z7>2sB{jvL-k=pwDfi}_+Y|!P$yySc$6(wq(;y}c^Y}6GfGEN^4OApJK*6mcc!wen2 zjgdpqU7A29Gg%6jiFOcxD8zyc^f{yS2N!?)=F81T(TM!;$@O<%bAS|OXWd>fCF-gmky++zy$kUm%*bwNuTdj3Ojl z$EHEo7#)qx;uAhFlim?Ynjke{(Ayw+v3{Slpg>Lf5XJHmt5<$Gf80IZ1(mssW zr*mpjCn1C$*^NkG!gSkXa#-}(e`?Ew0zm`k@2bhi?q;{1^>$cn`6wi1b#!bHyPG$# zh0_K?yhePO$R-V-pw@D+FB6N%ChjX4wkmj}x21av{c1dwlqYb5+=C{Wa4<8N>y0ir;9gJW6 z8FCukqiTUU`|Vc6T;t1eUv`iHgctI|LP@Qrq_sJ_IV%7_WR(Hr!Nu>-=UyGUS#SRA z;_D2MQt^+M1EflR^-=}?JX^^=uYURLU+HsH6olNW;N&aU@Ixo+Y1n~}(KPg2(7@3s5Cw7>UE5^y4~7={2ON?jCaa9r(MVpav##&^4F%u6 zSYu&|a%gkDt(nyfT2OZ%_icmjEQZwZFgr>BMs_#}%u?M+^w|!Rn(n96mt!ZlFC^{@K1jCKQf_o9fMi z@H7owii%S0@bnd=gE_RcG;COIvpZbfp}d8<7?;OQ29o zh!-g>NEN|Ifd*7_gEtu5RbPr9TbCQf7Dni?<2mly7Qi}bU{*E(n4NJ@j!#%YU+O>voIyGRDvH50^7zQ9jsGW}nJYp#;u2m1+P|U)d)P_(H zL{uwS&0M1>skF-@-Hf2KIe4eDZ#j2hrCkWneTeTGZ{rR6V*NvU8NZGVs^g$B8(5F# zGffkgla-L*LP`^;mto}UZnT(3O=i7LU}_o_^?pL4Qx1m{`2>a z58^(!{s)W?3=Zht{UaQoVTbG6Z~t62w0ACmpLJMXK!^K*eno(4#&cS^m1z$&&MzV8gcE50PxKq?<#@9a;NY zdqHz((U0FfgX5md;jjeeqAt?I2<{jDg*ANwuajEZNj8lJF4k)Tjef?58hRqJ#b~zm zfGPN<>XclY&b^R#^#gCIR@d@eKY9TD5X$@uMH%PQJ)}}Al;c?qWQ}_diyZ}MU~J&y z=Wic#uy6qKCWgajfBfBBzbPDV{qB!noWqLzBnWv`00CKAQfii+4Up~K+U~kqXYRXB zv$YF_We-tn@EMw95Afn`-9&XX4o$KNYkd zDrqFRN))6Du9oc94WLG+AvWI_N^RCs@eaI}8DVov;`Ed*Vp@Q^KGc}4r`0)GktQ1> zq<+dsO^7v)fhTCqZnFgk8>Yl`D=}sS@#>x|gP}l}QY0a0(wJkoO>3CEcBliOGuS5(D*rd z<@ox1@a-4he(^^D$6JR7{^pNwetQmteDT|huPZm~_LWrj9dV)~O;b{17-Ke*%BbymbVc&ZkWra8z5V>a5ENmi3 zYI1YPh#mIbJwbm5`9#C!9pF6EOrjyH*^id>F0`!SS9kP0pGFgeTr?)&Y= zSu}6xL7khs9r}6{z`^nMp5R5B*gx7^FZ2Y`QI?8=kckCR0thK_O0;qVV-0L+m74y9 zxxoljLzd)z?di2xzL(^M6>MjJe-jY!6p6_5Hu7Z!#Sz-u&zcyS{S@T{XJtzjPXdA z(}4BS+IT*a#V3j07$nh-o7UMh;FakJX`op@@crW=G=45YqkM?`kKyy~nIlG7E zdu`Sx)aP;<(VWYJrVVg!t8Ewss0J$m$n38IUW?CoXWP<*VLNDYw4!x*9HbvX3H&%a z+XhMnDwk5oTm?Z$td4;$ONpbTh>+T~MoM8-s)eFz5l9aiv?W4kvoACl8BLEn@#^he zl2)6(RzglC7!5jT{m1LQaX#rM7Nkn9 zk3$$k)ADMmvkik$yulG6L#W#{(Yg1?FzEyB-k+-(ku63!?;}0Qhw&Qghg@?Q{=i^_ zw2mHqU#f<@ByFIL0_u&+>yHa&<1}c{2KS$R_Es5mP%HAsKLA+G?fzBkjTU1R zwKt})t(HeG>+ zr%G-%oIeOrKf6i*a>({sAYPoyNTK?X!n;zGdYMVlFV&#cc7@W>3478bXnUV^z+h|P zF)n{3TT0`T62s6SSi*HpDR+}$KuETaOg1r3^_qf!GnZKoGt}XW`zR?X4eyI{9xWWf zDLz&J2>7DV3OHd2G}Hlb%k_ktY3r6C@*gnOAppqCIBCj0TBw>?B<0fs#z;Uqi*vG+Vtza z)o2XkbtRAby-lduP6idb(U8Yu-`OyF9D1ISNh|Zp`G=;ex}qP8MeW%Or3iVxkWwC# zdWQI)p}qVxt<{=`?s02hc>hs@pfxm3IjISOOXRmckd2LSf~F~?Euq^jB1D|-!zMCc zPjauy7Cr520qZ2XQuBQn6>3!^c%o$u!lV`f<5VPNn|K8J#hf97C<;?Mbub^S(dss* zXzmL;z?_v85)k@F8d>G>?ghiVK~C6Jn6~^d#i)?QS`#+9>AC6YP{`e-!Xv%1Rg4Skx(5~NSbXYYEj`O>1ru*MT+q-s9mp6kNy*S7p*+tFO6S2HE zh~|Ah_v#ZnAw*&6dnISi&)je!)z07*#TN+G3*AX1RF6K=9~FCs%oL>;p_0r>W51Q8 zwc?pzDw(gf^^F2UJ&n`lA6pvpI6~qrX%l}8AS6(CPW3W;%tfUSE?N0-qCGQ7Vt$ zEY?Gl64I}oZT;aq+xolj|IL4W>#cuXguF_C$Xl+y6d-r0Y^~cD{Pe2{>}oZyqSZSn zJlKiyZl4zbvUCUC31WcghTzPH?Z!5=zWb|R?$-_ucI|`E)Yo=j8nwCIODJI9M5}0j z*zeE5x$|ycJ6iL5-RobiqTNpaH~<7&5FZAJ-M9M$U%lIit(6lS1#CfT4KDBIr{8^r zYP{YWG%!-VgL*Mq@Z*dQcH^75{kz*fyO9uIOHNYDN9P@y^5#?@-voe^zG+1jk?dH< z(NO)P7OJXPP=HcK>mWrH>lXw~eIz&9JXAx)3m~DZ^v3jLJTcU)InqK~KuE)s$q-`b zlz{#cG~Scha+WlJH9ktY*se&qULU!cCp!S7g4xbFs~~iBTBdd;1xiwfH^6CI0H7qh1 zApiG&_kW%S5LfSPu&%Me?lJXs;aJ4)9UO>de=#^xUDxij`+^5XpMN&e>2;fITJCn^ zz=YZ1IY23IsIR(XaBy&>C*KU5CiTN!x4WZj&}gsAj{_@^w>uE&#%td{JmL4Zv~`8D zgI;g-*X{T>bk!SuUA=ZUy6dmWR*!i7J`;FN`UZp6X7bb}S_gqn{_EJlk8}RD-G3yYjvfeI<3+xi!p&p))>O! z^3o$Z;Gyrzr}p!Of_jyFl4NOlZEw6Z%T|o|73|mP*$l@KPORsbWvoQ;PKhEIF|b@S zG7e@%cC6Q@N^byF9Fn$D$!3qBBkp8fNC`jK4~CnPsyz8ckb_BZ^w3 zkCC2`6BZRx%fZ1*YxN(_1CZ;#{momy!^c?&`Rv;_UnxRfDL}ZEmjXn=@HUg(=rK8r z7LN}D#P5dnO}Kwdz)0dDygqgp2wSouZ*!S`fu_8JP67}waQWEncESQ=$7h?2gxmy( zwkXLD{>MGi0%rjF{P;AZ$pOF6hc+-8I{+ZyV&RQV1Cfrgnvzf*L+_@nsF@6lT+?q7x~Fc_0VtR- z9NxG)&@IiFjSn!Spf_>X&K>U&q(*A;$*e%D_(H)psU@l*yqf*dxoJ)i#1oR!SFTWi z(7H`Sq-Jh64fHaUCyNgS@Hg?al>kJ2Nx=peHJZ*aM>-Wt?lqh$7!ca^B4qvN0?7M+ z_~(DY2SLec0Qt=quNEP%6d>vwAH4)1RVt-maA6=gEEccRX!rSm4bBJa*y)0bmd)bC z%id#xG%rina09qa?O;E=ZdkP#NHF98lruJq%jLk|!F}*8faS+yGTFUOkb=c%0SpmN zA&1L@9%ASWVtjajB@2dI1)a_8a*hDN2<(7*3*ZT2BzBX-MfrrR;O19VNb}GZk}E!V z%AW*C5h418#9^pjCBJl~w3L7m5}!4!wM+9%kT^29H^Z_1OgmIL8*(_j8LUZ<_UO11 zc7Ud`?ha|lsFI9nQAp5}7iYy8ZsOr`eThtZ;nG`rqN^r3dN?k>o8qwn(^D3p_IVAA ze~m6ufXEqkVnM6P%m_s-LTOxN$X+lY6l&Eoj`O%!4=(;^dIR-8-}}vJ5c$npZ@yB5 zyh4D$KKNf)0+9ddRDh^uqSi*)dfX-l&^h5Hjxpf`T}t5ZF#xHe#bSjH9+uGh*fU{-Za$9QZy?{15PDIq;gc_)QiE0gKZDq(WK)KG{6m>#<`) z;{y>YhLqqK4Zhxt5d+wA!d=Ou0LYv`eIxM+6-3!djqaSK!pNjrCK#a;LMB06T?jeA*~Pl zc=C}cT0W8$2QmwsJ)*EQluq5|n4@|ORlJ#?34A}eiRbPVud|XmqfWyy={hADkQ^G; zh?=|NEH$k*iovOWAs!8cB^8D69ydzlNmX1v)g!6ILb`@013R>h_EbxS!9R{T^xk#&83` z;XA-XkzsI3r6QIHl%AL5{X*WSk}f+UBgXyFcRlf>D>D!JA!ZEQMoD3eaF z6fY@CLZOMF#F7@?NtkNqlwCGa(*pH#4XS^C@srmMAb5wB0OC0fAaZDcAVg|c?qTz-D{%2~ z63wNO&KOZvCVVajTEu#b(TneBHqbIJrP3;#EePZlf&Ksn>TI|1#%CprSa2TkyFmxZ z25y;^SrrZ zC6Qg`OKekta6~!0sMS@c7bcaoyI8tVd0BC2ZaT+C>H|7#p)EFONt7ksy860IAQ8>R zxBK*>$a2s?=8l3ALuslYIw_@iB|)vKsAzQz+ciQeiPAThN*WOps|G(oqQU9W5;L2Akhuwi$rGfKn+-lz&vXf)dC| zB+FoTo?^?u7q|h*J&7immdc9Jjjhm1E8K=oS`F!Rr9A)ie$w-(RPvHxrCaAK6;?(1 zRIJ$2g8#mELODy0k*{sW@n#`I{$Z1x7{b~c-^ zQ%e5EliHwFbu~(1c*+5Z4zu;B^G5wFZwQ2;hUe3M6241)kh05)qPYn#+hmbG3=rVS zvJTGnZ)WqM?0B#Po8#aRhW@21(3BO7X^#S=K39y;3lUX_<<8?`y&pUNHkC)nZ{GTK zWe9oj_-DWU7sUjmScIbM;P;DJ$)hYKP||3tH`^7A^Jw1~Sx)6{n>}8%iQ{@{%E?{^(p5Wa+{= z5%S0Lj8GjS8U{aaMj1Cv*gDQcq~5teafho4aBuHML-?-?!|O;b};7!!V|w4 zQVHXmJLw5)iKtqM1)BxQwP*qx7{XX~6w`o^e49WFRBo~~nbrbRTKGNEr2FJ*bM;(2 zo(d(h&23J0ypi$?k!D6-n@k#RIx$prfo5<{5C6)(#Bl?1p*^Km{PoKL@<}0yRdW18 z`AGdd)es3GI8uM-%P&5A^Rqwx{`zYG5cxmz|M7qOUob$7p7H=`$W}|se==bFh$(C| zd9+e)Cfv~4E=}Iyke3vO%Ph^toU`f(KW3F&yg}sER>mWdNPeP(c3Gu9;f@sPCWyb> zGy#jzH=sR*rR#j9LcIEov0S2)FSNU#LPr6{F*4_dzV^}*i!k>>u40`@b8!%)A(IOA zIPj;?wdS|k%>JjqT<>(ncebP1*`sYMVX>^O9o# z03ZNKL_t)$7*BB0PC~>LZpw&1cCgXk7LG*wrv`@GDX&WV5RU8#q#$U-2pJppm?7B` zn$U11#TaqkK@3Q#U}o`EqZh}( zg^d#-yjnwd?%Y91?8BAwmq#iVs~)EdPacqOw=TBC5!=)tQ4V_{MF+#=L5()?PcD| zQ=F{dopVn8*Kd6O#`SMLBVT`i{Z#>^{(w0zKt8tr4=)2qj);11qfZ-MEa?ZwK13L` zt7I}MUM%k2>fKc|ETfFPsuq>9U_y=_G!jvlvXZK=-Gt9*6A7HgCYHa99QW_w&r{7X zg^yTJzrBG*oviM;Ych zFR3w&k5Y;fL7#NjhSG^fR#oH(0w$nb)Oy7yF)qNI*x3EBo;zw=SHcjsnWjUKKsVkZ zDmKTfiRn6}*uFqS8tN9-*QeukcXN%y2b>U&5+*O~Pv!fl2%*NhJV;7TJkj-}6sp|p zmQJS|UZjJG((O~j$0Kx%#h6~f{8$(3!i9hN@wwhVmy31v-*qqo?c9F3#_Naw4++xk z##2Ze?oNO>s&;*+}rt~E;mLVtN}`b30&;escwJ{LgB1?kYZ^45>d^#VRXJLEA9(*Nf1gI5F) z#lNox{h;#!V&S{X=@{D;zT2gi9KlD%FLh)cOp6>2;q#d%Yh749Qhj(N3H;{$M&*!Pa3z6M%BkzBXI|=3H+8{r8NYY`n^fX|52r`CN|LanJL^ z{hb{{vy@r~5F}R2o>QuKQ96D4&~J}qqe5+RmT+!%>*VCvZfZoY*EC{$bHI*w?Dvr$Q`>2kNjtT0BvVsbT#Tu$r5t%s{p84Evb&$?S;OK0Io_4G;hII(A z1apqgz$dh-Pm7+;G%#r9QaKG2y3nj+tXrW#Wz50HaR-b@t7;loPebRpg> zshDkuiIoZgc=x`(TDuYzKR`=U^%+_F+&aA;#kncOSq)OGHChVLPCgBUpr2Q^TsFF* zcOEgr6?e!OS%_T1inFkQuR)+{x8a2G`t zTXU%~l}i8Ha*e}59RV;p0}i69bozE`(rDDRg16PwS2}$i)ugMU&e!jzDr}{8tzNZ{ zQt53Dhp7T6Cu&kz1bd%-W6*4+ri@1Y8|U?w{ggxFuomt3>`1_(Gl^8fQLS*3KO%K7h!B2hCFFLe z*%$<0RX5ic=%Y+6OI}x3S3z4_VWGSA(uTsq^4Wej{4_m1U5I3Qx)%uju?Xehhw;B37+tE->@blBzns%36;{~LqN6%!quU2X2XE}tMeE-{Ve z9y)rmU4e@A1S^Zw>^tg#g&$$n1u*TqiZJt?zaMkRvm^W9am0t@mdj%*rGidWNn10e z;_zdZc!6YQi&(NBeuGexpnRDCAm2;ohQx-NeDnZAO*pRgLNUxkCIJY?|Iai)bcu4( z_wHz=ypBAo++J~Ma^BfOWHI_nyj(ZQ*}1Yxic`5cYw-DucqqKZ2)q%=DD#W`&L-3>pdp3ZXcyHqs$O$ zvWxQThNw2J4JMT2Qd`s0HRsQ3Oh+l0OJep{e1Fc7u2;(x)PAMT=GaWNS^KF5t8MoV zo95sio7Of%ZM8K}S>X8DIqM*0(x3wDMbvb<)7v_HTXlrbSMZbaz&sz zN5s1ML*~JXsr~fd|LRu{MX(|#D+dFFO9GIOU;JkT5N#rWn0qN|ugAWEYV!45@!Rc3 zsdjr0l9&Ld|P!2dO&i z00fCf(@zC7<5aQ6lub36_Rg-z3;67IpU0Z6KSJrXR+x-6NX?zMX)~yO4y)bgcji!I zCX@C2d53jB1)7co^wuMk9WE=VfxDtNIUA@#`(Dawvd+y~t&>!X!@7Z*2CzBku=aj7 zYVX+71hDGLvDQ+(8dDB60J$BcG`B3C2}2F{~8~90*C?H)BJHiA_r<~ z!W_ae+dn+Hjw_sQfL^=|1qJ*1B%w5^h<1X_Vi8wN0+4tA>FM~BO7uOZZU_o@|0ekO zW&oLgOeR*NMC8=RaMo>l{fV6)I!y5aLQzVKbq7^j<0+&DeC{jXzkHse{GKVQ%`;9N z@tma2?K(m=*_|f6kJ>9!7&Z0*D$_$9wK|-?9n;!9-=B55sXe>EL0NWyv)S1J#~9@{ zHBr}1)-I~wG(rJmjT&`%tR}5xA2kc4-)b$Us78~?(L~K%-bW4ks;K>-zcYJ)aRrd$ zKe=P;K5EhmGpP*JUH}k)!3ujVb?)*kwZrb4JVTw|OC7X28!2jT_h++T**!<8y`y`n z4tqJ3>2Q1n;HH5o!V45Ek5$SN$mz{rLp`mT*d587r_=m|GO@J`mBhyRABa#I zbH9fHBA^)%+=3_hsoA7022r8z5Dm>>#Os)`YNmrYg0YYCk;mqx^hQncLPjXO=h*C zrxTc1rjdTX)mm}%NOfnc$8&Tp@8I4RyWL(-4fX9EZ8Q5kHI4iB9<9&tjr3(X9G?C{ zjV&DjWPyU^!5CdO%~pa+js`K3Gv3$LH#!8Ntb!8~{9$RU6 zIZG~->dUmdi-9GiXegAzj2HvG@T639lu!0qP$D~24#3}58!j4D-fC2;YQBGVa%_KX zcHLKcLA_6^fR*GSM~Pg(%^ocV+HPx|4!9tYK}EtbH^NOC!FVPvt{59Xz+(Y`3{-(<2j;~B6H6up0I^wd#yCtycjTKO;}uMawfbOV56VOb zPKCyVQg=04A9c{~%gEf*GUKSxTJ$hS$_ntIwU~jk4*9m6K73~6L)dWYy4B?HSz%VZ z*&qgwUf`rd%ME4%gVsKvE<8S%?+XCpwfkyjfcaMAf&ZaQde9L}UAL&j@c@#qU}46D z=(0I*s%MwCHUv5xTmfBTM`l@XP=yK-1pO9z6^k8&2Rd3VRIN1x4rUltmgaP^7?s9R z(9{ab*7METY6Gm`hPK)PXf&0^kp{g^Ra{+PTLpT^D(Gm)+`4aiMU@S6_{asU&Os;K z{HvaTAjSSvdo2WrXG+fHau;~sKz@FRKeE>X0Rl}dQJ*!g0!f=L6p;dCX(A^iQP5(1 zM?Dy%_>TjKza}4OQbb*rIxgkxVdLKrK=g_@01*jfO1&15vw?|g(^yLpSbSir!^uzo z=df8|oR&nDu(I4QH-f@tM=j=`icu1~YR_w)Gbw4}=Ds6g;q%Iz3A3)!J;}G)~}* zV9+|7scEO(W6wae4t8{QlNP4>!qoM6i+nszrGXAXjH;T0>#qrn1r7U~0(7}T0b8|R ztW>Kb$`R0<(xG=@5y;jn426RhRbja}BI^*?W5$!^%K53jfYE5AGs$JrZnZk6sJ*(i zwa6pJyooZIZgfR&QA<;OL+?n9Sis6`K_#e#nlTGF5YshwO~Cz_);yFU6DJIgbEtE7 zvjRr+!imvY9g$}n*{-7w;D!t!@2&ErF*ULlhAn;zv_Dx^Rks0Ff*=9Ny4C;u$w~ka zTRed9L@b~}fSn8ZLcp}OnCw7Dz^Awd%ttLigUyDBNPxux>&F216IcosGh}qI=&V+l zc%E*9JS7kt4oK6W)q?qtvfN=-yS{gD->!~~8SseLfUk_Ybi@z=-dM~)(6k4KSac}U zTBV1bn=KkUI;$Q$Oo9EPM0vfyM6qarX0lcf04?SNs2!QgfxxUmnggC4+yp!a+E|my z1q&n3I5+d@Y$Z;(B1nX(#jb8DZ>`RSm7c-lTLZ(LPzo9JS@=nWCAhdoVW{mh)Q@0~ z1#O9-46IC;Mcz5t;tZ9LX1}1bZa&zvxegVY64MDC)v%ztBT!J$P+dE8^hj>{#W(b(**U+4hr9-u%$vhz}5$aVu7;>FV{sQns44Kn2*ediczP zi;gb5j)mb6$12B{1Fc1&t07D`{Lj`qlVA&x9G?Z3!^&q^SBCO8<(;1dA3J z=uuiGRT}F1YE=p`ojM8KoZ?Y0BtQgDgYwB*C8`R_Vo3}7np#`oeXEco55==pkADNW z66W9A;;pW4Z0s$V*DV`Rsc0VHY8QB#ZOElps23;@_O1qgaS<3+H2{dHzAHME@6zr8&FIfo<(xVsEn%i9YD;<@K;tF)Iq-#)jmc+*lZXNuCkb*{RNbg3Nbj) zMU;d%^Ib+$ISb{;35H)oYJ>x3pivA8bXFFDXo>J3g z3eYjrlQUd4T+{+r>S{fi2NN*;#+&*J6o_MVLc}U6gzE!fUgY$}_t4YQa*!uO2#_VuRHQLA zvZ4`!@bM2Pudd1wUNo!VlOi>zAaC9F{~g%*A#@{@^7uLFe3bbO{sXUU%S8p^I1vU( z7}#^|kHAO+B5K zN|w;QS%ns?kW(TQX7$N%D}`9rIaq^A?V@BTn{RBYwxyxIqA;hpoX@hHw8_yL!2-j{ zKRCkS2pfwb$i!?`e^q=BJuNM}69yKO0%Ym$63?u^5Fq59@$n5cNV~k`0HQ1=DBQg6 z#7_?V>?(A5;{t?-vo}PjW;mFPYc4_>5t#Ll)5=jPm#7pcI#V3}QCBKQ=?nDqrl&Y; zj=Mua=2Eo|x+E$`88ldE&U9>>NbtPGpK&4V#7EVe9nJa;rBm~|*> zZK1>DabB!UyyD1_-No=J^t{ZwS%o%P#F+M??<+Y>e}XqvKcz!z6{}*gFtgC0vKQwS zWo{^73CcU^2T-ixG(B0v5k2p#hhg_hsi3Px65m5l!=4N=K$bl7?}^unzLyXoz*GO# zw^~4FUap3ZO)`LNfB(h*dDUu#0Aa<=Nsr1S7j?^m&&@CogM<^EsucXg@=^gC(L6|k zjnFI)rwgJfg6i)n#Orl1Uy&Ou5)(1=7nm?2VK)q_h<toAo-&~-rSVdtYpdWz5d&n^l0yk^`uT*Uy>$7hZ?6Pf zJ6mkEKuT7AN=`xEy4629<$VVMM3~ZNfdmVNU~~d6NTd*Fw$YTerR-p+&jJYrK!FDq z3Vtmq#aZmZ#$kX)^^ZeS!yU(fIY`4Il7t-W5<{>aMgmww00dRsj7x)pdhYPU#8Onk+0 zBy4FnoT!$lMH{9ce{tDX~^xdQey0Y#cJc zbuiya8J{0_FEK4mzZq@Ei%9{pFJ_#o=M!*FNSxtq=)t>5*4(205=6%2ZCB0XEUwhwLn7xxKR2@I^Xw6-C{ zjwvMu2r#n#n0P!tNa(14?=Qdp!+pTVH|51*OVw73QX%7tQcXALgS@o~mL5n~%ZLDy zuO^?zA|_fvS4m!TlgmIWa7qu6UNOA8B#?#xVFjyqNcbf>A&PXz`BlY@t>q)E1Q_Am zl;zf=%VjL2}q4UOt{mtR3Q{2$r%L{AR z_EwnhE_0kTz+hsU^D1}&;~NY#KG^cI$)1WrfBJwH6*Y{{$<*m(3XmmBp809w0sXH8 z`-Q*%>#u+Jv(*3gty$#n=QkcBXHR&r@FPL!cJ3w(<$sY zX&3k+C3;Oq-dM98l5BDMP~Ny>tXe?hC`7}KoPUEr-t2}uwN2A79R+b-g~^FqdpMzt zdBM{;*@feoqemw?i!+o$d0c>0dk{r9BY?d3Ol#r+{W3m6?iW73IY5*a2LgzQ03c5s z{@MxvqLLAW{~{-ZOYDSHl4}JHBdt)fmK-3eN@C=3)|4cH5zR4y+$NnIR;L*AdoydB#_Kz$1Daq?d^oYUUWy_U z7y;z*j}phdr}5$<J)Yo>0Nvo1~^cb}O03ZNKL_t)N#K+^J z8>CK%xRi-jU`NGyBu5_k(i8)Sg^tD&kN^alDBX;ZUtDG?eVA#~7trxIDWvk_*9sEeD9!lLHmla4S| z3M-0~k(1Bj%In*tZ9TMO5!Ti#1~Z`h!40BHQh8G#Y~PAvnPC*NT)=e%3MHi($ssH+~|L?0yAOF??qAXpHs@jQ20P;(_)ufG2 z?;gijV5Aj<5ys|MMW(Wk`@^Od&zZ@U&m+e}fcN?^ zKG=9oj4ukKiN|5Dw{M2AiE4#33t(bkK#EHk$!YgIlD|Hm%VK*Qq+2~ggrBfxFWR6B zy5~Vkel}M=kcXnAgPf!=ibqycH&okLUfWsGGN7f`e;EQ~$>Xm+^PAknPqV*7Dv`hZ z^-rtr8$P}{ySqYZH~;~{A_2(CS7rb}bP6(nFmggjYdpbm6qb@}1x8C#B~li-N53}7 z4oeI`^4A2@M;7Le2&@%7&6z=WgjYM{ZEdZtLhM*Hw*%hm3!)=D?Ro4yNe{s78P>E3#yNO*VE((rg@%8N^ zE54#@hH@DL1VH4;RsWKBSS!C5gvhV|`ujgV`Gfn0k9#P8pb%>eLUzj97-%PbLQn0aXesqEkX2h8%vCe%acxPwzeX>XIc7 z1wbyA3fU>2n;0M;n(PjnPAMc{(hBZ`5NZVh6Lmvca}(KP43?&1YSJcctH@7`(pZ3$ zRr15tpki`Ig6>FlZAb6;t}!Pk4U zq65DM@c5VCJ^AkAOCAn@NR8`-?9|)X7$6#}6V)RZ-EXacmZnJ8yz&h!V&sWSh>^!x zUP7*#4wdjp0m6GaKe}yfeian;<~O!hXK@8(l?nrn38B|?qa2UuabVAnLs-a!MUzfC z99|}manFMQSzSL zdHkbiR{c1!7D+1w@L0O!{>nX6n3760GIq*o5d_EqyG^eqiH`@rA0w^6WW!=@J~{FP z4D7KFLTk6FFG2*tlqe1|1|V!2Oi}q&k*1buqNO?03jJqc<$ASBXH=uv8OQO60yH~N zD};3SJ$*9l&q!8)V7a5@iN z36+I&)4-*DQ-B~kxo3Xxhk-7SB;qR9{3!F!zgzmuswMYd?g1b&VQHx#rNt5ihzV#A z14#l2nnP;x`Nm{vir$1ELtb>9+4~i2Lbv4-0fbZXLfn3#spT7Qt9Eie4=UQD5nmOi=kz2E)u&jbImdGqFf`O}a8_?vg1d~(%$OO`xr09o6e zI>Uknhz)q@dV`ovHiya>Eln}lDkKl+<#8%iq!$6&s>#(@XlW`QK)#9wNWLr)n63}@ zno*quOgo`as16`@3smDVMFt}8h29C_ie1Gx5=9;=+ZlUIMn_Ler^Vfi$X=94F*pYc z7rN(I>ZT5H;o6N%&W)-35L0^?e7-k;zzPOGShe)YC!c}x4L^4{M;xeA}67uCHvPplFLE9utUc925NPwhqbc#f6>&8(! zG1ee(309#&gg=6XNlFV(!=0{c8_b|n>FJV!v8QlM)He-zB>W!@C=tyPWV@2M2mN%S z;sSiIPP$lVaZ?7_pc1B5qnML>2gpB6?xhU0oZW3$I5(rqmGd~8w!e=6a=?x_UYRdV zrQq}67}nByj366J%)m=$GuTU)myowzp13WqsEZ#tq8yze5kPqLc9cv^(-(nxzFsVp zp`r!^4~mX+P5R(|1v}32m9(v?ry~bdq@OHSpiFZZ`kOgsi|3vr$*kZmhdw&wR7sg6 z^k@KtEx<+T!vLw-u=OO6tr2qa&Q(}pN&}n_J?6V6azbz_t+Hpz#}hVhaB|wTd7i3b zPUloF$J*L&=D7NsWt`d#*}%AEyPCFE2|7*o=~S89X`B}rc|7(OIgHi<19^u z{^pyC0tk#M`#%Dy;mz&2#14sNq#qNP4PVgf>HrlXH*xKCOBoawD zN$rbZ)D&4jZ_?yH@};Q-*HFSu)RxDYIO&TZfZ!~L<_t|?5u-WTjliP8Bvo8Y#dT8< zMM{xataBY~sT%Pp%Gj~dB{N0MO_sK{VOVW2|73I z-`biXIM_VH&Frf^DcHO(TcDg2ZwP?j@JIXl`lW_*yM-vGG#NtR(iFMFLI;qLEln|4 z@>E#>o3pD?Ck8U05a6kP+pyx8TuS0h5MT-I)I?Rc2;!OHndcC#%~%WL*A+V zEz5;s1LVb$%A*|#(?nBGr!^v?rL)xnEAIdW;u#!5?#IIAD=0Yw90|7@?mh|$mhvAR zfRvO)&9|m_0D0=2PtIO-Iy9z>N^x;ZeUmpC`v7eUN$}EC#v8jvxP4vbUDEcsmJLT0 z;)CTLS>fK<5n1`cESY{#b+Q;dL+wWzHyD(=#>BEzG;1Mxbs{glj6nd2%0QmOo-a-0 z2tf)KL0z`j?RFOfsmspG33Pek=i&uYSH86_`+9b^7ys4m^#-y>yxzENBP7eU^}LPy zG^~|lRO6J;;B_dv%3EsZqc-X_gJ#5>tdtgyx@ktR1@1YNfgFxV4gn&S6L>+72tdF% zm6b#;^zs%0WY6ipZ+ZLFjjML6CLL&ye(=>8rdUgA3gJ{*?)AM~fw2DI*r35eWgeX~ z8sDh*t6Q4NH>js}XInIT`*vq26$;P!>LX^|hAFfQkbD(@=A|N=^s;f*luE^^et|~{AS^yRilc|u6HkNe-1zya z)gGtDa#1g?2jBDu6aSO0$D&kP&oo;uwe^pErPDX%Ok|qPjblYRZ{B>Zu6RSARbM_i z4N2i`EtkD|%XFb0(M|J{S-{~1WCAapNlM6a@?|W}+F(3yoMkM``OFTI2MZ3+1q#^< zYl6tjyA*U{5|W#xuq_HYOf15_9sye@KA?tiTbQ*S{tg+nJ>_{fV}pgc?p7|l5{4hP zY)}J;aueZ0FlFJ0xNFwKuzA5EKq3GliV6ye0}!A=&b|o%GGo_RYz8TGQu+zkw5^~~( zAecznO7j!1^M-ceaJ0C*TMz3!#72*!xIdw{2}RD*i5Kk3i&Ak!B1T-XP8LCCVWq60)^S0>o3k8$qUJ?AVBmMch@8U z$id8xfK8XNqO-idsl3gBsH1tlx`2-}liAc#oq^*(UkKE;^c2lVv$9wCb+G=VB~aTs zFwl^fZZ=zV`r@vxc}rk(>p+9ws0uXqwU%eu()F-%CO^6DrZbQ)O(h8mxfd{rN?5F9 zyrv>LtPe)O>Me-uUdWDQu*ZKAu*Zi`%sO2)S9lS{8&_ZjSqo{oV z9y;wPeZanqbc;@Rj-vMBU6(V3eZPhM?kR+dDHYcvv!IZCa)4B>C2w28PLh_+!_^&V zSG=+Wc@%Gg4j z7buA*Edy|s7L#XjFM5hvn>(F+{zAtiB9ny25si--t$iEvaH?Xwn60!lOg#t74ngCN z#1DIv0Fqx8jy3_~DUhnt9d@55BsJ^KzI6PP?N=c{c2hgRL_RaOqo;iZHFs|B-dQ-i z&*@bP+5U*>o(aCljQD)iWHT4TH*>q^;EP*h*6oe>`+D&qtPg%IYN zuuA#F9{m~y%UHUbNx>8u{d%aBE0D{>#0^n(P52c>Y&j-UfFDa`Ty}z{P$pVL2q3_b zw&ZQ-AFm3MWG6re8m<1OT%p=nu(dT;DHg_06ew(@%dH8(!;;Hg1V}VM)<*7xaA4hY zwGJ*R1;yq(b#>z>C%$&r^oCqlInW`ad4*p8u-B#0o9#tVP0VX71_+S3PWJ?a%z#y6 z*$P!fUiRi45FkFM1MaXKoH6C@1FwyCQ#u65x!#d-2#ho1?#}Onj>dh~lnnB4ADclQ z)FEQW;t&IX7!3TB+J;`O;3JI2i#35LaHFuIkA~LwtaUr&PF}t$pL*)GQ|qq2W3#A~((&I?)R;Tl8)%<#YHTK}9ZvLVk5vmztOk3H3;GpX zGdxz{lTcp!jB5-6#g*ZMue}cYOb0U1><)NbttTTFzK+gY=T8FUwYoeGy*N2JU4kNL z$@jd8!WENv-mz`P`J`Y1cormf=sA-5EJ3IvUgcUaQIjYpPHU$?7#MGEs!-iz$wP&D z5}_k4t$lDr#1)$%Ky;3lv0iq(tlY*zx}GhURmukXXb~V$00B=~dDsc*$2LzFOz;=h zq4!ssUtIa@Q=f1AT4OdE^iu#JgWl`jy7mmaN#k((c7Y}xt{Nu&SJzEyPl+9lRNHjp8Vvh*NqC-vcP=4lD7$AN6TiZT7 zd+O>{tzM-=0Kpo>s?ln!_Ce^S&*ysJL@&s#^24ph{r+~>Ibb;ryXO7l;Pa8JsvKxq z6$9_T5WH@f&z(U4$*Iah04dHX?tuXDw`W*&(&RQ4%_&N_=dEMldCQ2k4ZRUU(w3uJ zlY~Z#qi9iv5X>u6Lx3c_GigCX(`+x^m#-{^l|;nBC9t>z549&%u&`Qbsaj<;LXtUQ z-pM8tKL#SLflxhLQHfv?APWH!Nt!?dq{{-+&*$-nNzK`_A8xy~>FamQy2=IskjY|q zvEP-|x9^SJM-M`uqZR-HPW0(^f1m-pUiMeD&ry4JwC7a$H$#8~{PPeXd-okV0#~mF z{q4E%wQ$}afX6i#*SV*FiPaaV%C(yng5*vJmq7rDjDcgpVADiIZjNy(75pRsku;77 zg8G}c)_?&Lx7)1Et>tdEeawOMhQ1pXn-U`~6H)vW_U2~9&A_&lD%A$G%nR(XqOBc> z2sLyDcTcz=x?~X`@c_aOGwB9{6?^Sgtx8%&1H?0PXVcf$Zol~TOBUU~Lx9v3*UcBt zg;$|!!Ni6WePZ69HI4x?4}h~Hr@abkkbPNMRr?}Vtqx{YwF7|c$*T$iq&Tn@YLI|G z$D>sWB+0#W1}VB+@@1@Wgw;K6gv7K3b`v+D-NfS*mxmS+d1756#gx&}+)}6pvt3p& zhIm2s7sZ&rij{!i!LYR&N4>}`rDA%&9!h%4*1QNii_>R~D2Sdf5Mh8U=7WR)0;8LP z>9HYzbOI+qtIon72HV^JcIVpH*Y3Rd_1BgPFptLqby+!MX#0XXvj+jh-wr2w@4Vj+ zV^MZhRSnNlpUt(;4-do7`}}?kkSiB}T)A>E%MSr^W~9pRhsQMq>T(AF^7_H|9*sKr zo$xpo1_2~yE^*s6dUD6YB1B*!>2|F#Y1L0w%f;Xt3Ga|4Ep?IpKon96cKWM6?KyNPq;> zW8wM+4Ui)LJa7gAG(ZAb!=K-J`Rf;NZ+h;-+n1<4qgjC}7c`N2zRImSx`)~`4gj(b zyjHXiw~vF@UG4Mjb9+A9kug8tUJE|&o39!^0$%GUDmp7FhB{aHhcQ6rhv(t(<^C+! z*l(#leSUv?jmD4)Api9|x##^Qd4$ymm3-0-7S5OdMXYW9>&qmL1#r{HCxSK>@8@4G zWYH3~wX}A)==5305vLs33;^_Aj*QbpOo{Z7(F9W-xf#q zZ;uc_mWSdMg8*sBE*|!1i}8nD*S&S~8Mkh|`1Q84XYX8~F5OvuV)cnL7fzph?&|8* zU!1;j>ErFkPJq|b|FM1N>W?o`r|+IPcI=DO7j}Gd?D&a0r>WCl96NsOF8KScV>hsz zI)3&XFtt9uaq7kw;P1DOe|)3$vn!|X-uU>`sk6s-zLiwYo_*r%y0d53MM`X&Em+>% z`NUg0x2y#3o}y)AggggiTUefZaXbF}5|-D(@+_2p4a>9OtB8;9!~eaH-$b?o^4fs=YNC}@0%Esrm4W8YIuW!qiO~Pap+;r+D|hY$_js0; zou9vP>+Gq&!M6)<9p387t#`LsJE^Q~LArf$7RD zidMf&4@mfGUyG7w@kl)Tuh0JLqXUr0O0ygqAmt-oe~pa>$mP82#q%@vk8f{U{oyt^ z-|vEXed^4GD?2_t{M09BfCjk@<~#t%gV@>^y!PO#Dmb*S0+I#svV7;}9U! zrQ1)zU+m4p$9A4LcJB0*D>qIYI|0AHwf)%k+n26fx&wf9V*7K^6Do2}p?S6XxoaB< z1x&hYC!!}uV%f6k&6OBe3H}~_d*bln6X-Jyv*)(qiS^vJ1@hd+7q_k67zM?b;0z2+ z!e?*YeszO?bzI#S2u0k9)ovU zUb{AT=bdc`Z%9sTdh^+l7(Zj{kca7AUsa# z8rlg70VL1spV5Q>GE&^`vD!A>KDFhw+fYG($NVR=z%=^%f8PE3g*^ZuXU~4J7Xabb zy0dQ~fV{Qttvi?YTsZT^-7kQJb@~hNWV;PM@3;e=TDNcC*|~As;q#|2T-gbJ^1GL) zOQ*JNd=7Mc>GsRuf%?)bufHEDufGpJzaAm4{Opwk@=mP0{q~D5BY8VUUS1&A4qtm> z6PCk4*_22=-L&b6C&F#7!T+vZi|j6Zsuy2=8-2o86nVM$cHX}I%IoyoU&8-;|0nMs z{`^+?-#+}we}6x8=dZvvs2x1+(_Pzsxu7?6_pki&)c$p^zY}?jhhGd|6#fF;*~>rs z+1oEi+#`DX#GAqUN+7|3IsD^Qj}SmY^~X6a@c~lT?$cOw`ct=70u6HM4tNYb1vJE- z(?G6Jz;pse0W`5rZF%d>&k;c0+H&jCl|2_OojwhdcE13Yz~`qgz|a5B>C=}keg5VX zpPsvN<;v&l&aOiy*1AvDLDe*P_S7eztotGI@@pMLsj>-G2LU)P#QX8a)~j0{}nA!WoW0cr@OL zHw+Pj@knWqQ2lWZozYQ`0OF=&ar3U%<=VAIwLxck>+b2#F5UhAZrnHqp7=N=7XSpB zSm&XS0Zgph(C0XQEP*<)14f6%~`|-ik^e)Ji(;uHWwsQ{pAjfYU$3Do7 z<99AyIeiBnxAPdb2)4&6`jtW1x#@}HI~U4I{4ZK|KCu(EU71j}pnt%uIQG`2V=LpD z7~8|P#&#@E9X^5ItX}Bi?|^YEg^DN~5`R$Bxr? zKfd+x$2ac4INTj~?(CSGyM5~8Q~Ty-=WYS#8)I=L&V{E^y9| zAG>}23Jz{Re|Gii?MHUZ%^qL9ef8Zlv;Xa5FpX|s+VQPj=T{TT(@sL@NEI%iLg*do zqEbRYN+8rohbz7Jj$BCyy-1BTsnUBd0i<^k=_rJb^bS%4{qg<}@1ET=pLWi9cIU&M zooD7*cE!xi7*xNzTCE>{NAl;Wmjw9XdBb>Og?Yz=FQbv^Q9s$X#W`1a3W6uC=9(V= zE7IBHDZ^CqFBU}En|fpV4c;GZ9Y)Qq z9DF|giu>C#b;ermpmo*D+e-GAd~|nWVWFyu(l_*wC3aEIuPNIQu}%is1NYsXAo9qg z+Yjo=H>Zn>lhj)_zpD)OMOVlRi~Tk=nj=?k_c_bhUO^I`-}97)On^}T*@an?ophzigTOUcJu_h16y2x) z9uVm1(Xo_IQT91J!1O)@M+lMWy^rZ{V?9HEpuyWdsU$j+0}Il{m3(t?$j1Q~(YzB% z?D4l%@OM|LKu*J8Q}#8u&n$(AhnFMjYeLj(W-Z~geJ1@5!>))#H%si4!8(X{sO02J z%a4feUipO0>Wp!qIEzNW)qjWw{_CDLU#we#UK_QcbtDNTRNt_`$R4i{Z-XjjvzZs) z*%Ec&JY@k!2dzv{wEdVGm)RaBUj9nFC{s@rH$$0|QsYuqWXp~JQOW#GNJp<&&kF8K zxC3CHUCK94?e&=0C^$iTSoF52YH9uJuG-w_)JYEF^d=2UC5Oz{7=P>)$jb5<+e*DOo{c44`as<6?36u-Gc7< zzchJ*F22jMWG#UdKz%!2hFROBlv{#pDzH9Pa;jQsiK5d=?S*s?akNtXke@(~@H4rjV!v&ZzS7bm&Xo}V@ZAPo4e@Bfhz zm*__(N5cYE*spvn#$p}{DVSgtZNT!)@GM;ADaz>850_?T8>JL;z6JhCNxmv*9w}A&Yl~z5ur77#X7Y7<+S`%~HuE@w!J@$fRw@eA|mK9=T1`b)=>Y^?*_x z!3icMa#V%%deDf-<|WY z1>6CnSSrC9HxLD_{(f5`p5CQa>iON}1?I;a@G<%^57D%F9HnA4yhD*tT=6wrRFeb2 z-lH4Cj+EtY9kO9Agdvib3U-^?Qd&&zUK!3()V3EomMJ2zQJMMF0z)D$63Pp4@k4&%UyZWQ>j}~=(?x=xi(-&U3$9$!c0rb9 z^F`NO33?GF3w64A6kP2^u7UNBBoMxYywo&tu|@<%Ex=eIgm`L;forKwnaI2{`83(F z;ylkw9V*rJZ%W>IOZ|nOlQg{da#aBJo5=fc3jQd(I!2{4zCvXo#kDe#iUJN=YbHPw zVuDq5V!M!1{~WKFo%x^4I_QrW*^5Cfgrrg9y9}Q^D@l#qdf1a-b$ril(-Fe7I%-V* zO7(=Cc;?e?364DmQ{ba1vqEQn_3udbymjXbye?8nFX5C->DtCQ9rj$@*oBsvhr8aW z+wMRzgyg?2u7GNFM~4H8w7Aw&_t>B|)UjT?cScTr`S4WQn{PV^^8I1Jh!fxa zGO|fZnM;#srIoX2rl^|?5DYOt!KixD$?ZptJUr!}J0>joQ!LyYJHo|T=A1Av8%29s z*5_S@so-AhkA-EI^Aka0`7wEOKD>dMm-5_{j-n>EBoG+(iP3`1}?SaQRH}3vg{X znlQ2|NhS^(*%~*fATS_TjJIocJcnD9f%arp@7Djs?n>n}91~-Vat4Nj(eAT_YH-5> zy7TKGxzyOeNP~dn@i-&nXp`?K8t7>!hIs?Od?7|0Is1e>A@h0zpp%Fqtt7dpcP1mO z#1$uhAv?8(ygAad9qC1(EgOz#pb@Fa+_mXsk#kRx%!)9zr`HJu38coB#DF?frG13x z3!vxj5NJKaT1;MLXJbF@O!^$U@trJB0`z|;ACbP0^Vdih&LS| zp48&|RD}fXXvPDXGA`I*f*!13sC(AOZ#}uHv~xm1eZV?zMK^_}95$%*=)MG0y4xXi zFP?5p6UgNCs3N(0vK#kSOiFDPxk4<dV2wMk!50D9I*aI@-#L!_h! zh4qo;SfQtE%ad03j@iu<5bMY!w@?wo6XPn5?2BgtQJI#(*&CoMDgZ==O%$qhopi(u z{Xj_p{HIUeN>9wN57y@2%Aq60;6FVYdH=gu_*dEnN8FwMRi`xSfZIrJXXbR{y04za zDryuANxDz}Y$*OQ%~$6l9`amkv;rg?>0e_8(V%qRF9NPA!X)CT8k{XEuAlPIE#fhG z-5WZoOEgj-SvE3`8hEYAmKl`(`s@@W=XWGPdzInSE~p(F$C)St0$ZLJ7doMbGtR-hQ3Rb{EUE(b`VlaFaR5ULcU_|6~FGOpfH-$%ap1xtPh+HST z-Y{Wcge?B1NGMhV>gp>81|Iqi69)v%v}K~vGuW(6xDlh&XR=lDzOd14X+D;EZC>ue zzl;@UlR(b=d+ED!6R*GPWK(iiAAtGE_+V5`ry}5fHzs*n$I>`gpkB|$Wp!S z^r6_7p)SGs&>$v({o7)Fa*gQ0Ng916k1)$ME{nC69tro#+uxMbq}h4wb<+=ED8^Y@ z0wtC92WK?|8#9IRbCkfK4SK4%G$d?*47}y!zBy?Vrpe4EG9S8FYpCe_Oek0c-8@_- zFbE2jsiJlbr0b{87b5Q&KKJI@p(spEJQRnzCS{vv|9t`+T0auL>!t2eG0AQts|)eu z`T~S)IjNet#AEa9O>9REouV2aWTNpYPdP3a{E1GpU<3iCY>QJIOKYh|-C;MK zqMpIBdEEHT6f?TM^3f%Phsy`kog7lfq6%W?)y+rQW6L)x=}#TRJ|TsuuqSH4{Ik6z z2?oo*xABGLe|83+&?$CIxd>GKW6-+42j+N627UW1u1_JcVR-K2!SH|450LWDX_VG6F84*OW=;Lwaqs&Tk{g#nrZ zbgExqfQ>0d`wd7l2TrDWN$j;HZVD;XU+DOa_B`FO#QOfF2)qT9zQk7YnNi`uLRh|| zcX2oI;}wDP);ndag4g&?$p;9HbA~3L%>aS}CjI&sTqdggp(GH|BEuhMbR;rA3D?Yj znty;Vny(d&*6HX~FJ$3de$@@ur6&69nK9&M7CGEk=>5BFKN#^@uOSp$lNbJC1QSevnBucx|k~Q_Gh=ag1?8QvJY);LF`~?9P$z)?*q*1rJqy_+{02@yH{r)RyIHVMtrHpS;VVH?Fftvv3&ljtO7Ny*-R4l|c9Dr`d| zJ-|B_;NM_3@1iXFJNp@|M0UpbS9XY@bWBevwkL}xdT+%nl-ySfsaZfMzCD2YCMnyG1&!%cTd zF>OX+Rt29kdB)O_YbU-4#&+T&x{VCSwrcQlKJQ4|IEQf;^RV!y_mue9HvA^&r%Iik zA-sKkP{rIfT+w5#cNtCf{s8g-1(uhdAZyp7FOVJiHFp>F5KpF@gAU&o!>0LGcfw+o`F-@b3ApIW<80#yQW@VGf*DAB z?cXGdiG~l}b*X5_Kw?R#Gr68Mf?aeD?_31lcoS*}|LQ)6`w^ zb(mvc+$nO^gs=$rc(bn3gp(9(s~gYp;)H1()TdJvs$+jF9TjOjT~YX$6J&UOc?}yr zVyS-4u6JZXV@Lt2H0FfxeKv?(p{0+h<5ddb)ly6g?3=I(HlU?|uM)hu_`az{twP8E9x?W?{=qbluTI1hg>bB~oLRVUV#GHZivl_jEK- z@|0CJ^0YGIG$!KbgXMAO`UbEuaW(+D+gRH=ak=vn{TD9R_xpdE>4|{i@JtHS4Cp`lbJrfh{w+5|~ zhpn@LJFTq~@qZxxFNTPTlaZr^y|aa#E$}~>28MPn&b&lK^#3tJ|KC==he!XvXb>=HPft&4YHBt&H(Od-rl+U-`}^^TsJ|BAQE_R$wqc3BF((ne zmf^60)JE!rq!d(0m^5EYaByg}?-K}-(J?=;XvnE3#igXbc42^|)L$!bWRz4?G?ZU6 z2*lqW&cH*V&^|sseyzcNJM;ej{s*xA`hLq|C> zGNPkzFx2ezwF%3>NCAdKhf7Sw!b0)24}(fT#mz}hPfuA9gZVO!z{E%?CPex+iI5+P zb>4$i9F8R+LONQEdN+g^KM3Cl(t))Pe;E6L_gleS=d>BF8ZTWHBiTrg4qbNnP z(eUGBki9t-U67AVM1WLXj`)4w=P8@QT#K+F8KWl`Z6F^_MTS^ToCFz<>Uk8=P9Inm zkD2U)QyPWI$xOw^O*T{UbG{m7tLaCmBi>Cv;@2_sWiQfN{f`J|+{^&%uSK}|N|b|k zWL;$>%upO!V4BjPIyUmHqrGppX_NG1Vaa$fJPAq4KiCKj}v5 z%0}CZr!Y_@g2AF1Fa3F%Oc7*{cb7r2RD%*=izmfP^;Jyqi;L~g_;QE>jHd{4dvGrp zr)OnlWqp19>lU*1PwLRnkgA#-ub}ATIC)2V`)ZGdy`$4+7r%dSWNKPcS3~&KN|3*+ z$kWaG<4J960q@JI#@zTH1{Su77W3udz|OXc*E;gUg%s*2#^pFRCZsx=+crV zKKDI2m0T>xUL2~HT%vw8dn49xGNxKT;p^>e3;#Bx6Eh{<}fg&Tyndz|VOb^e7*r#S=v}kN-Oaz$rs#0LJB+nTS5@moJ)ixp|y|zOH6Gn29c1` zob&%yvJjX{Ol1N7kJ|q$|NlSxe_$q-;YNF;q%?gG9NGQfAMHN>#eE(7y?@k*f8OMM zk`R5}w|vFpl=T-|>Ils`b29ZA)e( zDucW%UcW!^zt?DF)`$Uz7kMIrHqK*Q*~X-zL4QtbMIT zD#^uOV!oSvq52#N1HKWrKfVE{d!8@1V})*40WVeC->cO5pEWFsK6K?h5)+&Cc@zn@ zxL)uxQ<8rdZT`Mj=0>Zen5_Ji20KA+DE?Ye?chWR;uRTum@6WI`qq<2znQzZ_WEmVuRvyAdb&$$weMdyOC4)ASPKDL|4}>N zmkDKL36S{h&uP81$C-TDtgeK+fcyJOC4ryRm|6yMtW!hyqANw}+~ZI-D{_c&CuWCr zK{lC0eQo(NX^kt$pR1DF5g z7UH5-jt!Cs)pJd3JXL54)zEyTt*u2ciT1l0MGWjL@{C>PKfrhbNnpQj#z zwc$nyKJxU|BP^2AwmD@}>Q&NcXLUtb*&J#UnOcq9{SmjX1g}c>42;3I$Ra&F(l#?<<0Nke;nF|C zP=W0~80PX(x311~Zm4rN*Yhzp_$l7i(h$*h>+yTuSAj%)g84bt`_RzO3h4xw-;;YuKI&rAod$+h?@8bKMGhlirk~sKNWpm>q z5Qv4`igjNp!H14U#fPoq;aC|tLGgltl&Uq>MqkCY(2_t#dgJP^*gaX!vH&T6-iGx! z#bmg!0{xzwPUGM`JQO%t<(s7Uk`hz0-aa6ChQstYIpHHOY+{x12BL_R+gY_fy$hNk z+zzN4S&-r>`5vK}R3q39j-6O-e){}saJdXYuZdaQI-Tu1+rpJ-y|vXCuK)hSslK-J zzWx$umkU>eA7FgFEu%jyr((Pb96Gd2lC6Q)YBMNW1C88UPcMfZ;Jl&+(hVNG^{pCP z+V{hmIJ{BBa<*=7lCsB2Is6NTBARaZUDI@@w+i}(M!|4G87>JX>Nld1Cq>`2L0*vj zo@)F)%@l-eAx8Z^$Dg0cR*s>>%UcEuWf~!vqM@o#+)Q;|24a4Ld+moOD#d5anf>Zu z7DJWE;z0R=|FZ*9$v33D@!0BT+Ztt-(J}79{j!rDsO5u0xE}L-Qhps+kXH^V_<8l@ z`y?q;4SG;>#lq`;M`{51C}Ugg4l8NSDk$ycQWxyU(JlrVQ+XHanxX`3M~p&8*J=%4 z`~&&Hw#~?k5mA8Np=R`VDquT@^XqdWK9AQOJJGJcR#z$(C+Y5u|C; zER0#3B+?RV2TI(Lpz0daX7(`m4~3K zCY1l!`+V}d(fj;I@0Tz+Dv3Bse{&;UD=mnhs;G0cl{3X2_k)Xt z5)BU_EEy4(6R(!lrv_*s@axsfuc)1{$3vv<~E z>CowK?=_7iu%$J5M_vS`*%^|%?B^CVOkFArEk{!RL zL5n~Ju<_^X2DWMOV!&6EzhGBYG?zsra+NznJt9;q^>3Vqj7L7NY?WTlEImF9?*3)4 z(2I35sIB&_H}Z2TIk14gqU9MwC`SG0s4Dqj4+^Qgc(<0rG#Po5L0x4UDo$r6!OjKM zmcct>`N9S9c((g`s7b8&k4rB)8tPol1;a+KR~gwAI(=|XD2HiFj`U`%lcHO?aJguy z&oVzVtS!UxGvZeRbLvVFuV_wlMfy2_FYtU%^Oqr;$3{gX+z9&+bXjF-Dh3c2(%>|& z5M5hY%=qeOoVJx0>l_-D4%Coq&ZciVl*wQhm2f}UcAY$kDT*{o#Sycc?kH)CWW5*; z7`xVf*y-uL5JQ5$y!m}$e39;jY{18JOQ9I5RH2r#ME+e^0_|rxY!vgLKkb zfrzTqCPW}IW}t~l7sVN9(3DcjG`nV4vG*^~>RD2QcvbXs97mByCl_b)22sjb#Uc}Vr3Bq%3H!?9yR-4YYn6n zbbYl)s>0S`4&y>dS&~vY8Uvn}+(tHtr=DVcN=qFoXsCB08W6(rZr$wD=PX%Z1@dLi z7ROLn@FDF&A%AK5vdAORb4|cQn<6}{uXet(P(LWc(b5_P)o_0P&Sq`>1pcG}S^Wmq zyW&R0GBG8^a_NNAg<4GH2H#Ns_sy^8r=BNv3G2#Y{|$wBjHPM`{B{CUB`xT1gi0*g z6t}5*`%@_SHhZ+V3^fCYa(GR}6-^3~V~D&ecibgq{OtPkNh2)sKC#DkHLVra86F;$ zb&k39j=G*61`XUQN3}zPo4wD^vCq4NzjecF14lVi@i{L_HCv%Dpnsk`byNl#jDFI_ z+WGXJro2kt$SkO?8b3EYpKE^D*C9=APopV$a4Q2glye|B+1;(|$gYB6T;OQjQzRgCN@?u;o z1TQC=3I4%O#hKuKlUu_N47DS`f``;h^fn{CW@Id9{TJD{<9gXCM9wd3u0V~WWL|0#*q@7X{rD}67BW<&N_qwelevFw<%385urD#dsevrOx7x-X0|VUWtz zuDvfYpRe20TmdAkysnSFIyE^c40IYBTnwb9cEn#czploA;TP{_{2jL~vC;3|w(q z5w|;NT#Gqz-I>X^jfgbLEk?W~otXBr`EKRG9r<0rE`($g5O+fNuD~1Zww-&zByDOQo3tPG=LQut0o5yDY8e-GuE1bTCKRWZf*x^i9ajpYI@TuWD z5}yxx?^AgnM`=B;_5RR(mM|o0G4>VS%Hp=Q%=VlQVQ1{*#p=tPJCxIhHF20YAr)J& ze+a&qrU{DQ8?Ob2CN*uq2+f-UX%159YIu?pRTjb={x~--^?ExJBw;1Oy`3m|vZqVx zP)MGLk!ojRnbrG+`in3VmV@1DOxy#PZFk1Qy;P!LPk z&Z3t~mVtOm49mj~&uQ90N=QJ5Av8SV0l}3MV|Ahyt2P3{B$c+E?+nBS`SU=OES%Q+ zs^JQwSeYXR_M$zoM~9lk*VX^s&M%uJ+QrYm_x8dK!1+`1(w%9XcU~3wnzpS`6&Yl? zJJQCLeT${6%a)NaodT;00P=652JEWfU4O!LUKu0#HyVS{?$LtujYZ-(ZT2m0ikk%= z+K+Gtyf|kZrG`v-w&S(~vSaOk(@5W^<9`1WYb{paGYL0INV>De2kEy>eY<~IQrAHc zQM$vafmC#n*~4l*N^F9td{#aZfqD~~WhqxCtZ*?Zk2IpdNz#mS8x!Td3fgRu5;HJ{ zn}~4pDLRV77JVshY5moDg*3DdW()b`8Xou=q2rr*_dEiU=2yE#RSstlJ@nhxi=O$*w`mzRZ-qY zEXUR6xDW217+D>Cm?z+dHJ(f*jyxQl9kJt|&aBF>h zNjP7yY2YJ*6|EK=2mRpbjAqp~Nf8r~4k+5^uJ!G2SuUgmtsqZAeKHM%76N<`NP=a` z_5*j>$rZo#*HtHYC|%m=x-=(jWt8gbqhDfXTy_mZ@$7KxeNDk44&Hr}NF?qdBD}{A zp1=+ej`DYkPtBVMf&VlisqwO$rIh#b>G`nrL%fJdvS{lQceoCu!5sfH$Mr~RYp^tw zG!3g3R@pO2k zgXY_{iu6w_7c9Uh2b}Wv7Y^CIUgrxR-4pQtGR zT8+yv@kUV~s?;4ff~g%e!Sia;A3=6&4{#`(>YjcJhfl#qdacnx+B^#5!&&{Dtq$GX;YDciXr}Z6T=S+YS!hhO`dS8SvK*| z5N#x2Q;0mqUHrL;Kx_+zqChkcHSE>#8N}-xRFVYDbNQ|h1-y%;4LZnXg;I_b74JP4 z{2_;zM0~X{Zd1_eN62R% zKq~UeUz5bc9p`6xZt6WMM>SO7h#h$2gSzsl;C27((;jm`6gz49gIkI>DyV*lt=IK# zO>aVOqh7sneidMJVF)Di-N@b1!J@BF2}##6fq5=_yg-=UmOetfURX%1H~d!5C9T)4 z_~&>4%o_iK1q3iec`weEAT)u@z1iAPdG~VWpvX?6YFCC$_lHYrR&~RAC5QMwRT%!G z5w*5c^lk|ymX<^;cQLLhU7+){2~6#^_M}I@*vibr@z`RSe+7q1c^7+u9I}^19VmLY z`^{8uSYKpeQ~BxMN=0!H!E{7IG(MQ9)XuuNYC4)W`sa)YZGKR#lV=m0Akc3 zYp52n+uL74F)~duYUO*61_)HadqO3%8)3j!lP>Pj4OY}lS4r~n?b$KRGDn|Ko>TY` z)LfRVzQ=DcE_mz`+M46$|6MGG^k&skap9RT&A?QtJGq9Nt8<-Y?j=NdMhcynSBUb) zW`HE1vDi!r6@w%rdyldBm~2VZ9&FFlsg~EUy3vZGTCGoVQBWSyg`1U>GGeBMI~(ZE zzq)EN|Kl}LQUaDZ`Px{@kA9Yh+{>ERMcK^g1Ot@BGV^?$d`uaH)5R`Mi|&fi;bj6W zacYu|&wFj*w~XdFBV4=8NBKj(84Ho^*X+)axjlLfR>aNjquW|drwROA+>_Snx~PD6 z_vu#qlz;v)3|_$LOZCxfcgPa9yn^@Av&1zA7QMdr16_Wo;wX95Ppb!Bh;*7>F3~0C zfX9KqsUGKjlB?l~_$(PId zd_Lg;3K?&;kZw>xKyGp;YF@Ab29Tu|mbq6_sZBwDN+_#YlO;vK56>ex>kUs0J?N+~ zSca-u0b}F+)JUwf3M~{0Es6v{vnmQb*uz^k5H^(bT93}p)`y6!M-9JL4l%saGBQ{h zzIr1jSxav+R)tZ8{1(2+=BSnKu_|$Be4=te|2@s z>~~=*>$2KV5j>9P-?jv$Kjr+!9Y}bAA&NL+=ZIb{Hx_cXupg;-)>T0Q{vPaW=D`p|f(Gtu z5uDuubAH4|%)YS#=EA*@2iq|v;i1`%$@&VDBZcecB$7Iwcjg5B(O6yzRbeozyBsU{ zE6;Q7mDo!M5QxNbCIWDUfZ51rmY6wZn4lkL`h!Zi_x>D;syv%4ODoLs$j}EzD=wmt z19T-q3xtl*f5Y1y0|Nr9Cl7}clE_Vvh_5dpZ-b-26KN_-JV-zN!*Rlh5OAJ#A zf_;&DyRPvtvZI;R`HPn{_BPzC_R|@Oz4Sq#h%>JiDc+|xw^=`8W=@=_Q&tCgK@Hu8 z1`%^?K*?$WW&Xk7UebKnL>gv#y^==$)_$#fBFFHq1e3DAifwzl-w%t_Cyxp#4c(l{ z=-!cCOE~L^e@~DH?}UOA9b#zxsL$vx#3abA|DMe$)!_5iy(5!LAVf2bv`brk#3-c= zl4&e$b8^C2_S0x{2@g`3rRmaj-4>g>L4SUAaX=_frg{q_Yg{gxRGbTgqJ_Ek zmm{F2{90gr__%YpxJrS>u`b-)Mrs&-0|lR}l>6h@*t%31QPsGlg1`ECkW|v7E-`J} z&5HrwDN>A|=YFsds84Rau(>l63dF;G!9uWfOTkZ-v*!1KejW=ai#Vy+8@mn2*U@jZ zZ~n9UaxWY!UAb5mn?ob|XDXv2pNJ3bN>p?hqH5{$0va%o-(+-*U!B&>x{%&FqXq1S68kXdty(oH8)<J zFj_Qp5xiZkl#=0$&7A|ryI!5QJ!ztM7#efNM(jvb+Bk>N5E-iF%nieeLgJx*(KLQ( zb}A7*mEo-McTtkCD$s!%nom)Zfnk2}X)Ra`xj;5G8;gONftdg#luDPZ+HNWsX1^3r zI8Wd$Cuv0CVCP?%WAmhRN`mS%aTvOCGTD$cy?hT65AoCS6Q@`9LY2F%Lgn==5{ozJJZ9*byZ0mxSVCt+dov>ah+KRyD=)TR2nM#-M3|CKwwq=?I@#F!%9Zw2M z%LLf`NLytBVQfngBM&=A)`XC{;IS5Ef0PA_SnJU>GZ$QhTssTzKwII?(rz*x&wHi6 z2%44q1^lsKJq0JMW0Zs9u(LQgESr=w;v40YELlF4aYE5AXgwJrV`2*2!^`Zhw zF)0&M|CAhtlFQSYg5M$`6t?9JutxO{1;_rGs30;qgjmDDt7ciy7ftqZkKKfeWZEgax2wJaM08l=xxNeBa>LX%)G6BUV*ACs8-lviNkVe$W3qt*5 zBnxyY?CoC-v@_{(9X|+E+YOBchFPMQ+uGa;MT!9V#`#=arNa-NeeM_pD2p)Y2KJ3T zGE5>YqASAlKc_1g;0hY*w7SqDov~%*;q)A=my3A5j&d|{{iTwmX!cQCw#rF+6qS}! zG+;|6j@{cPeIpSWy^y-0-ibwzeZT?_o;IM7rzQNu+fKU1<&cn*-BhS;O|FQ*kiC$n zWa~b z-Yjup%m2zNuZiM7C-~nRdd+f+5$&&vJh`62lya=ph$tVFi^vGFvDBQ`3H|c*F45{n zLH>E-m2vu!EZC!Y(S~&FshBQuX97aVZs@PSkJFup3;Y4qH5WkbGYq%-Nb`{z7Ik%e zZFN|DNl-Sg#r1Pp*Z~h;+gC~f%#PX6@XEgCRLe!fA!%Q5-kLirOwcX&x9hmByO~R0 zo}CKRAg5+zCSlEdWlLim9;HAb)AptCmMO-pXA9~qx2j2(h)7q6B{L@7B2C1YR6};P z3W2yC(i#7*CjLV6%OJRH$&RS6sQ~^aJhCmx2-XB8y*NLoK=ukVwRt+cZkc?eS4Ui- zw|pq0k0T%S`WzfoA`?LZf|~Y1@0o!p4NvvsvDj!NCU@-RZEio$s)w9TRWJqQS!67( z^We=6Zm%SB((%H{OMon0LRh&tF{uZ3AKQlL{@J{G+1x;V^9Cv-m5iKM;@2!i65|m! zYbTrs|Dn;T7-yu`$5AdwAA(k@d3Jz?sv>AH1thd;?OW135Si^cH))GL;sz>5hrqn3 zXz{q4)oX5DM1ENHX*_`oKYUM1!|<2-A2+;f#|j++GQ6djAQ@HE$;>yUOyqNuH8BEe zDrEm4?Q)~ec!kEN%2~zd`f;ZVdJW7`bjF4a%dW2uQ#fKwBns+4ar(HVr{Q!)gAWA` zzY_UwACIDp(afFEz4H-(kFJ1!=408zN#nEx`z-KQKkI=UGCYHlCxOCt3FTVabgVDp z9&uhyHJU}<|rlRh=$joaV77v-9mAQo`7Z?{=`Oh$|p*7I+p*uq2dPQ z@6M^+(L*LYqnSsaL%A}4G>&cz3MLiQd3pGG;7pAe78}?fFUcIDIk%D5Vz<@8`mQmD z3Ttg0fDgjN?c6|Lywv}20fZ6DOs~-5YmI2hUrEVU4&-3s>o9?-zo8c7;E~C-Np_uU zaIb@lqHRgf)OVIXHV&YGebJyx$u9t(b-O_HzDc1l9C{0?HJ=CL#&v50!iJ+yh!73F z73MvKd4GS!jqS;LY(cuRsy6K^g4ANh9Di7>30k{>vmQ}LhV)HNP@=D|O`4?6w2bLZ z7vOmNk3jk(6x!+`5AqaH+9Z>=Tc|LgfGE^U@LhsGrJagB=CQZqdMNirp^c-8Ayv=-H zOllrb!dgFhG!_WtbgO_ZR|As9KII+5Sc7a|*X+QxU>1C~OIb zzWKT2u!j$@xP2j!=ra>-&zZGIl$|Q0&qe`;{gXpQZuQB`-o|gW=i__{x-J5v9F6Qd zXgJ#Gmu1hWe+81KYGcATWcge#?!_v_Yg@x(X;d9W!6x^$uX$}+q7yL4)TCLuXfCCD zIF^@FNo$5RiN+*dYTo>jK%GDESDi?c6@k8)PkAhk$FY$@Q$2gAzx#=jx7NtlSA&7|1+DcgX29ur%$;tM~0>X%{sow2MoMnVQ z`_n-PomVF?@VV4|5?058YOuuhmLdWIZ+8HxjHMMEw-SNmM}5n5F2bSu_#@= z4nh4-JurL)5$H`*GpRw4k@7V10c>?CWE~%R`er=PORQNX6?fIjCEEm6yLxCsbrO&M zViwhOnlv+4O-sr|>q{cOUsL=br(}VxP6B(b0n~1-4|||r8-O34xr)6){{l0DVy=hV zDR^TZi@nmvPVb8+N1mLj;h%BddatCKW`jVtQ{t@ji;m8JkWaE)bVH4Xld3l}ofzxW zcB(lPpx~E9sccw<qk896sXG7kGPEI5`1LFJF z|75!p^1Y+lT?uM9(+I7z+fB!M?jYsm0zC&c#8OKq;%^t51ifWW{qVaAXkkF@O#=29 z9-@>s@!%PYx{4nQI4*(_l^nT)csJH1x>ZX6GE-$08*?&p zh&5-MM~V^Ywg_atkXa-LngIwG_inhz~<_MK>|n1 z^D*fk!#Q+7%7f?Z`BcLye{?S6_!$B;x`L_3(N8N?XErsTUkCHMp(85j$@i z2Z)R52lMG5PIe2W-D+qY9J=Rtw4zxSEfED9Vq zCknowE%!6`qwbAAx1v*=0sM>zg`1g{&83DHp4;AXvme#&%Zei`!|zODW}=Dko~hE6 zoILEuV8vS4a9u5QX6)y|CzGybw&u)Ee{3z!^c&p{q$AHtOG*&kGS`J6T$j$#JT8`S z80*Og`w)f*5V^_Hh`7L;dov!oe2fPAhggD_G_FgCDnjso7=z#!Q3i8qN}h=hRQpn! zRG`_Nho;kJ)s(%Hu~=vfn9;J{e6{ndMnPC1DOMb@(tfEko%a77&{OhS=I+zja}N>f zULV|3lU|`x&1xP9jm2fG*9S;@t z<3~6oJP-o!MUU!rh64jNxX3=G;Epz=%YPZi^u=&X$5GMkL@k1n>{R$po-QTc`@(N@ z>_BZN#ne4lFlV-@?gan6@~EjyTPw`>H*VjI=IiPd!yZ0K5RlxWMJkG^%}4GhJnS9} zN`Bm^jwYJ5Ek08%n0h=$@*!ktW*({^1xbu%rsjYe1~bqkH4*OSyBP3jn3VqbbGPGc z)$1rFB(HT^D8UIs*5SW<2a4j$f|m6*poXAOu6*~1K0Lk&6}#p8+dm(xaBa!>4~Zah zDmTgIgZKMU*KfUw9h}{3i8Aa!GC3^Iuh8bkECm9`qi#HpDf6a z2tR>97e)`CQHR|4oFibDtjv6>ttprf!ybwLQBQ_eQU6iF>fQt^co-;=>9tI!$5EX7 zi#%-azMXcA2|MHH>5lyf%$=Lfw!@(uf+l?BNcUI-0%aB(S0aOCNPc6MMUj8Kvx$Xc zMHd_1J6`^6XGc+PT6y17@0_LTxza!DfCP$nVOEcihwherN+4g;=X5{Z#(?*%jj60x zzv<4H1O9nD23~7Tu);)Rn}k?^qCBP3NUS-eVDqO^d67I7xfu$v6K zzsH)~vey!TKOfJ|Quh|0_niY%C$;Y}udeoCKphQWqVdF;o~Zn%pq_hd;|T|b0ohT} zer~BoHf~N02=tFQvLt&mTZefxouy?afpR-04sLWYQv1pD#%5>&QUWGA;Y!dp;9JhN zS`+vjfUQ_zfnkRufE*`-Z^9;V_-+O{xIn;hp6;0o3+!DLmu0(WeJuX7>(SOH02-^2 zrNFHV0=$EpQNcmkOVb5$R5Rg46Gle#b z)D(yM2_I?!3#K!52sgxmsOWnhbv$W#e@aQ;V!Zx91IRyUJ?Gp7r2 znCmFaqS%bfWIaQt@;=DeEj8VI5OXXkyN)j*Ej7K&A4qgr*UThiXoEMWCj|O0zz5pI z8jd?av}#s+&0W#y?6?eSXx>LjCxi}Rz}!w@9oh;8()G|5zeWv}m}Ce9XfW z5MH2g-U$MHmcOW(JGJU*9*exy##+6FU-}m`kGr=Ht&hyLD_RUA<5;9sMOHwd*UADt zu^F@U5437u;xXt)uCOhK+oF-rhHsGgpVZl8<3}Vyl4CJTP3o*5jWQ)Tzy8YuV9xlRQGeVj1HWFEF-UJy?rkh2b{<8Z?>7Y*r!iO%o7+*+eL}@!P?X5nvI) z2?eT|X1zK*8)n&~HCI-#q3GN~GkFnepnZ*6tJ3yw7=86(8#Lzl zk~d#~KKzx21uffDkZpxcLH~ zV~-+jEo;0AnpBdtX$M@yH7l+FVKP>8qd#<$Qk)Vo3P-yt%%Kvd;V`d=Cuz~brXpa_ zN=GGv60ui<4RS6z9H(F$N52q*zDd5UE9>6vLM@?Ms~d#QH++g0$r1?H2l@; zPVR4Pm3pj9nKuQWn*uxy-%qxzL$<%X2rZ{!bE~m?GB!0eEhw*Po_`tw^bM0EB-Y5< zEyZ<#muJ#~qL|$gn=R*8I#un2&m|8&hIN0gc_NEs{FsZu zy;mx4ij|Xr$q(MzVi3v{57E`OGac+`?|@kxmmUy5=RpXB%&%WB@AK_5vaE)*rP&_d}!&~66Es*03QZ(04R)Q#wynDBaC0t zwqM?o{yVG$@cav7QO${g0=;x+UyU#8^pMaUtDwOtv7lJho}fEB>Cur|^yRfhSo5o5 z4kDRYau+cDZ8oh7*x`Q|1s}fC;}--g(p~A!5ZYM{e0F5L^Ze{Ye1RCEP8Oj#RGYOL znnkUh4%`_lbIro@c&kkRZg3`oGt-XEp(e`e{R(vVIW2E8eOrZJ~^@(HY-WtNfm^Q`TU) zyHaC3Ho=@sSte)i)8gRgvxY|b&C}C&SGPU&A_hs&+^*KbxzW8EEakfhD>xEg#rT!G zGgFKM3KAbk07x%;bm>xtuHZwh#{m?@z z)5fBA9R4uOieED5H5uXQq`3xcH*&@vZn~Le2ve7)s`>BC;O_43ZA({l0LGt3x=NCG zAwO`aXQ5x|J>}YYo?|1uoBewi_yHeL`@!-nr9edmL<4Jqe=p4e!3H#D`Jwl6gU6si ztsIbO&0j9jbT==UmP)pJ{@GlvQr6}F5=akXrtvsA#@3wWfrd}afv9&v6F%ME*-VX# zVT0$1@7_mlxwVdpjxs%!u^C=(=gqdFcu{(u2XIMfzsHh;Z6@U$M_MLJ&9a^-&kh_% zrgZ>?O}q`cCD71_lRDjepHZN<2nwgrV6;I@ol5&4C2rtV@&^L%AA*603%nPkm=Vfk-3})nY`b+io3TiRdyb|a&5b&VdbXA@7 z4ADCu+xas2SSgv6OyQg#teU!B3kWeMjtBim3 zU3wBst!xSJ7~r`+`S*5KU4@G%f`dB{ho!yxbW;Ga>sF5ilcnJ{J_m()ZH9r@=6pgf z(aa({AXkKDLm~RE-Fulv4V-Gc@eN4Ln3Z?1d=#M|L@@Xh3P``34X0P-=afP!dW6imz3!_lYcSExH-?iI5IAkKbB zfcqGpb?$dqbtEIDI_xhX9+RZ}f!P**E+cejCR)m!%{N!FuW+FKu2bS?I*!P*qV1c1 zno$8iAmiS}&EA1DM5XlK{$l*`;typ2!yEVDla*PYfatrKj2KMX=jkRQOBsWQzq=5* zeQQhTgU9dwffn6cU)c`crnCP82JIhP)?`B^5ShJfRfrIunih(4&@!{!sK(rgDT=!W zAaaIOqMI0PxJe5z%(N$EEJ}YQp6dbGq|<_i%5AU8Nu7p*USV=_6nw;p6IgRz6$e;7 zPU|ux>)&kh&EE?t&6;;n{rOnL6QeYRyb=#;BygYt4u3P!a6Jcfk;0WunA_qccb{?Z z>@Ofq9jWUWb9bpC`5{k|sB}#k*jB*zBF1ZmnG|=3vraTAn-k~8ViV*Pz>Lp{u!Uzr z*Uk|qCdBaRrD>dLnng_v3=E9A93P%*4YbJ_f`9&%r3EoEzTC6|TqWq%fLCK8< z*ns4-8UbiO9%p9^qirtPbauv>6e6uwuXC z?On+xY5cDFz6`eTZB7@pf3c~$IKL2j`b&Ze*KD5;n?<4S__eDM72DGZa}^;n2?gY? z-Ns(I$26D`;rP2mzr?aI=}L?*(~`H`7VCIzueuRO2KJP?C`g-^+K9& zM7OetVo>?CJwOZL_&%uh%REf31VU4AUV6v%Cs|Hbt5>)7yS2Z!X&3v{SD{u?la1HMlPFK zOw|Dl+GxHUc?LzyTxGih>TR&vuQaiAz`u9V(BV&VVG|Cl-F_s0lOa~Q5^D6frB^O7 zGbf}aBewmiwoYr2CAZI#McE9t|%E1MpcNwGmFF3l*xtHMk@FztPZAr z!4xd!9-16DciZd~x{`OFDs8Z;IN)&L&_!B;v~%4kn31IJ*T~o9qI%a3);Ik@+6U=d zq?v3=Ar1Uf7qTD9)uI2Kxqt(Sk-Ai6$KUIKvLk(mWLdI~zI0Zax)$W>g`W8^n<%Wn zGEoiJe9eP{mtkI2Mp-0{o4*yVw4uC?@65Q2%7;}aFiYJ7B_bLmQDJ=;!1i@KDT}y=*IVc>-CaPbR3U+)HV`W7tuAw>AkKi0~)bPrB?7FaG zVu{+XJV6S58)9X^EX2rT8ZBEcMcla)mUyk1b04t z=gr8HwFN2Rw2eOAF^@p?f?~@yf^4@S5^@J&w4_BRmrYKZ0+;?;IyOg^H)+GKxxQK@ zDTc?2-Px3+X7q8hLZVa*R>l@ofA^ztN=K35+d>!)h*!yiZ-c-r*aESC!tC_HJ2ch_((yBy&CjrT|H(Fdi`Mz{@2lFncB zSF11*3Ci$0>l4PqS1)u1d~69Kl28y-X)3Q-Tdjpey=x*hN=X~nqL^`kFn zMR~}!O5UL%vEsrC+Ri)(qrbygw$_&?xk5t8afL36ka5D@6HCs*|6uWe{_x~Q>M9e^ z?MUJ5V5ZZqS;{9eC^rQ<%wgb=?PKn|UJ_I~C+m4)6AbQYuGjDym=vHh%+^d?T0f#e zQ1Ix*Yj9rf*e9yuJ6A5!6|kkgaE$#GXP~ibh$vKJt4K6Ah8?UW!tq>sc@{STXXw1- zrkiT5xS)djX}SF-0N@E-0rzn6I1c9?I=)~(Rbqq-c~e|@rDGesa|9sd6pbxUyskHP zb2O+TEM2y6UE48v1WwSJS}-@72kvwzE~z9tz4B>or~hW^Ds;DfH%LXr6buFrN_k{uus`=U0zIU} zK5w_I8w4%n&so2#sW}p9%JCWf&s&*Y(3qXYYMkGXK#0_Rk~FM9dl=FVYl<=Vtz%Y*z!k7DDi9p(09`!+j6_Lf8# zK^~cK;9GKiIOmFzNK_nu?xRShDIi|D;Rns@R1mPD@MaY;jI+oQCcj@w+->D#_Z+zL z=2ipWnZh+%*$853K&~%1S$_HJvMnuYDSbz&w;%}++De}KHu(5WLl8yzzKwz-q!3M6tn^^UpjiJvV+d&@6jz<45#b>si8lb?udY^F%QzYc6lH8>X_1Ub&&D z#UX)K$57vFsp!RG{UHP^dd-{7v*p!`#rf*|YAXOx05vbE^Q$B8c-6r!=*2+uSgXT; zdmPrjgMgT{NDyURH9wI@iGJema;=n8F+j1dJfmkw!<`Lx7 z+LpLO8X}tFQXpj8$4r^s+ujmizo>M=*SMQ7-`uLuTw_ME8v;;Bv@{eI=UY!r%69pV zOXQ>56eVOYQ94||HVHcNX%nE%rZzaf%_m!s=>=8#2chds9Z@cw-Iek^ZiSxM3no`! zg7(b=KSFOWU3v-MTm#_6hc`U!VY%_qDUxjBw2ps6gX)sq91yeIwa7ghIajBd1|<2x zoTrC1eE0WYm~I`Hv*Z;L;id=U?Oqrg{nqncysn4Y=W3A^_;NL&hn^)KlII`!sd9)p zKOD6=6LG!#VdZh)ntFBERyXAHy&tx*$!^f#Z4K)!j(J3KbnI<1`sjscsFTmLH#9!) zc;GNdq{r`59&s%b&np`bm$w>r+M~6K!4ztwV2eIDyiOWCAZ?mB%+M!mTx9s>BVX9? zYR7xw3Lc@V)rvn{O<3e*))DG(VE*#;R;VqwuY{H7g$cb7R4$xQ=#v0{i>h9ALAfV` z7IPYq6uq{k{8P8@AfiCVr-4)J#Dy%OTy+HG_d3s4TcP^QA+=&1s0hG`dYF(3-7FM zR9F=WaC5$VQ^(p|UBke55W>8o-e!?mRZ^-UM0Kn_t6>CCKo1S}_sGfWDGA%ahVp=Z z|3-*#XNVsF03ZNKL_t)q?}dpC3CDXi&}F?Rh`ehcb7yB~X&e$SR$hwBJ~#MXL*k%a zws`3C7!GeUXwfV#y`&rmKRop`AlIcuo;$v6%3`xDVA+UF1}4$$w{s*>C#YIa&e z6NU#qsrEubM69L}aN{B%W{AfcR{I~fO-H0+a$@~iKt8W~P(H9ghn11U^L3 zxJ|Q|P|`#z=SuL?fLs?E4=&f0WN4VpZ9f7`=IAcuOggMezbh`VC2kd~3WV%%B`oOU zH!Gw#18eGIvlu_#L7(RXr0e;suZ2nGTPmXlSo9Dt>*B_jF;dDeQN!#Hh#38euTrxHuS=; z7o5iv6{OFnR#0-B$!8jRQ553O9Tt!{tAJqfppZE4BR=z!^kK#=1~@?aW19N@hnbIQ zK(3K&hRBEjU@NEGU|H>jt2wb|#bY{-OLh>>6}8VCFWXCaxHJvhkC)OU{;66`0k z@gY4Y4(ewaYoeEhf#Anx$gV80!C@3RK8|Yjz;+CElFI*sMFe)7Xivm z)-(onOhm;%PRP};GJWc&JoxL$`hgOA2BXz!K9fKMlhZsyfh6~8B$T8cOvoydK6?_;t?~x=B*1jhjWPWyc)Rc zUp*HjBf`g9PjB#>gjR-@DQ$X9dZ?D(Dh**&H!$pKo@fCniY$BDxm2QY67c7o(yLaB z6D70L07-%s&s-V0WQ4fgWwkEtxvHC|l;=r1e2$OqebAGzt&aSzJm;eh7l(@hglb5l z|JZ^PAJAWNo3?s=^V9Ddp|l3MY5Xtrru{-NQfn{wON;SUCYJ7Lb*3af9ZJ+Dm#trR z1Lq@Hi&Y<#1feO9Lx-9ly>j9L$_VuoK=$Sm3F|vB^YXS81n`q)bM&4AhH-68AZ`yw zy+7G2&>CT#=JN{#V_hq1fjd|gP$*TEa{Kn!y&VTRouZ=~zkhge9=7hF#7b zu~Md+nsc&4iImjP2Q`!+!rvrrPa_!hFU+C*?o&OBKD{ zaMH=}MlB<-ylk;p7)`XCH^XMJ$UG}Bgzg3@H(SBTD+wfv8*>xhIHz@1nke_vo)6V$ z@qlE>Rc+i=vnEfuRrA?T>JJ}MIJlXlp&j*>o7~0i&QD$CG$6MR+!QW=^lscNWg|b8 zf2t<<5)L9j<%nT??$7tXYW`6412eO6BY*(vjd;b>HI!hNi{}xgHqj(!9dB&(`@{6> zqQYn?25al8FQZQZ`lz>oPni8fjSMS`GMpPI-R#cX$vhZsRAp}~GqZcMv>?|ki)vkb ziZwb6U)MoXr0IEAqE->J(>Z2}xJFyH1c0D~w%>I1NE@)M?(9%u2V1SD$N`!Jc%gLB z1g<-cX+XXdAkJ>i{j&C4U7Na%uWQS~T9Oq9*rT2rE0Wq5`ar?sB&tzX& z2D9_h6sB4>E$HR%*$UQakTkp)=G7oEEZYwk4WQfLe!i4&q%@4lUb!&T-t(q@rF!v* z%y&2xT8WAyKOZ<+-0duX)6)=gv7$|YQ&1&ljXtzy2APvZu0>r2(!hNlAaguD? zuu4GKw~~{QP=QQJ=j%vX20nD=II~vb>KB2E&{Z=`bZpfNI*dpY39c^IS{fD{VTtZ5 z@Txg&Ms8nI{D?v7uOlp%+hYk{v<2In8&^(_437-7querUDaomQv*M7|)=N`5~6 zxOa4UAjo@D>Gno3=jf1x(65$RK0$?;7Hab0rq+UJbtPL@LwSx$jj<W|lG`QgYOqhF6^s3saDo2S|gr}K^%d(Mkxf@&X$ zHzQQH+O+NaRxw3TPz>KX9)v4SIwm((R@PP~Iyz2T5is&a#b@OtC106tS{*JGyt4LM zJwnLMlS-JD<}wm=Nok1j1uIT(xcJETknQ8M2j7i`4QiCkDj%w*4E~V$%-pL`MCE&PTmX&UUqP*_XK!EoBSr_ zTdC$`ZKQUur>DECv?`z)N(H5*U7J14$BmVJ6BTs6LGt?*2iMU%JBWGN>&9+rEu~F{ zQ@^t2vLryA-K+rI$_O7}Hkv4`$c}DfHq=UAKQa*XRLarc&i5Pv_|R}t0 z$q;+NyeH;7fSaDFvPHIDmcSmiDkP;$EdBrNz1dR}`TqZ(RJuAvmhPm|748gV5<{>^ zk%d4}6;jm~xd=AKNDy2=QE;#n3uOtSfrAbzpyGlk8I2gx07sNXc9;QS_yDq;0lw(r z{`Y5m>X**ifh_I7XU@4uPt}<@%G&*Y^lN**UN2^B;1p>Fm=v4!{!+6dULnxLCvU%d zdiUztQXQC<78{$ykBejCgT|fzc82bWhttHFwUyWNk3FkCqIX)7pLxZP?v^ITM#v8d zwZRq*fkP7@k?O-kp~yTpo&!?zS<<_6+hjofMWdMPXWeMmq6 zSv{5eOdJK|KLQAR`5~2lFTfLG9?ioeOu@f$f;mXcjAr6cZ_({qZcQFyn+)A9hL{ROubv+Zun9 za)1~bXI>L=Y1Di8#3+d{p|XyrVM*WJhj#R6KuN0iZ+g>t9IirBS9qmVmXyFp{JpRY z$h#&JA@0i*vYAP-%F9>oR&2*}QWg55XScGnP*^P`lmF?sh^}Z3vYD2Fuyc8>@p-jr zI_Dv0J#hgN_qnIK*FhX@8<@8m(-r7emtg?L;?Bx0Rh0zRPMO|^UXGu zf5mm~+}FW|{;@O95pOiN*tdF4m{A-MfzYnA!x#`@MxDb*&wM__Yot*H|F}_k6i(u^0}mM1Jt=V=XvswXu3v($m1muN z|E|&b>FI|qKEA9KhO1_Zldd)>wiPc+f^47@XFlQ4^%b%#r<+7S<`VJ5UOglr_1qQZ zZnrJ5T*0DMga$+nUUK@OCV3Q)zaJ2Gn7y3%= zamNBgCW0rrFa`vmT=H+AZ`JhP_eu@{W>z_m!>WGZQm7!Zd%tODoV@F@fXW~MLYGf8 zvlLvKp!eqe3?*N@`|wzo#+10}P*c`6oc{ywINKn?2Q#Yf^>S!j5$D3OT7DSa3WFV2 zawnZGy->MZQV8%%-zie`Z?9Ysu#V@u{yOcJp4{ki#b>tyEh#r8h11Lk{R6-+vZ>V{ z_)$Rqen9xSbZTA}@zH>?Li|p$;y(o&h6U=b5zPdr!Izyxs`7~M&6MmaW|$hGIG;}F zM{K1wTvZC?pz~tpJX567stZ5}hb80Ezo|58-E#yAzyrv-k+VOmbTFIB zExK7u^#X$LVIg&iF1iO9YtQWNB?t94-p#whrq#~=z~nO^8{qP6p5K{jA~ z@#Hj%O66X-o5c>@a|QFCe^$^q%~y6;D}s}p z83qu5l24O1KW9b(`MUvOzUUPkzfOEJsfDju_(4z3)yjiCsmB3vxVP3tk?>0{5R2&2 z=2B)Dr(X9)x}%atV;wqO_bl)DcFMNkw*p*b4vG(6pr&emdn+eI7YtCWd|gN9P^qUM zRjNQI9bjlcM*An`7$9Y?n_-$5Ux1Yu1R{j46A9=90sw`Ks!I>Kf>dg8fes1j=L6!C zpG8hWN_ML^5#dukowZG;Ro}<&G$XfzgUyH)zzbzD%$ zW?ns=nj+169nem~L@qx~;cx_}-&|$7#N5791@GvavQBi(tj)&Ep~{&Bc+1{7zKE&U zm*y-=--iJzldC=rfJjNjpHeCYajB6EK+*%xIwDnb#1zv2PJqey|Qzc}^uGq4FhR zt8J_PS(s5AlGy8ihJ0A9qCcy-*Ao;Z@oh70@yDoHR05Hpl z1H^&!L9yaXQahs~2cQ{C42vO(Lj5i*#~C@y?APYic^en~O= z>YI;>6z=I;8T^ov4+JNK`Ya<(xl2!Y^`ofO>0%XnVvt`*#vpexJTV9>d<;SX)E@-Y zhgV}IQ9%AyK(sGz3%KWn#3uuPa0MCh_tJvAN&&{_6oS}_d&#SYhhuAq>LXhGhtO@d z%A~BowYUKn=eG|@&MyiR2f2Mmy%GP2kKVY*@FchE_YLItC_H*y#G!00J>6=BZ_i2P%nLi@d#< zE}|(;Rf$7QV38mvFKTFtbI`8&?!veeyi_M3x7=B*{A6;^-J8Wq$mP(&X0e=bX53EI zpGb>ZkiQiWM%oEc>cfC}0cymx5k%U1HrOh2=zcXZ$t%6$`BX-1Wdl*4m3+#`44pGg z3Mb`DK8DZm1(93a6e{nUEJ^0CgliA{S{+Z<(=V0!i{F5xTZI9tttI=Ieb8m}pwW$W zh8njZc89?{vSC7ICP8pyMm@ALZbC5>oGY{0#wK7`f?Cb~;a3bWHtX1*Jeu_3TUp3y zxZ=uQL2Bd8T?jG6sycrUXaF! z05bW41?~-YH(Xougz|)B?EUMHiHU1px*`k;;VEk|HL)}ao_Fw)uWj6_Bu z>yo`2b^a-e8SrD|jLRnvA08PtImUH}WqM@DF|%%U^0PE*mY>rtCv$OEf zLf6>*DpAYGj(uLvqbZ~(C<#H|E)1ryJh@f>OsC!P*ZlX{oRsP>xVtJzsO%--W3jlG zwC+f-CY?^pE)Cx+ua3LVqkg>pmwl`#AUidM<-h!Yxrf7kOk7p7Lh&g;Jb`>Da)uzO zfi3F32;@rdS3>RkV^y5zFVa556W*99nlFb#K1Q+XRRs5=l_pZ8ZfmY(`e$Y{5XMt3 zM=HvdCHlK<0c><^e4}L^?pSfg94mtegiQ~l{>TX(onKxXUK>CM7FB5X@TAo;J~(Po zDreRK#4K%_xE7nEmI_F<*796=33r7Lj7eJVoZ*%oG!lwn(zWhPu z1dL^1Bs`)zgYCu(CCp$i|A38aiVAN|FUUJ*n*n(;*opntX;pjuaJw0#7eAp-;e-#iwd|%P{@I^wnj}gWm zOQwDfXa{E>ZPqv+o#I@ul}C`OiO}Y-9e-Wx9qe@WH$qyI^ZS>?kOqXA@j0DD{7U_N z(y!iSh-;tmINavfh(Egufuo>hc5w(bOwXDO8`H39Y{hJFtgRXdLJ|x@P)tn$K#BxL zAti{IM(2!-@!?6w^l*p8wmGD;tzZ~DJ-;JicOdTUKhtUY;!-UTGz{V2mD19Cclxh! zH;&U%4&}SJ29jz-k($y|8=kuWffI0{3hO)^X@F|N)p#!Td}0)k{Qv}1=8F_fN5h76 z%#1po;&H&Wpf4sl_p)7*RTY@P!t3m^JDh$ zxuK=b(Zh?jZUfM7bHFa=N^)JK)_$8l3sjTu+ce9d& zq6PriGPvQ88CLBRR<-r}tO*+11P10n3Jj4LortLh92QJ9JZv@DMi7uOyu9n(EdiOv z8bylc^o!t=dHF}5@o1uaI8fP$T%xhK*&p128S`L6`143Bcc-xAamV7Fzw%90#CdjX zRgIpvd%i^!kiP;5QrpWFy{HUV+QXTnR7`|-8Me-ko) zC5l(2#ib!1At<{cB~c#u_a`yv_xR5?D^gQ0WgSpcK-d|1j|`wnaLQ)E00`a~WT4iq z&QZhG2m{`Iz|^q&+TkdH5Qc%ONoN#d??xey0f8|;l1|J3rp!qQ_uiD&SpSr)lS!iFF~V*W4E`ci&C2wgA2^Yc%TV8Y*m z-?;?2?h^*)4NZu%OgPmaLK+Z&axG#Bq*d|TL4T51tb3JAQ#?|+4x9u;K^2TlOm5h_ z4d6;=cL%gEF=RGCe%YcKvpBZq$I7~#Y?Q?iud0RxXZ?*&a!!*^?Z0CCodJf0#oAg@i; z3*KFg^NdgBXT8z4whDf$B6sK$uYRN|&c^{!K=uj<`*JGx(A%&f2%@^=wATS$QLc&= zomTxRSg&nbE^#1l{9O-yYVj2X;m@fTOd%7k!spcy0paLxBno}?5Bas^?c`H3uc||B zm|M0CnT<2kI#e}g+uCTGvic9F*=(9yoQ~=1Y7b|O>+q|d{(_! z`t#d>a&TI1y7M`5igJSGuuN|V8beWDNs>3NIMGNMb<%)9oK?ch)l3vh$mYxl)O0c10@$}Y4MPZVdQ%cbQ03ZNK zL_t)W5D6tI@$%hQcdA+OzZ^NDpkH`%tN=*#PT%G;A3MXk)+5hkapV+MAaaBEulvlX z1=%YgfUKAEhXdA%`3-+MWY!GNrgL;@JB2J>jwv;j3tl}(_iaul?BguS2{ zo!cDm>xxK+`)cp&nq9J(&A`s44zp=yyx%c9V*)8k#DGtOKrR;x8o5E+BN_a=<+)kF z4k@Fm&gnBr6@{^$GtU5+hxw)F9~3@*{P=FcmCVDjzG#C9+)&V*_wr?JtdiUzXE1di zuCT#q46;{1gsN*aMR{!4fKPk(7in=o6+~Nt7yMYQCxk&G7DBOum3+n{M+D?o6|Dq$ z(K}h_0kef|os%$T@VwVy zgJEvo-q#jG4iA^F)z>vR(rq;aSUaVGF=lg^ttQ))fr41pCQz87X5K%|NiQl1wkAu+ zlP-t-L?(lUI)@*@;@15d~zg zfPkuIPU_>ZLM%l0RIh&$Fgr2r6PJU}N-PNCVX@&y1QBlI?Sq~#F4r9+%ztAY$`AQ^ zgEKEgn#akKo`TpC-|z57L2Z1S2r?*GciB9GOxkQc3+T{P-+~#{txb4ARHEk5d0St5 z%nx0iWnF#aBgPKGh7cu+Zgh?sKy=IQz#y|@a0+h}k@G*K(s-XuNx}NZf(!`~#+AHu z+7PO~qBJ&KU;!ppRq${=e!m`JGVnfKQ9y`y|MHwZ~Cl8;Kqk?WgA<0pZ{MDN+$cT6EIaWdR_$BAV!F93CI5DN}8mrm^{f zk*<03&`A3_hL}_yy99KYR%}k|@xxbVOjp<75@hgMJWRLzZQTr_qxMY{lV!n1CG65= zv2)XD`Wi8t+&~eAJmLOJ4ZW47FMiJqop0i^-#7D|{#E+#1qsOC!ABI30Eg&&we-UC zumz>0N_wL-pz)6_e}#Kdtj|Gqfl1N|(*6Ds%Qs#F0MBv>fj6xw)S^8KAa07JGNFAW zY+-<2{FEa4Y;?!EpwoN|j$>ByQro=Qy4<#kc}9^kP;_FZ?JwZrwjzCH!*jkgvB5Dm zXf$A>* zyqdPqy8{|lJ8fAYW?nB_F~AGm-kqU_fsK}~ zvY5Ymb+&ZbMwFP>r?ZSL8XPmT7W15K&|yUk0|R)v0^nD@WT@0H>;>Nq}!c%tuaCsJ+->PXY&By+E?!JeE-Zi0GYE* zt3b%Q)H2!Ol~O{;x~;9fGiEPWd)ctV-J;6?&Hc97@nMU3wP#q?F)`9VGp~YNC;C@u zs{WFStc;c|uEBlPZ8}S!dH6231VkVZ6aoMMgPB(S=I#ic5mthVIr-J73Z zRyPWqe?v=YxN8&x6iO73zaNmyJVEu_urEA*&P#!>8txYNg4bLz`o%0LsL+~F>^OeQ zn?VNd5@)@0^Od2xd1CWvsSzTOqAR{{%+9)_;S^sE&?mvz=**x4Tpssy)xwx#xV

WFMhm7-O@nZOb6^>N+nfqcl# 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 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 07aaf3db477e609cb7f90c0321fc9f99fedf75d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 29 Dec 2018 13:47:59 +0300 Subject: [PATCH 2/9] 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 3/9] 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 3da5e389731754d9085ad4946724956de28253c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 10 Jan 2019 16:46:15 +0300 Subject: [PATCH 4/9] 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 9d899467d108800e830a9795a36fbbf9b642327d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 14 Jan 2019 15:35:59 +0300 Subject: [PATCH 5/9] 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 6/9] 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 7/9] 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 7eae28caf02592451ff3e64b167aabbb38c98139 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Jan 2019 13:58:20 +0300 Subject: [PATCH 8/9] 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 6ed28b0c0c4291096b6a407888dc7a2779d3e6f9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Jan 2019 14:10:22 +0300 Subject: [PATCH 9/9] 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();