From 3f37031690761dfd9c8a7eacecdb94a2224943ae Mon Sep 17 00:00:00 2001 From: viet-nv Date: Tue, 5 Nov 2024 16:25:54 +0700 Subject: [PATCH 01/83] feat: earning landing page --- src/assets/images/earn-bg.png | Bin 0 -> 217240 bytes src/assets/svg/cursor.svg | 4 + src/assets/svg/liquidity-pools.svg | 6 ++ src/assets/svg/liquidity-positions.svg | 6 ++ src/assets/svg/staking.svg | 5 + src/components/Header/index.tsx | 4 +- src/components/Menu/index.tsx | 38 +++---- src/constants/index.ts | 1 + src/pages/App.tsx | 4 + src/pages/Earns/index.tsx | 144 +++++++++++++++++++++++++ 10 files changed, 188 insertions(+), 24 deletions(-) create mode 100644 src/assets/images/earn-bg.png create mode 100644 src/assets/svg/cursor.svg create mode 100644 src/assets/svg/liquidity-pools.svg create mode 100644 src/assets/svg/liquidity-positions.svg create mode 100644 src/assets/svg/staking.svg create mode 100644 src/pages/Earns/index.tsx diff --git a/src/assets/images/earn-bg.png b/src/assets/images/earn-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..086ac0666e8bc84ca94dfb8dacdeea95e8e7af7d GIT binary patch literal 217240 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�aUBzthp^d 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
-feTHJaE3F0efebR&+6>;1Z>

x#6)E+-AbJ4! zSij0`gtf%x+aHCFmn?hiW`o=ECq>h~L<}G&X?A@jPI>?zk{8&JcTJkCp5DYUW()$4 z8Y_O+w&INfM~XU^$ay}xp8WGL$uwkt>tX07+B77%UI zvBC`%Qk$1PjP(xdk0-W>r$wxs`P33$*sF`iK*9ZX^UHs@1C|9Sy+reEYIeoq))UqI zN}2<$Fi>TexQ*5*4ewO@(yA&%-jO+K(@PD&J&#;Aq?+~n*%S`%JRYk&nAiN0nGYOqR1&7X{zC8NsLNclwE zQN>nHMemmi#R%rHtOCbV?Y6e1=2BO20(TUN0Wie1m63|tn7vE-bG%n)rgwbCE3MUe z8m8841iG2FxY)toMgIMrG%^AzILaIEHaoadI8;3=w6+)UUFzXG@6gcqM(!iYE<*4w z7Ck=EYk;#chpt`$znxJ1O$)w_d!R6^CXC^F8saHFvBa+%`E6bUcDT=qPZ3vxg(%T_ zlZ(U^wPw^|Q(v^AIJzfjQH3!#%z6yXYURKshNP_*(I^YqdP+;C`ozsw@|!h@W_RNf zYVlfNORVr1uKHCohx8bT(v}xeG^dd>E;saV+AxX!qS{9fVC>tO8yhq0Cg94vv%&Ym z=v-@mMzEqCx2XB(>+QS2K%R{BU(cUAH3}h_N;K7a%I5-~z?;87o7XeU!Z?@yiEy0u zu`Fmn?1Q%>uLucFY5^dw-**Q-y9c9bb@GYF7E+xbwr^_lKWrktq75h3VyVpUpV3am z*5N78^);g4gYC=S_kW3d#sf7=L8OZT@+U%j&JjwX_Y*fL(*z?}>v}zKdVR|WbHSjF$-IfRVbYGuV=}Q6v z{@@OBZ01~tdQ)(xyJr7Tj(c^HCv|>#spw1#5?1jk;%wWtayxRv`Q%anybW#lSf&5+ zl`LAcc9S3#08RH0cj)`0*6Ddc_%!xlAhs&S){Doe)q+mCL%3*&8QP%Y@gqt|3AVqj z{}|3BJWjJ|>~1tS^t)KZ5e{GN1qml@QjSEXIrLlD$U5AyKkdTOr$iIO2k#Cw3eHEK zN^*bj!f$C-+7cIRT#pF`9+rR8#nbI868q9#6vmTv??NDTRvb5n6fTcSG9+enBmIeb zxaAPX_VE;C<9GHFQ|<`K;oQvn$WHm+QCOdl*R$*y27-lOoSUvzxS0|z>*!`%iU{A( zeql6QRK`oT6mDSyAA>Mm4*q53<&mn`e!@{t6j;KqKXEa3SmIBej)j^$DA>@5#(-Fe zPZI8Xf1Nl&F1eU_G5Z&EV0Sleei)@C_6h&j@ew0Y(7{3-2)63B3MJua zk@FzBI9gf6@mtKE_#tfmLLy8rK=IU_DyniZITL9gE!mz$*{WLn)jK2%DS^zlp-ROJ zs%C;RmkA$OxjpftCZgd3SvFqOoP`n-i~LJ#dKnLU|EZgBe&g~Yij&9WhDcO!hre|u zw?iJPEX$*L&Qz-E2jN<8Z`#h{s^q~9aicR?F4$~v(^&4>h-c_>2oXmZNp%2Qk(N;wY|5U3h@ z)0ao60~RjWmV5nyC5&F@+Sr|o8J;~>;wgbSGHOTW5;Gi`t8iWoHxt>rPf|(@rG=Y})1aM90WrheOf28>H>JOyF@; z(hX1jx{qa)p5$NlH8x%?6j9_z%3t$8S8@*(*Akbs?!^S9WE_foe@V%h5?O!2q~D6` z-xIQ$fB+z~EG4abz+KE9Kks_+WQCV1)q-$u_NQO8(Az8yXG8@@3Tohq^WZlY%|fw4 zWW~?qH5S{(f~~Sn_0y zQ0=2d9mqc8swyN|CZ9maUwB}D%v?(E>|GUd3yICc$bty??w3gyt~&3A``B>vwzKx|Gr6S5!!U-x##m+D62fbyC_Sy>HZL`$ zWkUy-=Uk3+HX6x1-BqQ&I@%kI_vzmv1xmR7nuq*Ypw?woWtqq`JTdZVO!bV^HZbc2Af(J?yT-S2z9 z&m+gNQN`SH4X@0@#Bc()S^q@kf<04n!1GK5Xq znsVS$X%h{Gb^3a9^uT4!Dxr!$-Xf8=6ofO6NYh*RZ^^fy$DuEC*Qxi%$^$^_BX)Yo zo}y%z-TK%@<*A`@#4&%SHJcuRlRMpq5u(MZ9m#bD57X;CW)y&iAEo&u8?y{Fbw&XE z-p5a4PfgpNx8=@6wK#9ofa5OvBL>bPPf0V$?1M5&pP+yWOs&*Z|F_m5QAL!x-K*n$ zPyIMns)pe3mgi7z9_cg}9&rKuG^tP0<7Vg7Qm2h^!wAkaC|J$X5JL{-lKbN%wpv()d^x5 zsLJQ0JEG`$>5CE%?6`}PyKyqBOy1Ly=FkMQhJKIJwf(z&$(y@J@5MV#-5^_VC04#7 z{U8PXngj4^XR3s{Cyf1m3p{yvf%cgl-f_h5H6mc>1>kK3M2gQ=TM|AemdMTvor( z4KCG!*I4z03c^3pc0UQ2PtX;jmbefGgadr;zi@Fq?1|#v*S^EV`CrNlP+9*T8hnxL z=jLs3DZ>>wZ zCRly^sAd2^hnP~@=PJL0qs~qzSMm6+Sf&FRTy?)qbVR|l9?5R!BuxKdFy`X*o)yj=t`VZWW{K$kDX6U$$y0hX3&ccYX%8p^!Q zUzcw&plp2$Pc3SgTH}7zNBx@O&EN8G;xK$UG24&nL`*rhT{c{}zPnxiW*ir4K(hqj z1pfLo=ei0Rb$Ah-up|00)Fmfv^-{lP)2~FEsuW}RZHm(3sBdTOzVY@gv*;_xxHkOv zm1N=QqD*NPhQrUlz@4J6lLtt}{ z%aa!}@i;hrdR-^Pq!*B@)Vr2Oa9X*E=Mn;Vf!g7iPmYG3CJ{@!^hb8~x%^!Hv(U^# z;YT!fb%okWGeKomiS`|>v-KAe^KIIuDmTp+w*2;<2;_4ThVOdUEh~zJYyPfSCsJ;a zOgcpVxfVknXVm5LF*M&PqF`~`-H;K)0vgtb$I;gNZbJI9W6HMt;9Do2TVgf%4`|NP zo24|%Yku}Q(gL)_`NpxkoQbySE@{0zBrW$6?c~DW*%+eh>1KKdH?cX{U9k}}7IP(f z!tpdKz53Dj>xs>Mx8;9;=u{KVGG}i^JPAS%zhGB-*uq~_Ek#^Zu8L-1S>omSHXeK% zAfCIWAak?CtZ^BcaV{7960}&}Kc97{LQ0JXcFDA6m0n4w9AO0L%FtN-OS1cN@hpPt z|FzkE_vuD767{X1819OHw#rw{+0t|mqVT|4p%^;CJ{5zp&T!%4-h6g*7} zA^VUl@{KP+I&2)@Nv`_lqNi{^IQxCY8LYigZKDQz%s;sv;VH?%TR2ej8aE-pP8D!> zY(v4Hx87`EodHX?-M9n1LG|*}mCgvj0mbzGo+5%Thp~;9)qLa&N~wACJz6^Pr3i)d zLk;QEN|XNhS(QS25_FfQSoPq86-YDzaf)tx#?e&;%HgDzgy{YgKGnieWAM`h9dt2U zcVl@Xn%x-N=*nb+Cz64Ok~Pmc1c-d02ks4~Sz{Y+IBKmwQdzz)7RUV3AJ`1vY2VXv zotD%br!vkFC6rWO52{%?b@L*PpX*p`#PDR@c9O|X;Gm;&>AM;Brz1rKCXHmAw^m#e za!#Bi3_yUOq76)gvP8JT-D?wWzvF~PBt1#<34mb`PYMW>zCyCgs z%XC1XXjklmm!@}5@KJ~4`VKO`a=nJvUUarRQrv}gAC^eWDQRAZX^8mKMU{N%NEW-& zA4#2j7(71sI4X>4mWiP>2K%GFiJ#^#@O~iu-nD+9=qu1xaNX&``{~T_dmRd%J0;}A zv~;7@`U5rjBU)2*&xo3NM{1Tep-!&Wz9DI=IfWMJSKU&vaml#dY=ErX2|@f@+s?nq zZj@v!`-5xGDGAfrK2QDq`6w4AmO17@=Ao9FoVC(&P1l6BV_8D&AAy}CAw)rbMJ1Z} zstQ}`K`sI%IbP8yu+>&=HCxi39uZ3f(wPPez+phFyl3#r4yp3$K<-+tE{l{=Tg)-F zf9`M0UrlQdu3Wl%>Hm?sR&*K9>P-qlE|A}n_1^0N-`i z9b&?~K}oZ~V1`R0*}8N4%%yUc_s!7)(I<5o`7D||Z>oW#x5AK3Vt ze4{?92$D->34<({DC*8gl!CrUcJPtk>$LqPm@j`1D`?AO*k9Q{UA6K`K+q%rUJV9Y z%oMsOQSjz#`H?BPbBqZ?!HiprkP0#~?%4>h3u6V%Md&eCmg=8s?7!_B#R)fg_2{IAqYQ1J zTAm;bBo}G`2K2T^E?gO8s$O06FN+JK!~S=ekH34_&ZYrs&v-kD2qrh?gwMK3EC>vz zsb3<69l|3IQ0tz%^T!59Bk~9)yu9ld)n-+j3p)2CJZPV>!uF%kC%S9B)ICSS;U@K_ zE4J|kYD`W52&R9JeEXCl#J@8xiLS=>`lCsVrtue7GF1W@yL}#?t&XwByALl%iKKBK zx+BPFM{|?imd(ujO`6a3T)){tE8!L5A3K)PKjIkNYJ2=b-&^l&(5Wy}-eO%TM{DgH zhpUe%K|{x&?C!`z8=Zg0@YM=3ebQR-Z zBRb^+`1^T#*saqbQd(H>pU=8W*?%|k932b)FFMvrXj9wm@Pl15Pn86~)K}B8(8c>I zaC+eocWV)7_w@1i!Uel%o`U`+rPuv;|3c#uCE1J@w2q~s(K1+7W>q<&G|QjU-u_UUvh!< zoOtGETYu*Y7GF0%mqWZP@*aa1>Kv6b>+a2wo=A0VSA%cp)INC0c7MNq>1JjjHIihv zi8(Wh4N6mKkQBf;wi9|Y2JVLcQ=taxOKv<`?MryS0^KB{R0J^a9{LD;;1^dS%WR>b z^F+a{w%vadkS(b&0q@d7hswfU7MJ8nEqG`tzN)mi#;qgDwNZW5XGIL`tno1bR~Mio z(KCZXIv!Q*`u-C+i-I|)kFf=gq7m!F6JV_?JVst?u0t#&gKZ_9Q`HHo^WhtEv`xlp z?F{ByxY=}TZ??4rcpv$&gxHCi6dob;yZXe?lUXdYBkQi!0-GPyDOyKkOhX0 zgjwo_wUrn0R(LtdTNLB`TkCOHd1Erz6x;5m{W*h1FIQlv^VrW<;1^jgDT0BIuie!E zpr(=Yyi35-b?YcScWQ&(eQ()hOg0~!w&?G68$JptD1JtBL%0iw;nA1V?GNAJj((~? z=H|*E=rZX@ij9(c&JoY{YT2T9l`=(fM@r+L8b#W*Ga@+nW@pFf5#qu;ae;g&LLKi} z6V!r24;4Y$>^ zt_{qnA=($7LqB)uB6-L!`f=rRVANR6tEdD@&$hgo_MgHa3mW}C)Ep$OCAl%_F?|4_{~Fwwye#^vX&)hz!jyJnv!6O{Gfn8EcWwDy@Nm2&uGWK z(?;kXhbB6RZ(6-Cn-(rH+4AGJkS>#>Z4>%Q96(HOw_JtAE1Z9U+2NLg5oW@hv(80Y z9DC{aAs=s*WBl+9goiwacFJf|hHgt28eQbUR@%S*-SF2dsxiBjorG$hM_aYJiw;QC z(E%;^tv>XGO2_6~EnC^1*Z^4NW{@=4hr8T)Y#`-PlWT@hw>B}j^cEm#W_(O!xa<;K zRID8TuXtMH>S+(^uj;YyNUTsH1mPW6u)~yB+uCw)9E)CZ#yfG?wJmQuK52CAmNTA3 z#cIpKZMk~#D(`U{zNAa_7w(OC)r+3o)3)#?xV&M?h@L2a+>#_&=skNV75V)dY$EsM z90vrJiHoOJ+7A`D6c_|O(11qOF-mn1yZezeW@f56u7o%`biXW>(NNdRf_o70_odOb z>*bxWPN%hgMYh60QOi)-G1RE-`%X4uG0ms~)As{TW8C84*3@otVirqsNd&P|@`$x- z;OOYMT@Z?zb}MwJ&UO}3d6WsWYRjF7Fzr_!18UX3q~-h6J8@5>Xsx@sm{W?IiAZ@a zzXS@}ppvd{UWhL<&Em^Rs&9{0gp0&cN}GKKeV(}z2hE6|(-W=*8|qvwNB(ag5Nl)m zD=p1Fms`A(Fajte&m2sk=P#?iSK`<@UVU0zZJ^D{0LjiyM~Un`tZ@zqROG2ZYsmcyE-ou>Lq){a zdj}L9TDyzfzBjY$p$?O(bB@$krf&l+qz={gn&miQ-8$mI$~sAGH5>}hq@bOK_sIkN z5~Jlj^*5oWcNW~hvTTTr#hohfU(I@y9$Zs?!A-+;#Z&3Pq|<{-n5n<9lj4{7Ngg|= z^*a^Ocpl}-!XMS>3rI89+2aW%tEnhbLTVlxvJv8QzLo(j4Z}%$-J|c8^uQ=i6 z(!mcch9^k+#8Nsz{t)3xI8VF1EiI?#C-l5J=T8abJ9pQNtRb!g}~bMDwQi0&9? z@6ae_%=H~J4WSIek^Ru3(mu#_`dmEM?qQ-M^)=mhz~ZX?$PV&;c8-<{xUG7C%!rWwZFJ%Uv@*n;r0pYIq{Q8< z*8bP*h_-1|B%bi~C<8U|0p0ItmkY z&(EhJ0hq>kBV{z?4EDDpH#c$_**s`%&ZayC*9V)n=lO#q582>NsHFanZX}fRe!q%; z9w-bVZ2o2bl1w)Zn!IXx>}zL!3;GEqQ0$De#yZ&O@GtywHmjA%ooU`8P%)4M5cUk@ z&Hr?uBvL;KMPf{b?1ARH{XLzr!T)9Nk#XC&z8VVNvxkZmM2WSs^tJ%06iQ`B5s zG{4S;qz8!Fyo0X;*h8 zmOVP*tJ{SA5(-xc?}%fMlo&;LROF-bkbJzXYiG$c6P*V9>v&HP^ilZ zQhHM?)Jhst`ejMC=b>uvad=E}y5E$=Od`hcxs8ru+HCtv{qJ8Fp61kfVnF-JQxde7 zpE0atLCXC%wrzIo6MWIrDlI$pP-LoyYZ9DtVcB+6(^-6?R%($_ou)HYF3J% z+mHXS?DDSJ9&aGQQ`MvQXr`ZTU?=Mlce^_JF=nbuW`ce;IF`{4KYe-!dGE-YmrzZX1TWV#yZD# z7fPs2Gm}Po#=upFJ8#MzqQz*NY}}g>aJ(g%O$I6-y$>E1-Mi56a$?Yto>lVINRF#2 zI`nqrN><$EsKLvniyP4*-lVVJcH!q@T4D37LqEb2;I*viuJ_RK*t*!~E;BDqzk-dA zrkn*c7Zj-pY6PQfeXF*{-fejk5w`Lxu#J}Z3IfDz42EpW;gLNmaM-%3ch1sUxqdKa z)}z+VWFmInrw&5r(bgdp@JYvk*0gdfRn^cd^SVU)5i`nd;S3l)eRVg$3&dHOtQF9h zlu33FXY+5T7Gx_>-BGBHHebZg=2)^~8;qH`OOv;O z*Znkx4VR$qV&$2>btq!N9SZcP%bBBkEqp8bc?i7b{7C5s=Gtwb@d193EBOl)Z3L~)}YiEsASr0)C@wzZ0tC};PXM{rkY1>_THx9HROux zMQ~R%9byX`t5gXlnM!{4(GXU5FZ@C-fM^BAe;b0^9NWU{{p~xwisUS_eJ`;a|F+BO zhC;C;P|(2}MptWckiVcsy%_UgcP?=s^bic4`2h6rW0xCshP8+pDKge%+$BFcKvH>9 zu=Ev!`hyKNf#f5~O^kjIw#qouPBrjxbbOg!%wp%=x$67w@uBb{RFCIc8u{@PMaK`9 zj&XnST%#JtUW&OmqSml+_~aYK?8B2{~`bC0utboV1o z%ZOSz6k96}v_RpQ`w z8RA714`{ZGFks6a*Uo}rP=A%B&;LYvM8+Pof}b>Usej8IN`XytBj$ZSxH6UA`Ls_X zIiiCy#m^76`SYK>8nizX1w5IkgiVVh%^L`&PnH>|BkmB_`7Sv+flpy?oaI($}BFU;kTLtmVtMg z^q#Tn5bK}M?4I=6L4a1mS;M$Qac^%@)-uLE{gS-ZU$IPYHEoRgOZm7qx*slWrW8K~ ztn@-|1SeYy4QLA|-BKi$&A)w^muUBAklR`RczDI^VlUisO~_Ie%5SDvI&q)9x_QZS zgn#iRwLIVh}U^3wfdHhF{?U1fotA>3pV5JfLDVX$vSjJsvK4dGn{5 zYbA0Mfq(`R%F4x7rGgjZ#HkZyC^x4(?Szf;d)*~vltIMJ>YM=Ae!oVzHNPJS_&ZA` zDa|0wb^u*c_wlr$hLXZ1-K6J?t4IT6B$a6>HbdEMzt+4R6sYh~u_W0hjEVmxd3dFd zS_yx$_Wq*-{I1W4_dQ!BaiY&KSNv8#S`#mx%ets89I0@W7Bfs-5Y99-eO(%lS+x)) zqmjQRmh_D{r$_mXTAsb;_@B`?C zwbuL~>7(1$pi1&VS6tpdr$P$VZt>@Qq!chTPCC}UV0yendBZbi!^j*ZNXAP#rx{JemU=;+m^Cx3oldzkKrb~eu|+TstUV8#vZD`X9^N7#N* z+GEijo90=wtza7kTysF&CS)6*ZF+8d%OKZ|EC^}?-vn2C@7pn~SeA4Lzh6Ct1vZ-F zFG3+!z!7@tTdb4|3Z8|oaRWh#26>fr#t+a%bOM>)!@3J$L1Mqfnj)Ame_q4 z()H19R?*b_IL-T7AOL~`mfA*!W6oLzRV7Us-=HD>#t6;WzsioCO4M0H5ncLvf{vUn3xL;rg^QmPbd<%{u&2m(r&`YXxUOjehCz@srL z5RNt2Sh_!bOd=p>&T}%ZC>A0tDRpu$Qx3R=PNH;DIs}9)e<*RjoNe<5l5vG zXIUINB~%gwc~j?{oR066k#ny(g{PJ>C<*WeL#Z<@h_R8rRCCTjF3XCE7^~{yFwFT9 zRTpLnc}iK~1?6HV`W(wwqR_spgq6fj98(6;;}OpIw%hZA-oMFrqr6el`;GdinyR+E z6K!%Qg(LC)D<_%yMNQjbQB5_jkESygQI z^79gv_@H2FO&*J#PtR zSI|!<{<%9lb&I<>j?46xx@+ zQ=!So@cQZ&L9jw{%IP`#g-`BdWsG>v``!>{@NnPBJVzNcYCtEclD-D1OoAZjFCcK{ z@a$h-3t>t%y|iPemCRySI_^_c%*;no*yVKpNdl*eSquyjN>0IehwxoB<}{d|b^UGT zUck5}yoVpJuEBPyYY2m(qtvD-egw?Y)Qo7QZZdCuA4GK&1+`2(1e&UI-WuFV1eiZ! zw~r2G)Pc8nCUm3be149dY#|zKZ z0OSp9Q;O8%%JzpdyI2=A3)mf&v!CJTAzJ$}v-9X<`32jpyIG3MLf+i`d25)ySJ}p6 zt!e}{#VB|X&oh1ecD)qYDJr}1`7HAiu7D<+If7Y-`Mp*QkL=Q$#M)-ATmEm{^Q$P` zZ~}&EuAy#k>6gtV9RGY5yU-La?pPjgrohslfj2v)IL{u2x*nEk_b%SzKGio}8%KmT z+KZY}OT@3KvoQiY`^h~}2Cy01^C&Vof!PbOw0!U08do3mtk@g;UdjC^sQ0-NU${8_ z#J%G?2n9utuPtp2(8{CTdA^*|h=wOquL7UN=3WVkj^#r2ZDKP(f+K>JK(RfFuMIm} z_4*Gjx$GrNSocpljKn59h)^0p^;b|lQ8S&VO#a2sj_X1-r@l&{Oe~8L zN?dxVVBn}(B!Up5NS*OBYH3*B@RwNLy>!3F3H3y*M};iY;!ccGAWB&7JE~W5oO-q9 zmk$IAx(8n~t?(Bl61C|NyM7W|nYdAy9$VJxM;c4X#9DpWLxz2(5=>t2;doW!low#VbI@RoSg6a> zmWqSzS^uOQO>wS%(UULNdM&o^pCZW}Y@{^y;lV%E>_n6Bz&z{ct=4aJoSD;hnR=Mt zRJt0Pd473pl2VIPk{(|c1br1~c-m)Ij&_s&zn!2M!Y?k@k94)S!J9r^r^!T8FvJ@t zx@D*Bfk>>3DYPK}R>%2kH)hn6J9fP&_`gcj0k`!91(R_tfDbGT&oc334}+^3z?wb) zWj=kP89De_XNc-Qx^{Ikjj4*jd-_ISEIK@s+%$LLxUABDPIH9Bz1 zutpI%CQF*+4jeEmZQqAi-?&sH?o^ZEx;AfH44ZN$PE9a&%1-!Sz|GlL%^XS1}^-YeB-jaFB)f)g8t{)1KeaHC*xNE@u(nM}p~pQdd?b=wyJ) zl76R!_^Y7f>S#xv2U2c@61}_8c^+jL-pRLGGnt)P*XLXSG%wj6Cx|Juf}OyE1-ILQ zBMe1=jDMy7chLOJ@XL>$@-A&Tpv4r^vTBxTXRJ;8TT7DIV*_kx+9Hb5PEApEIeP>5sV}q30?K^HhPn~FX zfs_HE9E+fmJ<3sl@$7a(u{)Fc7|h4YX^E=*+X~*cN7+q|$j`F5cXi&6>MH}8N2zLl z&HHB>@tyEbmt$!!Jm3um;!R;yaNH>)|9 z>;{o*)H0V8K8*ozad~tt=YPC`L@o(zuD6H@3R(I0rZJkw9cAki>A+IP;6y6D4k3TtgHG$YG(FlM^6D{MheRt4VRs0Z+BnYRrqoib*@w zTT}Toq@_-M{c}&CZs3MJ&l&9ov~+AxiHwA@Qk*scB6owMw&oah+k3|oO!xI}=>Lcj zl5o#W=u*`^c3VHK$NVf4zefz5tSUFnJ`H+!6_95mbY^#ae#V(%#?zNp1D4T}0OFYO zT(_oCto%CYydfEY++_adIgkC-F@9{@kkF?mg&jO(P1dDuK-iQ}{X}!dSZ(g>UFI*n z?ZAU+y|&HT^G|B2?*hM1CEmFcYJbmq7r1kve5ajSplgRRTi7Vlm-0;9S9CI7fBLpJ z{<3!5kdQh8J5jiv&_YfHg<8~#J;-lc5d*21Rx_SNj_42OxsfYU)E|aR_hkyJ3nW$p zpAHdz|7i9XmLOK_`nBh?YuANbWaz<5@uU5#X}kii<1iOKq_a!$5anLhlVt512fmuy z7OCW%RTG8zAT#c%uV&GQkK>k6P!PWJ{60Q-hSs5>b3RuEzGN0T?n^8;#_}*Xx%~_- z(hD~ougH?VHF*0^VuD1%yB`E9k)1YXGAK{$WfMJ9#k!^xY#zfY9JM{uiKQY#ig7eG zC9HW+n|rjHBlIY~S=_2GLTRKB-(N3gc9gG8ECU%pU|H~aYsA^ExT@6T(MdRl4dD=fVg=MhdigNhFiwWFLR$}2HRV4+~P4hvlXV$&RT|`go1MWJqP=*bL2vd1| zX=}MAXgE%HH8f&?Hjg?p6k7^A17^;@$8FbTLAM*_+*Izj>rgW$3#t2;V8A%IyEoj97C z9qV=uWuyJO~);#JP9=x#X5|TaC7SUTn=edNb#VALP3iO&b#Wm#l;OZI@*d)6Y9GquzH-tUTYl|Y5V zsdE|{S_7)|Ag72DTBX~$`#nD{V<9a8fy$q~-J=&#dU8)fH~F66R;Ku$3XoRuZo~Vh z>z)w?1tH!m|6KZADgq1HwNxP*TOf-et8^qcz(Q6dn;QXsY=}iUTjr$LllF-WpLZ4) zU!u}2QoU#rFngOmoCNq#qk>UJDzTWRU51RV@b#TZ8TwB8s)j?>YI(f+b|Rqp{^9hT zJWYws$GHd~NcO6tz6x`Z1&rR#YpQ8q&|r^fyPKk?KEXZXl&J$6 zK(9R*fh|zFp8g+Kspo6-dDomEQCs-$`SUwsPldfIJN?HLztX(T7O^h$Bg`f5s}i#dsg&dC<+2epey!wQ^1XVccr z8K<9U*1R{~jiu#IaHb||!@$Q1{R~t<g;E5Q+>JJC83_BZ>I`yIF1)-WC?~ximpZdB9^8#cO<#NlA3H%YWb`>c~l3{{d$`Tn&>k-aF;4@ei}4R7~5ITPe>%LPMUO=IBRTme=&^|BDY zB2GW1a@w)X+)&XcsO~b#?qJWOPT2*{8Tqp*2j~O6@^2ekw+st4J5IA-Vkw$f8hMx0 zkzJ#Ls8v9UMc)wqrIAzqEYszD08gv~7t8`pe*@JmWL(TF_2=zjPLD|Ki=tlz!F^Rq zfrZba{J*a@H*kc$6>BLD`)1#?hRL2@J&Mt2Ran-Rn}Ji|)|Q3*NhBOkMbHRu!E5Qa z8nmD|^Y3?@dWoXi5EY|J@$c2BEUyM_iPHg9#5kS3F+q{QKE;02s;r2ohuR9vvTdq! zT_oS2_-BEKv#qjcHSyT5m{kLINHvFe?EMedPg@fNdI+TbrH20)V!*|G<;p$9?B1cDX+tvG*lO$>L@ze?z6|283n=K#( zBlTCaaHl^J{$D?%hr+Xwys*NIpydDt-?B>o7 zFqZ{8r{QOXqI01wQbNkJ^l*(ypuG2N>QsnyuEuYv59Gpy2nonG`l=T{!s45MqYC||L5g{?y0gF zi7BJqHLa57a2;HCX(#2*9FWCkTkF!|F2?EZJ3jJeru2|u?X&qWqo<=hvO&*-60L)t znqa{clWmKHDrW_cPd}oWV9U{&J3kKvyv<~s{v?DMs8QRJ&77fPz5jK6bK5!CN5DhC z$Ad8W_wP}-o}yAj+ck*_s{ruiuT@@dTr0S#a0oM~dv%SBQN3&pD`{PZ987m5x9^<& zEGy@NJ?$!x$BYzz$h? zgRWeT01C^1;Ep#d02N7LKO94nuM!)4ltu#%U3}vKx#1(YQhZM8O3>mBeY7ANmVEio z64ma0BGyQTv|-xh6c|#M^T(c-S;^?;U*4k-b=Ew;O9*R01G6Wm#|*O|btcQv8NXy2 zYe8Xsrl~q}9#Sp~zsE$QZNrz$kEHI7#Gf|-3Yp^)|G7REj=R^wosk#|D5}!;VKkJG zk*@u-wN>Bah*`s2AU`BkOMiqzI=QE$!D|FO2Z29HEWC%}`Z0S5 ze_L~xb25av^bqtg`tj;d*VDD7_nozHLU7*3vLDKht9~{_f%x!rR!(&Ryp7LftmhsiVRSMwy`Z1DBqF6*r z$rfO*fo8J#Z|w@{1cSYvXZbqU87-8jSj(~v^^v12@8YQ{Vu_9)lpva>Q{E@OpP%)W zKn$dWSu%Mt*ayPzW08c;0k}3Y6U{BH^oBg(@OEIzJB4V>m4h_qdrmyN6&EtE%2k4<<@=|jPFv`h^Drx< z_ny+8YR>r)8Nk8s+! zDtIpGV$%ayW0VB4@cm=$$AC+N`KKL##`=RE1jHwgCI1q&(;aW|nwWwKl6#?m>6yM6 zFLY)(nmV#Hd7!zWH3yMAA&X`uE?f(#=3^ieBJ>|pu=F!09+9G=09^*YJZxJ1!eS$G zh1yGwVXkk-1^I$}u7w|v-bE~V0h8FVUo3PsKR7 z9~jZOVpy5(4jdwjbCprQ1hg#Z2P%k@q|3Y@Q@d`cqQTQ?bsbV#M zPGh@-&*H`weWWmjZ}nhGi-k;nRgm2si3j-5n|{%Kp`tZSJXQ>4&yrPnr?mi+QY<32<& zpHpqAV>|^Z=ADOZJ2qQt!wMk@rMTkSa@1J@qwf)~WeM zy$;@++^aNo=o|W+GIZcOcy4(2)YMf0DCeG%@jljC%B#sI$1j&YWWzkgWU+TZu^ zs=R<}4B*%(z7x(_Ck?696PmyYR-oEz(+~}w{~iW&kJwd6sY~h1WsFX5 zx7dr|p=X&VR7p)`ArCC?WN#&d$6uq5m5V+!o(rkcBe?lgr7pU1C&H(6{MXV9m}xGKozu~e+AzR1{j#pZsc zQuKbbCw{x@c&#uPXH7F0=K)BR({p0tAO7%_tJ74cOp^9~-6B02*f%N|k^3+^Lx*+} zBYd-aij3|)2>Kv-l;zWQbz8x{wmL%x>;FwL`U8aTB3N-Yu>Br~g49dQCZ9)8dN_FQ^x|(syLmgE^C;)CCy1d9~#0QLxVgQv>=7Wy49Jh;|aY3P%?O-AYH^e zu>d~_?!3J>^u7wQ@W$30(GTv~>%6BAI}Wc%KljK34InY5j3h8B=G+})2TJ-w1u~i& z_xKQy1P*{&_o<0qT54kzI{lY^Gfgjsf^!}|(=K8xK&|CQaF;+3bxADY_JCoX*cT{< z(wIszE+EJ+sAq8Sng6R@(cP66MQLceQu5<1c^tg@#-9ZC=2Q%opZecpR8x8^5|Q`3 zgclx*rG`i)R*K<9U`?Q_y#bi^yFJFflS8s6CAX2SVsdZHd0 z7CcCc?=s1j!oO&PfCOyRD8(3%_jUa=!?JJsmQzD={`82nYw}{t!|b;m2>5XCm@DZl zd$T|>3aM^Yz5V?IE^Q<_5lBNS4^>_`^L97r;MBch6!1!FfeDB^4? z*TrgIWkd)QI;twB=s4;biU_9tuFFdxnQ&qea>^`m<*wvOi7860fa+(v3kC8i#M+f| z_?v&3JU8SYqVZBvsa+I*oS$MGY{{M_oop~fh5ka}sxpO92Iu37mYM1dyY{oWNP1=3 z=|%s8&Ba=;V9Uf+1{0C^T}{!EQb22`rgJ12^*V)I2Qgb#G{i!)Nz1O@8=_GKPbeGPoi7aZ9P=P5idB!_|hEFXg)~B^{UZc8o`^v_G-6frLa|l+%du`pvyk zJFV(&nG9Nb6^+unr(BO?xgF-q(aA)x|2}$GCco>CmFp;J`IU|7uL*2FjgXAhOSb$I zP1g;{4fJb-?oDnG_E@ztiGSNsBK=j*x?@`p%bDk*&HZ+byH7c3y9N9+!X@$`hBR_ycSR<8 zu;{OV8vnaDpFc<|WA6fe-3FRmkWX5MTM$6!^i=3-B>V4$D)!paTLX*VQ{!7Wh>=)! zObR(cv$(31#UEGahp|`y<@fMoKZBTqvV8VPQ_T3#`Q-En-kXLCIv?eN;jMQ%Uemn? z+*y{(I^RW+e!jqSTE3@g-y)J`&(E0aer6l670CPfi;>VyGRK{I%Vwc}&hVo7rJjE) z<_SF|LyA^fW{egA)d_Q2uHU2)_q0ckKBo*XZ@;Jjq%*P~S3T}>k5F$(bJ1v9)#G-(A3KTzZsq>i zr1qkWE(H)&|BX@q^v)(nowwUOOKZ}PKXK59KDm9pztEl4nd3BrB^By_iSAHfJ`Uk&f z2;;r{evLcv;&SF93kKz2bzB^Nk)9&}5mYwo_RT!I?)%s8i?f45oO`6SP%(DVn749- zGt8b51rbRk(@7@t-y(`kG@Mpojm(1h`VnYn%z_iO%z3faBA&`E3y6{wXl4wkWKB~o z7NIaH_8};VEG+{OGh4WB?5Uu*tQHuG+~gRw7eN4`%cL$2=|ysu4G9o z`0Fv?!l1CZtEN?I0g`m(n<^JpdYEtYc{46?<1Ijq@diT=mQ=mZ7Ix>XBo5gT@c2S^fQ`&trEyD_!Bg<#k{xcUb>mK3a<0$Hn{2XOXkC7*qI08wJPSrYsT&uLFCEP*NW2x zmB)PhzDbhzAp89e+1F?t7V>^-`v8%4i;lXNdveb43*xyQu?9pw`CEBXm5p*cO;<%p zN7(drFm};0wcQQ!5k>xtGV#}M-U0ufeoKKdo^X;(ORWn!e2V~;zeoINR+A{3v&s;Y z(VJS5u%z{pmhh1^i{nPb{`lZ|5H|a~TmX&yJ;FRVKhCO}@xt!Aiv8ckX>=~1w|n~N z_0qq394E+DUegwXBfY+E4DUt=yHcmS|Coz;KUS2sPjN<0;dg^4$=$%ANK9ghyb4Ac*TU;VbRG~0OIgsudrC2%{9LiQ3V zVjIqui}th|woQ96hzcoVFkM=I; z?(Ca|5eIr#w`0)(c?j|QyOz2G zu)h9KEm=`s)4d2(d!X~<&wi7t51nodl*L`@fh{ChHY*C?npNEqYGY51P|u_vQNdn8 z6EuRJHJY3!TL+bSJm3~t+%YGg;=Pj)BqC5Bo0aB84m7(lIAEFCk9@>#>qTKK<~g%N zIxvaNsBD$&^#2g`-SKR{-TNOpG!zv@)hc4I)}EnCZL0QeDPot{yG4mrd&J(G+Qj;_ zR@F#iZ#5IUwwS+sUr(Rs_jmHw`@ZjUu5+F1Iw#%Cq7>?*W@1i}Re*@7j$DrdjP=wR zf~%^8uc*E7<8Z$UQG6~JdA#~7Hnjc&jWBvB?p;!7)7|hatY@m0JcdouB0j=XL!L5b z>-lswb7wlK0MDuDForH+N)-7oa+4TcVMpw zoqE-9#0T*Sm-Dxh%Bpx+u*pQ3{h?a%`=6+HZWG>kspvd;FI1Kh$y9v*c5!aYzNEsy z<>O5I`aw0fk$g^smJ=P?z0OoL3SKI(Y*AHZ_BBRo)Peg->@cHrA;Mmi_x88Q*?!m( zbpKX~{C_vY`MEDm13-sLDJ2wav(K}Cy^Xgw-O!Eq@2tXbMT@~-kIo#iVp_}gZ z2MHr4mD2L@v@i&>A+} zFkJxY+vSu7Cjm45c?yj|+nXaU+95TIy-C762QEMAD2E&Ik`Kq;3A3~sH4gmARPpp4 zat>z&ld9Kp*7e^O4(<(xUo3y&%}HLrj~jZ6lFHP>4RMgKZC}C;VkV1l=f)R!IDb_) zOo`<9!uKQSIsRvPTxISc2A1g0u7AM=`unDXN1u;>1UYl+(TA@H=*ijYx_+eJ5?{P0 zM0w$28*p%S{(^Gq><$3G(s-4oq=aV6Z-j{4pO)d*c%VgMu~&Nh_j@#+UeVi~qbn|3 zHRN8+k}p@%7f8G$eZ?x@EC~%@1ioPo>qlvjqzetwZoCzTo}h{- zkY@E**;^+%1l)XvAjSkIotQI8(s)TrnBaZ7vsOIq}s z`C?|D3UNnsiSm^vUc7{e47WjogS1~jg!ZN{sn($^nN$|p3O}}in2he^M>4%V(aDgN znm*LM_jHy-G}0|4;Fe%KW9a=_=(SJ@Hv3F;?GXcPM*u z-IvrzGu$HTqF?%D;}_lL7aJ?-f!7MAfOVk~CJ`t6H~4tT>*%XF?(}3$sA~f2OmspQ zgN{9Y61Hgzk4t5LT1ajgGT`OK4(k_58QD)>XCk`u|DR?xQR_Y7e)hc9FJRSpR1khL zc#Hw)*yY9#>GIa?jOj|OuH)spYs2qafqbv92pejO-L5P!oj(7CNpkMVG)sFovO{Q@zE>eCuE~-xqt*~{Ie5U_jdhZ^x&f!a5hpnRt>_R6SBI+ zBIdd@L9||ew*maoTcR93xv~GH$uUkx4mt{I5acgnoi!eOPm6q^f`z?rZwqzC)mYs! z#Wl4BGe%wW)OlMI8e6BJNu4C8ez(LDO2XTdq@lVU%S9Ynv@w)=tj2lUX7Pjw)d+w$7TNL$D8(r%mMi;0P0g>0&!%N}3*&$Y!@;YLr*P%YC+c!tp7=nv|&QM<`#F{yIX* z*{_e#+6E1eP%27g#8_v`R$nPqW0M4wsT|ZtIL9Ed>^Bt&B)|Xoaf<}NOqhTntDp1g z{>!h|NFxPtu0*yBu4IbnYlxVb@t1R%wD90VenZH|xH5xNhfR3{BtXa02 zH!4pwN%D^s8|x$a1|R`aW~vobPpvSkvGsjCbYZJNfOV~}p#GVC2s;J6$yE4d33r0} z0q?0;#WvN`*{NVq?gV-5imN~Fjd6DA`kyXFj!(g{iqMXiOO)f)7?Qv?wtz0|nbE6q z%x^-(i;a_~wUH@o4P3aPp1R_=;lr+?CGW#lwE^3N5x3pG7Y<~rb2)1Pd-Esf4Qa8lamjrNBX;^NeR;oH=0@f(Yro260k5D;FHyQ$h!OW79+&Y{53!romg~*crW8m zby1x_tV?+Cm<@%e*=ao(*r}F=_~Wr=8VIxkpoNXu*T&WIb{fe8++&ryl1LS^s}%3s(cXpqUwF!&8W z;l-zSHz7MQRtnHQRO-~iJIiiEu_K3tpo1L0?ptx?_N85tR(f^mCxzL!N6KI&{~P+u zpB&fG-k5bAKHN@C%^`Q}7yXWxm8xV9WM8pU+Y9i%Pr!i1cO-@Pa+XG0x zLsiD3x05Wn1(9~e#c7Eu5a!fyl}n=~?Y3My1v54IZNeI^liO3lz**2-a6(*dg$amVY5~yT~Do!^^Hd+<_h7{Ks3YPjm_+80r6&1 zAeL}~-l#1}B@tb$aQw50b$B5m)M@U1T((mM7q)VitTUNAVfETvbZBnUo^{nBkY!(Y zE)`}p(VjC)opsCUS{+T>(;N(rc&gZJeIOO7e|PoEanbYfCZ^Yy@sy}vuA$zH&m~TV zv=fn$-h2H9-gs8u9tHd*BW7#@Akdh=XRW4XaL)O*9j^J^ua~Oe9GBtogHhR*;YH!{ z#b=xJuQqMpXNV9j%y#Xgr71EmLWoL#{}-n?D42FrTo>`ez7^$4sHs3K!ap81N-8d{MVg6^ntrGO zhjbg!OleE?Q?Fo6LM^t`ogREA5}=nIRM)#PRs_WlAOo6Ef?L9J}stoqud9dmFxULl!H9 z5YAQ-Qhgnscybzm8``&IRRj~w0SK-qp5=s`T&{j>eF-LtVk%{#my`R;5dVAfBjK|q zzhPMD4byG^t!=}C&cRfP77bMbDlg_nXzu1pBcKZhYX2>GVX6$-3X84j$;{a^T&fAA z{%#3wedlEm(edv;{n}f|19`hwOFX+yJ_Y&$z`kKTC6HubuRLJOpMyil$wd{DT93Yb zdBSu{ABm#l&V}twXX@hLgM>P81e9BFz{2db>rMRufX&B4!{9ymBrHn&fR}OmA%TV- zeH$A9a?Lwn>EV=fA8VL^steudvGgeglfr{FV=c%k;zlG?@-#T-!j3uDulO42{ptEY z)OY60XN}6MDy1)a`uB3@YBV_6_RtEQjcmTb#7-ZoH_VAd8L|^-tVp1l`ASXLiRfYV zRa2u<*_+_TxvDTp-Vy0GciE}2yT@Uf!LcjDmFQ?|Z;LC26PcW`H1EUj&rj4dYc~u# z1Af7)oc2L2fk3_(9i-dBeGG{d(=OcFV?UOoSw_r@eNoBhU!_i7O>m;4C;F+=7C=P#vRA)^ai64+%@D^SMj*Q-QTQ6B^+v zIs*jdobSY!1gUWzv7d7a3`h;VhdMyXS*`r1uXf%Q@;9^PZK_LxV2YOU>};o?+T ztyP}9{R+b0*&0~<=U_aRreEUpzkDS<`>7~TLxVcF8@=p$|CMX!$~RF0jUhqbawkGx zo2td1%5n}%0$*t7Y>BZd;(;t%V)C2wTG4c3jcax6xk{1#8%sP5cN>%ya+D7x3>UL~ zs`g^f6r~n%kQA1j)_27x??)wos1;QxhdQ`qz&4gYcl&?Z43E!UWjvryfxy)8t6zDY zry{Gsi}Ot#fjCsq`}okCgMOt&ItzIsvBgm0i{&E>qY9*a?R>AT>e<1`CM6}yg~4Qy z-0Lf3ddgzR@6BaE;>Gow_?}y)MiR=8+dw?^S-k1*xpKl#(M;quf zZ>rB7YLr--KFgM1`r$SHa%+ZZu7xM-p8<@_3(-J2#-Fyc&(W9uC8-z1^FL2Rn+8tW z^?ma=|FBn(rp8wf5icdg4Ny^wJg46LR~(RqUq=3_#6^IlxX09=nnJbCenhwIi2+^d zU|Ifq8z1Kk+Jq`*M0S~~Whq-;l^Kfq^RQos0#Fj~FO)cvB?NqJE{t&2f5jzc`Y?t` z9}AUd2M~$otK8@^Yq-@G?53Vhm3p?j$7fEzYAF4FMNZ=O6Wb@OiGH2TI7O# zBYldbL|fS&Gl+3;) z#x*%v-YhoiMje&jvDH&{9irO5#}7qx#MIyY?r6#uWXa* zm4v3O?SSnma{tL~e-~~+QP{cYR{1X;`Nt3tri03SwHXQjyfK}K%Cxw-zLhc6jK^Y- zGyPp}pI#XKX?~!9=S&=0uEM=9UC4Y0EvvIy8l!`-YO@z+uIMs~)={+KghZ`R6kqWCNeb5dWe(lZ*OC{o8K><)0f&-Q^_%x0cGkrV1~(@cHc7G6EOr`Z zD4A<8M30`7*}j0^%7DPB;&)u^Aj9$a*x0G(orUebujk|2$@q$3fEu;I=gyp&9Lk>A zmPcCQ#*xnoVA%J|6a_082rE(ddFcF_Ua6#mL#hp0gFSPr7^k>%dy{z&)O+M-SaM-? zcN-#ad=TY5Q|irjVD z&j@Tso6czQ=5~|w>*46T9mGEK$b*BTQ?tM7Nj9+hI3@7O~Ue}O!Ahz4ZSh0l30}b2GU}wk#V_sw66LV=|CH~GYR16bs zLHdl-+uXT#{PDRO{)(cbJ$UaI9bJS%!?@7U_`Q|lUTXWBG6xEjFBMVvV7`xKf_zAD ztY_YyR0Vz%3+eGCKVw54?!~6{1Bm4$4WrVIO-*@Vt(J(k29{6~T=nu>y`cj6giea4 z0X6CZYBC856Gb?bZhWL1$Qti8x_n|sH_Qja@{Se?(|k8&9NT@}xXac@m*xQ2)U@(? zi81YR->f4eg}!nZom=zhpzgH6qy&skB?S1HoYkjmZVR?BO{$W1Uu6_4KnoU0?>3l8 zk4@R5TR$UiCul|QNhM%RNeo-BL_6Nd` z1ex<%gW18-k6GZoL5ayX=Tl!{XBno-gJoE8(keGk%Q)26^yIUhuiw{88Az7NCO(`; zcV6B09xA!Q2Q6gTn1>aQi1-WrsEXWJRpl&n%l$2Xgp-H#`V0OrO(Zk!t^bYBMEfEH zRUY8_PjMzv>YeWvQ=MBDM1@wehALoT9egQ|Q3C__I|d3@H0;ZRW4(T;$Bmg}>L2XAEFW**Rc9aSUek70mdOv!)c#+O& z`?G$L;gKmQuW`iI11ZB|y(foxf z5dJKo{<%MALgeg!bdXJfWd^RL+FM^vMV^n=M6@+?6UbEXMGw-kY-T<}=^UNg$ppw6 z5hAz}M%FUvtd4FH7Lf08P#s>cMC1)7v8om4&qs)uS-p&Zi(Fw60-_k=-SK4&!haFi zNGIzs_ws)Sal%%u9dVOjBnlYT9>eE0rGs*s<=$`$Wd+%biQYSSZkm|v zzbH%~1I$;dp8d(3yXotdUtQB6Kd^2$ctniV(|7&Dj}226b18WMQGF%WD+wH_P1WaM zjV>1tg_!Hh?Qi0}HF9HzyMLlggZN8k#T^LYeHMieAn~DimIEEAnq7!e6+5x%af}BO zyqkex}uJq|=Q#P7$nb`)3D@IG!hp)xK%qfiv;|{yyQ_q~9*T>fD zxr8neJ@p8Xay7T~x%x$Yy{yw{$@)vwfoV~=aDa^NX6ibuK`||o{i1RvE)i*&abLCz zYL{fVxZ_ih0xChS@Mth+X@xY!=ma?mF3&$+NjGp2mh>CF_&EFh4S2BCHmBll2_QhEGX(y`IDX{};2I*&X6gyqN_k%x?Xv>hE0gv~lu)M_1P` z)pgG^*>EjPQ&4{q_g90*vP*hjZxzgB9{~|wL1OJtt(#AzH?p@D)^zK`(4_2}f;uK3 zDkm(dGl>$dt_p^N&(D_8uUv^h1jAB!bPOl{1zV)=bGLn|!s7x!*~p&V&POA(E9upa zeX~<;C*JHa~_C z$as!EOzIea;wGU2v824~?q)F~gJ$hsX=gz9v>Of7nnemXsX|T`p@1x|+{6sUm2^(- z-1-Eh?+-~|e?Hi{uNi}ug*NHfT1YfUPVZr8u>M(4-o6sbv2*|f@rw#(6cOln?ZCsF zTegkU)s+!DE)-_KQ+%Cx#OIyOvyv`;(>7gAH|bJ(n9h+iVH_J845k5WKE@4w3cmjz zOdgy8<7O)80;*BD1BNA{Z&X#WQ#GsG(xeO#kP(ubsh5_wpaKa{p=_Wu>H zY%jHBPP(B-hi@)js7_-o{Al>S4?)IfOhxYQQELszLdC(b;9{@Iuj`q|`<=myF?Zc} zzsGvvW@v^j*}I?NDoLa6cP$s2@#Meb3k*u6h zbn7#929ml$MP7`0*#XU&Yb}-Ws4*ixbmRx9ur2md0B)1wLanJ_g2<0aeE2ktZd=mb zRa0f7kaKcd(0!BM#py?1LvnoSj$ zp8Mp>GbErnGz;i`$R|*qhPts7z9s85`N=zw-i+iKf!Sq^ z0?UPLXg-dWa;0@0?oT~kF6Ow>%Kl-lmr21_#7tUey{`$c5urw!{JKXu*imcroQH6B zN3r{4^&vr3I-{GHu+jS>TH-axq5ONeq;E~#MaDJYWhqBq1&hzLq(LIO(Z~c;(0$oO z1+z;DMP%~n(Yw+#RGq$Z>{|N}noel^t(=<-h(gaW_LJ+^q~gy{d)c)nK3H?XEKfmL z`%7r-3lGIMV(h!iq!Da7OVZqCzb!4HBpR0)7@Oo+3?oY_+{{8H`BSf&-gEEj?hk^|L{wCrs?*Mqaf@9A1vT4jGW`P~ zL`3*+#0AH$31K#fAvJ}UdagI}|GoUA&#HCs34x10&tjVbvPqm%{)aE8G|u}w@FxtS z`K*8vfPEux@skiQW|V05n%7gQr*7k92 zfDgLa1FCJg1v;8FE2TOj=`5ky?tC8qp5SJAi1M#ZjtzO??a zolJ~%9e@0=(=ceu~@4N(>D>jfcj{fo(Z}k)%x0X zk$e-|12?Ya`P_9|-DX$rzMTgLL${Lgo=+{D8dqTVeGb#lq&d2fY#fXaJz1kL%A!kA zsVC<(*mm3gPJhwe@1Shf&8w6H!L8uy@H+3n#Azh__MknM(04&j{x3(F!d?*BjX=S_ z*wmQA)YugZ0%)({OxtYrl+}2x>P7JF$&a`%Hti{^@h`5`e@@VwEFGvMW!K&Q+0;?2 z!k&vDMEBni;$R3Z+=i6-K&=j-M@Y}@=LFrE|H1rt4>V+wWbRsnUg>Vm5gx`ht{oOh z0`k~!LkfpJep05(q>7O<3I4^ZWM8#jLrP~NxP|&QKet*@poCTOoVpM-`Q@bLi@6|S zjkksrgDSr~l#D@=z|UKUBg%R+K8W0UcGgOr6(2@odBg&hn?qoko~y#>plR>>{m-H# zkhu?`F`P5>!}z)q`vRqYuR43_Pz7@2nyR5-%bo`VQlfn>^6F2iMpev)nqAhee#^;p zn#m*W_b%6gv%I-ub_#Q8`?dw;>^ZiG;A;521A7tZ)Xc zr-&^H6=F;GcwME)nbnwJyA_zS0Bw<0m&BLfV=ftq=-s^{SD*1~#{?v}-800Z)5^x3 zpz5bB0ElvVc$lv9oPadGUGh-6DQ@B~gUPB5gx{z};)@%&mD;PWgldH;a$3LBg#ER2 zf&8GR_EZ4n=vmJHEk$%)=hQ_-U!N-n=nRJ)cZUy9uDJZlxpZ0d;g*aW;U3=a^wBw; z*bLCUK46G9zt+$5n%f^0{e6hzo+jo9i7Xcv%(b{)v|N8<7tYenp6>)(S5n1ZVZ3@t(1L3P+r~Er>sQL}E>|k>0V*1>`};&KSnTezD4A#ygw)=c)x0BC$CU z*J3~ienXwDKxgH_iToJFIDfL9|v#GeeZ=1bII4@bq@!X?|cH?uE8*M5IsO9Q?a|d-z%= zMRx*?TRck*N>2QVeB#H=Vi+W}H%c>dyp4Q+=zSv#tHyE|tr)2ba&Ca4RfSm@OU3rL z4UR?MkO*%+yRJS7i!*BzmQ$B(shVMo_qUld1{rIC2Vt0Z9fr>7AKvs;XmD5Bq(^_F zj{M;JgC})({HzF6NaLX@O>)3HV#^FfR9EQ?6YN2ScM)}~j)H=2f;kZyuGVZ?&?urxg)JI4VqaKe&3 zE7+Q=y~|nCeXJlH#Mdt>KI{1dnBbibBe9mC_b<(3oNU+Nlc2z~!xA-9WvH%DBNxV5B za~*bh*I|e0Wrs5l6!Vq}V@K}_Ck#)Kr(zx^{6~%#Y!YcCKlYTpMuqp`Gm7i237(1w zvg%uqOHU#?FK&b7;%4kJn>$Z~9v2k&Vee{)5Woky;YyFqzA0xfSgL)<7};~w?=X8Z zPtgUJ`SoZ>xHUN>LR6b;`d(I0r0{Nw5OWG;ykaAXB#F*vsjbg8v1TZiFo`br?a%Cv zPbWnHP9c)nCC2%!yt{OE8^lK~{*|_%5@^o^Rivk)Eqgwcy}?W>e>HxTGrSu6dh4ic zSMIEpq|Y=FT|d4WokWuLEPh_E%U+3VPaMBlw{x^RrX=xCD+{v(4N@;_#3SE@ZR=eF z09>$|@H`tb=s+FMd-rTjo^J`ly^7a5P|%%ZAAT}wXFaj0X9oYF&%(W(>EZ_)NEfvn zJx>{7<^bR_U%%=2t_Hts*Xuo=W6gPtQ`h02Hs{vu_&#%L4Sl|6VE6Bo#muCc8Mw>Z z?oVO869W*{Ob9Ca4^7+B3u0?`>x>9pJ;6!ZE7OV}r5#Iw>wXmPg>NGGpg+54c3TxMThevWhZFAZWC893ai?dxKxfUvks8#I# z6RVwHL`ugVj!2B5(2=ih_j7h!e9Y~dEjm+No|lKkw%i_e_Ol%~O_J~#eEfKE+bHbH zY;pEQDlMFGKU}k2WP@Z>H9iE4b!tMptMCeEE}AERxhhB`quR|-x*abGjV-{B2US$q z5xFO;7c6l0=fXLutt9aMdSe$qR<2$3n!Bp<+bO)N%1Ayghv84!zubzwz)|ZVKIbO| z0e9bP^o{PFDgJWzwc87Iwx82d%=dlfiOS!)TSX~ZL1Jv{tYXUXGfP^()*M-=Z>Z!w`$R0bIr}CGsCvzOvpEvI3#=iT78~xK{L;Wk#|3GJL}e{SRkhhe)_}d zIL9!>eKF{~!#30XkHCPUvRut0Hk*%pa&P}e1v*U@QU>0J;yl{=NbD=0US@%)rp{}h zswMAq`acN=aI#IlaT4kY#UlXmL~{$W8=<1{M#)k8q>sS^cjsW^x>ocC)-qlp26kws z!>lxYlU`C-!GQ<+J6jrxZ51E;Dm`J(2&!ev>5|ZO7-y%d@!b5ABEmQ9I3oC#WtK7v zncUjSDyb?jbTT!t3UcRp6tLJjd#@_nB#KQVvx>xcx@$R(&wC0g))afh>>-<)3}cM} z4;k(k)hxCXLs)T%s2-2qFXmthCl-ewJE@g9Q3XNcl$!d%l`M2qs(_Z(AkdcCr- zH9T?doBAMOQKwNp`-u0c&8G#R9-T%&s|pyiOqc#!K>Y?%&a)*td(mYpp}i(#mFF)i zuZ?xY(eEhLESyLAT&dm_erTgk`?c4X%~SvF)6aiDphIlJm)L6E9^#6K5jl57qQajL zx|)-FR~kDcP4qz*njK<6m+l>c@;t>XZ#}cIpEbu%4@a~;2c}!}f34~V(llhh*2fnv zmjzLf&FFFuRhQy3r9^foyTNS{kEJyWFE1nRm}uA}-10ifNHU8HFw9+s(SSjo(iE+U z?3)M!G#18=ywR$NysCbrvU2_Sz#*t_cGnb>8c1Bmfc_QvDBw_DQ(w@Ry$LYdWen8_ zQMnx3qZ!y#FgDzgbdvo3TJN~v>)5@6=aO2xim3-rYy)E=pq;3o%V(^(%;~S5^cSYj znbY`|l#DgH565&wlPg{dk=){+HfzkXxyG{U~(A@i)rry<939`2zr$ zLCU6HPHxmNbzcVvbPQ|^KBNTR|DY@1|3<<1c43ofYj|j_zUUH{X-^QkQflY6DL=nR z(d@K#xO0PIPVY>+Qzx z4VN+@3Y=|fA+`RRf;l=Y#?g6JHXdM-W@<0+5c}hK2_S8KJD5M zZKU(71m-yz|;W>mnd`HwY3ulK(%K3l`I7NM{ynJ?F~~Ibd1ejqaHT|6-MB2O7gx5IslH> zYID(YhNn7Um zglPE&R~oKuc9r8t?zebn3+N|qeC1FbM^qL#Z0JNO9Nhz~zufXRQ|^!0pPFr`;u{Rt zax%FW?tybIv#=`nw~6%E#AJC&X@E;W*-PJw9^5@P?%WP;{OF-#2P*iBVhDc9c5&#S zf7@e6qM06j(56s_g>rpJmEKPs^VVim$zn!n>eEn9C=UOM1y+)*6`hZnK-Ns#`$awF3NQN@<8}ug1A!y7@b* zlC~bQ8C7uxMQin&RY5H=Vh8=vMdh*5gm1Rz|BNV1L01egCBOJgFwnY$?t^pI#+_Ds zHRU6#yDA&iEsSai{NxB3@zOgF1&MBB{0C@fD9gbRMVC+g#KTnUiIIxK^rKk5FnsAi zKXV_n235XB++6*W!n*Avp!J58_CHnHzak}XE~rgqM%eB<#s5nuFh#4;ONZ#g-)iA% zR>>TVmGm=(?ZNJ?nD;Beuj0L(k>gX4WMCxpsS*YsD~={Qin#z)vVf$?OfxuTuQqYg zdPq~74kg0~EoEmTw`Ha>XihS3vz?1V#z;5Ccj#E*SeIVbH^y9BOt^Mdp#hOs#&f~D zWool%I>+tS6-(A?m(KakXxn@2=I1n5wX@VChXyHo+|IiZ-T_|xs_PxW7yxfw95#aLYNBY^j^*Ymjq<*HiMtW z_HcAx%hck-heH3-VxVKo*Y_v)i*kZuZ-{4>n3U!|G&(74v(Biu+aSEz&n zKGLVRea^?PZ@Di@aZ?CVH#WB zJl5*_P@BmPiCjfCtdHfTM0YyFAV1s|wMWeon+}hhG9b@2W_08cM#Cu`rt%z=XZ#aF zuhy?tj$Qjz9ll_bf;_w|qZ2&2K7D}UDN$z~u*zk8gZ^37hV_3ts zj&Sse7jLIcB63HJI$36L(WrCry=N1XOzNp0s7fVuMDTw`=6|ZwQXQ+>Z!@*vA|t)$ zWG0c&1m%>^VGJo>wT=7h)dht%CwiJH9{Hu?XJSZo*So*8-4%P!Bf?3_fVVq`YplD8 znyZ$6_J0F}KY1uBMKK)aUXB;FsK4gecd3UD-L>&}J?Lk0O7W~VGf+CP(CsjUR80vw zIJv;v4u!hRwRbvKEKWD63$|F9@2O$lwe_5L9829KTg!3Jp3;dotwAGrbFAO4*AuAy zBiU-92o}copp()$ zAFx4N96k)FrTFsABQy0Ts;ymTO8JI5nk|FyGRr98t!#NP1+j+CPk)N5 zvYQt2>1E=SUq_LXmkPlP_w*ir{8@(OrDc#-myS23c&nTFaWQuP9o?(mFn$=Gy_K~Q zI=DJk$5`cyJ-$RqU4N1YgNpUfb#3bBMkp9W~I=Gr^~3=)cVBw}*wF zA|)~S@;b@ghPAmGTgeYml~2qr`QXuOF*0=zVAn~xe9&*{@0B9`Ru4E(*Ee!GJsNJ9lff?jvTlb?ueE)c?O07K>Ye->)@hAd@;)a1~~ybr=1 zPx4EqEsaW2jy`()D;lOMLOT|A%dGRuYR-MZ?!5*00n$27RNk8NCWV7^=b$DyDWE~2 zv}ubHA6+kB5ZJ16A@Be zqQ2?37{tyXo2ma?U6#q{40?P#eIo;^h+myC!2?Z)e-g(20!_N_d8;iV2J)#$23I5D zUKK|Soq1Lg|Ml9q`=N&2m!?L~m$oleF@hetdhlRW?=yoze5F%|QUlI`Ro z;r|B$XMha)o=kboM#n{h87ef}kbQ+8(mY~27dWUJ*=Uo7Xs0h9ScfYMCmzmOJCE}% zjNo1Hi5wd30{puwka#(&!L!Eew?KrR2uEBa!ql^7D@8XJk=I*FqQ#KoO;$>0JVjvn zyF39cx4Tq_yyjD81`rULV(8=cKxmbRGK}Ma&G#g)y4x_-JE#0_S z_1IGW$rd;e27{T|PI+G141MSDE!xJC&;YEtI{swbPD#v0ukmols(Jj6O zJSQ?=*E%ti3<8>*bLC=bu7Tef4m$deX~+C*rk3ZjT5S`NCML4pZW}&9K|8%m;p;s@ zmUgfm{?QsW~C;1bWVbQL%8OpjdVOW^fCh_=R zZ{q{@)Uy7QVxPp}lw-V6YF$&>OE#RRurglUr`ffcKmg`ja@6a{iAk(zd!Bo@D2meB!EFr8+7wg%w zWa~_)2~qn!*8O$hB&4aN&L}R%dW~XGQ5(nm;O6Az58OuFsi|{9=A_nL*ne$ChJ3H$ z9rvi!ML7_;8RH)IBQWaS&wG)e2B#qNhORfF2I~j@919;MZfj;8{dVp2Xpy($SL>6eD1q;xydv3qWF37O6guuV=D-ayj#Z;yCC8<_>#7-_oqPBnp(-<{20b-Ww@ z!@DxWbj#JL5rZ@tJu4yqvb(lc&zt2})q3K(?Vj!3?jQ1@e)f<0RL%?j9H$G3WY*2C z>s80D;(?7vOK%t%Uk}CycPc(({;&SsPMSJ(&?|Uj#S#QWKI9Fd_TM<15w4u%UA!lg z1H(jmh+cJg43u4w7Gx?MPvmkG2qpd$rOTX4xA;&wXIV9?pCq4cX3m>TRanqCogjXe zV?i%Da!O(_G-I1fkkF6SsLu{S6@;N(4H*XY4kMg*)-}^ytGlV6MO0+ z<9QMKyNof@6#Z#JC-YmDIJq7fe4b4}Nm9lm9ETg5n}3XmG>r>eFii&X=?R4_?5$KFZ`X|mL=GVk-UR2 z=Y<6S$<7qJ00UKUhH*7*CMOVsA?y#mB;S$XICcW{u=>E z-mqPz0D-unu5T;Tg-~K;Ikk`40d$}SQc&T4h0&%4<*|5zXtTvK0r~sF&5RPwr=}%r zn^3}I%;6Gc$Z7tgJ(x7l=NeSGV3>Q>s_lbSPlTb%%#6~M7|4@Fjr|6Y_#yi-&tVKm9TIIaTh zqu)D;n-}CLbi&jZ2|(EU^{%0t5^1fRFh_}IxU$NY*^ozkd8Pwwl@n0K5#LJ&Z~PcE zHdqd4DN?HqbTbkGQ~S=k57sSy;U|c~%j%lBC^XibOq5 z)x3JZ!sA#rpNrqh=pl8(M-Uco=Y3q-rcy2MhS|uWA=r#)95YXh{gK<|v$s`f(z6%u--YBUxZ_l? z^6P^|-!BVF95h~MJU@#PqfBNf-*nt2gs*ou5?|<=R^x-^x|%IsgX(k)|Jrk{XOivJ zyY~4s{eGa8n)bJg^xuaPEpMN1Iu_^>1VD_kli!UbzFBlP9h)sKY}7D!`CPe}^g$T- z7sdpeC~4`cJ?vY5V9%V#%>vl92IV;cY~8x6!BGjQP9HY}(<|5DYCZa+5lmQfT6x*; zZ;`JYcR1(ESvK2Ik~vN=%lyTNg#PcIehux;n#FG;>+@=I@y;xGXsjf>myIpYDei82 z%R{8?pky6#cEZWUQAPZ$H@?6b^1^;aPd_1o{Sbjx82x`-U1wNR>DIMtWIzc5Dj>xK zq)YEX5v7BT-Yh_ZbO=3w1r(%rA%xJ8UJ`m!dXa>H)KH{`BE5$Cow+k}?>yfx_zCBn zcfV_|z1G?W!9|2mVGi5WF3VuXHcnE~N8{6vq)T(gD0Yzmb%pBE0zx8HZO}2Z%3M}Tz@pQpIjr~ zai})>I3zd8%@@KZ6-NZv9uYC;3EUqGh}l`|H^1(V(R-edkD8Bf57%CZcstHv3wk58 z6Zz$Dro)8u(5LfFUzMnQ=2n#H*{~K%bnImi#by1=YVrF^-GeKhw(=djhh^wv$-WCtrD2UN|ee zvyvLnl@iO*Wkuc>04*z*TE7m+fTK-YaQx2`QuXcHdkqc8#r@_P1~*YX1GR8|%b!{Ecz`#{p;jRpkplR`GCJ#Mx;(nZ6Qzl5t%OPq(<^ki+yRAR6-t|Iwwg5HEtBhFepQ0n`|_1Z+3*I_S*M1FJX_yR!n>9T zcc~t_b{Jyld4<+Ti`xMAc`+i&_R_EfI*)fbNL93AOB*f-(wbL8sAIGR&7%v|=F3(4 zMxuN8$o0Q$HLNF`zK0=R>2}QbwtxP{|G}a>NhPsZ!OWiA86_+KDzy;)KvNcW0<&3U ze}!O=S$Hp4G1F{{6QQ|)g&q5fe!hPAYN?%h1y5$ct%#{F)VT|2pynX%H?D_$!S9|Eox z_M==`a!h-cvzWsf9Ln%cUH~U3iv6fIr;f|v&ip{WJ@Gjqx))f{wV!JC=H`ICz{drL z?Rk{yi4%P{4~Ge2tS|jS(c1N5f`I3(?YTy@PF`&F7i2_k#!{$;0}RJgyF}|(V_259 zq>}7E@T`Y5*?*2`kDK%C)5tM5{GB56>Ud+Xc7gGG)99qzqL)JXO=xD`F*ZPfc5J{a zpf9R>gTCCw7XE730?Cx8sU5FdSgJWPT&cL8v_|Q$Ol#27)jq}Znut{N+(n8Ry*Wva zk)!5II0|4tcn91Mx@oczHl=&6`@aQPU*=uC|K$vF>ICvinG* zER;&M4=#_J2sv~CXDe?7tiMt38P57TO{>R?U4H%Zmi1B7#aFfS`ZJxOKj6ulE(87{ z5^gvS2D>Wrn(aTRwjwx5YVCw)mVh6=0Or?#m@d+WSRLsX;upFbR6z@gdV*H z>)?`W({?K|csLxC_W7bopZEc4QBFnb?{Ngj6s8iz1tzi!22qB!E9)aS1zYTeM^|h) z%H{k<};e{J+$Uua=^0w!8Mi;@koI4K!V2L6$t<}4|#v1 z1HlzRY&FfuSw3_gfvyWo^I@qrkvgLDqRBj&!=e!p<v0gIY-zgE;;E62YPPFN zkt~~!4+BZ{Z}HmWo5B+x70nxbtCM#>hqL>)d7o4bt*_Vy@1B)u>CztJ`Ax<+%7KMz z@~_L2CgrA3TVeIj{FhGups!3>21a@ho$t)E@%k zH)$LsHMmpbBYO_5x+Z+N>>5J+vtpc$)?iGLMbPsDp-26*=!iz%`u81_J_>iEPX1h)L?_6QAAgbU6=mR zqu)S;a0w*Iu7iSYu{7N9WT?i1ApaQuPn~)UTehk`L`X`B4mHW8%86NND&f>nXd6|t zEU>nGdlF=e&C`=3)iBpzPY)3%6Vam?+fyoyjP0@GOkJp482!nFkZOE^OnNTKot7TE z-zsp{8;)0l)jU>))r5r4iZ$HQyQWpzY6NbM{+c-Ng){ccmv=K?`XT0%IcisG!-ut_ z@7JGe8F5cWo?iaaZmOF`Jzq0^2z~7{-X^H7S#*cjZZXWv-C=86O*x(AFS2%2X+HZ` z-toe&s)>=pkg*f*IO%Xf&Z!mKtgt6&b8+cq-fe;g#DAN7Za9@q1T^u2wJ^LorR>P4 zCS`Wq@Lf2JEspMHr9zj#*9W~0r>qR?Bw7okkFxsz%WR5)jZTQXHQC1t#R05XMumW8oA z6)b}fJL~64$~i$=Ye-=1ES&wgN;7QV<-<>BXy&FN@4rqov&z>2KLm5Pch{_z~pl?*iFj~e+>4KJH0xpdh_^;nZb zeclb!TjctWhYv1+6MGoCO-3`-5D{3*nzVk*0zf(TTN_elE+UrVh?YHtE4&B4jR??0 z{7+{(<5yHw^-k(rg6dBpw4?8Ldmhi# z-O?ayiuS@8w%)=zk|j@i5g)y2Elc7(u7A`7U8_^23(~&CUXT{O^k?5E{=zn3fi#sM z_(&17WDFhTM7zCLFv10R{!M#C8>`E`6GJf1KMH6{y7Ls8_P7-FzM;m&fSYdMj$(D| zL_&(5qg@%bJ-43s5Fa^ea37V`OqcA6}-i0_K)S>{tDc*fM)m2TNx=1BwD9iWpU>~)bH0$=Ae zWEr|tDJWASCp>I=Yi3h9DZ3!|0W)!HIocn$EG4EY&yGX=VLL^|RUf*~eH2Lcv!wpo zW;vjn?o9SrOAyA!Cs`40{O>5fMR(o=!eL-I{(#`QI9IzeYwgD_R8})HuXs&W#E0|h z(LJSF>0y+6N;tf1*c8~PDt;v2K~SUrt(p=}lW^lsnBWZs7RPtjm)svVlN$*GSt-6B zQQGzfZetfzp9076aWaZ7b`uA8*z}q3jde0^6sxUWly5W=pEPkRrN$q3|30KTk(X)6 z+v|8}CWfKcOVleUTn@sM({djENK>ZZE9j-Z5tAwc z3KEQ~U>cvzFuGAzZB}fc{Cr7kU0OnWcyyw0{E|cT?=tSx^N`fGbU_{8#AvV)lws#jGRsM;r&gwW(<-f1<|xug?ShT~KjB$yuW%TGvuhLnRz=oz4Z6c_}4mAm;8p z?&8#c?fA@t$IKyb_E(bysQY1Bu>agV#f#j*kFAoK&?Zrid=*`JTBiXK=WA-xPU2@Q zsd3|mfngnrE`GGZuQmzg+rk<&i;JJOe5|9^8gU(s(nRY=B+&R?G)qh_j;}g*0L)cNpBXXkGx*=Bqi(`Sw;T1!2FVe^O#KBn3C`#cc~RTMFUy7 zTXzS!k2kCfUPbqW&Sgq%4n_;GO57PnwOZj4C)a8E&2aV0T9p3JLT<*8R2v$}s(dTA z2mCd$zny_kGbacU?XS0EL*KjHSx(XD39OI1OHk()Mu&ujEy?yc%MYG&9dNSBM{RH? zxmqmzj`9c#Gj<-t(UKt_^kY#2PCz}*(}dMT>BAZb#troAxocFef59fCs*?Fxy?$OT z3{kfF`5ajG>qw7!qD{Af(&~*J233UKvp2$x5PY>swxRiTs5mKCu+|cStYsJGyzXvj z7V*vuy6avMTJ+?#s4Ot^4xPctlU~mCxvpqf5}&|Uxt>$nH#VrDm6|8ln+;(NSv2M_ zxsO`r>tC`t{y^O-O*?mH|9scQsP4R$?e#L?3gXV_d%8A&L=r5vmO?T%T>p2uzP0(b zdu7ZwXnp;I<7f?WvXbXuaygu5a{1!a4&3P=9msVQT8$6@cg!d(OWr5JKEK5TcHP9i zSa=VFJVUd$g%D{{3|%Zc2Ms-tA_@BCK01ar0e+C6I+p(jI}z$!lbl#uXtMKkf?3z- zu=1>*-elY9C`-Rv7iuvDLQ$t;^Gj5z8)Y<{p=}4DM}_u#1 zn=^r4M)Bb9F@OgBW_)ajo8|DWIng_U3j!T;s35rhf^E)fJ^HUv^wg=@Cr0l*i)XyV`r~9k1?Q6kY^`1vJbGrj!ZO_Kk@j-lzrM|# zvHBvCWVe*W2bA8U40&drxx3E!E_u~Gh6 zOXbS=S~URycf;(t1_4oSL5N7MJA|V3=!1n3TCuz;iVz376t5#+qZK#Y-|EvpXpSRL zX^-Q2WTQFg3;LZ$MV8x= z0wtg2%T(X-jBd=!{oVd*SeBNBn`(Qr0{2?TYh|8+COKea_zsr%yHxfbxQC(YwHJ1F z2JPk@DKhS+Zb&w@%CE3fgfCbWV6Vqpb;6%e4+CX7;a2fPVOs3SK6{%N(ZIJef|#_= z*GD)=M;#E362~0Vh(j(gIvyxA%Z_Llxq?t&E8t1Tjx!(pG@m|SlctXegqZ*?5=XyA z1-muhR>!;*9$7q}0&x zra~@bWQf&Si4oQyN^x}pZ2)2%@F-UaC0i5U!0;0dm)=N~8?$9Ko!^{A8l+~p+)0}D z3f-5k?M{pj>6+WGq?|u?WqpLM@}JX7l;Zeu?L|}k53vWm3EubW(QysFTm=piZjICZ z3x$G1%8)b~APnnHuiJEhqPBL)O2@&IiGe7f+e~vUk#W8@WY~e2l7wdTL0pnE#DGNW z&qRFJo`-!9r7(iPBY-Ki^m7{AnzYwd}UE88YKid8`kFtab;v zdQ)axKpVr&7@?!()26_eRfAfAB(5q%!lUeOqsQ%iix5RweR?@XOTORv^bM#3=!8a{TmKG5w6DZd^CV z{?4*D;oLfVlv;i`tFhj?Up5`yJB8`kWS(?$@O@H&)4li1f0;P4;Xbv|;ak}N+!x2U z0?F8x#7w_wp9vDpzDJkG&s#MGnn=-u(OeJRt5hW?4qe{eCn|mbs|Y~j42Fdrxcx3kSmhh7p^9^CYJoP#&6|9-hG+A)i7VVomG zT7M)hJyARlNSAvrJETLdxsLYsZ(5-Q&$~gAvnScIYZ`p+8*f14HO&ldOugdo^B^mK zCAl)@n6L}iQlCe)9P$p&I(yNN@jQE4wNQjrCrhwQ+W4~;8!ENo-~Xf}3w{%Teo?Se zS8gL5{!~=7?rNsfX*u4h{Z(zSsyGunHfCyP-o=xazB$-zqo{*KvFgB{;%T94tb8q8X%+1u}y7Ur4b zocu`kaJPrdzyGW_QsPbqPyc&EIo?iXcN{$GTm0U)7O3Jd=G%OHoGSg`rh{kvfx+S8 z=7HU&FE8KVPyM0ll!edG3Dw4c@klaYc7SXRJclHs|6BmHgz2HYq7usY{{U8j2&GZw zO+E*fL(*xV3*pZ*$GyX>9rAtG^xdHPvpja$)QdsZ13|{N^_HVfmba2`3yxU_yD*gu z^XdAjGTpsOL>fHFENOaGrf1_C+m%nNr5+#vA^iOn-NRxT%e>dO07A5K2XB zZOb4&XCPhXx)8kUtZMT##Aql^J5T6omP9Ley+wH$!ovMQn|a;mkn*3A6?rwu!os&7 z#Q|jbAeZbWclz`+dRMAz$UZh2oYgTS8r3bU=PA$*xSX_LI&4r+qNw&@cR~k6{n5s7 z=~0T8^?1=$qMYW~io4yAP?stUw++Si-2_=A-`TcwOEwRNIW9sMlHT@UX{U^-NUO>s{%MpU_R`%&|MG88q)Ub{lw8r27@FZf$8PD^ zA)f#b?u!#nP*d`Q8?XZ~wPW;3JgGqQiT7EW^!OKJrhS7CVdzTtU9-jg<(nGj4kGdC z7c?vtUpE}wc(irDb$>N+ZdU%vMR)N- z66wLpq4cblIvkpza~Y{pv-7B-X8^EB(YPrW<+3F1sc9Gq2iG&~3SWh!{%02Y&F;N# z8c}AtC$bVL=zrAbK1Es52~$jLxFUQ@p02{UoM!(y^8V@@)1G*xkOA!$Pex58hH`tv z4FyK6q23=GS4q^`%-u&o&T+zy43j>RlSL+JI$%_$0bgXpl}=}Om;nGY^OPBzU~)CLEmRDg14qq4<^LlW!wT^ z?_+3^tp*BKpQ}AEj70)o8R6+Y@`#rLwH7luhg#1sJV_~>7~DBABl{}8KAE{-r%jxj zA6Fj??xZvrw9dv7^(EF{j4(UseYpJhfb!I-Zx@^=HRkZ<>55kdy0%$b>+pSkj93i> zwOr^7ix=340rkPk>-_z`-XqPYRk0#eqr5EbW4Bk=`wCXPO?gOP?R;`g-^V(R&58xS zLzU;BWM`X2%RChy{+$SbX+Co)ljwfKAZwO~H5sm-A?dEAzOKK71u$Q4C$pmsNctLQF=InC_ zc6A)&@C=;zl@{1qfk$PGDL(nbN#rTuEft()E$$xbF>eqoWPW*l#V`1CdioX~kQYXg z%p$(jztCjS>7WQ&iYlG#2bie}4(xh%%8Xix%u$LNf7;mYHu(eLvAk4Pg}amQM>4W& zUsQ0HsoU8W_J0kpsHH-*Ee4H^&DQcSr4AxiJa7x1ToOOx`?%`sMTXXY%UJkz2l2f% z4pq>pl^ycX%bEABG~ESLE(mA2X(Oc_6Fe_pt8VE)eEdC<)H#`m7`L>K z!9uJ;-AIZ%hM=*kLBvw7V+XQB*;V#LA+)R&O3?Mz%^jNZ|>+9R;6nS=VB zibQX-yoDR#I%!2?Ye_5YY75aJPNo?~gRNSzL=MHO85cS;p&Lo^CQi;HU*5WD$xiH8 zZ05GBYu0dQcBwvk_Nlw+{la`6FikOki%txD1pbTRGZE_qLTWyZ><=miZwvR&4xGdP z`t{IbA@MD@krDIOft|(~qcs&E*e{Y405YD zGC3w}K09pqX21bn-HJSSQ_e+ZjP)4?SQEgeupm&L&ELCWR6{X6RzRVef-VF-K3ac{ z9|f5@JR8K>ZtYzA-iD1;g&hpIOXlg`43(y`K8k$6Hp=?j>lq0o$P;7C?EUIcN-|P5 z)$>eZ$KITA35N8aNlP$J1J{9L`B4pnDuj2f+WaS6V z@wF^s(y}vMBNC_aYCj1ofL9ir_aY4VSz~&(2g>yaqk5}PY{c;pywxdm8p8?|w z=u7xcxRJT;pr7SJ1~g44NrGE-y1X48F(cVUc`2%(ax-Tzb-GE9QYM z@olJ26mHj2RmGZYDvSSpAWsPd?5Ep|Ly&rB{u~vT-NTpBc#-4Y9^Z*m)QvC7rAc(u zRtmP|f2eC3-ShJ(DKO~GSdXZ7&ipG(5BY(ew{!0ktxw!JA7;uLY<(y88i$uZDh(Z& zTxknB0u*W)$in?4CTtUAvtB6XHqCX#7iDTsWmnIu!TZ;oPP3V;caNit@_qtIG`#b_ zHRi?;GCJ~7&$zj2o3Oj>YdN_^eM6g%o^lzPb(;&0?KiRz@;WHHc~n)=H(dK@@i}9p z%X|y(;*pQHx5P^=Wd{JjkJQkFeBEkaf`|tA%-y2`z9*vsmOU52oK6|F$7Vx zbVQ3-jGe3VoAH92nAi|AZhBKZe>GkiNf3UVman|UY1}RK`fn-Jw~x*eAVlNH`6I4c z>8nx`(%h*GAjG@z^IDI=BY;s5n%WR_Zp-H;M`6;Apc>*`0Yl>ZuN1A#Bb@64{)~)t z8F@|xL}eYc1aEJv0(#SRir%CU`5@;v?NaADpNm9n?d##>z-C+vcs$`t=H@Jbr>&RX z+>S)<#E$P|m_cx^5qm$mPh*{q*M4Pa1&zDqBh&2knf=_ukdnLp8^ zI51D?UmVQf$2S#A>xv1}vSQ&UCE?Pv$3I6F>IRPnM%8-76DOsOKUO{*>uEi0?la-H zfJ_R5uIKY4)|74isfw9(AAIbtg6~DwB0?~`)(tMMh-9#G}HCl#Q+ z)^ABB_^*7e=+gcV*k~M~$Fvs4gq1Ts!s4ELRJc@3+g)iodOpWivCAJfwi1bi$psTd z0_B3`xS4o>uo!QvHi$#S~*m9@M!is?prh=+AA|v!}{gg%>o4n z$()D8XX$6S`{3m528>C&F>JrBvg16y@R|wU0)xH z-F;=WW3&_@>eVp6Bg4?E|2f@+dt<=bV!5rgGzgxC7~F(ARi*P_2S>Lx^JS?&N@NRc z0M%ZLGH}2C_injcGD|aEpzTEZUW36w%9H`B^}XWQn$8ZBeQ0g}N?hBCMJRAov>OW} zBLnU6KOvjUc0L-~vVNAJD$S+s<>z5OpykZuixbOw%gGh95T=H^#b@j*Q5V$gOHpPY zI~zQw%5WD1F^*?j(K@;^(wee-uB)>|@M8vh;iTk2p=ze-_-MO!~8Y zRU?3vH2WE0+2_R9l}#4a=I4T_(o5<}42D4nh8*67kb1;>6BnK6fe(%~809G0(QnBi zdePm;OfDc2w+^QUE0O2K28@y8nfhzEsK1TLK=j)fQ5KpOP?G1qQ+wC&FrfR<_U9xZ z=t2>lq<8bkql)*_D6C;R6dtSY_#(%@YV5Ndx`O+7U-^tIFZL5kF;Qh@|IB{9(pDqQ4K=oi(#{4b$JhGVNqU+bgFh!w%e3^V=-)Pj7MCX>re3?8x-ul&?V zuh!&lw1s`NreyqGTyPv^YiJlus=hng$;r{cal@2PN*8ukj_|$X4>PkT&ItyRS92gK z!Q2>~TLQakF@oPwvNg$A$7MrUx1r?yx|8AM7CkF>d~%o&{|$f?*e{2q2K(LT_&e@G zPigP=Vl_igv#$Gnh8X|jbPwhN_xpY5apCjPJ<2hGOft{ae^^1&co)54F|CYuNTJ}K zs?LlX9ZO@%X@;4-&v=-&V1Mw-FaM@(+AKscTv>V2Se1Qwy2bC!x#{g07uU2(|A(Dou0lt7=sWR6~QPCgNL_03I*g+ zUzjbJR4yf4?V$K{c^1l1lLqw})nSzUv0o;Z`&Ecl9E#>&Z^8m+!GpB4SzK2nOxK`n z3E;6aUzA_f)lB!-YyzuP^Xe#NqSfg^adCkIEoL>T%WW@?tGff-Cr@6|A#G-P(j0&| z+HbL~&xx0;2+b>l_dl&o9l<3)M^7~5g)#n2N#$ZuJ<0@SJm|H$(Ia)uCDWej z7=mM~_eYyWx5?N}J>j7ucg_`Jj%+Q&mz9WFmYTVJNpk2+%6ITeqesI!+q?N7L z?H|1uec@{oRD>Q^x)nSfTv>KEjM)r&(?i*aOv*3+JPty=(Z!wFeflbh4Tg>EKG~uqq(v7sTd-f$^SrrUU^W{4=oELHrgwhI}x${b#W$t1lk~RK-1Dl5(U4KFAjb&Wd64&Zv;^6xgTP z5xT%A6vsPh8~fvi&&AjrK6E;MS!#37NGBtyVV>dlSnvsxq%0@Khb+e;#pk>sGb3I7 zi>O!Q)5a2RLz)J{V;lpfQjcTTpBqht)FiU6HNaZDZxQlHl`F4a;3K%VyUV3#V=Ba@tO$L&rw8-x#Ob z9!H6klgfEg(e)lMn@ zKacm}n{iMm=F^=gbq;2JQI%<2QKW(BnMzS8?d?ZhQ z#9LMOLvOmb=1b7DZ(B?Ta%1nx7nVsGT95ZeVCLOb9>LIhp!yrZ`#}p@7P?Uh24%Kp z?o$cvi(cG?HX8{6l=7c8FZyQ0F8-^?%)kek+iU7Fl~qLQOLU(yl(F~`uMhst;z?4I zfCDnCu(-fDT?uC5;T6*>Oj+%eg4VMRJ&uu&*PnRFF#JrBmNJ8#ZHN2BB)v70T*&81 z+T>2`+MluQYe(k`@$)Vxg%I|ht6S*?AqF8^xr(XPya3kuOFi{g!lkY{W&E2_r4JDP8U)L-TMGYL(U?(qY^8^}drDvkLmw`-Q0Dl^tAAWxafk&i zrm=t6Qa@fks4bNlvF{FoPw7Fh;hz=S95fu2(0nQXd@ulx`36S4&eMjXzs}8npp&s$ z@mh%%PS2)Oo7c%7c3nz*(-NQYG>Os*^#S*S9ox(zI zu$L~@;4dlvi>s?Uz^~>G{&TM-RcNuiw2z*Ong^0<^!H_0U0Ue3kd!kjhlv)?%C>g_~RrputS5C zbq+b6u~~{WDUj1xIu>IUcgBY`1Km;~GM7vOSK*~;>X3ef&b*5IgNSO{2j0Rkd! zJ)kD#{6%4dZ)koXegz;L(kl(|EQ#%!kGiBYG6q|iovQdD8-^a*yIjUspAKW*bXd`h z?B{Z$*&!kPL~$D@KXkKZ{DD=r%f>gB&|F4x&5|~cHs8gfGbi0!FN{Ky zY0${IbJLL1?H>qoWhz1IlnjpF_ZG7ETh1p-Vz@Ak~3wGAs9&k34?<*>7m(M>MR-=?gcYkQH z@;x(<7q^+lJ0eooX@Rsd-}($D>5tL+09u`;{f~d`G__ZVJ173cjTxWf2;P6_;lyEY z#htp_y|f8xrbnC(2XOqf<*JygLh<8KeMLKC+UxSh(t{(j8e z{=;?)3&jn8m%J!V*r(TM_-@*4-+i%4KWA`tA4A97ZUc{-!c7T&;KE~NHfJRIMmQ2; zNWCi;rgPGqqv+G@MeKNC=ifLOFYu)j3$v&!Qh_5N|ENrva zXLy&38z2xGs+yRp{1x7!E>Nq+H#|N0PL>|+tA1$Ft zIB?%XhF*p?^Pu);&e-j4fX5~2_tUaLQ$cOYTcr~o1G}{U+=vl0+bZ47I?*zIwI;x- zl=c4*B^o+PG#hRYp2^*PXb2dil+(l#akf_d=QVgXdFEQj6t>TA?)>~x9ttM83$%J|^k;!!Os*1&3=mtxBp>d>Groaaj1=5tHHy`&c#V|KBt(~_miMnZf?u}E@VvyvOD zAM3$w?*gD5#sxSKKm6Y|dI(jC7_f8+`cfTjT(es@umWilnJCe7n2G^D#Db)t`D*f> zX)wnj=AEkxd3MuoZr=^YFnf+6_Rs2v6HG{owJqmP)Q_>aHLGdwuBTgA2>lw&ooUL@ zTZkc9o07cvyoQzW%V@Ie>PG?@r1ej;W`9Q?ShQ>44c<>TIQdyS9v4XXt3~E=5S0!6 zB-2_AtVUKSDz1^cbuc1=<$)$ES}gCqEEbY&3193O*x?;c%11vIfufu4Z*LxqGgw(U z59C6kiK`*6<;dZ6PZTu;6icoI53T&DJN}xCLXAV)VCyHNIg0D`vzq6z-4i^rUr+Y- zoYV<&bN+|w^||wby3lk7(EuBXz^Fxu4L5lP{V@Rld#wmF08qWi*{oRhZ#ydR>HvE+fZhi^?r)d;Z(JKx79CHv3Dq7_LoX|=kMG08!O_tE?_RDj_|A% zPWBx7O?NU)(_jla5~^;ECcy=&{De46A>sz?gIcf;*W2NC4ks%Y9e8XWk7u}u!{BL# zI()08OWg^TKQLc`6gT5u&tZ!#NE>y2VO~p1*}(*yc#r2V%RnW@#Ew~<*5hd33)qI; z2n0Mtg#d+7m3i`eeRfjYMFlH6Pl|_wxzBe*xxll;&+Cw6*EyboKbeF<#mGYUl|aI* zxz8th2znyH?FIxJd;S|k(<;wmx? z*edNnV8{_MW=29w(w$amzrAP&0a*XKCYgpCYp#A$eAL`Inbi)bll`CKyBJm(=v&+% zG8wn;>y<{uSjy-~;#iUXw!Rc;n3i_eVeVLc^fL~wv1(uYMAQY52x44aCkQc^;qof! zb#DTgU;aviN$l=<#d4#^DI~(QRWklC8Vs7?eN>mR|K{;HSz~m$zbYEX#dF8H!oErs8A&=AO(j(3&bSy( zCAj=QtZj07)zIzWx7V+!gcDFv_)}=e%11eJ`e?6}jm^}$;$d~(H*z6GLwVwUqO0XS z*mPMLpM4sNZs)kDVK?nwX<46V+j4EZmNc`LI6TfEzFEdNEUjY!P+p2glMHj_(}L!g z#%b$v)aJK=4Dy1tD_8#WjEd=x1FesOoPhAEsk=U6JNwrA;njKSN+-II)N~HycY6ev z1D6qHn$i89A|8^oP&*Evu@(%1E6f};J8InmqzH~)lj{(C63S|kak{U}bysJ+Wl*nW z#|mk)bkXTImSIUq$msj@+6S*MZ)>{Q!IVSS#T}*<;zq}weuEmrRV!$U-B1+ClSjlv zAMi*;;y`l5;jO?NtVIE09Bi*zl$x%Pj zTUuehm)SJZh9qP#QkyJHY@8BlCO`B4i#lQo@ zi?f@Hb=sJJ{JW=4*v@5)0;brXCP|Ufm3Mav%Zo!k`3Q?LsD|gnm(5+ zfaV1wQNFFCf#>o2mB%}1qvrJw9`r|sqx-Gnu^TsKqW3ek4kH<-flQR6IDr)XxTJbu zO*&}))#%OBM}W-FTXJ#~d9Cf?`OWI)jiGhvWj^Ds584s=LdM$0nOiwteGk={8GFfb zvxZ7uy3q_)+e`&EPnFK-vi|3^{EBBP-bCdC@Z^m(xV3 zIP<0u@V0!JLEVbdchwQc_dh^VJ*P~WejNNr4yj-MeQ+|Atu}AIpZ{v#s@;#Zd*ABI z#}l8DMyU7+wJOkmry_?{w0yvU??3<5DTB$?m^u@Fy{4`TuB)%;Nsv3L%IxyAZ9K~O zJ?oP|`-V$xY=LOti5%W#ofz27>*!X@;fmF2w#@p%`r ztYzwaxx@Ql5Am_2xcyMaIn+-bc7l zhfP$_oM{PGfY9vg2k)YXe%*W&!Zz6m==e+B%=JnQ+jhvm01c!lnCNNz%r*%As=tF` z>37>S+g8}cG>Ui*+ynjAQn?W9KCE13qa@3Jqg&BH8bIVxBrmR;kdCMxNhIiK=Zo%p zhmZ14CfBsqav|aqDSJ{9e|-vEdYkF9ld16h$zO`%?6S7zK<=dh14+1fkG*LfaNk~9 zugNiFWGe&^z|WlkMlHoS#^%}GSsj6kEnwpBgVj2YwPxsw3RNNjgVOV8`Z2xBTV;DA zqNFYv<-6G`zE<_pP|q^(tmV014)igJk{uh0T%5FTm1a+N%(<0{GqZHr=AHJ_z)X+q@JnD;<=Au(aa9K6*f{)5(r`ftDrE+X= z6%`$DvD-|^erYU5-V5@$+PM19YFC0wZI!$X37xXzQIS?M3rAOUl8aqSY_r9lKo&Fw z$m^Qd0RAEQmZ1CLpF~6K~o%A4UPELTKtyS`}Whgg#-p-dh`1XmA;`25Xabwej zsgj%;PMefJea@F@J<0Mr;7OZr$4}Dqy<1*wqx*99@K}L(J-X~xeh5!~ao6#7g*14V z0=5)CrLQ)NP4;pJC>2X62E)*;pm&*}ZS#$;MpW^Kt$xqwUe73!P%N8J2Lc`8Rxod% zEAdGXR0m`uvX{+PKtZNk`VZ;i_blk5!T+I?-a-xR&LkShf&>j*0r@9~5U^l@T*-I3 z&bD;8)kSOScrXTR_7vuVm$JS%6~!l^c?L+03I7tnyci}pq_=ypbyF?=2T^R`8CRl# z-*e)xAF%w$W*U4!9Zn7hJQgb2I#&!3r9HUB2n7s zK?smgu0~{!Nm91ze689-ZO6O-khadzk;R=#B@!R{+GQaA)-1h+KJlw6Y!pz8HPWyA z4y-aXRbEn*hl3X49marZv4>NF9KDvl+1DV%D?H5vYRE%jZOklB1LdOLK!BS0u|k!X)NYljAR zD2e{u^W*kjAI{bC48jvc`EapV6P7XrHp@8^E!u?0ZGF)P6rcaed;zAyA zd2Q$O0fDp7RIwDH`LusbgoS~5T1pIsV?8ncgv5-;s~@>V0jb_&2^FOACF?qVBMSGW zH+6c`stw7{UNdUStH>)brE+*ZrOs-&A(N)%>AslCUtDAEj4Xdgpg%hpl=Otc+B8vr zBvWF0gO~Q3>{pi{H8sk|@?MEQCmwBu9diMm3U(`R+DxN+yBae~T}DO}v!5G|sp+VP zQ*{i^Zj0Q7rfU)ZkFM_yYI1+R77@7-5g}@%iG`xlq)SU6Aczo9QIIY*L7G75RYZy) zNbiJDEfj$O(p99Fgl?!xOXxyq3GIEndVk-Ud*^kW_y>b|%6@kDoZUUkn$$NMLeW@! znSbG_H#7My3VP4{gR$mmndWtg5l2yD*v*G{J4_9>f|93-Uc7Ij9Zqhj+=FtPZQKM( z>=_0hp?Bc06G<4~zmjy>Xr<>5O_y|Z-~s14dCs%-VRCis5#luuK;6>%@cwY?09J2{ zzy5S$DVZy>ao9s`*JUpj_#RBIiUxVSQa7x*>2!utaiM<0X;B$Fja(2r^(d2mXV>TL zGJfye;_X_$iy~E3gHK-5m(Hm)CW{h>Cpur6UDxK#39qA3iY<|Ao7#8M7R2G5zE!ds z6Nd#MKb%s)#(@5>1E;nIGBlP$z}Q&r$5%=$H~y;;FGl8NN8>?nZI%6?TDFB{`;%97 z2^@=Tgmhp^E-4Ys4SwvV%PmWi`JQnQbas2Lm`Nk}soTuT$p z3ixRTfz>j5#1oE9E=mZqnz#?PxUmte>FG=&I}X1yp=x6I(ifvldZaI<^Cb>niyhWT zw7T%o5y_s+muFIw4FG|(A?A7k)eUtA>H!JpYO4trsRh&I?)(!9?;c%DAXJ$sx$0fR z$|yr!(b&zchJC0j9d{ck=$sa$7etq_d6}@SZU$D@ZZfl0n73xK&2VFSqTj(rEN)-k zn~CeCk^L?(4|*Qd)oEPUzn&h>*Gw5bd-zg|IORo!%xt;{o+j8?^z%s`L$`@*klk*R zzjNZIWvoT8loKfJUZ;0|ggCQ~#lrN84dJdGskyJ8p6&bE@fG(9V!uB#sLMW+&?C-I zw@)c0H9S9aTZuCcFFWh{q45fdbZ~LU^|jpbqu#Q5K<~o}+sG}YP9edZOIeq)lJeFk z_rH%1L*|Nx2+gGk$t1qyqRYPBU4pZ9JN7O$Z{kG^`+vlSRRWt{>iI+TO}d>QCUEKV zVJX0BzFwzKq5p8+??3NN&pZSwk(|I+EXET!c`?=icA=6y4v|%me$7@(h24Uu0Wi@; zQ6WY-JwES5Pi-^Ki21l)yx^uVq(9O55cXm4w4RAEkAI~QaW$98W-G*c>N2)ml_GA| zZmn;BX207>|CCJelPe=l73gclls!6nnn==_+kDLoEDbP^UmbfMrV~_nW)O`McC*ffG`CWqN4PMRUiP$>r-fwEApm$xYT_9)09GGN$e6p57>3t)N}n#r|H_Y2qt= zMhQfHDgt~{)~X9Z+ZhzKYUd- z@er07IikNlQpT6()^SHWDI?x}@8fFnfv7YCBWq+N!LxK}TQpY!*m<&7z{R7e!BLfkncykoXEm0dS2)`~ zCt?&eAEI;ro6uFKP+WaZx(HChjtLWyv&Gt79vK2QZSlUS77#GjWi!+=?zCqc8LAEb z)4B1{+8Du>`Ym1+r~RyYiLFo?C4`d`0HRh?W@7{p#NWpQAzE~l0imGi;`6Fbc&C5o zN!}8{${j zAv$0s)$ypouQcVm1IZXb)7AAkef2}Dh*AYnBlnovn;19`)ZNO<8 zolj3d_l!i_Wz*i-TDOe~Y1F^8!s%Z2{^Y`A`7oS(Eta5G7E1txDpXBD^&`j7l57^g zDJ-j)a`1?kl9*mb*pSD}^(?3BBI~k`_B-l~1EBXkD0l2#QT}UtxWa-DT!3N{TPr?j zv>J?O6oTxtjB1W!vO%^M)3JSge7Tf6^Xj9We&up5k76rnP-7h>y7rKOq=g+U;Hbp8g zUr$2JXeb~*gYhCIabrhEe)t2jAoO&8Qt^*^W7OeEqFKwDi1&!2hI7{8`_R*baK$lu zx_#KrQ8~?`{Acr3Icau zOnzS5f4WC!#I(=CsJ-okUN7(eh_sj8+J2bg<6_~nrzzsqQ@y+2b92*e1GVc_JWOO2 zjL7-Ey1Ol$O}s|X(}GV*{?N)=Br>CaDJ|BY0(CdnYQRTpa!urODDF3r?yAqV-hd55 zYqAWq%jB?brZ*nK8x?I5l;B7-eE0ozQ6^uBtwZda!o{?kzx~~{Hdx%dal%_zQSlp~ zuV5i8Dp8_ytMH;lsHmo3tqs*cO)%kVrdeqlpZt$hPZ6ySzg;3?g#YH_@9%!_`+-{b zv4{DwfP8Kf9TOk!7RSvl(an*=k}h7!kK7Lq0F*56XRbHpWuk#ptR6E@YrkD<^1vb~ zBhplwwWyHi*Dc9+ zzpwYxiF*NK=!0+<6`)zooUZ=FdU{ul_$!*Z+1n~J+a$fOfpxY>2wOO6?b}RZx2Ueu zLPYfBgvZ#S^1{eLs7!T_Yy7>87@4&_O6=$;A+BG{>CxMnF>dBb5d*c!#?|%iWjE}@ zUat)-_JQ7bjWUifG^s;DwJhK6biG~pX8)zU%FYO z|6@~sq(;9>1j8~b;E}-c=7+OjDw_YabUpN^_|j4B+Q|T2VneJdj-izoee;ay-PZ_! zGZ$P-mzmcBk@DiY*~`ysbIWH**ol=um1fwd%Gy#c~Hy$Hcn$+=314@-KQ#P@$Q;8Nk80*o5c^m znof|vN<5pPxLl+Yjv-dqM_iUKv;QS?wOF7FOy_<@+Z0~bL>aNstX4!tFM%=!8Hxn3 zW*=s2vSR)O5}9LEB)MBt6%5p>?Z9ITXsQ6(s&zt)6Rog+M7va3mxUHo$=Iv6#M7}XAmZzspFlVF$ zPkhiurM`a3qk$6qr#)WEMG>&5d(LlN#})1lak3gs$r_4^fY+%f*zg8-FUz?!-^wek z93RaYnC1!VT!KD8KHc74F%<7Uw(tQ%t9&z!L!ar1&ipHTB zTK5G`+aSohUjx#U*%VuNb~cve_2a0abL*{RXec1KbUNuYp>x$efek1fxE90V>PO{h zf@x(h07_|-|1`lGMAGUlT=q2hymTF>xfpmZ@|(+}XbCNtX=1u*USz`)R&L?pW9v1y zHZpH3naP9vX0}HBjlOfzF4g=d9zV?izBA(k6H0V8vGe8n)%UHtoBfgdAKT~X^&;t= zsn5QmiF^dR%es$~c#9osFI}tO4{O|sdgK`O=u$^wFFv#Mk)-?b9U_&ZOKn*?bg z5yhmzz*DI3u2aBt$gJ^*vk-Sbx~{MXg$5c}`q|$D`V#>TN{Iw-fep)ZcTh$GXj5p^ zqgiug`2NieY3Cgn%dPu=`Vs7J((ru=@kaWStPV{>+!@XMFYWsM-Wr1&8!9z;^#y@u zm>sJSevqOZwHhd0V3x$<39_<}U+`|Mt=+TU3`fzIJjxID=Ue*n-fkFlOiy{XaUU{~ zQx{Hp$d}=;TPyOAOS*dT`g`!Gnfc|^7HMI4YAl}KkJ_eS3K{igt8p@DSmE8MboY6v zL9PRltET$+vAqnx9>6lEuEGPJstljhBsS1Nid;b_aNnnej=2PDj zsuC_&1uljC}`27t&23X_>yJX}Jm+VGa2n-HwSfuH$CVb@RG|G^;kkEu;JtSyOV|7CUr7`B@@s{ra2 z<>?DjpL4Eb8o}+AKRS3`nJ$1uOmC}GGq3C;qpKJlN2gsi{=TxOoj-*F;MWsjde_+l zl#W2~Y3cqA7S!}K$jQQ&>2VSxArvdL=W^ieRG`CO%?@-r@WwBNx>boChqLOduzrXk z7_Xm>&h$#7in`_GhVPyb$Vt*tWl3oglbnkhC+KGepWEo^)PP3|YuG4)mPZ&6@qoNO zFqdG48~q=GI&%81i@5hZv(ve>WM9(?^pKTAjJ_Ptq>hdnL`RM!Srp|XPyNb ziKc|Dh=0E`8EXY7;yAA>)$b=KB6+bURFnpsB7&NOx9G@}uy+2AWPz62s^Ay&Jyi;q z>fg4L?5eC1R0;A_BlcUIo4B!t4^=vjoqsH$U{2}=w6 zBGB-2?S(NgN_JZvX9vUCvB?=i@3)Rt7RU4=Nr!-_fe|TeB5(J2aZyzve3zug9F>1hnhr*lup+G?Za- z3K&QHB_w*PLyDwX_jNs;ckU>LYRf%VR}yAYf(X#Z}lpG%79ueX^YRrEYF*dJRI;1l^LOFVMoyY(K(K<>M7JC&6B z6qtUx!8JU@7&!{Z}|ivUI+-Dvz?`ex6%m&Sa$*e4UY#=DN20YrDU zJZpi}l9$LKsmm=1@?u4f9}a(h-N}w*>xLbm{Cf{x$U5hWh5>?rtjIZOUn1{@`3MBb zH#}txKrRI{7Jyg%T$WvH(&s;%5O&<%UIVod6ctN|{=#I$_A5;V1~hkb_!@`h+?7cO zpLqVZu)t9bZ}HTtiUecg5-cs%w`U4a2K;cMFQVkjetB&u%I#1XiKosA_y4#2k4 zL4(x19D#W z_L^;*@A1k_Mre~{ysES&vrw05ZIwW$i#O4c++^KwUOz6_besuNY@TqDt{!fDi&oZ7 z9t$TI*KmcSJZOfo)COxVKJ1kXDXm1H83fP^jvxFJH;z0yfnU>UE5lAB%=>!;B7kPH z&m4~smua7V{r@x$HB?X8k; zD+l;Ul@U{Y{ohTbA2`@an7KBlK;ko!-B%+`buW)32K!To>JBlosA$!&4Tp^GuRiLj~E#Pc8nPhNfI6kbBAIs7$|3OImbwNu$x_GqYexM`} zN_pyGK;II@T>%1U^ga3FWpMSaf=%-Wz6^@HDrqHT4z*7x4krTubn`3aT`a--sJCY1 zVaEdiu=`x5+mAWt`$&GY{tgt{vvCxLcLW>`?8Y?^q<$fq^eVZjd|gTQj70PW`KW2Y zq-Vu$#Sy~y{{lu4eg%YYu>=DX!m4d`r3vuwF+Qw}EFB_?=^tIrra{0vYe18(WPoY` zSz`2s{mq0v+UM};%uD8|( z{2cJz8x~4HZ5G{zH-U^9Uk5h8U-IXeFdUSHqfSc@`hExtgT@=tKH74>?E9JbZmD%G zuyKb+-I0`YNT!_Q5&YeUe#DU*Xk>qmxKt}>XohW2D=f+Df)pG99M+|)AhOVvHAtcwP_5H|Lso!FT!;z4 zNq?p@RsjI#v$AGSQ)e^1%QYdV$Ya23s05n+E@>i(BIH7hY@rE(@#OFCKH>+oCB%VH7mwuJ@Qs9CA1S?Sr7-Zq7$b#!#Kby<||+}A%?=!i;h z%wMO7%VaocuIXr=<4s=PqVJ=LgMYa^{PLwF=J%7 ziblJe3du_Gu-lwF~k_@`7sqd&oljONc%#4pqM->n7OPpS>P?L@*yNBqQ zal#8CVS-=MbaxW|SgCx8yUV~p<`I7M?D7cDPo$1EBYR__G??bAVnXl%cEFi^m;GaO zAI-)Ff3nAi?HTggqjlCfdK<pOac>oHLZYL=j<$;t;#dcO`9r}m5mdmigS&y!5>x8U&JhOTH8E_bbQb9UT&$FR z5>@MFllY+TR!!7s>JQfgg2D0BTWLL+A+AIRjzzxi>(4eaOCW5+it&*MigyrG?Mvi%r2n|32x{o3M`eHw^+ zB_NC2Llc3T6hVC%4sIAogg7d#c5V($y$O?H$3riionT<5|KmKEu?Joa6&d(lrue^nNKY8uQOHOi0RyTQadlFlmr_`WJA-FB5WyGar2Q{Z}VOvsI@OyLk z^Vnx}K*>~@B)nIg z_i&D6+~yB4q3yS_Wyu{JHK1`^WO)jK-XZfGZgxkodcszrlggA@#ZHKwxFt=W`ZR@L z?V;QmSG+VYyb`o^cSTlY$VW=k%2Iwe}Qi zm|0o(dZ@GQ;(X?shKFyLON?Z0zkE)oboOci$fO;Y{5g2y$>aaY5B|Els|?M74cDe+ zo$J!X(<0P#)BL*0*^wxWBPt2B%u!YJ>s6<=Ilw3HLL@V$$0?H7RSSsZ$=^)s5yz7h z%(Nyc$oI?ub*V1D#yqf!x(CWfHqdJZnY)C#}*0KB`65bjZA&+}me-klUSP4X>@qa^Oeznh@sBU27B} zmz_j?*;hGn{ErYn!w*}iy#CIQ1%IeZ;Zrwg!PQ<33e{)&V_8jRgmfkgjyiQ>ZC`mfgXh{tp*?o>0WmYn=M2u~@iD9h=fk!H#V^#hxArN!m8tl|vvUok)mLe> zRD9EGkY=>JBLX(;V^8R3UKYoKMYo-jK(Fi{M+E*)6ZhARWSu!fPs@LW97i`?YpTB5 z7B4aqS;^ijV-A<$8m-YHV`rg>*6_vYcEH`tg$J_>chA1^P(HlI@8cr>ByXe^D}vHS zsW^RP;`qmC@|*>`kzZ_-M!QL?DX+4-NWp7bz9z+M3e=0OexJE+cFh$Yryz3J2fUbV ztJpKT!?e*VB&qDlFc)%9d4c~p0R*P0Pk-Fwmuy{o$HOoA_{RT6Cw^UVyy|16#kuls z&-}Cfv)*$Q6N!xS`#APxJJnh(vS*dsxP>%Q3funZ!a}EODuEmXtP0E+4CJ>rA*8GF zY^0hH)c^SpKiUq!s)_;(lB|??fQ8bgilT2Q)ZLkEh=mJPw}z^)x2(#6pSQ*@x+@~3 zGwW+JUaZv>&nxUMiX%mECEb`Zg#p+i$%oC5c^)lMgT6v5qCst{K*IbPNZ>Z|+~BUp z^@QT8TM!3$Q{cp%f4h_y9Rd5B>+IdUU8JB_q+J`i-TU#Z+=gwbwDiGXy%k*peq3Ge zKnq@Iy9fpL2DJ9VdqV&LeGT0$Np%RWgl>71!7qNTkXP%EmpL(QQ;Ui2lW|SOkDPF# zP;a689iRmsieM3nmurhFw4dlGy;{)Yn666-I^v{jnA)4i-rFh!4PTk8`4xH$LPStU z4L&m;`Ik?*2KW>d)b0e@l)~OZ@do0RfY3~gczDZrE~qF0UaN%~4;jQ#aAwU@m%yPTtU#gRN}4(Eh$H)u4n zR6fnx`~O_`g9D=py83*ouWk#)zT0u3?wkGhvNk(ah%FpU3DP3Va`*Q!DB{MdY9|;R zB(j=BP!V+Xju2dTAb(qMkEIgWNdYf@;q1`=1=S;rrzphg%iL*HEiPHT+mw{s=Ltsk z(4@)D^kGkJnRF(JG@f#coliqwp4>r7zg<<(&&;3RsPbV%n8OJ@(?z}oU$Y4!eM3y&V0Efyg^m3J_< z0edGy{q1VAbOycje^~-z$6eW#T!;D+$p3#2yV6zS_(qJwa~C;`=x(DYl1at^T4JXl zP@BOdo~e>UjTu|HLtJ4MOo~pNtr#!$bg_}lTed+F%gb;Mx(~#k8j*HTe*RyxGM{62_%p!xdQlxOU4;YV(IDynNf083*uRz@nDg@ zKz?I@(qJbX`$OO#`Ps5ZqV!dNik9j|TGYy88t>bT&$;41`s=oo_oMx_rVU%1tvP#c zVgC-HPWl@)YPk2DG}XnBq3}p@niv+4SLTXf_H*UOc|at~f|!r~+teQ%q#asNG1bS< zj&J%7BamhOQoeltN^5DmC;u<69-6otGaNjDhHR*gW@@3*=n#=Y zYUM>tG?r96@s~k`e*`T{W`Vp$B;L~@xUm1gx@_PWKg{q{LnK-a%j9aU-6U%hyT6iu+Nmq0@h%8d^1Jnbp6!CUy$d#}Qsl$K|?ryDN z6*e8L0?wfaJhZhU8OU$L8VtZR1g*4F8f&gJdG$ZGrs!aOZ%!f-DM6bqD`rKQi6(&% z8I#yoW6~$@^kb)6<9GWIsQvTku$bj<=l2ZD3DUf>hN#OYV|DgZ$MmV1vN0aDkvd}V!9GK*uT0uaW~R6h2Z6RHp5A1 zR_My|8JPWVyZBD;OvJrqe2m;xU8KF>s_oQhOD`zvJHyssly zw@p&t(+SISg@OaSuC@|+oBHn&({BtX@%EX6`1_4R(kgPk3Rc(M8)~n-LRH){3^2Ca z-Cd}CqoXru92$wDZl*PW&4z*PzrF~V`P6~>IAO|50?Io1o{^D04@^sr62ydViZ-$> zHcF=v{BTbOQ@DE6Onj zwOP^PnXgnKf3h`DR{%d`a9``2TvoKuw3#B_3Mx>GY1E(Vki>w-1Km^baV#!Wh}IR$ zVM@b01t6pe^U7!RGb7&<>pG>MH;+|v_RmGxPOgwC#BVo6h|EP3ZKKTGDFnGPPH&-F zY(+(-_Aid=hd4IQgzz;w9B5KmUJ8Q7@8WWgz5s-W0P%H=e+M~+JbGGXG9FRMt@Wt7 zaFjKp7M$8Fm$q5zf{_N2yNp#DNRhGq6Suf%%f_R2{Ie|;ezZyPFoK+!K2npnxq_S& zA%|{IQM*h)=ySLFm9VoiALQXQ0icYcoIA4G6 zuqtOBt_Y$f%~vRc5LY$j0f88F8#bZ4y@cBXJD?R`*$XM z=|&bM6>rUnAGMGheLCKl&1~xpycMjhl}c4!nf+pL<%&vebd5E4D&hEUYJZF_Tb6(e zmz(ma)>3i0X*s&ZWmiwf9(JM4D^zT}01rq+(971po)dRdj=MDewz%MoaZfI*$@VBq zhR<+`=pJx3Ww%_#&rD|m0)&K0{`zGGuXZhHal5+AkqXxxb`bGM| zSFU7D4ES#!WpG6tlM+Ml-CFX_)Rfqc>GtH0lRcB(z2$*Yh%>O4h*T*jT1(n#XxPxv zu(Larq_P|N4k5S__GR&GBuW1>ZQNUP$x+{3CE#_d#`)e|i~YCc;QSJ&zF=q)|Aww8 z*xMHvL$?jrCes&z<;>xo+9>a00WB1@NX$2GFgU7_NjaBpW1qua*^VE#ot86CYSq-c zF|wN%=Y%On(JMdu^hcWJx1Tw3_e4SfRW$LCD3n8If}_?g=eyoJh-Wg=yG=-E)?h>{yu{N* zXV#;JniZ-CY6@m|!rkjiE4kg2`Wf8Z=)J2h&FReSP58L&f3lDZ5J~D>oaUFynrwUP zsMLCiK9#^sChd+?j@b49nEm<%Yp&IUOAkLAApXACr^ELSg(ebLkhV%Z3-6g${U_qa z@e3~HR4ticI+IE&mz$zy?k{|s*;!0dA6dv<(+XqgaYTj#;Sbna#szOOAjR3$YS7}0 zQ+q2KCg}X~yNGhxus&xS%W2w3v1I1_t@=9FRKizzv0iy>xRrq9B`tEI4Lo#vw-ev& z?iBEF8<38-aZ=?Vcx?@QY&gu2%rZ+GdiPcd1-CO$`Vt6L&{_?uOd(X)W(mG6KY1K| z?LSX@Q)tWe9*_pKxb}v)oAy6aKdc*;%BHx(t7gzkV$!|iS{a1!cL| zs6JX2J@>vmECVce9$*+>e8G?FW2nCp9gsKfRL_)JlWEY>YOd6pG+JR5Dw#q^a`DG# zp@2y1dp%kZf!dO(o}O%;(TbXMMME3Y!kn$(aTlMqocLP%^X>Uqw?FhmAiT{Us_EhZ zgIGcZcI2SC@zg!Z9S;CYnL}3v5}OskJK)F z@HI}3F466`k<#@(cxyW*BAGKgo$YaJ*IhL({NSE{#*;)x@(7e2Kg9%C-HjJ^~mc9KT6tZwMGil-3J#;F;$qi(YssYR`~ zCEoe5-*HW;$qMz84KP??$4S_VueM9Lp&k7{tvg?S`^RoX zt&k8ZPjI)MDc~FmQt?Z_&U{~KGQNF8}_& zf*+l{36w4>`K|?7x>nh6CdZrq|ZPy|mkRDP$aLAXMhWb%4F3-JTX3l7aGrUS@}_XNGYRlOd^oDa0nqk$fK@BVd|>(?%QD zf6p0zFn2isIWGEFUhYO3ZI|2p3d9fJ1Prx&s7D%OZeSXvw-sC49ibk5-ALOyHff5N zJ_k-NhH5|W?aqNmSPzZQ3z?3KJgFfHnF+DvqSv#Ten&?iwz;y-C<4p|}ZZf)OagLY4Qq|*Qy$lYPQ{*Cm;T|O$wrtO! z39PMl%*|^H>;64l+pcfMb?TW6LwigXL!31oWriwqT<4+|mQ^7eDUzAn3r0`p9PNq& zYvU&|XVbfXcU3&*juPqwzE!Qy_rNqIV!JyvtHH2-muLemSi@OMW12it8uTIZ-AKJc zYWj^#@SLlznA>5sN-b6jEH|db(K{eK;W;CYZKiC^ zxAS~n2fqjHCrDzgAsa@n5J?}A#W&piyoklS5Nk!oDPAIy(tuEegn{TsFi<{@ljNnV48fw`euk7~hVRD0Q z&pPLicpE{`DAm=nJmapDwNY z?YCiyAEn`TuzynrgMirIhWbe{J00fZFpig|$Qs{U8NCqNm~orpCF=Kmc%ks^F7>;T zR;z#+hliL~i94)hl)AT5`qUB#;T`L3p8P^{63hCl-dmEHIgZGReuLGgnVS8wSh;OZ zAr>p`#Rp)u^5(3>2p^j3{{1(-QVy{26>_R%HpxoTP7Nnn02p(3Sn;w}yOW!TBES=*$d*@}u4bz$l+RJk*2` zn$1-6frn*-GEcs08TAbJIPO0I=_OsppjHUA?t(#WD!{}|Y$I9y{EYp8t$K4nsi%Q# zLXezXdP$9v#I~a^bWT@XAK7_VJ?bSiygfrRcyyxs{-rB5%bsIoTGNIU*Ug%u;Slc* zf25tW_?c~rEu((GZ$|(7(1*N$dY+CdmEzI;^s9b}yfv6vAD9~@r4ZS(6MT}CSyN)8 zMj`f6wT>?>89$6GPs2wQBVXMr!gAo*#53E`nLepK(s8n%qbxJGTGh^3j%s~LUWwS1 zA4U&lc1NCK{<#rF{=jJ{emEfMu7bDTmXL?owTHO_s?mMH2N$=UfSoTQg%fIHx}8b#A>pH)Jn#|i%YzhP)* zAuxn$EM!oN-E5t-2GXUX=MK3kMtIcR+;UJQ`=CgFjeT2~G*{c@Q{&?WxbZW_%%X^}4U$Wiw-QQAKqZNBbM)1)3%?>W*04Ivhh>_XT-}CA zW=puM7$L~6w(#-|ZE_($?!b|}rlDiS$w)7@@<>PrtsEd+n>A@zlQr5HE<$ECYGk6l zAP431FooYC6M|dhOIMHn2Ub5;+T{Q?PD(_QHrp(Pl5UxTfzfLQ;ag8^z;ESU?kqp_ zhHPvYDvAK{MH7M(z2X29Aq!EOjqQiuTFXx)j77CCi_Gpdq!07(cxh*5JlgW$e??O4 z_B^|H+FZT(Y2B}+283tv?@#o+`&}sea7vnDkmlhIJDgeYmj=7T z0+!EvUMvtWGFaIG%c-yH)0U+Q;@H9A*$u^n8`uDO5YHq) z^DfpWCc-YB$~(H>;Zb`<*6*#bXaiF!RNv~j-G9;{PdB9|-v)MH1d2xcSd-P6MUJwW z+(-DzCrZ`0cozwmbWZooIJX)?C|zZZFyd~zHdex@*4f) zzm#{!7cY0kFK_msoURx`fu#pB8xP!pz>KpxuvG+miy#1Jh;0%9imwWtv1>%LcOXTF zFp{n19qBSjY+mnV`fE^;>7@mw9`4X>r3RRxyK;F|7rZWT+_&P56$JMZkR#`n!$ndFjx;+*SITPPlH)Nv4-fp;?N#ZQq;}?jum#G*eW&2>H2X>E6wypo2S(qFQ8C2ztrLdB;Ylu4$K;GR+uy z;6INRS3CZ=^BJ8VA5I`;O6&(e2(F2c6Lu%SS8!q@=>??znRgHq#&PjAkzR>m?zV{cQwNse4u5mbcL= z$@r}mAPkO>T{ITU(6y2-(;MINe}SL6Vjk%g9s*}`?Cz}{C8WJZ_07w1ddQ~;FAqXDK} z85gr1C}+U|bk43tt!@QVYGz5lhZQ_R>+=wUB9y9epX`@W8o8&%~w zDJy6EQGZ8=z+H8Nfb58Eu?r*dx8Ad5RP3hYO^@#vZn5c1VECf!BrwQ;3hV9iWSwMu z*)%oA#f?v|jw*Bs>|sig#FTU3F9A5E^XY{$I4D|IqNJq9r$wO3QEiu_x6+YZ?Yq|= zd@8y;AH@BFYl<3k1Dkix%txput35dCv`!n*%m$o_7({=vftS;-L2dORXpRkS1`i1C zgj>^Uqo?DT2wRTuYMSZp@_?aX;Lofx|7E>!tyN`obChNCap0^4XKGk}bQB8#k9Uhy z$X)@u7*y5WmGKJEu$Wz^nk^lHXVxr3x1l0_;w1*!LMPCgsr{C=+GZV8KiSp&pB+)< z<&(`@K5nJ^yD7^?f8UcgUl?$qhP57bZCBvR$RIO< zAHBLR{s{KEGlO3;j(;QJ3A~?@J*1CtTsRe=+L;IP>j?${a8q-d8eh{shz^YO2l~yF zSJ|EdpljmgYCX_5Vs&_p0@pU{4_ckwPIism&U6eOG%>5J2_XZ7>$mcB>Nos zrxNhn7G;zbJ0BGW+u04i>vD~Gu0b*x zU$E{k^L391RQ0+yqrecBq2;%K2(L40VkLv^=QmzNs%EInwKB=>htD?k?fYIVs9ZsU zD_2M#HVo8Lf!)+ve7@aCadU?`37?BbnT!kX$4pVGGC=_<(A)}Pmq8Tp;u6pC$Yb}@=Eq6C-kuS3JR!gv%>z4;&ja;6v)ijhCeB-5 zG9fHIx-iklz#RaAqf~SsKlX~)n$^i(tj9Nq`7aX~{HmV)yXL!|8GE!U(i4Kd;0hF} z-IYzcyC~-?*9_z_;o*`=X#?$b`CbUp9Yz2N2WEW42YNZ4henJz$>JoMs~N|5 zLgmm8g?Nk^Vnu&34A?%o5akGu&IQ%SJ%MlMflSgOfW^ml2W`PNWxEC(c$ODyT~J%w zK*(?QR2QYS7%EI{cwu!?stq+^4_>^?w@kejDEhjUZjU;>T>`_|a~MrJx>7 zBW=>ix<*U!T&a!mVhc`Gem$s3KWTnCgngAKgM|6ua*^fnr{BMOq$<;`n-n>Y4+Tu? zi>IS8$5xMjMop>5w(Gaut{9VQEL8|`f85t!s1}2KcVl_UKS~R!c{Ilj+Fy$z0Y8)v zZTFqHCnGxItIdI5@J`*7&ZrINjJ31=!nM2n#Ac(Sa+L4AAononis5QlCR2H`7OGDY zXs7j`MZ)oU)f2U6etf#P6zm8fg(s3&5+<0{Y&0coUswO*Iq$9+%G(LZwugRdAE{74 zK6g+A<^Ilbb&^3?_I1CdmoJdNvH~vA%w5^+K18x!VoE*n6_CQEG$i{saC6UgcE!Ra zwa8rmL1E;;hStJ<2E}bIot85Hsm`Hxd(K7?ZrMSRi=3H0gW8p4;lB2%QLQ#s^Y)1r zl4)g|Sv~Wnn>Ia#^=V<#Fx%pQJ(&UvGyJx5;_1I1W8f(0?Tf?}lFD53tZ3OTRlYNj z)b*_TH*9Sf#~ZGee`!cx$S)uIm49&5(t<)ec*V0{EY_V5qY`}i`>Q9Bwq2oaq7MG5OkB?Q3r$4^Yhy(@e*;)>8gGe4UfUiPxDV+>SZ6#D{2or$N2!~ zyz%ih2(b#zZ;h_O`T^Ogw<19ftJ1U&7jjM^SP^s~lB4qA(NJs)@^fP}PlTYGAF$qM z8{i3Shk#8b^%79(^GJWvqK7vUPFdB7FMZ*rJb51Y(QEpD(*$&M8{C(j^kw84A}nz~ zeS|_?TW@wYXBs6gZ*@0K52OEhu`%JFa>b7~4v^dn0hKwMFwHsrFOG;2e9*0Ky#LTH z#%~!}FKQ@Y4x<;mH@p=k4r-K^)OPpiAC5n{jS8nzh66#|9XR@)+7Z zKy4nZTsI`5F1Q~-FBCJK4jN@(iR-^EMP5{F|7bFGQyf(UF=iGU6gicA_T9+&1wr!m z8)OwDGp2Ou=joc|wU5B3t|oiyp}VZ6$lRrkZy&f1!R;IKYi5E1_LQ0W$UsriS)MWV z7iSMeXGOm}F_o-6XamV*-$E^JD%gOk>AI*&1Rt-Df<4{CDW{mvcpnuJl`_NaQ zDY9XZ!!q&3%vNwA8C4Z|mUE!F^rPOYz(W_+VdzPD*&t_?&Sp@~^;6Zxrs*F#vZpDh zYleO#aPEDsKOwMZcSg_o1}l7cZrS?0Yzdq!*di#i6U#Ig97-Tf$v#|EFY)pE3jw)u za_;$QZ!T_+v+!Z}-L2t@Jue=~;5W8(EZD&a*7eig7vW^fCjYC!{+(SnMbuE!8=Gn0 z#U=yR6gDelO-^nOVv+oJ_nQAwJ`N-b-#;hUBon6Dwr=N}Xc)G3eWKiNG?-@J>)js~AIQxV5D?GJ>EfL>zVgR*U@ zsY}nk2@=zGzi=@o87iE5XbWc{-vlV% z771I_^NYc!L=!z{E#Z`sS!)zL6w`Sp0kZ zC!O4Xn+d~gbqb>#L@qt2%8%nQ7eE)8OJg^wsu0}GAFLTiSRd79IY1)I(8`Nl+C4&; zN&S<7LIdna_}JX#s%F`-k>fC$zqGA_69-aYBM~5m%;E~T)0cmf>yK5=aGt&|@GdUs z4%Vz~UdLXTP3j1v%fiQQ6cMiJ5C=uDPbq zL&s4f9BS{|**hQuA27{+9^BF#?bdYU^^Cb6057+iWsmOnLmD^|x=1xgP=@8)GfQ3x z`0ZX^d->Z3l-u8cA*`5OuRyefg3#AO+*IzY7E$Ls zk*P`HV00jnQJ=VTkwA8}jzUV$fYa8VdLSKWiRuA-a^+4yJ_8uUydaWER2TjDcBw5Z z4QhOF>x6m!x=Vw+v(!Ztet&njGu?yQrypG&soS?G@zbQ|&tlBYg(uPYH~ankEtn6K z59NTdZf52&$m0$7;3xQgR_i>*dYr6cS;3cttXx8YjB6`_%!ai5y4=JORhu z(B#uT{@!g%SXc!)?$W4)qjMv$c32NVZvWZ3HExT#KIiAMr$@`tk^!H6`=3E=f9~d9 zsxRk}`}l0twR_&5va|jY0Ys|f^kXBkvD4UU9 zmQinr1Fn1}XiZApYBbu&9{qU|opVXbL6Ou5GdU#0bfJv|R{WkH6nU3z4e_d6rF`-W!g6M`JAN$~^^Jr}v3`M!iA{@7;f)x?4Ke=uf*GNIFb`e9JI9EH8|@ zWvczb9L0k6*|B9a3KM#5qxj0ZVLMsq;f(h`w`A@T1}ddC7ng`H$n%9|etSIYv}@h5 zRNZ2)rrG*$OMv{tiM3%I|NQoC1fA)NF@D(Zbg4rUwOiG=m(qqo8+uCO$@k$cgjA`! z3sw3?BD`+dl_s6v_Smy=i^VJNBdSp~kAk;<9q5aqnih)b^w%6OO+&3@TGg$FK7`F_ zUo^rii`fB%@Y(CzFy>VUdPS3>9wC%s@W>Q@cRyBGFpf!waOr^9xk(8(v$kQ?yvzVm3G?4sJ*)4EHWfs0=&nH2V2kd3LcGVL}S>#E_K| z3(*S!f$j^TtLGm*T2-fnUst>!|2Bn~dE!ogpoHr{mO}OlZINhZrbuOtD`=fmYnkSZToR@B`yhg1E3i;tc%(LTp^YL(R&u!f zLH6~^x!IB$g5>d@X=}-DV2TtEhh?**NPf<8Atnr=j0|Rcyd?J1D~F6X#zgB1o!67- zxvTAtV(f~#SlHnU1{Zfx{%|NTzyQ4Xs9T;49NY}YCAGZ=yOuX?002D=3SVMe3I|Z< zmE2A6CdXI#{WDLqf&E49H0=OeCsRCa!mfzSvL87&&~1FW#a*>pu*fD8YjaOBbdXSkIGP zO*`<4)JWyj_qiOP!b{T?%^O0;I%R}OmN6yzLQZM*@@BzX$`49GnBCu-a-aKS{ys)0 z2Bs6Y8;W&Ugz-WFFAMwo_G!&N+jEWj=a}+U%82dpVi%FTyJ_)xcu&E6k~wL+&lV-I zyM`1edF6{s1tHm?URkcnw!{d|P1o$gpkjy+s^-mpnPGlB=GExE>nMjMnT<JT-_t6T0V1M}eD5Yn#ibv*_CP`M?=@!-|6hGduJEL`uy!y@f_mlmE$PQoIOPIvQ z;l)?PauAB^Hys$Et1iQaG>L7&j~gb^%P90yb-&%@6VvOzr*}b#Kt|mO+mz9Uo@V69 zO8VcTc>}g_!X~3{z`-MaYyct})n7DG&s~8mTJ=B8i&{+-{PUA$G~WzQaXd^a)Tt*w zNsrL`C9hd$``*=@q*T~raiVxbQUuA1-)RQL{&O`Jdu70H zHrFhtj=Q9mVYw959v&BcE_ZKTI@lc<)4u4__&c!`c{G<)$u&HU^q3s(G*Q-tohCT; zwLaTD;&v5u9Xxfq!(PMjLhnWA8DhIcd_kmAmojdDcu=(D^rXsq?SZV^->saW;yJFV zPKqzovrThhamzCP__$yiqqKx+DI@E-grsP#p%^LqmS;7_dbsAwtFy%SyB~=8~;S6VT5UXn&*=KiAO^O^isW^ z25m7%wrf4Vu241cB977pH{dd3YKDNfG-sY2MoYiai^r`uA4UuWtTIIFt zS8$C*vR`M({<+WK;_=S(3%qafTxYNjldxRF)Ajo=q)X64von6@K9c=U_*p3$)JAmKJz+@qpCJ}=y8dUFZ#fEG6n{XPy24|KiQR@ zBKROdi$eM5+6!(a9f+eQG2aB-pqO*R?(zaV_I{9f*Po9pykMZ?_}V=VnR8qvoM+;m zzl$H^lg~1Z)=Q^i87sP62lqFbkFdCb+_$4azJjUTlIHJ`zKOdOj%{n3X{Z9T@hwHHnQNFcwa}8nzav%GP z+^(0llUghLnojXmW8$;e-rHQfv8KP52sjvPP=d<#KhH+81Tz!RVpr#u^wEG< z`7CS}3EOj3BMs~sy{^MOac^tbAkVNx?8Kf4=;X?7Y`Mk$9ap((TDH#u3HG~c%ze34 zB5Nt!YEe6UI5+p~-1lbqcRds|DC{%54ZGs-i`)AWz>@Y%Y+O390VwB)YeqM zw8S^ZfF0)S1UsJ)8Rdf}edEQ-w|`bdCb}e$@>Wsrc$zcg^q}&%v~+Em+f8`ewi#Xr zDVRI|H-$8Fl3J7MY!LlJY4QGad1J8}lnWXGF8$*?#xcE7Wwb?dl@{_B`K?Kj+Br`oY{Z3`s%3J;HHuyLvKB@1 zwzgNpt;A4EmC-uY*{pFrW44A;Q_PEkz@@BOnieDh?7v#RdN_A|JJ6cM_i}vx8n7EB zKc9ko6nkn5XIB(wZ*KAZ@y1BqZY$FL$8hTY?(H^~H9>(Cg=`TWl-Oxy6uHX}7aMoZ zqvH%T;>h1(-f1Uv{aM zVT21-@5wa0ND`8Jj*a28KfZW@u!ytNuYK*N2l>rJ#BvT8Irhg`;s5`FONsSxXSIJG zpMIg5b(R`h!`8OA=t)#3r$ykl`LKboiJaA;zHjpGqxwmGfoJf#llt_xgay%;M>a$n z4Cx8HOg-NwFK-z?5Wu_beCEr)ug&6&el0a5K8RVrsD(uaiR-z6oB%F&QbL%2O3xlz zjNV_T!0+%n_;Kh}vC^Mh{Yq}<@aHJjZXNacXE>aP3yMbt&3lt5MyE^^~pNC7SO^xW|h9urO&pMSpD{(Jk24QtQ@g(TdYq22LM$Tm!^-1zTL zOI=+?z2h(W@F1jW?x0Y#as%ZQy$kjf@^7{XIml+`l??-PBZKqzB;>P4;KQOk3if&p zEKBasl?)te;PNGYkCTz5Jh&|iay{SIiG`~(`w&f+zYl#)fQDT_?EU4wYc6ZzBN;EO zMA?Xl3VE;j)kD=8N6yFOvO3uA{Tp7oGqXcl?{s^VJDJUvq#cfqG1)U=MNn!w)iIo& zaxaMg87z0clUcx6N#Y^;YwN>>0TDa#gm2S~osl)Fz-4wP)(w@|qQ}C+4l~73+uy_w z>vq3}to!^~%6HjUk(1V02tXVWBca4e6M-5o^}kSv$SJrIKkMvD56RT)M7Y*-30F3- zwk>f``rW*kqHtHlyi%6mx|VvhQ^|fxRgHZ;eqU)A;;=v&$j{R$C%EGdD`-I^WIdW~ zxKh69nDZ}0Kl{NG^zVq`8}qq{-J{3HjwdiY_BP%fUQEF)@m#NmHmdT%)hLj28yj)< zM2VmNO^Xt5yA5=eQ`aqzPpFU*9vg}H4=27QvwxS%*By}Iun34dQ%iQ^Q&Y#34iv3# z$ouJI+qP>{I)X+&&*m{yB+5*pRWNt5pT27Q@HPymksP*k<%-lUei`n@V#nlb`1^6@ z-)D5TJ~2v7^$8`+@d^zST3CnLA_%?d`R}gyjW-p%8G|lt3A)v!=W){O4@cpAa%l#e zZ^{q<{5odeZ(A6C5jN1OkSWXHpjp6;O0`;?|qXY2pZukZE>WGTHQJ~bO;NJraU z!jP~4f4#ZwyA7s4fA6F=0#ec*K28S*-Kz7YL;ZC#9&>cTl)IXm$ih6ZNhB;JU=A8B zfiT;KFUon)3khhX2+f=ASODf4ing=IJhS^W?cfcY3kBqS1%} zX&|`~BHUSWa7tw3lfAIx{Pta9ccc`JpsCO_M9dy?*jG5gue)1Pa3^oQ;qI>Vz@mNW z{O{r9xsvbvG7>wC*l&{(W}^}oi{cqcg?2DLr6m(RNc|FL;$OdX_67HV3k{rTrG}Dc zl^APP=7zE_m&!&yQ{A(B)juoz=NpOa__sT(rzMN0*4yz!R2{TC`SULKjSN>isIj)! zFX~|5)T<`-Y>Q3U2~xW<^WTU6y3W)TiW12YO^c&kT=1|NHThwLQ6fx*-eu*wJSMK@(voQax@zRfg@wA0+3s zi|Z*eAB^P`sPs%K`}_dM%Z6R;VYl>6J5K}Gx!(MPz2m#OGL|Q`^iiPVo`s~- zmXkA^VSn;~gj5~3b+-fNw%R&$Z0LSI)$66q7R;n;fY>?@jfae#g zN`}-c-x!4&7L><1?a=&tt`+_VF-X#asEAV!jy4C<9$uV{MDo~b@ZOIjvFz3Nnp6AH zmo`%OQF?79RMA+O-lQ|KHxqr^h$t}H9WI&g@L#Cf=t3JC>qXM`! z>x{?d6m|RA(Z%-CP@@)Qnxqorw~zY@at=rB3oVXbkJ>KQ_-egz*i92Vg>ftj{k?Nw zpsi0cw_gyMkMKNrdfvN)a`~1LJZlC!m%q8`{wmm#k%~zt9CSe z>yDq$Z&$2jWX;6G5d;9+eg z!l?Ud8bT@}hI0r0oZ-#k^3CHDy`MOzBFj7XlQ8(YYP1dHevtFfQ>&ehXM=^~`|%4+ zIyme8(JY>c0^x+}*7mM~opz7%o5}#wJd=#RwXign1=LL0$7biE*@g(_htZ*T6qukTlxsQrS_y#jLhxyHK$zthI|$Dexp z*$<0uxhytt9dvPkKbLLVLR;MNc@{&qhq!*_FRTRDp)$t~yP&gv*CGx*4!_=H@Z*}# zPP*9iCe;Rda1_*duAUU~G%WpVkqhf!-h1{NUb9$T$@F66{I)bAyyDG4S@qrvO=M7k zQ$6L0y=FkTq;)dMZpo&Vcr3D*@Us3y&!p{jX66r=y|2lOc5KWnR(qVS(}K4sdL7j5m3w!=2|JFG#zF zlu)3`0$WO;q}vCH0z@CoInR}{`!&9gsLY!!>{Kt;e%UpA1k0|G+~ig{*${8=Z0YHG z<7?-Lm^EMW75m|9^3zxM8Lq29X~gW-hBS9T3ALvA|Gy?8Kf8Hy;K__AJ)5vK1N-U| zE`t{oqFIDHy$tpy0O5QmDtdFH>dK|RafVX@s-dYu?N=&y$n8g|zF+#+{e2IoVRRFx zYQIB=kIxd~0&%CC4g@5>>%3Ry&8@-7%-~o0X$#N)nS8wHh5B_b^vY86LQnL<_ot!N z%hrc6AK$9Aby7-3OL;r zhmu0Q3vL926u*YsfmGCzCPiDoZJROJ-p_XOKnlGBDS}g?;q%ii=Y2^-DKR)_rWRAB z+#R`DvB6OoG90JDg0636>EHVd3}uGoqlO0F{r&!e2+JWff4Y~yA{3Og3XkhA@;}IO zm8-$<%a8Ccy?dt>-@LNBf8rJ}#7j^z^uGFYKp`t`oh``TLzcL038mc7&5kqGzT0?EPo~#mNj`X9INvx!0VJ} zvBdp#CLnOotq4gs1ED*C_1}zl{r_uXcb+Qa&lQ9F`#R7uA&W=!4>K1Kc89!N^ur`!N7v&QT>}F+P|)C z+c{NHdHT!KDN@sLV@H;6H(u<(jj~e_9>I!hUUe-n3BsHbV z43^$SKHOM@Ks;7y(h3y4qGSfIb5?MF88c@Pp?_*~tFN^3o2_xarq#P@)F5=u=|zsR zw;)Dvt)+MO8}A-g}O2Vv{%qUvCda zyHjHC_6`yiq^Hvbj7--==_go1I4(gb8O(kVhD{BvK^upTWwPERsmiZ_4`2d=8j$?l+Lpey7+_TU$h|p z(_k~X`(W54WZ2fXc6h#2Zhl*Bk$7ja=cHH-un>|Kj*jjbnjm)&B?l=tDErOVdBvY% zb-N#Jq^_I7sMa@pKFSbP_^GRYq87JupJ=N5RKuvT(#Kv=T-~@q<(*>Y#OWa!uV3Mw zfEn8xGTe4n8}t-R&QdMy_sBT@Xe|RZZZ#T)4e5Z^jwV5kNmtFOm^B-uAaV5&t(*7A z`s6KqoE%f?8uTPAyN`;)qke{9xE0u7HbcuD1|XN6#QNQFnA<zY?lnyn2#r3&F0!eoIH*!Dv|2d2!;KN$Cztc(e>* zA03_#=pFCzR&l6#N3k8)-_YtVU4Y|u;nJC+Q`ymkiMt7zY=_4u83p-=pDgAK650Ml zV=p!KDAHJcbvW2U!j0?IeRz+FH7qKY(FMi}UaOO*nl3fQC)=ULKH$B2)y3F1w7%N% zR#QLaVi3QbigfFfq4+)QAws#Bxi0nU)D)1$DqN-uoloEc_qV2R);hbxC9}?-o5?pl z)`UtneDFegGPu-|R+p1s!w#oe9oxWZ6R>YBYDwcs<3nv7_y`O+bt%psPeRo#i@pSA zI9D1qhFvAT%vCzUCxlwkF4rGoWo~&R7}xDK5P>VIqt_2plfWz)p+gQWet=fILKhkC z*1kie^tUC}U!K3NwWZW#(Cd>Q--S1AOC+n%ac~}dp?`GTi9Uv3e1bhJk$$=rau28e zKYTU^-UkXFux2aMH)jK<%8Xt2C~|7`e>W>^-&2iorAu z*j5<|qZZr*P^*khP&#m}Ijv_)LWC`TeVqYNz+`zMD2ew7ZblEJH&k4erpqZ#?W^&6 z&_q)^V}c|%(55?1g!U6`?WL<8T8g;JKHDX{OzEL3pJv;xc{dg>^`_=-H@>9=J8sr8 z=H?@*)xde6?@n+~68*V5m4hcuHbQH~yQ5WGkh6bY;g>FMN!bBi%pqq3)H7vmq+M<* z9e@hodtumVt)IwKUo(;o)1A%Og=aKB z`x+Gf>bm|%iQW|Cdt9F8b3^sCog-yc9(QUjyEd3Y;>D258B$4%sLh%=iwzhx_OR1~ zNFxcSJlDG>w0iC&>G;QT5c~F}(NSX&0C?wqwy^=G%73lP=G%pzmf6NXPr}oz3&n`< zt(oV>M|oi_>kyUNqaw?aDDNk%W~K zyghzs5p6xeirIx*m>JQ<7sh>8iR%^D(93W(p7X8`%9H7kfH?08JAW)Wtjf-FHxl^H z0CWnpw$>vfWCr`A#(Qhbylk20Z@bKCNp>G}+bM_%7s+(1qj!0c{oIJqw8olLZV7Ml z|E$`9<;Zbl{E(|*_>B!dGo9fYnioG>5W)6X;`c4W+s2B~Y?%74ioCd7Ls)gJ?zyR> zTKXOW1#Qo={|67z3I!U}NTnX#+xs<7x4JViq@QXVqMhp5zpi zGbG+R(QACkhr)lM*m^eU=(MX%Z$fuVc9c7^tIstMnLhS_r-r3!?o97=N9yrq5sZSR zel;QK%DL6`gaREc?jO^v+~Q`aMSHRR4?}*OM)^bW$FlR)x4s#Cl?k|7p}di4kcCP= zc$g4#lV`Z9`Ezttxg6rnz1$T2b%qcYDajh-vnDL#I!?6;g#4~2 zG;u{`D&-Olcj)vpbMtY>n z^)Q#gPomEs=VNM+(j>0tyZF)HO4!pKw+!?Gd`nE&r(bI-l0#37dV!{lkv) zUS19JyFB4NCBmBrL92HUZ$-V?`knORq^ycUin3MHj-S-L?z^=05=4-4_FPi%kJYtz z$vjwH4JC%s$0-FmZ|5{?!2@~RQ_x?X0`1%vuoMEFF$^UOsqx(b5s?&mIAw7cC8i2i zD&9z+q03f{ucU;#UEEw{gBMHcr_iu`H#>TkEoFH-Pe#c2%x&iC)%mSZibWE+-rikC zY}T{@fI_?hv+G4uY6FfK>C0OUt@>N>_K38!9rz0_Iah+-Iy_^L%DyJIflG-{m9hp+ z3bDMoI}h(E^<>!Ijf8(D{4AO@_8~xLlGhC)_CMH@(I370bJwpicy^++4`Qa@59=PA zbm^UQZH7S8Q`{AvF^V7JixtU53V33XCPBOFFfrq`A%iu$rXdnu4csMm35dtX+<>9# z6eT+S&jKjkTnk0gHkQeJL;iO|%5tR+%EH!n+y@ujgvZnyUCLRtZtHRUvW4Vy(#5zx z?}8EnNt>H*6kHw$#G4yWFj^vp-TNW$amNGg2jL;U|Q5w&T z4k*~SD~WW)kCKiJPYCu{K*U>%`$vj>fIu430Ce0i8PL=nR?XpFLd-}AnVv;3XG%xt z(>+7x-h4~0%hbt2;&R1ytR2Q*i!~L01^*E-t|Z-P$WD{<8P0=e@D0T9tDQKH6;O;Z zcpaXuMDG5Aa{vqDrLt|Y!5?THwHSAFBm-0%TPG&h-Ywtr zGaOI{9J<1=I#L}s5XtDg#ljd}mGMTH-jgUUm+$ui*22D$v6V#cT=IJDlP7cg+hl$f zi+-!6>fx{b--`j8Fdus+DB1AB25MC?JSdcUdDi)7w9soK4}q+gWIYh8%xjQl$Dh?D zvNgyZx?4X&zFhO`89%mb89G)^T777^NboxP-qCU}M>`8UV>|!rm@7y{xBWtj1U)Y1 z@kla#Ua&q1%e9w5%DZ&G;GBWNfuokD8Nx6{z)A&UDRvhJ=3j2h5#%{<)XSemeY zwUcN|@|ulnmF@#opQK!4+szfxU&$t4NzzyrnXHEbjmxE6B@?7nXX~BjvlqbPohFm6?--|d*o4&RBtqP3fah2;Y_z-b)OTh>|pqB407b)so&5s4; z`@Bxi-RGjsyQQl>HAtjL_gu1DS7*Z#)?+Py^cePhWK`Kj?myq(VN{RYB{di~VsvvSUozh=tw5Rps4h%`qtddM@lD1_bvEr{E}G#sOI zt&yplIfb5Ag_D&o8)1sigN_^oFGl9$1k+D-oc2q}0+mU$Dd_cA3}pdV^j$DU4YXFS zy3rvo#5F0%i3C&5@#SUS)_-GAG+FsE@%HGdx`&&LEoF9)a?ST=Woe{c`U^qDnwNA^ zUo>J?l)%Z1&FG1tQYU%-dJ-(8a+4 zvI5XfEbZayai`U@9vwfm`H3NM#em|!43Y1vO_wvS-D+2}3VFo&08Hb#UMf`YxHPNr zA_w{9VZehMoZLF>x+RuPFNG=Po5Ni-a33gb3kpz*((QDR*2)JH9}h>1Ton3iwe}K8 z(4f(3h7f!1@x{2WddpYY3#=7y|MtFLliDei>{FBM+IV7N&6YXZPSsj(v{!L`)q|l)8WuogI2?g!Ett4}e=>B-KE{A&0`tNK+_ccNK0Nj6KB3{^^*Wfy-yi zI+s(MLyUj>3O4-qos$al2z&;}Y?}`D%kQWkuD{DTczah`oYmwWM(0G|2X?h*Q{G7j zdnWX%zX~71`cqtnc%;mKrrDE|`Mz?$xdDLTpH_-=vPNOls7qcW$wjwZm0CaH+ntRK zwMqr`JyxwBj8}=F4TRoIdeoP%L_*L%iM(PLm;dozP~Cd<5#$--ny^`KRCs#%WnwEp zRzvV|vc#v`Pqz=KOy0R)Va=rU=wvmo>bO#ge||rWmL*as>)_wlMMUI|c-DH*@TIEl zQJJekra`{KMK+{GDowQ*(kl+5o}g{R`47mT1$GBezaS}zZeCeyR~a4)R^6y9HJCzH zIPS5+j&(Rj8?lT)k}f?6Z7l&v3eT4fGxI7Co%=TWw)=6d=2LxSsbi@x%tkG!9Ye1h zA!hA3ftHp7hry}^O`6x5wL~OOCz%kU-3bV;My#DRo9lH!$7}Nr_n#)X)4IuZ-gT8l zS`(BP*RUXiA)GWlZMH9g-}6b9JD@Cfk@GGWvRH$cp9;wD{wd~E-oE(_DeR{3OnkXB z#<-v?+k0nYklR(J?y6I?vh?dam@9B=BTQ@SD=(*+<#$)tdF|Da~q&+OA7)+tVHTG0`XaBUd1et`XHFO?I3Y;AmFamR~wZ22F4f@Hm>ClO!TLvc$8 z;Y0Mx7Pnc2Dqr$+i@lZnX-580QpCInoHOHb=K<9wwV!F}5qhw7Zjai0w?x(DoFe_Q z)$o#xw~Zla3;z9xnXy7FtOM1mDeAGv-Vw<-t&(u3`7&{J;i=krm-q?4id2F}3nVLbj#Qh`M&Sn7f zekCOrOq)MyYENXfxhoZi0CcNoh-VTAK$Cn+>Y^1(KN+yd;G2+SqCx-xad&{_S9ze~ z(DUX==wcJjlU{LGRtg2UQ}Gz(R2HcWh(R?f1alLSQCM_aD#+m;LwL#Ds0#4X3^t{) z=tm1dtMB}`a(<%eo@+!8>o-5TB~Xf64ib91xIs^cb(ZjwuU<7|ReiQZXq^_qA`pZ=C(a5Xeu-kggEBL%4&o9q zxS&eeV2@ncjlO?vv2Va4Ke>t6FTyBz$> zM31Q=zQq|Z23OJ5WEzIRG|2>5o`G@h)@l?dT|A~5wQvKxV2y%VY0hOhU~AFd{r;G_ zCxrCp(55;DE9%a8CRMXz-@mk2Vw|07;_;XU>5qN?!zJIAp-cvuy~A=4NJb&~YtaPn+p#g^})4=nt>=seTCFo9sosZQ9@d-;VmRv|K*8 zrt;d1Pj&#T+vB4Bf7X1}4y>8{`9wg3^U_KH;t=1($|ZtwRjP1i?6qL(lZ-4W-(0yl z3G1PUu#DIN_^95B_Lx;5vKy$Pl3vIFf)4caN3fmHFyushdGYaLF;nkPAMrHc)k1)F z^ie0W44M#OA)Hp%4PAv)cVV3*xi3t@GS;wnyQM+@wnuWI9cG>U*c=OTI<31l&2H4P zt9z^K_nV5bhw$-(4YWYTQi~@qPGvT0F^Ud#a+(!5C91- zLKP0MJXwC+_+7ecHwlt!qbt80513jEdNkP|rFpJ0b5Yg4z4YfSNJEyfVMds#Jy(Tw z4)T)C?H!hVY%PeEy4yODZ`>3b=%WsXQ~6&0ZuKrgniWkF3&1G)W zq=@rPC<&Su4{_2C2I|m9v4%!ybt6X+)>INeC@qBr6g>IU|x z#sf*Ucp2E|Z$Et({{}Q7fv*jg$QRlftCbKz8eydQw!fT1HRYw)Y$UCc38??qx*cXq zFc48xi!zgVXJ(HNeocSWjq&=(n7VTuq+^##5&ytVaaiY^bvKBjbeF^RTg#^+92kQ+ zif!Yf)kl_cU8Q3jWz^=)a%I+dY(ey;w8#4KHjEl@SU4jVu1um+tmg0Xuq#Gh=5pzL zr3m=2lQzK@)?DuTMwRLD!Md$7CL`??3VQg)T)4VNEUx1#yP#txh-c)Soe-C-97#K- z&*Cm-o|aXS!-(TRP%=h;24QD|?oK{-ViIlpYx4NWSeE_x!-(0wQ({c%ukpjKy9YOK z{yDstYM(5BagUPt#JV01zZnxRaC1{Qol6pwmO%6qSNFbgR|x&ScDKI!dyz#wr{>CdfZrgXG2DBI)7o{{!2J$|<}|=RNdXU2a~eg=N=&zHx5z!L*;w$fJ0Y zft~mU3jJ>gw0$dW!TV(sIQ`1)m3N58BZzDYePNW6`7JjPi<+dnVGTl&G`l(OvL^SUB)-s@ z=S?~Vp+8p}vIn7S`&4qK9)sW*qup>_Qiluldf?c|Rs7p#9iC|LZHc2y|I7?OyG=8E zmoIp(^ALXx1iC+R%)wYuRNBH7)!Q04r?5UM4YlJis{$dM_q$h!)-GIWlqWh}cuCl%WP!ASrYdBs8%Kfu@scA!>?adJT)lFJoP! zSRW-dRwONDC3e348@D@L6k?#squ{AUJOg^f&%7qB*WSUoYf>6?G;n6tU{Y_2zfbNz zQqRt!Q?*7utEVZqs>!-@=oWW$Mo8Ekko^G>h-cy=ff&20EYW+L!^LEr)ddrm#`6>h z3t8eZQmJz_WQVD|Ol6%Pd%~NYh8{Abbz4?Hx0EQadm)T4o2{Z3kNCIKNdnMgfuv0v zlITEE|Jw;Bd#tsbGqIzaD{xD6SI!a})$DHBZ+2U+`vIDbFa#T+vTtCO>peOn^wagN z(ge(Fj~2?m<3Y|2qJjzZYfNRbkoelr7C0zA^DS6YVt^`Ad?Q#Z=5`EFX~_HOP;D`q zCpfwSQ*<4s*(45(CcC9)~tph)rV(!pCNB!fTeB<820X1?~5qH?pEm#D|dJ3YA65K5@x zx|bzex7Cn}VAn}9> zJk$Ebn~R}B1U8-niIZCy$-i) ztI<_k3Zg=i$i*``Z#e6xfR5)>`@@HY_ARBi+)ajEsCT1!Ju>DZWLhfMIy1TfM?x}Z ze*!UQf7=M81neb`@AYaxb$6Zl=Y}(K*PyKlW{L_kcRNFZ_ruoya=F(S%`ex(s)krD zE75@&R2u6)j*QifJN3Y2kAulpL)YG&1h2fav|-p*2Vv-*o0B}(w%#v-Exf)Wu{IJR z&Q2B6ADQ%_fGp3sUfOsq`7jRi)>DV=)Y+}0jFl=yGHOiGviW0St&m*OIH{3Gu zK4Z|(iJBV~S4%6LN;b)AY#82!e#_>;4`#eteq~^Q znTr))QlFjSOGN{L>{iJC;;1AG8(gDJl?thCDDV|S=A5?D>E8IA^-@9a7tCyk=u|~Z+b_D zS^3WCjq~;HsCPO(+w1xOI%L`uWCaXka#J6+-3UN%iKw9aEoxFFisKnI%2!_9SWd|d zb>Wn%PmrL}g%(X+uQP>pTe1K_Yv;(KjKiP9qA#QfEMiDH$^kuqZ2x1AVx`#enD>YV zWuT!l=3%hw48k+Eo*F8`Ty}}WF5wxvq!;kG3=|fwPU`-78Dd1fdHO9T?Kf|-gP7=% zGZ00it0qcXc?axKYE4SEvH(2t$iki_eWwi&t|buf1_tVOu$^< zl!}=27?Cjrf^15n0h-LcG%uoE5uy5}iATn1wB0WnKa?}KP}u+Wdj3c=N&-GC)@LV0 z$1vt*D&hM{Bw6xNrDF}|+9yF+KI!e7eNss4ko`vgaO?67Nr~P!iCrmmc=7pNgWqdd zj_@X9&)WX6w#90s!rSNeT*9q(ingU13D7iX+P*G%hv9h>wRA~wk%%ZpiEb9SsKy*rT%(x z%er1T^cA1nX;tfs<058jJP6GbgAI|@AcL32moiEq)1L?~4xoUR43L$*uNYyp zIYi9l%z`-{L>%fV{!P{p%^;qD;YSLKbF(P38CywLSA~X80IHv1)rRPT#QKvDqY_N( zyvTA7Y>5r9T8@2T?#cw+*IMG1iud0xF1zk8*hu<@SF?mOOoD&L6{ZUO#?j8W|4e!N zd#LO4K)VYUR;>IabRjlgmfy>Gtz;pIs-5Ksuz5gR%VD{)TvDrM9r zpqEjXff2V>Oh}sr<6QB_K)%} zTkFtAd$}`okdmE7tX~2+nKmuiLWJ&hm$d4G3(-kE()8s68aN5(Zn3=0*ZLf#WC=?^ zrnr1oD1-3yd06tW3)do3*t%;?a>C%Y!>?ixs;@3o0*2ziD+nIy9um@QSYCUsqg}&Y z4!KZ!d6O`_P**c^$Bcrl=i%S9Ce^8dx3jMzcIDlW9+=f7f&xN}5TKx5n0KV~<|1 z_eM}K{MHlWBjn{T6HF_Fc{gz$i!XA-qKb-;X!yP{MiLItk}3g*JEPwY{>w$}~_+ zGv;;M(6X@m<51O7mbX!pUMra6f^Y2U+hJYz9O|CHycl}E7-yKjK0FM|*wOIuo`mIF z3L}?tvrcmReUqN<3Lkq>qpfWw(R3L-bop!8`sSBzu5vUyhoj@XbPetbvZ~=t6#$2Alh!^a&5b~ zOWG@QxyZ<0Ny6l?9P(Hzs(DIR)+<5Doeow#4l?G+0#UR^W;r`F&Gv)u z5p9>MnUmW*Qq*09$FAz_;)0s2$`ul{+2(SA6w<2tJjgnPF)$xJXyxICAw)P5fO>~^ z#|um&)ZjP>h))29M)WGsuKsxBAV2i@TFCl>UROYmf%Ov|`v13EMDm}Mb+YDPsOxKW zpGzJLNRHRQWzp$(fdOM@=vJunxNCx{E`zr2LfIARF{~M0Dibt?6<&2^XBf`mTp)8* z;o5$Gcd%#YyA)edP-#l$+u7V`xdO{R=4iX^RWRs4g^i!zTmULs3G)4& z9&S4_t*sxw+2OsoirpYU>p725#amJH%2JUqI!Izv(q+3SeEg`l8_OmM=fS!XBmqLa z*AyhBDUMvCGWH&G14`SIXPN09pzU@Cec>QEL8ft3>5p@9xNy?pkKM!c2f@BicSnV=Wk)V0XRzI-$&4Ll(Mli9HqKzlr! zW>{|~l9~iG2vs?m_W-^XbINJNUTFkGB(3G6U;e8$-jW~Ux^}(^B;7B%8CMX3?Ep>x z&j~$QvfR++o|knuzLq7MF|wdC zvG<0waTAJ*LHYA>_Mi;*pO9H(p~v&D9Y0mSNF4y=70|yT^o+A_4+7D)K@kaP=Vc7Z z5*u&gSF?*CAblQOHRH7T2MwYr!E?^N!5une72Jv!IZ5<)F)jB~E!UhByuNer@3wp6 zV(5X#F!JYQQ*N?A4gD+YG*ty1a9v>uGf)&?Z_c$7kY-*1bTF3E`}J0M1a`HZHB@8A ze&;?*#}tOs>E3Dr=C3E|;?}Kgsvd?*nktk*)%hY%8JL$2OW7`6Z!;+=Lr7AJJ~uk5v&vvHx;ewpJC-Lj#Zmjdmj!8 z+a-2VXRMsAvGaXAk^ko8bkgG*9Dc+{s31?4DKq@`RW%R{b`%0^6Sn5WUgc5+HgRx< zf@y*=lDB;i6oaHy=Ytr`x^@UBy)7U(M-QmPHD)RDbflics`-ZDKMyGdg&c*(6>IxT zIV%wYiQTuV=}v%5zxA{BR6tskpIdYq)5a+EydARQ*@}8iZB*r}Qtf#{{mscY8KBFo zzBnaWBYA5aY!!ZxvpE^a2$C&CsCT>$OVIql5tX{YvKnWR)a#Ex# z&O+}JYN4OS{CrfCYEJ;g^_y`W>wtWx``?8FL~Vrh2E<5_ah7 zbK0f1^d<5XmRdgxuqw(AS=%db3A1)bB@z$cS%ew}*qewp<>|gIzK9a9-D@7~KmOu8 zhu2Jv?Mv6|C4YO3{9)XngWvWKhUU<{D^ZHwZo^#ja-qwK_36ah`2eCui9nCfp$2Ft z{p{ugG?nR4VS2Q~F&;nKae;?#r{MyC+|K!M%2Kl-7sc%LAm4ZEk%{8{m%HXb4;I@W z-E7=A7*1t2X?7>QoK#hG<}VKQ=NK%OUgGB^by)C6xE73i6g??3W+D1ql8Q?r5GenH zo7Yjt(FJEW?8JBxX9T33E4}}s6{9N!0DHdFBOfiQPzdzU?Uo;q_V~jod504E+OEzK zV5W4P_?H3T$5M?}*@D*?%-rJ>^Gq?d4$Vuh>iFwP3cPYz<5C$XsuKBzZcSnNXkTi_ zM5cAj^Hc?|nQ)_{>TUlvKEv zDX^Q@Hs!1lT0YpQ3kEC~KIaHK%pK5|b((pF-vy-3&y_ zi`hJ&QF8ixD*+e(7zW{quGS0hfi?_$Sd+aqd|y~Uo;<)0f`$O=pt9ruOJas6V5${o zIg75Vw@e#9(YfY&C;UsB8V8y;_0`AQ!PBBE{>IgX{Vj=kJZBRRngUdf&R{`Hk*oaY zygx)Pkk*8}R)jnMv*I&%O~>o)#a};kSH{xMb$rkT$JRJ(2-Ku!yUt=1`|s@m!f%}4 z&vDlSt!&7ml&}~uGI(Iw^`(+2@g41Ze9BOjCCi$sTpp?Ffw>9BJKc8a_cS!XTrO$P zA|=T^K3Vw9S|Ag4qv7s)UyD=aO|(hnA=f~@OueA%q_3&ktiJ$&6Tr<;{)kob&-B%U zMq$~4lc*f@xHKfDI}1^m0#-ohntLP4B9%&iw0P#7jdr%#f&hC_&{p4$ox3U_^Ka(L z`i**03lhL%ZxiuFHL5V1fA=SzitI-CKRNv=aQ`<(Hq2@g&VT%5F&)^l^mIS$foK5G z)4jZam+U5(lFLL*C?{Y^TFy*90r2_5n<4YR7fD@w{ zC&#dI)ZDzW2Dtlm!KKX7bHi6|D)MPC+?=|iv2|M$%(*=gn4e7jJIb9}H@(L48~KWrNMD7YUhIHYK{Q)!+A#}(i`60B>c$%n05vD1gz(9h(Y$qeMzYpH zbq^{7JqMibyRTsg(H^S~kC%$<_N$Pe5q$SS#J+L2As1;XIpwIa`ztwzy@_pKE0UjV zXnJ}rzCrwFA5Y0fjh)&rBnMMkNLAgJLVM|g4SGw-@zzBVef^Z>ZtmeOPO4S-;r4LD zv0fDV?s*`XUU*7MI~(W^ugEZeqWDg_m2DPHG~t(y!Iz+b3~E|3mZUS=wX^vP++6X* zY}&CtN4!xc^GBkx5~sgX;)d5a9QEs0@Lk8yxgM#-wLsYoO)21HQde-*ATeoV%$m1k zcKNr^pUYAAkm4CbAAeYbUfQc!SNQI!SIlafYRdG3L9BrX07fAY{UfX{0}Wt$Spd=- z0wR12J%Jb;NG*c6|G2}3@yw9K!223HVxdAstVgb9f6dY9%IOHeD%!OG1EA(SZhX() zGr8S1$?IbqubGkz_&>%^+c|TP^G7hvpQNm4hb`H?Q=1w#CqOL?3vTPjj(ukJi}Xl$ zL6OI+a9j(^Dik1o6Ywr|$Wi<2HXx;DC-@5?ph5vqvG){d2xkJNN^TLihXIZl72?vvD z{*;v-`_eH*MsvcNr03g!F6E>R5$PNmu423Mazbl|jqBlm^M#-b!>?sb%ulF?_X#Q2 zoRqZF=+YKvUXn0?SW@wnqcW>zBlGl5mc;Z#Jz;YEE%1v+g?#QX8np-YE@TjI%OOXX zzrhnV@Lqe1-Zx&Y5=TR4*VbZA)&}vnPHvTr!hKmX(U+}>jbKO>SwZ~Xzq=%Nc#pE0 zE|cZhbOqVoUu^wxNyz)dk*T>_frGt9u_eaFgl!){#$Z*v8+X<3w9O zAU}a=uqwZXb46iBk^l)cU(FWN^P2!<&W-Pa2?bGZkWo=1tw(7laZi_I0oeC!SiMpIg3Roj)55` zgS5Qj0Gd`GtDyXHOGr=z9NTY)@MdVTvnn^ktj3otv!w3al*~#lfNEGrFTxKN+tv8M z{FKag{7bwlcszcohx2UNr!F%W`w!ijf+8#j@OHiMCbWnQ1!Pito0{^VT*ES)b(z!g z>RbTG@Mu#9$Y+t?RnG{?kv%3H-|aW+?&hU1;4C3L+E(In9~wDzNtKXeP|=&>JUuo| znRve2z4gL?@65PkG_zJ+|or00y_oq%qMIEscx1X7%A9`kd}kl`%t5QoD)AS@jFzB!C_Z zqww3h(y0J$-VJX<&!SH~9{k~Ss;Q@+U9?tYKYRvbmws2tdQ^25H$IS1qqkDQoQYkD z5@2HL`f0BrOtxwUnt}-yU9cl8TU&&CRpkdExn4kvc{;c$C(^6no+@-Ddru?s+!ya* zP5gF)tCi>5Im(weIYH3BC}9&P>(WT(eZVdT+H&FVXb#)2VO|m^RVqpx?@;}@WS*h4 zRE=_p;eaz!?gGY9gO%W?$;z1k){x3qr$11H>;2WReT{-?tpy#>#9_=eQ^fOTJ(b-& zY8DOe*b{Oq(`p@H3`otLWSx#@TMLMa>#q2*zy^D%q0I-;PUPZLLZRWctOLoimAV?w$y;Ti@vpeAh zBnJ29I!6w|ENYg)2f?#a=IF^>9OsFM!6>ElcEGI>>TOauXzVOn`%{ie6zLx&g|Dcm zo`TY?U*)Sx->hWO{@_YQVps-89T&S2F3X0(7e*YMZJraeTX zSLg>2^TUgEDc?g6+fPaQixwgW%PH*YXsDYdR7TZewn$WHkrY|59>FA=aA6ST;-v6w za3#+hddEpAhiL?TQ8$fu@3>2pG1EP|I?HDJ@KrUUN9sv9_u%+g?RYWPCt(^e`l6FW z_S=scu6(XTrq%@yhD7~7c2S#WFnQF;5+W~&96gs=3ipy)5~i}>{xpX^z;#; zk-?1nrBoik_hOyP!ECht^m;5VjVF}9H=TVA!a(Bn16Rp@KAPO|;N?RE?{HML@Kl%G zbX$}}POYX{j8%a-LEXE~NkCj6y@BN=W46LnEXDK%>R5ADQ2W@p{5Smc^xq}m#I?P{ z!5YKQmK-!NRtOu^@^`{3e@9_?Cbn+3DI;xdxm&(M_Mn`FqA_9^m+cCMe>Q8W^ zcRun_s~6BF?OnL*_HvjGNH^Z4k@-qYkuEARYP0v*4-uR-i!L6kN9AX4*(28nNs(!A6|y zNfP0ocKD<31AMWtX}x9S+1^CUSzjZ4?($&whvyRGo_!gYpcE))BgKKdI+zAK z%`f(Q?^nH>?UFL;6~Yp^QMlshiQ^0=Gw$Oa`8Aax%>Bkcs|2pH^s31w!>Vk$Z{5sjW>x>TIgR@t2=Z= zpDo+Xn=ey3+c^ zJul6U(H)&%a)=8vAcd-2{IQ^&5?u9DBp}KI8HDSJd^c}Z|5dh7bSU)LIZ$jKg2G(PLExBeXR&GIzUi z*kuGR|2hG7-$ZG6e`{T@41(*olgunkbT1i$MOP@GV2;r{1+#_G<03^4Q=&}i+z+9+aN;zoBbQRy@E3SV;o@euf=NT8FvO`z!{W}s!92h@}JNS zXs`GRoh{Cv?&~Y>xqYX|6#vC0eLD|+nHnQm%U$*B6;)&$?$N7#@qkwy&My`Hj23;@ zBLZ_K5*uoRcsKglfWCIK%rPyk6j;byEZbJF%iX+)EZ*FntQ+#J?C>O=6(kCFnlk5_ zF9u(6730e+ZRTvsK6(I7uqOyuUWP32r?tF*bl;7))kY#kxWdNr#+yDgM|%; z*Z#Q~2Y-2sKX?cLgv;~+5*oH6?6-ei33%2(YatH>n`*;QN17Uv59DZ$B}( zr1pd%)BC4mX>0by2kJ)B=ypNKb}Lz&>*y3DhZ2Y%=Ixdd#TKs-g}>N_fwsP%tv7q4THni=yNMH zKj2JKPNrwa*G@?%Eaz7$Hzx^JKWxNPKr(Yk>sgN!O9q+~ zARjXNfeMe@fnmuO0qE4u1E+9nC5$OBvsN5q-$tKO{4-67UJS%hvQmnpsf0N7L=yI& z!yfrXp1gghWSQB%&d6zn{&mV#$(3j-6Y#7<>k7PdNOnBn^3~&AZf9b_J^7?no%dH2 z3E{*S=6pA30AQl#tHPJ5d;Fm{()549o4<#eTHNYMaJ}&w;u3^c!1s{X1cf&nQ}#Mrdr5 znH_kh-kr(Xs$e?HxNZ^HYA%1WD(-M;oqfcd3%zFw7!TN$D=@9Gg2YN(7C${zCAxO6ourg?E7!_9@RXZ>~+&~!`{m&L|}d38u-KGuT{z{^2VqG z*Np`QXVzl!$*o>PezAb99zpj|ci%dCWIq34ry1<`tsy-WJQN;@f0@GuI%_-f?^}lp zgibiO>qFcR;Zc7u{J%rU!lUK0vrw}2DPJ^9bNGbkm2WXcAjkbGqL;|emd*2&()>!x zlr`%Pka$HvuJ1gCSChiME>~&R?Z`_x48f&?_nqpg)GTEMO>a$C{N)qs0_K`kKii%x zheST@bV5b)6-{3hA+O5?K9++nQtqn$l5cF+qOdeXpBi!nF*vF*=K>yzZ8+kV_Yvj? z?+K^%X)zz@>^ooO&TkUYgB8046n*M{fYSQttc@s;Ez=vxJS@C?sO?hn08%+&W#4M8 zp#_*F(a_kgxoXAqqQMVX=`=I%^t~TKDG`_YJOz$*oNg5Fz;)u1Q2FDse^W<^%W|n+ zlY>sKH49Y;g|e@dv*?32if7suFFdb`jr(I7GRw|6utX$wDYNE*HOC-7)2=QLZ~-=; zkE(CQcRo(Nlo?^cxBumO=|r!xJJ!WbwZ#9222Q%V zxu_!JAwy19!PTkL!q^j0g9Lo4EaDuFVZi$vKy!(3@*(H7SWo!~jWZ!4eSo$7RB2?u zp&M5_U$1R3oHd@YUcI6F#R|Azl{!r4?ea2XT? zw^Bdk2`aMm;f}SN*#GP%G?mMp`X(;*VzgjwNZVRM=IyXH3>7%@>?Tf1Dhl1=i%kF>IqeH_i?)+Dl^g-uPsr)iRwGfsE**ZJ_-{BQM-@_TSS{Ev9M-Xy%A%`ZEHTjQh5CnP6s?M z{qNL2c{cSDL`CJpr>xJGLSG-HRP{S!x)dH_gAd@ zu9Ri=c6T}$`_+22v)xgJymwQc{vw;1iv7{>z}+dM<(ADA#YrlT+RRAzLxAZPa)|lYrN>S<&^l1}#xTJ1}6;v?=hbCvz zelf453BgtKsS#|pwIxW^2;-9%Y(lu=?q!{GI%!)CCrVc$LD=iC2~N?-g?lz-v9Wx>#Demm z0wRUnTgXTy(kG5gWF8c6lZeZ@>F6%29{x9D49toXu*3L;|4})4ak@Nzd?Yu53{VUv3ngX z?W%ISsE!LQGwYw)+ho*h?Sj48P0|pv7NXpR5EmmjE2uG3zmxcXd2fWo2_+sBpPg4? z^xCfitN?qEI}FUHAInc-gFD31=G*;Q#44lFi44r&{s>H!gdgEs_rwpUoi(8AvbzU8 zpVCgOkF9@K$N;M!Ee~Kz(bpvNW8u-TzWJY%{bbw0RUH0@)@jbwsBT#5L{ko(Yp>M1 zQGcLVLAUitQ4%?4r1xxs`V?k4#>bWmSfi z^|FIkCFLea!OL+@(M*ZapW5u2-nH_2@>VEuezyhqkcGOyj|vWdnSHQP4#04m=iKoL!o%jwS08F+H}?I99Y7os-diC8kWMh zGGXhX!+twkpy}gAbTtr9`Q#wd=Dl~WmrqyJaSj)3o0`6*MRo`BV>4cVQ7(Jl`K^*o zx;`TAa>04b6`WNcN!Z7tDiCX-F!z*tWTPpt?gyJ1VfR#$2BTC6Cn1MV-rB(CwkI9c z3Zv1gJKG9vr+aIvJyyV?qwXtUc*BMHPgQf!K(eIMH23z*N?~Zwe>%>`_iac)U#kzD5}yE^cDk~Q?*1usV9gAjjCBth`6ybx0W{1d6WiFR`d#{7^$qm zs?yf3gy2rRkAhI^zg73&MGeJ_kI>oX{IPy0Ly1#{=TKmrL-4cGZBq2bTvL3eOz#E; z@d=zz?l|G@MF;KrCto4aINJ#S~`ks!rZ5v;{GH zXszW3V-!cBRp&9as+Y-&;Wd1qk$!Y-C%)}EJcJjuQ9ZJ}xR~d6_#bAaTDX5EX6R~& z+xMu%e8#C%2>6?e7OQLKFdftTM-dPxQY8qtP$>b)WK6hK@SS&}vXj8o{3Oy`(qd zN-|AG>*Mhw`-Qw3+PsayFOvuz^_&>5EV$(luQG#kD?MC3Tv~G#8QXL@x+~#O1M_m4 zAv^crf#Hojh5Npffm@{kY_Aiw?f)i7PwS#%GW*%;K>r7`yla6*majb@ zTE#zY=>Nc-d2Yqju=q!jZ|aWLdm4;xTl_h2V~gpAs9;<;+roZ2pPDdrjiCBU$-=}~ zrDC=E#s}6{4IQWW{u3abMO!J^&>;mAJ)2Eg1?genM`rSK@1u7no>vZYWm2saI08H0 zuIz6}GxSP1{6gfsTm=?2>o2_wR4Yd`*pulk6|3#fGGVDvl)FJhyX#H4Nu2>9#n!5{ zy|^{F_+Q?Z@6kpFK)gOXp>|b zaDQkn|8ST4o9acqm1yZbH91kE-?y9u zVVu(NjrGj*r+vz!2i1Wy+yxsVHqcowb#)e5LC%jnVb(R3c{vv8eG3_F5E#EX+sM)L z_GbpY1`Epc2X>nql_<1|Pwe9YIz(I44vM zR6np+=@r^gj?3t8VQ4ZPUSkP7J~I(fe9ehzMXZ8oC~N9>EIzFA09^J{T}LU28lFO% zVpJb5ND93jP;tZ)_&E=I>!R;yozrwMq7SbRoiHA&FB_#$v_MQGaY%eA?tm4y314(J zrwF!T$kmTrKFg{*wzk|9Um})K9ND)=0+g(}@`7KtHlGsRsAVVa9t z9+f1XL<_RX|HT!Guu`>P&X*@oLYsBLiKEeVB@@{!t%VTd7sk>ytMPTuYVNj*`R1GA zjeY9wjATqy?C1d1-G?Sb>;}+*y%n9gv8K$n+ySl~Gt%O-Ju=(x>|09E;19{gJ9AfV}Y81m1y%{RDNSCxtuWiy1Xy`=@Go!05!1X{q|&J$s)1ofAb@Stk4Xy1iYa0EweebW}5t(dJOI?umDDmWNVHi?d6!Nfxywju||hDcMa`-~4QkwAFfA zD)IW;7-SHbU7}kjPA{9V2_`uAQm5LS&Z6F_&w5&NU@n26Qq3$zh>D-hru9F=EMj`) z{8R~%0Wq-rx$5&6S!_lI6`1Y<7GM*f_*zT6y_?5pdT!guQYG$T4yJjw^_BB+3PdZ0 zQd%^WpPW|N+lo#g4^yq!xfhhr%&)|+HRJpn$4O&HKH|6FAt@HyZ(2=H7eE`xm%LP5 zl>ahS4-h~>)Vs40^EYPDCMRaud^7XKikso^^+`tOZ|L`PJd=ZtIlxtYH|>84xJQ<| z-y9pc;=aP$n15oW{vJ`Vcf6u$R8StFmUFl!l-f(BJNl6qY=ifyI92R> zYCbyWJ*b7r&dnFBY@MjghuF?a?yp86DkJW}*nm;e6RGUt{?Y2urO)myy*BMjiZQWK1Ky`R{2Q8<^oM7(DlWr@BinNApdIN;6l&8V}bT{y0CW;DW` z9-)*FPyxS3s{Y{`!jdSFf$p7x&tFa@Zf7hn)XE}9;itY=LG8Aa3PL9_auNU0Xr~Ax zzp+&#Ua+A+H73?fe~nMgtAwZ_-|g@`Ny#z{=#X23ZN|zg(B`s+SI(5NCm_YjglDZR z$TA1&#O~w($uPvS48lMM9}Uz%nIqW*lW~|}qpI;TEzoR2rfhzNScWf!-7g@1^%u*v zX_UXVJjh6Iy&Qpi`oe1>9`DF8_QmzGB|0Hy?Knj=>~z5Y45x(T)WvO{xiRFPy+}2n z1*pg6E4>66TPKtG3x<5E!gjg>H4wG-u`wrlr1j3WY5duBlWTg0N9mZX)sP1ijQ@9a zzw-7TSG^?Yf;$(o1*G5A0$`%67HdHB2X}w&A7X8eq&~1DuxXLyUINHKGyqf35xDP! zQ++2~0AQU-09V&`jcB+q7=#GeRyKW+A`X>edpl$+vHUXS$XltN-TZ@IP;qWRBYo!R z(8@IvoZMx$@@(Gk%zS(_5)C(hrG)z=`(E3^*oRn3WPRhy^D=DKkMejd@+^^`W5~OX zEg|Cw;_>y0EIECm>XVYhn4BjBES%XwrRbK>1_)q>wT%5jRKh4%g2Y=KtvipI^{|KhCe)M z(c^TAP|S4)BsnY~KLf2OVKo{h&Mrv8r?#!Ui89kIVQdL-(ohEl%fyQL>8p-J>otG+ zzf_~1{3=bd{cXBcX$#!wU2Wn{5K1*-ITUj&6rWgLAB|S3TvjExcAtZ2u=9aJxfzh5 znDHsy6;wKU@nQC-e}s4zLMi}HWv}-(Z_DZ2Ip5ZEX9DC0fj4XMLyw5h zGH*SsKLZtrm?Pw1^I}@Z1sZIDhp^*%A|B_&%UjzoSwY{@d=p)HJi<9J4NclH45C&U z0L!wrE$;Zh_`mJ=ByDJ|i0}R#MTyHeD<{F7iB z(GM{hxX!1Yd;o8yPj8M5?$I9md;|Nv;;;Lo{Md71I(T;u%vQ%t0^S+<9 z2k&jpp{}OcAuS{9WD^%oYU24Cs_7mgtNNA9+h5C!qeoZ=eX};IC$G7qetKV&%PCwu zTygGlp9yK`9(Cb9jryQj{(NaoQu=jPOOk>kn?7z8ZG_Ck(FxO7=bN6L4sSt~0j889 zs~A>c=$i7Tx3kLJHTvwkNdn z5m`cB{}-bQV|+$BJ83^77lu=+T&9M(Ue!XNgi>-N@~14L)#rtZHZ8qe158*R?6%oz z$?-p>so_Bj^Oi-ODUL?i8M@$DtOnolMcK*_Hneu;>_J63>;)84A0C<~;9&*|NvngM zPOfRN7}JAfHc-rgljBxV!A{viD@39U8|~8y*$gWXXUsquo3U$0>a0G&j=9aPawlLN zvB}=bUvOZnxDy#W_8ak1w8GLqC0E8i$%p@dsIUvR% zyX#erAU=vT1H9TNJ#%v|!6II#clX6RbivkCiUx<-99{bEe4^uzkbf|63Q5mUU2tz$ zZ36hwGs|Jw%e+4n0OB zvURnMLluV+kcXZF3^R2>{WpFeH*~lq_|%Mryr?oY7oo5f|3moIL2Alf!z*f`ZI2HH zi1y>vER7RC8FP9*rj)WO+9+~g#Jjqa{egfkJxyD;6_`HcGpeCKSMI9t!0zkitamah z51aKx__P=v*nBOyJ6v_#IDDDVw^ zBr&k3*pD|9sJP$m=(zceS2GPd zReWcOd|EvQkVW)>FrH`}4p6)vWvf=0c_8&MrvR!l zyWS={^PB?{Vx%tG-YMhY1y&|oQM_An1VnMpIhlE}B$8|BlljHmEzvgHCFO?%@i7;8 zYJ+~@w%bj5MC~%K&2whz&=-NN;t6DVrd~EXP;aLbKOnhpN{Epg{BK!C@iy&yyV7xz ze&(Lu1YS9ra`%_5$z-vwJbn z%NO573GYXfJ5C-^V?ysB3;8toM;cCD&f3?464=()Hd=W5c{F&gQnH%{$>4p9~A)8Vo6Yc$SzmP zxPKi}GlM01bF}Zu<~<)-lkUVlz4w$*ph|Ii;AZ3@`s(_O!|0TEf3b}JLqPlW!f^No zPg7}21h(nN7vD5~cEe92jfM#-87{*)6Ys7Q{l#=wwKk#|Lp+weN?Tc*)Mxu-Qj?U; zq@>O_c&o~G1~^U+b=-2)687`SO#^Fb<7UxzsE>&ngyPYSoUULF8Q&|!p!cs4qa=n> zK;Qsq!H~PN(LF@cTKfa2-{*M(F3i~DR#t9uNA=_8QeQ3p&_HWvSykq=`?AEeLC?Dy z`)%T5=H<6JBmb7n{<*C{)!2!b>9D@}-*WAxrO?@F`|%ctxyF(0h1DHl(tX-;6OXIX zah`9_t)PqaG@00`YiRi*w=-f|i%nV7w5g%RP4wcU%HA@kmbZDXlaYl`h|z&jxYdtz z#2_#+lFVn#sOfyGS7PM1;34l<3dlNe4$XEY7#_NDBIPmHq_apH;ptt8%`obmn;Ry3 z(Ol2vxLiuRUZv_A+>W9Ba5J+M%Bl9`rx187YxB$Opk#c(!=~ZO>QKV(%Bp$atj#Sl zklC?5TC}qY)hc5*;z1jz4;4+JxrqrGn6(RZp;jmLT>MgbbascwQ!Qax7}prIcJxNA zQ^hI;>6_CZ>N|xvWGqUcZuqtD#oaAnc;eLPe_fy+^`5CYD|TE&&^2?Yt3VME%PoV9 zeY{peX-{_$7Wh(n{L}3wdOy-Dn%dM0J$!HIm_Ia@Dt#8cl6Ckv8Xai*p!j>|5HD`& zal0th=7SldvYoewU%nYpf@`#iZTBCdMvR+THf~54sL)lw0Z|I$PueQwi;Zi zld8j@7fGLJhl}I_RbPY4z8i((*!gzheu)|RaO>vvP|N?Xd~*~!L!OO?9vLgA@Sw|t zZ7v0Ocj7{8kNb4(=Sat%jqz@H6N)CYv9i51eq7pB2*_X@N-#U$xAU^GaoSkp%cBZL zxtz`(JSI$_fl_6|;`Z-~oQd|u)4upbQN)nRedA2ck&JsB=wsX0v0lS#UFQ%aMbrw1 z`r*t~rQegAifuW)YAny0FBj7kkQZOxqFTei+MbJ&nr^=o*l8RgxZ-}llFSnW3oV0d zv}mMn|46`Pq^GG%EO*bIBI@RuzgrZG9t1Udw1lFDkl%k_i^uh`C64f^$o9s%V+D4H z@+a&LtNB223J{H;SLpKmEHW8>EbJw_JKecXS+0NRwh|TnFCCF$;u@zec#Cqf-&PZv zD9I!OY_(Qp4FhJ&Ppu9aZ&oYXh)nyV=zGwf0%I~cIi1XV<0tibgbX#jl&6750&YN} z_^u_2XYPdOg~v~$kwu|&zUM_8mPYrMTx5K&PEilA7rS>qq`yuM+=~8q)mQtV8&v#G zW@gJkP?|Y%w2+WuV{I@(xz_rLHqUOwu{w`6b3TT1K)bbVPVVhpx3WUUi@oKb@`k}=uqni6R+_5_YeFHvpOcq`O8>n{uL4CK|s}ICmz3mB2e6lyTr0^-p_Fjg( zM!X2`Qqh-J-M+pY5UsYM_3ZVG;idD|WR$zuke8UpXUW?sDtEr~h`BJNE6?r8+b~VK z%P$x%8`&T~TPl}yFS*YV6{W7sbIi-x#*5q9+Yd36Zy6|YvfO;2WhFR{%Rz^s_`sN#=IL=6G?Jd8a6*8o*wdxxwBs3pPE{TgmsEIUaaWw20mh@pHed zra3Y8AVGdzFAUwhvqydlGrQXOxceaWfKr}QlT=`J$Xv^?KSVY)XR_r8u4{RyoJJ9B zB30=af`aG=J!+WAVB0EIL4P)VP-xB%IbzU-xGPARrm0F@zV_t4cA>Z>W+;5~R3#){ zHeObpVm6epUrboe|lr*22l(iR+yOK~)UGYI;Y-UHqqIySUCY_XR>Bue_CC)(@Y>HzP(`gTW z7srxa%TDUQKM>;9^%%6ghv-TW{BhReenZM^cbYfD*S1b+nq(jHm+nnX-|3tv(aDXj z$o0B<7uAnavL6TDiPl;-<;sjgR`k9Q8;w-0kcKYOias=hu8D-MkpdaKi(vBy^v|(I zRflHWxdh#CXfu8;tM0Ql&CpGi#g*5taz)@4w}{kQJr)%|a{ccY2S8aL`7p>_0;)eN9Tlp4d4kLuq!eYh!YVN94k%Q(|&f z+T?l9tFU_7eCJ8-@2>3Di4}h98Y_}vsR8KxfRov;QJ~yy1-N*KOeh07fK1cwfhE{l ziGZ>k1Yqum$57kcp(vmz&i+KbrWgCiZ^qLO-Xu`7-Bl8+xbVetI8AxsNM|)Eiy)hA zjMz-T`&@Obp0kmg+S9T44rc>Ym^xs${1J8R0b4~WuL2w?I7yny6ml?*eHKDVtoUC+0ZZjS{z8C^jJ>kQQJa(P*Aw)w(&r7z2&ALI7o7a0kj9eo zT3|tum*=L=xbYW4E*-q`){9N(w3|o&d8VA$$9%1Z=P`wC+<{^^ zB7lF6J1HAbD{iGE1)C;3kVlIsU585WJCtRa-W~3oT4gD=+J36BT(CVk=KeyUF}zap z)Aa1}fr+}PvYc*!;rcAaS&CeilktUjVmjg&Mnq4!=X+$ zn}XKc!dBfw@)1c=dE{n?*=XhuX9b;jYu&B=TNTLAcyw)7RLDN+5#;nYm_%+EjA|R& z4)`Pn{!@g6apSR4d9S$TK8%Ks1CllFG>RZLyFiD0)9Ql7mfwqM8@^+3>7MGaAtz6n zlJv*>)gqtrC%Mr1sFu;jI~sUjRPSm{y6PagBcm^H=*`ni9lV`Pv+<1Ybmg*=g2K?? z1wq6-SfNwmP0k?7Z`h-NvOsLE zFH#oSxgEe;*RDY78g%;O@AvoP@lvJg?AOlO!RCu^Qf_sXV$IP5DPR2TQn;EoO-0X4| z2QK#erL%GhnRWHQbpJ<=9P{tFji!Ljs;B{-l?{)ThQjVpO5rR__xGewQM}cG9HLf0 z`i}|FR~kFrn@1|A#N}Y_VU0*mQHy7=wrJKCr!uf?IXv^gU6Fx*%HL{ZB=MNy#fpy7 z3-z~6F1bVl{W}7;Iw!b#@8f3l-sGo-u;?far)Vr^_v(P^4jwL6_ZO^j>^Rid*Q4l6 z(Ho^Rs+rG}(_9pqV7A{gdl&iGLDP_xxR>dY4x$utDRRV9y&v&bOCo+VbDy2txamJD z)%uD1WeFzbI}krn^KNu?Q=>;Y&ANRHqiXZX}_iYnj&;ENq$RJE4;AUP~xC=-JwT#80-0h&^OAN)VB z1#kPat+SC(pXstmdrv{8RQ(yY`>`iXe!uvdO#-UgbM$e7qg$ZF+jU|IxR4-0xn@YP zr0P%s)JNi5axV0!_&53e#)AI*1yQ$d;knniN|M?|{BjSm1$n!@eHHc_E|%m2PF>Q) zFaB0c_&7P^84`Nz_P%z)g0Ix~p_l3vP;b(g+3CLGBqA^?eVX&Ip|{n5{hFYQ!j#8y z<4!ki(DbwV23f>3HgKe=v!GfIF+aMs_3&$n*p(F_g*Oya)^6JlH5eP12flIo6y^Se zBluFz-dEWE0Ayuun*TDT`)JhMY0Q$>v6CV^u|72ulN5&)o`3uMYeE+0kd5`9A7NXa zcCM;P;a|!a78~m<^C!`GN(N7Fd+*)nc^$ecTw#xJr4QM(bm3xdtUTV73GCE z-a;1rP6Q*i%Ed{l8Iu$N{xV;o^vL;B+!e7rcIxu({={j$kGvPF-5ei7_ix>+a&lCt zbcR2E>@KSH$(?yj-NId7gPt-?{sf&^f3g@zrAphHKUK3xd*q3 z0VMIe+vEAM;?O_mOhK{j{O&}xFHB5hAzaop$x?cwDZ|vdAkP*m0$6`Y+NzT&j=W>fX|;`Ea+CWP)0KVytRizX%^4X{k?iqQpu0tHP-BVEIeB%nC&b`#`wZ~U5co9)P?OVP(D?|X`f&q+%x;~q7$r>pl_^bGE-B{(rvk*XEa zohBc3QmxlbmXtnw_SyHQY(o8dOMrvLR^fIdD8St>)6KN;+sl(V(i>I3QLco&5G!*= zcCM0WRA)<@2dQNLp~}=>#6It)<5PW?F`nsK;%$G>^8@UGD^_7xRigtMM9|x@*x~>} zdsoXQlc7h;wmvNI_y%DEo_yx`qid14HnB|-0z?)6d+^9RlV^mT&`Q-6$nuqA-8U40 zlH{N<{&&kwcAaXHS?+E+p*+u?w%Xwno!T9e6)ud4y{Mz!ea3lBPcDbkateJJ)XZuB zfO#&J5;huw3vpJhL1Fg*T~D1cH!-;n_|_w=SVS4lb1}(oM839NI=gtSiV+zj#@$+! zj?pj+fcOz>zu^lirjX-fZnpr+6*53OsDQtdQ?(9N^d66Oxn zh*wNmLouC)AEK^2m`*V+|3Kolo?j-mddAJ?i;m1K4nbw}l{_=o zA-Adz?@4?}%fN&Nq!_oqGyM8TM;e|Xav-Hz00E(G8SM?D$C+SOGf0k%bKix@FK_l9-vmJU-E3SSF3 zX-ja0lP22I$4`6FGQ&#{os-*S*ED+#j>yrn#7xtC+9*3oK9FT+W6)$3;PFPdoOr1 zfG~Io*Ao4D<%MEYOo8QLwSyH=h+3GfIBF{N$Ne{bJ5G#O&mjMwB8Ri4`2!Z$)_Mf@~_yd$|W#wE2Je#F)QDQbIR z&9h(I^j8Gy{}zdl(W+~5xf?b=FOw?tfZIOuGGtI_`dt@tz<;;gSG^3v!8l?z%LOf3 z|CwKWKD@>&Qjj+}{@3fNmYq914s>zd7B(GccoPe%1DUUE@xAj!U>hw>&pzsxKDUJJT*%FI4eo5+djS#0g0d7 zXiL+4ZRkwBQrH|ggDF#jfqO>;KU&rJ3mX$h@-2Er6Xvm%5{_8~MuO@1j;}K0)AQ!p zGpX7nF+yF#^@O!2+z^wdT@9T@bT_Iee%TVp0wy$n@70k~(b?&5T1em4`3~AR|D6U9 zt_lWin$DQ?mfI+OBSo@4lXYLx_h9LoKq9xBvKu(43RH{Rp^CiUGdRBGuX{{dFswX&cEeVr4ni^z?~yL zqpLX1K12*F7G!>ABorA zo>ebgB`G71+N6i&azrl7`YdcVsDH0GUUC}G^v|$YRG;SAf0S5;`j+;)g&Fi&le^UN z%h9vRK5f7J$nw?hj$Zb1z2gi@zBLwMgK54;o`MOP7QM0IJN3^H^)*qBy||@k=calkUy(X zlrI8cx9g~2Gp1&FZ|+*pIAHA<&eR~?=Ud}pssUcdeD40d)2EP7M8a^{xhNEUlQsn7yWyAl!yq=oYKoKg0Kr7TrwGpG(2Ggg+?F5huOE0DB0R zd+p8r*XeyNpW>j$8I8FI{~n^UX0Aye1$K5EZJ)Lhp$ z8O1!&kchy!Q}A1(xm9q93>h%A!@Q8iOJaOw#~YNMWVeAG1Su6%DOL}Z9C<#tVLkE# zX}7F47f?k>AYH45CJlTQNkYqDy?NtSLFMHw84uURP1M(l)xI3|Twza77R6nif2o3& zHTL;Dxs*Jt;3a>*H*cmUI)W6AvWBi}Htu@U{GrEN zE_luQq^j9oI`iK-JdM5UXpPaqa877}j?;p;Z|^pF&r%%&Gu}7_PURm${QO+oL=rVE zz{-9D_dw(0xb!G-bq|suNj(Lr?;fk45{HHUwb!B z>3KqwL@U0=+^UVubpX|tU|sU+vW*=Fb@6U;sC-O>nT3GbZ=WSN-;c4-pt;6lh`e@m zc~E@`MR($Q11W?TyM{zQ?s&MP?eE@8?Q3902tpW_q)p{>7@`L!7QR|ed}QS~`M<(Q z$Bsq2-pbYN4?R6udGRoR<2QfbC4St@KcD^ybFajpczT{EzD?RLc#m zHOKtrmO7BYU&*L38@arZyyMBNMujDj2}k9NdScXg#rM#GYPS7rl}bs6mjJi-a>p%V&vUYeK7+Dph>;zdmia}QRl3NEC ziC)8bZ#kzJeP7`FrsR7~=#vA_lMK7QK;(&cOVwTcdRJH7x#dMVv?!eZTE-4n10i?$ z`x+`5U!Nc&S^{it%#Zb9hJDZ*Ff~-&witW}E^N9RaVpZMJ1m|Z>U2l8V9|&?-1g!X zv8G7`Do?nzhx|cwSq$k5guWLo`KBfaEx0f`;>91l#8>j{0Oq;U*Yt3jY4U>?I9JX8 z;~o~()7m?_?{2s=iNM&l&Cl%1(Yfa_M#MP4gH~tjav;nF-gML_o59E2vkH z1!v#ignM{JE0Oz0EeAAxMetq258tb$)oWTlRoh>)EFYU#p6o6pTJIQ5cE#2zbw;o9 z9?zpZ^|Qv;5pOK-baRD{G6gS+Uz@~fB~y`rvyIsJPUDjwdb#rm>!R7sFSSDr2V6(B zcIaBDn3?Y-#oas^@LiIRDAjx}8gz9B9qdM0s0Z{4pI_ZCZtB$OX&7sP$d}k{Fhv9H zxX{vltw8H=99*=e&e{BGWkK*a_Am46d*>dI|7A)aYv#MN^JkhN>mP#hs|ew~J<#kJ z__5~Lev%4?kLQp4>%*BZ826bq%b#s0l_U(NN>0H*az&{+t8cT66VPjZ{=s|-C0H@B zy4XD!rp=R6=B+a7!w+mXNR}5p>exsItXc))Na2h1+-3O2q%&!4FdJu(xaifuN((wV z>l2e*sH@CRsy9`ocs%>oKJtx(p`G6_iLM-Ta!LLpB!-t%*Uw7nLG?gfVn8T#s(`Co z=ltyDG!=Dt@cfwb!9{Gr#CH9UFG9Ns9=oN4kUe=p@TAD2M)NaZ_?ah`+C3WugC) zcKzb8-LM7-fwPTvpYF?M=G|Vw&Tn(F<+^tIR_9 z#DQXIQIF&nGU(`G{r+@JN!F3wLT{;M?K&c(@$$14Z}&4OlSr!lW5(yD7GjS&y_#U& zHlUk5{U{XCMt#16mzsw)pH@irfhrpN$U(ZUh5J1bY$MtGSq*!1)>G$K0;)~us!o;% z)l;Ueq$lsr#FNTw9f^HPW(uecKFEJ<0P^J#JOYOrBEw^pRhy$UQ|F08><)-&(0x2) z;ZDPpjjH3-#k~in0KNvcr`IG2IgA&u3*`$rOey_tV-3dFKM76yc!;!)t$QuMLg{`B z%BGHymm~syxkS8Acla%5?wZBdF0togw_K+SgoYNb^YBY9gV3ao-m&g*SKfLZO18?AfG?$RkGv zhOr)+-HD6n@sL0s*;cQQ)ypNCtFfVp!KG6y4K5X^`Snx^>A4rA_L7}fdc(DofjDP8 z7&+y8T0ssqxC_BC7un~rWG-(m#JK0abr`rf_z9oN$2U+NnAhlibv-oklYoxeH;BvB zcLoOQX9F+7Y1Vt%ZdRKkGWwLzSI*k<-mjY?)gE3QsOU(d#jx27n)yYGW^E%F=E1u9 z?YPBy;2?lC6ACM&g?_KHMfyP{8#}YDV&6%s#E<6TeuX>tHu@S{XS~y_{ATJ^DYjK_ z6FzcTVJf`zu@@G|+Rs1sLh6%dW4Pi&AFnM(70orbvHZ=hf2Lbn^I^W9`6cdLIBuq= z`Cc2)*5pIDOEcJNYK)fUAIU#-D)pbztQv{sS@cH@ybu7!9r_E7(tdI~smkZY(_)x{ z53@oP?$b>!6a&ndd?0Y$zh^|+k`nC_DyiHU8_T??6G_s&1^E*LHq{{_<-1`l4JxtP zE+WdiJ!e%_-7}oP;X8U;sfxLohe)e>RdI8$ETD-0O`A^?j^W3M!eQFb-58$>sG5UH z`?D6v_2(XjIdw`oHN%Zru=&q zSeNf7Mufp^w3(L)Su2eFHAb@9IS6VjhO>5kY{Dysbz9p+w_};$M^_Y4CP1)X2sO(T zD4(WrA%h%p$9?0MKjsFP0`;P8?Dx*B-JKn5Yzy%_!oeTIk{uK4?fpmqLZ`6qs(G|Q z;Mb8^?X#8OjD}D35+FywU$FDpb8*i)czKx7(kShEET$WgG{|2Z7b zp0s#65`H8h=LyIyv;VDc+RS$nh|g&j)X!u?Qn4m(4IWefqba>^d8)TJpub$iR|Eor zAc+7Mai_24>DBT4ArEm6(12u^Mkyub@USX#H?5V_a0@B+=Vo|aJw!$9dYwWpv-xn= zM*`nKVzD6|gNaE@)^y-&+gnj~>JJ_Vo7W|i&S{IclJxov@5@~2)LL%I82Ia(*qY1Z zn%k(%>1PHeG%GXi3~9xT>qvJob>kEXC7}!oZwFJZk4=5Mc~ilq@Cb6-pyPRMlo|1S zS+ZiG#G6BY$a)WZ>A>b=)`O*Y}TJ ztnWw7o;(gwsbH)F&*=ght&+jj#!TZZ;p0qOcfZwuJCB5~FQ4%Qy&lJ>g zx!-D04I6^7UM?`#n~^$b<$lpk{GZGh-(iMCt>CzbHJA~9pUeZl4(G`k*~IH5{Dz{H zt6>E{0Oyeh?LNdfBDcr4`NwQg;RAU#f}_iL)kHS#tTsKks|OjoU{ZL8E<-jMVG1rX z06cP?maE`*eE7Cv>#}RuRS9!!=PI}RFEzgs6}dKMyM$&w;0Q*3~E_tQwq%OfQ%nt{RU z(-DJ8 zS$|naRNtc+WH4FdJ~|l1yp*HOUgN1%1Wh<#TR~GQvL~O1VvGBp38$&vK-vt~OL1LM z_`u}PeE$Qnmg+^b@`uZHIC2g|+8&i*o4#wZ!MZndpdtVPe`QBBidYHBOQ;lOM za<6rxJq#DQvN5k&Z7{|J@FNB)G)H^ZETp$2sW+9MRUCrT4R5^Uq`TRsj}=%2;Pdh5 zCK2bx<3FoESz_E%_rghlB^&|*>6SS`-^;a!q+{Fr2=$p~P-Do(bzcGZAUTuFKE+Ag zZzg8fI@EG3i9<=eGfrZ4y8&xBgjGcNxM-EhH;r>nem676f*%MgMhwn-%lsVBKN2J% zCPT_@??!F}+jw@M!FjTC6p-&vZI3&+gvkw^Atp*)PI5Ya44b)2>1!bzCRtzjQ}#l8 zT7noX1lQzw+-mpyRt}?kep=jsoEY`u;Ca1-*jBAIzQy@BRt|h|RN1Je`j;%m zSJ3N|9$J)=g&Q3o+!@VMD@K0remC`QpyGsKnFqJFz7URD^doq@ui{4d>iIG=Ioz(6 z390=Z;Fy+hk~#*oTQ&;RPKApdst~4n+^DAyy(YTZBkIk%w_^8b6G8`S zzq#5S4>UIu`2FuNEYGC2>Twj_E^LA3*h6A22u0$4B?&xr;i=_I!>87*XLG#rrCCiM zX2fi$&p+^MW%^Php_b=HmU0F)dDp-VdT6JkdLL0liQbkM`#v4Mwr6}E)s%DMgB!j1 zq81wPN*#jlv;A7*0ITpriInXNlxM2yMGXklgqHr4OXa9iTG{e!64>>saivC96;rALVf3_%Qp`AfOlT=JLM8-+bJ}Ph0}3l~VaU?(C6P((505Y4Wr{k^zw&clOY9|)kC>NqHS$VH_95Kb zbXWe0#TDEL#9y?sWI|bSk1W*#e(Sr$C}qt#^lG!SW1)nRbm*utPMJFaAFQRr8?6sw zCt4Y|2j>{JU$&1zCH|gNv=d+H{tJPVzrST{JQAdF^t-wT`jXL3E54{^JV zUdv5iZ^rldX(#Yl&3@2 z*-;XXQw1}Ktlda|Bu!l-^ph`h=%4l~_#bD#(&Fi0f6*2*LCAfPt+vQB>HZYp5QLeK zK-*et>pQexezz?NpD>no%h{3VpXWVNu zUQT)m*Pa>i{~l|TV8%}zFX32>F@#Ki6__5Cs>@;ZfQXDOY>4^mo3I#KwS`ZOINXeG z?4alK>@U;%nS>01>&x_L`0(k@VLU!BwO=2)x5GQzN@uYrpo1-(#v9U*nENpZO%c`Zy^2Z<)VTUF<)w1_9?tm^W&dv8PJ>=gVm`?XM8ce}F*MW9+*MRV3`T^Bxe zlWwH5ynw1ltxf!#Mx|&a_%op`$X&;!$cD8xXYGO(wX0kuhwD2>PL05RF%A2Gr6K4q zp1=gun&io0CTslPf$=o4>V0PuO59JF3tm4u`ugVC#h0$x4mQ;n_#n&^pDqYN7(eG_ zkXeTVmW^j~1R7@8xTlI<*P`@8!YKlD^#ukqYTT+7)i&sK3P(lDNZi#>-U6|c*p zVJjD2K)uIun1rfN!CN1N=^2od>lv1EIjA}cGuM(KS(Ccy(Nv3=KIBiwGiWga44YJZ zLcrc*4)x$AVhrnHioclw`IK?MUSbX8>_0$dGyOE<*vVtakMO0_j1b(8*PqD}HOR5_ z3$G>)CYao8|DqvRTSz17q_Ez_$FvFIl?5A-0w;A$@RT%3%~MuuBP%CDRXonEl1q+C1Sic7BhH>Y zS({8xi!U~#1SGZ9z%}TT-LX}4JwNZqJr6UInj$wK5(~Jadmx{f` z*Z+AZ<+A7Y(o8Z9AGynr5v%0p8F!l|t&%i-_$t9Uj25xVwFd|?FD|Qm5ipW9HjD-5 zC~7iRb_#2=J$S?aSyk&^#~m&mTel%sud9?!-)JRytf0^XjJ~6Y!9Bk7mC~^LKMxXW zICLpL2#x#Qe@K;ngo#FLDez9r%)0eLpUi&LhAm7Lu&1Wzxz5O3!;rH^SBY((7o^5Y zq}60m8Dpi-Ef?@OH-1X-ykRv%9h_MCeuyO_R}p_ z(h&=t1b^hJmt5duRq~JzVtlaSAaIFbsM;9Rb{Ut|npw7zX_WCn*vvDhMUH7^KHJ*D z0D6Y##-ah%6Btl{ND$2h#=j0A>H@4wxO=;))wH>nfjK|88;p<3+a}`2af3`k>Z6ZY zr184flOym1N6i7~4;rj*w_jA@smP$`-<;>kxS7yk z%wg+er7*zo%xd_#jd0OW!zk1xe8T2_Eqh(@rX22 zl|MS0jMK*Bo1?3~Z`SkIzN`;TGr1M)*G`%}!}&h@u&UvJAZ{AhJQb z!jkhmsH}MYa*HZwu${JC4x^8Nk)5Mw$jni?)TbJ8Lcc>Dc=qc==44##q^$9{xsAlA zrO*^qZ3H+w)Kt>&^p^39g|5VJhnmNZ2GV93@Ba?rOIfy4z$_bBbW0K)gPYwv1;`(a zorTmr1W+E2&KTjO(C1RHQLEEoF+R1Owpp@z=6cT;jC5!#FO>U=X3r#;&v%8-$wHA|u9y5P|xwO?8 z?8hE_-srVk?d=?noJF+v2jCwCOy%x#?8gd?GkhNCV%0v-7Ei)%iYZicK4|?%YQ&b8 zrgZt(dIAo%gP({zX zWXK)vuVntu%?M!bWs$=L8Zy7j&$Nu*=_UTOB~X4hAx7S|lHxfkeuXrSx;$wmHjaz! zbeEgH2k7j)bUpY899q+5P0Q%8+w#jwz>$$o%v;q5nMb#f!g@ri=hr={KLcJ#;|1iw zC!XQ4nQasE`nQ~3BhK>U8*aHQ*A+U#JF&<8zIDM?sffZK)QZ)8jkfs<`=6TL2xXDqphp2N0dIrwL2SMpjW< zi{)%NyKlMX?ape8Tp*2c5JiM}MYn`cnEXA{|MMcFwFAg^h4}0*Vm9Iz}$_cUNCyB{+DAF_Qigp4B}x+R|VceRr!y7 zM1z<5h`-u5AE|wWf>td`=jymS^~i1Hq6V;R`HXmg#O31p;a$GUY2qu6EO9&ZQygE% zM)hSrnM$!rFVV_i^*VP;)x1e3u+X_XI3ChDXAWz;EdbLU_qfkjIV(f%;#vx}oZC|(=t8lsnA1rf|7x2z-$+K9qn zHtP>%SN~9YBbxTm^z}XRcq?gBA|tgJMS|x^TV!1k06G2L;b8OwB)Of!bGPEoR84-xpieOVFK85_w;rYHr$KOc#tH z_<5m^Z&$O#QS19RL=CT*SW~$D?dXB=uO6qyx(!3dh0d=TZtNSkr^aLN+5L|#0xaLGU#0XfC(2u=f`v?8od;-ZHRfy|L{!=sV zP{v5)ZToK=?YQe3V&Exhr(7kkAZ%tQ9Hv#LINmBC!?rB?%6Pt#H_Zd5g#6?IFP zK#sd#EZxpl+L(yIv#o}SQO#xUk}*n|tcIcrsdyP!U$6PbMO9LV9G2Q0!733BQI+5J z6>Qr$uZ`;pB%a-0FZnjFd_En8+fLHVqaQ5X$55);!dEv!08C_UeYsU^i+U0hjOrzY zevl!HoO?rgQkR{&JH|wrTJU80cX=R>SE^!&pVk*6CPEnFGNGEmneUGu_FG5cCxoji z>inBFsj@~k9d91JANc{MPE19{)NkYbD5}=$B|jd<1{=;J8u`7*^;ek-fa{tPJBqr2 zxZYM2j+=$BW*jOqA=>=am}h-Dn+N+{)JMkJ3KTNGqE+h0GJXBf{C!6UPP7RZWpTU2 zv?P1=`=2618pl0N>C~_Hcr4Xz`&2xw$LuqM<`J)r6=p|*9Ij3!QHueDQ96ticV#AD z*roOd4ULxOGDyW29*liU!ONqXv^K;Pd;+4k&j0%pUtU}6UIu%X(r6&>)xc)J zl@KFC1pStOK3`*O=R=GoK6pypwmPx&EM#!4*pLiKA6R|ymbl4V8IEfU_NC{?UlJQG zc{X`^z#MiPV=#DV!1F7%3b5hq@aA;AlW_8CTKEgr)aOlJ1V;#voQhS#1`x#;C(mGA zT9bBCLF2Ncv0|;MQhVP+Qnkst-1i8}+`Oo7qT3oG0vtXuyH3Og8!$$O%aF^ZHn*BI4@4BFodWj_O zpeFzqp$l9i-B-!9(huc>@cB2EyBWGz;5sd&TYe5-##z3ms0&DKuJpCYP>$@?>(J&f zlPQEVh7w>KhF7JU12}9tZosSG{>Lm@_lbMJwm*#Gl+Ji@zow_53#MRMhxpbGFTKiw za*ZPb>z4UdY)IM>|ryoWwV0Tf>qNHTBK)Z9?{HwgoeK$0;d8_b`pp>ct}gCP?F&c`>xk!cPP0$Ud;6@TK)#qXYK`pqw4PxB~EDz&%Gcau6=r$9K8m zxWWF14Oro~6|ZM)WMw1o3Dm^PpzaIw+w1w%W(+8(x#xPRl|4aAKh+s`bmFUo>k!y3 zJ3Bvw>IsZ&NIBstRBRfa1k+wF1t3vE8+7IKsJlW=%$}t}6HAV)pO1+4G|{AS7WYT! z=mT-v5}ecIP2hvwRr1r)^e1r9*kWG`EQJ}hh`}BBwsEOGbj{{`k4 zvJZYR?%}>MVe|1psR2!m>F3@EgL9i3>A)K$iyj6?>FJvKZyfl#!BSBVoqkGFSCe2p zx`emeK9JGQ2xk9o-gvjtS$>ntDS&i_gtB1rtWVWiOM6BU|BYimM7L_bz2@H(tW6P4 z#epwxt5$Zcv@yldDp{03XDow|8(OHijp|dw126b|etmFePoM2Ow?l%}PMpd7c9=PJ zy``@4O)aNtb5dD4^`c^s~q$my%cYM zB?7;de^VO_{fzjh(hdZuQQ7L)p=J#S6yife9>{}i1*qF2wgkg7sMeEw`ehZhXS*VB z?f3KSXe%tOq?fmlU=Cm>ELh=^F&ZHT3qpEog)m}Uqjz`}B2WSXr6Pf5HBF`UU->z-PLccS)+)TdLM z)Seg>bn}O#3oPZ~Yp2>-0%W=C4_$RCB0^165({EPx?u{1;vQwK-f0Q9f z38p>m%(G9RyH9y+)}7`GKs>whnQh<0UYjaZOkg_P`YI5*<DPe`PozIT{V1J6;XHAjpD)-=$n9sfb_8{Zy;( zT5{BE_Rnodv5(uZkwU=+&i#BQYq<;%RQdj{Pg?+|NYHcyKG;olV1MvhFVW`t?YfT3 zk^`F|ODWgdyKL8X`Y$cPm;4@{ojgN|UbG7DGGDKUmFP`V3iiCqTi?9VA_cG1c~_+SKHFACpIlYOEJgWz^2n}t@9A-!cC)*-8pwl~CW zx`TE$b$K(te`Om#=pG3Dvmh(x+$-Z!8Uof#S47zVCjx&zo-z;3t~?@_DXBOT*)2vq zK0nP6ZlB%y%J!$CL>>3GSyo{6&zM+*kG@#mGrSq`);Up5!)rKvWO=}0FMTHm;EF^dQ6l!Ti%k92&$L(G& zTfZgb)c1~G&I)onISSLVO&yz!J+vD!MV45VHIdA=`7mU;SHVp?zrFMi1M{w-lmA}A zkI^1=VnerVl@)0OU_j&RP&}tAxlm&-lh()lwWxkm8bno22rgbe2ZyjJ9MzO*weh$O zw4GC-U#HfoH&E=oBydSbCeAr^54PCxNg!uW@N{uiWS+-CRh;|D>nbLhIB%_=HZT=^ zQJ?9Qx5LOuQ8;%vn#rwPi+YD?*80hBxmo2}@%dNWLg$8js_AD6>)c%&kR1A&37Ex} z*Sw~moshSpNttyq4{OFiEK4^OgyU1ZfcZ0T}fzUTn1;0+uxBqIqt8e8Pj15 z><`b?WvubzFGD6CY-}c4AU6Maf4X*Ll9AK@ztU{~nHDpkLMK1*nmya#Oih~YGzPO% zax10I>QFBvuv!7RqamnP&rs(?^@2w{-(4`oTyg9>t@XfsqT-6@=W>Ivo>y$R2{65P z@Vb<#TSD0VWmW2Hp_=iiO#6(M2NoE`a}G*V%Y=4YANw?bk4OUyBfkKOFuR?4Tvn>0^ zG@?6xuxbp7ZPm!<>ktno(D;a~14@Y}wFg{ZhV#Co>-2s0-t zqKY54bfoMLx5m{^p4lzmo3Lk7t<1C()aE+Jt1zU)e(i%3TVV72$8k=Yv+K3}?l59J zX=;#0se$bx*H=zLS>g0U<(16xH&R-P(opoa_5*=;&_#z9PM0A6gA7YWA8k+?1l}CaG|5$)z1V;jtE~0RSq(2Zn`Qt2o{KRoi;-2L z3f3u}AdDDX*(ee)jknhxAbD0hxY{9=fKQffd)LAnXJp8UbD+kC^z^G=GiGI6TCMR> z2We{(Jp29D`1(WRrdrBcNU>6mFJC2zTNag;e{g_SW6XElO8j?aH2=WoZtXSWOFHXp zG;m+oXLcD2R~U!!U7<>{*q(-%dR@$9MAKrTl+h2vrGC=E*4?<(jV;EC{hejb!K7mKKlRxyCJKeJjB$oyh_S3&%ZiqdwhnkxTLB z+s-8~LJqb@52aHa;@m?r;HdL=cK6Ch9iC^PxOxjOTVOrYMBJ#$>g7bZ*U_Eo&`ePU z4nVup=DT+fWA@$IT5BX2hwH{Y0-Qu^FLBnsXK`~PTdoePV6hbY?e8i27`3+j%+0L= z>)0Q!QHe?v>{PhNT3tHn)rMwrlE?%F*FLH6+EzYjKwUonz^cQcv80=)@slJ=&VP;e zBa7|U-Mo_NroHxhDIj_9#dH?>Q#0H>FuaImEiZy7XuqVZYEzwFTJ83`>YLP!Wu#bu;~`ncL%dv-xpPqtsp@lPLq3 zvbZ7X{I|z}jsQYSL>rt11wC(u!s~A z=NlfvT(1#V0Ytpr{Gy9@E?@`@$j2QT8u9`iKA!{}kM@uGrVaUN2ymk4H>)#&y4j~= zDeNSBn~p)a5-t{#O*u^I5oPr)OioUiE*-?>t*d2Rox}~iIoiL15d}SQA0@$L-BQM6 zT$-#`3T0dt-FM3DHf#Yu4Dm|%9ph?{>QXq^i3wnn=@p_Eb0u3&56{Ynw$R6ZT9`dhsNIrUV%a#8Gyyk433JZUn5U}$ zK_pDfEt)7Dk>3YS6zk-%{nIr8Omiz98ExdYx{-+QVS9S=wjb%YrP|T>JGucT7^AL& zox0G|TB5EwZgn1$IcFN~SqB{8`6^(b%h5S>ywB~di^uuW%oLczdnM}tyqU+K+{F>i z;ZCHm)lcN`Uv9x8=oFke_l)!VE~``D9Z#FI2>62i@3)Q(ruJ(1YAVH^ngAWDdL~oF zG`VW_E+yXKgP$KwZw_~sH}TM39_NtDSP16jseRP*)Alh9_W+OzxoKwdtNq| z_20F6%;mGs(0Wby5Q)cHx4FutBNbosSYTx^9k1fQAj5%TwQP{9WF#BYT3%OvyzPDa z4CQj^)Vn??D@tNQ&|t-)WDiD z5KuCU?eO~1ac}#gFaDwKsGHj#Rc)PG@sqPb4`T@qG1mrll!NbrZnWc+tY_+< z#74A(eD8SR5iz)_FxiT~tnsae7#Y;i{2ZwM3G%sjw<%-TrksI~rg*5X$)hb}8)k3O zIZ>{CyVRtP-39HeO#+kbgmz$dXwYgBSHSKob-YNx-Z%3;9UI8s%4`1bYhKp}f2+@KM5p*zJiF4yB;rktbLTtm98e zDV(2~PN}9Sn9l2#s!Ozs>poDEP8zuK@`o5Y6=)Rq?*WX&V~JO8mr`IMB9YXJ^>OBD zdl&zCnWe{o_CtIARSRED`pt8k4A2nMtsdog-RuxC`(|ldEEC&!40~=BugRz8C=!IsBWt9oy;l-#XjU$~CP}UG9 zBsiu+19oq@+5buHxlYMeb%U1Y2^^D9r(K^So-_kuDpx|BVhXg;x%KH)m6b>_fPv}a zstxT62TyPtio*Yc5yFMHr0DMC=5WrsG>P!44_;@rW4JYDI9--vlVn`>i>>h@h3=3D zyiRGDL|t}VQ-bx8l(!@xts^>wU{<=@`V1 z`LD;SH?F*DQ0(xCHsXHkw>j!vK(O+)E>z7y-$_Pi*X5)exO~;>daRGZQlCuqAHpig zAxLHgSzq6n%eJ1;tkS^6GWx%o(~nuD8F}OMFG1*^OvjE>wDnhCR%!h?O-ej@2^1ot zrMGpBMagXhl#ctg@|K8(YY=3}S)flPy~S!-uZ^JNYyQkYT{z(ltE7Aj#e6RJjXZ7# zQU{%h>Wa*Qn%@HE!iUWxH8I(>I%P5W+%89=jLQgz5X?tOA+>i)l`#GR&ejs3L;Z6#CzHtI>d?9@*9Ea#b7}dY~-azPtOs zS@+)je+}2nhf3@u!rjBx^(7!xCCAra#(=N8}w|C!&=y>G?p$^T`@F6#xzym>P*434Z7L znX5VjYA^&dy7=49M}Oq85*%-Xz(8StLGwk0`h1bL}2I!*ZBPRQI_Vbv!8j@DGOzr$tCyk$=|SUe8=eJ?C2oD#;F;iCpOTPML;av3>6DQ*ZX_*NBsLnfu$8N+JHXzFJfd0W9r4%A+RX5JOU* zJub@?uy;~6RIvxq*#mXo0E%z>|39ocr5N;SCH1bn{P4ni@C zp5P|arkrc6lkn}s3C2aLmE4nq9F%W=J$~|61<$x`^Sd}`qX@#%N#!s8-{avT?kV}5 z`bA&a_NAQEzsWw0PiVXh+J9qtur&~fQzf*kodkFJi0h z9Xb`|0rIh0AwZe-!%wQqW#a;-WSHUh>aHg(`ow3^r$W!&?pBw}In*Uxb78MTag4w` zrS&CQKIh-d%P&&;6;ee6S@z}Z88{qXqMCE|EfqtjID4T&>No?%=&Ix2?}a#likrz+ zQ16Z+pQ#ef!pFYR+(4hq_^+gnKe(%;C$9qfsJ!Y^^QA8GIj!_W&gZEkW*vXw%BOKX z5o!ADr)#FgPNvCNtISZSASIGLzlgpy{_)ucmcTUG0Fg2LuZ{9@YrMX`JA)Un9dOb~ zBQ+tGcW^aqfe9V5kD0d&hNcV4;HyV;GmC)9MJ>IO{mgsR0oT7BlNoa;#8uXytj4}-@v!7Bti{qIT73SUi1 z{rt{fGmczUO2KIl))m?Ickc!E!lGvqG{4;Rk*#C$EYDa0QOQ*!o5|ijN~TeP=BRIbWpi9aCsL zxiS8a;>Ul&HS<>aWZ)3n*?Z5#Cf;8tomKA7#0z%*bz9$VT8!y^hkK@XgJ_h8KpS{S z7{4hK5i{%dREFMmVEKB&>_*DWmDd--Y2E0M#h{Kfc~Os;RDN zA65Yo5fG6kAS#Lq(mRAu#X?i*U1^EZr3ONkrc~vY4j~{QT?sXG5Kvlz^b)F&&_W3i zTJSqQzxTQ2`R8LT7jdnGoU`|w*)!M7b&XVIp18`yHt5!;%5aHk)p;1}B8u!d{;s8% zXhb_y*5St+}1RPhFWmXT+9WQi{DxU^yZKj_2_sNCl zc}$t@-AgZEB#<`h^E(`uB63RAWq%1Wedv8WYhzsb{0(8oIuvL~m?_`0Y36CWCz@tr zymL*X;%?g$F$CT8;UtqlDy+#d!hq#+>w&U#KP6o}Q?Nw@v$FM*w!bj6Hm#;`*3ndH zYH2tPgEIMIbv5cOgS}rg$Djp7UGMvp)K@y~hyk45Ii6JPZsqFczQZ4Ip`bfuipsBg zd-0cWD1T9sDxrQ|3!18opjr+B%pf-H%OA&17t5?gZ%CmzxU`5`^IhbTkJskJXmDbzW%IT0`u8IyV>mD^SF7Xk z*S)!VyKnG~hqtK6RNTRAkBZo+4yrt!jr^GBS=9~6*?&mR#1=wA-O=3M_S?2LQvg)F7^jj>&2lJvqmpzJdv z`ntJFLPAs^hnOaAE1Xzj?JT$&B+c!$n@+h$x+B`_W3H+%hFH=#y_#ME_2XF23QBbL zh*pa2*i%YX!@q~wQhvrnLja*lKM7*q@0={3F>MSPKi?@x<|7LpuIUlb`OG^8~Fg73USObjuH_d6ou~8m%&PD9dP@ z2F7Ivt4dVH9{u$2!dH`qW6taUN3i;HQ^u+exh6kc9ee37p1(8&D0WubrLImde2G8v z?W?%Ot;j0&8juV4hYSc{Z zf}-rWyp$k)u8BY*a%FgUE&6ekpUOocf2<-qIBAHhUcY6z=Nk_ejm}OZ`0NIYc#Cv{ z6s?Hlau0*s6{Y8qeRyY+DRVC{7dsn1xLkmzH=qv5V!Al6wDxp6Rkw@OS3{LLg}CY| z%f61uMof@9rnINkJ63-R6Vkn-W7b24pX`A|OIcDA@s~B2+}Ty}wZES@ZzAbE>mO3+ z_pu}pV6#tr*Lf@(7r{GqhlL6UT$YWZm7tl0mSBZrFyc2Nz;j}n@|{f9q-+Acx`Y23 z&Z$$bImPzM4#b-sw_HiZKBrYpm{&YL2`kGQ?r_aFdzIQfvHA9HHoZN3&GCb5#Bx;p zHD8<#Ubm7;er&(Tmj8vSjE5>ckUS>jqt zEGb^iJuT^#vnzc+agMK6p>)}|eIq!4CIm3ULBw50;#IC=z6={Gw680=g%YS+GWGA zFl{q_x1;n=ycsgNO1R0s95>|Qho;pT zp6MxXfNmnQbJW~z)%*G?x*Y|9|7Lnxe?q>%`LcA@dS?#Ki~H5o!ouu?`e=c$@}1M^ z=0TzP=cMY7;xO-6E_2(W$@W~*as2lIxF zr)Dqt4SAo5+PMcHjNP6}E3^9^wYmERX_yeE4!dhld2aZyn9{6E*i|OBD6TizvRClG zYyvVmlPMZS!;d1ZFvGw}IV%w9LI>)9>Up1YNhV>KQ0n)34LQ841llw~FQur{t5kzp zoS5>Q7Ds1Egh5c6cxHo130daaK<MN{r%j2agYU>{Vww6hW zIdkuIkANj%!bOJ$GRf3t%v+V6JaUB##qo{&0;pX_x8hHy4A+D5MSYVeF8UgCE6tJN zQHY90k@pn#Ew!x7uJ;~F>vcTdd(_tbO-{9x!zd=3Q%e5J#NYinJL?El0^)?tonr0_ z5)N!vT3lO^TI*{V%6qFQ5Ij{|u9NGfHt|_i{Ef4~DgH~>7tuJ(aSi61&#;Fc#>^mq zpabRkbO^ijxoCgIOC_pSxWmJtP|F+qJ2Z)5+Q>)1K%|gb-cI$+sn0;O?SFr+<&j!i zAw?jB0a?Zq;3~V#4Bxh4mtM|#T27!V(`CU&p%GN6Xaps|=_V<*C>4{*KM!kXggUu$ zfvu)uoQz-~YuUrP?g8>600a7rwSKtr0fM99i5-!vw$0Jib1^>^hK;h^xgZ@X4Cdvs z)fk|)y}W*#lmb@rm(E^7v77V))CFO1*&F19NaAtWqig3YeDAE~M90-#MeURE0RFTu zA#!_>D&*n`(D$E;o(O9XLl>p8u5imoqnM(H{oTfuhn$kAav@^VW#iruwoiE2_;W!` zkrQXrsTqx>Pu~8lE>p7$vyEm4;0d#3*`+2$vC7E%%Xa#u@*Hj#6(>E0xP+E*QVaLz zK2xO*%Sf9C)rh%vvrS-LZ??2FTLi_2K+fMRp=j|mhg0=5(u@9p=dFD?Oi6ZoP+^#7 zv=-3UDmLHYLYXF(o7XNM0q#(s!-E zdwf^f+nrYcPiHoIWrf9tt$zEXz$-L-Q&hSeiD99`PQj;&m8R`v6&L%Ua$_Y2?z~%& zWnr^fYvZPu?$ZbDTePI3=H5EY?c;na+^%;$C9bJvZ5?Q;BK^V5sN!&Q0RIqnRmt+$ zC*a^muiut!FMEF=^n}Unf%aU_dc4zLM^X2)R1Rv%MGi|BS3&a8D<;OTk#+H(dh1@@ zI=c~R(O7!DVcBme(s)~&BJy7&0)7X15|WEWz7dD6m@8TYch1oZXQkVCimrD! zl#3^~bsc{S$fWL9l+OMEWjsU((gM;gG-A28;&eR07z0A;Emh;d#R#9F zD>!<@vtky04%(AosiHZsCVhoN(nA>ukzaP@Ru`}iU59q{b3I!QA{IE<8+}2NoF+3*%d{7kt$8-l8VQ{(n~hcj8eUGkSC#6| zvF#C6R}b48rg`P!w-yC;mt} zU@RUgRJ=q4{LrG;T`XH0Tf*(7H1W1S`zPg|z81vl9k+|-hAx+MyLw&q!YohC91zd$bn)14?z5DAL zxzCAJv&?v$Nu4NQcED9zVn6oXU19R(7!ylejBr-%SCdIdw_;Lzwo3O@lWlFz5c>?VfFI0M)@ZngPq;E>S$j3qrIwHVB{u8^f*h^AGCn?y`T{ z_MaKsn%d}AJ{cLFdVV+eG|oTe+@N!s=5+&VZ3}GCjf@5sab#_$H)l)+Rx6HSk&~-m zo3>}P?^By&mcV0=Y!*{*C2xPS`&`Xg5=_{O&{G`7h;4sD+=5-vhE_e@sgfxGNR(5N zh8Ebn_9+s%sAeBauPk_6{6JSIEmSU&$>A%fkYe9%cH*0@^a{m0Rc841a#=!pqFQyJ zezD61T(X@*CH})4VI%dzG;KW)cQMr;4VlM2aNgX$tJL^FH2yDq()@AnoI+MwP4Ka0 zcdEwr1&!^P>t$<>=tYM~qi$+z)$Z5DN%+kV92xVJ6N$xhZFxZq$ku_h+c*LKz%%Jm z=eL@ezbqU0w91K7)JXM;96y(^@+YT4<1BOEF3sir$_c%4%i2`rx&W7L-L&`V+niXh zAIJr*kDxAvqz_bjk+)F~OGr0H$koroDV zBPLE6;eLu()lWPB>a0H+tLOb?a($Wh-|gRX+@ofIhHW3}`~ku)f{^rB`qk;@vyG+F-f zD#cz=A=P`+P`&Qaj8 z6TM9|>?_lTu8s27Ar|UEPYe?V&CtmG)w9DZdPEXeiWQ1l3vsJFm)*D1{!1UZwmpsA zUQs7@(tRpG_pCb$CUetoK!Xf@1A1Yft8PYhMLS3a4e&Z_}B z=|1f4YgIpaG|sC3};cvGvoTZ;T|$&e)2D6WcSYw0%cZu_eX}SekTm* z)XjY#`K(TE^*GFnt0!7wPc=iD4<)jUa!cubIjzw;Y5e7MIQ&}))KzQZ?xz)5F$^FI zvC^rB!?3=ARn;GkarFs46U)}S)%Ej!Z=8$4X`d-9<=&yfia`5OV~Gi?zf?F}AughQ z&ZVNz+o#~_>Q#}Ewb&CEGQ+YkFLn~I0~+WIi(Qm?dCHf3HP=QL7T)gHyw@RU*qQb_ zjrQ`iyO(HadT*k;JkKnBY7|Z_%mWCm9%@!UZFhMvTfUM!jrMqysPX1@>sgTpT<{Hs zJHz>%M5zh%V#4oZc&%sVuj?X+6BF)FIbZs((DDCmh;wM+Y07RTN?<*}t`T}^2sn-p z%EO%pDE_bXFDy$TBI~1L8oCgto>3-8H}5qmx~hc@2QDsrJF%Y|D#52ugxmnEreWMf zb)cydJ+>mFNB>wGR38J_9TdQiq-tOIep@lhTn_ABCH-2dMP7HPRO3b;Hln?M>fqJJ*`n?a*NOO>*_}C z%kCXC`e`#U_V|KMUO+w5bLErvcdCXNoORm2L-W>_aCqPHO=eF}WBA;!x3!n1IPB+Z z3Vzn+$Y)DP32UG@S~y(}OBdvLky_SctIM73K=&Yp?>u9Dmn62=kE%qRb=*or-UU8Cx-kanx)cXCdX! zz>ka5O;27Q_{^Utl~QXK{1h0i5v@k(3CGSi3wh1DFD6}9)w2b2LK^F2IDrP`^dV>d z_tB^k?EdNtwNb8b{6#z?8Xn7_XZx$#kN|FX@Ut}Rae!9icKzLFizw-QcPWfg*-Og4 z{o5}!m-B2`&|lqCZ|m&EtnTzY`$Ug^ks1NkrDxGsjzR{;gkdJ?j;=JGTu3-wy%Lpg z4Xoj;dsieetsAFXjgMXNdhtJ3-z@b7eY&9|vS7$yN5nQ5EHo;-9}6{2X9H_(%YDIh)G0OjHhiXrV{!+pJTohC~Gi^1t} zm{hHo{vn|$Kou|1ZRom%7?LZwq^2$HJHw*K*k(@$N+xH1GmlolVp!Y>4M#uFA~*-` z*MF_O0cnw+z*>`BZZf_#Nb4DW0NJB^}DZIY3Z(u4{YMz zeOrvQNISgIxytn-$aAahRXE>6$85Z>2v8ZyXpdZ;%kp}P!|a;wH^IM<`rGCLK_UO)xd5QuoHlQ-Vkv%`rsQy6g!%S=mq(VIEL zh|Vs{_$c%`pv>^&AeV@&j~bi5iOh+QoH|F4>>Hi1YUmyd!-Cd*_Fq_ojc#n9W@-S5 zBbQr?cRTW4Ox-lr68PAEl}^F0F-q2ELN;m_=%^O|ya?<#d&PRJJz!(*r6fHHaLDd#=tGZbB#8IkvHR>(U?$1EIEZ}Zc=JF@`NQig&vJ|o$- zLZ6rR(TP;!R6~Hu-IC1d*cuN1m3aIbI&83T-Hm;F<=awjlA=LVz;na-vihSbxWF~+ zi8x4W4;VQpqWNYseVNnJpzi){kJP?*WxZXA`>DIWe0-Jl{8i7@Pu#PzBp0@IW;E)M5!L}O3FNx3|Ywo}NKJFQbzh(d3&LcL=3QfoB>duV5c5y*|uOH1frC_g) z=2K|$`OuCp$LHT=NwJVjyj6<&@F~l&Y$)TNX`s0T2#{dehbHK3|9n(-0wJUi0E6_x zQAD3a7_1Q#U5SC1HdNVLs*emdVmr4&Mk3zoJtC79 zt4w0I^VSR8o@SHhXD7&em435qO)0)ar5RWgy4_o8V^?GmC^Pz0KBxRh&^)`*+hqGU zL_P%;0-vEueS?pA;mTa&Di@hV85EBb0pmq9l4^V`!dPSQjM4b+0e$xDSDMe*uWf4$ zZlPsQ`o^H50Wm-#1v%Bt{xzVcY355=ZpF81=day3>;qG?UsDtMRYEw7lO?C~Lg|)N zx8pf~9lRibkuL!rz77BS5t0W*b$lfxYAiQyzI`A-Xsx8Ql?QXk!|%$0XU{zm0^a!I}#6!0epwI4I|S}Wke z*SAQ@tq{vDc7Qn9b$(l8&fM}4!eT1i1M}fvFpAIIF{)p?Phej(-8=3)5Yf&|W!+Cn ziaQyQtZW=J5N7k}+oYvMUS86~YuI$EQvE5!DN9N5+HZF~^udi%CO1+Y>TqW3Fvs@% zVwls2!(jlxQaZ6OryK@08_FA4<3=Z<7_6AgG9RSBTp{AM8$~C^9@&T6wpEEUB5|;K zPNz!`Aufx}>MGYv{{4NBnl97-_O>K3)DkFY^w>=^lq}z}cNAZl@$Jy4 zSQ_$HcHkRVa<+UYx5i+n7e@>!?XenzTb8i+j>q5u8bIdxJ=jEA-!hc|l9Zh^LH2#Q z2NHMeO`1hr48!1^?@CLi^r^`+rD5{(iBT*E#p5E+bmls5p4m=3Vrz~yb9C251C!-H z8Ce*q%53iVdpEv$<8$;U9ILkq_$jD+0>?P@t(OaX33TY;Y|%H8mf7>kKc3S_Zd)Jo zB@>aIpt(_O{8jTnGy78c3Y|c@?$3=($;X@GFKmUwj1Swo#}7~VWVne(G_0>E{XT~1 zcJ1(4K?`syDNL=_N5!#M!8g)Vv=i8)fPliub9~?c_$XDXRVWr;@(pBvIUT~?moQVi zj?TNynbxZUIIHlyso9s@YX zu>Ojo!+pGH%GqyISq!v##=eI?fj;tPwRgq9G!%)8HFc`7{$in4#Wa~+&Ht% z_S-OMY=P$YZZjA0>Bur_-NNXh?x72&w-->HxvF>|+Ty!!bSA|A-vtT6M6bwXEwnl$ zX;>UNi50J}Xzv_6-U%tQCY0^^@YB^vv#ZHl1!U^edf@3-!=0Dgh?3MXdUom#4i-}^ zu(-4yg2r&gs?^lhUmtqvRH#RxnuyO2yPh&15;5TA@vvKqV)dGJ6Fm}FKfmXZ`4}%j zi>X=+TK4&I4XC{ux@FRcSnQb#Ta>wol^IuQv`c$SOp&^tAdb zM@=b&Ijg~~>>vEVbAPoAwP<{=Q}yMQ--O7fNIAgKxiCD8J8(cQ)atcoFECKxvr)Y6xvhGc$Iuu7jOMhm(aKIRoA#R;k5n- zN|l1S_l{zF8(?VkDM zI@CNIqk0*PsM{EmcDp=F()b$Swm94#Ic0n)JBqHMk|!?eRD+P>QXvTzhq>Ei_XVT5 z^n%Y=Mp9?=~E(!m;)eDpOK0 z;hwz^x)s5)0`$fVfTc5t!{LB7{YAk*zKH%4KBf8A9R`BmbWQx)Le%IdT$7%B-SwX~ zhvA)+6yrnCoTHhRy%?aXH|A>CTI#W8c*Ct!O*Vi3{=wv{+KZ+bmNw6O8QA%9V9;{6 zSjrE72*2Jr+y}e_9UDujPv$>J{68_!XK6ClQQ$E4G(F>E0shLGf>%>d{3@4^m$;fDSJ({2p$0 zkVcZVLPGbC{^MZ*vi9%O2rF;p-*aq%3%%<@#Qa5jpRHdXRQ&D#^$LKg{Qw;9>lam? zK=Co_0OlE3MvdX?w=$|Q)4oAil`^iKitIB&1?Xj95mdPFt1DygNdB_8sOdL97(A7X zj8P<*doO02IE6nsGyK$Zy4=&03HRz%H+~_-3S5$o?mwC1b(g-_KQ{Z7IiUs)aLJd< zuPzosPr}*sd^ysA0*X5tfs{zJIG5oTPe{ypzhdspN&+;yyp_bK3NO@U30?X#g7KzJ z+t_De!dr#lwdBmRdBjkK%mRhfdzJw_H(1ad>|aN{RipNMt~*M{XhV_xk%`J?p4W{C z!qXYz83`7c=;TR@%%s~rK-)DR33df!6~OxqZaI3qCJy)r{MxP>pS&+e3=R_&PK3Ni z&|U(Qo;4EUF$>`ZHx}Jr?$n8x#>VcQ34L~4zryC?qZi;XQz<`voDI%OoPHdS=+)mR z0f=~Cb_2Z1L3 z#^;{>b(zZnnL64+I9A^3s=&0>*|^g@U!LWY|(&F%@gxdMbz*IXnlxbRjBY$vR7G?>~C;gB1llJR8*qk2IFSZ(F(t$Aa zys#Ot5rDYSApMm%EvWa^^GN?rl!v^C1X7#x$pq!Tvfz!fwM61#Bih7Ujh>ay)XQ9K zW%Y9@0rWx@|AJ$}fK=BJ@v}nP&@+NJ@PT|bX*8aw^J^BryR#ZT`?~DtOjh@;z6LZA zq8HwehS|L&lOiubs})7<_U=u;j9DyZInT8Gonp`o>FNc7p5Zmj(9pRkARozp z6k!LCFYt{}?CK~PMZa@CUI>Na9vfS@Ja%|tYRN~rvRn$8?#viT>pu{(tUIh-oO{!K zQIq%Ya-aIb#zWfiXD_83Pwq$lR5;waZ$4Zfn7``5_}+3t21LwwwbZf@ZT9MX>`Tk9 zQ;R?!79rHx4QPS`R25r=31J?pM(C`uNI-+p4tmi;OApXFDy1@w{v+JRa;zqxKnS=a zo1s%Xkixr{3#|Mn1szW{8P5JIVucWt<5E4yU>}LjuKa4QGrw6nfv?M~(|MPOj(PbU zOdC?6R3?qMU3j403h}jYx-FU3Z!@K2-)=I65%gAK!p&vxX=Nm)WzJ?xB1$TGw}C>d zP~z|5ST}?pF(5IDsd0Atl20#x#ieY;YLUdI5!(9t-$D4Vrztq5dPWe8%hVvbge1)}OXmYFD%b12bkL+QOeiCwy}sS}$4~2|Is>CgW+8lVGq* zwUx_Kse`Kbl{3F|k@UV60{$cWTO-B!RwTgMjeu!KO-GexwTC7RkbM4N^xpF@wG)}y zsXI&mnBFSt{;4oA?`Z1#^7l8*rIH4&DDCB_XQh)AxCT4#y*l7b z*u`P;9*6w?ey;M3n%zq?%jj>$esY;6I$qkuSChvzpX zF6r>uJft#XIpFwF2?$m`MX~$%Uo}c#J2p~)9`h`kuMdrgp+K>PYKUfyQ%EVM3aa=0 zn4iVe$kwOZ$kW)vupT=eA)S5Khsb}`K>)tnx!hT5DX6U*);|tUq%@Y*iCD4Sx#=kV zm14O>mvkH>fx7H6TQ*8D|9W&HGCHN+Nspy`yD+Ue;f2nwmNs)XEAC??PZEhJRsml= zGvtGG&voadyz-TPPkuMEl))komAZ?mfKD{kxUk0YEbQAqPB9i_3C`h$=E%gQOCViw zh`L!$ZAWO;qPXV+T?q&B?ze}TUpTc7^ZSl&*{5)kGkOKD^rPqH>@>=>MwCufk6Ad& zdxtFj+?kw(d8~xTDTyL_-z`dnb>IR-`?~LAI02C+`}q_tfZ7l%8%8YJNQuVxf4&0l zZWr|5>(Rtf9F-vXc4UbmB_vfK{~aQtihr)tjg_60dxuL(n^xCx93;$XT*dHF#xBGYunN`sZw=gq<_k)52f> zL;412E>U{p%aDDqR%#rEh6`B82yQTYVEWd;1`bnUlQ5EoMtI{|(=bP7r^|BbQzHa% z7!fTY^x^(76_{v*x`LUUn^p6k)Z@f|d=Ll7=={3`L0k)q41_fc*4I2k7|pTV@yEbI zdaL;Tvomg_;n9RVGzr1n7kL39uf&buFct>)pQe<>5T>>|hib1=`HsQ#;vOk^ImtU5 znnmAadxLQfFGNr?%qzleO)}?NO=m@$SudCT*al)yxqjB87~ZU8^7U!l&tUlb)Pg!$F+XQD{&a zkf_H=!=CpYhLyfGN%ob0yNUIvQXxrZx+rAktBl@jkuTHyKA9^DyA;}I&e-teg?lF6 zE5owR^gp*o@v-d7A&*30K~Djd-N36J%9=Fv4NdsAITAA8YgY#p9*qBD54x z6}j1Cz+HC#ZQ@VJ;(C33ujYQqo5!>jbAYQM(l$<8O_+L9d8LMxo`~ixe*<7nU zd*$mBnSWT#mX;QjpWYh@F1L9=$LITAL?+uv8p#rl!a9kj`F}YKmd{M$ribNJbTCh8ca{21LXsg85UCggwi#H&l>q@DreB43BdX z%)C7e0Tf4`+E=}cCnv`$3D;$b$gVmdZ0#Rj7xh*_lz-LAM_xn=wUF{85}Za*9Q{<~ z&n13u+zFj}&L=O4yc?P~f6z$f%fOP#ut>+%_m$4VIf_n*-<==-h_io7Rl{D=sW6q^ zU8;Vmm}&#-!AOpHQNn>yl$rKcB&UyQRcDSTZUCO-%KBHijAcOA3-9(E)Xjc={`C}8 zzXT_X-+*1KVu%5Y(`VzSuUuq4*u8%w zY}kXRs%&^gCZ!(zk27Hgr=O;L{la117wTN@dt2Mfm4$aSn(+~M@f$0n-*~*v=v>1$ zSi&M|&~d}>r%?x~wjAfFyQlGH6ZA{O;5q2;vvbN-Xi$=x_i{Du!eYg_1>fKWP+d|Q zvHNveabI1#Q`tE(BdVufcz7MlSguUYVDdP zRkfr8`(5>WtY*Vif0g(3Z;6!RHBxgU|A6N}Sc0`mTxW_@>JNJ_HV-+A5d82R_s(Ld zs^+2`a??63+Fm?~-BBD9A`F!eOkSKb8E=n*D#TW<6=*XXih3s3) zO+>IzZ&aHJjeNKzQThUKk>A9;x=p^m*KyhLLhL;fLRYC#$Hv%q^~syiR!aCaS)X zecbAfa9o|=g6iw51q-5XJa0mOpqb=9RcF%)sT5FG)B=bA#aC~9-D)7xaxA&=tj8XA z?1Nc%Q83-&@`g#DT^l4tlFo5UeJ}lXD?Z=`T>8aDKx|!#GW>oMegEh+tql*$`!v zH(%Vv+x4CbXfiUeyj<e5b-0@o+UNp`z)3wxy0?Iy5>5nF$m}L4Tsg{-@fS{vD;02 z$))OUnZi18xlwp;^FxgG>0;ZAM|`HwHEWV?@6uCzByOqd?u353&!RS9viOdb#|t#^ zGT}Anu#?Y9EoOrM90EqHxy{sMyiS^Ua@0S64c&ATo;TEZKK&)deAr;`-pI8Ip1HQI z+VPE)I>Z`<+Um>qc9FM08cDeY&qIl&R@O`(`AlWPRh z>sQvT8^BvV_lj3!PW^Yf_n*teKI}C87${u|+1*!pc}0(DZ5`9S2FybxZvnCaia?Le z`Sa(^N`CsV0i87JQFPk;tI5&O$2TH6(lAImZEP|F5=hM_`yJK#(zOXU?(l5PUrFmp zRIJXMTengt-c9_G&# zA}DIa%5-*K`*kwarLm*7LRfmw$#$jSU6Y=>B1A!;`o|AX_f)cIP$ieJoaXhREZF7g!SMEW<=R|6VV z)J&%#C2Nk}cjf`zl@WKSV*WuU5VPH5Ks?x)buUmDiNGLf8ortYc&Vbm%K?n5_|Wv} z$U&FCR&i>BX5g-oC;N7=7}M2%CYo5QUE0*r&{?%dupwdS@(2##HQhZ2DiX`C+ywe- z#f5pW)}TXYC?FGB!|nXf?7|&FK%S(>Q-~_Ln-Q|?L!_~QrZS`i9^*zs>12lo> zv%2zdqi0s4VYet8^K|FDVDF1P&@W1U{&ua~VcSLu*rrW;O4e&1MG;pYa^~J*=bvx{ zTU8#KSWTk79`4g-VsMzih0XP$qslsjNm#%U2U70uUDp_zZsq}0pUCRKfe0B-6?fK_ zLV&vVSO%vBF!_A03hiix;<0B5Nui0Jf3zx0( zd6&>1=k-G5#(nzy47kj_6vhC1@$*@dFDTEiC_A|iJ(XbZ+RAjzu zWRqZi)yMv06KX#6e=jL0+oq%|@SsvM8d&}O>0fzdUqx^6qWs3Y9BJ44bcRCt-FNB@u3!QH(~r90>X@*ZE{deR{9!@|O7_WR zM_a49pBopSWN&C8b#lUb?Qd9MKbS#?d!M;{-!co?w)<3b7;n#ecck@;XI)rw2u7#M zJx@%BWecdGw@&eQ#qU}Ss`Pf0mqKig!pdT>#vE1E$*`S3txA5ai}`X5Cu1mr~$w z8_GT+b)k5Ia+F|Nd(ck%H+yPk50S{Cl%pF&D z91vn8wnnuwTdQjNhfv3`cZD{d6Xety53Twfk5YO92-d}Efl6C7a11zGb(#J(qqRBm zeVqRQ&Zw9u3TIptrbomuB2viAU^AVN`@Q#Rb0a3hG21ZE$+IP>`>B_*9%dEsG8aid z9$yKYzWH)F1ehvx?`Mv6TJg_>oE{o~KjFO`0qFTNy1I{9Os_dq`aes>p4mBcc1roE z4GnzJibVZWN!QTQ0VLM;lAOIc&Cz+z1Q1ya<)Nn8MDn2FOO1cbWU;*33*~$gzFpaZ zUpvbW3>>P;lU%)tA*DN9|i8(EsFPriR3TfJHW-S{@f)Tz;b66Kpfb2&JnB+)oT6_A;=_ zz%qUeULUfX&@arobrA%P)%E_$lZI`*dplk*4SO2jRRe^30@p3?=*b%myXrmIzR^#& z78l6NgKZU{3<$va0i`W&1i_cTwegSHKR>)QD^XI$0*-pBI~WEiSp#!ujLy#ft&%pg zKd?VO{M`uYiF1xZXZ^lc7a;nSef!PauHS8)Sl<#Kh1HONLwO1Brq?TjG}@E3y%V;o z>t*dGdf;_pDc5b5aUy2#$Wgr-R^Lu~07(&A!_9TN_K`~Hv>nm+1qFHW z$z+CzdY9FzPEf>s=*Yg-g*n&@8IKyJv~Oj-l2O%^06Xa@F~yAG^(L`bK zWbPj639((l|5H`a^WpGN)5AUlbT#5~td(fq z+p3DJgz`I*Nnch?^GYE=IeU^uandz2&(X}!m)k?7$eDnR?0b_nbY&2RrPH2}y1w09 zbC{+zNim`FeKag5x+12tM)*M`w5T-~1X_hBHrM9wKKg#IwMyUVyH}wz-vuD!K1+L4 zU-P;x90TE2M-^Bu0Rq&OZIJN_D`&#SzUb#LF#{4zHw6nv6Er%nkLSWU+%^!{*8nP=O zsb+|;CU-lPgQux2u(n~14Cw41R}_27f5M0PCDl}BrZNkXv90!QnW%fB*(u*c!QDMe zhC`uwP7~}-y1xJ*BE-@$(H_O1(b?zf>=q<{qfPFdJRs6^aoqcU6mtV}grlvxURb-& zC6D_N2G_RsU~(4L4KfRz03Dn}?XD2!KhNJJ;=$nWOSF5e0 zWQ|xxVEXUhXxpeYp5`cJ2eTAxj2Sg>5E<|rV-uL|uz#1#dr!0iBifBEgg7w)y z2`}?U#uREpUq~2fNV0gUvM>F*dpq&za}(+mE^f#1#ur00ilUQBJuP=k8mJn>w%?Qz z?nLs?RJ#T+kB*zYIgPV@NS`3(QiNRr^oIi`kL3WBKzu(b*Qrk4OD*aeFn7-tUTfY2}+Am%n1F7U^Yl3rsrs--r;yNsmBES zEzVhmP4dNIRxLfiBd0xjFiJyla`PK|$CBr)Fy%~pSim$s>E(}&umz*EO)sSINvG<@ zPQH#4(icZQ3IPLScQ$w-x1ALvk#7z6e5&4292F6?q;W?ulT419#RCZ;IVrBLy-}y? zL4PLzTe{Bj2q!&9()|TWP#2IfM#G$McG@4@zJMLN1rTu!j*B6gM} zdM<6G5GR^TC)5BmO*K9NROoNIyD%GderLb)Ob9joTzMtRQ)jRpAfL{Qz&*$)xXN|| z2Nsjv)=LKvt}j8wvH+lp;g2Hz;SVW&d)s5CGc6|{Bj5~gayb|ZC_Q_TY*?81BANe&n}X6LQhi3;qKnnl$cR$5^48eh+s&q}y`+(^OB zO4NWZo@gIwG?ou20W=iDu}ahtSCeb#$7ShLg&*#1-RG3+r=k0OnOkF+U@CxjvEiyDj3eV)LtA<#a_NlWL0^=e5EQ*g6oghYnkhdrN8ncv~(pD3}H$2 z4G@vGkg3=f8Q|CvHWN&ZRm3=M$bC9s{NNkq1hD=bA8N`}ti9@MkL)X9wm4gA2Q5&@ zrV-D%ORymDlw0*FFtZ%mzi;-tj?rFEy4B!JwG-6gCR@y=mSH~vDAGaY_7(P}9Chf1lj%Z?u0f0W&jUAfzG6+^60}&zrw5(Xa@hvY{ zkTONCj7^RDHgXJetW;#4L*$ZRH72`Yt6Ok>iQko{M`-`&!MQMUT1e7Mg+&RdZiY-N zY>EIQ$*0xXw$r@WOt3Cv4;#BDA{_5czjvCNmiy&C`8rD4Ta}9#3Ml!=8sG+|f~M}a z_9XN;l6;`5bxP4FWViM-6Cm3ml;^wxYFyWUP!YfUZr62;bn{TOou3j2 z1GSS+`{=1)Tm;Q?cUf;2u9&cS)f;o31(fij9*=jbtlVC(FqB8cy`g8c6oXH7CmH>K zl1vJrzvwv&3gu*7C&uAAo=S0*`#m@e(-=J9)n$f$T2PlyRnsAJJIpH*BZ${ z=n#hH{iG=oF5I0U`5Zg3Vjp=1%OFXA4{+-(Jo)U+5Wwo^}2H>zm*YT@{1CiE>_uUZs7oak;VpLP%c@yjuNe`S9Es2&U- zw!qjb0&=eVhxCZI`g<1)ZxxlUjTMlHUts$+^WQ$((|1=WSAoOUTO< zPpDh-IYes_^rVp!cUi`tl&INPRcnPMaxOVui!XErCkT~Jh z@hB|x;k4+bD~DQ&pNBqYVAx%Xsn-D7{I`@sNjbsw{8HyOOKALxB(%*-h1NdWtl6q$^AcjeWvgDGji#~>5qaoJ`S;Qb?KqaL1U+BNXX5e zvIy#3E|rFG>K`vgNa1MgjA#w7FVjNBvnHg7^uuCchoN=hXehHC-NGq>F-%>gGE7aBl~nPogd9LWm!8YfjvsbF9#SXfai_5ruD zvm2Qtz`rf}+1GfOp`n!7N$9!V{Rxt5JV2xLuX;=CVES`J8%G)OlBW;eY>3A{8uS1S z2w~i(OMpRgZ%QH7Pi-o@{aC$}Sd17DLM~eHKzi!@m5_}SgbPs<=JQ@XD2U3m>vI(# zGM?*v{ZR|*pi%_9JpG&56btaDyRo7C9=9uqu9lT72cZf`vi@xks49zsg7e|(O+xtM z;i$A2n#&g$e2^%w(*waV3w=Uo+rgoUd^|s={z>Fvu=x4tl7@#8t2pf_nt^V-O_@?` zuI&jL3F}jbDs~5zPFyVBH)jENvojn=MP%$ny)|gVk8p#dC`Ze2bKUP1hB}c=%KZis zKAw zlZ??&@mJMJ)V7Z54yn=5+WWzdqSwW9FbQN!sob)Cemui^k?QDDF|W%-u*fAIOw23^ zh)YAJ%Kq7hC!9#m00zlpk7D)xJKWEJK-2Cx%&>;ubi)EL;@>lWtd}VomG$Ecr9m&X z<1%m(;s*bSryX9(0Nwzci`Ta;ff&B!=3A`UeIH*_ZX-kKnymg^HFlfZ+@BsRc>sr` zKO9~ml^xZ|ui;_wprUy6qN!b-t+HNL-4pwYy!)h(=RV9zM?0K@F2W2w0M+XA@W#eG zSV@qK*i6@(%A%JK*Tq;qkN3@lwT1U>A7m!K8X?(V4Rp&o%KqB*~TH8*|kBh92yX>hUiTg}8HtTa|fC;s>kZg50shs10fWU4WCW31eI??S4|-icby<&=FRSf`7VQj|h+d+6~4 zV#sk+G>Q&5puZvP%so|=W@Z8F{2n^&k6q-lL5qlt(D|oT#{nu`X>~~LY_9RxTL(zY zm@ONh>4rwdl@Xr)fPg|lQ~u9=(3ti3VNzOGj^1Z_npXm6E5IOGq84xd8N6sdlS1a( z``QHfrB$mH)i>Xe-I)uT=QNqY>uf5hrqUTRquT3Mwv!e6_pH7j#WZC-O2K?AF~A{=79s#G*^s`>>=K%o6%S&Yk3`bjkn;Enj{P z+K%^J zd3gRN@iPu$2)MeB84uTQjU_twK2gJ&2@4`*p80BV2UNdm5Ej~+U#c~&1u9dFw(Hr? zVLp9bT`ZQ8_s*i>Ip^Pu_Rx);FexI#x38ntq&a?=p1k^eMrwV*ybk>Ni4!!Hc`jl7 zMYGevLCw%OaC;iHUGU^fD)a3xNhFTR?T08P*R_v#6`&chEV`;=>OsLlz%2zZjDYw@ z^=CY!mQ(laMn;anfbjE{bLHt#Xb}f(w!BVsuxb){QcAAP3=yg3+NVBd<6D)@T>bV7 z(b3SV<;c@n?Asno8_$$#b~p;HU;hGlF3Kwz#8_Ggm_g$wUMO%M4M;>`p}r<9v@b+E zgyVq5_aQ<2|I_+XDFdNl0wTQ#2fa}q%lQM_l%tFO@iS055D-JTXUU-86X!F?2Zn%P zgvd>vWh0U9-y`ED2`gck))(&83Msjdb=H%y)_Dsmy)-vEzR9VW729!uB}7!te?(C) zt+ZzS{K$=@JT-Jv5=s5TnMGj-kbBU34=r2`%YN6`*a>fRCD`=%JKx>UD>hdK-Ky8=&^`ehq$$iP0x7I|(kZ<`(~Q!F^{EK- z`alPzVR3y8#Eh(8@ZX(qYNrwGHZ8|^cyI0*JBI#Uu&p;)r?;{k1KPf!`a>7p%OZU( z#Bcaq`Pru<0f-%HDiP|-4xlJ%hiG7Tl;d)E*m4x-s;Mkdnk{XHj{2uWTxK%OW&K#U zv8>ub0L&25!bieara!()dp++YVa|G!8oVVc7yL8|XgM<2brE=kvvYpM%TCb3LEDMC z)S^sV?@x7~NAM=Yq{@fHtF7Uy+a#d%yxF%$khiAB@umvd9^qe#v*@=g>}PK}!5^$6 z;1p_8G+}B2>LcLTyP)Eg@h*lgB?PTC>&HPp0L>DZIL_9j#j>VA(|6`~5*V%6(>R9? zWsmdyv80D1m+_R9u%R?g-CcY6nfQ z1*KJEVb6M4SVC{$JMo2sukf9HiM|Z zV+@LZN>T@vTQM_G3zI1cm}oS_m;dP3vz+I@k&i<+{T~+-?_R!a+d(o%+2}_{=+CxB z84rKB1kMXKnpC=@#ugh9pcbVx#g;Ca)aAl(_?gPsC2l_rF#*gf!AIY|1N zr`Y9G1MTJekxSt}!mNAo5JPQ@(7nepRO73dJH`C^AP4oo|57_+dFUPb*fU`7{nh5H z6D-#l8SwdPQV7;?|KGij_>u9)Sqv~G|oIWTIP zDTlnq-~IW%z{D&6^E}YGj`=HfVfb~ANV{QDLj0y{5a-j43Ah9bjI|(i8uFdlIdHcu z&AqaZQP(`wxc{Uc9I_f*is^_(A7`MCjJv5q21|)y1xR|kuA4IFKMpCow=&+>GBFA2 zP&3iwA1~7+be;O+usdhQ3e;_JaSvFK-e+M`-Ga_@Bh4xW60w_!n;8xHqYe3-(#Gv* zTZAUEPtG+%*JWIXaV=k@Y@yIHr;#Y`2630bSg*k!Mp<<=Z9>y}8(W9FbV9Y)4jIw9 z2GD^Dn72f6-kv4^{=H94#yhUR)GQ4FD-Sorq&hSLSN=1KScEeDP{uQ zAWms7GxLqb#@1vcIA{Zr`eQX*+vIB)sy`CK6^5De5>ilKMWBzx%wc&w6nkC%j16Gt z>zezySq&`V>UJmk>8VXTa8A8fRbW+MSUMB{;HH(CtGPU_OvTF6BdIzLj7RT@b0eeE z7c`Dw9j4sTN3@qk*Sr7wz^zthMy~hWM33hK*ZR7uq;g1BtUNXA7X zmI|0SmpJAMbws5|b`yp6KWz=y=vy65=I?lw4>ma)X`sL6Tp}qoccobjh%Q0_PqPIN z48GKszxHsU-+=CR=oX2=BxfW9Qdq!^1cHL!Pj#scpER2Akixb{=S*fEpX0B|G?mW^ zjS+c6w095t=E%a|tVPlj~a&Lni%<}n2ZV2ei%wfVH;CO1-mgxUaq0+PIKl69i9r4{!4nsBzBAyK;>%7P*in7w z6fO4haMJJtz|$7L=)Eb+Xl_59I5o66wY!8#-!I^PZP~`Cc~s!R6=fNkg^$#T##(8r zs3JJ?!$*l2$0ZvJYi%Fy?275UBpaLb~K`3t0)td;7~Jy0pN$4S>W zYJ0h0J)L1-;^{=_9(#>f2;+m5%?r!R&(ub$@VDY?c*#X|J>>q=FnXr(mE3c@-2#yK z_U1OxrC>>vX|)^ph83(bsL{sxtG8JJz{RdFP%PKQvP`#|`yh&`9;$ofSnB6T-;Jfp zH!kd&u*T7@Gs@OUuqS7dKi)n5!3#f7A{e~>jL+(6V`FH-A*9xVRmhyxTr7Fp6l!fgi0?;8^5F(k{{GNj!cV~typcfkIo~5MCHoN zS{#SdlHKHk`)nL-XBJh~7cTFRY?8ST4aX?-RVG8T=rnBWm+SQAFRVYYA`+Yfhp8Ut zoxNgkNg~!YFnGn;!1<;3`A%Q3Tm)s|OKv-Ta%w7nk?8f+`%`S!a0G??U~y;j3n@Il z*B}dHAd{~7lM7anDxVHtxD|3YC^+Hh-KObQTf%rFa~t zH1wgN$m93i#_B)a{kU8aR~>l#DZIQW1AnzOuN0XLSzQms69jd zBwINx%=s8{O9FX(QeV_mhjN^4Hq!82vIL|fujpH4gj`BmC7E z&Apx(8oX^hq;X3)p|dQ5t*tDAT5Qah;v$n{y4v-RzfZrQ1RKRukQI7##GWP?v%6H? zV)jXolTA0Wkj!lEfx<4CNM#vu-~6(r z*v(`fp;Exy1Recvrgq7^6w|pe?J0N;_Ejj!66OTHnJ|xAG%g{U>{I;xUy8xaD!Di9 zTGx zVN&vEV6oC!n)Vw*NF9oliM|*T;C{A=xbT3Opa2OvbzG@ z!v0nIE05>u_E}n(7f5ZTlUd!DVmih5Hoq|s=l1AMtEPYE3V^he*xJErL;CBp)@O@s_EH&cGdAehPCniA@@#Gz- zHbwhTX{l^fGNL`JvKWZUe~&Hr4VlO;C}&-G3?p^ literal 0 HcmV?d00001 diff --git a/src/assets/svg/cursor.svg b/src/assets/svg/cursor.svg new file mode 100644 index 0000000000..274df257e6 --- /dev/null +++ b/src/assets/svg/cursor.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/liquidity-pools.svg b/src/assets/svg/liquidity-pools.svg new file mode 100644 index 0000000000..bd62353ea6 --- /dev/null +++ b/src/assets/svg/liquidity-pools.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/liquidity-positions.svg b/src/assets/svg/liquidity-positions.svg new file mode 100644 index 0000000000..a66214e69c --- /dev/null +++ b/src/assets/svg/liquidity-positions.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/staking.svg b/src/assets/svg/staking.svg new file mode 100644 index 0000000000..f4812c0fac --- /dev/null +++ b/src/assets/svg/staking.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index d2381332b1..46b88d13e0 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -18,7 +18,6 @@ import { useHolidayMode } from 'state/user/hooks' import { MEDIA_WIDTHS } from 'theme' import AboutNavGroup from './groups/AboutNavGroup' -import CampaignNavGroup from './groups/CampaignNavGroup' import KyberDAONavGroup from './groups/KyberDaoGroup' import SwapNavGroup from './groups/SwapNavGroup' import { StyledNavExternalLink, StyledNavLink } from './styleds' @@ -208,10 +207,9 @@ export default function Header() { {!isPartnerSwap && ( - Market + Earn - Analytics diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index e6b57a5f7b..7938b99e71 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -223,7 +223,6 @@ export default function Menu() { const showAbout = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const showBlog = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const showAnalytics = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) - const showCampaigns = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`) const bridgeLink = networkInfo.bridgeURL const toggleClaimPopup = useToggleModal(ApplicationModal.CLAIM_POPUP) @@ -352,26 +351,23 @@ export default function Menu() { )} - {showCampaigns && ( - - } - title={ - - Campaigns - New - - } - link="#" - options={[ - { link: APP_PATHS.AGGREGATOR_CAMPAIGN, label: t`Aggregator Trading` }, - { link: APP_PATHS.LIMIT_ORDER_CAMPAIGN, label: t`Limit Order` }, - { link: APP_PATHS.REFFERAL_CAMPAIGN, label: t`Referral` }, - { link: APP_PATHS.MY_DASHBOARD, label: t`My Dashboard`, external: true }, - ]} - /> - - )} + + } + title={ + + Campaigns + + } + link="#" + options={[ + { link: APP_PATHS.AGGREGATOR_CAMPAIGN, label: t`Aggregator Trading` }, + { link: APP_PATHS.LIMIT_ORDER_CAMPAIGN, label: t`Limit Order` }, + { link: APP_PATHS.REFFERAL_CAMPAIGN, label: t`Referral` }, + { link: APP_PATHS.MY_DASHBOARD, label: t`My Dashboard`, external: true }, + ]} + /> + {bridgeLink && ( diff --git a/src/constants/index.ts b/src/constants/index.ts index 8a32a77b5f..f41827ed59 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -169,6 +169,7 @@ export const APP_PATHS = { LIMIT_ORDER_CAMPAIGN: '/campaigns/limit-order', REFFERAL_CAMPAIGN: '/campaigns/referrals', MY_DASHBOARD: '/campaigns/dashboard', + EARN: '/earns', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/App.tsx b/src/pages/App.tsx index d694425119..7942dc3027 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -62,6 +62,8 @@ const NotificationCenter = lazy(() => import('pages/NotificationCenter')) const Campaign = lazy(() => import('pages/Campaign')) const CampaignMyDashboard = lazy(() => import('pages/Campaign/MyDashboard')) +const Earns = lazy(() => import('pages/Earns')) + const AppWrapper = styled.div` display: flex; flex-flow: column; @@ -314,6 +316,8 @@ export default function App() { } /> } /> + } /> + } /> diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx new file mode 100644 index 0000000000..8bea6a50b0 --- /dev/null +++ b/src/pages/Earns/index.tsx @@ -0,0 +1,144 @@ +import { Box, Flex, Text } from 'rebass' +import styled from 'styled-components' + +import bg from 'assets/images/earn-bg.png' +import CursorIcon from 'assets/svg/cursor.svg' +import LiquidityPoolIcon from 'assets/svg/liquidity-pools.svg' +import LiquidityPosIcon from 'assets/svg/liquidity-positions.svg' +import StakingIcon from 'assets/svg/staking.svg' +import { ButtonPrimary } from 'components/Button' +import useTheme from 'hooks/useTheme' + +const WrapperBg = styled.div` + background-image: url(${bg}); + background-size: 100% auto; + background-repeat: repeat-y; + width: 100vw; +` + +const Container = styled.div` + max-width: 1152px; + padding: 60px 16px; + margin: auto; + text-align: center; +` + +const CardWrapper = styled.div` + border-radius: 20px; + border: 1px solid; + border-image-source: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.06) 104.02%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + background: linear-gradient(119.08deg, rgba(20, 29, 27, 0.8) -0.89%, rgba(14, 14, 14, 0.8) 132.3%); + padding-left: 36px; + padding-bottom: 44px; + text-align: left; + min-height: 360px; + display: flex; + flex-direction: column; + + cursor: url(${CursorIcon}), auto; + button { + cursor: url(${CursorIcon}), auto; + } +` + +const Card = ({ + title, + icon, + desc, + action, +}: { + title: string + icon: string + desc: string + action: { text: string; disabled?: boolean; onClick: () => void } +}) => { + const theme = useTheme() + return ( + + + + + + icon + + + + + + {title} + + + {desc} + + + {action.text} + + + ) +} + +export default function Earns() { + const theme = useTheme() + + return ( + + + + Maximize Your Earnings in DeFi + + + Unlock the full potential of your assets. Offering data, tools, and utilities—centered around Zap + technology—to help you maximize earnings from your liquidity across various DeFi protocols. + + + + {}, + }} + /> + {}, + }} + /> + {}, + disabled: true, + }} + /> + + + + ) +} From ef1830b6904582d9dcf49cef47a93c7658d0619a Mon Sep 17 00:00:00 2001 From: viet-nv Date: Thu, 7 Nov 2024 18:27:39 +0700 Subject: [PATCH 02/83] feat: integrate api for zap earn landing --- .env | 1 + .env.dev | 1 + .env.production | 1 + .env.stg | 1 + src/assets/svg/fire.svg | 3 + src/assets/svg/low-volatility.svg | 11 + src/assets/svg/play-icon.svg | 19 ++ src/assets/svg/solid-earning.svg | 4 + src/pages/Earns/index.tsx | 366 +++++++++++++++++++++++++++--- src/services/zapEarn.ts | 43 ++++ src/state/index.ts | 3 + 11 files changed, 419 insertions(+), 34 deletions(-) create mode 100644 src/assets/svg/fire.svg create mode 100644 src/assets/svg/low-volatility.svg create mode 100644 src/assets/svg/play-icon.svg create mode 100644 src/assets/svg/solid-earning.svg create mode 100644 src/services/zapEarn.ts diff --git a/.env b/.env index 1a752e7625..f56ab031b2 100644 --- a/.env +++ b/.env @@ -47,3 +47,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.dev b/.env.dev index 1aa6773ffa..74ea22a571 100644 --- a/.env.dev +++ b/.env.dev @@ -48,3 +48,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.production b/.env.production index 474c13ba1a..7bf1468f11 100644 --- a/.env.production +++ b/.env.production @@ -47,3 +47,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.stg b/.env.stg index eebf91f8ec..324cedd5db 100644 --- a/.env.stg +++ b/.env.stg @@ -45,3 +45,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/src/assets/svg/fire.svg b/src/assets/svg/fire.svg new file mode 100644 index 0000000000..6553da38b9 --- /dev/null +++ b/src/assets/svg/fire.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/low-volatility.svg b/src/assets/svg/low-volatility.svg new file mode 100644 index 0000000000..75a48cfbba --- /dev/null +++ b/src/assets/svg/low-volatility.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/play-icon.svg b/src/assets/svg/play-icon.svg new file mode 100644 index 0000000000..496aec879e --- /dev/null +++ b/src/assets/svg/play-icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/solid-earning.svg b/src/assets/svg/solid-earning.svg new file mode 100644 index 0000000000..d3b6facf36 --- /dev/null +++ b/src/assets/svg/solid-earning.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 8bea6a50b0..b0e2fa63da 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -1,12 +1,21 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { rgba } from 'polished' import { Box, Flex, Text } from 'rebass' -import styled from 'styled-components' +import { EarnPool, useExplorerLandingQuery } from 'services/zapEarn' +import styled, { keyframes } from 'styled-components' import bg from 'assets/images/earn-bg.png' import CursorIcon from 'assets/svg/cursor.svg' +import FireIcon from 'assets/svg/fire.svg' import LiquidityPoolIcon from 'assets/svg/liquidity-pools.svg' import LiquidityPosIcon from 'assets/svg/liquidity-positions.svg' +import LowVolatilityIcon from 'assets/svg/low-volatility.svg' +import PlayIcon from 'assets/svg/play-icon.svg' +import RocketIcon from 'assets/svg/rocket.svg' +import SolidEarningIcon from 'assets/svg/solid-earning.svg' import StakingIcon from 'assets/svg/staking.svg' import { ButtonPrimary } from 'components/Button' +import { NETWORKS_INFO } from 'hooks/useChainsConfig' import useTheme from 'hooks/useTheme' const WrapperBg = styled.div` @@ -23,18 +32,92 @@ const Container = styled.div` text-align: center; ` +/* Spin animation */ +const spin = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +` + +const BorderWrapper = styled.div` + padding: 1px; + position: relative; + background-clip: padding-box; + border-radius: 20px; + overflow: hidden; + + ::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 1px; /* Border width */ + background: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.06) 104.02%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); /* Mask to avoid background bleed */ + z-index: -1; + } + + :hover::before { + top: -20%; + left: -20%; + right: -20%; + bottom: -20%; + padding: 1px; /* Border width */ + background: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.6) 104.02%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 1) 0%, rgba(0, 0, 0, 0) 100%); + + animation: ${spin} 2s linear infinite; /* Spin animation */ + } +` +const PoolWrapper = styled.div` + border-radius: 20px; + position: relative; + overflow: hidden; + padding: 1px; + transition: box-shadow 0.3s ease, transform 0.3s ease, background 0.3s ease; + + :hover { + box-shadow: 0px 12px 64px 0px rgba(71, 32, 139, 0.8); + ::before { + background: linear-gradient(215.58deg, #262525 -9.03%, rgba(148, 115, 221, 0.6) 59.21%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(130, 71, 229, 1) 0%, rgba(0, 0, 0, 0) 100%); + } + } + + /* Create the gradient border effect using ::before */ + ::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 20px; + padding: 1px; + + background: linear-gradient(215.58deg, #262525 -9.03%, rgba(148, 115, 221, 0.2) 59.21%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(130, 71, 229, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + -webkit-mask-composite: destination-out; + z-index: -1; /* Position behind the content */ + } +` + const CardWrapper = styled.div` border-radius: 20px; - border: 1px solid; - border-image-source: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.06) 104.02%), - radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 0.6) 0%, rgba(0, 0, 0, 0) 100%); - background: linear-gradient(119.08deg, rgba(20, 29, 27, 0.8) -0.89%, rgba(14, 14, 14, 0.8) 132.3%); - padding-left: 36px; - padding-bottom: 44px; + + background: linear-gradient(119.08deg, rgba(20, 29, 27, 1) -0.89%, rgba(14, 14, 14, 1) 132.3%); + padding: 0 36px 44px 50px; text-align: left; min-height: 360px; display: flex; flex-direction: column; + overflow: hidden; cursor: url(${CursorIcon}), auto; button { @@ -42,6 +125,56 @@ const CardWrapper = styled.div` } ` +const ListPoolWrapper = styled.div` + padding: 20px; + border-radius: 20px; + background: linear-gradient(119.08deg, rgba(20, 29, 27, 1) -0.89%, rgba(14, 14, 14, 1) 132.3%); + cursor: url(${CursorIcon}), auto; +` + +const PoolRow = styled(Flex)` + gap: 12px; + align-items: center; + border-radius: 999px; + padding: 8px 16px; + + :hover { + background: #31cb9e1a; + } +` + +const Tag = styled.div` + border-radius: 999px; + background: ${({ theme }) => rgba(theme.text, 0.1)}; + color: ${({ theme }) => theme.subText}; + padding: 4px 8px; + font-size: 12px; +` + +const Icon = ({ icon, size = 'medium' }: { icon: string; size: 'small' | 'medium' }) => { + return ( + + + icon + + + ) +} + const Card = ({ title, icon, @@ -55,40 +188,48 @@ const Card = ({ }) => { const theme = useTheme() return ( - - - - - - icon - + + + + + - - - {title} - - - {desc} - - - {action.text} - - + + {title} + + + {desc} + + + {action.text} + + + ) } export default function Earns() { const theme = useTheme() + const { data } = useExplorerLandingQuery() + console.log(data) + + const title = (_title: string, icon: string) => ( + <> + + + {_title} + + + + ) return ( @@ -138,7 +279,164 @@ export default function Earns() { }} /> + + + { + // TODO:: go to explorer page + }} + > + {title('Highlighted Pools', FireIcon)} + + {data?.data?.highlightedPools.map(pool => ( + + ))} + + + + + + + { + // TODO:: go to explorer page + }} + > + {title('High APR', RocketIcon)} + + {data?.data?.highAPR.map(pool => ( + + ))} + + + + + + { + // TODO:: go to explorer page + }} + > + {title('Low Volatility', LowVolatilityIcon)} + + {data?.data?.lowVolatility.map(pool => ( + + ))} + + + + + + { + // TODO:: go to explorer page + }} + > + {title('Solid Earning', SolidEarningIcon)} + + {data?.data?.solidEarning.map(pool => ( + + ))} + + + + + + { + // TODO: go to explorer page + }} + sx={{ + cursor: 'pointer', + border: `1px solid ${theme.primary}`, + margin: 'auto', + marginTop: '40px', + borderRadius: '999px', + height: '56px', + background: rgba(theme.primary, 0.2), + fontSize: '16px', + fontWeight: 500, + color: theme.primary, + alignItems: 'center', + padding: '1rem 2rem', + width: 'fit-content', + }} + > + EXPLORE POOLS + play + ) } + +const PoolItem = ({ pool }: { pool: EarnPool }) => { + const theme = useTheme() + return ( + { + e.stopPropagation() + // TODO: open zap in widget + }} + > + + + + + + {pool.tokens[0].symbol} /{' '} + + {pool.tokens[1].symbol} + + + {pool.feeTier * 100}% + + {pool.apr}% + + ) +} diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts new file mode 100644 index 0000000000..fe9386b08e --- /dev/null +++ b/src/services/zapEarn.ts @@ -0,0 +1,43 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +export interface EarnPool { + exchange: string + address: string + type: string + feeTier: number // need mulpliple with 100 to percent + chainId: number + apr: number + tokens: Array<{ + address: string + logoURI: string + symbol: string + }> +} + +interface Response { + data: { + highlightedPools: Array + solidEarning: Array + highAPR: Array + lowVolatility: Array + } +} + +const zapEarnServiceApi = createApi({ + reducerPath: 'zapEarnServiceApi ', + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_ZAP_EARN_URL, + }), + keepUnusedDataFor: 1, + endpoints: builder => ({ + explorerLanding: builder.query({ + query: () => ({ + url: `/v1/explorer/landing-page`, + }), + }), + }), +}) + +export const { useExplorerLandingQuery } = zapEarnServiceApi + +export default zapEarnServiceApi diff --git a/src/state/index.ts b/src/state/index.ts index d7a9ab8863..3b54ed47ba 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -20,6 +20,7 @@ import referralApi from 'services/referral' import routeApi from 'services/route' import socialApi from 'services/social' import tokenApi from 'services/token' +import zapEarnServiceApi from 'services/zapEarn' import { ENV_LEVEL } from 'constants/env' import { ENV_TYPE } from 'constants/type' @@ -108,6 +109,7 @@ const store = configureStore({ topTokens, [routeApi.reducerPath]: routeApi.reducer, [tokenApi.reducerPath]: tokenApi.reducer, + [zapEarnServiceApi.reducerPath]: zapEarnServiceApi.reducer, [referralApi.reducerPath]: referralApi.reducer, [campaignApi.reducerPath]: campaignApi.reducer, [commonServiceApi.reducerPath]: commonServiceApi.reducer, @@ -133,6 +135,7 @@ const store = configureStore({ .concat(routeApi.middleware) .concat(socialApi.middleware) .concat(tokenApi.middleware) + .concat(zapEarnServiceApi.middleware) .concat(referralApi.middleware) .concat(campaignApi.middleware) .concat(commonServiceApi.middleware) From 5a58dd299cad68017a4eb9f38a2c88a0ab981722 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 02:07:10 +0700 Subject: [PATCH 03/83] implement pool explorer --- .env | 1 + .env.dev | 1 + .env.production | 1 + .env.stg | 1 + src/assets/svg/help-circle.svg | 6 + src/assets/svg/ic_pool_high_apr.svg | 14 ++ src/assets/svg/ic_pool_highlighted.svg | 5 + src/assets/svg/ic_pool_low_volatility.svg | 23 +++ src/assets/svg/ic_pool_solid_earning.svg | 6 + src/components/Image/index.tsx | 32 +++ src/constants/index.ts | 2 + src/pages/App.tsx | 4 + src/pages/Earn/DropdownMenu.tsx | 151 ++++++++++++++ src/pages/Earn/TableContent.tsx | 165 +++++++++++++++ src/pages/Earn/index.tsx | 235 ++++++++++++++++++++++ src/pages/Earn/styles.tsx | 133 ++++++++++++ src/pages/Earn/useFilter.ts | 43 ++++ src/services/ksSetting.ts | 1 + src/services/zapEarn.ts | 83 ++++++++ src/state/index.ts | 3 + 20 files changed, 910 insertions(+) create mode 100644 src/assets/svg/help-circle.svg create mode 100644 src/assets/svg/ic_pool_high_apr.svg create mode 100644 src/assets/svg/ic_pool_highlighted.svg create mode 100644 src/assets/svg/ic_pool_low_volatility.svg create mode 100644 src/assets/svg/ic_pool_solid_earning.svg create mode 100644 src/components/Image/index.tsx create mode 100644 src/pages/Earn/DropdownMenu.tsx create mode 100644 src/pages/Earn/TableContent.tsx create mode 100644 src/pages/Earn/index.tsx create mode 100644 src/pages/Earn/styles.tsx create mode 100644 src/pages/Earn/useFilter.ts create mode 100644 src/services/zapEarn.ts diff --git a/.env b/.env index 1a752e7625..f56ab031b2 100644 --- a/.env +++ b/.env @@ -47,3 +47,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.dev b/.env.dev index 1aa6773ffa..74ea22a571 100644 --- a/.env.dev +++ b/.env.dev @@ -48,3 +48,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.production b/.env.production index 474c13ba1a..7bf1468f11 100644 --- a/.env.production +++ b/.env.production @@ -47,3 +47,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.stg b/.env.stg index eebf91f8ec..324cedd5db 100644 --- a/.env.stg +++ b/.env.stg @@ -45,3 +45,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/src/assets/svg/help-circle.svg b/src/assets/svg/help-circle.svg new file mode 100644 index 0000000000..9f74035e94 --- /dev/null +++ b/src/assets/svg/help-circle.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_high_apr.svg b/src/assets/svg/ic_pool_high_apr.svg new file mode 100644 index 0000000000..a53bbd26a8 --- /dev/null +++ b/src/assets/svg/ic_pool_high_apr.svg @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_highlighted.svg b/src/assets/svg/ic_pool_highlighted.svg new file mode 100644 index 0000000000..f75f1817b9 --- /dev/null +++ b/src/assets/svg/ic_pool_highlighted.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_low_volatility.svg b/src/assets/svg/ic_pool_low_volatility.svg new file mode 100644 index 0000000000..943eeeb223 --- /dev/null +++ b/src/assets/svg/ic_pool_low_volatility.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/svg/ic_pool_solid_earning.svg b/src/assets/svg/ic_pool_solid_earning.svg new file mode 100644 index 0000000000..6c9f4f6298 --- /dev/null +++ b/src/assets/svg/ic_pool_solid_earning.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx new file mode 100644 index 0000000000..7a5ec977bc --- /dev/null +++ b/src/components/Image/index.tsx @@ -0,0 +1,32 @@ +import HelpIcon from 'assets/svg/help-circle.svg' + +export function Image({ + src, + alt, + className, + width, + height, + style, +}: { + src: string + alt: string + className?: string + width?: string + height?: string + style?: React.CSSProperties +}) { + return ( + {alt} { + currentTarget.onerror = null // prevents looping + currentTarget.src = HelpIcon + }} + /> + ) +} diff --git a/src/constants/index.ts b/src/constants/index.ts index 8a32a77b5f..7e5fce310c 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -165,6 +165,8 @@ export const APP_PATHS = { ELASTIC_SNAPSHOT: '/elastic-snapshot', MARKET_OVERVIEW: '/market-overview', + EARN_POOLS: '/earn/pools', + AGGREGATOR_CAMPAIGN: '/campaigns/aggregator', LIMIT_ORDER_CAMPAIGN: '/campaigns/limit-order', REFFERAL_CAMPAIGN: '/campaigns/referrals', diff --git a/src/pages/App.tsx b/src/pages/App.tsx index d694425119..7bdfabaca4 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -38,6 +38,8 @@ const Consent = lazy(() => import('./Oauth/Consent')) const ElasticSnapshot = lazy(() => import('./ElasticSnapshot')) const MarketOverview = lazy(() => import('./MarketOverview')) +const Earn = lazy(() => import('./Earn')) + // test page for swap only through elastic const ElasticSwap = lazy(() => import('./ElasticSwap')) const SwapV3 = lazy(() => import('./SwapV3')) @@ -309,6 +311,8 @@ export default function App() { } /> } /> + } /> + } /> } /> } /> diff --git a/src/pages/Earn/DropdownMenu.tsx b/src/pages/Earn/DropdownMenu.tsx new file mode 100644 index 0000000000..e87aa76200 --- /dev/null +++ b/src/pages/Earn/DropdownMenu.tsx @@ -0,0 +1,151 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { useMedia } from 'react-use' +import styled from 'styled-components' + +import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' +import { MEDIA_WIDTHS } from 'theme' + +const DropdownWrapper = styled.div` + position: relative; + width: fit-content; +` + +const DropdownTitleWrapper = styled.div` + background: ${({ theme }) => theme.background}; + border-radius: 30px; + padding: 6px 12px; + font-size: 14px; + cursor: pointer; + color: ${({ theme }) => theme.subText}; + display: flex; + align-items: center; + justify-content: center; +` + +const DropdownTitle = styled.div<{ width?: number }>` + width: ${({ width }) => (width ? `${width}px` : '')}; + min-width: ${({ width }) => (!width ? '100px' : '')}; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + text-transform: capitalize; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + min-width: unset; + `} +` + +const DropdownIcon = styled(DropdownSVG)<{ open: boolean }>` + transform: ${({ open }) => (open ? 'rotate(180deg)' : 'rotate(0deg)')}; + transition: transform 0.3s; +` + +const ItemIcon = styled.img` + width: 18px; + height: 18px; +` + +const DropdownContent = styled.div<{ alignLeft: boolean }>` + position: absolute; + top: 48px; + left: 0; + background: ${({ theme }) => theme.background}; + border-radius: 24px; + padding: 8px 12px; + font-size: 14px; + color: ${({ theme }) => theme.text}; + width: fit-content; + display: flex; + flex-direction: column; + align-items: ${({ alignLeft }) => (alignLeft ? 'flex-start' : 'center')}; + gap: 4px; + max-height: 218px; + overflow-y: auto; + z-index: 100; +` + +const DropdownContentItem = styled.div` + padding: 8px; + border-radius: 30px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + text-transform: capitalize; + + &:hover { + background: ${({ theme }) => theme.tableHeader}; + } +` + +export interface MenuOption { + label: string + value: string | number + icon?: string +} + +const DropdownMenu = ({ + options, + value, + width, + alignLeft = false, + onChange, +}: { + options: MenuOption[] + value: string | number + width?: number + alignLeft?: boolean + onChange: (value: string | number) => void +}) => { + const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + + const [open, setOpen] = useState(false) + const ref = useRef(null) + + const optionValue = useMemo(() => options.find(option => option.value === value), [options, value]) + + const handleOpenChange = () => setOpen(!open) + + const handleSelectItem = (newValue: string | number) => { + onChange(newValue) + setOpen(false) + } + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (!ref?.current?.contains(event.target as Node)) setOpen(false) + } + + document.addEventListener('mousedown', handleClickOutside) + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [ref]) + + return ( + + + + {optionValue?.icon && } + {(!upToExtraSmall || !optionValue?.icon) && optionValue?.label} + + + + {open && ( + + {options.map((option: MenuOption) => ( + handleSelectItem(option.value)}> + {option.icon && } + {option.label} + + ))} + + )} + + ) +} + +export default DropdownMenu diff --git a/src/pages/Earn/TableContent.tsx b/src/pages/Earn/TableContent.tsx new file mode 100644 index 0000000000..09f65da764 --- /dev/null +++ b/src/pages/Earn/TableContent.tsx @@ -0,0 +1,165 @@ +import { useMemo } from 'react' +import { Star } from 'react-feather' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { useGetDexListQuery } from 'services/ksSetting' +import { usePoolsExplorerQuery } from 'services/zapEarn' + +import { Image } from 'components/Image' +import { NETWORKS_INFO } from 'constants/networks' +import useTheme from 'hooks/useTheme' +import { Direction } from 'pages/MarketOverview/SortIcon' +import { MEDIA_WIDTHS } from 'theme' +import { formatDisplayNumber } from 'utils/numbers' + +import { FilterTag, SortBy } from '.' +import { + Apr, + CurrencyRoundedImage, + CurrencySecondImage, + FeeTier, + MobileTableBottomRow, + MobileTableRow, + TableBody, + TableRow, +} from './styles' +import useFilter from './useFilter' + +const TableContent = () => { + const theme = useTheme() + const { filters } = useFilter() + const dexList = useGetDexListQuery({ + chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, + }) + const { data: poolData } = usePoolsExplorerQuery(filters) + + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + + const tablePoolData = useMemo(() => { + const parsedPoolData = (poolData?.data?.pools || []).map(pool => ({ + ...pool, + dexLogo: dexList.data?.find(dex => dex.dexId === pool.exchange)?.logoURL || '', + dexName: dexList.data?.find(dex => dex.dexId === pool.exchange)?.name || '', + })) + + if (filters.tag && Object.keys(FilterTag).includes(filters.tag) && filters.sortBy) { + parsedPoolData.sort((a, b) => { + if (filters.sortBy === SortBy.APR) return filters.orderBy === Direction.DESC ? b.apr - a.apr : a.apr - b.apr + if (filters.sortBy === SortBy.EARN_FEE) + return filters.orderBy === Direction.DESC ? b.earnFee - a.earnFee : a.earnFee - b.earnFee + if (filters.sortBy === SortBy.TVL) + return filters.orderBy === Direction.DESC ? b.liquidity - a.liquidity : a.liquidity - b.liquidity + if (filters.sortBy === SortBy.VOLUME) + return filters.orderBy === Direction.DESC ? b.volume - a.volume : a.volume - b.volume + return 0 + }) + } + + return parsedPoolData + }, [poolData, filters, dexList]) + + const handleFavorite = (e: React.MouseEvent) => { + e.stopPropagation() + // toggleFavorite(item) + } + + if (upToMedium) + return ( + + {tablePoolData.map((pool, index) => ( + + + + + + + + + {/* {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} */}USDT/ARB + + + {pool.feeTier * 100}% + + + + + 0}>{pool.apr}% + + + + + + Earn Fees + {formatDisplayNumber(pool.earnFee, { style: 'currency', significantDigits: 6 })} + + + TVL + {formatDisplayNumber(pool.liquidity, { style: 'currency', significantDigits: 6 })} + + + Volume + {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} + + + + ))} + + ) + + return ( + + {tablePoolData.map(pool => ( + + + + {pool.dexName} + + + + + + + + {/* {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} */} + USDT/ARB + + {pool.feeTier * 100}% + + 0}>{pool.apr}% + + {formatDisplayNumber(pool.earnFee, { style: 'currency', significantDigits: 6 })} + + + {formatDisplayNumber(pool.liquidity, { style: 'currency', significantDigits: 6 })} + + + {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} + + + + + + ))} + + ) +} + +export default TableContent diff --git a/src/pages/Earn/index.tsx b/src/pages/Earn/index.tsx new file mode 100644 index 0000000000..f5680f5aea --- /dev/null +++ b/src/pages/Earn/index.tsx @@ -0,0 +1,235 @@ +import { t } from '@lingui/macro' +import { useEffect, useMemo, useState } from 'react' +import { Star } from 'react-feather' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { useGetDexListQuery } from 'services/ksSetting' +import { usePoolsExplorerQuery, useSupportedProtocolsQuery } from 'services/zapEarn' + +import { ReactComponent as IconHighAprPool } from 'assets/svg/ic_pool_high_apr.svg' +import { ReactComponent as IconHighlightedPool } from 'assets/svg/ic_pool_highlighted.svg' +import { ReactComponent as IconLowVolatility } from 'assets/svg/ic_pool_low_volatility.svg' +import { ReactComponent as IconSolidEarningPool } from 'assets/svg/ic_pool_solid_earning.svg' +import Pagination from 'components/Pagination' +import Search from 'components/Search' +import { NETWORKS_INFO } from 'constants/networks' +import useChainsConfig from 'hooks/useChainsConfig' +import useDebounce from 'hooks/useDebounce' +import useTheme from 'hooks/useTheme' +import SortIcon, { Direction } from 'pages/MarketOverview/SortIcon' +import { MEDIA_WIDTHS } from 'theme' + +import DropdownMenu, { MenuOption } from './DropdownMenu' +import TableContent from './TableContent' +import { ContentWrapper, PoolsExplorerWrapper, TableHeader, TableWrapper, Tag, TagContainer } from './styles' +import useFilter from './useFilter' + +export enum FilterTag { + HIGHLIGHTED_POOL = 'highlighted_pool', + HIGH_APR = 'high_apr', + SOLID_EARNING = 'solid_earning', + LOW_VOLATILITY = 'low_volatility', +} + +export enum SortBy { + APR = 'apr', + EARN_FEE = 'earn_fee', + TVL = 'tvl', + VOLUME = 'volume', +} + +const filterTags = [ + { + label: 'Highlighted Pools', + value: FilterTag.HIGHLIGHTED_POOL, + icon: , + }, + { label: 'High APR', value: FilterTag.HIGH_APR, icon: }, + { label: 'Solid Earning', value: FilterTag.SOLID_EARNING, icon: }, + { label: 'Low Volatility', value: FilterTag.LOW_VOLATILITY, icon: }, +] + +export const timings: MenuOption[] = [ + { label: '1h', value: '1h' }, + { label: '24h', value: '24h' }, + { label: '7d', value: '7d' }, +] + +const Earn = () => { + const theme = useTheme() + const { supportedChains } = useChainsConfig() + const { filters, updateFilters } = useFilter() + + const dexList = useGetDexListQuery({ + chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, + }) + const { data: supportedProtocolsData } = useSupportedProtocolsQuery() + const { data: poolData } = usePoolsExplorerQuery(filters) + + const supportedProtocols = useMemo(() => { + if (!supportedProtocolsData?.data?.chains) return [] + const parsedProtocols = + supportedProtocolsData.data.chains[filters.chainId]?.protocols?.map(item => ({ + label: dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name, + value: item.id, + })) || [] + return [{ label: 'All Protocols', value: '' }].concat(parsedProtocols) + }, [filters.chainId, supportedProtocolsData, dexList]) + + const chains = useMemo( + () => + supportedChains.map(chain => ({ + label: chain.name, + value: chain.chainId, + icon: chain.icon, + })), + [supportedChains], + ) + + const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + + const [search, setSearch] = useState('') + const deboundedSearch = useDebounce(search, 300) + + const onChainChange = (newChainId: string | number) => { + updateFilters('chainId', newChainId.toString()) + } + const onProtocolChange = (newProtocol: string | number) => { + updateFilters('protocol', newProtocol.toString()) + } + const onIntervalChange = (newInterval: string | number) => { + updateFilters('interval', newInterval.toString()) + } + const onSortChange = (sortBy: string) => { + if (!filters.sortBy || filters.sortBy !== sortBy) { + updateFilters('sortBy', sortBy) + updateFilters('orderBy', Direction.DESC) + return + } + if (filters.orderBy === Direction.DESC) { + updateFilters('orderBy', Direction.ASC) + return + } + updateFilters('sortBy', '') + updateFilters('orderBy', '') + } + + useEffect(() => { + if (filters.q !== deboundedSearch) { + updateFilters('q', deboundedSearch || '') + } + }, [deboundedSearch, filters.q, updateFilters]) + + return ( + +

+ + {t`Earning with Smart Liquidity Providing`} + + + {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + +
+ + updateFilters('tag', '')}> + {t`All pools`} + + updateFilters('tag', 'favorite')}> + + + {filterTags.map(item => ( + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + ))} + + + + + + + + setSearch(val)} + style={{ height: '36px' }} + /> + + + + {!upToMedium && ( + + Protocol + Pair + onSortChange(SortBy.APR)} + > + APR + + + onSortChange(SortBy.EARN_FEE)} + > + Earn Fees + + + onSortChange(SortBy.TVL)} + > + TVL + + + onSortChange(SortBy.VOLUME)} + > + Volume + + +
+ + )} + + + {(!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) && ( + { + updateFilters('page', newPage.toString()) + }} + totalCount={poolData?.data?.pagination?.totalItems || 0} + currentPage={(filters.page || 0) + 1} + pageSize={filters.limit || 10} + /> + )} + + + ) +} + +export default Earn diff --git a/src/pages/Earn/styles.tsx b/src/pages/Earn/styles.tsx new file mode 100644 index 0000000000..db2fa0d35a --- /dev/null +++ b/src/pages/Earn/styles.tsx @@ -0,0 +1,133 @@ +import { rgba } from 'polished' +import styled from 'styled-components' + +import { Image } from 'components/Image' + +export const PoolsExplorerWrapper = styled.div` + padding: 32px 24px 50px; + width: 100%; + max-width: 1500px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 24px 16px 100px; + `} + + display: flex; + flex-direction: column; + gap: 16px; +` + +export const TagContainer = styled.div` + display: flex; + gap: 1rem; + width: 100%; + overflow-x: auto; + + ${({ theme }) => theme.mediaWidth.upToSmall` + gap: 0.75rem; + `} +` + +export const Tag = styled.div<{ active: boolean }>` + background: ${({ theme, active }) => (active ? rgba(theme.primary, 0.2) : theme.background)}; + border: 1px solid ${({ theme, active }) => (active ? theme.primary : 'transparent')}; + border-radius: 12px; + padding: 4px 16px; + font-size: 14px; + cursor: pointer; + color: ${({ theme, active }) => (active ? theme.text : theme.subText)}; + font-weight: ${({ active }) => (active ? '500' : '400')}; + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + line-height: 28px; +` + +export const TableWrapper = styled.div` + background: ${({ theme }) => rgba(theme.background, 0.8)}; + border-radius: 16px; + overflow: hidden; + + ${({ theme }) => theme.mediaWidth.upToMedium` + margin: 0 -16px; + border-radius: 0; + `} +` + +export const ContentWrapper = styled.div`` + +export const TableHeader = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 0.5fr 1fr 1fr 1fr 80px; + align-items: center; + color: ${({ theme }) => theme.subText}; + border-bottom: 1px solid ${({ theme }) => theme.tableHeader}; + padding-bottom: 24px; + margin: 24px; + margin-bottom: 0; + + ${({ theme }) => theme.mediaWidth.upToLarge` + grid-template-columns: 1fr 1.2fr 0.5fr 1fr 1fr 1fr 80px; + `} +` + +export const TableBody = styled.div` + max-height: 720px; + overflow-y: auto; +` + +export const TableRow = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 0.5fr 1fr 1fr 1fr 80px; + padding: 24px; + cursor: pointer; + + :hover { + background: ${({ theme }) => rgba(theme.primary, 0.2)}; + } + + ${({ theme }) => theme.mediaWidth.upToLarge` + grid-template-columns: 1fr 1.2fr 0.5fr 1fr 1fr 1fr 80px; + `} +` + +export const FeeTier = styled.div` + border-radius: 30px; + padding: 4px 8px; + font-size: 12px; + background: ${({ theme }) => rgba(theme.white, 0.04)}; + color: ${({ theme }) => theme.subText}; + width: fit-content; +` + +export const CurrencyRoundedImage = styled(Image)` + border-radius: 50%; +` + +export const CurrencySecondImage = styled(CurrencyRoundedImage)` + position: relative; + left: -6px; +` + +export const Apr = styled.div<{ positive: boolean }>` + display: flex; + justify-content: flex-end; + color: ${({ positive, theme }) => (positive ? theme.primary : theme.red)}; +` + +export const MobileTableRow = styled.div` + padding: 28px 24px 0; + cursor: pointer; + + :hover { + background: ${({ theme }) => rgba(theme.primary, 0.2)}; + } +` +export const MobileTableBottomRow = styled.div<{ withoutBorder: boolean }>` + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 0 24px; + border-bottom: ${({ withoutBorder, theme }) => (withoutBorder ? 'none' : `1px solid ${theme.tableHeader}`)}; +` diff --git a/src/pages/Earn/useFilter.ts b/src/pages/Earn/useFilter.ts new file mode 100644 index 0000000000..b603249b01 --- /dev/null +++ b/src/pages/Earn/useFilter.ts @@ -0,0 +1,43 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { useCallback, useMemo } from 'react' +import { useSearchParams } from 'react-router-dom' +import { QueryParams } from 'services/zapEarn' + +import { useActiveWeb3React } from 'hooks' + +import { timings } from '.' + +export default function useFilter() { + const [searchParams, setSearchParams] = useSearchParams() + const { account } = useActiveWeb3React() + + const filters: QueryParams = useMemo(() => { + return { + chainId: +(searchParams.get('chainId') || ChainId.MAINNET), + page: +(searchParams.get('page') || 0), + limit: 10, + interval: searchParams.get('interval') || (timings[1].value as string), + protocol: searchParams.get('protocol') || '', + userAddress: account, + tag: searchParams.get('tag') || '', + sortBy: searchParams.get('sortBy') || '', + orderBy: searchParams.get('orderBy') || '', + q: searchParams.get('q')?.trim() || '', + } + }, [searchParams, account]) + + const updateFilters = useCallback( + (key: keyof QueryParams, value: string) => { + if (!value) searchParams.delete(key) + else searchParams.set(key, value) + if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') searchParams.delete('page') + setSearchParams(searchParams) + }, + [setSearchParams, searchParams], + ) + + return { + filters, + updateFilters, + } +} diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index 9a45b370a0..57cc9f0460 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -211,6 +211,7 @@ export const { useLazyGetKyberswapConfigurationQuery, useGetKyberswapGlobalConfigurationQuery, useLazyGetTokenListQuery, + useGetDexListQuery, useGetTokenListQuery, useImportTokenMutation, useLazyGetTopTokensQuery, diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts new file mode 100644 index 0000000000..7d3565f0ce --- /dev/null +++ b/src/services/zapEarn.ts @@ -0,0 +1,83 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +interface SupportedChainsResponse { + code: number + message: string + data: { + chains: { + [chainId: string]: { + chainId: number + protocols: Array<{ id: string; name: string }> + } + } + } + requestId: string +} + +interface PoolsExplorerResponse { + code: number + message: string + data: { + pools: Array<{ + address: string + earnFee: number + exchange: string + type: string + feeTier: number + volume: number + apr: number + liquidity: number + tokens: Array<{ + address: string + logoURI: string + symbol: string + }> + }> + pagination: { + totalItems: number + } + } + requestId: string +} + +export interface QueryParams { + chainId: ChainId + page?: number + limit?: number + interval: string + protocol: string + userAddress?: string + tag?: string + sortBy?: string + orderBy?: string + q?: string +} + +const zapEarnServiceApi = createApi({ + reducerPath: 'zapEarnServiceApi', + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_ZAP_EARN_URL, + }), + keepUnusedDataFor: 1, + endpoints: builder => ({ + supportedProtocols: builder.query({ + query: () => ({ + url: `/v1/protocol`, + }), + }), + poolsExplorer: builder.query({ + query: params => ({ + url: `/v1/explorer/pools`, + params: { + ...params, + orderBy: params.orderBy?.toUpperCase() || '', + }, + }), + }), + }), +}) + +export const { useSupportedProtocolsQuery, usePoolsExplorerQuery } = zapEarnServiceApi + +export default zapEarnServiceApi diff --git a/src/state/index.ts b/src/state/index.ts index d7a9ab8863..3b54ed47ba 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -20,6 +20,7 @@ import referralApi from 'services/referral' import routeApi from 'services/route' import socialApi from 'services/social' import tokenApi from 'services/token' +import zapEarnServiceApi from 'services/zapEarn' import { ENV_LEVEL } from 'constants/env' import { ENV_TYPE } from 'constants/type' @@ -108,6 +109,7 @@ const store = configureStore({ topTokens, [routeApi.reducerPath]: routeApi.reducer, [tokenApi.reducerPath]: tokenApi.reducer, + [zapEarnServiceApi.reducerPath]: zapEarnServiceApi.reducer, [referralApi.reducerPath]: referralApi.reducer, [campaignApi.reducerPath]: campaignApi.reducer, [commonServiceApi.reducerPath]: commonServiceApi.reducer, @@ -133,6 +135,7 @@ const store = configureStore({ .concat(routeApi.middleware) .concat(socialApi.middleware) .concat(tokenApi.middleware) + .concat(zapEarnServiceApi.middleware) .concat(referralApi.middleware) .concat(campaignApi.middleware) .concat(commonServiceApi.middleware) From 94f668e43629d0596fcfdd28ad28de8cb6464d03 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 02:57:53 +0700 Subject: [PATCH 04/83] integrate liquidity widget --- package.json | 1 + src/pages/Earn/TableContent.tsx | 8 +- src/pages/Earn/index.tsx | 239 ++++++++------ src/pages/Earn/styles.tsx | 6 + src/services/zapEarn.ts | 32 +- yarn.lock | 547 +++++++++++++++++++++++++++++++- 6 files changed, 700 insertions(+), 133 deletions(-) diff --git a/package.json b/package.json index b8d1359473..4f9f6a51cb 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", + "@kyberswap/liquidity-widgets": "^0.0.15", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earn/TableContent.tsx b/src/pages/Earn/TableContent.tsx index 09f65da764..d4e3c0fd23 100644 --- a/src/pages/Earn/TableContent.tsx +++ b/src/pages/Earn/TableContent.tsx @@ -3,7 +3,7 @@ import { Star } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { useGetDexListQuery } from 'services/ksSetting' -import { usePoolsExplorerQuery } from 'services/zapEarn' +import { PoolData, usePoolsExplorerQuery } from 'services/zapEarn' import { Image } from 'components/Image' import { NETWORKS_INFO } from 'constants/networks' @@ -25,7 +25,7 @@ import { } from './styles' import useFilter from './useFilter' -const TableContent = () => { +const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: PoolData) => void }) => { const theme = useTheme() const { filters } = useFilter() const dexList = useGetDexListQuery({ @@ -67,7 +67,7 @@ const TableContent = () => { return ( {tablePoolData.map((pool, index) => ( - + onOpenZapInWidget(pool)}> @@ -118,7 +118,7 @@ const TableContent = () => { return ( {tablePoolData.map(pool => ( - + onOpenZapInWidget(pool)}> {pool.dexName} diff --git a/src/pages/Earn/index.tsx b/src/pages/Earn/index.tsx index f5680f5aea..d2d9afeab7 100644 --- a/src/pages/Earn/index.tsx +++ b/src/pages/Earn/index.tsx @@ -1,10 +1,12 @@ +import { ChainId, LiquidityWidget, PoolType } from '@kyberswap/liquidity-widgets' +import '@kyberswap/liquidity-widgets/dist/style.css' import { t } from '@lingui/macro' import { useEffect, useMemo, useState } from 'react' import { Star } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { useGetDexListQuery } from 'services/ksSetting' -import { usePoolsExplorerQuery, useSupportedProtocolsQuery } from 'services/zapEarn' +import { PoolData, usePoolsExplorerQuery, useSupportedProtocolsQuery } from 'services/zapEarn' import { ReactComponent as IconHighAprPool } from 'assets/svg/ic_pool_high_apr.svg' import { ReactComponent as IconHighlightedPool } from 'assets/svg/ic_pool_highlighted.svg' @@ -13,6 +15,7 @@ import { ReactComponent as IconSolidEarningPool } from 'assets/svg/ic_pool_solid import Pagination from 'components/Pagination' import Search from 'components/Search' import { NETWORKS_INFO } from 'constants/networks' +import { useWeb3React } from 'hooks' import useChainsConfig from 'hooks/useChainsConfig' import useDebounce from 'hooks/useDebounce' import useTheme from 'hooks/useTheme' @@ -21,9 +24,26 @@ import { MEDIA_WIDTHS } from 'theme' import DropdownMenu, { MenuOption } from './DropdownMenu' import TableContent from './TableContent' -import { ContentWrapper, PoolsExplorerWrapper, TableHeader, TableWrapper, Tag, TagContainer } from './styles' +import { + ContentWrapper, + LiquidityWidgetWrapper, + PoolsExplorerWrapper, + TableHeader, + TableWrapper, + Tag, + TagContainer, +} from './styles' import useFilter from './useFilter' +interface ZapInParams { + provider: any + poolAddress: string + chainId: ChainId + onDismiss: () => void + source: string + poolType: PoolType +} + export enum FilterTag { HIGHLIGHTED_POOL = 'highlighted_pool', HIGH_APR = 'high_apr', @@ -57,6 +77,7 @@ export const timings: MenuOption[] = [ const Earn = () => { const theme = useTheme() + const { library } = useWeb3React() const { supportedChains } = useChainsConfig() const { filters, updateFilters } = useFilter() @@ -90,6 +111,7 @@ const Earn = () => { const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const [search, setSearch] = useState('') + const [zapinParams, setZapinParams] = useState(null) const deboundedSearch = useDebounce(search, 300) const onChainChange = (newChainId: string | number) => { @@ -115,6 +137,19 @@ const Earn = () => { updateFilters('orderBy', '') } + const handleCloseZapInWidget = () => setZapinParams(null) + const handleOpenZapInWidget = (pool: PoolData) => { + if (!Object.keys(PoolType).includes(`DEX_${pool.exchange.toUpperCase()}`)) return + setZapinParams({ + provider: library, + poolAddress: pool.address, + chainId: filters.chainId as unknown as ChainId, + onDismiss: handleCloseZapInWidget, + source: 'kyberswap-demo-zap', + poolType: PoolType[`DEX_${pool.exchange.toUpperCase()}` as keyof typeof PoolType], + }) + } + useEffect(() => { if (filters.q !== deboundedSearch) { updateFilters('q', deboundedSearch || '') @@ -131,103 +166,113 @@ const Earn = () => { {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`}
- - updateFilters('tag', '')}> - {t`All pools`} - - updateFilters('tag', 'favorite')}> - - - {filterTags.map(item => ( - updateFilters('tag', item.value)} - > - {!upToExtraSmall && item.icon} - {item.label} - - ))} - - - - - - - - setSearch(val)} - style={{ height: '36px' }} - /> - - - - {!upToMedium && ( - - Protocol - Pair - onSortChange(SortBy.APR)} - > - APR - - - onSortChange(SortBy.EARN_FEE)} - > - Earn Fees - - - onSortChange(SortBy.TVL)} - > - TVL - - - + + + ) : ( + <> + + updateFilters('tag', '')}> + {t`All pools`} + + updateFilters('tag', 'favorite')}> + + + {filterTags.map(item => ( + onSortChange(SortBy.VOLUME)} + onClick={() => updateFilters('tag', item.value)} > - Volume - - -
- - )} - - - {(!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) && ( - { - updateFilters('page', newPage.toString()) - }} - totalCount={poolData?.data?.pagination?.totalItems || 0} - currentPage={(filters.page || 0) + 1} - pageSize={filters.limit || 10} - /> - )} - + {!upToExtraSmall && item.icon} + {item.label} + + ))} + + + + + + + + setSearch(val)} + style={{ height: '36px' }} + /> + + + + {!upToMedium && ( + + Protocol + Pair + onSortChange(SortBy.APR)} + > + APR + + + onSortChange(SortBy.EARN_FEE)} + > + Earn Fees + + + onSortChange(SortBy.TVL)} + > + TVL + + + onSortChange(SortBy.VOLUME)} + > + Volume + + +
+ + )} + + + {(!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) && ( + { + updateFilters('page', newPage.toString()) + }} + totalCount={poolData?.data?.pagination?.totalItems || 0} + currentPage={(filters.page || 0) + 1} + pageSize={filters.limit || 10} + /> + )} + + + )} ) } diff --git a/src/pages/Earn/styles.tsx b/src/pages/Earn/styles.tsx index db2fa0d35a..3b57aac69e 100644 --- a/src/pages/Earn/styles.tsx +++ b/src/pages/Earn/styles.tsx @@ -17,6 +17,12 @@ export const PoolsExplorerWrapper = styled.div` gap: 16px; ` +export const LiquidityWidgetWrapper = styled.div` + width: 100%; + display: flex; + justify-content: center; +` + export const TagContainer = styled.div` display: flex; gap: 1rem; diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts index 7d3565f0ce..b295c2e15e 100644 --- a/src/services/zapEarn.ts +++ b/src/services/zapEarn.ts @@ -15,25 +15,27 @@ interface SupportedChainsResponse { requestId: string } +export interface PoolData { + address: string + earnFee: number + exchange: string + type: string + feeTier: number + volume: number + apr: number + liquidity: number + tokens: Array<{ + address: string + logoURI: string + symbol: string + }> +} + interface PoolsExplorerResponse { code: number message: string data: { - pools: Array<{ - address: string - earnFee: number - exchange: string - type: string - feeTier: number - volume: number - apr: number - liquidity: number - tokens: Array<{ - address: string - logoURI: string - symbol: string - }> - }> + pools: Array pagination: { totalItems: number } diff --git a/yarn.lock b/yarn.lock index a3b9c7d470..e88749d5e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,11 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -2585,7 +2590,7 @@ ethereum-cryptography "^2.0.0" micro-ftch "^0.3.1" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.12", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -2776,7 +2781,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.5": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.0.5", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -3736,7 +3741,7 @@ tiny-warning "^1.0.3" toformat "^2.0.0" -"@kyberswap/ks-sdk-core@1.1.6", "@kyberswap/ks-sdk-core@^1.0.5": +"@kyberswap/ks-sdk-core@1.1.6", "@kyberswap/ks-sdk-core@^1.0.5", "@kyberswap/ks-sdk-core@^1.1.5": version "1.1.6" resolved "https://registry.yarnpkg.com/@kyberswap/ks-sdk-core/-/ks-sdk-core-1.1.6.tgz#14b03c00408973c66df7b896b94fa4430d7d460b" integrity sha512-VuG2xvNPY+/Ls+5Lrr41MuEFnJ/fdvpmXioflefICNU/n8UaNwB2QuD0+ozFFOflnEP3hIf712JDGmgRt+T1SA== @@ -3760,6 +3765,34 @@ tiny-invariant "^1.1.0" tiny-warning "^1.0.3" +"@kyberswap/liquidity-widgets@^0.0.15": + version "0.0.15" + resolved "https://registry.yarnpkg.com/@kyberswap/liquidity-widgets/-/liquidity-widgets-0.0.15.tgz#ac08cf27d2f8b0761f660a1131f76cabb3ad243f" + integrity sha512-qNjf41Jv1mlSjySyIH3jjP7Ft9b+KAT4tnquK1YhYdAZl2PJzvB6SoWVMwayT+18QRNByN8Iq+rdGc4pAFQkPw== + dependencies: + "@ethersproject/providers" "^5.7.2" + "@kyberswap/ks-sdk-core" "^1.1.5" + "@pancakeswap/sdk" "^5.8.2" + "@pancakeswap/swap-sdk-core" "^1.2.0" + "@pancakeswap/v3-sdk" "^3.8.3" + "@popperjs/core" "^2.11.8" + "@radix-ui/react-accordion" "^1.2.1" + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-scroll-area" "^1.1.0" + "@radix-ui/react-slot" "^1.1.0" + "@uniswap/sdk-core" "5.8.1" + "@uniswap/v3-sdk" "3.18.1" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + d3 "^7.9.0" + ethers "^5.7.0" + jsbi "^3.2.5" + lodash.partition "^4.6.0" + lucide-react "^0.453.0" + numeral "^2.0.6" + polished "^4.3.1" + react-popper "^2.3.0" + "@kyberswap/oauth2@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@kyberswap/oauth2/-/oauth2-1.0.2.tgz#4e0fdfa9722ba2f185a104293b85b6ca58be775b" @@ -4193,6 +4226,13 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@1.6.0", "@noble/curves@^1.6.0", "@noble/curves@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + "@noble/curves@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" @@ -4200,13 +4240,6 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" - integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== - dependencies: - "@noble/hashes" "1.5.0" - "@noble/ed25519@^1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724" @@ -4222,7 +4255,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@1.5.0", "@noble/hashes@^1.5.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -4263,11 +4296,85 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@3.4.1-solc-0.7-2": + version "3.4.1-solc-0.7-2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" + integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q== + "@openzeppelin/contracts@3.4.2-solc-0.7": version "3.4.2-solc-0.7" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== +"@pancakeswap/chains@0.4.6", "@pancakeswap/chains@^0.4.6": + version "0.4.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/chains/-/chains-0.4.6.tgz#47b98b147d237e147ff59a265ed3c95d1b8d0537" + integrity sha512-2uflmzHY+rno4+wTQL0ae4c4tbA/r5aGxWg9dNI/A4mS2jx/0lVbELwpSYughK2zcb6MbDbXkAZuLaNKhZe/Yg== + +"@pancakeswap/sdk@5.8.9", "@pancakeswap/sdk@^5.8.2": + version "5.8.9" + resolved "https://registry.yarnpkg.com/@pancakeswap/sdk/-/sdk-5.8.9.tgz#06bc66b4a3c40c087dd367d0a00cfcfd4cb6d4f2" + integrity sha512-Aj5N4AYsuA9mIEcpt+vchmNSfSI2r5j7QevX2NWUqk5uZUvTZLGOAcJcxIdKC7q8nUtWDT4cmSzZAszl6rfvSg== + dependencies: + "@pancakeswap/chains" "^0.4.6" + "@pancakeswap/swap-sdk-core" "1.3.0" + "@pancakeswap/swap-sdk-evm" "1.0.6" + "@pancakeswap/v2-sdk" "1.0.6" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + viem "^2.21.22" + +"@pancakeswap/swap-sdk-core@1.3.0", "@pancakeswap/swap-sdk-core@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pancakeswap/swap-sdk-core/-/swap-sdk-core-1.3.0.tgz#51a4d86278632085ff49318e1f8497159a6c8eea" + integrity sha512-nkeDs3GyNfvRGsTbTAO30yl6ccOTr5WQERsKAaxKTe1fbGvpDRFLo3nlR1ZddRCj+RYZSS+B4Sll4A39k7nFDQ== + dependencies: + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + +"@pancakeswap/swap-sdk-evm@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/swap-sdk-evm/-/swap-sdk-evm-1.0.6.tgz#532a23ccaf31abeb46ac67df6af2c00d22fc112f" + integrity sha512-q4kTonZ5DOt/0FdMB19faZC4P9v5WidTnS7vlPalMguUXyAj79U1Hh1N37wqa6LcWFz2lTBnOrkaDst72h9MqA== + dependencies: + "@pancakeswap/chains" "0.4.6" + "@pancakeswap/swap-sdk-core" "1.3.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + viem "^2.21.22" + +"@pancakeswap/v2-sdk@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@pancakeswap/v2-sdk/-/v2-sdk-1.0.6.tgz#e9dbaeb853cf01852d42a21d64160db00ce7e16e" + integrity sha512-4MmazJLSiN3JgRNtJ9eHB93GnQDmQudm0CDkBfnp3cOjqP1QRIFFdk8mkwoD4PxfrHmozWlzL/0eHT5l553kQw== + dependencies: + "@pancakeswap/chains" "0.4.6" + "@pancakeswap/swap-sdk-core" "1.3.0" + "@pancakeswap/swap-sdk-evm" "1.0.6" + tiny-invariant "^1.3.0" + viem "^2.21.22" + +"@pancakeswap/v3-sdk@^3.8.3": + version "3.8.12" + resolved "https://registry.yarnpkg.com/@pancakeswap/v3-sdk/-/v3-sdk-3.8.12.tgz#c65ec4e97411f04bb7c5745c730e544af603da68" + integrity sha512-mweDAjb7fzpVFBbMEOBqUCW2jVqxIGnazMIn4WH2jl2gQImf2Jjgwy6JJYV6QcfDkWNV5zNhQ6y62LhQbQh/fQ== + dependencies: + "@pancakeswap/chains" "0.4.6" + "@pancakeswap/sdk" "5.8.9" + "@pancakeswap/swap-sdk-core" "1.3.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + tiny-invariant "^1.3.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + viem "^2.21.22" + "@parcel/watcher-android-arm64@2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84" @@ -4385,6 +4492,11 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@project-serum/anchor@^0.11.1": version "0.11.1" resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.11.1.tgz#155bff2c70652eafdcfd5559c81a83bb19cec9ff" @@ -4505,6 +4617,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/number@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46" + integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ== + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -4512,6 +4629,26 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/primitive@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" + integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== + +"@radix-ui/react-accordion@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.2.1.tgz#5c942c42c24267376b26204ec6847b17d15659b3" + integrity sha512-bg/l7l5QzUjgsh8kjwDFommzAshnUsuVMV5NM56QVCm+7ZckYdd9P/ExR8xG/Oup0OajVxNLaHJ1tb8mXk+nzQ== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collapsible" "1.1.1" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -4520,6 +4657,20 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-collapsible@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.1.tgz#1382cc9ec48f8b473c14f3779d317f0cdf6da5e9" + integrity sha512-1///SnrfQHJEofLokyczERxQbWfCGQlQ2XsCZMucVs6it+lq9iw4vXy+uDn1edlb58cOZOWSldnfPAYcT4O/Yg== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-presence" "1.1.1" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-collection@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" @@ -4531,6 +4682,16 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-collection@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" + integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-compose-refs@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" @@ -4538,6 +4699,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-compose-refs@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" + integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== + "@radix-ui/react-context@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" @@ -4545,6 +4711,16 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-context@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" + integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== + +"@radix-ui/react-context@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a" + integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== + "@radix-ui/react-direction@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" @@ -4552,6 +4728,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-direction@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" + integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== + "@radix-ui/react-dismissable-layer@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz#883a48f5f938fa679427aa17fcba70c5494c6978" @@ -4581,6 +4762,11 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-icons@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.1.tgz#462c85fd726a77854cd5956e29eb19a575aa7ce5" + integrity sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ== + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -4589,6 +4775,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-id@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed" + integrity sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-popper@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.2.tgz#4c0b96fcd188dc1f334e02dba2d538973ad842e9" @@ -4614,6 +4807,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-presence@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" + integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-primitive@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" @@ -4622,6 +4823,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-primitive@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" + integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== + dependencies: + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-roving-focus@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" @@ -4638,6 +4846,21 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-scroll-area@^1.1.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz#610c53e07d017e24b62bd73a0a6eb23fa7331b3b" + integrity sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw== + dependencies: + "@radix-ui/number" "1.1.0" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-presence" "1.1.1" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-select@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181" @@ -4682,6 +4905,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84" + integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-toggle-group@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz#f5b5c8c477831b013bec3580c55e20a68179d6ec" @@ -4727,6 +4957,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-callback-ref@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1" + integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw== + "@radix-ui/react-use-controllable-state@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" @@ -4735,6 +4970,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-controllable-state@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0" + integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-escape-keydown@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" @@ -4750,6 +4992,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-layout-effect@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" + integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== + "@radix-ui/react-use-previous@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz#b595c087b07317a4f143696c6a01de43b0d0ec66" @@ -4879,6 +5126,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== +"@scure/base@~1.1.7", "@scure/base@~1.1.8": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + "@scure/bip32@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" @@ -4897,6 +5149,15 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip32@1.5.0", "@scure/bip32@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== + dependencies: + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" + "@scure/bip39@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" @@ -4913,6 +5174,14 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip39@1.4.0", "@scure/bip39@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + "@sentry-internal/browser-utils@8.34.0": version "8.34.0" resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz#36a50d503ad4ad51fce22e80670f8fd6fd195a27" @@ -7562,7 +7831,49 @@ resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== -"@uniswap/v2-core@1.0.1": +"@uniswap/sdk-core@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-5.8.1.tgz#48fd7a1246c2fd972cbdd78d9ca35b2414786ef6" + integrity sha512-Woi4IOGPEIz9Jsmc4sUIRoX7Iwz90p/u3NjdUYxwucb3nf3fnsZTvrDzbYdvi11nsTeQVIJnIW1C4T0Ae+bLwA== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/strings" "5.7.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.4" + tiny-invariant "^1.1.0" + toformat "^2.0.0" + +"@uniswap/sdk-core@^5.8.1": + version "5.9.0" + resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-5.9.0.tgz#8f1edf4d0e94b314f4394fa5abe0bf5fc9c5a79a" + integrity sha512-OME7WR6+5QwQs45A2079r+/FS0zU944+JCQwUX9GyIriCxqw2pGu4F9IEqmlwD+zSIMml0+MJnJJ47pFgSyWDw== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/strings" "5.7.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.4" + tiny-invariant "^1.1.0" + toformat "^2.0.0" + +"@uniswap/swap-router-contracts@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.1.tgz#0ebbb93eb578625618ed9489872de381f9c66fb4" + integrity sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + "@uniswap/v3-periphery" "^1.4.4" + dotenv "^14.2.0" + hardhat-watcher "^2.1.1" + +"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== @@ -7572,6 +7883,22 @@ resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== +"@uniswap/v3-core@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" + integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== + +"@uniswap/v3-periphery@^1.0.1", "@uniswap/v3-periphery@^1.1.1", "@uniswap/v3-periphery@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7" + integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + base64-sol "1.0.1" + "@uniswap/v3-periphery@^1.3.0": version "1.4.3" resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.3.tgz#a6da4632dbd46b139cc13a410e4ec09ad22bd19f" @@ -7583,6 +7910,29 @@ "@uniswap/v3-core" "1.0.0" base64-sol "1.0.1" +"@uniswap/v3-sdk@3.18.1": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.18.1.tgz#cb714b252336ba662a3298c0525f6668101b0fef" + integrity sha512-TGrKLToSWwfx6VV2d7fh4kwQMlgspXTLE49ep5zfYODVVqV6WhrRdbteHb3e0bjdjxGSj0gzoLmhsjmoJTE1/g== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^5.8.1" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/v3-periphery" "^1.1.1" + "@uniswap/v3-staker" "1.0.0" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + +"@uniswap/v3-staker@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-staker/-/v3-staker-1.0.0.tgz#9a6915ec980852479dfc903f50baf822ff8fa66e" + integrity sha512-JV0Qc46Px5alvg6YWd+UIaGH9lDuYG/Js7ngxPit1SPaIP30AlVer1UYB7BRYeUVVxE+byUyIeN5jeQ7LLDjIw== + dependencies: + "@openzeppelin/contracts" "3.4.1-solc-0.7-2" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v3-periphery" "^1.0.1" + "@use-gesture/core@10.2.27": version "10.2.27" resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.27.tgz#0f24b17c036cd828ba07e3451ff45e2df959c6f5" @@ -8079,6 +8429,11 @@ abitype@1.0.5: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== +abitype@1.0.6, abitype@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" + integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -9394,6 +9749,13 @@ citty@^0.1.5, citty@^0.1.6: dependencies: consola "^3.2.3" +class-variance-authority@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" + integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== + dependencies: + clsx "2.0.0" + classlist-polyfill@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e" @@ -9520,11 +9882,21 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + clsx@^1.1.1, clsx@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -10271,6 +10643,42 @@ d3@^7.6.1: d3-transition "3" d3-zoom "3" +d3@^7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -10761,6 +11169,11 @@ dotenv@10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^14.2.0: + version "14.3.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" + integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ== + dotenv@^16.0.0, dotenv@^16.3.1: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -11537,7 +11950,7 @@ ethers@6.8.1, ethers@^6.0.0: tslib "2.4.0" ws "8.5.0" -ethers@^5.4.6, ethers@^5.6.9, ethers@^5.7.2: +ethers@^5.4.6, ethers@^5.6.9, ethers@^5.7.0, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -12743,6 +13156,13 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +hardhat-watcher@^2.1.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.5.0.tgz#3ee76c3cb5b99f2875b78d176207745aa484ed4a" + integrity sha512-Su2qcSMIo2YO2PrmJ0/tdkf+6pSt8zf9+4URR5edMVti6+ShI8T3xhPrwugdyTOFuyj8lKHrcTZNKUFYowYiyA== + dependencies: + chokidar "^3.5.3" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -13659,6 +14079,11 @@ isows@1.0.4: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -14374,6 +14799,11 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== +lodash.partition@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.partition/-/lodash.partition-4.6.0.tgz#a38e46b73469e0420b0da1212e66d414be364ba4" + integrity sha512-35L3dSF3Q6V1w5j6V3NhNlQjzsRDC/pYKCTdYTmwqSib+Q8ponkAmt/PwEOq3EmI38DSCl+SkIVwLd+uSlVdrg== + lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -14497,6 +14927,11 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== +lucide-react@^0.453.0: + version "0.453.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.453.0.tgz#d37909a45a29d89680383a202ee861224b05ba6a" + integrity sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ== + lz-string@^1.4.4, lz-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" @@ -15722,6 +16157,19 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== +ox@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.1.2.tgz#0f791be2ccabeaf4928e6d423498fe1c8094e560" + integrity sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" @@ -16190,6 +16638,13 @@ polished@^4.2.2: dependencies: "@babel/runtime" "^7.17.8" +polished@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/polished/-/polished-4.3.1.tgz#5a00ae32715609f83d89f6f31d0f0261c6170548" + integrity sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA== + dependencies: + "@babel/runtime" "^7.17.8" + pony-cause@^2.1.10: version "2.1.11" resolved "https://registry.yarnpkg.com/pony-cause/-/pony-cause-2.1.11.tgz#d69a20aaccdb3bdb8f74dd59e5c68d8e6772e4bd" @@ -16913,7 +17368,7 @@ react-native-webview@^11.26.0: escape-string-regexp "2.0.0" invariant "2.2.4" -react-popper@^2.2.3: +react-popper@^2.2.3, react-popper@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== @@ -18228,7 +18683,16 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18301,7 +18765,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18315,6 +18779,13 @@ strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -18677,6 +19148,11 @@ tiny-invariant@^1.1.0, tiny-invariant@^1.3.1: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== +tiny-invariant@^1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -19530,6 +20006,21 @@ viem@^2.1.1, viem@^2.20.0: webauthn-p256 "0.0.5" ws "8.17.1" +viem@^2.21.22: + version "2.21.45" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.45.tgz#7a445428d4909cc334f231ee916ede1b69190603" + integrity sha512-I+On/IiaObQdhDKWU5Rurh6nf3G7reVkAODG5ECIfjsrGQ3EPJnxirUPT4FNV6bWER5iphoG62/TidwuTSOA1A== + dependencies: + "@noble/curves" "1.6.0" + "@noble/hashes" "1.5.0" + "@scure/bip32" "1.5.0" + "@scure/bip39" "1.4.0" + abitype "1.0.6" + isows "1.0.6" + ox "0.1.2" + webauthn-p256 "0.0.10" + ws "8.18.0" + vite-plugin-checker@^0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.5.6.tgz#233978091dfadef0873f0a8aacfe7fc431212b95" @@ -19697,6 +20188,14 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webauthn-p256@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd" + integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA== + dependencies: + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + webauthn-p256@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" @@ -19917,7 +20416,7 @@ workbox-strategies@6.5.4: dependencies: workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -19935,6 +20434,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -19991,6 +20499,11 @@ ws@8.17.1, ws@~8.17.1: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" From 0054a9e670abcd9dc7fbf762e01511060365910c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 03:14:10 +0700 Subject: [PATCH 05/83] add no data notify --- src/pages/Earns/PoolExplorer/TableContent.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 6e235a8e3e..3584afd52a 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -63,6 +63,13 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo // toggleFavorite(item) } + if (!tablePoolData?.length) + return ( + + No data found + + ) + if (upToMedium) return ( From 3fb29d8b6ac9fc473131c498b7e05c9b8ac536f5 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 09:45:13 +0700 Subject: [PATCH 06/83] add missing 'chainId' from interfae --- src/services/zapEarn.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts index e805e3fab6..d06f900235 100644 --- a/src/services/zapEarn.ts +++ b/src/services/zapEarn.ts @@ -24,6 +24,7 @@ export interface EarnPool { volume: number apr: number liquidity: number + chainId?: number tokens: Array<{ address: string logoURI: string From d5b9bcc19e09ca497fe57c06f21efda692b2629a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 11:48:15 +0700 Subject: [PATCH 07/83] fake a pool to open liquidity widget --- src/pages/Earns/PoolExplorer/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index c8ebe6533f..6cd1f4f31b 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -142,8 +142,10 @@ const Earn = () => { if (!Object.keys(PoolType).includes(`DEX_${pool.exchange.toUpperCase()}`)) return setZapinParams({ provider: library, - poolAddress: pool.address, - chainId: filters.chainId as unknown as ChainId, + // poolAddress: pool.address, + // chainId: filters.chainId as unknown as ChainId, + poolAddress: '0x641C00A822e8b671738d32a431a4Fb6074E5c79d', + chainId: ChainId.Arbitrum, onDismiss: handleCloseZapInWidget, source: 'kyberswap-demo-zap', poolType: PoolType[`DEX_${pool.exchange.toUpperCase()}` as keyof typeof PoolType], From 5eea48cdd1a677e539b408ff3213670122f71e38 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 13:22:19 +0700 Subject: [PATCH 08/83] add navigate to earn landing page & open liquidity widget --- src/pages/Earns/PoolExplorer/index.tsx | 244 ++++++++++-------------- src/pages/Earns/PoolExplorer/styles.tsx | 4 +- src/pages/Earns/index.tsx | 52 ++++- src/pages/Earns/useLiquidityWidget.tsx | 50 +++++ 4 files changed, 195 insertions(+), 155 deletions(-) create mode 100644 src/pages/Earns/useLiquidityWidget.tsx diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 6cd1f4f31b..f02107bb0d 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -1,4 +1,3 @@ -import { ChainId, LiquidityWidget, PoolType } from '@kyberswap/liquidity-widgets' import '@kyberswap/liquidity-widgets/dist/style.css' import { t } from '@lingui/macro' import { useEffect, useMemo, useState } from 'react' @@ -6,7 +5,7 @@ import { Star } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { useGetDexListQuery } from 'services/ksSetting' -import { EarnPool, usePoolsExplorerQuery, useSupportedProtocolsQuery } from 'services/zapEarn' +import { usePoolsExplorerQuery, useSupportedProtocolsQuery } from 'services/zapEarn' import { ReactComponent as IconHighAprPool } from 'assets/svg/ic_pool_high_apr.svg' import { ReactComponent as IconHighlightedPool } from 'assets/svg/ic_pool_highlighted.svg' @@ -15,35 +14,18 @@ import { ReactComponent as IconSolidEarningPool } from 'assets/svg/ic_pool_solid import Pagination from 'components/Pagination' import Search from 'components/Search' import { NETWORKS_INFO } from 'constants/networks' -import { useWeb3React } from 'hooks' import useChainsConfig from 'hooks/useChainsConfig' import useDebounce from 'hooks/useDebounce' import useTheme from 'hooks/useTheme' import SortIcon, { Direction } from 'pages/MarketOverview/SortIcon' import { MEDIA_WIDTHS } from 'theme' +import useLiquidityWidget from '../useLiquidityWidget' import DropdownMenu, { MenuOption } from './DropdownMenu' import TableContent from './TableContent' -import { - ContentWrapper, - LiquidityWidgetWrapper, - PoolsExplorerWrapper, - TableHeader, - TableWrapper, - Tag, - TagContainer, -} from './styles' +import { ContentWrapper, PoolsExplorerWrapper, TableHeader, TableWrapper, Tag, TagContainer } from './styles' import useFilter from './useFilter' -interface ZapInParams { - provider: any - poolAddress: string - chainId: ChainId - onDismiss: () => void - source: string - poolType: PoolType -} - export enum FilterTag { HIGHLIGHTED_POOL = 'highlighted_pool', HIGH_APR = 'high_apr', @@ -77,9 +59,9 @@ export const timings: MenuOption[] = [ const Earn = () => { const theme = useTheme() - const { library } = useWeb3React() const { supportedChains } = useChainsConfig() const { filters, updateFilters } = useFilter() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() const dexList = useGetDexListQuery({ chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, @@ -111,7 +93,6 @@ const Earn = () => { const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const [search, setSearch] = useState('') - const [zapinParams, setZapinParams] = useState(null) const deboundedSearch = useDebounce(search, 300) const onChainChange = (newChainId: string | number) => { @@ -137,21 +118,6 @@ const Earn = () => { updateFilters('orderBy', '') } - const handleCloseZapInWidget = () => setZapinParams(null) - const handleOpenZapInWidget = (pool: EarnPool) => { - if (!Object.keys(PoolType).includes(`DEX_${pool.exchange.toUpperCase()}`)) return - setZapinParams({ - provider: library, - // poolAddress: pool.address, - // chainId: filters.chainId as unknown as ChainId, - poolAddress: '0x641C00A822e8b671738d32a431a4Fb6074E5c79d', - chainId: ChainId.Arbitrum, - onDismiss: handleCloseZapInWidget, - source: 'kyberswap-demo-zap', - poolType: PoolType[`DEX_${pool.exchange.toUpperCase()}` as keyof typeof PoolType], - }) - } - useEffect(() => { if (filters.q !== deboundedSearch) { updateFilters('q', deboundedSearch || '') @@ -160,6 +126,8 @@ const Earn = () => { return ( + {liquidityWidget} +
{t`Earning with Smart Liquidity Providing`} @@ -168,113 +136,103 @@ const Earn = () => { {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`}
- {zapinParams ? ( - - - - ) : ( - <> - - updateFilters('tag', '')}> - {t`All pools`} - - updateFilters('tag', 'favorite')}> - - - {filterTags.map(item => ( - + updateFilters('tag', '')}> + {t`All pools`} + + updateFilters('tag', 'favorite')}> + + + {filterTags.map(item => ( + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + ))} + + + + + + + + setSearch(val)} + style={{ height: '36px' }} + /> + + + + {!upToMedium && ( + + Protocol + Pair + onSortChange(SortBy.APR)} + > + APR + + + onSortChange(SortBy.EARN_FEE)} + > + Earn Fees + + + onSortChange(SortBy.TVL)} + > + TVL + + + updateFilters('tag', item.value)} + onClick={() => onSortChange(SortBy.VOLUME)} > - {!upToExtraSmall && item.icon} - {item.label} - - ))} - - - - - - - - setSearch(val)} - style={{ height: '36px' }} - /> - - - - {!upToMedium && ( - - Protocol - Pair - onSortChange(SortBy.APR)} - > - APR - - - onSortChange(SortBy.EARN_FEE)} - > - Earn Fees - - - onSortChange(SortBy.TVL)} - > - TVL - - - onSortChange(SortBy.VOLUME)} - > - Volume - - -
- - )} - - - {(!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) && ( - { - updateFilters('page', newPage.toString()) - }} - totalCount={poolData?.data?.pagination?.totalItems || 0} - currentPage={(filters.page || 0) + 1} - pageSize={filters.limit || 10} - /> - )} - - - )} + Volume + + +
+ + )} + + + {(!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) && ( + { + updateFilters('page', newPage.toString()) + }} + totalCount={poolData?.data?.pagination?.totalItems || 0} + currentPage={(filters.page || 0) + 1} + pageSize={filters.limit || 10} + /> + )} + ) } diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index 3b57aac69e..6a10b602cb 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -90,7 +90,7 @@ export const TableRow = styled.div` cursor: pointer; :hover { - background: ${({ theme }) => rgba(theme.primary, 0.2)}; + background: #31cb9e1a; } ${({ theme }) => theme.mediaWidth.upToLarge` @@ -108,7 +108,7 @@ export const FeeTier = styled.div` ` export const CurrencyRoundedImage = styled(Image)` - border-radius: 50%; + /* border-radius: 50%; */ ` export const CurrencySecondImage = styled(CurrencyRoundedImage)` diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index b0e2fa63da..6b709b03f1 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -1,5 +1,6 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { rgba } from 'polished' +import { useNavigate } from 'react-router-dom' import { Box, Flex, Text } from 'rebass' import { EarnPool, useExplorerLandingQuery } from 'services/zapEarn' import styled, { keyframes } from 'styled-components' @@ -15,9 +16,13 @@ import RocketIcon from 'assets/svg/rocket.svg' import SolidEarningIcon from 'assets/svg/solid-earning.svg' import StakingIcon from 'assets/svg/staking.svg' import { ButtonPrimary } from 'components/Button' +import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'hooks/useChainsConfig' import useTheme from 'hooks/useTheme' +import { FilterTag } from './PoolExplorer' +import useLiquidityWidget from './useLiquidityWidget' + const WrapperBg = styled.div` background-image: url(${bg}); background-size: 100% auto; @@ -59,6 +64,7 @@ const BorderWrapper = styled.div` padding: 1px; /* Border width */ background: linear-gradient(306.9deg, #262525 38.35%, rgba(49, 203, 158, 0.06) 104.02%), radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(49, 203, 158, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); /* Mask to avoid background bleed */ -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); /* Mask to avoid background bleed */ z-index: -1; } @@ -103,6 +109,7 @@ const PoolWrapper = styled.div` background: linear-gradient(215.58deg, #262525 -9.03%, rgba(148, 115, 221, 0.2) 59.21%), radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(130, 71, 229, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + mask-composite: destination-out; -webkit-mask-composite: destination-out; z-index: -1; /* Position behind the content */ } @@ -201,7 +208,11 @@ const Card = ({ {desc} - + !action.disabled && action.onClick()} + > {action.text} @@ -210,9 +221,9 @@ const Card = ({ } export default function Earns() { + const navigate = useNavigate() const theme = useTheme() const { data } = useExplorerLandingQuery() - console.log(data) const title = (_title: string, icon: string) => ( <> @@ -256,7 +267,9 @@ export default function Earns() { desc="Explore and instantly add liquidity to high-APY pools the easy way with Zap Technology." action={{ text: 'View Pools', - onClick: () => {}, + onClick: () => { + navigate({ pathname: APP_PATHS.EARN_POOLS }) + }, }} /> {}, + disabled: true, }} /> { - // TODO:: go to explorer page + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.HIGHLIGHTED_POOL}`, + }) }} > {title('Highlighted Pools', FireIcon)} @@ -307,7 +325,10 @@ export default function Earns() { { - // TODO:: go to explorer page + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.HIGH_APR}`, + }) }} > {title('High APR', RocketIcon)} @@ -329,7 +350,10 @@ export default function Earns() { { - // TODO:: go to explorer page + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.LOW_VOLATILITY}`, + }) }} > {title('Low Volatility', LowVolatilityIcon)} @@ -351,7 +375,10 @@ export default function Earns() { { - // TODO:: go to explorer page + navigate({ + pathname: APP_PATHS.EARN_POOLS, + search: `tag=${FilterTag.SOLID_EARNING}`, + }) }} > {title('Solid Earning', SolidEarningIcon)} @@ -373,7 +400,9 @@ export default function Earns() { { - // TODO: go to explorer page + navigate({ + pathname: APP_PATHS.EARN_POOLS, + }) }} sx={{ cursor: 'pointer', @@ -401,6 +430,8 @@ export default function Earns() { const PoolItem = ({ pool }: { pool: EarnPool }) => { const theme = useTheme() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + return ( { role="button" onClick={e => { e.stopPropagation() - // TODO: open zap in widget + handleOpenZapInWidget(pool) }} > + {liquidityWidget} diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx new file mode 100644 index 0000000000..0ab3109847 --- /dev/null +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -0,0 +1,50 @@ +import { ChainId, LiquidityWidget, PoolType } from '@kyberswap/liquidity-widgets' +import { useState } from 'react' +import { EarnPool } from 'services/zapEarn' + +import Modal from 'components/Modal' +import { useWeb3React } from 'hooks' + +// import useFilter from './PoolExplorer/useFilter' + +interface LiquidityParams { + provider: any + poolAddress: string + chainId: ChainId + onDismiss: () => void + source: string + poolType: PoolType +} + +const useLiquidityWidget = () => { + const { library } = useWeb3React() + // const { filters } = useFilter() + + const [liquidityParams, setLiquidityParams] = useState(null) + + const handleCloseZapInWidget = () => setLiquidityParams(null) + const handleOpenZapInWidget = (_pool: EarnPool) => { + // if (!Object.keys(PoolType).includes(`DEX_${pool.exchange.toUpperCase()}`)) return + setLiquidityParams({ + provider: library, + // poolAddress: pool.address, + // chainId: (pool.chainId || filters.chainId) as ChainId, + poolAddress: '0x641C00A822e8b671738d32a431a4Fb6074E5c79d', + chainId: ChainId.Arbitrum, + onDismiss: handleCloseZapInWidget, + source: 'kyberswap-demo-zap', + // poolType: PoolType[`DEX_${pool.exchange.toUpperCase()}` as keyof typeof PoolType], + poolType: PoolType.DEX_PANCAKESWAPV3, + }) + } + + const liquidityWidget = liquidityParams ? ( + + + + ) : null + + return { liquidityWidget, liquidityParams, handleOpenZapInWidget } +} + +export default useLiquidityWidget From 86b8f828eda9954041d9a9b140bf354384d4630e Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 15:47:29 +0700 Subject: [PATCH 09/83] add feature favorite --- src/pages/Earns/PoolExplorer/TableContent.tsx | 103 +++++++++++++++--- src/services/zapEarn.ts | 34 +++++- 2 files changed, 122 insertions(+), 15 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 3584afd52a..db4395ba09 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -3,12 +3,15 @@ import { Star } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { useGetDexListQuery } from 'services/ksSetting' -import { EarnPool, usePoolsExplorerQuery } from 'services/zapEarn' +import { EarnPool, useAddFavoriteMutation, usePoolsExplorerQuery, useRemoveFavoriteMutation } from 'services/zapEarn' +import { NotificationType } from 'components/Announcement/type' import { Image } from 'components/Image' import { NETWORKS_INFO } from 'constants/networks' +import { useActiveWeb3React, useWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' import { Direction } from 'pages/MarketOverview/SortIcon' +import { useNotify, useWalletModalToggle } from 'state/application/hooks' import { MEDIA_WIDTHS } from 'theme' import { formatDisplayNumber } from 'utils/numbers' @@ -27,11 +30,18 @@ import useFilter from './useFilter' const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPool) => void }) => { const theme = useTheme() + const { account } = useActiveWeb3React() + const { library } = useWeb3React() const { filters } = useFilter() + const notify = useNotify() + const toggleWalletModal = useWalletModalToggle() + const dexList = useGetDexListQuery({ chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, }) - const { data: poolData } = usePoolsExplorerQuery(filters) + const { data: poolData, refetch } = usePoolsExplorerQuery(filters) + const [addFavorite] = useAddFavoriteMutation() + const [removeFavorite] = useRemoveFavoriteMutation() const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) @@ -58,9 +68,78 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo return parsedPoolData }, [poolData, filters, dexList]) - const handleFavorite = (e: React.MouseEvent) => { + const handleFavorite = async (e: React.MouseEvent, pool: EarnPool) => { e.stopPropagation() - // toggleFavorite(item) + + if (!account) { + toggleWalletModal() + return + } + + let signature = '' + let msg = '' + + const key = `poolExplorer_${account}` + try { + const data = JSON.parse(localStorage.getItem(key) || '') + if (data.issuedAt) { + const expire = new Date(data.issuedAt) + expire.setDate(expire.getDate() + 7) + const now = new Date() + if (expire > now) { + signature = data.signature + msg = data.msg + } + } + } catch { + // + } + if (!signature) { + const issuedAt = new Date().toISOString() + msg = `Click sign to add favorite pools at Kyberswap.com without logging in.\nThis request won’t trigger any blockchain transaction or cost any gas fee. Expires in 7 days. \n\nIssued at: ${issuedAt}` + signature = await library?.send('personal_sign', [`0x${Buffer.from(msg, 'utf8').toString('hex')}`, account]) + localStorage.setItem( + key, + JSON.stringify({ + signature, + msg, + issuedAt, + }), + ) + } + + const isPoolFavorite = !!pool.favorite?.isFavorite + await (isPoolFavorite ? removeFavorite : addFavorite)({ + chainId: filters.chainId, + userAddress: account, + poolAddress: pool.address, + message: msg, + signature, + }) + .then(res => { + if ((res as any).error) { + notify( + { + title: `${!isPoolFavorite ? 'Add' : 'Remove'} failed`, + summary: (res as any).error.data.message || 'Some thing went wrong', + type: NotificationType.ERROR, + }, + 8000, + ) + } else refetch() + }) + .catch(err => { + // localStorage.removeItem(key) + console.log(err) + notify( + { + title: `${!isPoolFavorite ? 'Add' : 'Remove'} failed`, + summary: err.message || 'Some thing went wrong', + type: NotificationType.ERROR, + }, + 8000, + ) + }) } if (!tablePoolData?.length) @@ -93,13 +172,11 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo 0}>{pool.apr}% handleFavorite(e, pool)} /> @@ -154,13 +231,11 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo handleFavorite(e, pool)} /> diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts index d06f900235..32ee616abb 100644 --- a/src/services/zapEarn.ts +++ b/src/services/zapEarn.ts @@ -25,6 +25,10 @@ export interface EarnPool { apr: number liquidity: number chainId?: number + favorite?: { + chainId: number + isFavorite: boolean + } tokens: Array<{ address: string logoURI: string @@ -66,6 +70,14 @@ interface ExplorerLandingResponse { } } +interface AddRemoveFavoriteParams { + chainId: ChainId + message: string + signature: string + poolAddress: string + userAddress: string +} + const zapEarnServiceApi = createApi({ reducerPath: 'zapEarnServiceApi ', baseQuery: fetchBaseQuery({ @@ -92,9 +104,29 @@ const zapEarnServiceApi = createApi({ }, }), }), + addFavorite: builder.mutation({ + query: body => ({ + method: 'POST', + body, + url: `/v1/favorite`, + }), + }), + removeFavorite: builder.mutation({ + query: body => ({ + method: 'DELETE', + body, + url: `/v1/favorite`, + }), + }), }), }) -export const { useExplorerLandingQuery, useSupportedProtocolsQuery, usePoolsExplorerQuery } = zapEarnServiceApi +export const { + useExplorerLandingQuery, + useSupportedProtocolsQuery, + usePoolsExplorerQuery, + useAddFavoriteMutation, + useRemoveFavoriteMutation, +} = zapEarnServiceApi export default zapEarnServiceApi From ad3bb3729295c85bc1e66ac6e55fc1a6e6927c97 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 14 Nov 2024 16:28:33 +0700 Subject: [PATCH 10/83] integrate new zap in widget ui --- package.json | 2 +- src/pages/Earns/PoolExplorer/index.tsx | 2 +- src/pages/Earns/useLiquidityWidget.tsx | 2 +- yarn.lock | 60 ++++++++++++-------------- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 4f9f6a51cb..a895c8041d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "@kyberswap/liquidity-widgets": "^0.0.15", + "kyberswap-liquidity-widgets": "^0.0.12", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index f02107bb0d..57c975bf4e 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -1,5 +1,5 @@ -import '@kyberswap/liquidity-widgets/dist/style.css' import { t } from '@lingui/macro' +import 'kyberswap-liquidity-widgets/dist/style.css' import { useEffect, useMemo, useState } from 'react' import { Star } from 'react-feather' import { useMedia } from 'react-use' diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 0ab3109847..e13c160335 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -1,4 +1,4 @@ -import { ChainId, LiquidityWidget, PoolType } from '@kyberswap/liquidity-widgets' +import { ChainId, LiquidityWidget, PoolType } from 'kyberswap-liquidity-widgets' import { useState } from 'react' import { EarnPool } from 'services/zapEarn' diff --git a/yarn.lock b/yarn.lock index e88749d5e2..44a08a3883 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3765,34 +3765,6 @@ tiny-invariant "^1.1.0" tiny-warning "^1.0.3" -"@kyberswap/liquidity-widgets@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@kyberswap/liquidity-widgets/-/liquidity-widgets-0.0.15.tgz#ac08cf27d2f8b0761f660a1131f76cabb3ad243f" - integrity sha512-qNjf41Jv1mlSjySyIH3jjP7Ft9b+KAT4tnquK1YhYdAZl2PJzvB6SoWVMwayT+18QRNByN8Iq+rdGc4pAFQkPw== - dependencies: - "@ethersproject/providers" "^5.7.2" - "@kyberswap/ks-sdk-core" "^1.1.5" - "@pancakeswap/sdk" "^5.8.2" - "@pancakeswap/swap-sdk-core" "^1.2.0" - "@pancakeswap/v3-sdk" "^3.8.3" - "@popperjs/core" "^2.11.8" - "@radix-ui/react-accordion" "^1.2.1" - "@radix-ui/react-icons" "^1.3.0" - "@radix-ui/react-scroll-area" "^1.1.0" - "@radix-ui/react-slot" "^1.1.0" - "@uniswap/sdk-core" "5.8.1" - "@uniswap/v3-sdk" "3.18.1" - class-variance-authority "^0.7.0" - clsx "^2.1.1" - d3 "^7.9.0" - ethers "^5.7.0" - jsbi "^3.2.5" - lodash.partition "^4.6.0" - lucide-react "^0.453.0" - numeral "^2.0.6" - polished "^4.3.1" - react-popper "^2.3.0" - "@kyberswap/oauth2@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@kyberswap/oauth2/-/oauth2-1.0.2.tgz#4e0fdfa9722ba2f185a104293b85b6ca58be775b" @@ -14551,6 +14523,33 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== +kyberswap-liquidity-widgets@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-0.0.12.tgz#1802b0cca9277a598e7e7e995aa8f550e5120fae" + integrity sha512-26G8spvW4Z015FGTQfAk1npjyF7X76YN/nQuPj9BYkLRqd2RdLL5+a+N8T1doTUcEhrQkml6hW7WOnRFbxaV+A== + dependencies: + "@ethersproject/providers" "^5.7.2" + "@kyberswap/ks-sdk-core" "^1.1.5" + "@pancakeswap/sdk" "^5.8.2" + "@pancakeswap/swap-sdk-core" "^1.2.0" + "@pancakeswap/v3-sdk" "^3.8.3" + "@popperjs/core" "^2.11.8" + "@radix-ui/react-accordion" "^1.2.1" + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-scroll-area" "^1.1.0" + "@radix-ui/react-slot" "^1.1.0" + "@uniswap/sdk-core" "5.8.1" + "@uniswap/v3-sdk" "3.18.1" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + d3 "^7.9.0" + ethers "^5.7.0" + jsbi "^3.2.5" + lodash.partition "^4.6.0" + numeral "^2.0.6" + polished "^4.3.1" + react-popper "^2.3.0" + language-subtag-registry@~0.3.2: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -14927,11 +14926,6 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== -lucide-react@^0.453.0: - version "0.453.0" - resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.453.0.tgz#d37909a45a29d89680383a202ee861224b05ba6a" - integrity sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ== - lz-string@^1.4.4, lz-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" From 0d32d3ea14896bc2652d12993c7f94ab87bd207c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 18 Nov 2024 15:43:31 +0700 Subject: [PATCH 11/83] add wallet connect & change network callback --- package.json | 2 +- src/pages/Earns/useLiquidityWidget.tsx | 9 +++++- yarn.lock | 39 +++++--------------------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index a895c8041d..973d1c0f96 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^0.0.12", + "kyberswap-liquidity-widgets": "^1.1.2", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index e13c160335..0f39f1a570 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -4,6 +4,7 @@ import { EarnPool } from 'services/zapEarn' import Modal from 'components/Modal' import { useWeb3React } from 'hooks' +import { useNetworkModalToggle, useWalletModalToggle } from 'state/application/hooks' // import useFilter from './PoolExplorer/useFilter' @@ -11,13 +12,17 @@ interface LiquidityParams { provider: any poolAddress: string chainId: ChainId - onDismiss: () => void source: string poolType: PoolType + onDismiss: () => void + onConnectWallet: () => void + onChangeNetwork: () => void } const useLiquidityWidget = () => { const { library } = useWeb3React() + const toggleWalletModal = useWalletModalToggle() + const toggleNetworkModal = useNetworkModalToggle() // const { filters } = useFilter() const [liquidityParams, setLiquidityParams] = useState(null) @@ -35,6 +40,8 @@ const useLiquidityWidget = () => { source: 'kyberswap-demo-zap', // poolType: PoolType[`DEX_${pool.exchange.toUpperCase()}` as keyof typeof PoolType], poolType: PoolType.DEX_PANCAKESWAPV3, + onConnectWallet: toggleWalletModal, + onChangeNetwork: toggleNetworkModal, }) } diff --git a/yarn.lock b/yarn.lock index 44a08a3883..bba5d0ecab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-0.0.12.tgz#1802b0cca9277a598e7e7e995aa8f550e5120fae" - integrity sha512-26G8spvW4Z015FGTQfAk1npjyF7X76YN/nQuPj9BYkLRqd2RdLL5+a+N8T1doTUcEhrQkml6hW7WOnRFbxaV+A== +kyberswap-liquidity-widgets@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.2.tgz#eda344aa0f9cdd928945a27db16d380172c0dd9f" + integrity sha512-R9PxPxM5xhHnuNofozWgy8VCHpP7yqGJUJYbi8wzvQHvf4Ysv6ZGJcQC8e5navKlImFJ0CqTZpHWIadhW7dWKA== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" @@ -18677,16 +18677,7 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18759,7 +18750,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18773,13 +18764,6 @@ strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -20410,7 +20394,7 @@ workbox-strategies@6.5.4: dependencies: workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -20428,15 +20412,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From e3a8d78834819f62b35e888bbdbb93b25e73c38a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 18 Nov 2024 16:59:12 +0700 Subject: [PATCH 12/83] add loading for favorite to prevent multi click --- src/pages/Earns/PoolExplorer/TableContent.tsx | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index db4395ba09..dc67f01d71 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useMemo, useState } from 'react' import { Star } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' @@ -7,6 +7,7 @@ import { EarnPool, useAddFavoriteMutation, usePoolsExplorerQuery, useRemoveFavor import { NotificationType } from 'components/Announcement/type' import { Image } from 'components/Image' +import Loader from 'components/Loader' import { NETWORKS_INFO } from 'constants/networks' import { useActiveWeb3React, useWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' @@ -43,6 +44,8 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const [addFavorite] = useAddFavoriteMutation() const [removeFavorite] = useRemoveFavoriteMutation() + const [favoriteLoading, setFavoriteLoading] = useState([]) + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const tablePoolData = useMemo(() => { @@ -70,9 +73,12 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const handleFavorite = async (e: React.MouseEvent, pool: EarnPool) => { e.stopPropagation() + if (favoriteLoading.includes(pool.address)) return + handleAddFavoriteLoading(pool.address) if (!account) { toggleWalletModal() + handleRemoveFavoriteLoading(pool.address) return } @@ -92,7 +98,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo } } } catch { - // + handleRemoveFavoriteLoading(pool.address) } if (!signature) { const issuedAt = new Date().toISOString() @@ -140,7 +146,13 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo 8000, ) }) + .finally(() => handleRemoveFavoriteLoading(pool.address)) + } + const handleAddFavoriteLoading = (poolAddress: string) => { + if (!favoriteLoading.includes(poolAddress)) setFavoriteLoading([...favoriteLoading, poolAddress]) } + const handleRemoveFavoriteLoading = (poolAddress: string) => + setFavoriteLoading(favoriteLoading.filter(address => address !== poolAddress)) if (!tablePoolData?.length) return ( @@ -229,14 +241,18 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} - handleFavorite(e, pool)} - /> + {favoriteLoading.includes(pool.address) ? ( + + ) : ( + handleFavorite(e, pool)} + /> + )} ))} From b56d9008dfd705f65685b56b5e26a6b1209b4e65 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 18 Nov 2024 17:10:51 +0700 Subject: [PATCH 13/83] add interval re-fetch for pool data --- src/pages/Earns/PoolExplorer/TableContent.tsx | 2 +- src/pages/Earns/PoolExplorer/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index dc67f01d71..0c288e8b9a 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -40,7 +40,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const dexList = useGetDexListQuery({ chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, }) - const { data: poolData, refetch } = usePoolsExplorerQuery(filters) + const { data: poolData, refetch } = usePoolsExplorerQuery(filters, { pollingInterval: 5 * 60_000 }) const [addFavorite] = useAddFavoriteMutation() const [removeFavorite] = useRemoveFavoriteMutation() diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 57c975bf4e..c00c5287a0 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -67,7 +67,7 @@ const Earn = () => { chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, }) const { data: supportedProtocolsData } = useSupportedProtocolsQuery() - const { data: poolData } = usePoolsExplorerQuery(filters) + const { data: poolData } = usePoolsExplorerQuery(filters, { pollingInterval: 5 * 60_000 }) const supportedProtocols = useMemo(() => { if (!supportedProtocolsData?.data?.chains) return [] From ee87d5622bb54b7746b74ab4a03d0dd28dd7a5d4 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 18 Nov 2024 17:57:00 +0700 Subject: [PATCH 14/83] fix widget over height issue --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 973d1c0f96..0fa633b78f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.2", + "kyberswap-liquidity-widgets": "^1.1.4", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index bba5d0ecab..b699ebb087 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.2.tgz#eda344aa0f9cdd928945a27db16d380172c0dd9f" - integrity sha512-R9PxPxM5xhHnuNofozWgy8VCHpP7yqGJUJYbi8wzvQHvf4Ysv6ZGJcQC8e5navKlImFJ0CqTZpHWIadhW7dWKA== +kyberswap-liquidity-widgets@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.4.tgz#a426558e43365161eabce538ffa5d990d69a583e" + integrity sha512-85hnxpAU2nlqeODI3TQGhw1V91/8lPc4dWfvqhU/coanjsL1Cj9sZLZLZmkIb0rjZBslUO79OnUm729ldStv6w== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 9c9cc433ce6d4089827a84feb58a065ba787efcb Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 18 Nov 2024 18:32:33 +0700 Subject: [PATCH 15/83] add pagination for pool data with tag --- src/pages/Earns/PoolExplorer/TableContent.tsx | 7 +++++- src/pages/Earns/PoolExplorer/index.tsx | 23 +++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 0c288e8b9a..6a7bc076cd 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -49,7 +49,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const tablePoolData = useMemo(() => { - const parsedPoolData = (poolData?.data?.pools || []).map(pool => ({ + let parsedPoolData = (poolData?.data?.pools || []).map(pool => ({ ...pool, dexLogo: dexList.data?.find(dex => dex.dexId === pool.exchange)?.logoURL || '', dexName: dexList.data?.find(dex => dex.dexId === pool.exchange)?.name || '', @@ -66,6 +66,11 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo return filters.orderBy === Direction.DESC ? b.volume - a.volume : a.volume - b.volume return 0 }) + + const page = filters.page || 0 + const limit = filters.limit || 0 + + parsedPoolData = page > 9 ? [] : parsedPoolData.slice(page * limit, limit) } return parsedPoolData diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index c00c5287a0..9fb7238283 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -95,6 +95,13 @@ const Earn = () => { const [search, setSearch] = useState('') const deboundedSearch = useDebounce(search, 300) + const totalPools = useMemo(() => { + const totalItems = poolData?.data?.pagination?.totalItems || 0 + if (!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) return totalItems + + return totalItems <= 100 ? totalItems : 100 + }, [poolData, filters.tag]) + const onChainChange = (newChainId: string | number) => { updateFilters('chainId', newChainId.toString()) } @@ -222,16 +229,12 @@ const Earn = () => { )} - {(!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) && ( - { - updateFilters('page', newPage.toString()) - }} - totalCount={poolData?.data?.pagination?.totalItems || 0} - currentPage={(filters.page || 0) + 1} - pageSize={filters.limit || 10} - /> - )} + updateFilters('page', newPage.toString())} + totalCount={totalPools} + currentPage={(filters.page || 0) + 1} + pageSize={filters.limit || 10} + /> ) From f3a438477f9a74ac29331c614e54a93f174def2d Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 19 Nov 2024 11:45:24 +0700 Subject: [PATCH 16/83] fix height issue --- src/pages/Earns/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 6b709b03f1..d8a6a0634d 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -135,6 +135,7 @@ const CardWrapper = styled.div` const ListPoolWrapper = styled.div` padding: 20px; border-radius: 20px; + height: 100%; background: linear-gradient(119.08deg, rgba(20, 29, 27, 1) -0.89%, rgba(14, 14, 14, 1) 132.3%); cursor: url(${CursorIcon}), auto; ` From b7ae38ea1c0e4c5b13c293d31dc884f016f0ef84 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 19 Nov 2024 16:37:16 +0700 Subject: [PATCH 17/83] add dynamic data --- package.json | 2 +- src/pages/Earns/PoolExplorer/TableContent.tsx | 12 ++++++----- src/pages/Earns/PoolExplorer/index.tsx | 2 +- src/pages/Earns/PoolExplorer/styles.tsx | 7 +++++++ src/pages/Earns/useLiquidityWidget.tsx | 21 +++++++++---------- yarn.lock | 8 +++---- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 0fa633b78f..411a1acc70 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.4", + "kyberswap-liquidity-widgets": "^1.1.6", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 6a7bc076cd..eb80993b95 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -24,6 +24,7 @@ import { FeeTier, MobileTableBottomRow, MobileTableRow, + SymbolText, TableBody, TableRow, } from './styles' @@ -178,7 +179,9 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo - {/* {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} */}USDT/ARB + + {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} + {pool.feeTier * 100}% @@ -229,10 +232,9 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo - - {/* {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} */} - USDT/ARB - + + {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} + {pool.feeTier * 100}% 0}>{pool.apr}% diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 9fb7238283..d31c73101f 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -230,7 +230,7 @@ const Earn = () => { updateFilters('page', newPage.toString())} + onPageChange={(newPage: number) => updateFilters('page', (newPage - 1).toString())} totalCount={totalPools} currentPage={(filters.page || 0) + 1} pageSize={filters.limit || 10} diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index 6a10b602cb..79dca69190 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -116,6 +116,13 @@ export const CurrencySecondImage = styled(CurrencyRoundedImage)` left: -6px; ` +export const SymbolText = styled.div` + max-width: 115px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + export const Apr = styled.div<{ positive: boolean }>` display: flex; justify-content: flex-end; diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 0f39f1a570..f7fef7c084 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -6,7 +6,7 @@ import Modal from 'components/Modal' import { useWeb3React } from 'hooks' import { useNetworkModalToggle, useWalletModalToggle } from 'state/application/hooks' -// import useFilter from './PoolExplorer/useFilter' +import useFilter from './PoolExplorer/useFilter' interface LiquidityParams { provider: any @@ -23,23 +23,22 @@ const useLiquidityWidget = () => { const { library } = useWeb3React() const toggleWalletModal = useWalletModalToggle() const toggleNetworkModal = useNetworkModalToggle() - // const { filters } = useFilter() + const { filters } = useFilter() const [liquidityParams, setLiquidityParams] = useState(null) const handleCloseZapInWidget = () => setLiquidityParams(null) - const handleOpenZapInWidget = (_pool: EarnPool) => { - // if (!Object.keys(PoolType).includes(`DEX_${pool.exchange.toUpperCase()}`)) return + const handleOpenZapInWidget = (pool: EarnPool) => { + const supportedDexs = Object.keys(PoolType).map(item => item.replace('DEX_', '').replace('V3', '').toLowerCase()) + const dex = supportedDexs.find(item => pool.exchange.toLowerCase().includes(item)) + if (!dex) return setLiquidityParams({ provider: library, - // poolAddress: pool.address, - // chainId: (pool.chainId || filters.chainId) as ChainId, - poolAddress: '0x641C00A822e8b671738d32a431a4Fb6074E5c79d', - chainId: ChainId.Arbitrum, - onDismiss: handleCloseZapInWidget, + poolAddress: pool.address, + chainId: (pool.chainId || filters.chainId) as ChainId, source: 'kyberswap-demo-zap', - // poolType: PoolType[`DEX_${pool.exchange.toUpperCase()}` as keyof typeof PoolType], - poolType: PoolType.DEX_PANCAKESWAPV3, + poolType: PoolType[`DEX_${dex.toUpperCase()}V3` as keyof typeof PoolType], + onDismiss: handleCloseZapInWidget, onConnectWallet: toggleWalletModal, onChangeNetwork: toggleNetworkModal, }) diff --git a/yarn.lock b/yarn.lock index b699ebb087..bcc9f40187 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.4.tgz#a426558e43365161eabce538ffa5d990d69a583e" - integrity sha512-85hnxpAU2nlqeODI3TQGhw1V91/8lPc4dWfvqhU/coanjsL1Cj9sZLZLZmkIb0rjZBslUO79OnUm729ldStv6w== +kyberswap-liquidity-widgets@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.6.tgz#77d9d947d11e3cac1de764861a1340df285576b7" + integrity sha512-/fo4HhPBZ6JnWrzQgIJDxHI5gP8nDKLySSfV1isJnjp7UiZc+ccDkV09coAJ7t0BTCMSytzHW4g18CemZyDzDQ== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From e011278c5b4d1d96133be0047e884fa0ca2864b0 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 19 Nov 2024 16:58:50 +0700 Subject: [PATCH 18/83] fix img radius --- src/pages/Earns/PoolExplorer/styles.tsx | 2 +- src/pages/Earns/index.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index 79dca69190..bce0b40157 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -108,7 +108,7 @@ export const FeeTier = styled.div` ` export const CurrencyRoundedImage = styled(Image)` - /* border-radius: 50%; */ + border-radius: 50%; ` export const CurrencySecondImage = styled(CurrencyRoundedImage)` diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index d8a6a0634d..2db8c00033 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -445,8 +445,14 @@ const PoolItem = ({ pool }: { pool: EarnPool }) => { > {liquidityWidget} - - + + Date: Tue, 19 Nov 2024 17:32:47 +0700 Subject: [PATCH 19/83] fix ui --- src/pages/Earns/PoolExplorer/TableContent.tsx | 10 +++++----- src/pages/Earns/PoolExplorer/styles.tsx | 11 +++++++---- src/pages/Earns/index.tsx | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index eb80993b95..5f4bc3362c 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -183,12 +183,12 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} - - {pool.feeTier * 100}% + + {pool.feeTier}% - + 0}>{pool.apr}% TVL {formatDisplayNumber(pool.liquidity, { style: 'currency', significantDigits: 6 })} - + Volume {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} @@ -235,7 +235,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} - {pool.feeTier * 100}% + {pool.feeTier}% 0}>{pool.apr}% diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index bce0b40157..1e58502f6a 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -105,6 +105,10 @@ export const FeeTier = styled.div` background: ${({ theme }) => rgba(theme.white, 0.04)}; color: ${({ theme }) => theme.subText}; width: fit-content; + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + font-size: 14px; + `} ` export const CurrencyRoundedImage = styled(Image)` @@ -138,9 +142,8 @@ export const MobileTableRow = styled.div` } ` export const MobileTableBottomRow = styled.div<{ withoutBorder: boolean }>` - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 0 24px; + display: grid; + grid-template-columns: 1.5fr 1fr 1fr; + padding: 16px 0; border-bottom: ${({ withoutBorder, theme }) => (withoutBorder ? 'none' : `1px solid ${theme.tableHeader}`)}; ` diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 2db8c00033..60d2d36ce1 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -473,7 +473,7 @@ const PoolItem = ({ pool }: { pool: EarnPool }) => { {pool.tokens[1].symbol} - {pool.feeTier * 100}% + {pool.feeTier}% {pool.apr}% From 9abf5203817a348333887c9b5f64d86c627d8130 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 20 Nov 2024 14:44:48 +0700 Subject: [PATCH 20/83] add notify if dex is not suportted on chain --- src/pages/Earns/useLiquidityWidget.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index f7fef7c084..9f42bf5bba 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -2,9 +2,11 @@ import { ChainId, LiquidityWidget, PoolType } from 'kyberswap-liquidity-widgets' import { useState } from 'react' import { EarnPool } from 'services/zapEarn' +import { NotificationType } from 'components/Announcement/type' import Modal from 'components/Modal' +import { NETWORKS_INFO } from 'constants/networks' import { useWeb3React } from 'hooks' -import { useNetworkModalToggle, useWalletModalToggle } from 'state/application/hooks' +import { useNetworkModalToggle, useNotify, useWalletModalToggle } from 'state/application/hooks' import useFilter from './PoolExplorer/useFilter' @@ -23,6 +25,7 @@ const useLiquidityWidget = () => { const { library } = useWeb3React() const toggleWalletModal = useWalletModalToggle() const toggleNetworkModal = useNetworkModalToggle() + const notify = useNotify() const { filters } = useFilter() const [liquidityParams, setLiquidityParams] = useState(null) @@ -31,7 +34,20 @@ const useLiquidityWidget = () => { const handleOpenZapInWidget = (pool: EarnPool) => { const supportedDexs = Object.keys(PoolType).map(item => item.replace('DEX_', '').replace('V3', '').toLowerCase()) const dex = supportedDexs.find(item => pool.exchange.toLowerCase().includes(item)) - if (!dex) return + if (!dex) { + notify( + { + title: `Open pool detail failed`, + summary: `Protocol ${pool.exchange} on ${ + NETWORKS_INFO[String(pool?.chainId || filters.chainId) as unknown as keyof typeof NETWORKS_INFO]?.name || + 'this network' + } is not supported`, + type: NotificationType.ERROR, + }, + 8000, + ) + return + } setLiquidityParams({ provider: library, poolAddress: pool.address, From 4d0ac6143f15ac85e0fbb9f1cd36f06164d97f4b Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 20 Nov 2024 15:40:45 +0700 Subject: [PATCH 21/83] add copy pool address to pool explorer mobile --- src/components/Copy/index.tsx | 2 +- src/pages/Earns/PoolExplorer/TableContent.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Copy/index.tsx b/src/components/Copy/index.tsx index e4cca029de..3977b466e8 100644 --- a/src/components/Copy/index.tsx +++ b/src/components/Copy/index.tsx @@ -106,7 +106,7 @@ const CopyHelper = forwardRef(function CopyHelper( ) return ( - + e.stopPropagation()} margin={margin} style={style}> {text ? ( {copyIcon} {text} diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 5f4bc3362c..b485bfac1b 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -6,6 +6,7 @@ import { useGetDexListQuery } from 'services/ksSetting' import { EarnPool, useAddFavoriteMutation, usePoolsExplorerQuery, useRemoveFavoriteMutation } from 'services/zapEarn' import { NotificationType } from 'components/Announcement/type' +import CopyHelper from 'components/Copy' import { Image } from 'components/Image' import Loader from 'components/Loader' import { NETWORKS_INFO } from 'constants/networks' @@ -179,9 +180,12 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo - - {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} - + + + {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} + + + {pool.feeTier}% From 4cb0f0a89d4af1e6c052b9a48c0a8bc4191beca8 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 20 Nov 2024 17:19:17 +0700 Subject: [PATCH 22/83] fix zap widget mobile full width --- package.json | 2 +- src/components/Modal/index.tsx | 29 +++++++++++++++++++++----- src/pages/Earns/useLiquidityWidget.tsx | 2 +- yarn.lock | 8 +++---- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 411a1acc70..284bb81103 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.6", + "kyberswap-liquidity-widgets": "^1.1.9", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 4c68967782..8a825c2468 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -24,9 +24,20 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ zindex: string | num const AnimatedDialogContent = motion(DialogContent) // destructure to not pass custom props to Dialog DOM element const StyledDialogContent = styled( - ({ borderRadius, minHeight, maxHeight, maxWidth, width, height, bgColor, mobile, isOpen, margin, ...rest }) => ( - - ), + ({ + borderRadius, + minHeight, + maxHeight, + maxWidth, + width, + height, + bgColor, + mobile, + isOpen, + margin, + mobileFullWidth, + ...rest + }) => , ).attrs({ 'aria-label': 'dialog', })` @@ -61,8 +72,13 @@ const StyledDialogContent = styled( width: ${width || '65vw'}; margin: 0; `} - ${({ theme, mobile, borderRadius }) => theme.mediaWidth.upToSmall` - width: 85vw; + ${({ theme, mobile, borderRadius, mobileFullWidth }) => theme.mediaWidth.upToSmall` + ${ + !mobileFullWidth && + ` + width: 85vw; + ` + } ${ mobile && ` @@ -95,6 +111,7 @@ export interface ModalProps { enableSwipeGesture?: boolean bypassScrollLock?: boolean bypassFocusLock?: boolean + mobileFullWidth?: boolean } export default function Modal({ @@ -118,6 +135,7 @@ export default function Modal({ enableSwipeGesture = false, bypassScrollLock = false, bypassFocusLock = false, + mobileFullWidth = false, }: ModalProps) { const animateValues = { initial: { opacity: 0 }, @@ -160,6 +178,7 @@ export default function Modal({ bgColor={bgColor} borderRadius={borderRadius} mobile={isMobile} + mobileFullWidth={mobileFullWidth} className={className} {...animateValues} > diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 9f42bf5bba..25e74bb39f 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -61,7 +61,7 @@ const useLiquidityWidget = () => { } const liquidityWidget = liquidityParams ? ( - + ) : null diff --git a/yarn.lock b/yarn.lock index bcc9f40187..a3b9658712 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.6.tgz#77d9d947d11e3cac1de764861a1340df285576b7" - integrity sha512-/fo4HhPBZ6JnWrzQgIJDxHI5gP8nDKLySSfV1isJnjp7UiZc+ccDkV09coAJ7t0BTCMSytzHW4g18CemZyDzDQ== +kyberswap-liquidity-widgets@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.9.tgz#5cb1e26468ed231b67a2b87549218a00b127519e" + integrity sha512-J7LghMNl555Xl4Wlsv87CKEQv5Ecjgm1b/5mbFSGfH0VzGZU/9jfO8ZG/82cM4ei/EsTtoa2mB9PzOiy/Dot3g== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From fab72663bb1074863f746d35eb2a4b1cb360cb0f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 21 Nov 2024 11:12:55 +0700 Subject: [PATCH 23/83] remove change network callback --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 284bb81103..bae2a49316 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.9", + "kyberswap-liquidity-widgets": "^1.1.10", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index a3b9658712..b3b2d378f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.9.tgz#5cb1e26468ed231b67a2b87549218a00b127519e" - integrity sha512-J7LghMNl555Xl4Wlsv87CKEQv5Ecjgm1b/5mbFSGfH0VzGZU/9jfO8ZG/82cM4ei/EsTtoa2mB9PzOiy/Dot3g== +kyberswap-liquidity-widgets@^1.1.10: + version "1.1.10" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.10.tgz#f51340c840dab206f42a2dde3662cfdc6a192cb5" + integrity sha512-r8c27XP4Wa9Kg9Np3aVGyMm/2pZjCcs7kuAF9HPwfmVD0FZuXN5ih1oP1vwVf/LfL9nUOwPbIoFn8Y4UYJw3LA== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 1f96feef93722c7ead227ff5235ddf7d78a2c910 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 21 Nov 2024 11:16:39 +0700 Subject: [PATCH 24/83] fix default chain filter in pool explorer --- src/pages/Earns/PoolExplorer/useFilter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index b603249b01..26733f01e6 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -9,11 +9,11 @@ import { timings } from '.' export default function useFilter() { const [searchParams, setSearchParams] = useSearchParams() - const { account } = useActiveWeb3React() + const { account, chainId } = useActiveWeb3React() const filters: QueryParams = useMemo(() => { return { - chainId: +(searchParams.get('chainId') || ChainId.MAINNET), + chainId: +(searchParams.get('chainId') || chainId || ChainId.MAINNET), page: +(searchParams.get('page') || 0), limit: 10, interval: searchParams.get('interval') || (timings[1].value as string), @@ -24,7 +24,7 @@ export default function useFilter() { orderBy: searchParams.get('orderBy') || '', q: searchParams.get('q')?.trim() || '', } - }, [searchParams, account]) + }, [searchParams, account, chainId]) const updateFilters = useCallback( (key: keyof QueryParams, value: string) => { From aa0e6955a083213bc1cff3e9c60537095e6da4f3 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 21 Nov 2024 14:11:18 +0700 Subject: [PATCH 25/83] fix click action for card on landing page --- src/pages/Earns/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 60d2d36ce1..0fce8a3595 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -196,7 +196,7 @@ const Card = ({ }) => { const theme = useTheme() return ( - + !action.disabled && action.onClick()}> @@ -209,11 +209,7 @@ const Card = ({ {desc} - !action.disabled && action.onClick()} - > + {action.text} From cfcd5204702ff65b9e764fe51e5e339bedab26a3 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 21 Nov 2024 15:08:58 +0700 Subject: [PATCH 26/83] turn pool address copied to lower case --- src/pages/Earns/PoolExplorer/TableContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index b485bfac1b..d62c703a62 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -184,7 +184,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} - + From 69e4fd6322ada71f557509b81ab2c4181941ce9f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 22 Nov 2024 12:10:40 +0700 Subject: [PATCH 27/83] fix earn tab on header and limit the displayed pools in landing page --- src/components/Header/index.tsx | 7 +++++-- src/pages/Earns/index.tsx | 13 +++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 46b88d13e0..e53bb9c0d5 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components' import Announcement from 'components/Announcement' import SelectNetwork from 'components/Header/web3/SelectNetwork' import SelectWallet from 'components/Header/web3/SelectWallet' -import Menu from 'components/Menu' +import Menu, { NewLabel } from 'components/Menu' import Row, { RowFixed } from 'components/Row' import { AGGREGATOR_ANALYTICS_URL, APP_PATHS } from 'constants/index' import { Z_INDEXS } from 'constants/styles' @@ -207,8 +207,11 @@ export default function Header() { {!isPartnerSwap && ( + + Earn + New + Market - Earn Analytics diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 0fce8a3595..a4485117ad 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -239,6 +239,11 @@ export default function Earns() { ) + const highlightedPools = (data?.data?.highlightedPools || []).slice(0, 9) + const highAprPool = (data?.data?.highAPR || []).slice(0, 5) + const lowVolatilityPool = (data?.data?.lowVolatility || []).slice(0, 5) + const solidEarningPool = (data?.data?.solidEarning || []).slice(0, 5) + return ( @@ -310,7 +315,7 @@ export default function Earns() { gap: '1rem', }} > - {data?.data?.highlightedPools.map(pool => ( + {highlightedPools.map(pool => ( ))} @@ -336,7 +341,7 @@ export default function Earns() { gap: '1rem', }} > - {data?.data?.highAPR.map(pool => ( + {highAprPool.map(pool => ( ))} @@ -361,7 +366,7 @@ export default function Earns() { gap: '1rem', }} > - {data?.data?.lowVolatility.map(pool => ( + {lowVolatilityPool.map(pool => ( ))} @@ -386,7 +391,7 @@ export default function Earns() { gap: '1rem', }} > - {data?.data?.solidEarning.map(pool => ( + {solidEarningPool.map(pool => ( ))} From 2ae590a5cd1a2eca7a475e9eefb266355ec198e7 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 22 Nov 2024 13:21:05 +0700 Subject: [PATCH 28/83] fix text color for error button --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index bae2a49316..08234d907e 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.10", + "kyberswap-liquidity-widgets": "^1.1.12", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index b3b2d378f7..134d361d1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.10: - version "1.1.10" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.10.tgz#f51340c840dab206f42a2dde3662cfdc6a192cb5" - integrity sha512-r8c27XP4Wa9Kg9Np3aVGyMm/2pZjCcs7kuAF9HPwfmVD0FZuXN5ih1oP1vwVf/LfL9nUOwPbIoFn8Y4UYJw3LA== +kyberswap-liquidity-widgets@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.12.tgz#daf373f38b5120dc49089abb36ba3d18f5aad957" + integrity sha512-XITd/X/OeBa3Tx1puJCfXgl22fdLNRpenn1vPjdzrunenalJY17VzbO+PiUGRPdhXZlOzCeWQ8BukPvxLfqXqw== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From abab980385edaf97219bb6edc4261fe88baf9583 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 22 Nov 2024 15:39:42 +0700 Subject: [PATCH 29/83] fix pool address displayed color --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 08234d907e..129799745b 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.12", + "kyberswap-liquidity-widgets": "^1.1.14", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 134d361d1c..1a03a6bd54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.12.tgz#daf373f38b5120dc49089abb36ba3d18f5aad957" - integrity sha512-XITd/X/OeBa3Tx1puJCfXgl22fdLNRpenn1vPjdzrunenalJY17VzbO+PiUGRPdhXZlOzCeWQ8BukPvxLfqXqw== +kyberswap-liquidity-widgets@^1.1.14: + version "1.1.14" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.14.tgz#1a0b6321132bb42919863140c0d82795579f8996" + integrity sha512-x6NrJHYAWbswi4U6t+ONRjqN2KcsKIBwQEutJHHSYSVABDtlWsEvCm6HhH0otFZ07HVIpfO/RcW9Ay7c2LYvWw== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From f0c717d4dbd1ce4797a9a6e6b59857689c666350 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 25 Nov 2024 13:58:53 +0700 Subject: [PATCH 30/83] update env endpoint and liquidity widget version with Tailwind --- .env | 2 +- .env.dev | 2 +- .env.production | 2 +- .env.stg | 2 +- package.json | 2 +- src/pages/Earns/useLiquidityWidget.tsx | 2 +- yarn.lock | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.env b/.env index f56ab031b2..51ee7d674b 100644 --- a/.env +++ b/.env @@ -47,4 +47,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api diff --git a/.env.dev b/.env.dev index 74ea22a571..bd95db92f7 100644 --- a/.env.dev +++ b/.env.dev @@ -48,4 +48,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api diff --git a/.env.production b/.env.production index 7bf1468f11..cf828e920f 100644 --- a/.env.production +++ b/.env.production @@ -47,4 +47,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api diff --git a/.env.stg b/.env.stg index 324cedd5db..b22e2f6f91 100644 --- a/.env.stg +++ b/.env.stg @@ -45,4 +45,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api diff --git a/package.json b/package.json index 129799745b..0eddd37081 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.14", + "kyberswap-liquidity-widgets": "^1.1.16", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 25e74bb39f..6d38229590 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -61,7 +61,7 @@ const useLiquidityWidget = () => { } const liquidityWidget = liquidityParams ? ( - + ) : null diff --git a/yarn.lock b/yarn.lock index 1a03a6bd54..a21a04acbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.14: - version "1.1.14" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.14.tgz#1a0b6321132bb42919863140c0d82795579f8996" - integrity sha512-x6NrJHYAWbswi4U6t+ONRjqN2KcsKIBwQEutJHHSYSVABDtlWsEvCm6HhH0otFZ07HVIpfO/RcW9Ay7c2LYvWw== +kyberswap-liquidity-widgets@^1.1.16: + version "1.1.16" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.16.tgz#ba9471a06c29140270ca9d15fd9b48f262799701" + integrity sha512-MqcIT/KW/gTHCq06n2iXS6E14Hfp583b0Ob402FJ+QP5vONXp5TIEPOiCXRHVIyFqCAdbYrpEH4JKDMGROMI4g== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From b6e6bb7c8a4a42d463af4233387a09c4d8c0d1dd Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 25 Nov 2024 14:08:47 +0700 Subject: [PATCH 31/83] update liquidity widget code from main --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0eddd37081..53ae415dac 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.16", + "kyberswap-liquidity-widgets": "^1.1.17", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index a21a04acbc..1171efb1e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.16: - version "1.1.16" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.16.tgz#ba9471a06c29140270ca9d15fd9b48f262799701" - integrity sha512-MqcIT/KW/gTHCq06n2iXS6E14Hfp583b0Ob402FJ+QP5vONXp5TIEPOiCXRHVIyFqCAdbYrpEH4JKDMGROMI4g== +kyberswap-liquidity-widgets@^1.1.17: + version "1.1.17" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.17.tgz#63778c525238b922345da8b25823cf9ec61f0b89" + integrity sha512-ci6NNNDkb+WvHpWOYrQoED+V5gQv8RZvqxOfZxrBtx1QRhtKppvRkAxX+tFeTbliYVzl1uBDXHCMwrSyXAbNcA== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 6207589c9bf6a2427b3d9effd980210596257120 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 25 Nov 2024 14:16:03 +0700 Subject: [PATCH 32/83] update wrong network message from liquidity widget --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 53ae415dac..ce46905b80 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.17", + "kyberswap-liquidity-widgets": "^1.1.18", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 1171efb1e3..3fd3968e32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.17: - version "1.1.17" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.17.tgz#63778c525238b922345da8b25823cf9ec61f0b89" - integrity sha512-ci6NNNDkb+WvHpWOYrQoED+V5gQv8RZvqxOfZxrBtx1QRhtKppvRkAxX+tFeTbliYVzl1uBDXHCMwrSyXAbNcA== +kyberswap-liquidity-widgets@^1.1.18: + version "1.1.18" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.18.tgz#7462e90e58f2344f75c8ea1313d8be6c27753371" + integrity sha512-uuLVZRN2/zBwaHsbS71GfzqU1Fs1YZSmqfU7eaS95UI0dBY+XYu3drX9EWi9gPggOnMrmBmkT86jmHbQZnTfHQ== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 70030683629e5a27487f6abb4e712b0459e10ebe Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 25 Nov 2024 14:35:57 +0700 Subject: [PATCH 33/83] update zap-earn endpoint from liquidity widget --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce46905b80..8551cac1df 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.18", + "kyberswap-liquidity-widgets": "^1.1.19", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 3fd3968e32..592bb72dfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.18: - version "1.1.18" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.18.tgz#7462e90e58f2344f75c8ea1313d8be6c27753371" - integrity sha512-uuLVZRN2/zBwaHsbS71GfzqU1Fs1YZSmqfU7eaS95UI0dBY+XYu3drX9EWi9gPggOnMrmBmkT86jmHbQZnTfHQ== +kyberswap-liquidity-widgets@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.19.tgz#49a73b90dda16a2d138594c714aeb8e87f235879" + integrity sha512-fezhGx2XuQfzdIG/STBThH06ZsyO6jKfmt7vAJ0Upqf8Z4KX9bPf9Cdyf6u4mS7ofz1S3DadhnLfe/PaDHq5Yg== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 85d04fdd617d6f9786742828f1d4a91ff88da39e Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 25 Nov 2024 15:07:25 +0700 Subject: [PATCH 34/83] format displayed price --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8551cac1df..575c54005d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.19", + "kyberswap-liquidity-widgets": "^1.1.20", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 592bb72dfa..4cf667cfaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.19: - version "1.1.19" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.19.tgz#49a73b90dda16a2d138594c714aeb8e87f235879" - integrity sha512-fezhGx2XuQfzdIG/STBThH06ZsyO6jKfmt7vAJ0Upqf8Z4KX9bPf9Cdyf6u4mS7ofz1S3DadhnLfe/PaDHq5Yg== +kyberswap-liquidity-widgets@^1.1.20: + version "1.1.20" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.20.tgz#f7e914e2853ac8fc6f1dec799c047737199fae5e" + integrity sha512-t0XFMVPTDdBCn8x+GfOfNZgP9CHmNc7KQ84gyxnrZnobIaDWBSUiNdd07hsrpOIhb4dU9f1DigxhlEIXdEcLbw== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 8b610610572578c0d66ed15b795384a99f9d2476 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 26 Nov 2024 12:11:26 +0700 Subject: [PATCH 35/83] show only chain that have pool data --- src/pages/Earns/PoolExplorer/index.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index d31c73101f..40f2e6cec1 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -81,12 +81,14 @@ const Earn = () => { const chains = useMemo( () => - supportedChains.map(chain => ({ - label: chain.name, - value: chain.chainId, - icon: chain.icon, - })), - [supportedChains], + supportedChains + .map(chain => ({ + label: chain.name, + value: chain.chainId, + icon: chain.icon, + })) + .filter(chain => supportedProtocolsData?.data?.chains?.[chain.value]), + [supportedChains, supportedProtocolsData], ) const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) From 026a0931e03fb964f5266b631f3a780c0e5a1977 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 26 Nov 2024 14:15:49 +0700 Subject: [PATCH 36/83] make dropdown content highlight by increasing brightness & re-format dex name --- src/pages/Earns/PoolExplorer/DropdownMenu.tsx | 3 ++- src/pages/Earns/PoolExplorer/index.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/DropdownMenu.tsx b/src/pages/Earns/PoolExplorer/DropdownMenu.tsx index e87aa76200..83361bdde6 100644 --- a/src/pages/Earns/PoolExplorer/DropdownMenu.tsx +++ b/src/pages/Earns/PoolExplorer/DropdownMenu.tsx @@ -48,7 +48,7 @@ const ItemIcon = styled.img` const DropdownContent = styled.div<{ alignLeft: boolean }>` position: absolute; - top: 48px; + top: 42px; left: 0; background: ${({ theme }) => theme.background}; border-radius: 24px; @@ -63,6 +63,7 @@ const DropdownContent = styled.div<{ alignLeft: boolean }>` max-height: 218px; overflow-y: auto; z-index: 100; + filter: brightness(1.2); ` const DropdownContentItem = styled.div` diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 40f2e6cec1..39a25c0407 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -73,7 +73,7 @@ const Earn = () => { if (!supportedProtocolsData?.data?.chains) return [] const parsedProtocols = supportedProtocolsData.data.chains[filters.chainId]?.protocols?.map(item => ({ - label: dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name, + label: (dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name).replaceAll('-', ' '), value: item.id, })) || [] return [{ label: 'All Protocols', value: '' }].concat(parsedProtocols) From 3310c01447d86087fb25e82d5bfdfc9dde8ff4c6 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 26 Nov 2024 14:25:21 +0700 Subject: [PATCH 37/83] fix dropdown width --- src/pages/Earns/PoolExplorer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 39a25c0407..9269191af4 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -168,7 +168,7 @@ const Earn = () => { Date: Tue, 26 Nov 2024 15:17:23 +0700 Subject: [PATCH 38/83] add tooltip for filter tab & add disclaimer --- package.json | 2 +- src/pages/Earns/PoolExplorer/index.tsx | 72 +++++++++++++++++++------ src/pages/Earns/PoolExplorer/styles.tsx | 5 ++ yarn.lock | 8 +-- 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 575c54005d..b621a0fd50 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.20", + "kyberswap-liquidity-widgets": "^1.1.21", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 9269191af4..22b90effc8 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -13,6 +13,7 @@ import { ReactComponent as IconLowVolatility } from 'assets/svg/ic_pool_low_vola import { ReactComponent as IconSolidEarningPool } from 'assets/svg/ic_pool_solid_earning.svg' import Pagination from 'components/Pagination' import Search from 'components/Search' +import { MouseoverTooltip } from 'components/Tooltip' import { NETWORKS_INFO } from 'constants/networks' import useChainsConfig from 'hooks/useChainsConfig' import useDebounce from 'hooks/useDebounce' @@ -45,10 +46,26 @@ const filterTags = [ label: 'Highlighted Pools', value: FilterTag.HIGHLIGHTED_POOL, icon: , + tooltip: '', + }, + { + label: 'High APR', + value: FilterTag.HIGH_APR, + icon: , + tooltip: 'Top 100 Pools with assets that offer exceptionally high APYs', + }, + { + label: 'Solid Earning', + value: FilterTag.SOLID_EARNING, + icon: , + tooltip: 'Top 100 pools that have the high total earned fee in the last 7 days', + }, + { + label: 'Low Volatility', + value: FilterTag.LOW_VOLATILITY, + icon: , + tooltip: 'Top 100 highest TVL Pools consisting of stable coins or correlated pairs', }, - { label: 'High APR', value: FilterTag.HIGH_APR, icon: }, - { label: 'Solid Earning', value: FilterTag.SOLID_EARNING, icon: }, - { label: 'Low Volatility', value: FilterTag.LOW_VOLATILITY, icon: }, ] export const timings: MenuOption[] = [ @@ -149,20 +166,36 @@ const Earn = () => { updateFilters('tag', '')}> {t`All pools`} - updateFilters('tag', 'favorite')}> - - - {filterTags.map(item => ( - updateFilters('tag', item.value)} - > - {!upToExtraSmall && item.icon} - {item.label} + + updateFilters('tag', 'favorite')}> + - ))} + + {filterTags.map((item, index) => + item.tooltip ? ( + + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + + ) : ( + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + ), + )} @@ -238,6 +271,13 @@ const Earn = () => { pageSize={filters.limit || 10} /> + + {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} ) } diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index 1e58502f6a..5385e8ce16 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -48,6 +48,11 @@ export const Tag = styled.div<{ active: boolean }>` gap: 8px; flex: 0 0 auto; line-height: 28px; + height: 42px; + + ${({ theme }) => theme.mediaWidth.upToMedium` + height: 38px; + `} ` export const TableWrapper = styled.div` diff --git a/yarn.lock b/yarn.lock index 4cf667cfaf..f928443bda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.20: - version "1.1.20" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.20.tgz#f7e914e2853ac8fc6f1dec799c047737199fae5e" - integrity sha512-t0XFMVPTDdBCn8x+GfOfNZgP9CHmNc7KQ84gyxnrZnobIaDWBSUiNdd07hsrpOIhb4dU9f1DigxhlEIXdEcLbw== +kyberswap-liquidity-widgets@^1.1.21: + version "1.1.21" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.21.tgz#97918876523a49cf034bbbfa4d4c6e315ea8eee6" + integrity sha512-o1tX9ZOoNm6UymZMXNq7zXL8tpuI+VG1mdRakEO2wB+qdn5lTktcvclCgirmZ+Mcb5jGPzJh6LSAkCgCk4UXsQ== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 399370628e44ba59ed24410a13103be798de0bc2 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 26 Nov 2024 15:27:18 +0700 Subject: [PATCH 39/83] fix bug change network but protocol still keep --- src/pages/Earns/PoolExplorer/useFilter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index 26733f01e6..bfde86366c 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -29,7 +29,10 @@ export default function useFilter() { const updateFilters = useCallback( (key: keyof QueryParams, value: string) => { if (!value) searchParams.delete(key) - else searchParams.set(key, value) + else { + searchParams.set(key, value) + if (key === 'chainId') searchParams.delete('protocol') + } if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') searchParams.delete('page') setSearchParams(searchParams) }, From 59f3733153bca1e034f2c559337b409c83926781 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 26 Nov 2024 15:42:10 +0700 Subject: [PATCH 40/83] fix wrong network message --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b621a0fd50..7e45bd3e64 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.21", + "kyberswap-liquidity-widgets": "^1.1.22", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index f928443bda..2039e557f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.21: - version "1.1.21" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.21.tgz#97918876523a49cf034bbbfa4d4c6e315ea8eee6" - integrity sha512-o1tX9ZOoNm6UymZMXNq7zXL8tpuI+VG1mdRakEO2wB+qdn5lTktcvclCgirmZ+Mcb5jGPzJh6LSAkCgCk4UXsQ== +kyberswap-liquidity-widgets@^1.1.22: + version "1.1.22" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.22.tgz#a6b9b39f11c20c9e882885307201072ff688b24d" + integrity sha512-MS4VnGyHoQHvki51eOri8zygWCuwH+yK8G5ChlGxl11oBr4ePAZBFgCWSHSuiCRNFVQuSNeNA27XIwTPCJcoog== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From f5b212a03ecfdc0e225e2635982f0143e81f3c03 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 26 Nov 2024 15:56:14 +0700 Subject: [PATCH 41/83] add loading to landing page --- src/pages/Earns/index.tsx | 107 ++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index a4485117ad..de37e3071a 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -16,6 +16,7 @@ import RocketIcon from 'assets/svg/rocket.svg' import SolidEarningIcon from 'assets/svg/solid-earning.svg' import StakingIcon from 'assets/svg/staking.svg' import { ButtonPrimary } from 'components/Button' +import LocalLoader from 'components/LocalLoader' import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'hooks/useChainsConfig' import useTheme from 'hooks/useTheme' @@ -220,7 +221,7 @@ const Card = ({ export default function Earns() { const navigate = useNavigate() const theme = useTheme() - const { data } = useExplorerLandingQuery() + const { isLoading, data } = useExplorerLandingQuery() const title = (_title: string, icon: string) => ( <> @@ -308,17 +309,21 @@ export default function Earns() { }} > {title('Highlighted Pools', FireIcon)} - - {highlightedPools.map(pool => ( - - ))} - + {isLoading ? ( + + ) : ( + + {highlightedPools.map(pool => ( + + ))} + + )} @@ -334,17 +339,21 @@ export default function Earns() { }} > {title('High APR', RocketIcon)} - - {highAprPool.map(pool => ( - - ))} - + {isLoading ? ( + + ) : ( + + {highAprPool.map(pool => ( + + ))} + + )} @@ -359,17 +368,21 @@ export default function Earns() { }} > {title('Low Volatility', LowVolatilityIcon)} - - {lowVolatilityPool.map(pool => ( - - ))} - + {isLoading ? ( + + ) : ( + + {lowVolatilityPool.map(pool => ( + + ))} + + )} @@ -384,17 +397,21 @@ export default function Earns() { }} > {title('Solid Earning', SolidEarningIcon)} - - {solidEarningPool.map(pool => ( - - ))} - + {isLoading ? ( + + ) : ( + + {solidEarningPool.map(pool => ( + + ))} + + )} From 343012aefcb670ebcbd7cbb86fc9f5b6d3609a22 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 27 Nov 2024 11:48:41 +0700 Subject: [PATCH 42/83] add token pair to list all tokens if don't have --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7e45bd3e64..01f952afd0 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.22", + "kyberswap-liquidity-widgets": "^1.1.23", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 2039e557f4..4d80e242f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.22: - version "1.1.22" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.22.tgz#a6b9b39f11c20c9e882885307201072ff688b24d" - integrity sha512-MS4VnGyHoQHvki51eOri8zygWCuwH+yK8G5ChlGxl11oBr4ePAZBFgCWSHSuiCRNFVQuSNeNA27XIwTPCJcoog== +kyberswap-liquidity-widgets@^1.1.23: + version "1.1.23" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.23.tgz#928a2527518b78aecea10973cc5c15d687e98291" + integrity sha512-FNQBjD0rWq9HeUOEYrqJWt7iswqu3UqOogT+AmEC6BN8ghU6J2vUJ+XamwDc++47ePwNztD8aFfsS2+LFkGwWA== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 7c6504a87609accbc668565f8336f2ebb6e2d07b Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 27 Nov 2024 12:07:52 +0700 Subject: [PATCH 43/83] fix issue q from url query string disappear on reload --- src/pages/Earns/PoolExplorer/index.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 22b90effc8..0d28baf702 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -2,6 +2,7 @@ import { t } from '@lingui/macro' import 'kyberswap-liquidity-widgets/dist/style.css' import { useEffect, useMemo, useState } from 'react' import { Star } from 'react-feather' +import { useSearchParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { useGetDexListQuery } from 'services/ksSetting' @@ -75,6 +76,7 @@ export const timings: MenuOption[] = [ ] const Earn = () => { + const [searchParams] = useSearchParams() const theme = useTheme() const { supportedChains } = useChainsConfig() const { filters, updateFilters } = useFilter() @@ -144,6 +146,13 @@ const Earn = () => { updateFilters('orderBy', '') } + useEffect(() => { + if (searchParams.get('q') && !search) { + setSearch(searchParams.get('q') || '') + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + useEffect(() => { if (filters.q !== deboundedSearch) { updateFilters('q', deboundedSearch || '') From a42b4222e923a395f941cd28330c2b1fc48d6cb0 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 27 Nov 2024 16:16:18 +0700 Subject: [PATCH 44/83] integrate protocols sushiswapv3 & thrusterv3, fix issue widget doesn't get route when wrong network, fix placeholder in search input to remove token name --- package.json | 2 +- src/pages/Earns/PoolExplorer/index.tsx | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 01f952afd0..35d6e83559 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.23", + "kyberswap-liquidity-widgets": "^1.1.24", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 0d28baf702..764ddfa22f 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -219,7 +219,7 @@ const Earn = () => { setSearch(val)} diff --git a/yarn.lock b/yarn.lock index 4d80e242f2..56e4929b92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.23: - version "1.1.23" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.23.tgz#928a2527518b78aecea10973cc5c15d687e98291" - integrity sha512-FNQBjD0rWq9HeUOEYrqJWt7iswqu3UqOogT+AmEC6BN8ghU6J2vUJ+XamwDc++47ePwNztD8aFfsS2+LFkGwWA== +kyberswap-liquidity-widgets@^1.1.24: + version "1.1.24" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.24.tgz#9d9584b2a59e1c32cf2559cd13129669b6aa1dc6" + integrity sha512-skeTRQ6CJidNf+n/MFdHUoMTvc8Nm5VUZK+LjdbWMcUErxgBMObAC9Y0Anmti+ECsShui4+2C8nKM6NZhNYg1Q== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 7eee2ea2a1ce1d3ea52087364b73f65e013d37d0 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 28 Nov 2024 13:57:57 +0700 Subject: [PATCH 45/83] add delay for toggle favorite --- src/pages/Earns/PoolExplorer/TableContent.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index d62c703a62..e64b91e873 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Star } from 'react-feather' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' @@ -47,6 +47,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const [removeFavorite] = useRemoveFavoriteMutation() const [favoriteLoading, setFavoriteLoading] = useState([]) + const [delayFavorite, setDelayFavorite] = useState(false) const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) @@ -80,7 +81,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const handleFavorite = async (e: React.MouseEvent, pool: EarnPool) => { e.stopPropagation() - if (favoriteLoading.includes(pool.address)) return + if (favoriteLoading.includes(pool.address) || delayFavorite) return handleAddFavoriteLoading(pool.address) if (!account) { @@ -122,6 +123,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo } const isPoolFavorite = !!pool.favorite?.isFavorite + setDelayFavorite(true) await (isPoolFavorite ? removeFavorite : addFavorite)({ chainId: filters.chainId, userAddress: account, @@ -161,6 +163,13 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const handleRemoveFavoriteLoading = (poolAddress: string) => setFavoriteLoading(favoriteLoading.filter(address => address !== poolAddress)) + useEffect(() => { + if (delayFavorite) + setTimeout(() => { + setDelayFavorite(false) + }, 500) + }, [delayFavorite]) + if (!tablePoolData?.length) return ( From c8d515c921d2255ee16e82785cbcb2895e6795ef Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 29 Nov 2024 14:17:17 +0700 Subject: [PATCH 46/83] reset search after change filter --- src/pages/Earns/PoolExplorer/useFilter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index bfde86366c..69724f7a57 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -33,7 +33,10 @@ export default function useFilter() { searchParams.set(key, value) if (key === 'chainId') searchParams.delete('protocol') } - if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') searchParams.delete('page') + if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') { + searchParams.delete('page') + searchParams.delete('q') + } setSearchParams(searchParams) }, [setSearchParams, searchParams], From 6c2c9d075e5a6891fd91c0778bebd016a59e9e4a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 29 Nov 2024 15:02:31 +0700 Subject: [PATCH 47/83] reset search after change filter --- src/pages/Earns/PoolExplorer/index.tsx | 7 +++---- src/pages/Earns/PoolExplorer/useFilter.ts | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 764ddfa22f..cdb8afa9fc 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -76,10 +76,12 @@ export const timings: MenuOption[] = [ ] const Earn = () => { + const [search, setSearch] = useState('') + const deboundedSearch = useDebounce(search, 300) const [searchParams] = useSearchParams() const theme = useTheme() const { supportedChains } = useChainsConfig() - const { filters, updateFilters } = useFilter() + const { filters, updateFilters } = useFilter(setSearch) const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() const dexList = useGetDexListQuery({ @@ -113,9 +115,6 @@ const Earn = () => { const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) - const [search, setSearch] = useState('') - const deboundedSearch = useDebounce(search, 300) - const totalPools = useMemo(() => { const totalItems = poolData?.data?.pagination?.totalItems || 0 if (!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) return totalItems diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index 69724f7a57..a367a96e7c 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -7,7 +7,7 @@ import { useActiveWeb3React } from 'hooks' import { timings } from '.' -export default function useFilter() { +export default function useFilter(setSearch?: (search: string) => void) { const [searchParams, setSearchParams] = useSearchParams() const { account, chainId } = useActiveWeb3React() @@ -35,11 +35,11 @@ export default function useFilter() { } if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') { searchParams.delete('page') - searchParams.delete('q') + if (key !== 'q' && setSearch) setSearch('') } setSearchParams(searchParams) }, - [setSearchParams, searchParams], + [setSearchParams, searchParams, setSearch], ) return { From cdb937849d86e2ea2ac428e794b9abcaef1b7354 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 29 Nov 2024 15:18:58 +0700 Subject: [PATCH 48/83] fix dropdown ui --- src/pages/Earns/PoolExplorer/DropdownMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/DropdownMenu.tsx b/src/pages/Earns/PoolExplorer/DropdownMenu.tsx index 83361bdde6..a02809dc4a 100644 --- a/src/pages/Earns/PoolExplorer/DropdownMenu.tsx +++ b/src/pages/Earns/PoolExplorer/DropdownMenu.tsx @@ -24,7 +24,7 @@ const DropdownTitleWrapper = styled.div` const DropdownTitle = styled.div<{ width?: number }>` width: ${({ width }) => (width ? `${width}px` : '')}; - min-width: ${({ width }) => (!width ? '100px' : '')}; + min-width: ${({ width }) => (!width ? '100px' : 'max-content')}; display: flex; align-items: center; justify-content: center; @@ -32,7 +32,7 @@ const DropdownTitle = styled.div<{ width?: number }>` text-transform: capitalize; ${({ theme }) => theme.mediaWidth.upToExtraSmall` - min-width: unset; + min-width: max-content; `} ` @@ -55,7 +55,7 @@ const DropdownContent = styled.div<{ alignLeft: boolean }>` padding: 8px 12px; font-size: 14px; color: ${({ theme }) => theme.text}; - width: fit-content; + width: max-content; display: flex; flex-direction: column; align-items: ${({ alignLeft }) => (alignLeft ? 'flex-start' : 'center')}; From b9131d13d43b7b35bfcb086223baa8e59ccb255f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 3 Dec 2024 09:34:43 +0700 Subject: [PATCH 49/83] add zap migration widget --- package.json | 2 +- src/pages/Earns/useLiquidityWidget.tsx | 5 +---- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ef32fae40f..8d154868f1 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.24", + "kyberswap-liquidity-widgets": "^1.1.25", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 6d38229590..12690df916 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -6,7 +6,7 @@ import { NotificationType } from 'components/Announcement/type' import Modal from 'components/Modal' import { NETWORKS_INFO } from 'constants/networks' import { useWeb3React } from 'hooks' -import { useNetworkModalToggle, useNotify, useWalletModalToggle } from 'state/application/hooks' +import { useNotify, useWalletModalToggle } from 'state/application/hooks' import useFilter from './PoolExplorer/useFilter' @@ -18,13 +18,11 @@ interface LiquidityParams { poolType: PoolType onDismiss: () => void onConnectWallet: () => void - onChangeNetwork: () => void } const useLiquidityWidget = () => { const { library } = useWeb3React() const toggleWalletModal = useWalletModalToggle() - const toggleNetworkModal = useNetworkModalToggle() const notify = useNotify() const { filters } = useFilter() @@ -56,7 +54,6 @@ const useLiquidityWidget = () => { poolType: PoolType[`DEX_${dex.toUpperCase()}V3` as keyof typeof PoolType], onDismiss: handleCloseZapInWidget, onConnectWallet: toggleWalletModal, - onChangeNetwork: toggleNetworkModal, }) } diff --git a/yarn.lock b/yarn.lock index 56e4929b92..1778019f03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14523,10 +14523,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.24: - version "1.1.24" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.24.tgz#9d9584b2a59e1c32cf2559cd13129669b6aa1dc6" - integrity sha512-skeTRQ6CJidNf+n/MFdHUoMTvc8Nm5VUZK+LjdbWMcUErxgBMObAC9Y0Anmti+ECsShui4+2C8nKM6NZhNYg1Q== +kyberswap-liquidity-widgets@^1.1.25: + version "1.1.25" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.25.tgz#c5a0e6b7b414303fbbde4d983ca940a8e66ba8f7" + integrity sha512-vrbklGiWPrJYp2StSHqHqxdaVfKPy5UikW0JGfxu8mlk6mxjldEngcvRstdh2baC8SQzWg2pxPNjHgdT3+gr2g== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 09af36b4ee72801e4d6b06febd9d88384563dcc7 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 3 Dec 2024 11:45:12 +0700 Subject: [PATCH 50/83] update yarn.lock --- yarn.lock | 70 ------------------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/yarn.lock b/yarn.lock index c704aedcc0..3ad45a6c92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4212,11 +4212,6 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/ed25519@^1.7.0": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724" - integrity sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw== - "@noble/hashes@1.3.2", "@noble/hashes@^1", "@noble/hashes@^1.0.0", "@noble/hashes@~1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" @@ -4464,66 +4459,6 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@project-serum/anchor@^0.11.1": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.11.1.tgz#155bff2c70652eafdcfd5559c81a83bb19cec9ff" - integrity sha512-oIdm4vTJkUy6GmE6JgqDAuQPKI7XM4TPJkjtoIzp69RZe0iAD9JP2XHx7lV1jLdYXeYHqDXfBt3zcq7W91K6PA== - dependencies: - "@project-serum/borsh" "^0.2.2" - "@solana/web3.js" "^1.17.0" - base64-js "^1.5.1" - bn.js "^5.1.2" - bs58 "^4.0.1" - buffer-layout "^1.2.0" - camelcase "^5.3.1" - crypto-hash "^1.3.0" - eventemitter3 "^4.0.7" - find "^0.3.0" - js-sha256 "^0.9.0" - pako "^2.0.3" - snake-case "^3.0.4" - toml "^3.0.0" - -"@project-serum/anchor@^0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.25.0.tgz#88ee4843336005cf5a64c80636ce626f0996f503" - integrity sha512-E6A5Y/ijqpfMJ5psJvbw0kVTzLZFUcOFgs6eSM2M2iWE1lVRF18T6hWZVNl6zqZsoz98jgnNHtVGJMs+ds9A7A== - dependencies: - "@project-serum/borsh" "^0.2.5" - "@solana/web3.js" "^1.36.0" - base64-js "^1.5.1" - bn.js "^5.1.2" - bs58 "^4.0.1" - buffer-layout "^1.2.2" - camelcase "^5.3.1" - cross-fetch "^3.1.5" - crypto-hash "^1.3.0" - eventemitter3 "^4.0.7" - js-sha256 "^0.9.0" - pako "^2.0.3" - snake-case "^3.0.4" - superstruct "^0.15.4" - toml "^3.0.0" - -"@project-serum/borsh@^0.2.2", "@project-serum/borsh@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663" - integrity sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q== - dependencies: - bn.js "^5.1.2" - buffer-layout "^1.2.0" - -"@project-serum/serum@^0.13.65": - version "0.13.65" - resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.65.tgz#6d3cf07912f13985765237f053cca716fe84b0b0" - integrity sha512-BHRqsTqPSfFB5p+MgI2pjvMBAQtO8ibTK2fYY96boIFkCI3TTwXDt2gUmspeChKO2pqHr5aKevmexzAcXxrSRA== - dependencies: - "@project-serum/anchor" "^0.11.1" - "@solana/spl-token" "^0.1.6" - "@solana/web3.js" "^1.21.0" - bn.js "^5.1.2" - buffer-layout "^1.2.0" - "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -10997,11 +10932,6 @@ dotenv-parse-variables@^2.0.0: debug "^4.3.1" is-string-and-not-blank "^0.0.2" -dotenv@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== - dotenv@^14.2.0: version "14.3.2" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369" From 7b223bdf52512af11ad827a4f960aec48f8df2f3 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 3 Dec 2024 15:56:26 +0700 Subject: [PATCH 51/83] fix migration widget ui --- package.json | 2 +- src/pages/Earns/index.tsx | 8 ++++---- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6a374e9501..5f4473380b 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.25", + "kyberswap-liquidity-widgets": "^1.1.27", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index de37e3071a..08e190dd04 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -463,9 +463,9 @@ const PoolItem = ({ pool }: { pool: EarnPool }) => { > {liquidityWidget} - + { textOverflow: 'ellipsis', }} > - {pool.tokens[0].symbol} /{' '} + {pool.tokens?.[0].symbol} /{' '} - {pool.tokens[1].symbol} + {pool.tokens?.[1].symbol} {pool.feeTier}% diff --git a/yarn.lock b/yarn.lock index 3ad45a6c92..17e3288542 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.25: - version "1.1.25" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.25.tgz#c5a0e6b7b414303fbbde4d983ca940a8e66ba8f7" - integrity sha512-vrbklGiWPrJYp2StSHqHqxdaVfKPy5UikW0JGfxu8mlk6mxjldEngcvRstdh2baC8SQzWg2pxPNjHgdT3+gr2g== +kyberswap-liquidity-widgets@^1.1.27: + version "1.1.27" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.27.tgz#9b3ade1ba580b72b8a480689196ef3ecce61e41c" + integrity sha512-aJw3y/iFSIAnJJojdvN1r3Ju41QPOobbyj9Ihx2VzD1ZSqtNAgIrqzTby7/AeiC+cMptdaZrlBeN2NI+T2Q1jw== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From edebf8ab66c93d60cdf9303a5785d1626945d69c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 5 Dec 2024 13:24:00 +0700 Subject: [PATCH 52/83] my liquidity page --- src/constants/index.ts | 3 +- src/pages/App.tsx | 2 + src/pages/Earns/MyPositions/index.tsx | 132 +++++++++++++ src/pages/Earns/MyPositions/styles.tsx | 184 ++++++++++++++++++ src/pages/Earns/PoolExplorer/TableContent.tsx | 8 +- src/pages/Earns/PoolExplorer/index.tsx | 6 +- src/pages/Earns/PoolExplorer/styles.tsx | 11 +- src/pages/Earns/index.tsx | 10 +- 8 files changed, 336 insertions(+), 20 deletions(-) create mode 100644 src/pages/Earns/MyPositions/index.tsx create mode 100644 src/pages/Earns/MyPositions/styles.tsx diff --git a/src/constants/index.ts b/src/constants/index.ts index 3c9848e0a5..7252d82ba7 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -171,7 +171,8 @@ export const APP_PATHS = { MY_DASHBOARD: '/campaigns/dashboard', EARN: '/earns', - EARN_POOLS: '/earn/pools', + EARN_POOLS: '/earns/pools', + EARN_MY_POSITIONS: '/earns/my-positions', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/App.tsx b/src/pages/App.tsx index d6a2e8a0b1..9813729c52 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -64,6 +64,7 @@ const CampaignMyDashboard = lazy(() => import('pages/Campaign/MyDashboard')) const Earns = lazy(() => import('pages/Earns')) const EarnPoolExplorer = lazy(() => import('pages/Earns/PoolExplorer')) +const EarnMyPositions = lazy(() => import('pages/Earns/MyPositions')) const AppWrapper = styled.div` display: flex; @@ -322,6 +323,7 @@ export default function App() { } /> } /> + } /> } /> diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx new file mode 100644 index 0000000000..9cf9813754 --- /dev/null +++ b/src/pages/Earns/MyPositions/index.tsx @@ -0,0 +1,132 @@ +import { t } from '@lingui/macro' +import { Minus, Plus } from 'react-feather' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' + +import CopyHelper from 'components/Copy' +import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' + +import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' +import { + Badge, + BadgeType, + ChainImage, + DexImage, + Divider, + ImageContainer, + MyLiquidityWrapper, + PositionAction, + PositionOverview, + PositionPageWrapper, + PositionRow, + PositionValueLabel, + PositionValueWrapper, +} from './styles' + +const MyPositions = () => { + const theme = useTheme() + const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + return ( + +
+ + {t`My Liquidity`} + + + {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + +
+ + + + + + + + + + + KNC/WETH + 0.05% + ● In range + + + + + + V3 + + + + #24654 + + + 0x1234...abcd + + + + + {upToLarge && !upToSmall && ( + + + + + + + + + )} + + Value: + $2,876 + + + Earn: + $1,76 + + + Bal: + + 81,265.87 KNC + {upToSmall && } + 28.76 WETH + + + {(upToSmall || !upToLarge) && ( + + + + + + + + + )} + + + + {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} +
+ ) +} + +export default MyPositions diff --git a/src/pages/Earns/MyPositions/styles.tsx b/src/pages/Earns/MyPositions/styles.tsx new file mode 100644 index 0000000000..9bdb95858e --- /dev/null +++ b/src/pages/Earns/MyPositions/styles.tsx @@ -0,0 +1,184 @@ +import { rgba } from 'polished' +import styled from 'styled-components' + +import { PoolPageWrapper } from '../PoolExplorer/styles' + +export const PositionPageWrapper = styled(PoolPageWrapper)` + padding: 24px 6rem 50px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 24px 16px 100px; + `} +` + +export const MyLiquidityWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + max-height: 539px; + overflow-y: auto; + + ${({ theme }) => theme.mediaWidth.upToSmall` + max-height: unset; + `} +` + +export const PositionRow = styled.div` + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr 75px; + grid-template-rows: 1fr; + background-color: ${({ theme }) => rgba(theme.background, 0.8)}; + border-radius: 20px; + padding: 16px 28px; + row-gap: 8px; + + ${({ theme }) => theme.mediaWidth.upToLarge` + justify-content: flex-start; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 1fr 1fr; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + display: flex; + flex-direction: column; + row-gap: 16px; + padding: 16px; + `} + + &:hover { + cursor: pointer; + filter: brightness(1.1); + } +` + +export const PositionOverview = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + + ${({ theme }) => theme.mediaWidth.upToLarge` + grid-column: span 2; + `} +` + +export const ImageContainer = styled.div` + position: relative; + top: 2px; +` + +export const ChainImage = styled.img` + width: 12px; + height: 12px; + border-radius: 50%; + position: relative; + left: -14px; + top: 4px; +` + +export const DexImage = styled.img` + width: 16px; + height: 16px; +` + +export enum BadgeType { + PRIMARY = 'primary', + WARNING = 'warning', + SECONDARY = 'secondary', +} + +export const Badge = styled.div<{ type?: BadgeType }>` + border-radius: 30px; + padding: 4px 12px; + background-color: ${({ theme }) => rgba(theme.white, 0.04)}; + color: ${({ theme }) => theme.subText}; + font-size: 12px; + display: flex; + align-items: center; + + ${({ type, theme }) => { + switch (type) { + case BadgeType.PRIMARY: + return ` + background-color: ${rgba(theme.primary, 0.2)}; + color: ${theme.primary}; + ` + case BadgeType.WARNING: + return ` + background-color: ${rgba(theme.warning, 0.2)}; + color: ${theme.warning}; + ` + case BadgeType.SECONDARY: + return ` + color: #2C9CE4; + ` + default: + return '' + } + }} + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + padding: 4px 9px; + `} +` + +export const PositionValueWrapper = styled.div<{ align?: string }>` + display: flex; + align-items: flex-start; + justify-content: flex-start; + gap: 8px; + padding-top: 8px; + + ${({ align }) => (align ? `justify-content: ${align};` : '')} + + ${({ theme }) => theme.mediaWidth.upToSmall` + justify-content: space-between; + padding-top: 0; + `} +` + +export const PositionValueLabel = styled.p` + font-size: 14px; + margin: 0; + color: ${({ theme }) => theme.subText}; + position: relative; + top: 1px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + font-size: 16px; + top: 0; + `} +` + +export const PositionAction = styled.div<{ primary?: boolean }>` + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 12px; + background-color: ${({ theme, primary }) => (primary ? rgba(theme.primary, 0.2) : theme.tabActive)}; + color: ${({ theme, primary }) => (primary ? theme.primary : theme.subText)}; + + &:hover { + cursor: pointer; + filter: brightness(1.2); + } + + &:active { + filter: brightness(1.05); + } + + ${({ theme }) => theme.mediaWidth.upToSmall` + width: 36px; + height: 36px; + `} +` + +export const Divider = styled.div` + height: 16px; + width: 1px; + background: ${({ theme }) => theme.tabActive}; + margin: 0 14px; + position: relative; + top: 1px; +` diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index e64b91e873..bac720d05c 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -185,8 +185,8 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo - - + + @@ -242,8 +242,8 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo - - + + {pool.tokens?.[0]?.symbol}/{pool.tokens?.[1]?.symbol} diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index cdb8afa9fc..192b0d6a59 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -25,7 +25,7 @@ import { MEDIA_WIDTHS } from 'theme' import useLiquidityWidget from '../useLiquidityWidget' import DropdownMenu, { MenuOption } from './DropdownMenu' import TableContent from './TableContent' -import { ContentWrapper, PoolsExplorerWrapper, TableHeader, TableWrapper, Tag, TagContainer } from './styles' +import { ContentWrapper, PoolPageWrapper, TableHeader, TableWrapper, Tag, TagContainer } from './styles' import useFilter from './useFilter' export enum FilterTag { @@ -159,7 +159,7 @@ const Earn = () => { }, [deboundedSearch, filters.q, updateFilters]) return ( - + {liquidityWidget}
@@ -286,7 +286,7 @@ const Earn = () => { textAlign={'center'} fontStyle={'italic'} >{t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} - + ) } diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index 5385e8ce16..cc6926321f 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -3,18 +3,17 @@ import styled from 'styled-components' import { Image } from 'components/Image' -export const PoolsExplorerWrapper = styled.div` +export const PoolPageWrapper = styled.div` padding: 32px 24px 50px; width: 100%; max-width: 1500px; + display: flex; + flex-direction: column; + gap: 16px; ${({ theme }) => theme.mediaWidth.upToSmall` padding: 24px 16px 100px; `} - - display: flex; - flex-direction: column; - gap: 16px; ` export const LiquidityWidgetWrapper = styled.div` @@ -118,6 +117,8 @@ export const FeeTier = styled.div` export const CurrencyRoundedImage = styled(Image)` border-radius: 50%; + width: 24px; + height: 24px; ` export const CurrencySecondImage = styled(CurrencyRoundedImage)` diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 08e190dd04..41f52815cb 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -270,9 +270,7 @@ export default function Earns() { desc="Explore and instantly add liquidity to high-APY pools the easy way with Zap Technology." action={{ text: 'View Pools', - onClick: () => { - navigate({ pathname: APP_PATHS.EARN_POOLS }) - }, + onClick: () => navigate({ pathname: APP_PATHS.EARN_POOLS }), }} /> {}, - disabled: true, + text: 'Your Pools', + onClick: () => navigate({ pathname: APP_PATHS.EARN_MY_POSITIONS }), }} /> Date: Thu, 5 Dec 2024 21:30:49 +0700 Subject: [PATCH 53/83] switch to pre-release endpoint --- .env | 2 +- .env.dev | 2 +- .env.production | 2 +- .env.stg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.env b/.env index ca38e603ee..86f7fed140 100644 --- a/.env +++ b/.env @@ -39,4 +39,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.dev b/.env.dev index 2c0f3f967f..a4b261fd98 100644 --- a/.env.dev +++ b/.env.dev @@ -40,4 +40,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.production b/.env.production index f100f0f62b..f73fef8b46 100644 --- a/.env.production +++ b/.env.production @@ -39,4 +39,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api diff --git a/.env.stg b/.env.stg index a1b443faba..fdddd58f3f 100644 --- a/.env.stg +++ b/.env.stg @@ -37,4 +37,4 @@ VITE_CAMPAIGN_URL=https://kyberswap-arbitrum-stip.kyberengineering.io/api VITE_REFERRAL_URL=https://referral.kyberswap.com/api VITE_TOKEN_API_URL=https://pre-token-api.kyberengineering.io/api -VITE_ZAP_EARN_URL=https://zap-earn-service.kyberengineering.io/api +VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api From 2e1ee69ffcebc896104b65fb8596749379f51a2b Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 6 Dec 2024 11:30:07 +0700 Subject: [PATCH 54/83] re-format data after switch to Krystal data --- src/pages/Earns/PoolExplorer/TableContent.tsx | 11 ++++++----- src/pages/Earns/index.tsx | 5 ++++- src/services/zapEarn.ts | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index bac720d05c..b3509e974f 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -63,8 +63,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo if (filters.sortBy === SortBy.APR) return filters.orderBy === Direction.DESC ? b.apr - a.apr : a.apr - b.apr if (filters.sortBy === SortBy.EARN_FEE) return filters.orderBy === Direction.DESC ? b.earnFee - a.earnFee : a.earnFee - b.earnFee - if (filters.sortBy === SortBy.TVL) - return filters.orderBy === Direction.DESC ? b.liquidity - a.liquidity : a.liquidity - b.liquidity + if (filters.sortBy === SortBy.TVL) return filters.orderBy === Direction.DESC ? b.tvl - a.tvl : a.tvl - b.tvl if (filters.sortBy === SortBy.VOLUME) return filters.orderBy === Direction.DESC ? b.volume - a.volume : a.volume - b.volume return 0 @@ -220,7 +219,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo TVL - {formatDisplayNumber(pool.liquidity, { style: 'currency', significantDigits: 6 })} + {formatDisplayNumber(pool.tvl, { style: 'currency', significantDigits: 6 })} Volume @@ -250,12 +249,14 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo {pool.feeTier}% - 0}>{pool.apr}% + 0}> + {formatDisplayNumber(pool.apr, { significantDigits: pool.apr < 1 ? 2 : pool.apr < 10 ? 3 : 4 })}% + {formatDisplayNumber(pool.earnFee, { style: 'currency', significantDigits: 6 })} - {formatDisplayNumber(pool.liquidity, { style: 'currency', significantDigits: 6 })} + {formatDisplayNumber(pool.tvl, { style: 'currency', significantDigits: 6 })} {formatDisplayNumber(pool.volume, { style: 'currency', significantDigits: 6 })} diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 41f52815cb..1aa9a8ec52 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -20,6 +20,7 @@ import LocalLoader from 'components/LocalLoader' import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'hooks/useChainsConfig' import useTheme from 'hooks/useTheme' +import { formatDisplayNumber } from 'utils/numbers' import { FilterTag } from './PoolExplorer' import useLiquidityWidget from './useLiquidityWidget' @@ -489,7 +490,9 @@ const PoolItem = ({ pool }: { pool: EarnPool }) => { {pool.feeTier}% - {pool.apr}% + + {formatDisplayNumber(pool.apr, { significantDigits: pool.apr < 1 ? 2 : pool.apr < 10 ? 3 : 4 })}% + ) } diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts index 32ee616abb..06c14b0d4b 100644 --- a/src/services/zapEarn.ts +++ b/src/services/zapEarn.ts @@ -24,6 +24,7 @@ export interface EarnPool { volume: number apr: number liquidity: number + tvl: number chainId?: number favorite?: { chainId: number From 372a0dd49ac7f0c99171f9453d589bb09f2579c1 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 6 Dec 2024 14:56:05 +0700 Subject: [PATCH 55/83] responsive earn landing page --- src/pages/Earns/index.tsx | 97 ++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 1aa9a8ec52..daeecf59e5 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -1,6 +1,7 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { rgba } from 'polished' import { useNavigate } from 'react-router-dom' +import { useMedia } from 'react-use' import { Box, Flex, Text } from 'rebass' import { EarnPool, useExplorerLandingQuery } from 'services/zapEarn' import styled, { keyframes } from 'styled-components' @@ -20,6 +21,7 @@ import LocalLoader from 'components/LocalLoader' import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'hooks/useChainsConfig' import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' import { formatDisplayNumber } from 'utils/numbers' import { FilterTag } from './PoolExplorer' @@ -37,6 +39,10 @@ const Container = styled.div` padding: 60px 16px; margin: auto; text-align: center; + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + padding: 36px 12px; + `} ` /* Spin animation */ @@ -83,6 +89,27 @@ const BorderWrapper = styled.div` animation: ${spin} 2s linear infinite; /* Spin animation */ } ` + +const OverviewWrapper = styled.div` + box-sizing: border-box; + margin: 0; + min-width: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + margin-top: 64px; + gap: 20px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + display: flex; + flex-direction: column; + `} + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + margin-top: 40px; + gap: 16px; + `} +` + const PoolWrapper = styled.div` border-radius: 20px; position: relative; @@ -127,11 +154,37 @@ const CardWrapper = styled.div` display: flex; flex-direction: column; overflow: hidden; + height: 100%; cursor: url(${CursorIcon}), auto; button { cursor: url(${CursorIcon}), auto; } + + ${({ theme }) => theme.mediaWidth.upToMedium` + padding: 0 36px 40px; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 0 36px 28px; + min-height: 285px; + `} + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + padding: 0 30px 24px; + min-height: unset; + height: fit-content; + `} +` + +const ButtonPrimaryStyled = styled(ButtonPrimary)` + margin-top: auto; + width: 132px; + height: 36px; + + ${({ theme }) => theme.mediaWidth.upToXXSmall` + margin-top: 18px; + `} ` const ListPoolWrapper = styled.div` @@ -140,6 +193,14 @@ const ListPoolWrapper = styled.div` height: 100%; background: linear-gradient(119.08deg, rgba(20, 29, 27, 1) -0.89%, rgba(14, 14, 14, 1) 132.3%); cursor: url(${CursorIcon}), auto; + + ${({ theme }) => theme.mediaWidth.upToMedium` + padding: 12px; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + padding: 18px; + `} ` const PoolRow = styled(Flex)` @@ -197,6 +258,9 @@ const Card = ({ action: { text: string; disabled?: boolean; onClick: () => void } }) => { const theme = useTheme() + const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + return ( !action.disabled && action.onClick()}> @@ -205,15 +269,13 @@ const Card = ({ - + {title} - + {desc} - - {action.text} - + {action.text} ) @@ -246,6 +308,9 @@ export default function Earns() { const lowVolatilityPool = (data?.data?.lowVolatility || []).slice(0, 5) const solidEarningPool = (data?.data?.solidEarning || []).slice(0, 5) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + const upToXXSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`) + return ( @@ -257,14 +322,7 @@ export default function Earns() { technology—to help you maximize earnings from your liquidity across various DeFi protocols. - + - + @@ -324,7 +382,14 @@ export default function Earns() { - + Date: Mon, 9 Dec 2024 14:18:25 +0700 Subject: [PATCH 56/83] fix endpoint of liquidity widget to pre release --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5f4473380b..7a79e134e8 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.27", + "kyberswap-liquidity-widgets": "^1.1.28", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 17e3288542..e507ff9612 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.27: - version "1.1.27" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.27.tgz#9b3ade1ba580b72b8a480689196ef3ecce61e41c" - integrity sha512-aJw3y/iFSIAnJJojdvN1r3Ju41QPOobbyj9Ihx2VzD1ZSqtNAgIrqzTby7/AeiC+cMptdaZrlBeN2NI+T2Q1jw== +kyberswap-liquidity-widgets@^1.1.28: + version "1.1.28" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.28.tgz#7a99f46b806ef6692cc78fcb8e816b85b051a18f" + integrity sha512-ZImfvOzFINhMzrqFKaNY5XdoHp5JmeD3fByq+RMSxMOpy0m9b5X2p6U/MkTE6zbEFO9R6tUNfoJIPPZ3SOYy8A== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From b0b88a313e838035a2edd2af0773f5f31e4225bd Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 9 Dec 2024 14:57:04 +0700 Subject: [PATCH 57/83] fix bug sorting in pool explorer page does not work --- src/pages/Earns/PoolExplorer/TableContent.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index b3509e974f..e19fb5e76e 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -58,7 +58,13 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo dexName: dexList.data?.find(dex => dex.dexId === pool.exchange)?.name || '', })) - if (filters.tag && Object.keys(FilterTag).includes(filters.tag) && filters.sortBy) { + if ( + filters.tag && + Object.keys(FilterTag) + .map(tagKey => FilterTag[tagKey]) + .includes(filters.tag) && + filters.sortBy + ) { parsedPoolData.sort((a, b) => { if (filters.sortBy === SortBy.APR) return filters.orderBy === Direction.DESC ? b.apr - a.apr : a.apr - b.apr if (filters.sortBy === SortBy.EARN_FEE) From 30b157baa7a26ef908fab56c4ac4e9eeb66836af Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 9 Dec 2024 17:50:16 +0700 Subject: [PATCH 58/83] fix bugs --- package.json | 2 +- src/pages/Earns/PoolExplorer/TableContent.tsx | 2 +- src/pages/Earns/PoolExplorer/index.tsx | 4 ++-- src/pages/Earns/PoolExplorer/useFilter.ts | 2 +- yarn.lock | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 7a79e134e8..3ed1566d51 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.28", + "kyberswap-liquidity-widgets": "^1.1.29", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index e19fb5e76e..9f40a37ab3 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -75,7 +75,7 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo return 0 }) - const page = filters.page || 0 + const page = filters.page || 1 const limit = filters.limit || 0 parsedPoolData = page > 9 ? [] : parsedPoolData.slice(page * limit, limit) diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 192b0d6a59..d733752d5a 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -273,9 +273,9 @@ const Earn = () => { updateFilters('page', (newPage - 1).toString())} + onPageChange={(newPage: number) => updateFilters('page', newPage.toString())} totalCount={totalPools} - currentPage={(filters.page || 0) + 1} + currentPage={filters.page || 1} pageSize={filters.limit || 10} /> diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index a367a96e7c..a34ef67c50 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -14,7 +14,7 @@ export default function useFilter(setSearch?: (search: string) => void) { const filters: QueryParams = useMemo(() => { return { chainId: +(searchParams.get('chainId') || chainId || ChainId.MAINNET), - page: +(searchParams.get('page') || 0), + page: +(searchParams.get('page') || 1), limit: 10, interval: searchParams.get('interval') || (timings[1].value as string), protocol: searchParams.get('protocol') || '', diff --git a/yarn.lock b/yarn.lock index e507ff9612..2548f47181 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.28: - version "1.1.28" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.28.tgz#7a99f46b806ef6692cc78fcb8e816b85b051a18f" - integrity sha512-ZImfvOzFINhMzrqFKaNY5XdoHp5JmeD3fByq+RMSxMOpy0m9b5X2p6U/MkTE6zbEFO9R6tUNfoJIPPZ3SOYy8A== +kyberswap-liquidity-widgets@^1.1.29: + version "1.1.29" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.29.tgz#1cf7789c046678b7bbbf2d290d1310e2217c461a" + integrity sha512-pg5Eug3Vw2gaIvyN2jfKjgl+18WMNpW4vkuDlNbEQQAawoOZFchFRgTu2NsAo9keYZbFH6l1cJtLJmDKhOTYPA== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 5a9042bac3af37d493e18b182787d5d96077b21f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 14:21:12 +0700 Subject: [PATCH 59/83] integrate API for my positions page --- src/constants/index.ts | 3 +- src/pages/App.tsx | 4 +- src/pages/Earns/MyPositions/index.tsx | 234 +++++++++++++--------- src/pages/Earns/PositionDetail/index.tsx | 71 +++++++ src/pages/Earns/PositionDetail/styles.tsx | 0 src/pages/Earns/index.tsx | 8 +- src/pages/Earns/useLiquidityWidget.tsx | 8 +- src/services/krystalEarn.ts | 126 ++++++++++++ src/services/zapEarn.ts | 46 ++--- src/state/index.ts | 3 + 10 files changed, 381 insertions(+), 122 deletions(-) create mode 100644 src/pages/Earns/PositionDetail/index.tsx create mode 100644 src/pages/Earns/PositionDetail/styles.tsx create mode 100644 src/services/krystalEarn.ts diff --git a/src/constants/index.ts b/src/constants/index.ts index 7252d82ba7..f623ef2377 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -172,7 +172,8 @@ export const APP_PATHS = { EARN: '/earns', EARN_POOLS: '/earns/pools', - EARN_MY_POSITIONS: '/earns/my-positions', + EARN_POSITIONS: '/earns/positions', + EARN_POSITION_DETAIL: '/earns/position', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 9813729c52..876d176749 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -65,6 +65,7 @@ const CampaignMyDashboard = lazy(() => import('pages/Campaign/MyDashboard')) const Earns = lazy(() => import('pages/Earns')) const EarnPoolExplorer = lazy(() => import('pages/Earns/PoolExplorer')) const EarnMyPositions = lazy(() => import('pages/Earns/MyPositions')) +const EarnPositionDetail = lazy(() => import('pages/Earns/PositionDetail')) const AppWrapper = styled.div` display: flex; @@ -323,7 +324,8 @@ export default function App() { } /> } /> - } /> + } /> + } /> } /> diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx index 9cf9813754..52ea01738d 100644 --- a/src/pages/Earns/MyPositions/index.tsx +++ b/src/pages/Earns/MyPositions/index.tsx @@ -1,13 +1,20 @@ import { t } from '@lingui/macro' import { Minus, Plus } from 'react-feather' +import { useNavigate } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' +import { PositionStatus, useUserPositionQuery } from 'services/krystalEarn' import CopyHelper from 'components/Copy' +import { APP_PATHS } from 'constants/index' +import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' import { MEDIA_WIDTHS } from 'theme' +import { shortenAddress } from 'utils' +import { formatDisplayNumber } from 'utils/numbers' import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' +import useLiquidityWidget from '../useLiquidityWidget' import { Badge, BadgeType, @@ -26,106 +33,147 @@ import { const MyPositions = () => { const theme = useTheme() + const navigate = useNavigate() const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + const { account } = useActiveWeb3React() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + + const { data: userPosition } = useUserPositionQuery( + // { addresses: account || '' }, + { addresses: '0xe403043a0f9c7b9f315cf145166eb747d9790e77' }, + { skip: !account, pollingInterval: 15_000 }, + ) + return ( - -
- - {t`My Liquidity`} - - - {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} - -
+ <> + {liquidityWidget} + +
+ + {t`My Liquidity`} + + + {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + +
- - - - - - - - - - KNC/WETH - 0.05% - ● In range - - - - - - V3 + + {(userPosition || []).map(position => ( + navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL })}> + + + + + + + + + {position.pool.tokenAmounts[0]?.token.symbol || ''}/ + {position.pool.tokenAmounts[1]?.token.symbol || ''} + + {position.pool.fees?.length > 0 && {position.pool.fees[0]}%} + + ● {position.status === PositionStatus.IN_RANGE ? 'In range' : 'Out of range'} + + + + + + + {position.pool.project?.split(' ')?.[1] || ''} + + + + #{position.tokenId} + + + {shortenAddress(position.chainId, position.pool.poolAddress, 4)} + + + + + {upToLarge && !upToSmall && ( + + + + + + + + + )} + + Value + + {formatDisplayNumber(position.currentPositionValue, { style: 'currency', significantDigits: 6 })} + + + + Earned Fee + + {formatDisplayNumber( + position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0), + { style: 'currency', significantDigits: 6 }, + )} - - - #24654 - - - 0x1234...abcd - - - - - {upToLarge && !upToSmall && ( - - - - - - - - - )} - - Value: - $2,876 - - - Earn: - $1,76 - - - Bal: - - 81,265.87 KNC - {upToSmall && } - 28.76 WETH - - - {(upToSmall || !upToLarge) && ( - - - - - - - - - )} - - + + + Bal + + + {formatDisplayNumber( + position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price, + { significantDigits: 6 }, + )}{' '} + {position.pool.tokenAmounts[0]?.token.symbol || ''} + + {upToSmall && } + + {formatDisplayNumber( + position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price, + { significantDigits: 6 }, + )}{' '} + {position.pool.tokenAmounts[1]?.token.symbol || ''} + + + + {(upToSmall || !upToLarge) && ( + + + + + { + e.stopPropagation() + handleOpenZapInWidget( + { + exchange: position.pool.project || '', + chainId: position.chainId, + address: position.pool.poolAddress, + }, + position.tokenId, + ) + }} + > + + + + )} + + ))} + - {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} -
+ {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`} +
+ ) } diff --git a/src/pages/Earns/PositionDetail/index.tsx b/src/pages/Earns/PositionDetail/index.tsx new file mode 100644 index 0000000000..fabfb92760 --- /dev/null +++ b/src/pages/Earns/PositionDetail/index.tsx @@ -0,0 +1,71 @@ +// import { ArrowLeft } from 'react-feather' +// import { useNavigate } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' + +import CopyHelper from 'components/Copy' +import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' + +import { + Badge, + BadgeType, + ChainImage, + DexImage, + ImageContainer, + PositionOverview, + PositionPageWrapper, +} from '../MyPositions/styles' +import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' + +const PositionDetail = () => { + const theme = useTheme() + // const navigate = useNavigate() + // const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + return ( + + + + + + + + + KNC/WETH + 0.05% + ● In range + + + + + + V3 + + + + #24654 + + + 0x1234...abcd + + + + + + ) +} + +export default PositionDetail diff --git a/src/pages/Earns/PositionDetail/styles.tsx b/src/pages/Earns/PositionDetail/styles.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index daeecf59e5..0399a66a64 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -338,7 +338,7 @@ export default function Earns() { desc="Track, adjust, and optimize your positions to stay in control of your DeFi journey." action={{ text: 'Your Pools', - onClick: () => navigate({ pathname: APP_PATHS.EARN_MY_POSITIONS }), + onClick: () => navigate({ pathname: APP_PATHS.EARN_POSITIONS }), }} /> { role="button" onClick={e => { e.stopPropagation() - handleOpenZapInWidget(pool) + handleOpenZapInWidget({ + exchange: pool.exchange, + chainId: pool.chainId, + address: pool.address, + }) }} > {liquidityWidget} diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 12690df916..1ca5cd03d2 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -1,6 +1,5 @@ import { ChainId, LiquidityWidget, PoolType } from 'kyberswap-liquidity-widgets' import { useState } from 'react' -import { EarnPool } from 'services/zapEarn' import { NotificationType } from 'components/Announcement/type' import Modal from 'components/Modal' @@ -16,6 +15,7 @@ interface LiquidityParams { chainId: ChainId source: string poolType: PoolType + positionId?: string onDismiss: () => void onConnectWallet: () => void } @@ -29,7 +29,10 @@ const useLiquidityWidget = () => { const [liquidityParams, setLiquidityParams] = useState(null) const handleCloseZapInWidget = () => setLiquidityParams(null) - const handleOpenZapInWidget = (pool: EarnPool) => { + const handleOpenZapInWidget = ( + pool: { exchange: string; chainId?: number; address: string }, + positionId?: string, + ) => { const supportedDexs = Object.keys(PoolType).map(item => item.replace('DEX_', '').replace('V3', '').toLowerCase()) const dex = supportedDexs.find(item => pool.exchange.toLowerCase().includes(item)) if (!dex) { @@ -52,6 +55,7 @@ const useLiquidityWidget = () => { chainId: (pool.chainId || filters.chainId) as ChainId, source: 'kyberswap-demo-zap', poolType: PoolType[`DEX_${dex.toUpperCase()}V3` as keyof typeof PoolType], + positionId, onDismiss: handleCloseZapInWidget, onConnectWallet: toggleWalletModal, }) diff --git a/src/services/krystalEarn.ts b/src/services/krystalEarn.ts new file mode 100644 index 0000000000..c10990fa43 --- /dev/null +++ b/src/services/krystalEarn.ts @@ -0,0 +1,126 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +export enum PositionStatus { + IN_RANGE = 'IN_RANGE', + OUT_RANGE = 'OUT_RANGE', +} + +export interface PositionAmount { + token: { + address: string + symbol: string + name: string + decimals: number + logo: string + tag: string + price: number + } + tokenType: string + tokenID: string + balance: string + quotes: { + usd: { + symbol: string + marketPrice: number + price: number + priceChange24hPercentage: number + value: number + timestamp: number + } + } +} + +export interface EarnPosition { + [x: string]: any + chainName: 'eth' + chainId: number + chainLogo: string + userAddress: string + id: string + tokenAddress: string + tokenId: string + liquidity: string + minPrice: number + maxPrice: number + currentAmounts: Array + providedAmounts: Array + feePending: Array + feesClaimed: Array + farmRewardsPending: Array + farmRewardsClaimed: Array + feeEarned24h: Array + farmReward24h: Array + createdTime: number + lastUpdateBlock: number + openedBlock: number + openedTime: number + closedBlock: number + closedTime: number + closedPrice: number + farming: boolean + impermanentLoss: number + apr: number + feeApr: number + farmApr: number + pnl: number + initialUnderlyingValue: number + currentUnderlyingValue: number + currentPositionValue: number + compareWithHodl: number + returnOnInvestment: number + totalDepositValue: number + totalWithdrawValue: number + yesterdayEarning: number + earning24h: number + status: PositionStatus + avgConvertPrice: number + isConvertedFromToken0: boolean + gasUsed: number + isSupportAutomation: boolean + hasAutomationOrder: boolean + pool: { + id: string + poolAddress: string + price: number + tokenAmounts: Array + farmRewardTokens: Array + fees: Array + rewards24h: Array + tickSpacing: number + project: string + projectLogo: string + projectAddress: string + showWarning: boolean + tvl: number + farmAddress: string + tag: string + } +} + +const krystalEarnServiceApi = createApi({ + reducerPath: 'krystalEarnServiceApi', + baseQuery: fetchBaseQuery({ + baseUrl: 'https://api.krystal.app/all', + }), + keepUnusedDataFor: 1, + endpoints: builder => ({ + userPosition: builder.query, { addresses: string }>({ + query: params => ({ + url: `/v1/lp/userPositions`, + params: { + ...params, + quoteSymbol: 'usd', + offset: 0, + orderBy: 'liquidity', + orderASC: false, + positionStatus: 'open', + }, + }), + transformResponse: (response: { positions: Array }) => response.positions, + }), + }), +}) + +export const { useUserPositionQuery } = krystalEarnServiceApi + +export default krystalEarnServiceApi diff --git a/src/services/zapEarn.ts b/src/services/zapEarn.ts index 06c14b0d4b..c9d80a70ca 100644 --- a/src/services/zapEarn.ts +++ b/src/services/zapEarn.ts @@ -1,6 +1,15 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +interface ExplorerLandingResponse { + data: { + highlightedPools: Array + solidEarning: Array + highAPR: Array + lowVolatility: Array + } +} + interface SupportedChainsResponse { code: number message: string @@ -15,6 +24,19 @@ interface SupportedChainsResponse { requestId: string } +export interface QueryParams { + chainId: ChainId + page?: number + limit?: number + interval: string + protocol: string + userAddress?: string + tag?: string + sortBy?: string + orderBy?: string + q?: string +} + export interface EarnPool { address: string earnFee: number @@ -49,28 +71,6 @@ interface PoolsExplorerResponse { requestId: string } -export interface QueryParams { - chainId: ChainId - page?: number - limit?: number - interval: string - protocol: string - userAddress?: string - tag?: string - sortBy?: string - orderBy?: string - q?: string -} - -interface ExplorerLandingResponse { - data: { - highlightedPools: Array - solidEarning: Array - highAPR: Array - lowVolatility: Array - } -} - interface AddRemoveFavoriteParams { chainId: ChainId message: string @@ -80,7 +80,7 @@ interface AddRemoveFavoriteParams { } const zapEarnServiceApi = createApi({ - reducerPath: 'zapEarnServiceApi ', + reducerPath: 'zapEarnServiceApi', baseQuery: fetchBaseQuery({ baseUrl: import.meta.env.VITE_ZAP_EARN_URL, }), diff --git a/src/state/index.ts b/src/state/index.ts index 3b54ed47ba..869599ca23 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -11,6 +11,7 @@ import crosschainApi from 'services/crossChain' import externalApi from 'services/externalApi' import geckoTerminalApi from 'services/geckoTermial' import identifyApi from 'services/identity' +import krystalEarnServiceApi from 'services/krystalEarn' import ksSettingApi from 'services/ksSetting' import kyberDAO from 'services/kyberDAO' import limitOrderApi from 'services/limitOrder' @@ -110,6 +111,7 @@ const store = configureStore({ [routeApi.reducerPath]: routeApi.reducer, [tokenApi.reducerPath]: tokenApi.reducer, [zapEarnServiceApi.reducerPath]: zapEarnServiceApi.reducer, + [krystalEarnServiceApi.reducerPath]: krystalEarnServiceApi.reducer, [referralApi.reducerPath]: referralApi.reducer, [campaignApi.reducerPath]: campaignApi.reducer, [commonServiceApi.reducerPath]: commonServiceApi.reducer, @@ -136,6 +138,7 @@ const store = configureStore({ .concat(socialApi.middleware) .concat(tokenApi.middleware) .concat(zapEarnServiceApi.middleware) + .concat(krystalEarnServiceApi.middleware) .concat(referralApi.middleware) .concat(campaignApi.middleware) .concat(commonServiceApi.middleware) From 54866e01e401af8a27ff37438d027cf330f6eda9 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 15:26:11 +0700 Subject: [PATCH 60/83] add tooltip for position screen and empty data UI --- src/pages/Earns/MyPositions/index.tsx | 283 ++++++++++++++++--------- src/pages/Earns/MyPositions/styles.tsx | 11 + 2 files changed, 190 insertions(+), 104 deletions(-) diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx index 52ea01738d..83a679b469 100644 --- a/src/pages/Earns/MyPositions/index.tsx +++ b/src/pages/Earns/MyPositions/index.tsx @@ -1,4 +1,5 @@ import { t } from '@lingui/macro' +import { useEffect, useRef } from 'react' import { Minus, Plus } from 'react-feather' import { useNavigate } from 'react-router-dom' import { useMedia } from 'react-use' @@ -6,6 +7,8 @@ import { Flex, Text } from 'rebass' import { PositionStatus, useUserPositionQuery } from 'services/krystalEarn' import CopyHelper from 'components/Copy' +import LocalLoader from 'components/LocalLoader' +import { MouseoverTooltip } from 'components/Tooltip' import { APP_PATHS } from 'constants/index' import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' @@ -21,6 +24,7 @@ import { ChainImage, DexImage, Divider, + EmptyPositionText, ImageContainer, MyLiquidityWrapper, PositionAction, @@ -39,13 +43,19 @@ const MyPositions = () => { const { account } = useActiveWeb3React() const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + const firstLoad = useRef(false) - const { data: userPosition } = useUserPositionQuery( - // { addresses: account || '' }, - { addresses: '0xe403043a0f9c7b9f315cf145166eb747d9790e77' }, - { skip: !account, pollingInterval: 15_000 }, + const { data: userPosition, isLoading } = useUserPositionQuery( + { addresses: account || '' }, + { skip: !account, pollingInterval: 1_000 }, ) + useEffect(() => { + if (!firstLoad.current && isLoading) { + firstLoad.current = true + } + }, [isLoading]) + return ( <> {liquidityWidget} @@ -60,110 +70,175 @@ const MyPositions = () => {
- {(userPosition || []).map(position => ( - navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL })}> - - - - - - - - - {position.pool.tokenAmounts[0]?.token.symbol || ''}/ - {position.pool.tokenAmounts[1]?.token.symbol || ''} - - {position.pool.fees?.length > 0 && {position.pool.fees[0]}%} - - ● {position.status === PositionStatus.IN_RANGE ? 'In range' : 'Out of range'} - - - - - + {isLoading && !firstLoad.current ? ( + + ) : userPosition && userPosition.length > 0 ? ( + userPosition.map(position => ( + navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL })} + > + + + + + + + + + {position.pool.tokenAmounts[0]?.token.symbol || ''}/ + {position.pool.tokenAmounts[1]?.token.symbol || ''} + + {position.pool.fees?.length > 0 && {position.pool.fees[0]}%} + + ● {position.status === PositionStatus.IN_RANGE ? 'In range' : 'Out of range'} + + + + + + + {position.pool.project?.split(' ')?.[1] || ''} + + - {position.pool.project?.split(' ')?.[1] || ''} + #{position.tokenId} + + {shortenAddress(position.chainId, position.pool.poolAddress, 4)} + + - - #{position.tokenId} - - - {shortenAddress(position.chainId, position.pool.poolAddress, 4)} - - - - - {upToLarge && !upToSmall && ( - - - - - - - - - )} - - Value - - {formatDisplayNumber(position.currentPositionValue, { style: 'currency', significantDigits: 6 })} - - - - Earned Fee - - {formatDisplayNumber( - position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0), - { style: 'currency', significantDigits: 6 }, - )} - - - - Bal - - - {formatDisplayNumber( - position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price, - { significantDigits: 6 }, - )}{' '} - {position.pool.tokenAmounts[0]?.token.symbol || ''} - - {upToSmall && } - - {formatDisplayNumber( - position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price, - { significantDigits: 6 }, - )}{' '} - {position.pool.tokenAmounts[1]?.token.symbol || ''} - - - - {(upToSmall || !upToLarge) && ( - - - - - { - e.stopPropagation() - handleOpenZapInWidget( - { - exchange: position.pool.project || '', - chainId: position.chainId, - address: position.pool.poolAddress, - }, - position.tokenId, - ) - }} + + {upToLarge && !upToSmall && ( + + + + + + + + + )} + + Value + + + {formatDisplayNumber( + position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price, + { significantDigits: 6 }, + )}{' '} + {position.pool.tokenAmounts[0]?.token.symbol || ''} + + + {formatDisplayNumber( + position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price, + { significantDigits: 6 }, + )}{' '} + {position.pool.tokenAmounts[1]?.token.symbol || ''} + + + } + width="fit-content" + placement="bottom" + > + + {formatDisplayNumber(position.currentPositionValue, { style: 'currency', significantDigits: 4 })} + + + + + Earned Fee + + + {formatDisplayNumber( + position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price, + { significantDigits: 6 }, + )}{' '} + {position.pool.tokenAmounts[0]?.token.symbol || ''} + + + {formatDisplayNumber( + position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price, + { significantDigits: 6 }, + )}{' '} + {position.pool.tokenAmounts[1]?.token.symbol || ''} + + + } + width="fit-content" + placement="bottom" > - - - - )} - - ))} + + {formatDisplayNumber( + position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0), + { style: 'currency', significantDigits: 4 }, + )} + + + + + Bal + + + {formatDisplayNumber( + position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price, + { significantDigits: 4 }, + )}{' '} + {position.pool.tokenAmounts[0]?.token.symbol || ''} + + {upToSmall && } + + {formatDisplayNumber( + position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price, + { significantDigits: 4 }, + )}{' '} + {position.pool.tokenAmounts[1]?.token.symbol || ''} + + + + {(upToSmall || !upToLarge) && ( + + + + + + + + { + e.stopPropagation() + handleOpenZapInWidget( + { + exchange: position.pool.project || '', + chainId: position.chainId, + address: position.pool.poolAddress, + }, + position.tokenId, + ) + }} + > + + + + + )} + + )) + ) : ( + You haven't had any positions yet! + )} rgba(theme.background, 0.4)}; + color: ${({ theme }) => theme.subText}; + border-radius: 20px; + height: 160px; + margin: 20px 0; +` From f1229cbc6348ff4cce193d0d5d3080a885cd5d66 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 15:26:24 +0700 Subject: [PATCH 61/83] add tooltip for position screen and empty data UI --- src/pages/Earns/MyPositions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx index 83a679b469..b5a3754447 100644 --- a/src/pages/Earns/MyPositions/index.tsx +++ b/src/pages/Earns/MyPositions/index.tsx @@ -47,7 +47,7 @@ const MyPositions = () => { const { data: userPosition, isLoading } = useUserPositionQuery( { addresses: account || '' }, - { skip: !account, pollingInterval: 1_000 }, + { skip: !account, pollingInterval: 15_000 }, ) useEffect(() => { From 4b51e1f09ed7f93a3a9a550dcaf4650407b7d11f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 15:32:51 +0700 Subject: [PATCH 62/83] add logic only remove search when change tag --- src/pages/Earns/PoolExplorer/useFilter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index a34ef67c50..5171e6f3b6 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -35,7 +35,7 @@ export default function useFilter(setSearch?: (search: string) => void) { } if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') { searchParams.delete('page') - if (key !== 'q' && setSearch) setSearch('') + if (key === 'tag' && setSearch) setSearch('') } setSearchParams(searchParams) }, From 8f44a10971024f8b684f4946762429f7db6ed104 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 16:08:03 +0700 Subject: [PATCH 63/83] fix loading for positions page --- src/pages/Earns/MyPositions/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx index b5a3754447..d52dc64170 100644 --- a/src/pages/Earns/MyPositions/index.tsx +++ b/src/pages/Earns/MyPositions/index.tsx @@ -43,7 +43,7 @@ const MyPositions = () => { const { account } = useActiveWeb3React() const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() - const firstLoad = useRef(false) + const firstLoading = useRef(false) const { data: userPosition, isLoading } = useUserPositionQuery( { addresses: account || '' }, @@ -51,8 +51,8 @@ const MyPositions = () => { ) useEffect(() => { - if (!firstLoad.current && isLoading) { - firstLoad.current = true + if (!firstLoading.current && !isLoading) { + firstLoading.current = true } }, [isLoading]) @@ -70,7 +70,7 @@ const MyPositions = () => {
- {isLoading && !firstLoad.current ? ( + {isLoading && !firstLoading.current ? ( ) : userPosition && userPosition.length > 0 ? ( userPosition.map(position => ( From 2b3394077ff8909b388a28793a8107b7d484ac53 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 16:11:01 +0700 Subject: [PATCH 64/83] fix tooltip for desktop only --- src/pages/Earns/MyPositions/index.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx index d52dc64170..71a1ef9f8a 100644 --- a/src/pages/Earns/MyPositions/index.tsx +++ b/src/pages/Earns/MyPositions/index.tsx @@ -8,7 +8,7 @@ import { PositionStatus, useUserPositionQuery } from 'services/krystalEarn' import CopyHelper from 'components/Copy' import LocalLoader from 'components/LocalLoader' -import { MouseoverTooltip } from 'components/Tooltip' +import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' import { APP_PATHS } from 'constants/index' import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' @@ -122,7 +122,7 @@ const MyPositions = () => { )} Value - @@ -147,11 +147,11 @@ const MyPositions = () => { {formatDisplayNumber(position.currentPositionValue, { style: 'currency', significantDigits: 4 })} - + Earned Fee - @@ -179,7 +179,7 @@ const MyPositions = () => { { style: 'currency', significantDigits: 4 }, )} - + Bal @@ -203,15 +203,15 @@ const MyPositions = () => { {(upToSmall || !upToLarge) && ( - - - + @@ -231,7 +231,7 @@ const MyPositions = () => { > - + )} From 8a7f263cd5a8ea9a8d57b5373b2d4759883d52d6 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 16:34:54 +0700 Subject: [PATCH 65/83] fix description --- src/pages/Earns/MyPositions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/MyPositions/index.tsx index 71a1ef9f8a..67b0bc29c2 100644 --- a/src/pages/Earns/MyPositions/index.tsx +++ b/src/pages/Earns/MyPositions/index.tsx @@ -65,7 +65,7 @@ const MyPositions = () => { {t`My Liquidity`} - {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`}
From 1e8fd7b8161f4e9dedd2b0536ce0b9a56b82485b Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 10 Dec 2024 17:49:01 +0700 Subject: [PATCH 66/83] fix bug can not open zap widget for pancake protocol --- package.json | 2 +- src/pages/Earns/useLiquidityWidget.tsx | 3 ++- yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3ed1566d51..6c834472cf 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.29", + "kyberswap-liquidity-widgets": "^1.1.31", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/pages/Earns/useLiquidityWidget.tsx b/src/pages/Earns/useLiquidityWidget.tsx index 1ca5cd03d2..976296523e 100644 --- a/src/pages/Earns/useLiquidityWidget.tsx +++ b/src/pages/Earns/useLiquidityWidget.tsx @@ -34,7 +34,8 @@ const useLiquidityWidget = () => { positionId?: string, ) => { const supportedDexs = Object.keys(PoolType).map(item => item.replace('DEX_', '').replace('V3', '').toLowerCase()) - const dex = supportedDexs.find(item => pool.exchange.toLowerCase().includes(item)) + const formattedExchange = pool.exchange.toLowerCase().replaceAll('_', '').replaceAll('-', '').replaceAll('v3', '') + const dex = supportedDexs.find(item => formattedExchange.includes(item) || item.includes(formattedExchange)) if (!dex) { notify( { diff --git a/yarn.lock b/yarn.lock index 2548f47181..03f5b3ea2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.29: - version "1.1.29" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.29.tgz#1cf7789c046678b7bbbf2d290d1310e2217c461a" - integrity sha512-pg5Eug3Vw2gaIvyN2jfKjgl+18WMNpW4vkuDlNbEQQAawoOZFchFRgTu2NsAo9keYZbFH6l1cJtLJmDKhOTYPA== +kyberswap-liquidity-widgets@^1.1.31: + version "1.1.31" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.31.tgz#9b4538d543468cf7dc36e5bce96e0ca589ff49ca" + integrity sha512-hlq7GTyUL08r83S+IXO/VyP3YxImlMUnbWwTUYc+xGVGvtw4y5ZErwwcW5HairTUiFBbrbH42fSuc4xsYCc+Lw== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From ed7d01969d3c7e85d3c110250e614682ee7b45cc Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 10:02:58 +0700 Subject: [PATCH 67/83] Update pool timeline and change Kyberswap to KyberSwap --- src/components/CoinbaseSubscribeBtn.tsx | 4 ++-- src/components/SwapForm/AddMEVProtectionModal.tsx | 2 +- src/pages/Earns/PoolExplorer/TableContent.tsx | 4 +++- src/pages/Earns/PoolExplorer/index.tsx | 8 ++++---- src/pages/Earns/PoolExplorer/useFilter.ts | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/CoinbaseSubscribeBtn.tsx b/src/components/CoinbaseSubscribeBtn.tsx index 8925a40f54..c955cab921 100644 --- a/src/components/CoinbaseSubscribeBtn.tsx +++ b/src/components/CoinbaseSubscribeBtn.tsx @@ -71,8 +71,8 @@ export default function CoinbaseSubscribeBtn({ onlyShowIfNotSubscribe = false }: size={13} text={ !isSubscribed - ? "Subscribe to receive Kyberswap's updates directly on your Coinbase Wallet." - : "Unsubscribe to stop receiving Kyberswap's updates directly on your Coinbase Wallet." + ? "Subscribe to receive KyberSwap's updates directly on your Coinbase Wallet." + : "Unsubscribe to stop receiving KyberSwap's updates directly on your Coinbase Wallet." } /> )} diff --git a/src/components/SwapForm/AddMEVProtectionModal.tsx b/src/components/SwapForm/AddMEVProtectionModal.tsx index bcb1d33279..b7f71e3577 100644 --- a/src/components/SwapForm/AddMEVProtectionModal.tsx +++ b/src/components/SwapForm/AddMEVProtectionModal.tsx @@ -55,7 +55,7 @@ export default function AddMEVProtectionModal({ isOpen, onClose }: { isOpen: boo return } - const name = 'Ethereum Mainnet (Kyberswap RPC)' + const name = 'Ethereum Mainnet (KyberSwap RPC)' mixpanelHandler(MIXPANEL_TYPE.MEV_ADD_CLICK_MODAL, { type: name }) addNewNetwork( ChainId.MAINNET, diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 9f40a37ab3..e4fef7a483 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -207,7 +207,9 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo
- 0}>{pool.apr}% + 0}> + {formatDisplayNumber(pool.apr, { significantDigits: pool.apr < 1 ? 2 : pool.apr < 10 ? 3 : 4 })}% + { @@ -167,20 +167,20 @@ const Earn = () => { {t`Earning with Smart Liquidity Providing`} - {t`Kyberswap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} + {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`}
updateFilters('tag', '')}> {t`All pools`} - + updateFilters('tag', 'favorite')}> {filterTags.map((item, index) => - item.tooltip ? ( + !upToMedium ? ( void) { chainId: +(searchParams.get('chainId') || chainId || ChainId.MAINNET), page: +(searchParams.get('page') || 1), limit: 10, - interval: searchParams.get('interval') || (timings[1].value as string), + interval: searchParams.get('interval') || (timings[0].value as string), protocol: searchParams.get('protocol') || '', userAddress: account, tag: searchParams.get('tag') || '', From f7b4c1d8b2bd48b6a8bf3e38977b09805ddb6d68 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 10:48:54 +0700 Subject: [PATCH 68/83] update zap widget --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6c834472cf..f646233f25 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.31", + "kyberswap-liquidity-widgets": "^1.1.35", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 03f5b3ea2f..77b7dc3402 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.31: - version "1.1.31" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.31.tgz#9b4538d543468cf7dc36e5bce96e0ca589ff49ca" - integrity sha512-hlq7GTyUL08r83S+IXO/VyP3YxImlMUnbWwTUYc+xGVGvtw4y5ZErwwcW5HairTUiFBbrbH42fSuc4xsYCc+Lw== +kyberswap-liquidity-widgets@^1.1.35: + version "1.1.35" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.35.tgz#321a0ab3f4994cc07ab9dc9667ef91c66dd191bc" + integrity sha512-Gi03h5UbWg+uqN7uhqUPbN/JaHl42mmDTsIX8bijB5P3FZ+Jd27EH3X61ANfvHbzpUldtAiTb/8GE+zZh0xvyQ== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 91776a18cf8505939f161c3975c10b84704756e4 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 11:08:16 +0700 Subject: [PATCH 69/83] update user position button --- src/assets/svg/ic_user_earn_position.svg | 5 ++ src/pages/App.tsx | 4 +- src/pages/Earns/PoolExplorer/index.tsx | 73 ++++++++++++------- src/pages/Earns/PoolExplorer/styles.tsx | 24 ++++++ src/pages/Earns/PositionDetail/index.tsx | 4 +- .../{MyPositions => UserPositions}/index.tsx | 0 .../{MyPositions => UserPositions}/styles.tsx | 0 7 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 src/assets/svg/ic_user_earn_position.svg rename src/pages/Earns/{MyPositions => UserPositions}/index.tsx (100%) rename src/pages/Earns/{MyPositions => UserPositions}/styles.tsx (100%) diff --git a/src/assets/svg/ic_user_earn_position.svg b/src/assets/svg/ic_user_earn_position.svg new file mode 100644 index 0000000000..a04d03038d --- /dev/null +++ b/src/assets/svg/ic_user_earn_position.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 876d176749..f262792ac9 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -64,7 +64,7 @@ const CampaignMyDashboard = lazy(() => import('pages/Campaign/MyDashboard')) const Earns = lazy(() => import('pages/Earns')) const EarnPoolExplorer = lazy(() => import('pages/Earns/PoolExplorer')) -const EarnMyPositions = lazy(() => import('pages/Earns/MyPositions')) +const EarnUserPositions = lazy(() => import('pages/Earns/UserPositions')) const EarnPositionDetail = lazy(() => import('pages/Earns/PositionDetail')) const AppWrapper = styled.div` @@ -324,7 +324,7 @@ export default function App() { } /> } /> - } /> + } /> } /> } /> diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index bc83147e16..27a6e589c0 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -2,7 +2,7 @@ import { t } from '@lingui/macro' import 'kyberswap-liquidity-widgets/dist/style.css' import { useEffect, useMemo, useState } from 'react' import { Star } from 'react-feather' -import { useSearchParams } from 'react-router-dom' +import { useNavigate, useSearchParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { useGetDexListQuery } from 'services/ksSetting' @@ -12,9 +12,11 @@ import { ReactComponent as IconHighAprPool } from 'assets/svg/ic_pool_high_apr.s import { ReactComponent as IconHighlightedPool } from 'assets/svg/ic_pool_highlighted.svg' import { ReactComponent as IconLowVolatility } from 'assets/svg/ic_pool_low_volatility.svg' import { ReactComponent as IconSolidEarningPool } from 'assets/svg/ic_pool_solid_earning.svg' +import { ReactComponent as IconUserEarnPosition } from 'assets/svg/ic_user_earn_position.svg' import Pagination from 'components/Pagination' import Search from 'components/Search' import { MouseoverTooltip } from 'components/Tooltip' +import { APP_PATHS } from 'constants/index' import { NETWORKS_INFO } from 'constants/networks' import useChainsConfig from 'hooks/useChainsConfig' import useDebounce from 'hooks/useDebounce' @@ -25,7 +27,16 @@ import { MEDIA_WIDTHS } from 'theme' import useLiquidityWidget from '../useLiquidityWidget' import DropdownMenu, { MenuOption } from './DropdownMenu' import TableContent from './TableContent' -import { ContentWrapper, PoolPageWrapper, TableHeader, TableWrapper, Tag, TagContainer } from './styles' +import { + ContentWrapper, + HeadSection, + PoolPageWrapper, + TableHeader, + TableWrapper, + Tag, + TagContainer, + UserPositionButton, +} from './styles' import useFilter from './useFilter' export enum FilterTag { @@ -76,6 +87,7 @@ export const timings: MenuOption[] = [ ] const Earn = () => { + const navigate = useNavigate() const [search, setSearch] = useState('') const deboundedSearch = useDebounce(search, 300) const [searchParams] = useSearchParams() @@ -114,6 +126,7 @@ const Earn = () => { const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) + const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) const totalPools = useMemo(() => { const totalItems = poolData?.data?.pagination?.totalItems || 0 @@ -170,18 +183,30 @@ const Earn = () => { {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`}
- - updateFilters('tag', '')}> - {t`All pools`} - - - updateFilters('tag', 'favorite')}> - + + + updateFilters('tag', '')}> + {t`All pools`} - - {filterTags.map((item, index) => - !upToMedium ? ( - + + updateFilters('tag', 'favorite')}> + + + + {filterTags.map((item, index) => + !upToMedium ? ( + + updateFilters('tag', item.value)} + > + {!upToExtraSmall && item.icon} + {item.label} + + + ) : ( { {!upToExtraSmall && item.icon} {item.label} - - ) : ( - updateFilters('tag', item.value)} - > - {!upToExtraSmall && item.icon} - {item.label} - - ), + ), + )} + + {!upToLarge && ( + navigate({ pathname: APP_PATHS.EARN_POSITIONS })}> + + {t`My Positions`} + )} - + diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx index cc6926321f..3a5ac9745a 100644 --- a/src/pages/Earns/PoolExplorer/styles.tsx +++ b/src/pages/Earns/PoolExplorer/styles.tsx @@ -22,6 +22,13 @@ export const LiquidityWidgetWrapper = styled.div` justify-content: center; ` +export const HeadSection = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; +` + export const TagContainer = styled.div` display: flex; gap: 1rem; @@ -54,6 +61,23 @@ export const Tag = styled.div<{ active: boolean }>` `} ` +export const UserPositionButton = styled.div` + display: flex; + align-items: center; + gap: 10px; + background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; + color: ${({ theme }) => theme.subText}; + border-radius: 12px; + padding: 8px 16px; + width: max-content; + font-size: 14px; + cursor: pointer; + + :hover { + filter: brightness(1.1); + } +` + export const TableWrapper = styled.div` background: ${({ theme }) => rgba(theme.background, 0.8)}; border-radius: 16px; diff --git a/src/pages/Earns/PositionDetail/index.tsx b/src/pages/Earns/PositionDetail/index.tsx index fabfb92760..466f147903 100644 --- a/src/pages/Earns/PositionDetail/index.tsx +++ b/src/pages/Earns/PositionDetail/index.tsx @@ -7,6 +7,7 @@ import CopyHelper from 'components/Copy' import useTheme from 'hooks/useTheme' import { MEDIA_WIDTHS } from 'theme' +import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' import { Badge, BadgeType, @@ -15,8 +16,7 @@ import { ImageContainer, PositionOverview, PositionPageWrapper, -} from '../MyPositions/styles' -import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' +} from '../UserPositions/styles' const PositionDetail = () => { const theme = useTheme() diff --git a/src/pages/Earns/MyPositions/index.tsx b/src/pages/Earns/UserPositions/index.tsx similarity index 100% rename from src/pages/Earns/MyPositions/index.tsx rename to src/pages/Earns/UserPositions/index.tsx diff --git a/src/pages/Earns/MyPositions/styles.tsx b/src/pages/Earns/UserPositions/styles.tsx similarity index 100% rename from src/pages/Earns/MyPositions/styles.tsx rename to src/pages/Earns/UserPositions/styles.tsx From 318d5b41e1ef148a393237d659a20c8047aae245 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 11:21:30 +0700 Subject: [PATCH 70/83] fix bug sort pool when filtered by tag --- src/pages/Earns/PoolExplorer/TableContent.tsx | 4 ++-- src/pages/Earns/PoolExplorer/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index e4fef7a483..900ea7b527 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -76,9 +76,9 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo }) const page = filters.page || 1 - const limit = filters.limit || 0 + const limit = filters.limit || 10 - parsedPoolData = page > 9 ? [] : parsedPoolData.slice(page * limit, limit) + parsedPoolData = Number(page) > 9 ? [] : parsedPoolData.slice((page - 1) * limit, limit) } return parsedPoolData diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 27a6e589c0..f748902128 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -130,7 +130,7 @@ const Earn = () => { const totalPools = useMemo(() => { const totalItems = poolData?.data?.pagination?.totalItems || 0 - if (!filters.tag || !Object.keys(FilterTag).includes(filters.tag)) return totalItems + if (!filters.tag || !Object.keys(FilterTag).includes(filters.tag.toUpperCase())) return totalItems return totalItems <= 100 ? totalItems : 100 }, [poolData, filters.tag]) From 42d1620c5b5b1d547d2bbc1b4024a86a29f21feb Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 13:44:41 +0700 Subject: [PATCH 71/83] update description for transaction time limit in setting --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f646233f25..df790fa9a3 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.35", + "kyberswap-liquidity-widgets": "^1.1.36", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 77b7dc3402..44e8967d52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.35: - version "1.1.35" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.35.tgz#321a0ab3f4994cc07ab9dc9667ef91c66dd191bc" - integrity sha512-Gi03h5UbWg+uqN7uhqUPbN/JaHl42mmDTsIX8bijB5P3FZ+Jd27EH3X61ANfvHbzpUldtAiTb/8GE+zZh0xvyQ== +kyberswap-liquidity-widgets@^1.1.36: + version "1.1.36" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.36.tgz#1fe2d3e38642ccacc69be8147466220865a80f7f" + integrity sha512-njeznhSVuptWiW3FOoJ4O4x8LwVQPyTmHMWxJ+CO6dNGamQXRmaJpWkBIikiBG9206ZwgtvU2GQV6QcjdCitmQ== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 69c92d7f2cccfde4ce4085d19a7a0db7a6857523 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 14:37:08 +0700 Subject: [PATCH 72/83] fix font family for zap widget --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index df790fa9a3..c3b6707e95 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.36", + "kyberswap-liquidity-widgets": "^1.1.44", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 44e8967d52..ef243e75e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.36: - version "1.1.36" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.36.tgz#1fe2d3e38642ccacc69be8147466220865a80f7f" - integrity sha512-njeznhSVuptWiW3FOoJ4O4x8LwVQPyTmHMWxJ+CO6dNGamQXRmaJpWkBIikiBG9206ZwgtvU2GQV6QcjdCitmQ== +kyberswap-liquidity-widgets@^1.1.44: + version "1.1.44" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.44.tgz#3811f3733c71844eac5aa766cf23cf4cc4276edb" + integrity sha512-S/xSMuI/HM8OZWJ/TeCOT+oR8af/Dcxn0EeI5JpHGnPYlVbTvsZpM/fu0Tz08HeMkBN9/zZSjCdKdMLyqNVmKg== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From bc10023bd002a42d46c578195e898db227b9b627 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 14:41:14 +0700 Subject: [PATCH 73/83] fix font family for zap widget --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c3b6707e95..11fea005f6 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.44", + "kyberswap-liquidity-widgets": "^1.1.45", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index ef243e75e4..da1ae4426c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.44: - version "1.1.44" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.44.tgz#3811f3733c71844eac5aa766cf23cf4cc4276edb" - integrity sha512-S/xSMuI/HM8OZWJ/TeCOT+oR8af/Dcxn0EeI5JpHGnPYlVbTvsZpM/fu0Tz08HeMkBN9/zZSjCdKdMLyqNVmKg== +kyberswap-liquidity-widgets@^1.1.45: + version "1.1.45" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.45.tgz#62d44551b731aaa8a40a84bddff7e4532c0a092c" + integrity sha512-lKSW+99MCu0AGv5O3YAHPwD1X6nPPa7plVwKqOtGNfUWV0jGIKekOBciLlo4ZaAGzTi2fg8G9XTJPutA35YQZA== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 3b0b530da1bbc2be7a1e2ccd290adeb35483d453 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 15:35:54 +0700 Subject: [PATCH 74/83] add some sort logic --- src/pages/Earns/PoolExplorer/useFilter.ts | 7 ++++++- src/pages/Earns/index.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index 077528bd2e..8037006dc2 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -4,8 +4,9 @@ import { useSearchParams } from 'react-router-dom' import { QueryParams } from 'services/zapEarn' import { useActiveWeb3React } from 'hooks' +import { Direction } from 'pages/MarketOverview/SortIcon' -import { timings } from '.' +import { FilterTag, SortBy, timings } from '.' export default function useFilter(setSearch?: (search: string) => void) { const [searchParams, setSearchParams] = useSearchParams() @@ -32,6 +33,10 @@ export default function useFilter(setSearch?: (search: string) => void) { else { searchParams.set(key, value) if (key === 'chainId') searchParams.delete('protocol') + if (key === 'tag' && value === FilterTag.LOW_VOLATILITY) { + searchParams.set('sortBy', SortBy.APR) + searchParams.set('orderBy', Direction.DESC) + } } if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') { searchParams.delete('page') diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 0399a66a64..68042219e8 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -305,7 +305,7 @@ export default function Earns() { const highlightedPools = (data?.data?.highlightedPools || []).slice(0, 9) const highAprPool = (data?.data?.highAPR || []).slice(0, 5) - const lowVolatilityPool = (data?.data?.lowVolatility || []).slice(0, 5) + const lowVolatilityPool = [...(data?.data?.lowVolatility || [])].sort((a, b) => b.apr - a.apr).slice(0, 5) const solidEarningPool = (data?.data?.solidEarning || []).slice(0, 5) const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) From eab9b950cab4c6a72e178afce45acb951db0f777 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 16:09:07 +0700 Subject: [PATCH 75/83] reset sort if pool filter by tag --- src/pages/Earns/PoolExplorer/useFilter.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index 8037006dc2..cab936c3b5 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -33,15 +33,18 @@ export default function useFilter(setSearch?: (search: string) => void) { else { searchParams.set(key, value) if (key === 'chainId') searchParams.delete('protocol') - if (key === 'tag' && value === FilterTag.LOW_VOLATILITY) { - searchParams.set('sortBy', SortBy.APR) - searchParams.set('orderBy', Direction.DESC) + if (key === 'tag') { + searchParams.delete('sortBy') + searchParams.delete('orderBy') + if (setSearch) setSearch('') + if (value === FilterTag.LOW_VOLATILITY) { + searchParams.set('sortBy', SortBy.APR) + searchParams.set('orderBy', Direction.DESC) + } } } - if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') { - searchParams.delete('page') - if (key === 'tag' && setSearch) setSearch('') - } + if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') searchParams.delete('page') + setSearchParams(searchParams) }, [setSearchParams, searchParams, setSearch], From 5a6b8459e62f89ee6588e7b019e5038a314d5035 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 11 Dec 2024 17:12:39 +0700 Subject: [PATCH 76/83] remove sort for pool with tags filter --- src/pages/Earns/PoolExplorer/TableContent.tsx | 31 ++----------------- src/pages/Earns/PoolExplorer/index.tsx | 9 +----- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/TableContent.tsx b/src/pages/Earns/PoolExplorer/TableContent.tsx index 900ea7b527..bb763f3f7e 100644 --- a/src/pages/Earns/PoolExplorer/TableContent.tsx +++ b/src/pages/Earns/PoolExplorer/TableContent.tsx @@ -12,12 +12,10 @@ import Loader from 'components/Loader' import { NETWORKS_INFO } from 'constants/networks' import { useActiveWeb3React, useWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' -import { Direction } from 'pages/MarketOverview/SortIcon' import { useNotify, useWalletModalToggle } from 'state/application/hooks' import { MEDIA_WIDTHS } from 'theme' import { formatDisplayNumber } from 'utils/numbers' -import { FilterTag, SortBy } from '.' import { Apr, CurrencyRoundedImage, @@ -52,37 +50,12 @@ const TableContent = ({ onOpenZapInWidget }: { onOpenZapInWidget: (pool: EarnPoo const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const tablePoolData = useMemo(() => { - let parsedPoolData = (poolData?.data?.pools || []).map(pool => ({ + return (poolData?.data?.pools || []).map(pool => ({ ...pool, dexLogo: dexList.data?.find(dex => dex.dexId === pool.exchange)?.logoURL || '', dexName: dexList.data?.find(dex => dex.dexId === pool.exchange)?.name || '', })) - - if ( - filters.tag && - Object.keys(FilterTag) - .map(tagKey => FilterTag[tagKey]) - .includes(filters.tag) && - filters.sortBy - ) { - parsedPoolData.sort((a, b) => { - if (filters.sortBy === SortBy.APR) return filters.orderBy === Direction.DESC ? b.apr - a.apr : a.apr - b.apr - if (filters.sortBy === SortBy.EARN_FEE) - return filters.orderBy === Direction.DESC ? b.earnFee - a.earnFee : a.earnFee - b.earnFee - if (filters.sortBy === SortBy.TVL) return filters.orderBy === Direction.DESC ? b.tvl - a.tvl : a.tvl - b.tvl - if (filters.sortBy === SortBy.VOLUME) - return filters.orderBy === Direction.DESC ? b.volume - a.volume : a.volume - b.volume - return 0 - }) - - const page = filters.page || 1 - const limit = filters.limit || 10 - - parsedPoolData = Number(page) > 9 ? [] : parsedPoolData.slice((page - 1) * limit, limit) - } - - return parsedPoolData - }, [poolData, filters, dexList]) + }, [poolData, dexList]) const handleFavorite = async (e: React.MouseEvent, pool: EarnPool) => { e.stopPropagation() diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index f748902128..3052c425a7 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -128,13 +128,6 @@ const Earn = () => { const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) - const totalPools = useMemo(() => { - const totalItems = poolData?.data?.pagination?.totalItems || 0 - if (!filters.tag || !Object.keys(FilterTag).includes(filters.tag.toUpperCase())) return totalItems - - return totalItems <= 100 ? totalItems : 100 - }, [poolData, filters.tag]) - const onChainChange = (newChainId: string | number) => { updateFilters('chainId', newChainId.toString()) } @@ -295,7 +288,7 @@ const Earn = () => {
updateFilters('page', newPage.toString())} - totalCount={totalPools} + totalCount={poolData?.data?.pagination?.totalItems || 0} currentPage={filters.page || 1} pageSize={filters.limit || 10} /> From 7cfb140487c5cda81a7457309092f9ec7f2f3448 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 12 Dec 2024 12:02:18 +0700 Subject: [PATCH 77/83] filter earn by 2 chains ETH and Base, 3 dexes Uniswap Pancakeswap and Sushiswap --- package.json | 2 +- src/services/krystalEarn.ts | 2 ++ yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 11fea005f6..5b26b022dc 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@kyberswap/ks-sdk-classic": "^1.0.3", "@kyberswap/ks-sdk-core": "1.1.6", "@kyberswap/ks-sdk-elastic": "^1.1.2", - "kyberswap-liquidity-widgets": "^1.1.45", + "kyberswap-liquidity-widgets": "^1.1.46", "@kyberswap/oauth2": "1.0.2", "@lingui/macro": "^4.6.0", "@lingui/react": "^4.6.0", diff --git a/src/services/krystalEarn.ts b/src/services/krystalEarn.ts index c10990fa43..c60ab67928 100644 --- a/src/services/krystalEarn.ts +++ b/src/services/krystalEarn.ts @@ -109,6 +109,8 @@ const krystalEarnServiceApi = createApi({ url: `/v1/lp/userPositions`, params: { ...params, + chainIds: [1, 8453], // ETH & Base + protocols: 'Uniswap V3,PancakeSwap V3,SushiSwap V3', quoteSymbol: 'usd', offset: 0, orderBy: 'liquidity', diff --git a/yarn.lock b/yarn.lock index da1ae4426c..d04832532c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14251,10 +14251,10 @@ kleur@^4.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -kyberswap-liquidity-widgets@^1.1.45: - version "1.1.45" - resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.45.tgz#62d44551b731aaa8a40a84bddff7e4532c0a092c" - integrity sha512-lKSW+99MCu0AGv5O3YAHPwD1X6nPPa7plVwKqOtGNfUWV0jGIKekOBciLlo4ZaAGzTi2fg8G9XTJPutA35YQZA== +kyberswap-liquidity-widgets@^1.1.46: + version "1.1.46" + resolved "https://registry.yarnpkg.com/kyberswap-liquidity-widgets/-/kyberswap-liquidity-widgets-1.1.46.tgz#1cf01a03a851c31928f587284d0cb6ca5359fdd9" + integrity sha512-U+nr2R4X+zQTnbFS/rkER/aLBN/DJC0AmwgX2XZtSeBXgOx3BE8RSxK/MYIT0ZhzyWOWgQJb5w7sQb7058T1vQ== dependencies: "@ethersproject/providers" "^5.7.2" "@kyberswap/ks-sdk-core" "^1.1.5" From 706e69afaa361f605fcbd1824215de7246707500 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 12 Dec 2024 14:14:09 +0700 Subject: [PATCH 78/83] fix default chain logic --- src/pages/Earns/PoolExplorer/useFilter.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index cab936c3b5..5839f6858a 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -8,13 +8,17 @@ import { Direction } from 'pages/MarketOverview/SortIcon' import { FilterTag, SortBy, timings } from '.' +const supportedChains = [ChainId.MAINNET, ChainId.BASE] + export default function useFilter(setSearch?: (search: string) => void) { const [searchParams, setSearchParams] = useSearchParams() const { account, chainId } = useActiveWeb3React() const filters: QueryParams = useMemo(() => { return { - chainId: +(searchParams.get('chainId') || chainId || ChainId.MAINNET), + chainId: +( + searchParams.get('chainId') || (chainId && supportedChains.includes(chainId) ? chainId : ChainId.MAINNET) + ), page: +(searchParams.get('page') || 1), limit: 10, interval: searchParams.get('interval') || (timings[0].value as string), From 481455e058a39b717ab302e1ed4ae63f91b7b349 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 12 Dec 2024 14:38:59 +0700 Subject: [PATCH 79/83] add default sort when select all pools --- src/pages/Earns/PoolExplorer/useFilter.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts index 5839f6858a..50819b92c3 100644 --- a/src/pages/Earns/PoolExplorer/useFilter.ts +++ b/src/pages/Earns/PoolExplorer/useFilter.ts @@ -25,16 +25,21 @@ export default function useFilter(setSearch?: (search: string) => void) { protocol: searchParams.get('protocol') || '', userAddress: account, tag: searchParams.get('tag') || '', - sortBy: searchParams.get('sortBy') || '', - orderBy: searchParams.get('orderBy') || '', + sortBy: searchParams.get('sortBy') || (!searchParams.get('tag') ? SortBy.TVL : ''), + orderBy: searchParams.get('orderBy') || (!searchParams.get('tag') ? Direction.DESC : ''), q: searchParams.get('q')?.trim() || '', } }, [searchParams, account, chainId]) const updateFilters = useCallback( (key: keyof QueryParams, value: string) => { - if (!value) searchParams.delete(key) - else { + if (!value) { + searchParams.delete(key) + if (key === 'tag') { + searchParams.set('sortBy', SortBy.TVL) + searchParams.set('orderBy', Direction.DESC) + } + } else { searchParams.set(key, value) if (key === 'chainId') searchParams.delete('protocol') if (key === 'tag') { From ce2250d7e9b8a4ee8a9704e6dd2f8b07e855a39e Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 12 Dec 2024 15:52:20 +0700 Subject: [PATCH 80/83] add action open increase zap widget for user position screen --- src/pages/Earns/UserPositions/index.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/pages/Earns/UserPositions/index.tsx b/src/pages/Earns/UserPositions/index.tsx index 67b0bc29c2..69ba8a2086 100644 --- a/src/pages/Earns/UserPositions/index.tsx +++ b/src/pages/Earns/UserPositions/index.tsx @@ -76,7 +76,7 @@ const MyPositions = () => { userPosition.map(position => ( navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL })} + onClick={() => navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL.replace(':id', position.id) })} > @@ -115,7 +115,20 @@ const MyPositions = () => { - + { + e.stopPropagation() + handleOpenZapInWidget( + { + exchange: position.pool.project || '', + chainId: position.chainId, + address: position.pool.poolAddress, + }, + position.tokenId, + ) + }} + > From b0c50772f504161cb3337aeab147aa4e9529c5d0 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Sat, 14 Dec 2024 00:54:20 +0700 Subject: [PATCH 81/83] add position detail screen --- src/assets/svg/ic_left_arrow.svg | 5 + src/constants/index.ts | 2 +- src/pages/Earns/PoolExplorer/index.tsx | 41 +- src/pages/Earns/PoolExplorer/useFilter.ts | 5 +- .../useSupportedDexesAndChains.ts | 40 ++ src/pages/Earns/PositionDetail/index.tsx | 404 ++++++++++++++++-- src/pages/Earns/PositionDetail/styles.tsx | 141 ++++++ src/pages/Earns/UserPositions/index.tsx | 321 +++++++------- src/services/krystalEarn.ts | 40 +- 9 files changed, 745 insertions(+), 254 deletions(-) create mode 100644 src/assets/svg/ic_left_arrow.svg create mode 100644 src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts diff --git a/src/assets/svg/ic_left_arrow.svg b/src/assets/svg/ic_left_arrow.svg new file mode 100644 index 0000000000..6b8df42051 --- /dev/null +++ b/src/assets/svg/ic_left_arrow.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/constants/index.ts b/src/constants/index.ts index f623ef2377..ea33bf743e 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -173,7 +173,7 @@ export const APP_PATHS = { EARN: '/earns', EARN_POOLS: '/earns/pools', EARN_POSITIONS: '/earns/positions', - EARN_POSITION_DETAIL: '/earns/position', + EARN_POSITION_DETAIL: '/earns/position/:id', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 3052c425a7..ea346b8080 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -1,12 +1,11 @@ import { t } from '@lingui/macro' import 'kyberswap-liquidity-widgets/dist/style.css' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import { Star } from 'react-feather' import { useNavigate, useSearchParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' -import { useGetDexListQuery } from 'services/ksSetting' -import { usePoolsExplorerQuery, useSupportedProtocolsQuery } from 'services/zapEarn' +import { usePoolsExplorerQuery } from 'services/zapEarn' import { ReactComponent as IconHighAprPool } from 'assets/svg/ic_pool_high_apr.svg' import { ReactComponent as IconHighlightedPool } from 'assets/svg/ic_pool_highlighted.svg' @@ -17,10 +16,9 @@ import Pagination from 'components/Pagination' import Search from 'components/Search' import { MouseoverTooltip } from 'components/Tooltip' import { APP_PATHS } from 'constants/index' -import { NETWORKS_INFO } from 'constants/networks' -import useChainsConfig from 'hooks/useChainsConfig' import useDebounce from 'hooks/useDebounce' import useTheme from 'hooks/useTheme' +import useSupportedDexesAndChains from 'pages/Earns/PoolExplorer/useSupportedDexesAndChains' import SortIcon, { Direction } from 'pages/MarketOverview/SortIcon' import { MEDIA_WIDTHS } from 'theme' @@ -92,37 +90,10 @@ const Earn = () => { const deboundedSearch = useDebounce(search, 300) const [searchParams] = useSearchParams() const theme = useTheme() - const { supportedChains } = useChainsConfig() const { filters, updateFilters } = useFilter(setSearch) const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() - - const dexList = useGetDexListQuery({ - chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, - }) - const { data: supportedProtocolsData } = useSupportedProtocolsQuery() const { data: poolData } = usePoolsExplorerQuery(filters, { pollingInterval: 5 * 60_000 }) - - const supportedProtocols = useMemo(() => { - if (!supportedProtocolsData?.data?.chains) return [] - const parsedProtocols = - supportedProtocolsData.data.chains[filters.chainId]?.protocols?.map(item => ({ - label: (dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name).replaceAll('-', ' '), - value: item.id, - })) || [] - return [{ label: 'All Protocols', value: '' }].concat(parsedProtocols) - }, [filters.chainId, supportedProtocolsData, dexList]) - - const chains = useMemo( - () => - supportedChains - .map(chain => ({ - label: chain.name, - value: chain.chainId, - icon: chain.icon, - })) - .filter(chain => supportedProtocolsData?.data?.chains?.[chain.value]), - [supportedChains, supportedProtocolsData], - ) + const { supportedDexes, supportedChains } = useSupportedDexesAndChains(filters) const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const upToMedium = useMedia(`(max-width: ${MEDIA_WIDTHS.upToMedium}px)`) @@ -221,10 +192,10 @@ const Earn = () => { - + void) { const [searchParams, setSearchParams] = useSearchParams() const { account, chainId } = useActiveWeb3React() @@ -17,7 +16,7 @@ export default function useFilter(setSearch?: (search: string) => void) { const filters: QueryParams = useMemo(() => { return { chainId: +( - searchParams.get('chainId') || (chainId && supportedChains.includes(chainId) ? chainId : ChainId.MAINNET) + searchParams.get('chainId') || (chainId && earnSupportedChains.includes(chainId) ? chainId : ChainId.MAINNET) ), page: +(searchParams.get('page') || 1), limit: 10, diff --git a/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts b/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts new file mode 100644 index 0000000000..c0aa06a4b2 --- /dev/null +++ b/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts @@ -0,0 +1,40 @@ +import { useMemo } from 'react' +import { useGetDexListQuery } from 'services/ksSetting' +import { QueryParams, useSupportedProtocolsQuery } from 'services/zapEarn' + +import { NETWORKS_INFO } from 'constants/networks' +import useChainsConfig from 'hooks/useChainsConfig' + +const useSupportedDexesAndChains = (filters: QueryParams) => { + const { supportedChains } = useChainsConfig() + const dexList = useGetDexListQuery({ + chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute, + }) + const { data: supportedProtocols } = useSupportedProtocolsQuery() + + const supportedDexes = useMemo(() => { + if (!supportedProtocols?.data?.chains) return [] + const parsedProtocols = + supportedProtocols.data.chains[filters.chainId]?.protocols?.map(item => ({ + label: (dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name).replaceAll('-', ' '), + value: item.id, + })) || [] + return [{ label: 'All Protocols', value: '' }].concat(parsedProtocols) + }, [filters.chainId, supportedProtocols, dexList]) + + const chains = useMemo( + () => + supportedChains + .map(chain => ({ + label: chain.name, + value: chain.chainId, + icon: chain.icon, + })) + .filter(chain => supportedProtocols?.data?.chains?.[chain.value]), + [supportedChains, supportedProtocols], + ) + + return { supportedDexes, supportedChains: chains } +} + +export default useSupportedDexesAndChains diff --git a/src/pages/Earns/PositionDetail/index.tsx b/src/pages/Earns/PositionDetail/index.tsx index 466f147903..356eb7a134 100644 --- a/src/pages/Earns/PositionDetail/index.tsx +++ b/src/pages/Earns/PositionDetail/index.tsx @@ -1,11 +1,27 @@ -// import { ArrowLeft } from 'react-feather' -// import { useNavigate } from 'react-router-dom' +import { ChainId } from '@kyberswap/ks-sdk-core' +import { t } from '@lingui/macro' +import { useEffect, useMemo, useRef, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' +import { + EarnSupportedProtocols, + PositionStatus, + earnSupportedProtocols, + usePositionEarningStatisticsQuery, + useUserPositionQuery, +} from 'services/krystalEarn' import CopyHelper from 'components/Copy' +import { Swap as SwapIcon } from 'components/Icons' +import InfoHelper from 'components/InfoHelper' +import LocalLoader from 'components/LocalLoader' +import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' import { MEDIA_WIDTHS } from 'theme' +import { shortenAddress } from 'utils' +import { formatDisplayNumber } from 'utils/numbers' import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' import { @@ -13,58 +29,358 @@ import { BadgeType, ChainImage, DexImage, + EmptyPositionText, ImageContainer, PositionOverview, PositionPageWrapper, } from '../UserPositions/styles' +import useLiquidityWidget from '../useLiquidityWidget' +import { + DexInfo, + IconArrowLeft, + InfoLeftColumn, + InfoRight, + InfoRightColumn, + InfoSection, + InfoSectionFirstFormat, + InfoSectionSecondFormat, + MainSection, + PositionAction, + PositionActionWrapper, + PositionDetailWrapper, + RevertIconWrapper, + VerticalDivider, +} from './styles' const PositionDetail = () => { const theme = useTheme() - // const navigate = useNavigate() - // const upToLarge = useMedia(`(max-width: ${MEDIA_WIDTHS.upToLarge}px)`) + const navigate = useNavigate() const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + const firstLoading = useRef(false) + + const { account } = useActiveWeb3React() + const { id } = useParams() + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() + const { data: userPosition, isLoading } = useUserPositionQuery( + { addresses: account || '', positionId: id }, + { skip: !account, pollingInterval: 15_000 }, + ) + const { data: positionEarningStatistics } = usePositionEarningStatisticsQuery( + { + tokenAddress: userPosition?.[0]?.tokenAddress || '', + tokenId: userPosition?.[0]?.tokenId || '', + chainId: userPosition?.[0]?.chainId || '', + }, + { skip: !userPosition?.[0], pollingInterval: 15_000 }, + ) + + const [revert, setRevert] = useState(false) + + const position = useMemo(() => { + if (!userPosition?.[0]) return + const position = userPosition?.[0] + + return { + id: position?.tokenId, + dex: position?.pool.project || '', + dexImage: position?.pool.projectLogo || '', + chainId: position?.chainId, + chainName: position?.chainName, + chainLogo: position?.chainLogo || '', + poolAddress: position?.pool.poolAddress || '', + token0Logo: position?.pool.tokenAmounts[0]?.token.logo || '', + token1Logo: position?.pool.tokenAmounts[1]?.token.logo || '', + token0Symbol: position?.pool.tokenAmounts[0]?.token.symbol || '', + token1Symbol: position?.pool.tokenAmounts[1]?.token.symbol || '', + poolFee: position?.pool.fees?.[0], + status: position?.status, + totalValue: position?.currentPositionValue, + apr: position?.apr || 0, + token0TotalAmount: position + ? position?.currentAmounts[0]?.quotes.usd.value / position?.currentAmounts[0]?.quotes.usd.price + : 0, + token1TotalAmount: position + ? position?.currentAmounts[1]?.quotes.usd.value / position?.currentAmounts[1]?.quotes.usd.price + : 0, + minPrice: position?.minPrice || 0, + maxPrice: position?.maxPrice || 0, + pairRate: position?.pool.price || 0, + totalUnclaimedFee: + (position?.feePending?.[0].quotes.usd.value || 0) + (position?.feePending?.[1].quotes.usd.value || 0), + token0UnclaimedAmount: position + ? position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price + : 0, + token1UnclaimedAmount: position + ? position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price + : 0, + token0UnclaimedValue: position?.feePending[0]?.quotes.usd.value, + token1UnclaimedValue: position?.feePending[1]?.quotes.usd.value, + totalEarnedFee: position + ? position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0) + + position.feesClaimed.reduce((a, b) => a + b.quotes.usd.value, 0) + : 0, + } + }, [userPosition]) + + const earning = useMemo(() => { + if (!positionEarningStatistics || !positionEarningStatistics.length) return {} + + const reversedPositionEarningStatistics = [...positionEarningStatistics].reverse() + const earning24h = reversedPositionEarningStatistics[0].totalEarning + const earning7d = reversedPositionEarningStatistics.slice(0, 7).reduce((a, b) => a + b.totalEarning, 0) + + return { + earning24h, + earning7d, + } + }, [positionEarningStatistics]) + + const onOpenPositionInDexSite = () => { + if (!position || !earnSupportedProtocols.includes(position.dex)) return + + if (position.dex === EarnSupportedProtocols.UNISWAP_V3) + window.open(`https://app.uniswap.org/positions/v3/${position.chainName}/${position.id}`) + else if (position.dex === EarnSupportedProtocols.SUSHISWAP_V3) + window.open(`https://www.sushi.com/${position.chainName}/pool/v3/${position.poolAddress}/${position.id}`) + else if (position.dex === EarnSupportedProtocols.PANCAKESWAP_V3) + window.open(`https://pancakeswap.finance/liquidity/${position.id}`) + } + + const onOpenIncreaseLiquidityWidget = () => { + if (!position) return + handleOpenZapInWidget( + { + exchange: position.dex, + chainId: position.chainId, + address: position.poolAddress, + }, + position.id, + ) + } + + useEffect(() => { + if (!firstLoading.current && !isLoading) { + firstLoading.current = true + } + }, [isLoading]) return ( - - - - - - - - - KNC/WETH - 0.05% - ● In range - - - - - - V3 - - - - #24654 - - - 0x1234...abcd - - - - - + <> + {liquidityWidget} + + {isLoading && !firstLoading.current ? ( + + ) : position && Object.keys(position).length > 0 ? ( + <> + + navigate(-1)} /> + + + + + + + + + {position.token0Symbol}/{position.token1Symbol} + + {position.poolFee && {position.poolFee}%} + + + + #24654 + + + ● {position.status === PositionStatus.IN_RANGE ? t`In range` : t`Out of range`} + + + + {position.poolAddress ? shortenAddress(position.chainId as ChainId, position.poolAddress, 4) : ''} + + + + + + + + {position.dex} + + + + + + + + + + + + {t`Total Liquidity`} + + + + {formatDisplayNumber(position.totalValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {formatDisplayNumber(position.token0TotalAmount, { significantDigits: 6 })} + {position.token0Symbol} + + + + {formatDisplayNumber(position.token1TotalAmount, { significantDigits: 6 })} + {position.token1Symbol} + + + + + + + {t`Est. Position APR`} + + + + 0 ? theme.primary : theme.text}> + {formatDisplayNumber(position.apr * 100, { + significantDigits: position.apr < 0.01 ? 2 : position.apr < 0.1 ? 3 : 4, + })} + % + + + + + {t`Fee Earn`} + + + + + 1 {t`day`} + + + {formatDisplayNumber(earning.earning24h, { significantDigits: 4, style: 'currency' })} + + + + + + 7 {t`days`} + + + {formatDisplayNumber(earning.earning7d, { significantDigits: 4, style: 'currency' })} + + + + + + {t`All`} + + + {formatDisplayNumber(position.totalEarnedFee, { style: 'currency', significantDigits: 4 })} + + + + + + + {t`Total Unclaimed Fee`} + + + + {formatDisplayNumber(position.totalUnclaimedFee, { style: 'currency', significantDigits: 4 })} + + + {formatDisplayNumber(position.token0UnclaimedAmount, { significantDigits: 4 })} + {position.token0Symbol} + + {formatDisplayNumber(position.token0UnclaimedValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {formatDisplayNumber(position.token1UnclaimedAmount, { significantDigits: 4 })} + {position.token1Symbol} + + {formatDisplayNumber(position.token1UnclaimedValue, { + style: 'currency', + significantDigits: 4, + })} + + + + + + + + + + {t`Current Price`} + + + {formatDisplayNumber(!revert ? position.pairRate : 1 / position.pairRate, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol} per{' '} + {!revert ? position.token1Symbol : position.token0Symbol} + + setRevert(!revert)}> + + + + + + + + {t`Min Price`} + + + {formatDisplayNumber(!revert ? position.minPrice : 1 / position.maxPrice, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol}/ + {!revert ? position.token1Symbol : position.token0Symbol} + + + + + {t`Max Price`} + + + {formatDisplayNumber(!revert ? position.maxPrice : 1 / position.minPrice, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol}/ + {!revert ? position.token1Symbol : position.token0Symbol} + + + + + + + {t`Remove Liquidity`} + {t`Add Liquidity`} + + + + ) : ( + {t`No position found`} + )} + + ) } diff --git a/src/pages/Earns/PositionDetail/styles.tsx b/src/pages/Earns/PositionDetail/styles.tsx index e69de29bb2..1ee758effe 100644 --- a/src/pages/Earns/PositionDetail/styles.tsx +++ b/src/pages/Earns/PositionDetail/styles.tsx @@ -0,0 +1,141 @@ +import styled from 'styled-components' + +import { ReactComponent as IconArrowLeftSvg } from 'assets/svg/ic_left_arrow.svg' + +export const IconArrowLeft = styled(IconArrowLeftSvg)` + cursor: pointer; + position: relative; + top: 5px; + color: ${({ theme }) => theme.subText}; + + :hover { + filter: brightness(1.5); + } +` + +export const DexInfo = styled.div<{ openable: boolean }>` + display: flex; + align-items: center; + gap: 6px; + color: ${({ theme }) => theme.text}; + + ${({ openable }) => openable && 'cursor: pointer;'} + :hover { + ${({ openable }) => openable && 'filter: brightness(1.2);'} + } +` + +export const PositionDetailWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 36px; + padding: 36px; + border-radius: 20px; + background-color: ${({ theme }) => theme.background}; + width: 100%; + + ${({ theme }) => theme.mediaWidth.upToMedium` + padding: 24px; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + border-radius: 0; + margin: 0 -16px; + width: calc(100% + 32px); + padding: 20px 16px; + `} +` + +export const MainSection = styled.div` + display: flex; + gap: 36px; + + ${({ theme }) => theme.mediaWidth.upToMedium` + flex-direction: column; + `} +` + +export const InfoColumn = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +` + +export const InfoLeftColumn = styled(InfoColumn)` + flex: 0 1 35%; +` + +export const InfoRightColumn = styled(InfoColumn)` + flex: 1 1 65%; +` + +export const InfoSection = styled.div` + border-radius: 16px; + padding: 16px 24px; + border: 1px solid ${({ theme }) => theme.tabActive}; +` + +export const InfoSectionFirstFormat = styled(InfoSection)` + display: flex; + align-items: flex-start; + justify-content: space-between; +` + +export const InfoSectionSecondFormat = styled(InfoSection)` + flex: 1 1 50%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +` + +export const InfoRight = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 8px; +` + +export const VerticalDivider = styled.div` + width: 1px; + height: 32px; + background: ${({ theme }) => theme.tabActive}; +` + +export const RevertIconWrapper = styled.div` + cursor: pointer; + + :hover { + filter: brightness(0.9); + } +` + +export const PositionActionWrapper = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; + gap: 24px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + gap: 16px; + `} +` + +export const PositionAction = styled.button<{ outline?: boolean }>` + border-radius: 24px; + padding: 10px 18px; + background-color: ${({ theme }) => theme.primary}; + border: 1px solid ${({ theme }) => theme.primary}; + cursor: pointer; + + ${({ outline }) => outline && 'background-color: transparent;'} + ${({ outline, theme }) => outline && `color: ${theme.primary};`} + + ${({ theme }) => theme.mediaWidth.upToSmall` + width: 100%; + `} + + :hover { + filter: brightness(1.2); + } +` diff --git a/src/pages/Earns/UserPositions/index.tsx b/src/pages/Earns/UserPositions/index.tsx index 69ba8a2086..8582d9a75f 100644 --- a/src/pages/Earns/UserPositions/index.tsx +++ b/src/pages/Earns/UserPositions/index.tsx @@ -4,7 +4,7 @@ import { Minus, Plus } from 'react-feather' import { useNavigate } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' -import { PositionStatus, useUserPositionQuery } from 'services/krystalEarn' +import { EarnPosition, PositionStatus, useUserPositionQuery } from 'services/krystalEarn' import CopyHelper from 'components/Copy' import LocalLoader from 'components/LocalLoader' @@ -50,6 +50,18 @@ const MyPositions = () => { { skip: !account, pollingInterval: 15_000 }, ) + const onOpenIncreaseLiquidityWidget = (e: React.MouseEvent, position: EarnPosition) => { + e.stopPropagation() + handleOpenZapInWidget( + { + exchange: position.pool.project || '', + chainId: position.chainId, + address: position.pool.poolAddress, + }, + position.tokenId, + ) + } + useEffect(() => { if (!firstLoading.current && !isLoading) { firstLoading.current = true @@ -73,182 +85,157 @@ const MyPositions = () => { {isLoading && !firstLoading.current ? ( ) : userPosition && userPosition.length > 0 ? ( - userPosition.map(position => ( - navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL.replace(':id', position.id) })} - > - - - - - - - - - {position.pool.tokenAmounts[0]?.token.symbol || ''}/ - {position.pool.tokenAmounts[1]?.token.symbol || ''} - - {position.pool.fees?.length > 0 && {position.pool.fees[0]}%} - - ● {position.status === PositionStatus.IN_RANGE ? 'In range' : 'Out of range'} - - - - - - - {position.pool.project?.split(' ')?.[1] || ''} + userPosition.map(position => { + const { id, status, chainId: poolChainId } = position + const positionId = position.tokenId + const chainImage = position.chainLogo + const dexImage = position.pool.projectLogo + const dexVersion = position.pool.project?.split(' ')?.[1] || '' + const token0Logo = position.pool.tokenAmounts[0]?.token.logo + const token1Logo = position.pool.tokenAmounts[1]?.token.logo + const token0Symbol = position.pool.tokenAmounts[0]?.token.symbol + const token1Symbol = position.pool.tokenAmounts[1]?.token.symbol + const poolFee = position.pool.fees?.[0] + const poolAddress = position.pool.poolAddress + const totalValue = position.currentPositionValue + const token0TotalAmount = + position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price + const token1TotalAmount = + position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price + const token0EarnedAmount = + position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price + + position.feesClaimed[0]?.quotes.usd.value / position.feesClaimed[0]?.quotes.usd.price + const token1EarnedAmount = + position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price + + position.feesClaimed[1]?.quotes.usd.value / position.feesClaimed[1]?.quotes.usd.price + const totalEarnedFee = + position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0) + + position.feesClaimed.reduce((a, b) => a + b.quotes.usd.value, 0) + + return ( + navigate({ pathname: APP_PATHS.EARN_POSITION_DETAIL.replace(':id', id) })} + > + + + + + + + + + {token0Symbol}/{token1Symbol} + {poolFee && {poolFee}%} + + ● {status === PositionStatus.IN_RANGE ? t`In range` : t`Out of range`} + - - #{position.tokenId} - - - {shortenAddress(position.chainId, position.pool.poolAddress, 4)} - - - - - {upToLarge && !upToSmall && ( - - - - - { - e.stopPropagation() - handleOpenZapInWidget( - { - exchange: position.pool.project || '', - chainId: position.chainId, - address: position.pool.poolAddress, - }, - position.tokenId, - ) - }} - > - - - - )} - - Value - - - {formatDisplayNumber( - position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price, - { significantDigits: 6 }, - )}{' '} - {position.pool.tokenAmounts[0]?.token.symbol || ''} - - - {formatDisplayNumber( - position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price, - { significantDigits: 6 }, - )}{' '} - {position.pool.tokenAmounts[1]?.token.symbol || ''} - - - } - width="fit-content" - placement="bottom" - > - - {formatDisplayNumber(position.currentPositionValue, { style: 'currency', significantDigits: 4 })} - - - - - Earned Fee - - - {formatDisplayNumber( - position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price, - { significantDigits: 6 }, - )}{' '} - {position.pool.tokenAmounts[0]?.token.symbol || ''} - - - {formatDisplayNumber( - position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price, - { significantDigits: 6 }, - )}{' '} - {position.pool.tokenAmounts[1]?.token.symbol || ''} + + + + + {dexVersion} - - } - width="fit-content" - placement="bottom" - > - - {formatDisplayNumber( - position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0), - { style: 'currency', significantDigits: 4 }, - )} - - - - - Bal - - - {formatDisplayNumber( - position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price, - { significantDigits: 4 }, - )}{' '} - {position.pool.tokenAmounts[0]?.token.symbol || ''} - - {upToSmall && } - - {formatDisplayNumber( - position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price, - { significantDigits: 4 }, - )}{' '} - {position.pool.tokenAmounts[1]?.token.symbol || ''} - - - - {(upToSmall || !upToLarge) && ( - - + + + #{positionId} + + + {shortenAddress(poolChainId, poolAddress, 4)} + + + + + {upToLarge && !upToSmall && ( + + onOpenIncreaseLiquidityWidget(e, position)}> + + + + )} + + {t`Value`} + + + {formatDisplayNumber(token0TotalAmount, { significantDigits: 6 })} {token0Symbol} + + + {formatDisplayNumber(token1TotalAmount, { significantDigits: 6 })} {token1Symbol} + + + } + width="fit-content" + placement="bottom" + > + + {formatDisplayNumber(totalValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {t`Earned Fee`} + + {formatDisplayNumber(token0EarnedAmount, { significantDigits: 6 })} {token0Symbol} + + + {formatDisplayNumber(token1EarnedAmount, { significantDigits: 6 })} {token1Symbol} + + + } + width="fit-content" + placement="bottom" > - { - e.stopPropagation() - handleOpenZapInWidget( - { - exchange: position.pool.project || '', - chainId: position.chainId, - address: position.pool.poolAddress, - }, - position.tokenId, - ) - }} - > - - + {formatDisplayNumber(totalEarnedFee, { style: 'currency', significantDigits: 4 })} - - )} - - )) + + + Balance + + + {formatDisplayNumber(token0TotalAmount, { significantDigits: 4 })} {token0Symbol} + + {upToSmall && } + + {formatDisplayNumber(token1TotalAmount, { significantDigits: 4 })} {token1Symbol} + + + + {(upToSmall || !upToLarge) && ( + + + + + + + + onOpenIncreaseLiquidityWidget(e, position)}> + + + + + )} + + ) + }) ) : ( You haven't had any positions yet! )} diff --git a/src/services/krystalEarn.ts b/src/services/krystalEarn.ts index c60ab67928..4848024a5d 100644 --- a/src/services/krystalEarn.ts +++ b/src/services/krystalEarn.ts @@ -1,5 +1,18 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +export enum EarnSupportedProtocols { + UNISWAP_V3 = 'Uniswap V3', + PANCAKESWAP_V3 = 'PancakeSwap V3', + SUSHISWAP_V3 = 'SushiSwap V3', +} +export const earnSupportedChains = [ChainId.MAINNET, ChainId.BASE, ChainId.ARBITRUM] +export const earnSupportedProtocols = [ + EarnSupportedProtocols.UNISWAP_V3, + EarnSupportedProtocols.PANCAKESWAP_V3, + EarnSupportedProtocols.SUSHISWAP_V3, +] + export enum PositionStatus { IN_RANGE = 'IN_RANGE', OUT_RANGE = 'OUT_RANGE', @@ -97,6 +110,15 @@ export interface EarnPosition { } } +export interface PositionEarning { + date: string + timestamp: number + totalFeeEarning: number + totalFarmEarning: number + totalEarning: number + earningByDay: number +} + const krystalEarnServiceApi = createApi({ reducerPath: 'krystalEarnServiceApi', baseQuery: fetchBaseQuery({ @@ -104,13 +126,13 @@ const krystalEarnServiceApi = createApi({ }), keepUnusedDataFor: 1, endpoints: builder => ({ - userPosition: builder.query, { addresses: string }>({ + userPosition: builder.query, { addresses: string; positionId?: string }>({ query: params => ({ url: `/v1/lp/userPositions`, params: { ...params, - chainIds: [1, 8453], // ETH & Base - protocols: 'Uniswap V3,PancakeSwap V3,SushiSwap V3', + chainIds: earnSupportedChains, + protocols: earnSupportedProtocols, quoteSymbol: 'usd', offset: 0, orderBy: 'liquidity', @@ -120,9 +142,19 @@ const krystalEarnServiceApi = createApi({ }), transformResponse: (response: { positions: Array }) => response.positions, }), + positionEarningStatistics: builder.query< + Array, + { tokenAddress: string; tokenId: string; chainId: string | number } + >({ + query: params => ({ + url: `/v1/balance/positionEarningStatistic`, + params, + }), + transformResponse: (response: { data: Array }) => response.data, + }), }), }) -export const { useUserPositionQuery } = krystalEarnServiceApi +export const { useUserPositionQuery, usePositionEarningStatisticsQuery } = krystalEarnServiceApi export default krystalEarnServiceApi From 51c6f1b7cd434abd8d4d94277d19c2c3b39bafca Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Sat, 14 Dec 2024 01:14:59 +0700 Subject: [PATCH 82/83] improve code struture position detail --- src/pages/Earns/PositionDetail/Header.tsx | 81 ++++ .../Earns/PositionDetail/LeftSection.tsx | 141 +++++++ .../Earns/PositionDetail/RightSection.tsx | 71 ++++ src/pages/Earns/PositionDetail/index.tsx | 386 ++++-------------- 4 files changed, 365 insertions(+), 314 deletions(-) create mode 100644 src/pages/Earns/PositionDetail/Header.tsx create mode 100644 src/pages/Earns/PositionDetail/LeftSection.tsx create mode 100644 src/pages/Earns/PositionDetail/RightSection.tsx diff --git a/src/pages/Earns/PositionDetail/Header.tsx b/src/pages/Earns/PositionDetail/Header.tsx new file mode 100644 index 0000000000..330b89765b --- /dev/null +++ b/src/pages/Earns/PositionDetail/Header.tsx @@ -0,0 +1,81 @@ +import { ChainId } from '@kyberswap/ks-sdk-core' +import { t } from '@lingui/macro' +import { useNavigate } from 'react-router-dom' +import { useMedia } from 'react-use' +import { Flex, Text } from 'rebass' +import { EarnSupportedProtocols, PositionStatus, earnSupportedProtocols } from 'services/krystalEarn' + +import CopyHelper from 'components/Copy' +import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' +import useTheme from 'hooks/useTheme' +import { MEDIA_WIDTHS } from 'theme' +import { shortenAddress } from 'utils' + +import { ParsedPosition } from '.' +import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' +import { Badge, BadgeType, ChainImage, DexImage, ImageContainer, PositionOverview } from '../UserPositions/styles' +import { DexInfo, IconArrowLeft } from './styles' + +const PositionDetailHeader = ({ position }: { position: ParsedPosition }) => { + const theme = useTheme() + const navigate = useNavigate() + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + const onOpenPositionInDexSite = () => { + if (!position || !earnSupportedProtocols.includes(position.dex)) return + + if (position.dex === EarnSupportedProtocols.UNISWAP_V3) + window.open(`https://app.uniswap.org/positions/v3/${position.chainName}/${position.id}`) + else if (position.dex === EarnSupportedProtocols.SUSHISWAP_V3) + window.open(`https://www.sushi.com/${position.chainName}/pool/v3/${position.poolAddress}/${position.id}`) + else if (position.dex === EarnSupportedProtocols.PANCAKESWAP_V3) + window.open(`https://pancakeswap.finance/liquidity/${position.id}`) + } + + return ( + + navigate(-1)} /> + + + + + + + + + {position.token0Symbol}/{position.token1Symbol} + + {position.poolFee && {position.poolFee}%} + + + + #24654 + + + ● {position.status === PositionStatus.IN_RANGE ? t`In range` : t`Out of range`} + + + + {position.poolAddress ? shortenAddress(position.chainId as ChainId, position.poolAddress, 4) : ''} + + + + + + + + {position.dex} + + + + + + + ) +} + +export default PositionDetailHeader diff --git a/src/pages/Earns/PositionDetail/LeftSection.tsx b/src/pages/Earns/PositionDetail/LeftSection.tsx new file mode 100644 index 0000000000..952f19e6f3 --- /dev/null +++ b/src/pages/Earns/PositionDetail/LeftSection.tsx @@ -0,0 +1,141 @@ +import { t } from '@lingui/macro' +import { useMemo } from 'react' +import { Flex, Text } from 'rebass' +import { usePositionEarningStatisticsQuery } from 'services/krystalEarn' + +import InfoHelper from 'components/InfoHelper' +import useTheme from 'hooks/useTheme' +import { formatDisplayNumber } from 'utils/numbers' + +import { ParsedPosition } from '.' +import { DexImage } from '../UserPositions/styles' +import { InfoLeftColumn, InfoRight, InfoSection, InfoSectionFirstFormat, VerticalDivider } from './styles' + +const LeftSection = ({ position }: { position: ParsedPosition }) => { + const theme = useTheme() + + const { data: positionEarningStatistics } = usePositionEarningStatisticsQuery( + { + tokenAddress: position.tokenAddress, + tokenId: position.id, + chainId: position.chainId, + }, + { skip: !position, pollingInterval: 15_000 }, + ) + + const earning = useMemo(() => { + if (!positionEarningStatistics || !positionEarningStatistics.length) return {} + + const reversedPositionEarningStatistics = [...positionEarningStatistics].reverse() + const earning24h = reversedPositionEarningStatistics[0].totalEarning + const earning7d = reversedPositionEarningStatistics.slice(0, 7).reduce((a, b) => a + b.totalEarning, 0) + + return { + earning24h, + earning7d, + } + }, [positionEarningStatistics]) + + return ( + + + + {t`Total Liquidity`} + + + + {formatDisplayNumber(position.totalValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {formatDisplayNumber(position.token0TotalAmount, { significantDigits: 6 })} + {position.token0Symbol} + + + + {formatDisplayNumber(position.token1TotalAmount, { significantDigits: 6 })} + {position.token1Symbol} + + + + + + + {t`Est. Position APR`} + + + + 0 ? theme.primary : theme.text}> + {formatDisplayNumber(position.apr * 100, { + significantDigits: position.apr < 0.01 ? 2 : position.apr < 0.1 ? 3 : 4, + })} + % + + + + + {t`Fee Earn`} + + + + + 1 {t`day`} + + {formatDisplayNumber(earning.earning24h, { significantDigits: 4, style: 'currency' })} + + + + + 7 {t`days`} + + {formatDisplayNumber(earning.earning7d, { significantDigits: 4, style: 'currency' })} + + + + + {t`All`} + + + {formatDisplayNumber(position.totalEarnedFee, { style: 'currency', significantDigits: 4 })} + + + + + + + {t`Total Unclaimed Fee`} + + + + {formatDisplayNumber(position.totalUnclaimedFee, { style: 'currency', significantDigits: 4 })} + + + {formatDisplayNumber(position.token0UnclaimedAmount, { significantDigits: 4 })} + {position.token0Symbol} + + {formatDisplayNumber(position.token0UnclaimedValue, { + style: 'currency', + significantDigits: 4, + })} + + + + {formatDisplayNumber(position.token1UnclaimedAmount, { significantDigits: 4 })} + {position.token1Symbol} + + {formatDisplayNumber(position.token1UnclaimedValue, { + style: 'currency', + significantDigits: 4, + })} + + + + + + ) +} + +export default LeftSection diff --git a/src/pages/Earns/PositionDetail/RightSection.tsx b/src/pages/Earns/PositionDetail/RightSection.tsx new file mode 100644 index 0000000000..81b23aca95 --- /dev/null +++ b/src/pages/Earns/PositionDetail/RightSection.tsx @@ -0,0 +1,71 @@ +import { t } from '@lingui/macro' +import { useState } from 'react' +import { Flex, Text } from 'rebass' + +import { Swap as SwapIcon } from 'components/Icons' +import useTheme from 'hooks/useTheme' +import { formatDisplayNumber } from 'utils/numbers' + +import { ParsedPosition } from '.' +import { InfoRightColumn, InfoSection, InfoSectionSecondFormat, RevertIconWrapper } from './styles' + +const RightSection = ({ position }: { position: ParsedPosition }) => { + const theme = useTheme() + const [revert, setRevert] = useState(false) + + return ( + + + + + {t`Current Price`} + + + {formatDisplayNumber(!revert ? position.pairRate : 1 / position.pairRate, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol} per{' '} + {!revert ? position.token1Symbol : position.token0Symbol} + + setRevert(!revert)}> + + + + + + + + {t`Min Price`} + + + {formatDisplayNumber(!revert ? position.minPrice : 1 / position.maxPrice, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol}/ + {!revert ? position.token1Symbol : position.token0Symbol} + + + + + {t`Max Price`} + + + {formatDisplayNumber(!revert ? position.maxPrice : 1 / position.minPrice, { + significantDigits: 6, + })} + + + {!revert ? position.token0Symbol : position.token1Symbol}/ + {!revert ? position.token1Symbol : position.token0Symbol} + + + + + ) +} + +export default RightSection diff --git a/src/pages/Earns/PositionDetail/index.tsx b/src/pages/Earns/PositionDetail/index.tsx index 356eb7a134..cc501b3fa6 100644 --- a/src/pages/Earns/PositionDetail/index.tsx +++ b/src/pages/Earns/PositionDetail/index.tsx @@ -1,61 +1,49 @@ -import { ChainId } from '@kyberswap/ks-sdk-core' import { t } from '@lingui/macro' -import { useEffect, useMemo, useRef, useState } from 'react' -import { useNavigate, useParams } from 'react-router-dom' -import { useMedia } from 'react-use' -import { Flex, Text } from 'rebass' -import { - EarnSupportedProtocols, - PositionStatus, - earnSupportedProtocols, - usePositionEarningStatisticsQuery, - useUserPositionQuery, -} from 'services/krystalEarn' +import { useEffect, useMemo, useRef } from 'react' +import { useParams } from 'react-router-dom' +import { useUserPositionQuery } from 'services/krystalEarn' -import CopyHelper from 'components/Copy' -import { Swap as SwapIcon } from 'components/Icons' -import InfoHelper from 'components/InfoHelper' import LocalLoader from 'components/LocalLoader' -import { MouseoverTooltipDesktopOnly } from 'components/Tooltip' import { useActiveWeb3React } from 'hooks' -import useTheme from 'hooks/useTheme' -import { MEDIA_WIDTHS } from 'theme' -import { shortenAddress } from 'utils' -import { formatDisplayNumber } from 'utils/numbers' -import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles' -import { - Badge, - BadgeType, - ChainImage, - DexImage, - EmptyPositionText, - ImageContainer, - PositionOverview, - PositionPageWrapper, -} from '../UserPositions/styles' +import { EmptyPositionText, PositionPageWrapper } from '../UserPositions/styles' import useLiquidityWidget from '../useLiquidityWidget' -import { - DexInfo, - IconArrowLeft, - InfoLeftColumn, - InfoRight, - InfoRightColumn, - InfoSection, - InfoSectionFirstFormat, - InfoSectionSecondFormat, - MainSection, - PositionAction, - PositionActionWrapper, - PositionDetailWrapper, - RevertIconWrapper, - VerticalDivider, -} from './styles' +import PositionDetailHeader from './Header' +import LeftSection from './LeftSection' +import RightSection from './RightSection' +import { MainSection, PositionAction, PositionActionWrapper, PositionDetailWrapper } from './styles' + +export interface ParsedPosition { + id: string + dex: string + dexImage: string + chainId: number + chainName: string + chainLogo: string + poolAddress: string + tokenAddress: string + token0Logo: string + token1Logo: string + token0Symbol: string + token1Symbol: string + poolFee: number + status: string + totalValue: number + apr: number + token0TotalAmount: number + token1TotalAmount: number + minPrice: number + maxPrice: number + pairRate: number + totalUnclaimedFee: number + token0UnclaimedAmount: number + token1UnclaimedAmount: number + token0UnclaimedValue: number + token1UnclaimedValue: number + totalEarnedFee: number +} const PositionDetail = () => { - const theme = useTheme() - const navigate = useNavigate() - const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) const firstLoading = useRef(false) const { account } = useActiveWeb3React() @@ -65,87 +53,49 @@ const PositionDetail = () => { { addresses: account || '', positionId: id }, { skip: !account, pollingInterval: 15_000 }, ) - const { data: positionEarningStatistics } = usePositionEarningStatisticsQuery( - { - tokenAddress: userPosition?.[0]?.tokenAddress || '', - tokenId: userPosition?.[0]?.tokenId || '', - chainId: userPosition?.[0]?.chainId || '', - }, - { skip: !userPosition?.[0], pollingInterval: 15_000 }, - ) - const [revert, setRevert] = useState(false) - - const position = useMemo(() => { + const position: ParsedPosition | undefined = useMemo(() => { if (!userPosition?.[0]) return const position = userPosition?.[0] return { - id: position?.tokenId, - dex: position?.pool.project || '', - dexImage: position?.pool.projectLogo || '', - chainId: position?.chainId, - chainName: position?.chainName, - chainLogo: position?.chainLogo || '', - poolAddress: position?.pool.poolAddress || '', - token0Logo: position?.pool.tokenAmounts[0]?.token.logo || '', - token1Logo: position?.pool.tokenAmounts[1]?.token.logo || '', - token0Symbol: position?.pool.tokenAmounts[0]?.token.symbol || '', - token1Symbol: position?.pool.tokenAmounts[1]?.token.symbol || '', - poolFee: position?.pool.fees?.[0], - status: position?.status, - totalValue: position?.currentPositionValue, - apr: position?.apr || 0, + id: position.tokenId, + dex: position.pool.project || '', + dexImage: position.pool.projectLogo || '', + chainId: position.chainId, + chainName: position.chainName, + chainLogo: position.chainLogo || '', + poolAddress: position.pool.poolAddress || '', + tokenAddress: position.tokenAddress, + token0Logo: position.pool.tokenAmounts[0]?.token.logo || '', + token1Logo: position.pool.tokenAmounts[1]?.token.logo || '', + token0Symbol: position.pool.tokenAmounts[0]?.token.symbol || '', + token1Symbol: position.pool.tokenAmounts[1]?.token.symbol || '', + poolFee: position.pool.fees?.[0], + status: position.status, + totalValue: position.currentPositionValue, + apr: position.apr || 0, token0TotalAmount: position - ? position?.currentAmounts[0]?.quotes.usd.value / position?.currentAmounts[0]?.quotes.usd.price + ? position.currentAmounts[0]?.quotes.usd.value / position.currentAmounts[0]?.quotes.usd.price : 0, token1TotalAmount: position - ? position?.currentAmounts[1]?.quotes.usd.value / position?.currentAmounts[1]?.quotes.usd.price + ? position.currentAmounts[1]?.quotes.usd.value / position.currentAmounts[1]?.quotes.usd.price : 0, - minPrice: position?.minPrice || 0, - maxPrice: position?.maxPrice || 0, - pairRate: position?.pool.price || 0, + minPrice: position.minPrice || 0, + maxPrice: position.maxPrice || 0, + pairRate: position.pool.price || 0, totalUnclaimedFee: - (position?.feePending?.[0].quotes.usd.value || 0) + (position?.feePending?.[1].quotes.usd.value || 0), - token0UnclaimedAmount: position - ? position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price - : 0, - token1UnclaimedAmount: position - ? position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price - : 0, - token0UnclaimedValue: position?.feePending[0]?.quotes.usd.value, - token1UnclaimedValue: position?.feePending[1]?.quotes.usd.value, - totalEarnedFee: position - ? position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0) + - position.feesClaimed.reduce((a, b) => a + b.quotes.usd.value, 0) - : 0, + (position.feePending?.[0].quotes.usd.value || 0) + (position.feePending?.[1].quotes.usd.value || 0), + token0UnclaimedAmount: position.feePending[0]?.quotes.usd.value / position.feePending[0]?.quotes.usd.price, + token1UnclaimedAmount: position.feePending[1]?.quotes.usd.value / position.feePending[1]?.quotes.usd.price, + token0UnclaimedValue: position.feePending[0]?.quotes.usd.value, + token1UnclaimedValue: position.feePending[1]?.quotes.usd.value, + totalEarnedFee: + position.feePending.reduce((a, b) => a + b.quotes.usd.value, 0) + + position.feesClaimed.reduce((a, b) => a + b.quotes.usd.value, 0), } }, [userPosition]) - const earning = useMemo(() => { - if (!positionEarningStatistics || !positionEarningStatistics.length) return {} - - const reversedPositionEarningStatistics = [...positionEarningStatistics].reverse() - const earning24h = reversedPositionEarningStatistics[0].totalEarning - const earning7d = reversedPositionEarningStatistics.slice(0, 7).reduce((a, b) => a + b.totalEarning, 0) - - return { - earning24h, - earning7d, - } - }, [positionEarningStatistics]) - - const onOpenPositionInDexSite = () => { - if (!position || !earnSupportedProtocols.includes(position.dex)) return - - if (position.dex === EarnSupportedProtocols.UNISWAP_V3) - window.open(`https://app.uniswap.org/positions/v3/${position.chainName}/${position.id}`) - else if (position.dex === EarnSupportedProtocols.SUSHISWAP_V3) - window.open(`https://www.sushi.com/${position.chainName}/pool/v3/${position.poolAddress}/${position.id}`) - else if (position.dex === EarnSupportedProtocols.PANCAKESWAP_V3) - window.open(`https://pancakeswap.finance/liquidity/${position.id}`) - } - const onOpenIncreaseLiquidityWidget = () => { if (!position) return handleOpenZapInWidget( @@ -170,205 +120,13 @@ const PositionDetail = () => { {isLoading && !firstLoading.current ? ( - ) : position && Object.keys(position).length > 0 ? ( + ) : !!position ? ( <> - - navigate(-1)} /> - - - - - - - - - {position.token0Symbol}/{position.token1Symbol} - - {position.poolFee && {position.poolFee}%} - - - - #24654 - - - ● {position.status === PositionStatus.IN_RANGE ? t`In range` : t`Out of range`} - - - - {position.poolAddress ? shortenAddress(position.chainId as ChainId, position.poolAddress, 4) : ''} - - - - - - - - {position.dex} - - - - - - + - - - - {t`Total Liquidity`} - - - - {formatDisplayNumber(position.totalValue, { - style: 'currency', - significantDigits: 4, - })} - - - - {formatDisplayNumber(position.token0TotalAmount, { significantDigits: 6 })} - {position.token0Symbol} - - - - {formatDisplayNumber(position.token1TotalAmount, { significantDigits: 6 })} - {position.token1Symbol} - - - - - - - {t`Est. Position APR`} - - - - 0 ? theme.primary : theme.text}> - {formatDisplayNumber(position.apr * 100, { - significantDigits: position.apr < 0.01 ? 2 : position.apr < 0.1 ? 3 : 4, - })} - % - - - - - {t`Fee Earn`} - - - - - 1 {t`day`} - - - {formatDisplayNumber(earning.earning24h, { significantDigits: 4, style: 'currency' })} - - - - - - 7 {t`days`} - - - {formatDisplayNumber(earning.earning7d, { significantDigits: 4, style: 'currency' })} - - - - - - {t`All`} - - - {formatDisplayNumber(position.totalEarnedFee, { style: 'currency', significantDigits: 4 })} - - - - - - - {t`Total Unclaimed Fee`} - - - - {formatDisplayNumber(position.totalUnclaimedFee, { style: 'currency', significantDigits: 4 })} - - - {formatDisplayNumber(position.token0UnclaimedAmount, { significantDigits: 4 })} - {position.token0Symbol} - - {formatDisplayNumber(position.token0UnclaimedValue, { - style: 'currency', - significantDigits: 4, - })} - - - - {formatDisplayNumber(position.token1UnclaimedAmount, { significantDigits: 4 })} - {position.token1Symbol} - - {formatDisplayNumber(position.token1UnclaimedValue, { - style: 'currency', - significantDigits: 4, - })} - - - - - - - - - - {t`Current Price`} - - - {formatDisplayNumber(!revert ? position.pairRate : 1 / position.pairRate, { - significantDigits: 6, - })} - - - {!revert ? position.token0Symbol : position.token1Symbol} per{' '} - {!revert ? position.token1Symbol : position.token0Symbol} - - setRevert(!revert)}> - - - - - - - - {t`Min Price`} - - - {formatDisplayNumber(!revert ? position.minPrice : 1 / position.maxPrice, { - significantDigits: 6, - })} - - - {!revert ? position.token0Symbol : position.token1Symbol}/ - {!revert ? position.token1Symbol : position.token0Symbol} - - - - - {t`Max Price`} - - - {formatDisplayNumber(!revert ? position.maxPrice : 1 / position.minPrice, { - significantDigits: 6, - })} - - - {!revert ? position.token0Symbol : position.token1Symbol}/ - {!revert ? position.token1Symbol : position.token0Symbol} - - - - + + {t`Remove Liquidity`} From 06e76854828ca2708b420029d0a9b60da570be4a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Sat, 14 Dec 2024 01:17:15 +0700 Subject: [PATCH 83/83] remove arb from list supported chain --- src/services/krystalEarn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/krystalEarn.ts b/src/services/krystalEarn.ts index 4848024a5d..8540a931ea 100644 --- a/src/services/krystalEarn.ts +++ b/src/services/krystalEarn.ts @@ -6,7 +6,7 @@ export enum EarnSupportedProtocols { PANCAKESWAP_V3 = 'PancakeSwap V3', SUSHISWAP_V3 = 'SushiSwap V3', } -export const earnSupportedChains = [ChainId.MAINNET, ChainId.BASE, ChainId.ARBITRUM] +export const earnSupportedChains = [ChainId.MAINNET, ChainId.BASE] export const earnSupportedProtocols = [ EarnSupportedProtocols.UNISWAP_V3, EarnSupportedProtocols.PANCAKESWAP_V3,