From 983be94b1d33388187c2814ed741c517d79727a3 Mon Sep 17 00:00:00 2001 From: Junho Yeo Date: Mon, 10 Oct 2022 01:50:26 +0900 Subject: [PATCH] [web] Cache Klaytn DeFi stats with Redis (cache-first for 1m & fallback) (#282) --- .pnp.cjs | 10 ++ ...-json-npm-1.0.16-ca065623db-ef3823d278.zip | Bin 0 -> 32577 bytes .../bento-web/@types/compressed-json.d.ts | 8 ++ packages/bento-web/package.json | 1 + .../pages/api/defis/klaytn/[walletAddress].ts | 110 ++++++++++++++++-- packages/bento-web/tsconfig.json | 3 +- yarn.lock | 8 ++ 7 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 .yarn/cache/compressed-json-npm-1.0.16-ca065623db-ef3823d278.zip create mode 100644 packages/bento-web/@types/compressed-json.d.ts diff --git a/.pnp.cjs b/.pnp.cjs index 35d6a658..109858cb 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2623,6 +2623,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["caver-js", "npm:1.9.0"],\ ["clsx", "npm:1.2.1"],\ ["cobe", "npm:0.6.2"],\ + ["compressed-json", "npm:1.0.16"],\ ["date-fns", "npm:2.29.3"],\ ["dedent", "npm:0.7.0"],\ ["env-cmd", "virtual:19d75cef938ff992f79f7c2df615d8633195dd912a3c652a2d989db30e1123034740e16e4f3a7560d033a43c6ff373e9f7c6b2fac10646a68283eb915d32c6c4#npm:10.1.0"],\ @@ -7124,6 +7125,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["compressed-json", [\ + ["npm:1.0.16", {\ + "packageLocation": "./.yarn/cache/compressed-json-npm-1.0.16-ca065623db-ef3823d278.zip/node_modules/compressed-json/",\ + "packageDependencies": [\ + ["compressed-json", "npm:1.0.16"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["concat-map", [\ ["npm:0.0.1", {\ "packageLocation": "./.yarn/cache/concat-map-npm-0.0.1-85a921b7ee-902a9f5d89.zip/node_modules/concat-map/",\ diff --git a/.yarn/cache/compressed-json-npm-1.0.16-ca065623db-ef3823d278.zip b/.yarn/cache/compressed-json-npm-1.0.16-ca065623db-ef3823d278.zip new file mode 100644 index 0000000000000000000000000000000000000000..d94c59ef6d503e92daa40fe72390b1c046d3e049 GIT binary patch literal 32577 zcmb5Wbx=-QC^Y;o$D>aJcjC-Hq?=8?o`* zuOq6v|ER2v%*uMID)XuSDFX?G1@^D&d({%$f4uzX4f*fW*3Q^O&&JN!#oEM)`RD&w zmFVBA8rj*{JDNB-nHV!#I@#I&rziv%+<$z<0-IaU6#@*52?`91;-5rir9{N!mBn zRgFe9k%gdp-c}KkovO}|>g;YW$oYv^sEuM+daw2hQyEYIGlAP$%cR84 zT>>N*cYIE1;O^Og>e$h}L*3k#k?e2h zC!V?|VfXdmOBUy>TX0CNN*X+pQb+WAt-P2qRwV0{tRT5_-7T7Ol6uA&-~X5-`8{h` zC@$xGix*3j@#oMedf|;fz~tZ6Exl%DK(s(xpdsxeNWkLo$No`F?{zDYDpN1FqLdcL zk}M4w5^hC4?=r>l%SxwIlhix7rjQ}|i-stf^qpUYI^<0xYKa>EbXfQeN7-@#s*;LL zqn9SWdMQ7qx))YSq|o86bWqV;#`stNNZfa+2`vQ0Z~3+5yekz@iJ>uj%p6LI#^fr6 zv(K_Kg`8LA1R4=gElC{Ebqi&Zc!OaCmN31YO4^Dvmd{6Dxqn=_Fm8gAVe(5V`hAidI~7aA?;@-ban_4(KQ zpU=VwJ2vl00U54onU$9Y*mTVgi2tsU)mO-uxxa8W4fFp`BXdKiOch0ibr!V0sMazI zD=roX_{K@bX@O+%%djS-%yMHu4#?}e?*vD5y#;X9{E}B@cqUSqbtUM#1pr)vWo4DA z5L@YnC*uwc-C38FJB9~g(WNh`%1!0iSMkbVXdF5a46COF&c%g@S~_c&N-}+A6s4-e zBe`WhUXspW#sXA_+U2Abh0(SQtgP^ty0z`LN_zYfjHHFW3=*@rH%6_O!cDJj;>S$U z)Zi(*p;1FrPyg~$-NN#hm3fQnHj;YmV(E1V4%;Pn&O5n08OIkBDux9b7nD5=Ck4CP zS$2uME_kS2C9_>tChbyr!=cNcKixGn{AudU!^8t-oeQmb3bd{Ea<+MuAr6(C+Jl2? z{fe*#LX^RKYkI+|G}Z9aCrzEAq3`5J1lxp=PXLmSLHm?($O@|F%scA*w{=QiOF!H@ z2KI34jJ57JzS$paH`me|S15FB!&R~7$TBJO8(G8cS=B}}ak+O5}F_1 zj|3C;HGQ^vidEIEE(c_F8iAF)o~Oy_iR?-FrBDTDUG@VOd?CN@mg*L~`!yR3;>s!5 z0TXqnNa6OjtRYXg?rNQr&E*)jr{q|WWOczdX*|C;7Ga$QR_n2t zjE`hdJZ-L}#&%q8Y(aIzR7`Phma@h6z2doV@yUDoT4=YrjN6|LB&t;NtQN+&i*djC zinA?VzZXN674mLY=%pM@anRxqWNpBBU$b^|62}8Zr4^zlN31vox&CLvll_z3tSt=x z&Fr8pNqddICXa^%1Ecxhh#Oki8vmV>5|sq(0$8wsAM~3;As8#e6}enNI$e-8~h&(&`zL%ky)bC*N4SQ4>hCZ`O4#~oMb2&I^0Q|~8) z7`5FeiSW8oU}Q33;KmFOTSkClRF;fEqx?z{2~VcT?7Nnhee2h1{zlND4?A%n zx6bp2W;iUg_*z8MZo=_8ztl1;5qJ&e13XX(L$W>*5|wN?LN_z`v6`31%HoAH^$D68 zLQ=PCl%wXit)N9C-VCht6nZ$tdaAeiAZ-CUXvL=cYkOr|+w)XA50||_*ED@sTLyy9 z*bxPeN(RSMhvo#52LFv5#KQ&D=IsMqblrio`s~O`3-YR%+#}aGt*Jo)^?*+z+~-uJ z4AG|7itqp2yI=pQdz{QIZ2ql#l_UC~ongVigwVmjX#ZF8|JlEtsy0sPk{F;%z1~Gz zo4V2gnAK?VVrNena0v2MKQMT;Q|S)LAG?eY#y`hCy**98Uv0j*Ryj7NocOf8on)P~ zzTThweii+oz9k>lqg1Ao?)Z9twLu}R=9BcYnJktxpl+r)U`tHFdH_`$qBb{biJ{n? zl@WAxXqS*aFt6X#9e+?xn$`L4majWTmjAW zcv$xl#UYsn2CHvy5Zs&Ov%p8^=YmIFnMOYEf-wjESD~qTneQ0~PN!Q~avwLtwjV(t z;g9n9-0+pVV&QYo1-+2;r-ka~D~@wt_{%HUbJr4flV>EH<15KKey|ojYJb?+53x?t zn4^U+1A7A6Y4fC#gVFsZXDjZg2fpIJA98?!sMO$VEk$WWmTJ)-Q8BAyl!e0n`-60w zP4H{4+IB>7E(#x*nG`W^`F0iekJM#L1kpF7k>mWc4$D`tuMW#v5@chq^V@NR@%ozH z{1fKWmeU(aahFwtF;c5GnYn{^pV3Ke{^XBtKNLWnvjbGefk9RQcrB6{xrR_{J4&Z_Ok`bB$6zUE{-C zLLC5tnBuQ4386Cp;a;x?>j1O37d<<{w?tr_#I31eBcesDHJ1D)_p&L6Z7P99EU%Ki zGEF5L5$uJ&;RaJ8&4wgdrqt{Ou+}ouyv9}B+9U>sfYuEeIKm)6Utz5SA;3u3ywl>`|k;qKLQ5Nf4TEqLpSC(>FPr#?d4_M2zkkT3z!(p1phvA*EoA+L@KX z8yn$_D<9!5#%()3AoyT^&5uM{<@UW@QGKP7<<=qNb}gXg+bnSPm-$B3_M49Eo@O~I zNn8NUFWd3&ysh(dcoGBMBHhDP8|vlmMu)aB&Mki80yZ5J1;b6Q}*%l2wmd-*1zX?@MaIM?J{hSwMgE+F2FSEXveak-aaF~p+>lf|-bvWcsnFkLP&%(VDj0H`{a2a__|1(TBGWunmLV+&Z z7s*U|=>6hZdu3bQI!eU3d2NNvSo zFSv@iOs6t*(^Dle9}?;uSobGwT^uZjo>N0lA;E?z&NFE{ur2|q9XZt_or!t-0tbX? z(&+YZI9Jt4hae|~pZrGGR+vIPR@vu(GdHO10m5TB?-~?kBQz!$iY$N8cT$3qx)qpIl3R=0-+&R>Kn9y0x35>t!)}PS4QGRpRpeV7YJl_-^@xoPCnZ zJ@f=I)(>WZrVG^~lcUxkI3~qDCfEx2DfC^w$lrZd7S|vYrZg93t`xq#(=-+51X=fEh!bduoL8a>d+vD$(UNkWVXltk?DMQCoCj8ZlFXrl`t;m zbjqtId+Iof6u+OaT=ZCEOk>X_SkIk2$`v|oW(>Zeh|bEKsLX0g5)I7dXzhrWxCPFx z@-=5+>#rvT5+07&e5(cSPGsmyYTWym*k_wiFn635g@h`)VLrkhYX=Ywb zZmrfwn-=xRiaS1!w6d%NoPJgPSQf4)G|i_Vp15Flg?+op*T~cSj_j{ci$wMrMZsp( zZf5$(B9M|Ao@kpR4?X4Y`CR&)Nd9zqlE7yT#F6>hqRODT?ndYta?0d@TKo>kwMELz zT-~~bZRIBK6n#jQdk6{e>2Wpe2Y1ITf#v6?IIg^bbDaine^O1c1D0e9qY^zKRQTJB zS9FYMr^WtpzqU;~C3_T#UPH29+}Hump(uCD(S;)4)$CP0zM0QlP#h`(K<3PD@c+Kq#3iep<^3Bsn?wQwWB8}3`(F#Y|B4y> zU7ZD~+SsjgqJgUPdKF@^T5g-VyjTuxSJOBx)&@!fa{UaTDny6JjHM_`3;c6GyY~2K zSFHpG<~I7<=Qo@H8ChB4$5=LbQd3loZTq*kV2vX>ARC13WzWGGy<`BFD(+|KMnRqi>%jLHiL9xn@XACPQ zKxT|Dzk;rcd?*qy!w*B_u5_fNFwEwGp{ve*5%V-@Sw9G|OA%f7>)Gy@GGYW#DI*Ut z3m{>K-vq_p;K;C3+AH+sqJfgS3Q#=1;MHjIfN5uH3(Q8-5oq^-0q%_ziiw- zjvqXPv@0Aflh`tYy^I-5brJV=fF8IaUU3cGU|ZvJc`Y{h7wMvF>ukm}3lzlh@DL~! zu$n9T+>2M6atXWMy~$7GdTOyIw@xP#2xx^(Y#?PCrT0Ss3$&`&HA!WuOQz* zcH;P&bpAGA$8oTX%rS^MR`69WHyYo>yFuS+V`Z9 zMm&^*70BYF_=V3r^r`QQ)XEYxCIrfgkcAVjEpl^(oQ?v+_s2qsz_H7xF>7wcYeyB;A zd>yabAFFt3K2FB^0Ece*Eh`fLgc+gXh6k@+AMmZZ?PQ#L;3TrymUv$&0C<(q+ezFy0g1or`_`8wP#s3YVPX zf`J+QjbJkV69)aq_T&Enp+-$lr?ru!PoeDJoqV2q6VJR5=f;v%S5@F)A>djV7%{)- zf+(zw2gu>9_35^}r}MQe!KWr{FN)|WWxQYceUD}_u6#cptOfBgJ_B*cKMCVj_ZaSw^}W?+vn|NGF^0g*Tjc!bI=UY&^3!- znydcCa$jt(-Mzsx`HlREU-O{odtCce?LdS@+s?+3_VXV;OvISIhmG-fpGNg)wX@95 zboU6FB!0s#xz&tQJqBE?($SM=@vc{2v$116C1}Vc+N$o_bw>64nXvGqA*NWQQW_k{yW_eCIm4cBOQU|~S`ngy7#M!Wt{ z&=aR%u&}#eI6s!|Xnsrj=^pdD#UYa6cNQ;m$Tfp{(}ixtCRY`(ww!3l??SE};@uzX z5SC9mzjO)xWC_By7w5;e?w|9QI@@sUaO1`t%X@vN7^cl=@9LWRkc<0de0u6WVmren zqd12Hjs|AP`;uS7x19m4`U;9uqg9muW<`*;FD0unJs?-1TQJ5BhmsKx4RwP=6BL+| z$_G9C3CZXSRsO_Ev>{;s8XW1}v2InbEES(7p^tcB7xnHK6R5zc2d+mvb$MZQ3z7PJ zfIw>Ih(85>KPNIdkzLi|()1?8Oz$S5g52Zt^T@x1Yr2TqjD652ORUe=#eYi6uT0=q z2^Ucvh{<7ib;A#J`;FY3x)TB&_0VIO7lw~&U_XieboD`ebJGnJmWRC2`*|$IJ9=k6 z3q6xP14$AS^KGZN*x7*7w+eAiD!YLo)6Vr{Xs&xzr+l{5+v+4!vl-=_88a>&L{Fji z@|&WM_*c*+ih^>8!qHD+jjjB~gRdFn8QVL_)pW@d0m<0+f<=9myB7T5CBG$*Wy8O% zCJg7D#+@Y~P&X#X@H7VPLL}x+H$3hk>jY{dgRS5MDk|edv1ug#cF&Y#UP4U2-LH7w zAyPIq-bNyGEi7NSkaF>%N>GMhg{Lc%Zy+?$(Ddj*&?!(tKLeBoNV$Xqb7R=)&GMlA z#-8#YL=i-AK<2u{>+!P)j<>HWDIdzeyi&LRtfzXa`$GF*N!N(hM<4bc=;*$ zDr!U%F={#yYjwXu!Z_@|J7-N*Ybi8JsfHvwY#3)e5=UH+V4h>i6edxjTtPM6y!gEH zS@Drc?eL?i8}8E((tkYy59CDBN&0=H94GK2;{YCZl@S<3x zxF!RMHTw-qX;CZul1w%+zcnV`z@>rM%u4&~cTw|B^EE&wdtr_-Li#{+#`BzRWtA_V zL+CWG|0kuyf}NZA;*`4)xX$nq(9X^LAY?S`fq+DVoilWZkqf8G3$fcrFmD@&thc1W zH&OEBDui7;iQArX@h_^`2z^cm!XLdcHh1lu>eP;h*7vty2$Uk?YRdyk6 z3PP*`-|4?(*oeaU(K4*%QA}DVWbphl`S$e5q_?snWoEOn+0XZm{5Ie5{mqy29Sr$l1OmP>`uc*R|CQ zx^P5G%If$58q99>^aVU@Y;i#5Y#@2OjBIfT-n&kE|FmQ3)cE;;pf;jR@JK*5a~|wX zE_BM3FaC^yf>79~XD9Ac8)fC`E52;jZOdq|MQrgC=A0SHQ`KrE1||Cerqjk}rP!Nj zz173ML25O%4OM=#Zl`N=xk()Gh1<0dovY}@ahk@(W}}9&dBG*h6myBVt0|L)=rYkSie&* z4q{kCx~phUAeo5dKISndc%CP`uY?}%TS;MW<}1T*H<-rq zpzx)BjJqt|OiN}K>hFxKKD4+bcIyoFF4cuOBirqQH@#%~&Ue_?d|X{x3;9{=`bQ?t3>Y z6WcN{1?OaihvDZ|6_2_I=x*pl*_(Ji`77FQ{(xMWy$3`)i5_icN<;7_FYYF3rF>@|MH#$}}xbPkCz#@zuFC2i&TSjV6S*vdYr}2(`{a zz*vz^8IUY0ACWlIaoq37ws?&VsU@uz{w!<0qUj#YO;<&e_xci0)-vsU--76Y?2^Q~ zVsnQBT;Ev&!r3@G!PWd%{*G+3eEG(?!xu*QJOn@*WtY3zd6d9}~> z>Hmn;aWu7_HSG2Hx7=?aybRrpZjop1t7@`x1y4p?!P=qKMJI{&`);t_C(DbyaY;)>_{8}}v8N=A>g8sB&h*~p6_@l8>w>vRQPWlGI1*H`%>J&1vW$7O z{@f9Z9n^4ub#Pr4+BC}11j`_uVe)nEzG;`7YkOn_^*U9Sa##rQpe@}S@?aHYiwFs_ zSQSuMo+pre$VAKT)L3D;B$SulWd)9C+;=jDfh)xk6jgGV2#5Jw?_Fqb*dg{k_{)0L z0$|?()h7sKlu{=jJ1r*AXt^#Ocb7-)WDCJuIHV~18hdP0bu0pNcJ(gbVxI%b^Tsil zIM&iGAFnfYLbeaB9)3XZ_KZtjq$DQr4?`_FNB@QBGi`RWq}%I_dx@Jd{u+V5@h79H zpY_*S9PCYjMqYAtN~aBFsT@b7jn>N5AI7li{>q4Nf37fFkELxTz zd>c7ms4e5e#jfs3;NV$2q2Qg411!q;>foKCTAKh^C}}CUO&Qc#1T({$$9GB zljg31+P-(phrYcxjsUzBtSe%SL@Qwq=`FQ;*a6B8eQ zw?O*ruWjI9*NYZ^}s3;bo>cLYO1})L9^8&4;0HQF9=O zz+W={6!kUH#nrc^?sk|W zp2Ss28AW;;s9;HS0drsT@?lW1Z*t9)Wn!X5;Xb2duj+|na_Zq3^N)k^*o}OlFkCd7 zaIAFfQJ~SApyP8Ns?B2KZ)+Ite35)qD8;#)EDh_@lZ=d+yPFU6luX~#VPQA3x{mU2 zeaXovZ~As7yWS4j5fkWjyPCsYdY#epP|i42X4-5Ks`<=X>HeCiqIDO!m3ds*rkHDH zDBm(K7qqmZ(I&-n9^mVhR+!$FLe!+{k~vgOpf3R4aST1<5>b0%ZkTk6QvQ{Rgn^Y= zOR7uMP>?AWgctB#e=u19@zf{TitJ#mXX-w!Bjv>ei$d(#6vzPqReA`Y|3L->jCeO46P-f7*P6zE}_ON6BiO!bBlXfXcO6dsb%%p5n zZt{weeY14fK|5uxmKcARe|+LU)JlCSzrgMIwKUA_ymK8N0H)Uy8f9b|@C!KKP&s^RAD zmwB^nrh$nmC%?sUwfXwG8RYDKvRjH6T4Oy27*XiS5XeAM*ykd6+d+ z^aB{)72tsBiEgHvy4;F`s-c^8fE=z(0=ajuyIk}n&HJ#IRFWL5f?Nk;6vck2n#8yw zzV_P3@<)AQ4taxpj^97_OhSF3HB%`g8z<^UWDmA);&6-pAy5=pm}DC6)(;Hfp|JPD zy2>lBzv2mL7vs-PjQg^<6xSA2zt)NBrNZz#dV@mE<1)dx7nmSG?cBqxtp(n!Xu5<} zUo(Qv?F(=-4R-&t)tht+s=yT1@p?Q|-JhJiW(x0Lvc2ilYxn9v9_P}X)NECg6y%jr;d3Qf3~() zV`>hmc3z!K4VqBrIz4;qwZ=ydS1mQDh_@qr`fw?=+o~Y&IH4>No8FuVC3uj>+J0p~ zbCmAFg>moR=6}6s%z?~wZ$DYC%K9VmlQ#Qx^VZESIsS^V8QXWBuHdTm!~Ja)cMDtg zMX>AeGOc|?Vtv*OxifN$vItbw@raf{;Q~R<|mN0L}uX3%UsP)^oXDD6bopT#^a@c|R18}$UT?2Z?=1~5I58?XzCp6=p z+eV=s0n^R-K_i~`vY(qD35NSU(jNDfIkLdkxwy~^F_afCMKQw%kac5Jp^MeuR>f>R zn$7c?Z+x>%OaZ)VnQXHRrSV~i9)_Uwt=!q=C9{zBeBA7vL&+t5f}V-R4KR6F~-F3f=7^MZ0S^t^O<01ImG4wH9eZ2q2M+#+*@3I!Fw4I`3~Scg`<4V2rGINMa1zzG*eLx{6u-ek?8?&g|WS~BjI zqHD~XJf2i)Y1hz~PZaxVL(kwU4rY-mfjSZ(mYFcoskpj)_w;O9%@JI&8vD#V-aT)` zdO+jbJmoke$sw2Tm2>d44_s}PEP}IL_a9WE5KOz2e*LG__+u$a|s36d$BPfqq9 zU&?O1IpSl=faMqYg6ib{Yg32o4c1b~3eRscHpalrXX5mYc*6(o>PU_|$Hpk;cYCX1 zJR$#`?7Nc0q!qJwn!)bytBOzj{6M}|yG{A`wAK9mwt>9R{#%Nxz${BpscsFtC8e@Y zeItdKG~maarWPr@d-+_o350_%Ylx+ik9|`b;-7iNebR*=T=3^!qQp zGM9G8+b9B$==ZFTT*ZEYOt>scS76Wq@&rU}pU~owXtwoarCIrw`|mXb@1KSVXK-hl zg0GJsnsWNSJPFN8N!L3DHrwyb#pBkfvECa6uZ&(buy2A&M6TnQV{&h%o4`aFsp(3~ z?!wmo7dSJaW88VU*AJbQB|GN&*0_bDFd=GL$u?x4>@MhRzxTppas`Zt<_GX+Zuv76 zmz$9ff9FUth2Klo@apZ_-vHgy!loxn(R1&ruNrN3KW>QqF2jAK-WQJGqZiiZ>;kSD zmGWJo*{5WhvI<iU>`YA;F#QcW85PGKCJ36jVKE<@Uw)kPGD{pzaUIp zZ-tD{`Yd!3I;Z)K((c-?bE=3|5b@SwIgHq8TBiGJWV=(JEf_1P@s{{)Rxd1*BLZG; zr2nGt(s6bS>Z*4tPuQzc?u$2^lSqWGTKx9Q_f4du#C7NBzEvn2GQnc&*gJ93JH}q? zVG;gE_IJH(9u!Z2sr=6;Dp#$nx{%tmcNnUR9m~$&&+ZlqzKSNqN5j***Y#eCX6}2= zj6PA~QDpbmA83{l6*`k2Z1XY7NJO7hC!D>By!|vUy7hTG6C<83ca7SASSO~EAI>xQ zB(0wAdp@7r*%cKmsx&Fg{^+ccB!JM%IUf93si#~Isd`UGTYcL2E1IPg0$ZhbJXv6A z@rE@`=)L9MRpF-q)6TAZYxSAmwe5>!&8%LD=J#$z85MlG#Rb|{GM*pU?y;v(-z!O8 zMMe-3K1REAg~u@29}Sps9o$Ezni&hQ*&g+$=V!8vedm`H%nAcmSNJnZ2kdHbsC}s2 zM#y{JJ%U?-4SiDdJifndeXM@j<*0$8-OP8~F|5u4IupBKUdMx!6-~FV55;EEX+OS| zE`NM{4UiBV|7u%(-F-!SbdvP?CRF3Z4d7xq>e1kgPnY+&Y}J@~gDs)ft(CoEeD@|k zKqSEIEHp&WItC}zW&CBylKtf!G5u50;9rSLW0U_UYv7_H7x9-U+xey@tI(8O z;Y4$fCV4XFYCvHA16P_XWw$YL$ilicF2fcMbVVdZJ{~=03|OuN(%TMpQViMMxj}n)Fl)~40ja<(C9H`@*f*G#C?6(7`;}4)2cYAs?b1Z@`xZWShn5Y zZlcrH&h6|7aICK+H`H?P{!_t1*;O|#fE)feRppg;w1M3&ab+)^UyG<;)al9p(-jyD z>CU*kWH1kphK*k2((L05e*xos%poh?{EZ_*26@@=)53$Kx8zB{0ibi3J2i>2%8~MD z*0K39p-d`|K1^m6W4UXy^Wk-O=d%TI4hUIE@h{p*)sS4D&UZWgcT}!-q+zXBFH8^dNA&~lFR5=4 zh*_=dZG9*0BT_#uA0Lz6)I)vuK_Ea?tuOjMXl*K<1AAg*SK`|sgHtomb85+_OJg`l zK)qwfr^h#g1Y+qk2;7ag&jo@jRHsD>Nou79TAxZ5(Au3yk9)#9irFyop@xtB^ngbn z@DmnhJPL~XEBA(I6Xq-6tg(}?e);Un>lG)Hrf`=m=L;$PM zjaN7I^k{puxjLV@fQP?MWnLa7zVd8;U?&MiHTs7<)-T zQ@ph8ToLujFx&F8V2B!c7`)>RD;WYG0>sG*D{YXL$3^l@gFT%u!c2S@24w_1aJ~nP z(nB-<5YpY3xyG^o+%AK513jpH`z@`|%QX|wy&dz#%pQ>h-0UyWkeB)cpV^P88uZ0* zo8`;p{GeI0`lujDs6I2f=tUL1LhtB7ayn|Z@5=$?6XMfWEC~Vh5)cb%?F6Jq~yX?BZ-gV-s;>AJofzs*KConp=|7wuaNbD27`BrZ(0-2cTDn z{&OF*U|)z3(CgxpI0&V)<{;wrmgji88*&yP3-)^%>NN!RN#XduK-=KXe7>Q75}vT- zoP;d5ytw8k`{x(LF>ovHzV~h-RX_R_e7in0<)=N7*ly|jVsx+)VrFq@X>FlDd@#SZ znB$eSysW?YG8hx1Mhbxt_X>BdV(Zfuub>%K8yXb0t~a*VURJ~Wb(r_2kBY@#kB_nu z11vBGR?0rB6g6k(O-W`M>Sg${C1su;%QnL&Gubh073COidnf#apCnn?W`88T!snzm z=@X~%-yLuZDGiq{e6yE7LX{GZ`XNS@6*_9?oP1r1#beT*-0FJjIl;N3p9;kZe>PiB zNtcCPi#AmCar9Zws`Mv}+|(Chjekx5EQw#YW>~tURWEEA|Lp{mt*OcTF1JGCE^)xH z9AR)yVww3Xp8)-x!=uL(bfPh>*5OGXa6#ekA6x{0=$hg16(K(`{lk7N(okBa_bkV(6@z%jD-dQ zzb}3F);H1Q1yoxJ9E*Lb1N$KfbQv3Ez8 z%d6mv7w4Ad$|-RlsYfG+768-aMupAgLGnUuK>1`#r<|Fv8Ao%1ni<{sp|Xik^js(5 zZ7QJ-Gf~9o5WWJWSE@V^RQotdpSPau>SuI3 zKzAx?yD*wwcSkfp)oNpM@@Qd=_6B2r#UB^Je=lcn@_<{y+;U@o!k&HV+>$P*){-?{ z^of5JO6UmRaMFB(bcCDbQr|^e~18m{+ zl!5BUys!{Xj#}G!XdUwOn>b*@2}Zhg<{D6DplB0Y^oyq}JSWzGoKx8dh3Ot+e~Pt; zxaWp@P3Bxj$e(P!x*`|$ca=oqHDZKdTDTIc3Q*6x#icYEXOw!`5%O}7p*K(`yo~k( z9*<&?u!yb~s*#YUrRs;goI3RbCskI7QAZI_RWC)mT5O3-w~e?%r<7jmPK|AM**iS@ zus?wf`xXq(IX%R?$NavE4Ks^+psGQc)^(%M12c&FHbF++$eVzii|QSFNt$Kgg7_Vt zSSUdfR3tNo?Ii9fdPYj?rxl=Q1AJ;r8J4Q??ghqjM{TCJvB4c(BLCJ(nFlUCD1uCY zccivl?BADkk%Jsayfkx1ESHI%9OqEY`zcy~VI;m1|3DUbMowjrt+{~8!hF6^LZUmt z9q?1f%YJI08Y5U?MUN=58@}c%Qs+NLAE{m?=e6{tkA7P*gYmQtGlJi*pd93$6|F&h z?ZkPYe~|1dQUZuC<-!EcV<=c5X`he-c;|yje8g&gkmOj4zUi8LRGU&Qm1Gj@pAZ_P zh9Waq=Ar`K^xu6LELmnFR+Sffv>!&0SX}b98b!a#7}aS~UJ)5ZdyAa1(Q>2cAa$!* zwtVH}@7gaDUrM!6pT;WTm|)5iT35DR`N<5)qcO?z+kbKd79+o~qDJCpbD4$Ru;Ux_ zuI`eNF%V8F(ht`~R`ZFe95HGVuKgD$Eem{=Z$P6qKL!9MW|f1X3}!Gx%1(n^WlC}x}xB6kE2+w<2L zk$tQ^%EtLF99q;*Qm3Bdxc7%+Bo&W6A6P5=I(|06FM%QYsAfk38ugB*cC=Z$74lBr zddGY1e>c-56KdJ^D4}H@r``|B7^|^M+ zURm>mW27;y%GUQ9o_Y!cFw{@_68Y~ULci5J&W_&y4E4Xp4LF5eS&#VF8g}d=Zc%it zR@DUk4FRg2oTs9%pMTgX>-N4FOWuVEjNfWbkXEJ%bKcmwh?cKHqCTWr*AS=7xP$B- z{G^V+&-_uQ6!!ZG(ltVf?jC|VG0g&tsV-tYd6mNf99aaCm(=%2juU(2wzL?jx#HA$ zS81NXtYlsWc+@yi{W*>JIPb??ZooX=fnpcN(@@-3L~Ep9+-E-{J2|HWGAiSfWNukj ziKbKwxrebGq|%&2u5KJrV)QvvoT>C}nO56vA=uBc_qe1cJ*@aq(Ll#UubWxsov--Q z2=<*KmY4V`AqAY{39|XFBpkf+vZ{BD(w?p>Ud-LI?qky>3QodV7AT%-mOEB`n@594 zd+;Q03`=Aae{5N+%^8n*7IJEfEGMQaa}=fyF676f5@sl$I2Z@o$3;@0=Rqpxa*02Q z|2Tmns6DDMZMod^9GDly92Ary-ek6bqdGp|Pfe9(LG7(wp~J$kmyXMB%@j}NHwUHd zx#{w6%QVeTqW&9*SG{5j+jn zCSLa99B+KkJ_U}{SLt>5JfA}S*h7K5VHEeb4|$499c1?E!d!`c*+qU9b0JabYaQ@H z4J;&(YULkU{g65vQ39Q!G+@;V$=^-=>K;GPJ&Jk`cq89uy0<5}WM9x5cFA)e55scI zPJD}MzcO5~18%UjPFoK?MM&s8e`x42zT@nohkJcQyPDjf+;xQzHt&EqvK0CSmijDj zR>Qm_HiBl0*cY2q$_MLFc$hzEjVGH-BVuVdZu#a21l{d;1G%eq1c%R4oP@8oT03;2|x4(3j=i4^n!!FZ60#ierJc=icZ!Fn$4~Tn%Tn&Sg zVH-UK4h>=1sJk#D2l*OG<)|hcagR!TEL30E!rsjHL%lK^^_BO;bNlTfa>{*Q0$D-2obE;;>=K zVH4CKijV==>v4hEZNwg zzAX+Pb}8`y{QCtuXy0z7EPYwaU1&M}9ywSZvwO--oGqNDQM>b!cwX>{hRpOaNI|01 ziLIOoJzmsHY1!?3NEe{n5@-1n(g?UOM@p+2A~h$w1aDpa(;AtO6*H;cTH`vG-jPbR z-&IGMm7YeMjS*|dw;O_AP}+VTEqY(r8od@+oGpDthzzD8bZu1I^vXV9d?op*o%L3^ z3ZKbJhCS3CjEt6gwXrQK6~<5!23iSkJ!YQ-IMWBMDrUQO6<0!?h+SM~LQGKAwwjU(c8&WRh3Q07&S$>>w7)rc**Rxtx za=z}(nyyNSof~CTe%yNQ;HH#Q`VK#=DA+e^tS?Db5iZJB_spM7nlyc+z=P&7eQ< zqsi~a247J}_(0b{Oxfc1i%+os`JmlD9#;RqYM(ATf6MJ$BUlj}B^vvBx@Ax6-n>$D0LKXDe4PX_1 zF?NwY?YUP$+2n-`2ar7FE^z%~e?uDyq0(>^Ql!pV3qMnPqo193NM)+iLe5UBUVkr0 zkmtCUo^xhqKFQ!F-?^<%_mqDqxqqHVh^F=Nm*X+URc8tKbr<0_o^dSKU`aP&6j!&h z_5Ob<`^vDY)~#)%yCkF)=@jX1X{15AyStHYDe0DO1f)}1x)G3+?r!*&d%KRG>pgqp znOyS%e$0Es)8iTOFKYV3pae@t16z9oTTne^7zRK@0Q7SYc$xlCPe7z!iW>N#D8Kz{ z47X5d^F0Cn12Iz$@f@)&OI66jZ@VbEl+JUh5tm(WB+M@{Q0XG=S0lHYJ4f<{;S^+v z_L7$SJE-^?zW92CK#&O9BjP7&wk72penkz>T)PabImZ4T4HeOhwj;U3lKJ&9J14Gc zhw8l~h39B*or+RRv{r{2ZBuYuJ@^ovSX%AG70U1N8x)kj7(Ws~SOcPK9}KGfpAvn0w7x~V66^WTvfsll(%PU@7qQC}xkGqbYg7Oh5p<9ul z$n>n%o+As@HCCZ)SUVaHCZ?tyUS5i_VSDD1 zgo`Hb`!PkBy|T1U!y;Wu>EK1?8VX23%<;p9ef(%0r%Xo4hK(p<&{P^yGZsVHs3r?d zDzvfKFE4t>Sg^L7`dERj;YkY--{1xQ_b0u&cPK5PD1r0ZOdOd3e3K(u zMu^JE2egS|2nyevzJV%Tu>v33MvtMTf=?oSm$T5BFTl!&)0zLlg=KHo>X0xtMZwA8 z)3uEU-ZN=B>;QzI+!#^f*q`P~A!*643gN1Om#Yakc%+ofLk-Q&ouhX0%jyt z0x)nF(45PR=?L@qRiCU5AmWcqgz{LJTI@NZZZC%`LE14M$CAEt9q3QUf4SkR!;ewp zR-p_h{f+EQ1#dfGy_6UF%#2XO~? z)Uw|cJX(6=HI^62n@>5o{qf=h&!VHk?LXhraZ5&*H9#F_HS^Y8BmDE=`O^@Kf0$Bn z1SJzv0i#D9FnX9Dno@zI2Xs#LkW;fu;sc~#QhqTO*w1DmG)_Vilh`F;5BEuUmDsAh zfvO&%2OK>0?wn9`hUDz1{CIbdeZjxder7jrJdVfii${_WUa$y>-U*fnmvlbrd-s`8 z3UgP12zfNO1aUTayQFL;G?|Smx|jTGxDi-ge7IvtTbdo)jt0kf6oW(Qz7` zw{BGNr;bDhc};6I<4$^(DD9D235cGLp2yi+I=gv}Ts>Mc9`So_zhXe+@f@6igNOvp z04`UnkqtVxZg@X9-nGVB|Iyj#Q+Q5?G|t9jbpEkxns4s)b;sQNWmiNd1(c;(ifJW= z#zcx&qD41pilvxE0@u?b{JdilD&lBm5Dxp)zV%wA?Dd~tI+>n25Gn`tf*-aG)@V5| zsdwRT9dVrt#xGg7S-*vZXAU5s?wUhv-A*Nhm-e}a4NY|ARR3lmdVq=9U(jxt< z&L>Q^dg%fKxXs4lUS{w8p^UlB7IW7KstR~MF5a+>=e@0_#42|}iK*u~lpJ@yfH_I& z>(GTdov1hi->tZSHfBmITH~fQC=HmQx`~XqK`1 zrm?lN#%qIBMCe9?!^JL@+&E}EzFOZx-MX;Ou?)DKR*6xPGsPRa|rj`TGjHMbqVL2gqBI4M@jB^+)~vKWl^nAkT^DUu(p!V79Sg zIDrv5c}11?TW@Xrr|SK9OdY=8)R&=QxMsW-wz{SgjkPH1x5f+Pg|Tn9w+WpFZeuWQ z!XG1JzNr&~6}7+Z~>Q({Wxx47u7|9Hw+Z+^-wiXB~!7rNF z9{OYV;OzN9Mp|)| zjF>e-3UUkR!rSI6ISgl2P-C7%&<9oJI*eQ4+f!d*EYBB@4d_-Np6ZwTxO{hN)7rDc zQt(}5lh9>1C+o;GN?x6g6x^OE7AZrgz3y4{alnbkgVkt6Cjg z*rj=l7+sxYqBL)Hw|XlyHyu7N+?P%@Ex#vYPmiU8k8dbJoHm!fc5^$^0vj-@%Xmy4 zQZ8KgX^gQ;YCF4pcDrwysIor4_lo=OxwOj`Y&1!gK zZ*Xdv;!cjLuV-zKu~a`;bh=+j1`R6+&SlSiC4JSQz%*ykrI zebcKGhmC54)*uoxRXIuO8;xz#Q=&=V&-gx4-5Cn%+Km9azZ&*1+IdT?esx?m9NEOYSHrhZ1!Yad#FXcgb{r(9x%%fcDGAASQ#MO#*4sDK$6K2zA zV6o=Z{bP!4Uq(X{aI5vf-ZOS$RVwIhR%t@rtV*ca;0qi3*|3Cj3+tn)%Ah!|qP>;d zY9B61&mAj3!RoT46n_%HqrXie>qimdE;9ob#y~`Ng{G?KUNQd>DS#cnZd6>`I@xA% zYss92aR8!!-HxPH_hV}!?GbyWcHURhDtTzj!}WFzLJ@5gd|`|CA{^y3eed~&KNv90 z=83skL=UT7wig|4n9lV)i@F?Oq?7Bq^G>G_;R}OqkDupLk4uwh5(w`hYQDG&jHfU4 z#1Bd*b>TGP?WC~{^CR|O_m?z^IfALkFNLJ`fo#r2ljO?HiSehfrpBDr!DiQv#A6QCRx`@vF|$x2`g%nPRBnkR#tGBRAd) z_u#KSKYDkp0ipGca1P#_Ox|1GyEL&LBA&dQE%JP?^`kXWtAgmBehgapF2TSDzekvf zuBsy2kTr!d(988Z%mx)uUz@>ZCt(}jTCcW0arBl)NC$hr;`4@7N`}ao!RTNuEa8m9 zuCuF?uTbUm%YsaCvY??&^mK*2sJL?VE%qMyXw_c#Xe7!6-F}{EIv!JT8C0x2dK3@bU3?}G%n&E^q!na-8gpZ_RJMCnSSu_sAdu~m} z@!9JLxZkJFT;UbvReO0ff080Fk8E(1nX)brQa zLpx!iQi;@#V#K8s00Cu#)cKhC+2Bh*B43`u(<@DHw{!L8F`0O+?zlC9%#Y(^pTDL7=Gr5`%K~_ROYaKwwgTpC z{oM*BV{2e&;%s2|BaOr$F9N{661c>jzFf^W>_`tA@@nHlCP%H9)a!gEubvzk)RqRR z91RU3&qk*@R76N~q$C*VbI6sX=5_(j6$o^lO3$duM8`N?i|0~iYIr1CW{{W%Uy&2o zj2g)yLoz3#7~?{1zGhl%STQ9t7QEa<4?t?fdCL)>S5&-L7~OrO1a=Ok6;4Ngof;kZ zm7VPF#aTnb(xOHXWd+w2)bE=?>SyB(xq!nD4q#PaeMn;9;Rk5J`z3PhPwT-tb>x+H zulTZAiZ-=eI1&q_9)w|Cqe6Kx!SNMn8?i zaDfaR4BK>|M9(65dQZ~imS^%N0h|7Q(I9;~9kFk(I=UYFgoslEmDKHvV};3jz$BC& z!vb925J!?@f)--{zhc}P6kdj}^769pyZS8;Ju%faREtKM(CFdu*t}NiMoK9g$R=X( z7)>)P=mWgFSSCta^%Js zi(is2OWz$d!zt3deiy>B1u`SQ%1+^pd*zz_HwE@&xW(Z38G#PmA*%-l=#VBr^VNh zlcPc$p^b*{D6;`v#_#!Nv6*0Qs!g)KP=n{%v{DmyKJwo1h?-h({ivi&oVvuE#WCV6 z2g$ch#vmy>9Iu?#qRmn=88R8iq*)~&nv++mkW>_sQGFvJyqLdsQ@ELUbUfi3OFQG` zBWMYxhuPkIwM@=&_ITITNIX?@XM)ZB-9??1_vc{#U6j;=Tb^o$&RO@}8PgS2Tn|g8 zh`p8_VSi7i7Ey35j!Cd~G%x!A!;8Amn&y}J;x3T5Q;W&kv_^xX5<3&zyWETpy?SU>Fq2AN7A+zp+VOA5L$rS~{YxFR}a&@s1$ z*7umK;S?jHN`KG1oO}&E+^u-I_d2-kNoj4;6|56?w*iO&#wKT`k#P0lu1 z0qLFqD`OPkEX4Cr*Z(=#fyaJ3ds`DrBSEX5CWRk^KBiB){fG7W4u1a#ECmWZR6HjG zJ=@ZFoLD<>RDUK{d}q-{oOIl)+Xnr}n3^SOx9+_E^m*Dg`qpS5Vg;t?$K1rAP>fem zJ+^Q`s0%wX)>v@OFa>-cqK;oc^tZ!OP$eD=R8ejhiWGwv31rg6JFSN1s{(Ru_xb?o&B)t2M{xQ>H@`!E%cXcj;+JM{zbqmhGPt z?oh>JVTC@Qx(n_4bz56+T-;eP(j*LNYTkAll^>)V5uFsGF_6Xu7%j}RPX(;#VXUE= zy+dq4%K0v@>aY&RpoIRduE8#V7-DuFFSL4#z)x<;?L)Qe2m>NqVPqprV%4+T)f0w; z^qI&c0g+YZHfV9FEnEU2rQhB~`_fgskCOPj)TGCr1j* zJXl@a1YMZ7NXn8Y{dMB+PMI}u9errOHrC6RzTt5L_o&i|e*SC%!oa%6zj{L+|Bkoi z_epJ(-?e%K;703=3P|DiP|E@*wSQ?^B@3qyJ?M97T1)Kb?fQmJkH-^m$jObY`1oLP zADKkbpYsV~taidau^GBKSNn$4Oxke$jB>NE)OD+1Yp?$HY`5^Vd6qxZ_WO?Mj;FAMELvOo!(+Jt*yRTRYfY*NP> zD_65&-g#dN)rSeG{izWLpNsgYR2u2hX zAFMMxAHhf?TV6~+b2hsrxkm|(EcBFI)j#FXKN1vpm4N*=`d(@tTwdA zyr8YukqP!u*Hc%OyN_?Hh_oR3V~COX2_clN*B5u4>U6>DHzKZ17Pys3?DkFe7FD0a z2T7)%DJL)Qq4L(6(LCMf-s(sZSuPn?SSR^b9#-jWS!n68P4g;@a|J4HBxK_jvUAVc zynZm@xWQD?b@5(e$M*VSL#!JcI|IRsJ|wTuwN zT`Ru87k&qKXt7DqC)2k_;To)^LFQt<1Q^lvcW(n&k*fb-> zC(UGoc>T&nJL=XInd=}kUwwyQl|}Tr)(PM(arO=-X?o4!MCg35Hmu9W{~MS&mB*X6 zNZ{&1?_8w9_^X+vUNu^()$uRCAjm6wRrkov!3T4)#FX3{MJ#3(yysEJ1YZaw-benY zveyN@p&fW{)qSd%7#2?psGfk&3VtL$AZ^pkmMKol8Froz@mQJI;9Qc)bHPPkZDoK= zU3$7LA1N;dxf2(A{hl$}i?8l^GfkXCsR*a9HKZFsByuI3xs=b%NJ!V%Y{C&|D8(7l z4!=X1?--X+HuPMIO{YD?Sa$Ug6FuI?n;o1}GbH#Kge2I}c{qjj1Mt&shWXsNutVjK zNi&FLvQtb@G~`@IpB0`u5~o<2YN7H%uS$}qAR!hm?5Z@)%1G?V^l=~;t;(31vg-g{Myx_*chE-9Mv?kJSik>Od617W?D)%wZ=7y^^SZ2eYN7~0FccO>S>$%Ml973PxkGKM_gVG`} zxu0V@y-uI!hUOlkg1LHfV&&SgciSo(Z_OK16X<)LptrvgQM2x>OeMfFwd%MtYh6Zu zD-^Ze;8-8PT6v3XHAm<)EM7U~)?%kT$Z`8pR0gvr|MRY)m(|j&Ho1hv)OTK&ZG|0? z*H*aGMH*FB=8LJ41a*P8evR#V(oq**K7DhbXvgDOTtjukp0-&{I#vkXt0rM_sad?8 zHrvfAQSF=#R8P=MR}*A*jJZH2l_Q+)pQ_4gt~*N@lXF@^L2FzOjj2i9ohsasLUTDX zEV1sy_Utyku5du{L_p-Ral&s9xJDAhr)5th7)5JSY@3bUBEJD+RqKi9C%opn2*Wnu zVYu9X=gz7e&~xhR#(BqInO5LRG zpnI)es`K01tTfmXWQE5xxNtLyn!hVwi`k;#?OpC_BqctiY00dIEzEPHGFdph<)G(K zelqCHZg(+FfVzk5a%bmhAt$egaExEju2VXafo&v1G>1I-qCsDZw~a;_*O+S zbgA#kiei%b*6Dx%QFc|ty(AE~2fg1~uiLJ5y|-?PC($iA!8-10r`dR|O%q>$E_-3a zzT7Y7uLBrH z;_C6T&Q*3_iRFpoK)~3d=zI4{8H6es@|~c(4~`qk`wmCuJCYNyUBgr_jG|iwKL%s9*s? z`U1{-Tn}xFfP(%*X@&48UMpWd#7-w3|5Z{1h?9lZ8FLMeYQi*qC~#EWxN zM~HRIwB5$qcMZW$PCJXZpL+2M(6vi9JJlAfWVb)re2j&DNOf1 z-y)UsLVIPio@CedICvuTR8NrDmM@ndr-lKVT>xnL!K*}24cpo30XmeGWo$&7<>wcO z0s9{Ceb{u|t)He2VP{R27g=(jjFOmz@d-3ur@+fFM-FHAMBlp~Vrx->*{3wfMvEg( zgL&?e_!97pMRK+CsA*zH`i8(8Wi^+;)JjfRcD$)prQ3ExHC}vaPT=P5C(Ok9o^Tm! z4qtAo&prDogv*@d~s}NzN z;T7;}>S}q&=+|`4<9C;b#kJ49UNSooQ-_}f|Ob*}jDiu6M3TzWc`fT}1ywFGM% zpq@3rsp~=i4r?7fv%klgm1U~Dl+`#O!fn5-JWent$A%(bxJqET6^djdvq|wOd$0Y9 zhL=g#e1*d8e%Nky1GsdPP~v&lkQL8OEvoM3d;irOm9u$hNa80+NG>&81K*J&nPB?U zds&ZmK759diQwo6t`E-trfMI;=iXFzM`uVI!Ms4FfgZj`HJs`7v^8ub-vX+LjNzi^ z83Y=|8piguY%qGNG$NGrsqfgzWz@|)O1huK-(x3Ya_ znL)*;c4g>Ea4y_kPc|wnF1;jE^`}Y}$nxsSk4#dDGA_*?>rc8i+ap}VL4SUWAj#Qsjrc5L^c43U*XxD}yj1$DOU<;tqCSqI&G#jw7>K;~IfJ8uMTxLUP0PIk-fYhyk;IMzUzWt~0Nve{C{K{WR zTR%GRK)(El(ZuvF`*{W4=ZIv8FlsTW?EO0OGB5Y^qJP4*>79&o(er|e+U#C}0hJr{0Vm~rsPu$U z^^i*}>*HoO0lKhdVqd5U#Ca|Eiv}qfDgo*E@+eH>+}dLMW^ZOp=rG96Q7Rord46#u zsDXO}`6|q$JW2kXlCdHIBdksLA+k&c1bS!=F)kuMmRoyuJyCsznw_L>WVeA?jhHzgcq9!&g+2=EE zZ=J!tWMe^;HjK>EJXO*O_Z|!}%*G z3Na3`jjHn!O9HwZq2mz$^=JfNiK_epxc$_|lcO$Vo$$qT`YMX!QoK5D9y_h?+FO; zCGos7pehHY^|7i1@bZb;O8M!Y?NZ|;y}5sc*_h$-!s#e)bJ97Gewjf2_|#cU%fyZA zCZ!?eaOVsinGWh+onaxbe9Z+J0otjUiO&LYpOvlPONt&o7X*YxF|sB&&8_2r?WFM{ zvp2cnzyN&Ls`;F%wr7=)h`0aiH1|G7+Tqi!GwIqcA+vYBzNsZ|;R9(YH8f;yZjWE_ z^`f-%@O_QYGJ_buEWmDE_d>1OuDd^F@<3`|LKSRb)ohRCnsRv#ea?N}aWhxxV%EYo z6j(+xy)#Rt1!30#X{51z7Eu>CLulNxW>Hpm%(RP`yl_^7I|OQE&Spyr5?BlkaA$VVE=cX0T~#inwHX^lg5~ z$*rY&KB6Ht+R|2R4Aw7KoH-YlB2Un8sALeBmMbj1 z<4CiD2uoCPX>8=XbH_AZ_`;oOb>_#S{&-8;9FtQ_N_IXx-Et zd$h-tftZIOXwy2+|K&1Sbdz=A-e8^g_YKsB05O9QDPK@GeAL-PL>)^mv1a5qRGP6k01IS9syd&N6nj~O@YJ9rA0iE`M%5uPGm!A-@kNj6e z;5Hiw31kwq(i{*n+gs;PA^)xJ94I-^vTnb~=>fJp|0==nMczPxL946%0%nH#b6`NN zH6ZX008pJj2ciOg@AAt4>R|pm$3H7o{oOf%8V9Xa1;otuj`}meA2k14<*I+<{27qS zU!3>CJ;3>odmbPhkZ;gpPe8(0fcse*2#EUQYf$z6@Z|wMyZm_w51{^aWdXzk zatT_q2#A;Mz5YjdAZ!9*LF*~}g8lIL0XF}>GyA*HDd1!a$}nh_^k2wiPaZ)2d1v#F z(4e_genG3fcmVnb)4wI00A(9Af5k6sK|tWjzYdGP*#58JKR`e`#gAzh`0Vnd3O)e- z^M(?5H~({C05&6Nd<_sd+xyM`1`PBC1*+u-5GdzAq8WhCE< z!2Ris`TL3p>W}%0&z3*J`-{(?-kG4o2Gp177xseU1H6JvCBNK!Kxu$_5B;JsqVxa_ z&>sHJL_qyCei7+^^8k^bf&hEs|Ml7c6$sRG;1{i2wFhVc1^RPhpcfCnh>hz#Kn%F4 z1ztmd$^mNX{{`Ko|A)~3VE6~61ZwjBMTrQo+5gw&5GVwY63_q+N&?hm{ELKu(E}v@ ze*-coEU3Bh7p#WK1F-)wK7wL`S_pn&-dQ|=`MbRU6d3fd{tLL@^3Q;O9o<2(LC?a! fuzRik2>a*BSVjU0`p37FfS+(=Ft9Z1AOHP7k(v(N literal 0 HcmV?d00001 diff --git a/packages/bento-web/@types/compressed-json.d.ts b/packages/bento-web/@types/compressed-json.d.ts new file mode 100644 index 00000000..a727329c --- /dev/null +++ b/packages/bento-web/@types/compressed-json.d.ts @@ -0,0 +1,8 @@ +declare module 'compressed-json' { + export module compress { + export function toString(obj: T): string; + } + export module decompress { + export function fromString(obj: string): T; + } +} diff --git a/packages/bento-web/package.json b/packages/bento-web/package.json index 64bd25c3..46164330 100644 --- a/packages/bento-web/package.json +++ b/packages/bento-web/package.json @@ -30,6 +30,7 @@ "caver-js": "^1.9.0", "clsx": "^1.2.1", "cobe": "^0.6.2", + "compressed-json": "^1.0.16", "date-fns": "^2.29.3", "dedent": "^0.7.0", "framer-motion": "^7.5.1", diff --git a/packages/bento-web/pages/api/defis/klaytn/[walletAddress].ts b/packages/bento-web/pages/api/defis/klaytn/[walletAddress].ts index b773e738..085e42d3 100644 --- a/packages/bento-web/pages/api/defis/klaytn/[walletAddress].ts +++ b/packages/bento-web/pages/api/defis/klaytn/[walletAddress].ts @@ -1,7 +1,10 @@ import { safeAsyncFlatMap, safePromiseAll } from '@bento/common'; import { getTokenBalancesFromCovalent } from '@bento/core'; +import { getAddress, isAddress } from '@ethersproject/address'; +import CompressedJSON from 'compressed-json'; import { NextApiRequest, NextApiResponse } from 'next'; +import { createRedisClient } from '@/utils/Redis'; import { withCORS } from '@/utils/middlewares/withCORS'; import { @@ -13,6 +16,7 @@ import { import { KlayStation } from '@/defi/klaystation'; import { KlaySwap } from '@/defi/klayswap'; import { KokonutSwap } from '@/defi/kokonutswap'; +import { DeFiStaking } from '@/defi/types/staking'; interface APIRequest extends NextApiRequest { query: { @@ -37,11 +41,108 @@ const isSameAddress = (a: string, b: string): boolean => { } }; +const isEthereumAddress = (addr: string): boolean => { + if (!addr.startsWith('0x')) { + return false; + } + try { + const addressWithChecksum = getAddress(addr); + if (isAddress(addressWithChecksum)) { + return true; + } + return false; + } catch { + return false; + } +}; + +type DeFiStakingCacheDTO = { + t: number; + v: DeFiStaking[]; +}; +const getCached = async ( + __key: string, + __redisClient: ReturnType, +) => { + const cachedRawValue = await __redisClient.get(__key); + if (!cachedRawValue) { + return null; + } + return CompressedJSON.decompress.fromString(cachedRawValue); +}; + +const MINUTES = 1_000 * 60; +const CACHED_TIME = 1 * MINUTES; + const handler = async (req: APIRequest, res: NextApiResponse) => { const wallets = parseWallets(req.query.walletAddress ?? ''); // TODO: Enumerate for all wallets const walletAddress = wallets[0]; + if (!isEthereumAddress(walletAddress)) { + res.status(400).json({ error: 'Invalid wallet address' }); + return; + } + + const redisClient = createRedisClient(); + await redisClient.connect(); + + let hasError: boolean = false; + let stakings: DeFiStaking[] = []; + let cachedTime = 0; + + const cached = await getCached( + `defis:klaytn:${walletAddress}`, + redisClient, + ); + if (cached && cached.t >= Date.now() - CACHED_TIME) { + // Use cached value if not expired + stakings = cached.v; + cachedTime = cached.t; + } else { + try { + stakings = await getDeFiStakingsByWalletAddress(walletAddress); + cachedTime = new Date().getTime(); + await redisClient.set( + `defis:klaytn:${walletAddress}`, + CompressedJSON.compress.toString({ + v: stakings, + t: cachedTime, + }), + ); + } catch (err) { + console.error(err); + + if (!cached) { + hasError = true; + } else { + // Use cached value if available + stakings = cached.v; + cachedTime = cached.t; + } + } + } + + await redisClient.disconnect(); + + if (hasError) { + res.status(500).json({ error: 'Internal server error' }); + return; + } + + res.status(200).json({ stakings, cachedTime }); +}; + +export default withCORS(handler); + +const handleError = (error: any) => { + console.error(error); + return []; +}; + +const getDeFiStakingsByWalletAddress = async ( + walletAddress: string, +): Promise => { const [tokenBalances, dynamicLeveragePools] = await Promise.all([ getTokenBalancesFromCovalent({ chainId: klaytnChain.chainId, @@ -121,12 +222,5 @@ const handler = async (req: APIRequest, res: NextApiResponse) => { ]) ).flat(); - res.status(200).json(stakings); -}; - -export default withCORS(handler); - -const handleError = (error: any) => { - console.error(error); - return []; + return stakings; }; diff --git a/packages/bento-web/tsconfig.json b/packages/bento-web/tsconfig.json index aa92652e..a5d0a020 100644 --- a/packages/bento-web/tsconfig.json +++ b/packages/bento-web/tsconfig.json @@ -17,7 +17,8 @@ "paths": { "@/*": ["*"] }, - "incremental": true + "incremental": true, + "typeRoots": ["@types"] }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/yarn.lock b/yarn.lock index 3e04d522..f98c563b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1500,6 +1500,7 @@ __metadata: caver-js: ^1.9.0 clsx: ^1.2.1 cobe: ^0.6.2 + compressed-json: ^1.0.16 date-fns: ^2.29.3 dedent: ^0.7.0 env-cmd: ^10.1.0 @@ -5037,6 +5038,13 @@ __metadata: languageName: node linkType: hard +"compressed-json@npm:^1.0.16": + version: 1.0.16 + resolution: "compressed-json@npm:1.0.16" + checksum: ef3823d27807e504661a1cb1cb4518e6f55be9e483ee0ebb8ed20ab443cd9806e7dc6eb604d265d942cb58abd792092ecb6aa99e3ee4bdc51535ec1c17afe76a + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1"