From 1aad8366c4786ffddbd0afcd5b18cd2a6257eff8 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 6 Mar 2025 19:39:41 +0200 Subject: [PATCH] Cw 793 implement kryptonim dfx to fiat buy sell option (#2068) * init commit * add authorization data * Update lib/buy/kryptonim/kryptonim.dart --------- Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build_android.yml | 1 + .github/workflows/pr_test_build_linux.yml | 1 + assets/images/kryptonim_dark.png | Bin 0 -> 20086 bytes assets/images/kryptonim_light.png | Bin 0 -> 19654 bytes lib/buy/buy_provider.dart | 2 +- lib/buy/buy_quote.dart | 26 +++ lib/buy/dfx/dfx_buy_provider.dart | 4 +- lib/buy/kryptonim/kryptonim.dart | 222 ++++++++++++++++++++ lib/buy/meld/meld_buy_provider.dart | 2 +- lib/buy/moonpay/moonpay_provider.dart | 4 +- lib/buy/onramper/onramper_buy_provider.dart | 2 +- lib/buy/payment_method.dart | 9 + lib/di.dart | 5 + lib/entities/provider_types.dart | 14 +- lib/view_model/buy/buy_sell_view_model.dart | 2 +- tool/utils/secret_key.dart | 1 + 16 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 assets/images/kryptonim_dark.png create mode 100644 assets/images/kryptonim_light.png create mode 100644 lib/buy/kryptonim/kryptonim.dart diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 6c7e82409..762144ac1 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -172,6 +172,7 @@ jobs: # end of test secrets echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart + echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - name: prepare monero_c and cache diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index e307dc410..c5cb26dd9 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -168,6 +168,7 @@ jobs: # end of test secrets echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart + echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - name: prepare monero_c and cache diff --git a/assets/images/kryptonim_dark.png b/assets/images/kryptonim_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..646d550bac3af58eef0b07247f3882a5418098f6 GIT binary patch literal 20086 zcmYJbbySqy_dYx`45c8<_(+Gq3|&fhcS$3SAP6XpGz^Le(%mK9A)O;gOEZL|fRfUk zznjl@z3csh#agVn?>T$#bN1fXwXZ|y3v~s;2NVxLAP}LFqO2AO1eU!2!NURmG8CO( z0{jBIYAHyADu$^xL7=A~C0QvQPt$)UxQ*1hb+@Eyve9r!ykrm~Bo{mTa^Y|u|ylOs?v zDcX2!Gz;q-iY^4*Qk&3%&xz3zU$@0Tt&3o8w&q1BdYcdSyO2*Nn+2Lz-16^lq!e1{ z>@{c!RAuaTf<20ho(8jHna`31)=`qeH%dX)AWv-cAjlN5f3|U%7IIy>5LbI}^ce*+ zzdc19kqfO~f(=-j^`N5?&E7WRDco4Y(0yMwv4_i*t+H#MxVv6f%1NQ|s+dI*#ziw+ zwrL{|;2D zpkEow)q?rcM!0;i;Z1t&62MhZ@vGxDRt+n{$BdA?FTm9L6=Tt?Sayz}GCz zG}!x9A+4F1Wqx%};Due5RKC!4s{9uBSEPg_Q{xJ#-~`+gI7~To>OnYQi7$) z94rGa7ao=ky9Yw=nm6Z(mc>D5U;5fU8Owdv_=Q&`$%S%_v}%xYD>ut7_UP2v!*L?z zg3+-UvVkD-D4`|9Iu#))r9L7ef;F}$I0nmk*k)mDB3I^&mM!*zmNoW52bQDRZKw|8 zhWz~9BO4s927aq2KS72NT3=T zE{G)tD}QLfI?pKX!;eutaArXN_x)`?+d7qrPJMUcr;@&0g=BVGlaF*t!*WSV)-Z4iC)_ z(i`^lq~y%PyT(R4g02l^H$2~QFaS>Ef&3CuXh?XM8#b#`Mh>aN<4zljXq63e;G2Rl zm_y0P%-5+F^!<(T8)+Y)UpZl|n#Ass$N7q(Sm>oaDC`4)g%Ci?TqDJO<`kyC1_oC< z3`U?^KozS<`eTmT#OHtuS+KU!jVRGo%I!_mAoduSvndlB`h7j5{wg1cF|1D}179jx zml9kMRO%Ypbn-r&Q$;=+UNJR9g~v_ag#p&$MNp2EMdNYPp)@CZI1}D>iRhMjmLhkC zX#}7YQsxvWrB?RlJXm9MM=u^X*ne0EzEq)E=I|kNM^h#n2`zvmGt=wW9oLFM8PFuA zkaa4anjI1oj$`vEGDtDK)9ZXE%#**u6QTw3C^hrH>8rDgyJOvUwo?RwtxwSpJoRN| z`@Tur6FxPE&VUqKyM+`6cQXri`SFq$rZ}NRBxa2CadUiu^bz(@1Dl3)*Rt$-kNm|Q z(?s_s#me^zBMGx5T;-Dv?Xt3&J8uwM?2C9+xTtj3_;Y_lB@5m@Q+k23mLPNUB?fD-T>z>agnpRt%4q!n z9y6j`TTzfWUJczy-zGBmL|pJ;r!Kt3i^mH*0;hA(k0iZOBjn0wz#kRrb%{erl(Yjr zR98Tia4SXs5fLeIrveMFpMsr=FsmCyXY(XRwBRYEkhUymMS#86?gkmeZI}aME-UNb z)Nts3ebAfiv)lA++SpY}cE6t~%Y4ymy}K~We=C5d##uD7tJ-UGhOW1^-8B$Y{=)_$ zC?mT#*c}eh_{xAcD%1c+b$xZJM@KX6Wv=mzh<>}Nl(@Tg4TuRk=ZQ7f8xmUY*QdN2 zX$oRoE}ODhbqfP^ZKywv=}4r`9ebOq2{>0C zBKY;yFD@$~Wq72rpEpjSmD}9AhS!Hi-w)hWOVDYK?H5#Ao{Vu-QimTvgRYqf`oh1j zrEQ0Db%2oOzL8!LN&jVN__VH+RyhD6sccE>cMMzfJyN?q@0IAWuWTc6Q>IE6RH4x8Y@V2_U6~@S>#JfRXeq;?>ChzNq!JaRfvl!X}VVEv;akvZX8_FL|o5=XR zdULjcaA-+U+9`mP6bqXeA?L9+AI+9Z7IG~xW7vB*%k1Y8-tKqx>Q}Ap`Idf% z@Mj~{zUuNlV+L+I`tSO#VuLu0`kwc@i)i@`$cMLTQF{Ir)F9;D*0GYQ=|MCF6FnCR zd5H95^Y~HJTJL3RzO6hee|DgTE9fLd>BA?rycY(L@IFTZ6!qX~A~`tmoq|eRW+_`K zrM3>79RW^ExjudyweI*H$G~odd1B>(%7;%3=#8I_Z)YbL&mBK1pU&17MYio^fyaG#Btj#}Vr=5=eG~y2?0HCC zN7LL#rTd@6cWGkt|9(pyTGCI93oxL^9i3-M6>V%|&375(zeuB2@!rfXQJ7@G%nNti zil@DN^Jm~DlP(J`8Ys%Cbd%zS4*#!DkR+8z?FWtgEgS;kr3 zo-U6nr?(mTLjbmpAFGjnNwK$E1fQ`W7~t|QA)7db=M%!5q>PxHAROn2}+h< zO=~u(I;>p%$kX&e zP=7}H|33U0|0Rza7Ak372W?CzCainDU_etjMyheVgPMYzLPB03hg<7D0SR^xEJ%co z{;6+_0!M%zRw?m!^THaRprz;!&v-@Naj=L>a?h-th`Nx z^k3klTH+5r{JB(lRn8Cjc{z>l83aR)i9{u34U7=q5?KKAfsRZCv~5-233+4%H9)I< zV<`+oe*~S9#xPB6l*NeGFiNJj5xuc`mEju&GiTcK@2$Ln!1{l3aPk1hcasJhbl?zy5Jb&s%kDm?9*u^2 zVxtzMRhxJGD8Pw+pWpeZ0relL?s>+TWxH|u6iW7~MeXeyoc9x?Kx0#z)Jc2}fwTnU z#i?WfF;mg~lERZYLO{#W-Yt*ffY?MOkb@~?-@{_2CycG|Afg1I?Mp*@7#L_X0>AYR zM1sOf3kNug?>Cbq^7|$NRGQ$&BwaCr$Bk&7@(6?_ThIuhZd@4{v zk?f`QM1r^>s`!fA1!Z`7BuwcK?vDY~i4cVx_~Mzk{T%WW{*JN~h1ZI0jtjf69B(R= zLp@IJV%xOZxtPTx1OG}h$D5Q}?);+i)8Bos#8u|ieJz8UE9avxzA7J9K|SL!$gvG~rWyM;tlGA3+~r_nK7+ zZ}h9Up;T+2O*n`W#IoK{^k2b`UJYdn1ajB0ujiRvz8X?0CNbcTjJlBR5{JU)+Mr<9 z-87ItmieDNo`&^~5CCmdFFn|$#dm7I_zBmrrA5so6r3nI zSLG7`u8If|o!*%#hiAX`wI$hP+!cVlg_Kb(m0T5R=Upnjx*X7Nhr3N3JUidx3b+{5 zrN12sxYfQ17*(=bx=@4(mCstFF-oq_cY+*nxEp7`@7(s>qXB}#*2R-C1zL+hY->^} z#P1?HdbWO*k&E-Jbty(C=of9%2e{+-FFaR$1^=G+m|!9aK=a9DZED5PyqA}3Uz0Cpoe^-9~NeK0T`hs zlX!te>$?wbzr<8tIRAEXB#D=2B1*v$xB zt&N=b9~5uJzNtIeoWM;o^=9E;^js~`M7W}B$oFYM!6A5<-@%68pl>R;2*0Q9FfyR6 z&OiC5B4lcEgjEiW+{WbEn){FV;A7_ptIur55vp0@3o8DtQ8rF*uO&vX<9Ol4hKaKp z^X0*@w7n~04>=pN8=zT!r?WH8z0{?$8CE+G(YbgP)h;O66Om>@^cFTKXH=UP^7HJ} zR_7C(PDukPUDEoQ)?d)n|Kd*xuY673j{*m7+jFqi07Fa-mu}@rTMo2DYni1m{X^Fy zk+Q8s8F$Z|Ps-(Ju}90e5hNOR!5|N8r`PRfLoPpu@wo4<$cQ*B*RV~DX#9>fcbc~= zJWpqB+Mx2AD!Q*KLg4fxe`5LJEJ|Dze`_=n2#QnVqJgLfa7Adrbv%;cpMP{)EU3v+ zunx`r2+aD3(7iU!rsqDDDY`neP7P}}8B0IxZeeMs=Yy+@oxmE+8?0uPR^MDw% zIz+vfc?Bfv$1-HKSa6Dbj+1JJYAV0uF~Rk8*Xt4ck#kR+J|B!?teeZ0vWyGHU+D`% z&y=ma7S7qcL0{Ig+ia@5)|4IGj@hNk-*1D&;@#$;zn~ksoUxd+_jZhnl{|tF37Y ziS9d#vMpRM|NobqoR`#CQUw1GDGq-)GRurnOXDXra%e(&-YSew>`m;SlnR^);%oi+ zdyY%vnXTWwnP^FP9rjIAEDJ4Wn3dw(=7H?athV!&uorQPk}qI9;05)awe8Pwg%)^;iW&~Ya_)@XY7J-F&6HQN?rX;ixewf z*u}f19t-YdylelZ$$_HR|lLR1>D_Mqr;yzY~g$L~+1uSUeqj$_UA_!OioI3$-8 z!uk%%;mg#=h4|bo+cmhr;^wZrnPZ%3d824_Erf+=oqxbQFEO(X}lUy9Vi zAIbP*X3gU52N-&K7+t z4eg%Ogi1Z(jd;=EU25jg^v7vJO~B&Q6TSV|Y{QQ%O?K3=-hOT~XSiW~UPJI@3@BBZ zbH*YY$EX5k&i$ri)VcM&L;A7KgEg!qV;B6>1&<}6yv}HzJl=l!I=#}qgC(7o3oigu zdWCUil&r4%zsH&RSD{+*pX%20D5)g4dHm&;1;|gvq4s2f53t-~i2#m`WJ*{Rq_`_z z_e4-f%pqsFyB>zF14z$?_2Qc1CH1=Yw&6sJhhsR67=_4Qt1D(Ur9$}N?L>2he9uO< zk13=|PoJfxnF0Oo>(_PaAWi>7J1E(Eu3Bp@=Zc6Z-9fJY{Ya5rbO?-Lzw;s2`4#W% zo0-|15>r=yie`CCq877>lg<$4g9<{q-jicx)Ro=*jy-c&tKGGB7jN{rM1%9^2+%jM z8OAu)RP^%W-R<1c?O#i~fV-=uT>VN@&#Qk8rI>|DLAYHaLU2g1y#K%YMQ{6bufHkn z1)muW@=H_?SFgUF*}U`-oV>(3nR{KZv5U8V2I7Cj3)`;0&psArFn97XLkcRjkrCz# zO^n!KAA_G8_RZaSK*DVep8Kk=AkDf0#0n`OEJ8#jI4WYLJ@iKeF2T0OLlIdhK@|4t zw|5pRecEIKu<>u$KFQrevuo>)4%^*B{xPulSrF~WwnBeoTn!)013^Qn{Za zB734)4qMp$JlSdo<7cc4lb$Vrvn&863RHAV00KRzk5dz5bH#0J&OxMF!a({Vn9Nt*BB-@V93p%j7oczcVuG zHewSyd1Vv^MUFnXqJ?zK*XL_MnMJawV>>TAAMR&>g`sq1bKvsk+Fh-$^xsD}z6(=$ z7w9#7gbKWgcmyZUNGtwj$BWxx^g2P;H}wXiXEap$r|huD%f*3YGX~_j{0@XAx2F3q zKM@r0LWsGQZ;bfV>Hq54j3ure^NYIJH4mVX9f_F|-yvw95z&2HvcOv#9rF1R^_Js! zQ%QoM?qS)w{?`Mb8QD&|(o;d@g42z^c9XcU0O4&=qYhSLL{P%Bfqpb+RQ^%sge|>p zMZ6a6`VZ&3{m&BSU4#P1HvYAjG@=e`!#b}-S#m=d48b#`%*~Am5L)qDKxHv zBmOe^UGQs;3+jAXj)3AL?b5pD)*yxAcm`2AdQ2ya?vojzX(IFlJGa@uYtlEGfSCf@ z`AMdSk;7;6u2DqJ+P13Yr8zPSlaR9FIubKq=XsAMzxMji5=rob5>BLUEHW(E1hdqZ7v{>{~xe};L+#K6!IwSMg{jL??B6tiv zX@LfpcDG=R>L7g5Fk!fJPRoy}9v)+4hreH?yzp0Bo4QBX9v`T5iRM?GGIpY)F9hI& zKSCc@1L?JpM^d1zh_zhLMA?zzJ!#ZrHq>)7Kj~Fzd3}knsS!e9w0vw>;Y*)w9n|v~ zs>)HG(Vp%0sXVIx^aV$LYR=1%@dEXXF8~d;q~IOoU|6rk)>CX)Zd=DZ8$rSMK;rIV z#7~U~DgNy+c#(YR4oP35!2w<*T)s*naQt|D4>Jx)WuHwFKl^)}yLPtxk%)1xRh>xL zJGC;ZA#S;-$8H5<@_rVZDZ>q(_|iJXj+Je2gUZ+54do;IV_Nvk2EgB3`txR<%rhe2 zSY{*_fO$*u@L-M>1|SAEEqymFd*#34&+^87`@6+}ul)cC*6O?(_Gtg86vt?hpP-l8 zHIoxLOSf+A=L5BdY%tN^X+fw!=FNd|L_pz;+rv^)?-~SBv0%FU)JyT62L2)6!VX>b1$Rt~y<>;7g_YDrWocYI^a+J=#U z*P&JAu3aa%4qX8Im&3|AvL{$Swf{n;GM01C(vwpA-LnSTILaS9EeLO=o!@zQq6qqJ zW^j#@Q&yhXucJ+1&|yHU;7&GmeCqoFDr?VG-p=c}G2%&-&xy>cedVW{ih$W+3*4$s ztWSJTiJ|!)s~oDzks@_-GqfC;B$rnyxSj~zr&!7&z^S970Q^@au85%Ghd(MWj<0uU z{Eb89(npufXP?LT|8O9huV%By`e^EM^;W*pHfSh<dFC4+jC%=m}J;w>!rPKC+_eR(=9b;EF=erA$vpg^fTxg7#8rGfE4&U|!L7YFGvUuf?2{Ckr|&tC@B$ zRd_y3E&-;VljA!X|B0H`Bqw~+#fy?Gd~AO08)f6++)2le5Xt;L)(|=Nq--(#;h9^jZ`E<*fheZ4UnDiv)bXL0rtbtM2ylh!3Q3gQ_{Lav{nsKa>H4@V~P zoQA~;Sd~KeJ`#!VV+UpWoxTj%RdA2nLWG8Fz0+IMFST>5>1Px?`pM%b8vd{`h-7C6!UR^Z#i9 z;%{p`1u+^CApE8@=ff5N(*g*`{(R-{+rR6i@QcZ-_9*C^I+S)V-^L_m<#X7xw zsmz-(%}eqsLf`WchlchI@1w*WX`r-b?@VEFj6*7%&;gxbOR zYLq@wTG^ZRpRJBO2`6{@G&&H=PtzTq!)Q3FKN0@)(K2&SVudj0f0LRbC!dr_S+B8*4{2 zj~;c@{?(d`>Va>M@ZCKPOP;v-R3Y%=1GETUvPcb0frpv-iD2${_lIToN1UcJ?VR*X!q{aJd)Yd zZV!Hw)8=d5(lu^;4deFQH>cgqS?X0WHMBTl_h3FSpp6B+WY|{;ZK-*rB_kPHDFU%t zlhNR?ML+^VS6xF?P`2r=#X+SYhAg?=}+sYmomv@BFiI+KCu0k%||%P;Vs)#mFXMbw-%q!}(U-v6>)1U=UG zJ4u!{%WAYr>(U=6;AMhj#ep(%Vl|-6^pYj$%68=z-7}72Xy>|a`6`tTWR2V)l_Qf* zZox$HDdHTcmZS{;A;A~-yn_jBrxnP%KBjBtd^qRSMfl8lMfP}iVQy(a$PTLf@0+b+ zU(U4k%^fL_`KUSw>!6AlkMg>yPPWQcfP}0IcEfB9&F04@@t&x2S6MJ`o8CY>SvcRk77cM|8ZmhY9~i z2rTZ)M<w~xQ(dlhZ z#9&X6l8o4Rv%G`&7*Y75mI}uo?V-{oqjBH&tQgy7@A8R*w54ktk$<&u4UJnu=@f`@ zW}8`N>uW4Ymi>9xk&`2fRbvcA*|xq1^*{X){tJ;#G_;A7+pX>Ch98)G)ij}b^(9YJZ@ z!_cUR&2j_TjX+$QKKA@456PZ&2B|$vIErfz7TKx#sNdyv+e^Sj;L1Rf`bqfWks*~R`&^lFF`{kBDIYKN`>d@4%93xP*Nb&DV`suRFZ+3ENy3?(W zxNuS|qGuZS9rrE=}v!MzQqS;5quxFs0%W&5&bXf*K!BTzi0T& zx^l?&MoLA7wXzctGLwV9X^!4cy-s`Dz4*oz;K!@b$}N%5-Qx+3v(0ke9B_wrG4Jgj z!gB6glQg=pt0t(Z=~tYuTES^q6jbiBx#8ETY}cAucjmxse8uL(c%h)D-vPVW1_Ur`Yap2k8i=Eb(PSBy0C=KrbqFy#dmWR*NWOv?Hp`Sk^4GNnW|zuoCX zFWZ5~@Mnk?+0`TJb;>MKjDx)AJU}s)sfCh(q=Y1w-@sUdhume}9?_@UYnvq&YvtJFiLBBWuH66aEuJ4`5IG_<6+-@yER0!Ln+$anL@+s2rKkdTfK;6Z;+m zx9`t|{Va}ugNC=9p1!?Uc=|#XvZl4d$#)P?ihUWv5V`*egXw>X@iRnub<{;lF54g^ zaYWR&LjL@sPm|9MfflWY#c!-=GHvyAUD6+mO}6^$#MBWKb|3KHeEIJpFhdEI25S7) zS6bEdS?2!Zn`M*Ds`QhBi@N$I7$zg+h2}4Y1KUiQ3Je16w5RpRXE7eyNP%K<5fn<( zdPK}j51yj1()mZtZI0q9X+N1V(pPy2e-(kx=xk@~^eDT5B}l>iDZY~ui2b>R?Lilo zX(Q~5BFZFiz9S$Y95`=XFbRXzp-W7^a2KvZa`AHKqp~JvC3$u+gTlO$u1N6AUdmff>weEMG|C(+O z2AYS?D_qX@^A+(2L7^{-DZbfi>G(WWjzIq(=pH8w-sJ|=ORNw%1X^&j`)){1^9jv! zE4*RxtGDcJ$K&Eg;fCQCj!0=AY~Z2$xm+S>2yVP3=J?K84k5|$yZ=e=#6>TROyoWL zBjJswdz5{Ac1jvN(Y2?U|Lw3if-8_{e^6$@L*4HrmJ@^_lA8F@(Qjh4;q>^C5jG!0 zDf3>R|Ku6@QPHxy6+vHeX}GY2;?fpxz9mDm(o5&=1&n}lNqg*|>y~jYJYm2V%5U_f z^T6ieBtKkYt7xRZucKvkQ--(x!2`BmgXZ6&)DZ8WnZz-e(mmN$kM()oCmzWe6EtI& zY@C3Nl6OfaKT10eSlwA+TMqlJjwcxXUV!wFdRRTh#?RN-xMz>Wll6Z*5I|4OOESGz z`TX>S4rKD*7;&Q@EOyy*Y(pxyPoDDK+BW2yFx8!7`6M0{Cw(D697%xC(wkER;Hdi@ zIcWAi|FS2br}kX+%Yeq;hoT2R9=FNOAkbPJ*D|te^}BdZv&>@tm0B{f18W!aQ!m!>6g@g1mjV$ro^|C^#+z%vj>%J69{Mjc$wQpb%a0LCS3}oeU zwl?vz@bl@CTBWHAn*~Ma&7SQO+}<&3sJ}*n~?11Iqo*GqtZMbYBcRqj*{Rx*QSFsy$?G#(DH`3!PDE@Hi<($8N8 zn#zYN?A5a4IsYzB>9}=e4WJdjN@AW~K3<>q1ToABC{U(!I3XxwP1E<|-%2ycj*ppL z?oLYiSB{%qXEqMZFNmJd_>13N9|^98^Ed(J)H5^+&PX{uyw`NQ>w96?EhY1();qnq z&#CPDN8GC;$>2d-ay5UAP)`7Yy>nOe;iSb&XQj{Gf-OG+F|J#-X9zt6WNJB{3x*}O z*WE)~;mW#ZNOL%!w}^to4ZrqWRIv)rv^$_L`HSOH_jr_Zeue+*VV5^jUPcE>gYuy{=CkG5p}Kx4W@k zl*q+e-1l<}03vua%MUWf{dZF@dgtGbzA}M%wJSivZ@|}RAgl|(}DTDQ=6dk~TYjLw(dBvPMb;hGB1R|Ol}Qh7 zNi~*e`zxCUEOs{zkoX{?-(LKDu8b2YpWY!T6GoYD!~i2rpXbUd+A=%H7G)@jJ<-hN z)ajmHSMn{)^e*8tQ%Xof$k~Qj))a3U+LJqy;14}1!k$QgvRr5lOpXc_OcVwN!x-YB z7u2ESid8RvxH{h{(YvgXE@B+;oG3vZ!`-zaZg2!`UBzKiWXEy5s-B6vZYzLtr8G4$ zMkA|M~gQ_Lx`>7ujzWXDXGn@l;7-s9U_hGC^v!Qq(Nc7l%rM-C4md3#|qdXJ{;< z;~$-O_QT?Jj|x9~PKy=S_TFUw=eri8@ai-5`=ZRiDPf1Bkj_`3GJDD@H&^gvU?A53 zp6^98a}e4R)rAAxMs_9)0E;c{>3@LnR;x}vqDYgK1Iu__O4&1hOYzP8L-%Cs#(9P) z(GO4b48E)6rQ;&S>(5i(6?whv;GmNnx%^IlXrK)xEB@{QnyJmV_?MFi+i-*EEGv4t z#aa070-(&`MCzwBM8`eNCc0Ld>)X%gi_BeFM{ivcboCDMdfUF{^#vTMj)o&RI!<$F zTD4`Zd->mwHR}d4KtxMi5YIU$f0iv(0P1%r^NsDsETHE24CIFY0K(pbPlt1@rOQ=9 zx69uCi<2?M8+CxU@;yphN?(0j!|(QNjjDVv7$#^a8|jz;>cCyj4E|h0sSR8$aH{7K zM3!t2J=<;G<}{nX)(ChNtu<3HWu3XMNmy`~8;ma2SfTq&wGzSW7B{x>pGxkN_hyDW zH&^s7jw(>HHeV0$J|A!S>yP&yjHPuRjZHX)wE{|BsTHr)2!RidLCxssNl%<2B?>|A z{^&JdfI0Enb`q607~%S;F^cmB8rULd^)b_7M-w{|7^N%!=$nz3|Ki*1bCigO!_qe9X` zu{{BD80d>fdbtCx=8mbZ99QQReYaR2Yn-NbQ;GP7i^y6Xd6V%fqg0~@WQ(VsO9GDI z-c^Qk=@vh05&h_M{`+$^Ao2N~Vv~9Bqlnc>UvI9qLA5{XCe~AkwukFm0Ul<>GuK-k zl6P#QW&TTK%rYQXc5Tv|fnOPzD|s=Yy0_imEHNqEQ*n4bT?4#Fya5%rUOyBw@>1%< zWPj|9E2k_?a86mr7hDciE1`Ze^vQ1~$QD1Er)Z!a$xpsktEOp|GAFr7k_&Pl_IXPP zd+OsfeDgpu>A>v-r+V*RkVJf2rZMr_*3MfEqY)Ywq>vD66R2uD@D1&0clDRp@zz0X zY1Ncvle1yo#h+8+qF=-Jv$K+3+|QoXkThCM<{RR`QnpYH54*KRYdj}?kUsx-@j@H{ zK%C5;cb5*^y3816iNyXa(#&=0M<#ieR_Xbu)MJTBsC9)i=40Ytr`F#YEnOyhM9rFi zBy2StzR-lP8S=hDWNz@UiHbDtEy!BEV%T;J2Bpx4o$wYvGI|(tzkR0&#?pOt7;Ld4 zjdWRVVvIL%jsVnde!lfetP=N?cgM!c{pu zd}H|rF~p9LRrVFxZOjF7AN*xG13b#f)+*z@#sr>Q%0*L)X)03k61w=nJg1=WH4*Rq zjT^tB6|&->zT*?io^bHv5<5E|cvR+C3^S&%>K3nm_vsi-!1aOPM)pThur{_{mtsxk zzfBtAbA|luxoGe9fP?ikdbHvLtA5qQ;aU>BdoCUgXHbjE*LtG64iG=+^?$1)GoGe} zc5gObX$jlAcU$139IW>G*hc?Q>3XzoiLbrA=NRT;%s{V(bawazvNcJWT!H?p#sLI! zJLCwp|4Q){SI3_yMQSZytY?`G%_{UqZE3_60XESX6WE12TUEtjgNZVI}DIJRB zzZftZt3>l(jtzNFLx_s)O{DuN zhvBNZ>}ec!Y^}c{Xb99Olb(a-)mjYXGi??-1i;YJbY?!0y{u61zP7D zP1Z5Cp&$m9QR7#saxLGlB0xSk%J}LGvb@fA^9Ke1!j1p=BVR|ZZnsKUTcD9zZHa`d zb+~PEhx=NHr}rLvs-4*q9fwG2S8yF<#u6`kpSw9g&(L&8# zMR!k;3b#hpdd1x3z8`9jdsY)K9EaG&{4U+H`%$4TU~G4eiVSj4+kQThsp-c@iUI_>YbS{9O0;1oZbOcyjCQj?n$k<&ZQG{zUgR zoa*l8UNZ&1S4WEdepCM26P(9uVg*pL70TsZfR&W$!kY;8x=F3fY^BI};#H-mvAHiU zX%?*`SYb^qm;^?8^hT091m$Oxff75DyPui1J*mU~1IW_aj2zpZ0EM0l(AI5+#t3Sw zF}^HTE`uh##W7JqITy#RR*f$_>!n&<*P`S}ce{S%b=#@@b8qL`lB1p%&xrw~4PyVb zLbX#(@xQmil~ELTr8#eQ+&hEt+ndwnU+>4A6&Bxa?X1FR>k@4;CIEVS|0C@+y726| zdt2ZJiWRkESrk+;B| zivDayuvQ)B!8?Son#o9}%~0x})lVs!hDXQwDN2SBv?fX)u2cSEPwoMw+VEUpcu_`BLR$U<~r+ zTa(3@RC?R|ft3rIvnr;~_U;WiQ01CMfM4;wejY)e$ey!%%T#mS~d%*|80=5ueOP^zt0Fs%M@!1|slOp`U-O#EN% zM60er`Lr_@p=Ndbr6)dsYXCuZ1A-3{o+lahNXbG!9EeP{I_WVHV$&Eub_TPCf z?M)x0tze-iPRb*VVhKW!wy9Xc6lE+cSY9g|L#*B?B9Foczg>2AkD>b>y_U=y7>rl< zpp;IAI=_iYw1L5^U`!_g(M^tBpU% zFXRh;DoLWz`~cm9jU^y3!3QDec(UcG$`YM-uTc9qMjrm(*pUE-)$oxNVOChyLY?s7 zxzN|hZm$h;+EAyB(Hz0;vTs$#<0|@utittEq_*zYAH)TopgAK*MC{xmiXWRJuy-NZ zIW=w(>a}sZJb=Dq&ahJd+rXPdvcJSud3}J;cyHt{vmXG*wUyWj8A1$EEU$a)6E_4Y z5~S;{i*h4NNc6r;lRpZ3!RfQ4E6Z$<|LX&>x$Fmh&6K3+6gswJfm|{-jWiK*)1dKHG{t4!`<`6vNHq zEoQkED!62^E8+B4=*<76rgHMHVd+uk!&s=uDBht5$Oob1IXW@-UpSC~_h$yyldCFS zFT4TO_+Do0d2(CI_){gV34@sPNi8au;H}`??ga0Xtv`>2%4!B!ODK?kFzPI{3uMcNN|uD;mW-MFU8!$#iM?hN(VWr&pC zi%#_j&|FLLk1ROm9-NqXEMN*p=*QTb)?z(@`ND_k|HgL44t`FFKAs20Rn9%+Hl8dx zBUqTE7xuXuv#)$$s;`fu=npO3URIH$gBakog1;H-NAargt040wQx=)u#g0d1&hJS= zh2JkNDD;-SwF{1WJ)UM3guS#RgGm>@3*IX}Ne=4m;T9JYDVq+EYS-XcY$!ajmP3_7 z8u8HCA5cqEwiMv{C%3FQqjyoUBoEmRRJk2Q`(Fhk1R@m2jwDN(fi%Ryzu=}VfnCz6 z6T@eihhW`j0Qs}hYXtWQJqFG=S`2KAXP!A4#_D}{FGQ76M%l$6@vGbMMzC!E3nhI? zB%~5?_NOKL(I~~2-s6{r=`Ig&h-i$FQhcZ%9KYz+Nvj;agZ9|ykDuZ%K4(COKu2($ zB=>cn_=4^gp4lbYsp4Vsu&Lv=)ku*K8WsFmPcaV4C>_@%gID+gkD34#_SMfRAWtc`P)~d)c4uwO>`}50Z083y{~tqnCiYLL<^An47_PVAn-kxR8PjxYty4*4lxv zcKB>rOK*OeGT~adfBRuY3AsnDli7K-KHcqtp{f0o4bm)*`$Z`-0^qMEJ!Fa-q z68mT@Q|U=>`j44Sf&y4k zkAKP9b9mhJ-+>v6U2o@+XaUUJ$3x+O$@z`D5Rw?4ZS8ii#;e>^`3S{l$`lh`dvKht zx$sIyOAPor=i@PjE3Gs`=$9kC@!ZQ&+ji;U%Kr8MMtU283st*@hBVN6#eX{MD?B_t zg~7(?$Z!zWhdIgVFA_?5^~^EywncWn_bOuGUc-S+0OGr+SPV|E-31B9Q8B*BEG*P< zXQtX;T$C0;XAi;kr}ap(^;&kL%`=*Jf~EZHod}-NyFV(oFnld!JVped4%qGHs}A5b~;5l85qTsnw==H)st3X7%nb^NU+JFmdURfD zb~A8Sz2?!nQ7@Aq#{-K1^W&yP-Hcv+mQIci6x#p5gZvD~__>YNzx9m6>4_r*Vyv2->$wa1&fN}`U> zDCy`)8>(t2Y5NK9PlEBEqLqzF{g+jb)F40af;BOi)c+T`3r6(8v;vb{{`Y_X*Pp-9 zI6@NAVoGWu=Bjv%K>xwejS}Jh`|s~bnbqauYJ2Fh>-0ZPM2>0m9IC_TjQ|&^NPwpS z*efppwPPMefbBrN`7!l;E$&=Qr)0(qv&x*h(y(VuuAtT5njfw4Cp390KSzxkDI=HyO+=ktZ}$J>!aFB$!qlJ(cUOR z^;e@q_;aepS&$%CrdIrZ{rchJi!a9d>#y&fw&|>$aeI`ZuOf0!#^crGX! z7&H8hx~nMJ4Mu?Z-%z#oYP_wgxq3E8t1Anw(Vs(1fsL2{O+;BsjefHTH zFknCy!^g+?TP2zHXc4&~>wy~?FSi63ksvL@B*8@+Sxi~(F#;^cPO9-kbM2}{?Dt!ykE;*X0bv#Mzg*#B|%!FhYlU;lQ=M)Rz~gJi2QP4@$G8OxhBAf z1V>g%r=HkJnWT*XGy3&xaTu-XtCia{)fBsnI~q* zU&fkiu8DWvc_-x&=j={>#`sg7U-j54H)0yH#nb6?91Z7Fb$!yLNfWu$W}L*haG5(pELdvWtGjB7EDTh`~}ifN~Zd@wxyaJLDNuWnM@*1fI52}pXA5) ziPAk=N`Kfc2XKstge{uV+E#S=RG|nkBEjV=#~Nb+u(n7ko2r?bm4q)RvkmhvHyd3W zLbkY?s}T$wFP>^W7DEzaX={yw%lwN)!~FC+nW z3JK~j49TdgbWE*mc%#o~T6mJKN-vsPz1`7MA=7USSC^MuIf8mr5Ph^B59NJR>oGar8{EddbeheAt$x)|pit;4_QBo~ypv*w z>us`qZwRD-0AqVk25?dlB0L&hZ>b0{rV=xO0*NrQv=@y6Zuc1i1w?>ZT}XVnPHfN$ zuB9ZvNE&1+tX{7%VB5u>zssk8(bksy<*j_TCR(rLde)?ddV zz)a5L^P0>wjom0U0Y)Uq^!kT&a*dHRUKH!2;}BpCkJIv)#y-uRC_Mp2B*;QbB*BiZ zHkam$V||qx0akA#`Q@sBQ3%94f&e2DWXa7Dk(&E)Q}07y^GG`IXh!(cNBj905ks;AQ}Z1K6TqFR4w}5D0<5 z?HkRT`4#M%;ItpfD1+B$%3yX z_30Y{_5i7KZqJAW2c?d@b!Z5LN8k~XpNL!*eyC!OU<6oa-y&&n?lsSIbk#H7BSH^&ID!I#%LHw*wJ!i^hz#BMVL+JV4S0GEl# zEA5WU-k%X*f*p=rW_C5TI01S|Dy12M_jHYb5nxJhhI4KJfE}ZYXNA@bfwU3e+1}N>MC7ToJMYB) zi~v*Kb?4mL0BAAZp%QPoM9SS;cb$pATL7*BFj+(%>P!g2tl9`L46am-Hw3VKbXj@M zKscAYTb84|j+co1vE*xQ%kqo>1HS)wCiNc>@5vn32X>eh{GB3bV1?Wh!odHl^N7i~#E)l+*8>bF>`u z;<@2^04!T|wtK~_%lB6#bDrnjD#t zn2dnc)%+zQkA)e=dd&&}BfzYX&S;#-fU*{VH7o8Gbh_q6R~u{1BS04wIwtda6u`q~ z$Y>tKJU?LsnCIO|VQ$X3{s6vHakizDNP`M5i%ISpTgKqhXu*rp-JeC|`4qOny2vR4 zMu0ipm{8Ml&MjMsHD%r7|EUDrGlwCsZvmJu)R;N#_v1>Yo@XjaDwtP~*Yrx9_LCy= zYEI{C(+Yrq5nupNi`J!cu3shJ+)B~4VkO%!rAA)8i?)_-<~tJrB{^QtR&q7|m6wRT zP?Gi8GFn8y2(U%q%Kn&h?#q>w>))tU8DsT3UNo%qsnnbOdX&wElH^=D!QpcNl-!t2 z<&{eL(w70yV8Kdhy#6L4|H{c+Z5la3zz8r$n^B~xIOmoGurvUE@?x|Kuf+f?5?zdI z=Jl1xO=o@pyyl43kc bDFpr>tmA9K84ZD)00000NkvXXu0mjfHE~1x literal 0 HcmV?d00001 diff --git a/assets/images/kryptonim_light.png b/assets/images/kryptonim_light.png new file mode 100644 index 0000000000000000000000000000000000000000..85e64a3f2296e0c9ba9f36bf8049d6b36c2a42ac GIT binary patch literal 19654 zcmXV&bySqy_x71_gh4Kq(P-?=uV{v>6Y&9`rUlr z_YW3pF*9e*eQNLhxvoRlf2wjg&&Zykp`qa@$V;oEp@GC7f1v2VUj}3Ie*wQhPU>=! zXr)6GTWDx-GzDpi_wJweGBD@NjIVnAXbDaA&3TSvUKB_?6k{@&6z%f-wyi8P=66Mp z+6~-QB72?6LSstF)bA%f7>FlDBh8TWaMHY?TgE-yba2D6F}ESG;Zb^XBJh3kCf#Xq z@~(0CCT*vs%!bIr2{I#PK%)?$`0uNg|7xllO8EB~r-H7NVyYA>%s4s6@U`Ogzb3XV{@-U= zc^LG~nuN=GLp{(pAika-&)l-+OA;G6G~N5DLDB&PoP)|lDoiaC5_N^)jXA%fVK)ytp;f7cr( zw%ksq!|cHWn0?suOF5Nq9c_}khRimHs96QTU3_Y>r&X&R<2^bdzKiBhobMwT`7fyx5j2YYhO`>hA+R0xxY7jSr3?A6F-c5~$ z5Tp2BrR%r=bH8~javWJk-9{$K<{@S0bMApH3h@*-i zZBq)hYoyoU*HOE#rW+9icaiCTDw=nc*@?L(fGS{3S zy8NVf%do_I$JjQ3wqk8}vznuw$}gA20{st!i@*A>ctnTbab1Db9kCMUDSaAeg$;bo zRb%na3dRtb4bto0aNQRa&ytjCEbKlAH9NNY6De>X#A z%JAW9FKNV(EejO>pX3mVF0t@6A+KL&w4i-jOR=)v(RT;+YDiA-Uj_j^R8W-N-ghC- zFtm-hYClQe{MzFo(VJb*i+Q)R1+Rmx)`zbr{m~|s`(pmBBv@GZ*|pxe#`3C2 z7KBMia*}IPM;%$f7K*EeW(wnaC}wZiaKJBYLm+-1$iir$s69wsO5SL^MmR$6l#Pb6 z@A)9EiuI7d#$VtG4n+4j647K`w@TQxnFe{rG>yGl^Cg$swZXCP==Jnp|8#s&kcJ5} z?rpCb$y8ep{YayiU)vTNmvKWVBO1%5on@I>5I4j-sq2sSaHSR><-UUlxu>_Zt|gOe zNI=7lceWU;*8lDJ4Hj+bQr(VD{^HoJ*PZXB&XSI8y=m~CJB{Qe+*AT_oX)5q zSi=HdW00%GTcW@`qNM$Ktff104;Qbl5Li!^!eXtC^*zm{wj7lq9<;i<*$Z)?5etpk zso(fMMx>A7;?`9ppdqOI@ z{@u$~<-4%f$soV}dk<1-RM71k4Da)4^O{qX(oRY30xCrGG~539R*5-Yo60&wq0IRS zcwQ0_!LTYvR`iFTPFyIWYB*aevW<(BXj zddFYWiRzhV{Zd8H^B`{Jjb?ZUG0UwID`y!cMU3RjE5ly=Qbxosj>aSRkhKGt<+B>q zMH@~o$8MSm_{Fb5uB=zn(^MOFzUWiqU85>ri&!u<;?M+6gbHSn&0DSLlW2`$AyeFWqc#NH9FFf3iaS5hir?NwtG!0L%tml`dZ(i` zdU`baIM(%1yd@NC{{@5NQN>b+DK`R})%6bF?FQnI7z|OO=2RyHcl~{yB{G_h=4F)9 z=o9~nrS3;U!v}vf1#5dF{;qSro0q`p6!C5GqY)&OQnYv%^Ti)kL|!#5p&CO~L-mcL zAP5gDr<=RH@Pj94Tr_J3+8qY z7JKWw=7;BSgcA8EZN7ymtvE}|3llb<(c>xR4Kf4kK;T7M@uRc5ej`#gEKq6E`@9Eb z4B`SFDIrjz8Ix}88>n!cw97nM;mx`i>l0A?%r>@yzRjoI{gyGz^2W(Yb7EMX7D_Zj zwP3~)oX$(rDsr3eP7Pud_apa&_&ed_d)K}@+2a8nv$tIc@?KR&??5ys5*+Gj;pKKY z5Sh&eholU8ab%OZqIP9PaN+phE6f_1XI3%1fP#7Hp1G&^g8}p?IqP z+$yu{SE*>6UY*Aa=n`$1BlFRhDimY-Q2D zK*Pr)JXE>RLF(tx4J`{Z(m-3&kP+B2mok6$kd<8O9#C&jK3-(LLkk%Xiw@EYu%?XTZ+|qzJMi2}|ptaI(X)9p4GXIkq z>c=dXul6Z^Mtc~aSHdu#^5MJ*dU#78@=?R%)?*Uwj?x~IDuI~G$xW?yDEB&sx3L2` zSqXBZNh|1RqmXWiEUOhWT3ib9DWN(jPl#+mpby6bmSyFqrID3s4ty>8-^Lvc2$z+= z7G`W9#tOsWVhPmE=#wfULwvrs7!EG`>kZ~la{ky@Y0e;Cdy9;?HTZnRQnmTNKz-jUw2+tL`dxN<0Q)`t`7?OS;mz6pe>+uCVJz?@{ZC&Ohz>n7A5Ic>IcB@U;ZuF*`_xc3~X7P!OfQ>XYlZJZH=HsH6;`Ft*B~ z)NE#?_t8U9>rgi8flnjZOK9gi?j9HKhkrUYlq%B5OxT#%{ZY&h^}^nz4Zph`eIM!D zY+Roo2M@W=-g<9fCYr-3q8DfX-}V7|(0Ule zZjtaoV|31Gn6(eu3DJ2wv|@yOjHiZ;5$6}Rt#>NaH&eY`2QQVAvzXo$Y@SW({0mLa zS)b?sSy}oyf6NscE11!>>=6-&ATXdez}j{FnV*O<452J2LC(C6rG`jz@7OE9ed8zn zq{e}!O~;2D@XtSf+xliPxaYy$tx4u}aJmKQa<%sy~+SzD?I@ zknon)(!$;11Bp=GNJ~#7X|A5@w9Ltnkci~Bl2?XFkdytzbm^g<_Q zs)j|2GF)dAY8QRIGT-Lew}q1)Mx_c31jkQ3=O5Q*5O?#jd^TatF90&@WIrE^#Wv}5 zB|7#x8~@np`t^i2)Z%n=luW=eFBq4aibrgJ1v|Ru!HF$7KgFp@D`Cu**TOflTg0yA z%B&o60DM=&^>)!ss9^sTp*xZxDOm#=Ya}O@R4w^b{6}>tQ0IMWaSn{O)GxrRTobuk z{mN@SRW521=`}KSO;_w{9-fF1cU(GkKC?j1^WPX4SM<^V=)0wlt$=mNqWcq>pH{P- z@q89YcXeinyq4g$lV`-^@R%T+Hbp75Wmlh;vND%c$FsHGIvG=bR~wmSQTz+;=sCtp*jv+lgzC~#{V0^V_vNL8EzfnZ$uxM; zWm0d(`(*IRRZ4L3IpzSD@6+$i+V~5vadfI6eaH(w1uUN3QM;joRb7r)D9lL zw-%}am`2>0R0i>;Vl~UZDSnxch0)a#NfGg^tmJFhh+E4^hQXN(xyjzHZnPToH?kOJ zPNicYpV%^bza7QQ-h3J_8%35+Zd*qa8Kq$nJt|UpS7U1TE7-mY;;vNkY|OLb)2jnF zSs?oK-^ot75=m;AXSg`eyG%Ykt}@H(yhyxzN(YUqk{rUW;t|^64lxJTVB_Q`FBLgz zpX$e=g~L{mFe$%>Ymm49lF;K^Lt%$MzJ?UsbrDlNHuiBu#~ElkS8C24ajG%O^NL3_ zG{}jsTF{aHe%D+K>xz^{i?@loCbbGTz{Rd%7 zZ)>;+d*5Vr1Hnv_V&X39n=0kFK02XqZ5O`M=D!H`3CDg`;@h2F-O>e2fga_7q>#EB_-gO)(Bh*@5xWJYf${p6~mraQKLLfdNVdZ!FGII`M~ zdLwWP8kfv%uoGd0Wl}q9+qi`xb}2D(8~#fG-^mv5T2vi%w1*E*d~s{pL>LMzL4Sh_ z?r(UcI|d%7Q_ogby-^B`meZz5ILjtPPW?WKUS&`mSll$&`1H+kAc5o`<{?(rWl8f< z=K0M_cHGT&u4ME&BO{3vs_wlK4qzIwzBDZQkDQMD7;p*NW6g8x!jrQAqSk&ym5Q99 z=2~s>TdKhUis`<+kaIaAvQ;G^Qyuh8v+HetuRi4n#{Prrv>gLToq5RuJHtj3;o}}@ zM_h2HlM31MYnjB!NZN`WZ!KH#4-221CZsCM8I?aBr2AVkx|`{-PHI#6`=#gh&%%J0 z783?MAB&_=KVqnEe9N?7%ubi!!;hR<8Hjf||I1aG#N;5W=5|Rh%}9GGG6wcA*vN33 zcuK6x@kN8=on*BF*{pUEZSSykNICDZxV7GJkBw&F53=q%1Rr?=72_a7rapI-)OXLxRLijf z%iXDphT}iXH|ZG;;|k_!NDp3aS=@Q<52)97`F&)eXiS`PqfT~|j|`7(Q=^JFeP_jZ z@ED7 z3$?;;2iP`n@ae!XrLG}kn^9Y>4O?DP^!?I?n(y&fnu2p?{j2a#9&3byVh^`Ib}ZYW06?8DeTozn1cCVt4h6s|hh46jN`#GYh8*Mf0Pv%#aUo zuf+kPZ{$sQa(}3)lNpbYnAC}Jk=MU4#^B9ow-1+oWB1`u7b&V9DN$&wqU{?YvEx3v zcA7LBwgdU=;#hMQieX=B;_g3ZzKwZhJh>pY56mpbz|g``6yk?IjPC}nKG3sqtEt` zZ{-g|jQh1J?tebLzS7S5``fbqW(^J|8jaXLZ{knx%i^K1 zOjBJXB(K8fZmLHc??mKcjyrGm-vf>LoPBXhHhM>O&W=dpN!d8@(=sAO^YfZ}Xlz#@ zz4+M#4Y2-Kz;;?GHsqEY=K8n)h@~`1H~h@J%Mal2jzxH3PL9Zr4T)-uSwrM<*R{3- z1J8tWRrQS-EG)vn)Jv?!wLv`RuLS1E@PlAeiz=d-X7sw`Jh{(!@Dn#qXntlYfx#_9 z$FB$%;;3f!|C6y6t?D={HFH!-5u7E_7u)^G&k2I)k& zN(PvDKPInPwd@URv7bTYMFufbf)7Ku>6ReLq#Y7M98&%^%s_E?K4lzSeeS@JTmkBkc4DK6h-=&7VQVK@QD!R$eT5GKP|Tf5A!!Co!b#KJ?h6!WkN#czWmRB=DSe{ z#iF|h<;A6h96sj)j{9WHmS9{dZ-b`u8LJ(X68m^g1lx@H+^aur{;^xJLZQ!hPJR}C zCo}SPs5SJ8MDLrXHrZrj!%0G>ke7ZtE%ArO27q_tnq6Q3HN9NIr4|)_!KLr9mhhw< z;qg{=DAJgLYxIqSjl>S!EVe1@)pOR-Kl-WOW;J|CNtdr&)}T{wemIGa5xFZU#3=pI zK(;V*GP|m)A{>rBc^`Fu$vsK6DkYViQzozCsGQ2SH3K34YO}ZAR!(RdNBuYZrRSm# zQSeL)wToT8fG5bNt~Y%%8=qLR=Um9qcuKk?8*jV)hv}pBV}r!IQ^qmlAPkC;kMHN8 zKjo>#LUFh{I~xDIE()~gk64#C&qMxlrdmkc5vr)j%N!?I@{hNFaXMiLEX)%vG;S)* zALLz?^RBa7zzF>@M}RylN~*P7Mi*Fi*(iVdqt}h83T+jn*={>Eoy$l6P%_RS*mTeq z0ezC+@0AR`kU*CK&ZJo^qeS)o5o>eHo#Lu!phjteZQ(U)O)D0Wn9qJzDhe`l`@>YY z*|>OT3!EKY8yX8!%mmjP`fYmKn6YH3-(pYx*PkkOgrNnE(dwzxiH=Rpc%|Dy76zS2 z|77nTjAB+(smZgO@Z;u)6z=%Ru-IbKpFiA!a{6TC2=&(NiTVtXFTKDE3iV{D=m-J` z&+8|S$r?2yl2}0``rf-1FqHwblyugQ)bx==&po|Z{Z@`L-{)eUzw2_MFlAU2H6W7A$&evI zCMMcu?6#dhF?4hdFDHGymrE#Ws)sv@R|I(fwA z}ao^uxZMa-tFzc8p&!Wv*`a@3U7Z%s@htQlAY2&$cRI89GlZeY+v z13Gbbf2|z;PGGo=rnJbkzNv}bon~1&EZc=F>hD_McFe(r<%fPX7#?+_IjzRYp{tCb zv{H)a`nffhUXg?Eoe7|U>M62_y4EH1l@bUrMrJyYLmh{1LYJd%VymuDJc+7*x2)&W z`r6y48fhnDuj`k+pqM&U++t3)!H_Y_>#FP}HGXk4{^T!_XnwCOl|lg0@&@$0<@u5^ ziPmXw93~_H*~{mY8^!lbn-C`oYp{RY%tUL;xsT#b8yo~2s{N>eO9DpKzmqNJHL<=} z^&*y*M5j((=Yk44q@V>x%kjB}^XSLz`{s?eYpL0+oD#`1+gOSgms#s7m8pI04p3Fw zI^Le4j_%L_$?RwMt;?Z#3VHBKc;mxidW0Q?w2IpMs~4 zgj#Y_Cd_ydp{$U_7_yq+ImrH`yTV z&9t-F(h4iGT+5g!InvAL5oa|*13pjH(>V+So(rtPR8o0%5{+;9sCeZ0{uN_e50)L%iUW)2?P2GW_B^b> zLe>h6x2$CxZ@7J@7-y)p8EiR#Lm*+lVW)IyL|KM^>{9a`v(=VtY|oC)2<43=qIY~Q zFZz{*ICTLg#4`hJY{M&kJ|7dq8ZI=nbXY>y5Rghm9<2LC?|V4EeDs`e`i;+dRVEHQ4y5nH z`)Ukht**^qRH;c+zw%HwAU~h=QO^PVwcwGGrg9|v_A!=GN=V%a69(B zVgK#V7x6Lz_(^S(yO~J7y}%6hR^HU-)VL>m!{H%Sch%6?hMvoZ zN3Nu8mSTE6bd5^UEAFxVRLb_H?ft7XwEyn~Xb!Gx{BO*7Aer-eNB`m1)p{EH3qD05 z^BXH&Ovp<)6>uC<3K`zZo;MeK(E?bnQvTYRpJG||HA``rt>fK`v`4d_vNmQx{wD zNuQ>g`Uw9BPl&Ag{JaW2#<89lwd1IZB;QW zyv{?3S)&M6NtKw#Dh8%1cumV+ezVnwM5Qy*5||8VQU5G?RsPL(E7^Dqz3LyelkLI< zHuRPp>^#6+1f*9wRw9oj4O-F zZJ!)WGK;cG&B(6{8G*Mx!4>8pF+l0)3l)^}zbctdJSlPfsSa(z0Nqd0FfoGf@ZdU2 z-9$PpztH=rn{0Ms+j+!?+K#Qau$k8P!T#ch#JcwW6Z(F4Ey{nqaY5SZ{=#dUWo0~9 z^Bx-+Gq#O;JT_!!)rFZ_+7QLNDi4dkijDQV))=5F3|kEL1e@&Kv^~MyT%mq&fB$nQ z>Qy=@6+O9^y3SG|aYA(OJRngim{?s=!hMUZbxU%E6I7lf_eNMPg-Zq~LeBoalepPA zps4o6qJGDBAfylR#!6@ZUVq9LOFqzn#ZD0kk6>?cXIsJk8%KamJ#B_DP^4b4u{v{EOc2R_~QQ>P0*gkJnwEHqRX2Z#*cSwQCiLtsvCj zR@`DXxG-%12}PGzMx~pahD9C z#&t5H%K!x0u>GqjpnH61c>o?KC(v)kxk8Or-PU|Q!`vq&rBQ`GdpoGtdtFmCSbWLD zT)3D->TdvX;7=ZSLo_R~Yv3m|rIr+1js5D3=(7+3Hx~!ifk9|;qDh$I^k?5KCH-gh zcZr%tVd97iod#QWW777@K{M|h)xJG`m+ikTo*f#-5PfkwJ%4fgFAj2>;U%UTavQQ^ zyf}lAG;{i8<9Wkiof_=4G^{Q;x@JO|Kn;DLvkrCbw{4+Nf>grS8wC2DR%Ok6)|~F{eyJYQi47K9m zm9??&mu465U8ih`@%pV!qd&}bp1eKmJ2X-9b7TxXxLNK3U<6#g?5^qL9W2{ zMS?gyu)~8ZP_cOYMzSEnzZWJ_vPU{S36Zd`+dXPP2z}T~4)iuXgd%#e$a|TngD>5xzy%iTAc=lGW3xAt6ri|ANW&j=S!A7vdvGfleyF3Ptg=!7^^I8DvYgGX%C1Pc5{2FgJgY>ss&rv3A9f%Ur&!CDLD&Z!6t(H^qZaEzdrJaJ%`14D z!mgW3nhfd=C(RBTb=!TjSgmW_7*L)VBVU_K1X1N!KHACEL{*k1HM=sYVZG)D4E7M@@kZ@CWsPS;cGjOicnFtHq`(om+_5?1l>v@{K_*-X*_-2@ZA43K75}I zsMz9}y#W~ILzS(-|IhTW*lN4MSa&h~zmMTmW?Hmx0=+L#ms#jXd$mS^3RTDYkfoWZyf-HhMjzmzDv@XsD{Kl@=R7Q;S1GU`w4hXd>df@36g4;l z+fR|(K02z)zbj?eWuLukiAeG=4?DDj>N312NHTpX|2Jx?9nKO!TJNX#h_I?}kNYtm z+LH8r1*H9Bp$R3NgYh}n5hLSJU3$wTPjD9wx*LgIv^e$SM|rmL{mj&@gBSrR%IADq zj=kwnvX97^$k>;U_Vi;`5#Aeld|M7~3xqA^{x+|jog*-u%*($c$F^?Appm59vgVmS zu7@}#(Hf{R9QwUxMnbRGnus0DSgH12hQUu2zw7Go@P51o1t)nWZZQhOXd!Yd%CS`v zES&%t%mfscV}lnJC1sO$QI}i^xjVHCtZ5`Z<|Al$!&Z5e4DaW z@a-)?$t8sL>vE~wm;CL;vKLe5x9x9RYfEL9VzrxZDu7R*D`cOR%C<313O}wgX20ph z3I01F)W5GU64Z{LQn^kUw$tvC8Jrx*Jw2HF4>gQI+twX_dU^8E-bjzmafZ0WcQ#S9 zH=Nh72k!N(hRdE-wJ_#i`YsUoaas$%qsu;J4v!Wdq$e-GrqHN{=0c>U#bB_LLM>KY zCzH}elvzq^{s65U+<1lHjpwO_p5t$ZY&Q8B%9l1IG)+TAJ*E;Zxx+2AmRG5*Cr&`_ z*ZL5Y=OXyJ0GA53gqw+_UVd1TQ~WJbsw&*l8iz)>&;Qo*+zUE`{DvH~{$`E1netVS z^bR7_7y-CPCxr)_%KZSPWIib-IjNbCjIyi=C%!j8Xah`>A-{sfR#J(YmIAQ0^OHXP z5v&$l6oA;Og}0q4{AUMQj(FGibzFed$K-wqPA<=`;x5ZqffwMs8{?a`2VYD(PikRo zHl>nRZ44RCPt;btIcRM~F8bbN9c3G$l7$wzAN;+qX1d!Y*NQ#U>-#t+=G_)4hn`?2 zRdJ#0`TK<4N`gYV+7+49TW6VlnlH#>7SvsOW|ZE06Cb?;+@R%4~4Z#f<=4>JZw ztA3lvw9*Z?e&Rm#{e)VTh4fwcKA(!?O%?rNDQ>y)#P1Sdzt*9;<$}lx>1qnklL2nE z%iP{ge_0-znd-S}YCv%7@L#%^v0|t6yFK!^w}{QlctT6?0MFuGAGIb|*0-2B#p<~o z;7cc?HSUcbH%-!;xDaZ&=uq^?T8tAl6MT>gzel#+-gnL1xLL?`ok~0byWX2__`=Xa z4%1a|Nb$vN=uC{s$YvHF>s%gg9?9zLYP@XU-()s;tlJ&P%X8r_w-E`9pyLGVdK#u` z)}FlSiJAqjg>7czdT@zBLmWJnUArJ?C{C672@W2eMSwTPTR}`@xI5Fn*iQ|n<#JC2 zblSEurE3SKuKl+(aO$ekW4T^xk<{PDF(L&rL|XK(Un9?uCw0X7-kdPFxrywMCCGYq zHuQ3rlukp)e3gK!Qn2}>u0zm-p4+@AexQ3DN)D`r#_{io{=Fe*KbP37?!aZhc_W}p zhtx}XpV>-itsa_V$t;oc5+`T!C?RfxM8ubuBxO$9uc7E%oSA|*tG9N5wa$mkKttsR z-kez|T-h36X8w>=hI&~a-19qtpZ+>p>9wjE*T9-p<9@bgh-N9vMkg=Cm<|MH(Tn=w zr-PG2VxvTZ@PwCuzIEaCOwQm}-Us^TDHf39;&^;{syj0YXiU;G0WC#LHtxXpqEQ>5 zb6;Gcm_*BvA$2Lo^}N9;>ux}uRi|_s;B4oz{!LVuWFw^Wc|YVueQl^8{WD*NB2kJ@ zth49*VgAUrxt)A2q3T@#ZotHdd3}ZjHqymeZd=!O0l&fS8mCRnnK+i{Q5S*BUd`lr zhW~_+t&{X}MOVeg{E~Yq%mEEddl?0{Ns8P;uL4hIZ2wP;;zD$f)KW^I-(jWaX~`e? zk(~ENtWYv4ZOXtN?c`3LWk=?+rpoVtSIQ(#CA~23hA@02+muqQAEtC zDUUSR;OyYmL;=Vm3xu(}{8Gw$|B-oM!z@f?ED*@~b!>d*Q-JbPM{?bMuln6f?I+yV zuAQQsj@b~Gw>K#|@ugNl+b(MeV-|cefLCKbvSOsLes>O3fmwm)=Go?%*MAG0ueS=V zN@^yyu@WM#_U-r0XAiGaX(J>2*AF{!C#fGUdr600?nn_E|Jn56k7|qKeR_B4ig_3j z|B7a#L>ypog+gC)-;TB!1B3It8<4|1(yvzds`#9~_wtcE$YJ?&XLfN93v-iL+JCLs ze4&<0=%zQbo1xy?YMX@Sv7e+NRiy&Dtsue!*bdL}6AWgpu$ZMDayyn#^W`97TfET; zJAPsk`6zC2C6u^YEX``u#&<9D9eY99sUdb9>e0+l1X|6|BvtXEtd7BVkzeOAhn#qU zCu|tvS(88lI*M9$ggvK0$Wgky4}IbP&0fO)2V9Oj@-L%Q)Kz>lpeE8}h;CI4;Peru2ZovsX1;7uIIDpO^z8ahuWSYpjiOHA_ z+rD;`?bD*cF@74T73kk}i%%oeu_nKE1_1b{58@``pY}QZBv-%uWj)SIuKlqg=TW%c zeZ1E7)l{4#brj9xGO?Y7Nd(tr2n}dHHSybmYUR_Q|Llc0bDr>oE0ZLIkj83}zDO z^?sp@Tm`=9m?nI1e#F-)riqwc7CKA!#YhkKlsM`;$)-N3TP|LiDWXF1StlrLZ@8wd z>7&uey}Y*~_^$+DypX3e{_;SEwgR&C|2X zeDLANZTIZ$H=jw~z1%E%@ zVJFzj=Z$iUkZG_);)JJu~CrJe1?^0WejLwno+e-TE1 zIYV5HAng`3$!vY=qB>H%H6;^cJO2Z?i_tzHFQ$eiQEY_g(S?((Xlz18yS4MEQpWu^ zu0AUz>?(`#HdW1V1+Q`*G)m>rg(rBkTlEA~^^~tu9{K%lO9JMet1)8JDcj<=wV}fx z{+*#7p3mJZBl(ER%MDDnDwKYwI0Jf&=f0+sQIO&Q1@i6ObCHExn8Cmrx-*U(BYvQ7V}IzTAg91VG(`{gmqMXrdq@w3t`0@H_bi6o35WX zd0$mg7cCdfq8JC~UKflzFn3A(eD8;;*Ia-HWu;C@xyXXwqm^K3Nu0D@D=?HnRYizu zR)9wJH$9sv^JkZk!IXyIYZg~X=Vg}(RAuL(7gz6`eJ2j<#TG7hxVTa&`_}hL82qOP z(|6Io#Ekkt=c<-1H4Ja)zw*#H)paI6z*FhO$R`R2*AvuWN_K*_DVeNQr-#d(Qe>>; zY17&^%hgqGd@nrLw>B8wp5F5B4FFC`u7#BNR{|TXa*xe@`K>{mMDUE%5Y$5*!^){m zvJo*rw z5q#He~6`jDG8@2;!S5md@GDl_Ji~l4+we#?whSg;S<1* zBv3L(X(?q!|GsF+$qVuQia{#|A$gxg4Zw1at$3d~KSMT8y-f_P7i;$5jE8B)Jn)uH z-EwdPkj?X$^u>xtE)DRJR#viB;JxR=yq-iG(`H=xZ)+crk#Q>j_F|h;w#t2P%_2Gx zdfl&k0vAWAKFFg^=T;ZE9%X(UNjlMphnoL*JShQv=LehlTT*XFzwfj9Uqva8-r9Na zr68w_u^op#&+_qmxVx~L)N>nO{r&Z3JH$jPYbVo}>9?G{`9#XP45DwDXt}s3PWS+L*gj_g z70-^EZrx8TRy?wCK+a%mWFNrhUy)rO( zC-e6)xiOY|5%)61_)zcKmBAX)1Qbvh!sKiQAe=XXS|)Hf>;k{)!t7J&#Od>??Mq3QHHH z3bKT2X06(CEP&_|KqldvpS;Cmu16Fj^OiM-H^RxKK* zAq-Y;HXql?(r;T%(&ShIyOFrG6k@(j=ksp*&4(RWKInwxv)d8XLcTJYje4lsR94r& zyA;MSxG|l@JJYRH!s?`bCvarrj7HgH{@#s;-`jA1{z=Ktk%RZJ8+87!qz}2sV!u>3L8AcM|=9CF6rH_+y2Fkfy#!qq`U-oLjIUC+6 z&!!m6*s<>jz-RLE56zWN-c`TX^Tt@N-0M+qzQ5jyZ~|_oU_`-(Wn`>2ng=)%=t76fm~fxXhbXdb8#f&6MLTh<5qG|#vH`y4QKveG8xyo5+g>19I(Xa z!Qz_rm{srnR`SHBle)KyjGqtm?}bksxwN*9C~*JpFx=q+&?&dd2^TDKqa-g9OaBZ( zj3ZO_$Eu}$Lx`>;^lv2}{mlic-MH@88Y!$?dDurOrc>3kQ^$mV#e!optjLy5733@` zFkx{&$1pL6@Fea_cTs@HvH#h<#rww!GQielwlJt~He0+*)>UO!dFko9275`Zjz z-CZn-S!BCXS+oRdkarIe2}KyNv)bTUMaT$7GidMUL7K5kS)xrI+%&s-C#AR?8LDOetSZ90 z1@GSGq@-ca5Kd?^7uG*owf-=dVWVTVR0#(Vy-qSWt@r=0yyDXL(Thp=Q5O&SJCTD% zkDwcgx-DG@DL;8N3~x{Sz0YxBU)#PujR}_iUJ);wWDQcJizYDGNCM-)zC6k6s3#MX z5`Kgvp)Y7m6UHf!p0w$s`Bu++Q-ic}X!Lt}UO57GRCGF=L6o;v52;t?C0ogKPkh;+ zuUee5ecCYcPO}gidstUxH9p60t#i!gJAzq$64CF9kCOVr#R~hk`~bKF4Jqy0Kt`d{y&{N9`%bscJ{^PAJ`B5<761c2Du=>()Nfr$a+v4&6 z0xWyOB$1Hk-kGjRmR?KV)LpF488+v=8*N%@RSDqnYzeV&9V5G+PGqyT(;Fk%QoIiT zx?pLFRm$)R@hVn0=#ox068ba6GaIhfj0?Zie#XLFK7=HQmyn|1{roLjaQjC&A+HL@ zMCyp0;QQ!@+E7C1b1{kZyfQ#fwWqR>jlqBU&a@xvN+mk%LvBmIIHE7qZEGuDbWDEf z)8zth+i`IvgDk>%V6ZE%s`}cS%40RIox@05V}LYSC}RB8GIhRlh074A^=i;A*pKBZ z8mpEPI5NM;E0dC+-bt$%&X;K@UQ9gy!=V?pS&jDd$Ssa%=HjB6P_TC~*a9yDp9 z{Nq3M*Gyt(0M{B*{uDA~ytedwy+CFCc<;k`_P$be-LCflPC7IV0Co&LK=1ak{FRKe z5uz&t%hJjbpz{U*49_DFcN*kve}$5e+stI(?aX8u+fIx_Yd0TE?NPNDP&5S>z0yiY zi;!$+c0lbjX!^50PpT3{Hd}lG5GRf-9`gSJ9SY+0zZMmIeT^jEa$qUr7~-;#oO98*Ukp9 zx>9Hzqlj6L%{jS<+1Xj6C2P3z&O5cwKKp3Wq)9~#pC02+NsRCHMGxG}cx5I)O3I>4+naXsVx#H<8)-u;e^HV^{GWuL1!w zdraqrtXe#%oA242Z!OJ|Il2xTHcSsb_@L&SZ@%U`YvWGy8RJpTucl3#re&91HfS$h zkaV*RL|S_!9ZL-1ZL!4`I`hmkLn&)QIXr zVWG5TsasVD5D-az1__QTHKI8RXNfI&f%%u3Eth2{&T*IlyE5M~v_8g0LnvWPX%SGg zGzNrXg_R&XTQ@6S-co06+-&&>)3M@4f_$1ov{|Dqw2tox>%)MOp`D+Q-}F5FC2v(C zK%k@;1JNQolpNPg;-%BEo6i}4L`7xu#e#|E#~+ZTQu3{DdrNA`5iAW=k;x?bOO|2d zLw>wZX!o?7{$aZ$iSl@^J+z8{s%iuXLQB+Qn2$+nixiJo0VCQr)3TEAa*}PBf4OP6 zHpFb{ZLUQyFkU*_2aRwtR-?m#_5o~HgUQ-`FreWYO8i+uPOod1PR(7 z3}n<_I;M6OyxC_gEj*-a+KZ)D@3aQf>>N6EpZN!}eijL0VQ`R5j>d+zr)bt+(*oID zATObOE&c;rS@HwCDiffQAlYLmwb?bz&SM~)bVg$SaO_Ml=42Tct${yYJNvUw*8-bI z-tq;e3l!OwxcOe%zjL7T#`GFO)(~EsmT9)VxTsZ5^D|VdPJqmVGeJnOs|_RV{~7p` zjzAOyFnB3ARtG{tv{b@=dJa*-(>Vjs~F=tBTkmlJAr*(+o19cb;kK?Ah8 z;U@?Q_KdT%|3Nwe9Y6plXUMM-etwPc=_UeXuK0-@D(R43iXCpW$o{<}kOKmQiJV-k z4|P%%B0QR%Z?_Sk&6@-Y5MfVck!g0({XR#aMFhy|0`ZZkxT{*X5+I92DlJm+^jto{ zu6jd#GMz?%e14>)D{V@vEC6*g0fGxjRY&(~X6Y<1hCk~FNFtLnnb}L3%ruV`>UIJI zBhu?rh^1$1jO6j6cpp8705v=)P*%D$_Ia*EFAyMjQD_Mg?CENAdA>N_S63rI^)|?_ zR0WJ;Al@qk2zHd*q#!eHG9QfrruXRxR2%_P!{FvJu1uvXV`AD%1ZX&79DxKs6BAhP z+YzW30#795*OkVOf!3lI2@ovSleCYduU70Ob#Wbm7zo^4a~V5B(ycK%FLR&0N`R~h zDYYQMJ<7a5E}J6|8G&;W@~dn@gDm*;G690ytdjOjt|hYo>CZX>6+>W(q_dOj-HOSc zo4CDBfCeO6VKl)Fb3@iUasqG# zm2{q@XS#W!&CbmUaAu@Pu1e*()tUv-?{fqyfWUo{NMl73Q*Q|ZPJsQt3ZIpQ_YNI__y|l)$nOX7hpOfXPJoTq z4Q+7eB#dr)T}p3`K)Dg1ZYtqy7rE9q_(C}WHvb}TBMFUgo#r3$I~{?-5x89v8s9aA zk6gh4od8=~w8d-Iv+a`$tFbpnpezVr)x9*irj|vhvdiHF*xm}Qm5|`K+k4!fas+Zk z;IdkEM6O@v+G`JV<^-5+k#U*X&bP(cK=h~EMu6?^_G%xs%165HnHs9--GjyHf1NkXwd3QlD14PFV7hd z=Wgzqa+KF4*PY!w(N4|H2{1nLUzW6mq|GJGAAdL>p$iarMbdalKazAu7tE)$Q*Z(- zZfS6x*)+L^7B{SqR3QYOmvlpN-BlsED=VrKVDU@$c}d@p^mVUGQat!29g%t*Kaup~ zT29K}lvKv96w3*)bIbZENt;O8Skf0eCx(yQK?LrWbYpV;v4e8-?9fhtonQL7lcMbu zZYLx4$2u>POArx(HzbjfnZn~-uSXoqyKaDh6JWVsku{P^b?Zx7p&sJURT9S47o`mdvUDM9Fyj2VVC%}p= z*Z|_Lm{hT&k{y>PV~De^BqN}@nq9w(GmQ6I6ar3wMIl|>I2ce?k+f3H-2$g;E?jNA zwT=KTDmW%{Jt*k`H)OO9Vws?xrS6~76S`AEz#uA=KfI2)%av` zP3e;P`83)@zzMKT;CkS(kJM7Gf3j9(jOuq>7*>YW>dk&!64`9fBtK9R9Nw$dZbow> zo64VSSB}#0Vlu`Ek?yeSxC~NlKA8zT7_#qN%JHZam`$F zCT=<(mc%t%;?Qjb3W4E3{2xhgB^Pcc|ByuNEZ1wv^*2dGSaAI{xn4*> getAvailablePaymentTypes( - String fiatCurrency, String cryptoCurrency, bool isBuyAction) async => + String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async => []; Future?> fetchQuote( diff --git a/lib/buy/buy_quote.dart b/lib/buy/buy_quote.dart index 72ab7bd7d..8da70361d 100644 --- a/lib/buy/buy_quote.dart +++ b/lib/buy/buy_quote.dart @@ -289,6 +289,29 @@ class Quote extends SelectableOption { ); } + factory Quote.fromKryptonimJson( + Map json, bool isBuyAction, PaymentType paymentType) { + final fees = json['fees'] as Map; + final rate = _toDouble(json['rate']) ?? 0.0; + final limits = json['limits'] as Map; + final minLimit = _toDouble(limits['min_amount']) ?? 0.0; + final maxLimit = _toDouble(limits['max_amount']) ?? double.infinity; + final convertedAmount = _toDouble(json['converted_amount']) ?? 0.0; + final amount = _toDouble(json['amount']) ?? 0.0; + final calculatedRate = amount / convertedAmount; + return Quote( + rate: calculatedRate, + feeAmount: _toDouble(fees['totalFee']) ?? 0.0, + networkFee: _toDouble(fees['network_fee']) ?? 0.0, + transactionFee: _toDouble(fees['operation_fee']) ?? 0.0, + payout: _toDouble(json['amount']) ?? 0.0, + paymentType: paymentType, + recommendations: [], + provider: ProvidersHelper.getProviderByType(ProviderType.kriptonim)!, + isBuyAction: isBuyAction, + limits: Limits(min: minLimit, max: maxLimit)); + } + static double? _toDouble(dynamic value) { if (value is int) { return value.toDouble(); @@ -299,4 +322,7 @@ class Quote extends SelectableOption { } return null; } + + @override + String toString() => 'Quote: rate: $rate, feeAmount: $feeAmount, networkFee: $networkFee, transactionFee: $transactionFee, payout: $payout, paymentType: $paymentType, provider: $provider, quoteId: $quoteId, recommendations: $recommendations, isBuyAction: $isBuyAction, rampId: $rampId, rampName: $rampName, rampIconPath: $rampIconPath, [limits: min: ${limits?.min}, max: ${limits?.max}]'; } diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index eba48632b..ac50665f0 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -168,7 +168,7 @@ class DFXBuyProvider extends BuyProvider { } Future> getAvailablePaymentTypes( - String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { + String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { final List paymentMethods = []; if (isBuyAction) { @@ -190,7 +190,7 @@ class DFXBuyProvider extends BuyProvider { }); } } else { - final assetCredentials = await fetchAssetCredential(cryptoCurrency); + final assetCredentials = await fetchAssetCredential(cryptoCurrency.title); if (assetCredentials.isNotEmpty) { if (assetCredentials['sellable'] == true) { final availablePaymentTypes = [ diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart new file mode 100644 index 000000000..c439fa2f7 --- /dev/null +++ b/lib/buy/kryptonim/kryptonim.dart @@ -0,0 +1,222 @@ +import 'dart:convert'; + +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/buy/buy_quote.dart'; +import 'package:cake_wallet/buy/payment_method.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:flutter/material.dart'; +import 'dart:developer'; +import 'package:http/http.dart' as http; +import 'package:url_launcher/url_launcher.dart'; + +class KryptonimBuyProvider extends BuyProvider { + KryptonimBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null); + + static const _isProduction = true; + + static const _baseUrl = _isProduction ? 'app.kryptonim.com' : 'intg-api.kryptonim.com'; + static const _baseWidgetUrl = _isProduction ? 'buy.kryptonim.com' : 'intg.kryptonim.com'; + static const _quotePath = '/v2/ramp/buy/quotes'; + static const _merchantId = 'a70fe053'; + + static String get _kryptonimApiKey => secrets.kryptonimApiKey; + + @override + String get title => 'Kryptonim'; + + @override + String get providerDescription => 'Kryptonim Buy Provider'; + + @override + String get lightIcon => 'assets/images/kryptonim_light.png'; + + @override + String get darkIcon => 'assets/images/kryptonim_dark.png'; + + @override + bool get isAggregator => false; + + Future> getExchangeRates( + {required CryptoCurrency cryptoCurrency, + required String fiatCurrency, + required double amount}) async { + final url = Uri.https(_baseUrl, _quotePath, {'m': _merchantId}); + + final headers = { + 'accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': _kryptonimApiKey, + }; + + final body = jsonEncode({ + 'amount': amount, + 'currency': fiatCurrency, + 'converted_currency': cryptoCurrency.title, + 'blockchain': _normalizeBlockChain(cryptoCurrency), + 'quote_currency': fiatCurrency, + }); + + try { + final response = await http.post(url, headers: headers, body: body); + + if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 401) { + return jsonDecode(response.body) as Map; + } else { + return {}; + } + } catch (e) { + return {}; + } + } + + @override + Future> getAvailablePaymentTypes( + String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { + + final data = await getExchangeRates( + cryptoCurrency: cryptoCurrency, + fiatCurrency: fiatCurrency, + amount: 100.0, + ); + + if (data.isEmpty || !data.containsKey('data')) return []; + + final paymentMethods = (data['data'] as List) + .map((e) => PaymentMethod.fromKryptonimJson(e as Map)) + .toList(); + + return paymentMethods; + } + + @override + Future?> fetchQuote({ + required CryptoCurrency cryptoCurrency, + required FiatCurrency fiatCurrency, + required double amount, + required bool isBuyAction, + required String walletAddress, + PaymentType? paymentType, + String? countryCode, + }) async { + log('Kryptonim: Fetching quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount'); + + final data = await getExchangeRates( + cryptoCurrency: cryptoCurrency, + fiatCurrency: fiatCurrency.toString(), + amount: amount, + ); + + if (!data.containsKey('data') || (data['data'] as List).isEmpty) { + return null; + } + + final quotesList = data['data'] as List; + + Map? selectedPaymentMethod; + + if (paymentType == PaymentType.all || paymentType == null) { + selectedPaymentMethod = quotesList.first as Map; + } else { + for (var quote in quotesList) { + final quotePaymentType = PaymentMethod.getPaymentTypeId(quote['payment_method'] as String?); + if (quotePaymentType == paymentType) { + selectedPaymentMethod = quote as Map; + break; + } + } + } + + if (selectedPaymentMethod == null) { + return null; + } + + final selectedPaymentType = + PaymentMethod.getPaymentTypeId(selectedPaymentMethod['payment_method'] as String?); + final quote = Quote.fromKryptonimJson(selectedPaymentMethod, isBuyAction, selectedPaymentType); + + quote.setFiatCurrency = fiatCurrency; + quote.setCryptoCurrency = cryptoCurrency; + + return [quote]; + } + + @override + Future? launchProvider( + {required BuildContext context, + required Quote quote, + required double amount, + required bool isBuyAction, + required String cryptoCurrencyAddress, + String? countryCode}) async { + final params = { + 'amount': amount.toInt().toString(), + 'currency': quote.fiatCurrency.name, + 'convertedCurrency': quote.cryptoCurrency.title, + 'blockchain': _normalizeBlockChain(quote.cryptoCurrency), + 'address': cryptoCurrencyAddress, + 'paymentMethod': normalizePaymentMethod(quote.paymentType), + }; + + final uri = Uri.https(_baseWidgetUrl, '/redirect-form', params); + + try { + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: "Kryptonim", + alertContent: "Payment provider is unavailable: $e", + buttonText: "OK", + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + } + + String normalizePaymentMethod(PaymentType paymentType) { + switch (paymentType) { + case PaymentType.bankTransfer: + return 'bank'; + case PaymentType.creditCard: + case PaymentType.debitCard: + return 'card'; + default: + return paymentType.name.toLowerCase(); + } + } + + String _normalizeBlockChain(CryptoCurrency cur) { + String? blockchain = switch (cur.tag) { + 'ETH' => 'Ethereum', + 'POL' => 'Polygon', + 'AVAXC' => 'Avalanche', + 'SOL' => 'Solana', + _ => null, + }; + + if (blockchain == null) { + blockchain = switch (cur) { + CryptoCurrency.btc => 'Bitcoin', + CryptoCurrency.ltc => 'Litecoin', + CryptoCurrency.eth => 'Ethereum', + CryptoCurrency.maticpoly => 'Matic', + _ => null, + }; + } + + return blockchain ?? cur.fullName ?? ''; + } +} diff --git a/lib/buy/meld/meld_buy_provider.dart b/lib/buy/meld/meld_buy_provider.dart index a9759aab8..81ad35a40 100644 --- a/lib/buy/meld/meld_buy_provider.dart +++ b/lib/buy/meld/meld_buy_provider.dart @@ -53,7 +53,7 @@ class MeldBuyProvider extends BuyProvider { @override Future> getAvailablePaymentTypes( - String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { + String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { final params = {'fiatCurrencies': fiatCurrency, 'statuses': 'LIVE,RECENTLY_ADDED,BUILDING'}; final path = '$_providersProperties$_paymentMethodsPath'; diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index a6966db33..7bca7d405 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -126,11 +126,11 @@ class MoonPayProvider extends BuyProvider { } Future> getAvailablePaymentTypes( - String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { + String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { final List paymentMethods = []; if (isBuyAction) { - final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency, null); + final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency.title, null); if (fiatBuyCredentials.isNotEmpty) { final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?; paymentMethods.add(PaymentMethod.fromMoonPayJson( diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index f229cb833..5480ab2cd 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -48,7 +48,7 @@ class OnRamperBuyProvider extends BuyProvider { bool get isAggregator => true; Future> getAvailablePaymentTypes( - String fiatCurrency, String cryptoCurrency, bool isBuyAction) async { + String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { final params = { 'fiatCurrency': fiatCurrency, 'type': isBuyAction ? 'buy' : 'sell', diff --git a/lib/buy/payment_method.dart b/lib/buy/payment_method.dart index cf85c441b..14b119aa0 100644 --- a/lib/buy/payment_method.dart +++ b/lib/buy/payment_method.dart @@ -218,6 +218,15 @@ class PaymentMethod extends SelectableOption { customDescription: json['description'] as String?); } + factory PaymentMethod.fromKryptonimJson(Map json) { + final type = PaymentMethod.getPaymentTypeId(json['payment_method'] as String?); + return PaymentMethod( + paymentMethodType: type, + customTitle: json['payment_method'] as String? ?? 'Unknown', + customIconPath: 'assets/images/card.png', + ); + } + static PaymentType getPaymentTypeId(String? type) { switch (type?.toLowerCase()) { case 'banktransfer': diff --git a/lib/di.dart b/lib/di.dart index 174183f9a..83b3efaea 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -255,6 +255,7 @@ import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'buy/kryptonim/kryptonim.dart'; import 'buy/meld/meld_buy_provider.dart'; import 'src/screens/buy/buy_sell_page.dart'; import 'cake_pay/cake_pay_payment_credantials.dart'; @@ -1019,6 +1020,10 @@ Future setup({ wallet: getIt.get().wallet!, )); + getIt.registerFactory(() => KryptonimBuyProvider( + wallet: getIt.get().wallet!, + )); + getIt.registerFactoryParam((title, uri) => WebViewPage(title, uri)); getIt.registerFactory(() => ExchangeViewModel( diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index c65ac267b..5888970b0 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; +import 'package:cake_wallet/buy/kryptonim/kryptonim.dart'; import 'package:cake_wallet/buy/meld/meld_buy_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; @@ -8,7 +9,7 @@ import 'package:cake_wallet/di.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:http/http.dart'; -enum ProviderType { robinhood, dfx, onramper, moonpay, meld } +enum ProviderType { robinhood, dfx, onramper, moonpay, meld, kriptonim } extension ProviderTypeName on ProviderType { String get title { @@ -23,6 +24,8 @@ extension ProviderTypeName on ProviderType { return 'MoonPay'; case ProviderType.meld: return 'Meld'; + case ProviderType.kriptonim: + return 'Kriptonim'; } } @@ -38,6 +41,8 @@ extension ProviderTypeName on ProviderType { return 'moonpay_provider'; case ProviderType.meld: return 'meld_provider'; + case ProviderType.kriptonim: + return 'kriptonim_provider'; } } } @@ -59,6 +64,7 @@ class ProvidersHelper { ProviderType.dfx, ProviderType.robinhood, ProviderType.moonpay, + ProviderType.kriptonim ]; case WalletType.litecoin: case WalletType.bitcoinCash: @@ -66,13 +72,15 @@ class ProvidersHelper { return [ ProviderType.onramper, ProviderType.robinhood, - ProviderType.moonpay + ProviderType.moonpay, + ProviderType.kriptonim ]; case WalletType.tron: return [ ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay, + ProviderType.kriptonim ]; case WalletType.none: case WalletType.haven: @@ -127,6 +135,8 @@ class ProvidersHelper { return getIt.get(); case ProviderType.meld: return getIt.get(); + case ProviderType.kriptonim: + return getIt.get(); default: return null; } diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart index 508d68a82..02b2b3576 100644 --- a/lib/view_model/buy/buy_sell_view_model.dart +++ b/lib/view_model/buy/buy_sell_view_model.dart @@ -351,7 +351,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S paymentMethodState = PaymentMethodLoading(); selectedPaymentMethod = null; final result = await Future.wait(providerList.map((element) => element - .getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency.title, isBuyAction) + .getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, isBuyAction) .timeout( Duration(seconds: 10), onTimeout: () => [], diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 0ef38e939..a8ebcc8cc 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -77,6 +77,7 @@ class SecretKey { SecretKey('moneroTestWalletBlockHeight', () => ''), SecretKey('chainflipApiKey', () => ''), SecretKey('chainflipAffiliateFee', () => ''), + SecretKey('kryptonimApiKey', () => ''), SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)), ];