From d9e1090308ddc01e75026b394901e8a46993d58e Mon Sep 17 00:00:00 2001 From: AIFlow_ML Date: Thu, 16 Jan 2025 19:55:56 +0700 Subject: [PATCH] feat(pyth): add Pyth Data plugin - Create new plugin for Pyth Data integration - Add EventSource client for price feeds - Configure plugin in agent - Update dependencies and configuration files This plugin enables real-time price feed data from Pyth Network, including price updates, TWAPs, and publisher caps information. --- agent/package.json | 2 + agent/src/index.ts | 5 +- characters/dobby.character.json | 2 +- .../plugin-pyth-data/assets/pyth-data.png | Bin 0 -> 274412 bytes packages/plugin-pyth-data/eslint.config.mjs | 17 + packages/plugin-pyth-data/package.json | 58 + packages/plugin-pyth-data/schema.json | 1 + .../actions/actionGetLatestPriceUpdates.ts | 252 +++ .../actions/actionGetLatestPublisherCaps.ts | 201 +++ .../src/actions/actionGetLatestTwaps.ts | 354 ++++ .../src/actions/actionGetPriceFeeds.ts | 307 ++++ .../actionGetPriceUpdatesAtTimestamp.ts | 278 +++ .../actions/actionGetPriceUpdatesStream.ts | 386 ++++ .../src/actions/actions_debug.md | 191 ++ .../src/actions/actions_fix.md | 270 +++ .../plugin-pyth-data/src/actions/restart.md | 270 +++ .../src/contracts/PythPriceContract.ts | 152 ++ packages/plugin-pyth-data/src/environment.ts | 142 ++ packages/plugin-pyth-data/src/error/base.ts | 124 ++ packages/plugin-pyth-data/src/error/data.ts | 158 ++ packages/plugin-pyth-data/src/error/error.txt | 405 +++++ packages/plugin-pyth-data/src/error/index.ts | 3 + .../plugin-pyth-data/src/error/runtime.ts | 71 + .../plugin-pyth-data/src/error/websocket.ts | 90 + .../src/hermes/HermesClient-copy.ts | 317 ++++ .../src/hermes/HermesClient.ts | 319 ++++ .../src/hermes/examples/HermesClient.ts | 107 ++ .../src/hermes/package-hermes.json | 58 + packages/plugin-pyth-data/src/hermes/utils.ts | 13 + packages/plugin-pyth-data/src/index.ts | 19 + .../src/types/eventsource.d.ts | 41 + .../plugin-pyth-data/src/types/jstat.d.ts | 8 + packages/plugin-pyth-data/src/types/types.ts | 1584 +++++++++++++++++ .../plugin-pyth-data/src/types/zodSchemas.ts | 323 ++++ packages/plugin-pyth-data/src/utils/index.ts | 2 + .../plugin-pyth-data/src/utils/validation.ts | 114 ++ packages/plugin-pyth-data/tsconfig.json | 38 + packages/plugin-pyth-data/tsup.config.ts | 12 + packages/plugin-pyth-data/vitest.config.ts | 27 + 39 files changed, 6718 insertions(+), 3 deletions(-) create mode 100644 packages/plugin-pyth-data/assets/pyth-data.png create mode 100644 packages/plugin-pyth-data/eslint.config.mjs create mode 100644 packages/plugin-pyth-data/package.json create mode 100644 packages/plugin-pyth-data/schema.json create mode 100644 packages/plugin-pyth-data/src/actions/actionGetLatestPriceUpdates.ts create mode 100644 packages/plugin-pyth-data/src/actions/actionGetLatestPublisherCaps.ts create mode 100644 packages/plugin-pyth-data/src/actions/actionGetLatestTwaps.ts create mode 100644 packages/plugin-pyth-data/src/actions/actionGetPriceFeeds.ts create mode 100644 packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesAtTimestamp.ts create mode 100644 packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesStream.ts create mode 100644 packages/plugin-pyth-data/src/actions/actions_debug.md create mode 100644 packages/plugin-pyth-data/src/actions/actions_fix.md create mode 100644 packages/plugin-pyth-data/src/actions/restart.md create mode 100644 packages/plugin-pyth-data/src/contracts/PythPriceContract.ts create mode 100644 packages/plugin-pyth-data/src/environment.ts create mode 100644 packages/plugin-pyth-data/src/error/base.ts create mode 100644 packages/plugin-pyth-data/src/error/data.ts create mode 100644 packages/plugin-pyth-data/src/error/error.txt create mode 100644 packages/plugin-pyth-data/src/error/index.ts create mode 100644 packages/plugin-pyth-data/src/error/runtime.ts create mode 100644 packages/plugin-pyth-data/src/error/websocket.ts create mode 100644 packages/plugin-pyth-data/src/hermes/HermesClient-copy.ts create mode 100644 packages/plugin-pyth-data/src/hermes/HermesClient.ts create mode 100644 packages/plugin-pyth-data/src/hermes/examples/HermesClient.ts create mode 100644 packages/plugin-pyth-data/src/hermes/package-hermes.json create mode 100644 packages/plugin-pyth-data/src/hermes/utils.ts create mode 100644 packages/plugin-pyth-data/src/index.ts create mode 100644 packages/plugin-pyth-data/src/types/eventsource.d.ts create mode 100644 packages/plugin-pyth-data/src/types/jstat.d.ts create mode 100644 packages/plugin-pyth-data/src/types/types.ts create mode 100644 packages/plugin-pyth-data/src/types/zodSchemas.ts create mode 100644 packages/plugin-pyth-data/src/utils/index.ts create mode 100644 packages/plugin-pyth-data/src/utils/validation.ts create mode 100644 packages/plugin-pyth-data/tsconfig.json create mode 100644 packages/plugin-pyth-data/tsup.config.ts create mode 100644 packages/plugin-pyth-data/vitest.config.ts diff --git a/agent/package.json b/agent/package.json index 27aee4c340..552f971d25 100644 --- a/agent/package.json +++ b/agent/package.json @@ -92,6 +92,8 @@ "@elizaos/plugin-hyperliquid": "workspace:*", "@elizaos/plugin-akash": "workspace:*", "@elizaos/plugin-quai": "workspace:*", + "@elizaos/plugin-pyth-data": "workspace:*", + "readline": "1.3.0", "ws": "8.18.0", "yargs": "17.7.2" diff --git a/agent/src/index.ts b/agent/src/index.ts index 68a58daa34..3ff1bde158 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -39,7 +39,6 @@ import { zgPlugin } from "@elizaos/plugin-0g"; import { bootstrapPlugin } from "@elizaos/plugin-bootstrap"; import createGoatPlugin from "@elizaos/plugin-goat"; // import { intifacePlugin } from "@elizaos/plugin-intiface"; -import { DirectClient } from "@elizaos/client-direct"; import { ThreeDGenerationPlugin } from "@elizaos/plugin-3d-generation"; import { abstractPlugin } from "@elizaos/plugin-abstract"; import { alloraPlugin } from "@elizaos/plugin-allora"; @@ -96,13 +95,14 @@ import { openWeatherPlugin } from "@elizaos/plugin-open-weather"; import { stargazePlugin } from "@elizaos/plugin-stargaze"; import { akashPlugin } from "@elizaos/plugin-akash"; import { quaiPlugin } from "@elizaos/plugin-quai"; +import { pythDataPlugin } from "@elizaos/plugin-pyth-data"; import Database from "better-sqlite3"; import fs from "fs"; import net from "net"; import path from "path"; import { fileURLToPath } from "url"; import yargs from "yargs"; -import {dominosPlugin} from "@elizaos/plugin-dominos"; + const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file const __dirname = path.dirname(__filename); // get the name of the directory @@ -854,6 +854,7 @@ export async function createAgent( getSecret(character, "QUAI_PRIVATE_KEY") ? quaiPlugin : null, + getSecret(character, "PYTH_GRANULAR_LOG") ? pythDataPlugin : null, ].filter(Boolean), providers: [], actions: [], diff --git a/characters/dobby.character.json b/characters/dobby.character.json index 1e025f820b..f23d0f8d6c 100644 --- a/characters/dobby.character.json +++ b/characters/dobby.character.json @@ -1,7 +1,7 @@ { "name": "Dobby", "clients": [], - "modelProvider": "anthropic", + "modelProvider": "openai", "settings": { "voice": { "model": "en_GB-danny-low" diff --git a/packages/plugin-pyth-data/assets/pyth-data.png b/packages/plugin-pyth-data/assets/pyth-data.png new file mode 100644 index 0000000000000000000000000000000000000000..745b2a7dc18e5216c09fc108bf97222aa0433bf4 GIT binary patch literal 274412 zcmZVh19YZAvn~urlT2*ew(W^+KCx}vwr$(a#F*GmY&#Pt-@NZ-b` zx~ft49jPcU0S|)%0|Ej9FC{6a3<3h?00II^^AqwPC-vI4{@(=NQdCq?N>r3c(aGM- z(#8}7geuY4z#vJIj%wTx#lT?v4=pu}le=<6M2xb5|HQ!fz@LF3gHeOj3|-w_ETr8b z5C|q5bf0cWRd|SWS79^5$%7xd)RiL>LT`vjsZ2~v6d>!9N8~?<++Y*vlTzT# zW|E(;cfa{o-NSgLBeXv)d*7~9)27@F7{nKF3T z{@b8`jn9MUA8BjqVo2m+Yh&lkM=8Nr?VS#l@PRL{m>3)`ySng`ko?Ed|7-teou(d^ z|KrKd`M<;Z7a-$*B#g`qOpO1}_CF}!f4Dq~mL8@yT4I*A|I+i%hX4yV7vF#B|Nlw; z$K(G(YW@$BgN5yXBmbA={}-v|Z0aOxZ~M=si@^Wv%zuObxA4D#e2o8z{C{QQ|D^f9 zxc~Aj0K>=l|MpA(X6?A$3ip$JUz@KMDKCo~0$;gPfK8?uBjI(fW-@0bD>oLMLK|=W% zNEQ-;EGc3Es8aP_uI+ly{l4+P=bLrj>1s8E4~v}e-l1fj&7)K}PPP}A;%$2l%h!P| zy=!D{`e|is&5Th^6IX4~5>N#W2rWxhQ*atARN+sFucgP8Uk6LAA8GP1)Q@C7)3$wV zh^eg|54h9RD{ZpUGCoCWMJ;20U~;ah9^lxH5JLp_c#KR?cNm6ru;v-C=eI(%T;>SG z6dvlN)A?9+Y7b^8Y)@U`p3>pR$Hn^L(1{DRH%>|s2`-Cv zs5&;`Y_&GP3l|dVH*}fKQ)tU$YUNyD60&0O&|frm0%j|SolL+#$*W2;Vp1b5L$9@M zw6z*-A5<0_oxgDp@blklJ>J9pIWW@NN7Dr#!tWkeuj4w)2@?a$jePcFJmg2tn|wIB z74A92>FP!VuE+RgtNTbmY}mcIUI^676tI-sRAHhOLG zz%aIQVxcG>H9DH^fopedvZYB|r7+5DWaTb(lN(-pv65wE93B=M{#~Wb#ZK$ZzKy|t z&0!mtnMvQs)K7%1a@WQyfc^d0|DC@--|x%k_wE0E;`sPFMsDQX^W`f@`2CXq-MrT6 zs`6p~ed!-T*>|6brNcvT0Bg>to_-YHnwg1}zmB2N|Mb`60!jcwL)Xpra;uTjETsG0 zxt}oJ?||`OcmnfiY$T#|@qWZbHg0zGKFOx381wRgy4Bs(lAU6dB10Y0cko3+h-t3^ zKowsbnI3^zhiOcSGLI>cjZR~$X4t5>xh8PNFei4OC5)^xno=o) zlisPuDA!;Zk9vOnWf&ZfDx?BC7|j9#WFteT@lN%Uk}clUxj2Q(Cq+3buV;6zkl69C ziU{C&B6Ir0IqC_PB6kmJBqK>rW0&AW!0=Wk{89%=*Q3_7==)v?Nx zUU%LSjVAZJSOybZ_=%m}TYwiow@{`ko@1I0dWw3o&TLdx%5#HY7)r|cR#XfNdCA;p zdJ@~;Y`5T`P(g~u71oW6Q^dWh*|vPav=RhkEM;bj;a1Hbv-A@TKsbMFq==ERn%QAt zz=7f^r;L-SWXQ+Uv+F1%n%bf-CP_Jz3_eZkIu(SjTwTjnqN9m%=C(*9JcXi zTN)$R7xrwSlU87=C8_MyoRQrkfh;XyRQXL0HV7A%)`h~Qx`_9MeAZ(jswAKXZZL-kZ+PsNYj zBn-7TlGYX`!shJu(I?JLB%OBK_=(JQkBfIfocym^f4pwDt2n{eUco4dz88r`pL4ZW z1M0F8XOS+~gucj(j4hmOH$bINw#)Re?TxR5&r4kPe&$VoRofOJEUfcFv&~!M9rnnI zSWS)mSO|tc9b399SP5#xrz8EYdaFZjmJzWu92lpzY1O0N7dB`OPo$F!{qO4Y3?_RW zIyG5zP+!EF{nRE*ugOTtONRz5hJKZHJyV%`p0FjvTdzE2Kuot@W_N7ZQ3n(ybJ-Z% zi#1(&uhbZ3Rtvplme*K`Vx)0h+GUG|2(u1!pPlT_<7BbDka7!x*5-pyrONtf7?BBW zQy-k{&Ow$4Jg>52={z2^nvMiUBst0CY&T(ROj-r?xY|Rvat;sMctvqU`$;sphtkUI zSUc}#4$H)t+88tFd;jyIm|ZJYzgpD?mM)9r@C;8C0>8@PASi6qDDZ(($8E`|M;}-& zhvgmWc9j)1nH|JznUT56z|AFFUR=4@9}xkIea**u@{MtZD(hcXqDPg@+$8KI^Z(x3 zRNhT%J*;J4!`EeixRx!kf}lkYYZ)|5`Sjq>g*K{kPmVsnARCwKK8E%b`8dcDRA-7# z(V8ZF=33Kx*-qo{UvtFVDn#m9G#Ul& zcXNYoAv(3vU|E|kKOgnk5RR%YcNIYb$56C0IuAAT&V8IY0UL)-=Mmr9f{mlt|xHH!FoX4awzN9jFe4X|QU{Ikfe^xu) zc?+*>FzL1oa<;Iap_DyTnH+;fpu$0W`uw{1Zi#GjFx0o`gS0vq3|M(@`G5|w(S z$>T|OtnW#o@B7Bv=0^b?p1;3}$4<7oT>zCSYWX5_-7{*dQMlgTMA6C;wiI0p_ z9?%)($5he-5BP816?<+k2VbIRzx4SYmfA^=o!cFJtdi*xT%%D6SZt4F#zQcyr|!>| zUP|6gETz1?RbV>I0GwW%SY6_u1L1KPN%~cO3W_6{)FAN6(#c)TC`_tc_L}FN!$seN z@25@aj#cJ(j!hJKa8DVxBr_hzY)_e^R}gHIug;gm{UZQrdHxE{JTR7VU0p!g)2VBT z$ei+q7W+|)`<&USv+#(nvJ1*ws+wvh4G#R@LOSZa6l+|xu#y(0DQZ0Y70rRNQT1#w zIV8G^b>$h0eVGvWyBx(fs(Mw2^L6*80kg90d(Bn(kE1Z^_Jm0_X43VB+#x6J^nGqC zItvK`r>y*YxWAxV`Z4U@)w253a+WR^kC;C?(KIcCOV5c1$m-PeP>}+i zOczbZrh>Rf9A2KZ+V#V52SZnnNhXdKRf>EC$AX#@Ogijlik2Sx;g7VIad{pEf${R$ z@gHL*zojrbpSle2VT9>}(7HD!wD&OrJ?VEjDre&6?<+NVJV^5RZhfyTt|G{<=wdB3 z&b!>dTzoz_zb!Af`Z@dDZdMO~&)euw{m;3Ye13HD_{+a-Y*@Bm8Uc|CpMM3TP?OwO z34JN_Uq&@rQQNo$KJEaqI@{lkTptUFM%Sg~q-N$V&K#Hzuw5lpGL`z8&KH9aupIQ8 zEf=F0pq~?KI?JWY> zGrYEmKb}78(qTdS(2px?Z!9)+N)A(D9ORkf7^BSnW=Ne`NTdA2k-eZ;ii4_kH*d?* z1DB9@kL>WTws_|5gjzdcqgB5Qx{O~v2?yJXTiGm@Ca~2~QBkcAWoCm4APb*F;-Yyq zrz&Q{wYak7*e1#)TqyPl%SNgw9hKCNu$r0BzU4ILNl*UbYNlhC_w=(oq8M~;`gs~C zy|zd{S7`kq5ML>)&o;u!N8CJsyApleI(>)emTH>{9933j`wOm5|JElbb^dGpo4S1L z*)j1$>O0xLhKsEH54LwpW4<6n+X-CqP;|Mrp!UhfMJF$mY$7*dUb{8FB1Ez%WjQh2 zPqe5I1>6d~>%QFvrE)Sn{h(n~l*~yulz4^Rl&7&>G>T=mQ0O6MYBH$w(~-+5=mySL zGs}DWs4WtBM1OD8oV}#qD)Fw&gVyX31d(?j?T&F)9vO;k!v;!|ZWX_=Klm)scyqX@ zzV%evI)Spxqi!RWKjp6=J~8;JADEg0klsoCxRK)k<~ z1Xd$-FskhJdy)TecdODqnw*N8+!HdpQbyQAr=&&~Y)KBo6G1~!6ga8AwjW;#BvtX( zEpWMn5vZ3*NM2lQ^i+P=?Ly;jEBO&#LdR(RY@rT@5${zvS!kF}4C_qi-~{A>m8%K*IS%s8)NK5yP7@jO2EJWen23 zp%kly=v%nprH-dpI-W%HBc6M|!S7B*pNESPxACJN#^~dJoVV#|jKnt=-SB=}>2T%k4A{=?|;3RI5*bH^2 z&_5N4#XS1bugQk;y)AJc9YUr#CLKW7cjis%aab19zD=sI!?H>9SX3NZoGJV+v$dGn zti-jNS1ANMj-_K(&1uAF*onoG!VW;M6Zk7JcDK=VVL&v(Pv?87SRv6N3cZ^2x z)OVfA4C(x#Im7I$Ka;W6_3+ehOa~5oBP_KJi?MPj?NsCZZ0FoE0LS_D?mbg*G_)_` zsZNAsEC0eGp-91XtC%57V@-kv@#(`?nP|SthT*6b&WP&pNG@PGPlX&?cM?(zwonyL zHLpYCL#;ig4jMR6;(!f=w_@**MXz6H_2f)un^d!(ptTr3i+rk(oZDn zPuEtZ$1c^yzye8kxHRY~9-kQ%qgw;KPg%B5yjcim`&V&Y_NX}v+f{i`jwd7? zE`|{vslzD^>&RrUie`VR@6)^sT(L-~faPB?SFIk{uf_IEHkfb&1Mn!%63D-TL#Lb& z6f7xfC$+XmP(m4bQL;CDNhvfa*6&K>ep`TBNq6h!!Lbi_j2za7Mi>EuU2|o*xtkgUwvSn`cC7fj+E@IfLu>`H0cFwm; zP4=TlGhL?Xwr6osb1P$9W_tc?I*c7#YQeBS2ez_qmUCjiZ$DmlT#D9ZA)_ME;of0xbw9(Hz%*FMA#-_kZY*JBuuR8S?*T-0vJCOvzhn zv<2pMJRI~R_up23ley_Tihk5yWBU9(!ZiAhFk9wEV0H=P-E+ z**ITQCpPp~9ToSd*T`@#tEY**wNn zpLY}expRub?-f&PX==n z(bP?8TgU>1LkTeh@qn_$!uSmG+kPu)qM_8BY3KPL`k=JKNyCD6F)4peXns9oVWi}% zP88?Z1+PkR8#DJ<>V%Q_N7NRyZ(_ENed! zIZjvC?lJL>(1<|Kva6{%aunir6X@5v2Hg2g%Rs{Ld&Z0T5~7bNP?qpltvSf~PVIBE za?}Hyrr5)OfudSca6Qbr+F)$=&;WH-Dp_11P7fxEQ0e^)eo;hL;MCj^VHP-HsLUlZ zn1Y)78)XBjZzLg2fzdvo=VYm)6w^^5?LTtPs3TWw6*KKCTUN=4ln&_>n254E!&WDz zQ{4vQq}SMGOUqp_Y!Sx@@^MPSkCe3mFMYA=EBbEBrrwJR?@~*!>Yfur0>A}r@tzX- zyL)`mo>w(d#o` zrS*L%;d#Cs$MOVmBdCnlhR;C|wZnhf`zaav??+!Q``@=b_5!`d+=Cclc<~@$)5;e2dvF$rPi|@jozjWyoWJfAVp^QAgN-)&AR4etE zfpwI!>z_^w47ETyna&EHnIQSS{wnd6B!U>M{9F`O-U!&KyN@ZT{X6?jd3`t~gh{|9q_B*c$D=Ejy^5c*N; zwcY#Rg74OMXW#qz!-E=#qy8W5-!ud-zs(QUZ+uu0@C68Nd~d?{@28&6oG!qb1>I#8 zcHZ92ahFa%I4Us*9`a#xcAV#N^+F_r z%zXu<|Ddm9iVn@XNC1T@!{fEqh=w`O6E~%a)|JZaeA6XK%q_FzsVFI?;~xmsm97x7(e@w zQD8tn-@0X9i7k(^;fBv&M7ThK<^fvEP)Pp$<5L&Rvi2gDV|HE=qZ;8t;0NveF7EW_ zku-7XRML+W{Yp6_)8+P-dD~=^Y^g1iLsEYcED;7Hpc=`QJ4Cmm+%Nm=bR;IBd@WJ1 zjsd8kec^#0D#(lbH2Lw7Z9=Py=DjC{2vUQQtO8#}VHgy~Nj$4;o| z2@yslH)F(|WMl6fw;~NdcT$PB*UEhE5B&YW)a4ab<#PSGak)2anPj6ew0MF*cI10I zz_?MTr#8!hiT{Gmpi(Ua0H)tYaAGz@L64#Rz6G3X9BfyAnX1{|Wky}yaWS5@ z5bki3cJb;B`y}JVIGG*ZGtY}eH8(Q`!V|^--oo3*(~=!;yPros3U4lmRjwlB&b5^0 zyJDkK@1*pkmuGf6FaUfZT`peKX86B?ChD}-r)lNWhP~hBRPH`BI$LO=YOK8Nb?ZNs zb{ry8WuC0x=t#|V>s1otCf2VUr``o{Zay=45BA<~BB8F|hPZ0OImK$JDR}lx{rrBt*a?__U^>C>S zKN22mhAGaLsuVS%j$kYG?GEgv+Kx;M9O~aj^@Q>o6N@U{lI4_Kr`Y{Wl8295wo?yUs)q#@h@zhXo zguq>qm1Ic6H>yf&8(b2;6}TVyTnWQ;k8*IjvDA7?#1Z6%`~Xd+#!@e8sYZ-Tb=bUO zY)3keEXeGY$u>&Fv^+C+npk9j5^1{s`3qhmMzyr@Qz_*P@e5eao`3_3I0?GR!fLHa zV34AO*^qEF-l^pDJG=i9|5{{HpIa)7{)K`5uL9_RL?njH;Ub<*Jvyofm=bP@3VM-& z#xmv{!#R^SNM>^`AC=CN$`+MgQmh9I?#f97Qs#B2KY&|Vl;wL^j|YRImyXGR+!PJ1 zjQwZ|)J|M-i^cLJ;{E$n%c;o2w{VW+P9r%$n4{rqk&lWh?(qanGDO)X{Mih2k5DjA zUr}|BXpY+LhR~c9uYgDjwE}8tYyjRYYl%!MDru!mWv`Ude083g=KVjg?9L-_l%UnO*VDqro4> zG9Gb1VD6n8D|on|ub+fdp?#q>;>+%Z^SVG+OLV$IsgdQ7ylN&~8-``(jgbl4r(8rP z(Y03Z;nf$d_X2=wcN5YM8iB=Y@~VLGR&xs}I7k-O{qsOx-Ja~r>1HAXbP zF0Jp@di`icL5j~{b~}x!1tho;^>;2dI2dlVu%xK*=z9sj~KVGVEMsw$*a&zu`9-Dls zxx8Rd9uxBrc;=lBFQLrdbW_d${gHbT2N5Oyo3~9C(m0B9gB@WXg&JPC)pPu5B9tJP z6qEoyXb~Fjt!;94A`T=(LaOtqBBB>ZD?%Dd*x1*K%1Cx1G35h~xSFU1HC6)hLkgaM zdAK`46)jXTjW7ZS9b^h=nqWw9l#>eu0=E?kx+E#HRfg-Fti4P|d-Fg{aPiI!^J{+5 zM^^w>B@dRfg;lFOwoh0)%9%Ls2RFH>np>*G=ou<^?gYUJFXlp%Mf#MHO%5ABFdv1= z96w8Tl*=hnfyWu79%hm9cWL7rf$DEB{8B;Z4IM8Pq`x55c)ZaR)LiP-(c#dzwMnM$ z$&lFCl2WVqP?u%cq z4MDc~k0;L?_1d$ME@1s8rhVtrmg7UW-70Do@Su?K3+Qn^|GqyStzc1p-O$%~ z#++*Uno4QZ+L1K}mIDPgL)Yw(E@(PFxex?cAB1(*=m8snO@Ss8c!@j$#L=b&`ee=z zW*Z(PF;f#FBga&;cHw=bGJc5H$nBsi2srg`Yq`_4*=5C1pKbTOH+h_N@8B}_dTn~S^ zNSZ#;TW_rM-}UtT{4}#2Br~TJXu!EekJ1F z+Ev52IP3ccNG#~Kk3Ld9#-KacahfWnp;Z7v1_fCWL%wdFZ#1&RD?z9bX|U?##P^kUqt# zwq{Z8OY{frwO=S?83M!MInL6O&3kxwR?R+IEPLw%0zS-_CuB5bv1IwMZGpGQ%I@*n zvHfprS5s}Gd=}<6roS}?k%~B?!%N{Xd+|GdXJ50k{ z#-Tb>p>+FXid>V?$GwmWe-4a2Lzy7ENm`6s+uyI``ukz*s9S&WGRWC?TKhoW%WG>Z zk0^TTw7*jQJci!hM`_ppnYKpVXHMYF)%P&Bxk3QjjGD2qsnLGGu8y zmipzV5nN{S-l2c!{q`97a{TEoRmL!U?sU1L<#y_FlkdlDl+6$=e?c56(f4YBC-4$= z+CJKoSEjc}D_ZBZ=zRd5|DuW~;KoJp^2Xm-1#o_;%>P_|8D{k3!H6S>X59MZyo+H# zzv=r6f7gfTFIG>uj^}^p{~3Qj4}Ig)1kP1xy>ef2ct&kG^4a3j#T})`DD+Tsu^q}_ zEMPp@TP1qzC75mTzQ(HBfkXKs^nEWid3Hg=4Zl~GO3E5HXU@o@U)W3#5k?(MMoi=V zkr75P;b$nRj%X^_it{L!=KL@;ibcr{!-_DU{5`Q~!QUo+)~WRP(WoR&d)zuWre{a(w54Ey+Vsh-L|85|2Kxw!JgOEK)LbRty0fcjjVY2H zlN;@JgOOyPFQbUn=OWN_3)-Z^~%b;9}lLsvp+uK`KdwDYa zL+^dK1NkPQEj7}NCn^@^y*k;EiRAFVDZg49KC*|Y`po}fY8!|i)?)9oTTX>(;uo|Tp?;F zxLtd8%}dFsyI?x)x0Yxfx(%JoBtptrH9_bspz!F$a!;GqYI)H}6`47;%H%RhOuQf? zSb?{E9eIDNEV!^f;ZG~Njdz>=(0FiwCa7;*>w`d0C=p9F7RCmgGda}z79Blyn7^#5 z^8I;mt1YiTrn4;I$L@e`j@Pa6x^KJ)2RhH!ILWw`?xteq_)Sms-#7Zo09lmkrvP*b zy*g&=mawV=Fr3p<+Z0u%Ya3&HSJZi3^Xpsfd1pyh!`SmdcntIov-??nzI%~s|L%`6 zugO$U{dp_4aaX^st1u$T8V!BcBQ2tiOXovadoTaC%gL-)qWjI3KaND+r~Y*Bo0DH^ za)Bj%SsB~gr-;%Y1nVv-8!O=QU0eL8*XG+`yyU#}<>z&=l+?@{1>aQ?@iwdXGDeTA=~{zy#!Ta%unI6#cT zr$#?ARGt@yz`Orf{oODrVD&RZJB;7YR=4px>}5dV(}nNxm2+$&SmA7VEH^Au-K76O zgu^pjdUPQk?megb6aaqYj7RfKD%C_j8bqkJ(&t1r`zOosTnWK(lpf z(UfIP&{UL^1OGEG8%OQB!h1CW$DlSS7^mx^Nw=MQ{crTkxmKL zrt)%fhyZF|Vd89WtSc-con`s*QKytw;p|HQ$)_;G?DOA1n!Pzr{D5v2xa~kuiTP@jX8NhBErShg^_C@Swmm@5`K>6F|HgMOI0Lc5R?c66;A zYY3sVf49vjhQ8sT*FDA)=2u=H-3*EsHFp+69ATB4_5@ad{Xi6bhV%!VZ4a7tRNKb# z3Ap&$yo*qLUDu&Jc7F5XuS^K~=kKNfXaAooH^Z4GpM$<_1TJ+rfb39XFf1~K>-A6q z*KZk1mdE_+gcQUc8H+aZ6{z6wLn>*1Jy zP*qf>2lEjbVAS?QyE=hn@Drzj|J3`bP9jR--!g^uvOo}BieF2=Vo6{KQN6Toq<*(w zkOrgiM0k=D8dLXc!mq}Ll2^?t&{%MdkS*UWX%X|bf>SNbEjAZNN>U&dz4ui}cv9?b zK%AyH!H?YxAHicXd<+7~wNeQ8gq(FkMSik+aCoA?V^D)8w+>u_y_0eo49j~3lukpX zAT(zrF;8k7bKlC4y%GxDjBR}Oc7xS#AH=*71ch(ZnJE7wk z=He~E>)Zn=t+z0lSBaK`=Ux8%Xm`KUlLON4UBc01L_e)=C*Z+M{`=FK^A_-+k8u-q z=WQP_7KficHtX|&`QfzR|2F$A@v+<3{ip(X<8y4fIV1Fh&L_Ar)8|n;8o8g#w)uIWuynM|4`^_Cp)X?_cSvU zFhLPDeGKR1Y~;0m?IuYnyqVAj(vft+xKZJyl<>eD3Zs8W)y5xRxvj!c#4;)ni0Xj@ zOMkFc2~)JX%Es7G6aEm}{$WsTKQcG$l4jt~uKrsyr?F8a-x)n_yTC?A2dHe>^a_(0 z;>63^CLF|t;UqpIzkQqQG7?Y!cG7aXm5;Cuvn-Lx36SP$v!SwA9YEA-m;0j@N6cA; zsUE#(MeDh~p`vF)4P53kEQ6i+O)*1FwXeB)f^wFD3&w)vpw z8yIA~Hx1e?qrs6)vKclFEk<5KxA}4t)`Fx;hUN$pPWni}*)G+M5SGb7*^=dwQhY>-A4JKYwXVO4A^v4$q=7&^*Ozg5iYa;>YGNEAC z2cy?Un#~g_0_O>IO8HlaQaH%B$BxNoi6fB~Z9UdiAK%!PZ`CcUQ)vOZRiM zHQ|Dh+|(ouFhhXEg(_fvfl%DgQkv3@x(T%wb>$nk)jLg-Y>s)%xzK!6{+43u=t%@= zZRB=Bxpm4+Z{=gFxB<9_PNsS5$T$84lOfqkHoq^CuJL5YevQlTc`2Er?Zvi|XAnAe zQ-A)9;Sr?9`51pUber9d-Ph!@6wRL}vC;F;^s>wNp&WC8iFEwPTQvsoV@BZrlI*#E zX?O9?$x~bghZL#WiuNiII?z*_>C_E7)N?+_lEc#KWnAe0u)(ElHq~#cW~+sc6cR)W>diPChz-N{hsK^w(sTM_8O`mo4NAZcsxCOS^U0bJSglIBz&Mm z9LeqfaK0N|1)%4@Pn#bAcV0wl=tS`JxbQ1htBcRZO=yA?&q1=<$= zBq*UM2sXf@f#3v$D}rWdmvjavUY9vq_=TdR2gwnVRWV;TMRh09g7K(N7o+O(PYPrzvQiu& zQ8R6AOlg)bf|0msd8Kd^NpN*jXEia!EGD*AE}gloeTDr<)@sJ4=h%K z28DxR^F5dH(_T1z6EMmZ(bM6z)#`bFV8YhSMgBssnqz!=1;b_S4}#KjV4GGp?jfM5%H6L2up?xaHn$#%37Mdor6^v<v* zA4B`B$FNN6xLXQ-H%VJRn&b!T_Fm`fI0f9P3O$$Rdr9qfT~|{$TfkfpEMCp;k$b+~ zx?kZO`viPh;qlxSxl@`y&U78A^(u?KW2ayB@zwP?DNLWGLIzSFZ+M}A3 z`Msy0aXD5;0+yUz#!Qxp1W(FK?UloZMQM%524M)U9KyQco7gkz(nC_Zr#MBR(oqH8 z^Hv-?Fi1&0mLl%#i02lFXC#ZVxEDNp z(pfyrantq?DLDd;ppD|!XvH4nM9%`Jk=sTb{Bt6-r&Th>Etza6T@ZOmIqnC;#J=-7 zn?*xC9JSeIn`)YE0zy83-EN_-3CCNxp%-v(f_bAY_Jzhwg@4S{UyiU&OH!(e*appl z*8Qk-5Ri6*Q?;xo^R9d8%lJz;>>lSTUa2k7RZk;kO-dsNmQ%!QVmS)b3FS_)!7%s! zwu$CR+BU25oct)+C)<_N+hB_R%l;aBJ(U$d7Z~$r4igRd;{XWjn2HL#XkDp5bpICiGT^hX5bdn#&3XS8Khz0y`OL6#u*!CXZD;|fq&m$_&e!qBiKWfzAS)8!d06mAO+81S1 z;WL1KoCKxazWF*lMiBjZ895Ajf*8zvE+j^NV(md>{xT@+(UyU%71%RPBmzO^6Y5vY^R%Z$v&-Q2-^xX`fymuQv|a#*DDsZC_t)J`|dh|n%FBXHvN!E5$f+0;<6(N$D=VaQyTgCmp`D2l%C2;YmYHe zIUe>jv`5+PdO=c95IOf1x%k0LG$Ch)*tIL}1SGfA1N)e_%mQsNB3psKHX9Qj_}khF z@h!l8qwdWtjl$@eo0!pZ4VuJ_vIU2)n0bj*ka-J;pM%u(Y4V;@>TUmE7{?zkU1GuU z_s12+o|Nmx*SaMx0<-#-7)8IyKTkVj{2qOX9;p!Gev>~JFwEzG14h&-l-42vN zb^HYx&kKOSiV+qGm`w3r0)vL4n3rp$DbujtLRua<;T}r_(U<*gnwAB+z*Ic|*RT{R z1SYMLi68uf=?$c{l*-<8Z5kF)ia)jv0p8bWE=@gSE-A|a2$m0})$AG(JPVGf4jE+u zqp-e=_KxcQ$msx3KWFqe+d0{fg;b-9X+vd9@Ul0_HNI1n_kZGU$g6f$zbGHh^_9gw zdQV};!`p4vXIsCyAcvA1 zlF|h+pWDTLiwTGqlpD zQvIjGU$-pAZdxDvdhj`m)v%6l(}i3vr!)3P_xjJA2U#f8IE_>jl`KRdnB9>8Pvvk!`WY4i=OREg0$TQo zz}iT>{QFNWY8F^>R2*NXOb)Wfge>P?i`$aq;oIg6x%!Ib@nbsCz$Ua(Ii#fBf_Rrn zOn`7sdj|)9PZ_&ZBe5bOaW>uq^sy(J-dnK`^F$jWbfRydNj4McCphj@kl$B;tR~JmJfDsLdf{j-(J@meUw01V`>i=u-O1OHMl0+(gOhG^$WZ@qrs;di!nanS zrM?B(8W~tYQi3O*R>jk6olPa0|MC$Aflv}&ex|ET=6MHMe7p4#h>Zb>Sa}a;Y;S1I z4O{Q{=Pm<)(A;K*+65DbGMQ%KGXy#S1s^X7S!S4KeRc*lg?dZLw2ZJhlCizn6^F*C zijx-j6g~9l_``7?m{}L&^cT&yk|M68bT;h1Dvtxh){&FIHIHr{ung;8=wq?du=&94 zw-G@m%koQYKz@XxeS!XBx4d2LZ!qZ;!ij~jUm;KlK`|6ry=Ji5ia-%&%?@ejM##smD_Fsi~C@IbTY44g1Fzw5x* zixH^th_WlVq*5gDXmUy+xZL&K_1S3ZMj?+n;{)HacuE@e`bTh#7{;_fA{_Tfz?8C% zg2zEOYJA>^YbRi-o@EmW*gEBK0kn(T z>UJ9jeBBWa^ZS2wdllYHHEKPN#6HNtr47FC9P(bt+HALwQ`BcH`8ojiU+z#Ht?cB_ zU6mg*5u!^pWFHxO@W$U+96k+m_C`BiI|5K$P;es)YG`kcVFN$(*}FwQSDgao4l>EW z2X^-9+xO^jb3AY3D!UoO15^-8Tj-A}&&3|^kJQnhv5Ri~jhxeE&^PwTYN8))Nm+XK zvU7^Al|b=1o3tk{l`yL2DcEwRuS^4=~A34Fji`1K~TakE08TdHo|};KDqfm zH)V5Ef3RcHrP#rcb)|m@4hG9h3#}{EPJM^v9SMaj5*@vn!zBQi(BV0vc9w(m5;`k; zes;ko7oZidmIraUXS-1CdM)&)Z&-Y2^xntDoI6B)EUF5v zq6#`q;{4#ecZ4*NkLv}zGpa9fS=+DF;2s%0$*Nb7Jf-)@P0sF)tKWQET>tT3iS66B z#}B^$L_F}NFT~Q~LcI5~IYoS+GbcDOa1h;pGZt+<=krC@f>p7`dy@yCm+am%|u z5^uWUO?oh4QtyEKUi{^sel5QDt*_|aZ%@azO}Z0l`3%>SVnVxYY%(1^d_wl>{dYR4 zP3wu(dC59)@|0l9+J)0YAlgCWq{kf943RZ>pFSTr+cx6uY)!=h?J!lFOB|=!e9NQm zaW6jOM4WZaqHOE5WV)s<4S_z9mVikuv9n_u%WBksB+hYSJe8!gUsa~&WO$+tvl%M6 z!XXy`2IOj`F(yxvtKG;cM|2d%X3g>nA^m9_o)vH%vWV8@DXF4KC;;c|=TE$ZCzfU|w~DO3sOX z_Zz{hr>fa~r#r~CU!n;Dy;`Jc*LX!=aCnlNpNezB2G2DV5GGa`<}X?hY-l4oiFF7r zHnAv7ac)PdGBehVrj20OcJT=X={0!pUTHsPqb^=}XG)pmT!-exbGdE3hGJR|T~}kF z?s;<^v?>U=q)sTti_R5Idj73Ns!w|Abq~g-H)}XrrZ^}F3?Xqoe0t#p{Qv+!07*na zRBb|JkT~R&Q}#p&4xlm*3S{zO^pN?OCBQ&(7-V-sV3tIhzPD{1P(dz57MQlC-!ed~ zPB;%_>(OB}x;taAA5+o>gPXm`^bQ@Aw4>>+* zJ=9Tl{&F1Q=OIOMB{Ajmr;IUTu%{iC;U3b|g0A3}?hX+h{R+K4Wl@zY2bpxzDYrVZ z8l!oIcdi3rZ@kOt5uP5)N9_6D4T1#{epQ)16h0S2WrX@H75Ui(E|>C6o@*g2=#bBa zK7NXxUOsc=#d!1!|1qBa*8Qvs61=R_?I zy_(~ucis`#=@RCb@A`ZkJ9b?1B$ZfbKE*Z{Wuk!k%Y-lSZS^rt82aArZ}S4XEu5%$ zFCdp@`Q-#&=eT2bi{3u67|(p`o_OiWM`QE$-C7ju*CS3Ijpc=7v8F}oc3m@`)fcms zi2ejHU+EbA2}ocvf-bJdQJyj0%LOUXeHo6ZS)Xj||s;|4eg(3A+ zV^Q4bkVVWW#<_KXvC3j@N?%xbu!k`)IlEJ^S&d<75}NS=8DDz%62m^b+MrzbmqNxb;cR~~S<+E%)4C-T;*1)u~iZ82U14Gd~s0_C8P7}-e6iuPPA zL;gN)M6G_{TwV9T*~gYISUPIJigXMozZgve)(wgov-mlY%vD^EArYy*gi0|dD~xUG zG0c{>TnycovL=-#FKtu>Qxm3^N(qwz-zxb7X}sW2(ear$l~Fg9aTo>K_rC%0Xrz!# zec+Z54mHh-Rs2=vZiqMFnh?9HV86?K45*pme(!G&INJn%>5h+Rr|= zCNy6Y5Xo2h*&ID2&*icv`z{gms_T+dxiQEEP<9Q=f?>Euv=)r&@Zbj+gtIRy z9};X(&YonrlKKD|!O;g?RYOe=d@fq2F=H6T4)yL&r%ZI|-XGT8))`oLD?@ zyD@l(Z|}0#Cnr|gOSUL&?`fClz4r!xEPc$6SYn%RBSpX zXp(qyUQw^m9Fp={Py?gCdYpSN>uz)6VC`rug4rWwS;7EWMPek0AZ*2sN&L3k0NapM zjZ$Fn*|a>C+@3B=z&AE?rN;kt1vn=RNpFZUmW5=?} zdjFz+gU^dXO$=(0ESElVw@nRYYG=mwEJ0HR%QjHS$anLkHm%o@2?|*{x||b~>f#a3 zIk-v%Q{L$Tmja9G0=aAvJrbBqQ-=UHPL1dTd%YYF<_0qDdUVxt-5}4P1P|F4l+sM{ z^Cf!5}Wh~b6grT-_(z(cl1yH9YtirQi$BPtWGTtY~d&LNZ48Zeq(>7e4Z9F$^(s+3z zBCIXWbMssbhCX0HLEq!oburKhC=1EYoysD*RKMwdiHUxfd?sM}vAAWFzlnj80{mKf zvQA%%SiHnLI6Ml7qc4X@24f79+)m?kXgM%wbFivp`oyO)2$;H&nj?U_%@*|v-0i!s zjd$Gfsd)QO|4cmm%?GXT9q)XnUK_F!fA)L-I==VKujq!gQ+g>cztOs+ced?}&0Ds| z;a3kh{+INkUM_p`s}Z&j`8=o~Ro=ZqrEOzQXl2_cI?Qr|*z2MXney>hT54y(%~&vP z>tkpHXkdgkAD0($PRLsN4mAkzpDO^+m2i$$!qF&XR8mPcGp>PgbZ#)Ew2Stop1$sR zAt@vx3CNJr{*9&5(Snx^Ntb0;Bs^*G4+~-ZU@<*!HcOcFL5;QEwq6E`I~}NLyoPfi ziNNOCzP^2l93IOOgq%OQc{I>C+V!-T^5J*OOO! zYTEhc7LFXP2<#Iyolm9H{vwy@ZLsyk=PETJxxgxEvPk{lowMSzK~$hAxM5^Yo_@lCV~pg;lU2O`)J~&ou}V(nN>_#U4zwR068O3>1GLP=I;2jes%%{G#WK z2i{3Ef^`e52DMpqG%I)U`UKI@Q$Rq<9|$u=E%az~TbEI?98SW7H48R$`cH!c^UyQ= zAdG=SqLp~xCo>iOKCEU}&x4Glhdf`=L9p`e|H6woY{+|(h^*AvxvA@M6NGNa*5l(D!ws@kG_=f!QnOp~nWE0%k1JEi#Fll8pdinV6;AvNQTzwoK~8 znReS&^bpdVcHkzaw#S~UZ;C5#x+V6%^;W%uZCf0A@%i}neV>n4o_s_n2K^kCULZ_9 zJG&{a-v1_@l$PRE?arM#q1SRq@T~Cj+R0;y$b)g@D_YR;BrRW}JQRFmpiUAv2g8}s z8Pm3(*673_yK?bK%s1bQHW$D&R%FRwy)61tRxZTQ3yFP{4Hh~J&8x1LZ;au94JC{5 zCL4(>#-V9d#?K~H&Is}-GO%#Mng%+RMUX5B^jNndDQtvbfIC&5wQNU`HXEU&m|qaH z+_(j8SjjPriMc^bU)J#}iMYx()-JzI5VGb$Vwm~Im%1&t=`epC5{7?=*ea8sFBRSS zXf6sj^pVQ6h5O9djcy*1h1uqcg`r$%3Z8SWXeGl-85@aEeb7?_PjGgLr>HC5m)Y^= zi3I)n2~URdkimo&S=+Z?uAi~n8ROI2^zhDFoSD~abo5R_#^JPf;@DkgkSwwA!s!yH zC{*rOc%x7s0!lc(9H~Z;!{it`HSVUhdp@Pt2CeE6>&at!VfLX|*3a;b>D42cJ)yCv zOKQR+neipR$Zq18cCYZolk=43Fqx%sibsflE#6S(duyRl`w(buTCxjybz<;*?!#OgNUYcTr-Us;bB zJ9{8NXc|CQGWTzsif&`J$>mZylLsZ}EBG_0Xa$mK8Nmo9vDlsyG!TYg z;vA~(a3(eaQ@qB(Q7K_5x54OC+AT5u23~cc`T2`lc-2HqULW-2jmu4jcoLVgiw^N6 z=VBLa%{ph*5^_@2vvz66oV+xM)m}Fn_q}GREXzJP+y9rY2p`hl=eL(elyl0g4w+*9{58$FFD^$c}4 zr12^ja}8p66-u$;~mVUvHS4oz=-;QYVsAaa!Nx$CmYCTFp@mX%8Sw|LJ|>)jt0P5>Ol`wLHED7GU5vXJ^gDVv+b|eHImG~I%CUiIh3*-4wc*q;V@_M2y zSr+}8Sl`lup{^HalHwbvntB7%ZpFR32tLf~`6wd}-t`F@V59K$R2$XIQS77nMhxeH zVJh8CPTQ)Dwb+G~#au1w&Le6aKV}gHL}({fK!2$aY()>pXceU$F@DZXJ6$qjm4${D zWxMox9)1C3MZb`;aArvhH*O7>mLDf$k)1iduxPwZX~To@v7)iVT!0?Vc??rRD0iaL z^W59&uohyKdtZfTj4kLA>Xa4|o3+!&CE4-Ny`XbP3x zL~M~K10qQsz@L3(ZYBaH8jS|0gn2a!!t@Fr@tPJnLghNQVz9)o|*f;p0Uf z-2B_i6ycWyf2Xotr?(E@BuDU4A%iFlecog$=z}xYiBKSz1-3}nnSmK zBFwt&P9gvQ@yE>rEf5AFeO?PIW-{keM_yiFXM+g{f6p}*z>wK$LZsTV^I$}Y}&Fb zrnYm6S=CQi9nq$Qb|9o%)4lX@3T$?J>@x#muXK6bQcGWDBeFdr5+cie+o(6NxNI-} zO0cFEuR@Z>I)rG*Mv`^yM%XV<$_6a5hqPz~A++nRP!f&%5nQSjw9Q?@nNFE+osa}QwJ#?#jhvKQZ z`IzMWhnuEjdh71kJG(jNPaW3%a>w=ab!WVA9Mdf_w2p_L(20vt#W>VSUuE<*&$Hdb zmn#^s3LiLr`tb(lHBWB34+GKEWk|~gh`*=ekDEQ~-Jln}(`)o-A{NEL}T zuxMo0AvG7QnP9w!y-OS{!ok^rx&o6Jf2kE(l^y6=F2b7ZOfQpir?2Tm#^3{ws(-Jk zcHA!22<&dnKPT(O(43QbfEfCE06uT=VsxKVZU#?_q4TI2D4|HgCv~z&`57k_E6{M2 zWmu;?=>-k~_%8$cNfMahq6M21D$+(}jm9JKpD8D4#%k`wA&J+R=v8X^My_w{EA!e- zoASiY)0-^t`~;`Iy-&|#KPP4MVb&O@SvQ4&{n!P@iJj$W?wBMa{oUEC@}TDF zFcs+tIkrJCq%(gnKO9iZ*!gq(-L zmoJ+crnfN!q8kIKDM;*vk4;#~bdI>jiJcd>;6ih2gLrrthQ2Vr1o)gp!`z6CZKStK zhAl8Xh7gZhs;SOcF+^oIn|s8xgU01j4Itg;wk3AzDeoCQHNCK?hl2Dl4!c?tx^#-{ zMg8m@_r)P|Neep`ZS24~Hdpk7sm3LXBlVDJ(k9itip*VM&Y^0V#wa96<#weBy+TMY ztLC0N#pJxk+_GNOJ*gjLnVFr?g7V7Pv}0GCK5;nabe|kgc=M1Dw=*yfG9J_>Z*0gv zwA~B0^oV{uMS9vjmzooNMJ!FUpJj;?V{vp&r8OISyfG=OwL=g2@hdqTF4T}8GqV+G zt(QEc6gc!NJwgz5Gmtvj10$(T%EU%NG~ft2cdi>KjKms_EKS}ii;l)a=v1<^AQ*Vf z3qpa=O*xH$S_znB8&0|BV+e!KPH+Q^UdN7zBuHF0%Yi@~{Co?NT+!uZ-Q#L!4!U82 zu6&i^6JPhaNf_d~of>^Pa+&-`k=WHC)uf$vvAxcOk@-e;R5P(rDS5*)Bkle&t9mo-^VU~OG9$-@E!)lwHt1bs) z6GwPmP9n&x#|LC}=7>twjHTA^$g>^A+}H&h#(Y^|Ly!-f6mbE(;yl|Ve)V^n0GIrx zdWREVKPNz!Hd(H5NFJtAra1TFqt%Bb7h7&^%Jzy$O-@TMP5`YTW^tDvJW!c&;JVBr z%<*k5l2UehAlun%5h2a+c_!&{p_{yQbqTpFrWDdKv2|zc*n3^f=!?sl(@Swe_qq9A z0_|~)=?jd9m@cI=ldh(RCm2?_TZ zEe;u%lUkTf>s2IUYrIBhS@)(c$euG=$W6zVUD`$8wpUM`AJ(p%cI0#~E0J6Uf74f#ZFEvX(`TEO@jo?R|IWBZqRY&EF zEg#@pwUwODPgV|UUfhH}BG zO+%jCLCrp?69n=vCgHR44fIte@9Qpg_LeP$-zYD+;3{=8ODDOIXMLpGm;Quf0s_ya zyWxaXb!4(gO1qN=)3eS5hQISf3cTutca9AxA@4%40TSGfc>;r{ORWeSr#PJrWG{cT zEukitU>-g&On`g~yy&zmx1e3P2UP z!zWMZWx8kdRGe<2-nK(OZFTuITF~wC&DuPb$M3ar#R;7&jADnzEI}YJ7>Idg2@~Nle+$)Oj^%HMUxl^kjXri zJ_nBC><74=07*f~1J=wi+I3@1*dtpTpiTKDw;`+xZh8$Hum@azZKWnJLEr>lRDz}@ z)Yqk|3k`+wE>KxVnU#W7z5&TagDSAeB@dC-?*f$zd`u5f+Rma0(_zfjD+!H&mxWNx8oYbuZr?hCA)K9-nD6aD% zirVF(?<89<+Mb!7wQXEN9L5DZYAmi;Jo+yigHbf3S()+AL$8AB zK7akzuvV?ix3rQSzj3|5nkORX^@8hF-SfIxi?~gC*W-j%inM2S(OYa9lf(%&N`-Yv z2L3{}cIUh~!Y&<(pu~nGTvyLHHOPK~+_oUAeZUr-7sn+u+0Jp4q!t?{Xm<3(X90TD z`AX95mfJS$$`34M7R{`(Md6=PH< zOBOpFm|;OjNSLI#|5{#kf{pNUqfx3q2PK-?B&at$d22eAOBl0dPvsJ{T6AMaHEZ6V)0@uWhluSTt%ORvoUUyFSDo)sFA^ugQ)nFS0L8T8#KTw zpfgV&fTNB~yivPvq*O zf#&jHfZu|Sa0Dr*m=%t$ba@9Cm_-QEk{%vXaUec*Sr58gC)UuBxHKnqlvRvnOHs*^T+g%!rUR;j)B3^y=alPhXbL`l6eeBTg1n;fdtejs=ICWGfjFmIF z(~zd99b8h)`{%H!+a=MWtr8xo{&rb)QjNRHfjMSmqx;AHYF<>k@k{xTlU*glIW)QW zAQKGuyC|8d*qK02qjp^L#g!APq)RO1)gyKJ78tr&h}s4VD5}KuibNp>mS9fk=t+f3 ze8mm2AwaIniYsdQq3@BzJM(0x>?_yeg03tHHJ(w0{-I9Q`ea)~U<0V&%f7#Q!jTfn znd78**!=3hu8#+89g(nM6*J0q7F#6mFv)LT&;ToQDYh~>eVs4Uh6_PyqqQbezo9Z4?{nqf7%dV1dr~JZeVt-PXQu)OM1Jy#0*$SV zDQ}+W!3pH?6zt)nr(>&RZryQhY}z^>XXXya34PY|&=Q9F%aJ9sJI9AFb(#m-0?)#6 zk#wd#B35D+qh7wx_H)GlqSr_NPWap~<;ZcR;eNDPT13pbw9dvnpShiC*!ow3{AjKG zVYKPBF|AQ>E1+v&sgnMtM~utN9jjuu+h#D`st!`>FK^Om2H9yzub{(uLS#oHI|q0U zN=tBO)A3HCWtcd%6Pay8wrCiG*()_sBx6&Zx;tShrlN{rF-&f|WfnJW$TGMpR}M^L zRH>(7FjQMAXRw&c#>!ky=JxePOoZla2l$y3FERD{ld7HS}S>J2fiMq*r<+O$cMQ4XEdix>xETY@Cz}JHm{d&J6gYCTVCF1 zV?uH0Q6trET|1Du9fLYj4O?e|2@Fr$3R$+7pW*G>2CZ#w z8~Zw4A{zeb7a28xxvi>uk)%FPpPGs(L?@M$Q!Phkx&%-3xP+x(%QPCn(3e69RNdpW z*#Q~Ayf03<3K=&!?-=c|32(*+CtSCs#HMnSo^^l*8~AH<@Y)B|TaY#~-Cd%&M?Qs? zMLG*N68$FrPOOJ#oX#t))Lh-o0`L?>|ch*%gb-W35{8KoSlH|64`H%n>iPdi2! zE~#2RDkkbR6?CQw=?Eb<^w%w@4c9@o0jHnFwY{~ls7s4miotA733xwu%CB2{ikf0ZXj9ozlmvjTp^@fWPnwH#s@0r&jg4 z5#6K5PtMJ3-5Y!L80FPYzH&_QibjraGw_k@G-yfu|q*PA@oD z*?CSTB*q%Gc5hE09s49kT- zUyk%Vp(It_42utF_xTe8lV4R}=LNfmJ8+myd$Y}bj>NJteVOg9_E&p{nvs!sb0cAh zK5x0~I4{u%GVKD`_myj{G2p9RL)Lf*tL6~cIM{vPO2kXz#Wfm zTeifWU3>LDIW6{1OSVo9j4O5^S+e;Hqm;4}f^ICJ&Dpl}U4t5H=eZ;zjF&7oNe%0K zA-w$ojK3rR=<;xcKnFe-G$v^R$(nHd-2Gucj!IaPSV7a%%v3v4Z;5L(X2^0;j@IzE z$BlI+6|!s(PO|QVQ%(B?7ERs<1`tf9l8;|In^dX!4SbS|pl2)g^?BjRVXXRksr7TKZDNI6)RFD0|!>zeTYYa^*u@%x*A^^_LGfO=WjHmuIJZ z`ILL)rkBSxCiP`(>z=r5%QoHX`l^3Jc})vB4fa^oZ4-X=kmPf_9DC*3L)6YZhM*$- z<~Z$~YmD)tzzoM#Qo0!lOA(Fk?;}f#XoKCUYHuU`hX4NFjr^e;(_SMYXN%npK@5#i zOVunF3q*w>;9L=Ad^@`5zzdzyv=AjBLFoP?58 zWq=AW;eok|EXYhtXNf)$)(2~)VIbNL=#-pUd9#OdOV!E6OAb9BWUx@OEYVZ8B`LN< z{Ls0r&>*fwmG28enj`Z3Vp+TuU**M7JAv3;?doxTd#YXFik9+lL5LsNnP5H8T<9FS zi%onB$^~cd_lIx{>w{t32kWm1irjf zN*u$XkNz$F|6%XVdNfS z(9OB=|1n^|SAr}UkSt21Y|@lCwYpD_r_Wq-R?dE&cg6a?omp8!A9c&@_KD2>#k8ha zG3>QtN5oFYhzT>=Og9YMy5VX|QY}XsI0fZz)lcUxWG2C~wyU-;|CtO@LADI+=`+=j zQcoajpq$mux*bc|y)$gJF^M;5T2Mg#BlFMNHveB<);y^nvw+M9dRdNlraa>(8@qEXe&q(^({=L6B)@ARN7p1n_ZTPL>Z73?unlL31$ST zoMx5SlGQRwny%$ZrI-zsypG_X;!i(L zmObHA#th--5mZ6Wa1BLSKf35>1V`3f4%e;e;&VuDs+6=G6|&Dm?uCof)#u;fmUCgc zclS2yL)rAhasgkTmH}t!G;w5AWz>kWUwF{3OU{lsGx74x*V!R@l^MRR>5$`lcJFc= z&+aGCa|6UiX1-PLD#n(NcoJM_40^f00pM%WadR8|O6Dwy;B<#0>eiBbrFVn)@W7=z zejwrsGC!O?WuwC;1>NCQ@#W{`Mv{470JpAeb%RF_S?(_tlGbCow$Mcy1{mKV@+>R1cQad?;sF)hG*|c+j z&yJbWILX42pU$l(AyaGyQYnZ8y0S@LP$ez6ONKShi5oepK{ib@9Ug0N4^B(nUy7kC4meh-f7aaC}pq}jRl<)#S;IF&fcS; z09uM94@uB_Tx_b7=OQyHkN=q)ZNVF$7HgjOI0{{EeO;BW!p=H%x_SN0xJoM>n*Max z`c@|O{A3J8nak1qqAAN&nrUO;=<2qBrZ!6%^U2CZoyWz;3pa`Vdpxz@rwy)87g+;zaPNJt%hMH3px@bGy$y#1 zdnVi`eu9hFQS01PUFW7gyq!C{ksHpSZEAFB`K=Qn^|^GVAN>5`JNphydVO#LwS)`W zvcHONQvFt&Dw;*#%;Xo)fRpCHk=2kTiOCqajhu{TKT;z)K&mb+LLlQkv3W0DZm^Sk8;ySL6 z6I9gZpKx+c|1{b>b&!}RJw2S+D0PiS&__y|pBZX1!*Biiu;N1hsvg?x*3NY8`pbOF z^vd+nM;~Kn@i{AAfvR&~ecFaxGv=qJ)PaU^X25*Fpq{hwfuna;nXSI|(pPv*$_y9B zfNWmeoGwH6llT67dT{q6jvm1nWSV;S{(W|k?lAMk&TR<#K;^L^(sj&GiyG^h2_EGN zT*^i%=yQ4AvTSixe%=J9-tW5{0ty@SQPBmjGZ+gAT&Um z!6Jcl;m;y8;^Y0IP_^DrbE>9wJoz&gwq^5XNdcMvBW0Or6?2rHqSPRa5ZcwWBO zkRadG)m(s?#5M2*Du52e)wlo>ad69^Nw^!B6>6II)3n710-Z1`sSJ+LQb8nkat(yW zlvR51_)!KtAr}=Yo?IO;g`>eO@{BU@4Teb{c~w4X$)Qja9{5$RFbj=2e1|1vBk%GQ z=^D7D`OI!%eJlt56a@m#n3d}rf|`9c7x}6PswG#wzLrMIJpfEeu;MA>K=MYa?!4N# zFiZH3E0JmKI)9$ldF4y*bjs6WslNdv?*)Ejp$(AGFzpK-Iq{P=kboq?e}D2D$_-N~ zwBz~oqOcNEMFevd7QdZthV8LHlbs*asx7CzDu-nle<7J4VX6-`Zdi`a6g1(Y0P(4t z^b^Bm1g@k{Ssa{|2hUpfrqFeU8HeFp3#)yPqVZKX25vw!yG`=ENBSMq6Takd`-8pd z{zKNoU3)1{^-MWTcbTnt@ac!smak-d;P54qEK|7BLt@a2B!NM`S@|uF<;kFFx#BEK z87@ZES^gLKBQ1G?R=!LYcAt72f8s_7MNCB`$MkGc2tZf}5!b9DH*lrc;fOJC30O5NV<>eIq(y#gP#(_!W;N@CsJ^ z8pDAqP{AWdm8ZkzlOhoLjzAf88Yf%^Ecwze7$bp9UC+P)Ub^$rO?x)ikoa9L%L}qa zsh=681~)5ZvVBBo+P=man-(~%`U0QEx_5t29Abo9_ z_2G((oNKh7v$adgnG5SDB3Kj5FpNmK#S-D+mSMn=|jH{f^JH3E#Btp4(*E> zI%ect{&4Za)^z##*V%aSU^-xi@HR&Yr9VO`JiuegUvL}~B2(H_^=FH#u?HswO*GZVaqZ$ESBSjhMoyflgJ zi#Gso)q}LaQn@v7h+oOXX()yjZvi^Ol_yM9EXj|!qF?+L1ctOV8#KpNh9a@>jgHDy z*(Hn8h}Xb{ql8O5@`zN!EZOH}=(5^ooPj7q%noq=mCj3^eD%CZBxZywecD`U;Xlhv zS4+Kktr_YiYv(QeD)?hE7f`1H5to$K$vocW*N=|KEV)aohAmg}3`d>IycQ?QkU@|I z9IoT|>Cl~yzs!<%*eqn6ft?B}`vMkWtS38=Qk$nUzg@}|p*zL=0o{xW5u0oJPmu1 z5;BoZ`Dpj1GgoYPaa${0@}w+r?wicJM*aFH|U(3%sKM!host9toJM-t@gnib0NexuRKlLr!Q(Lz1+DL8EG39LsC{s%H)G z_zFo{UYE03#i;iu+o7!hGoCv0TH{7vyCs9@6s&A%w<|}qOQ8KcTE5E+R>nC$t60@S zIx|x%esy4+fp+9HkZZfFJ;=~5y!fy7rda4u8T?CFnDKhjoFTx>Cs@=LzO`gqt-fYC zEted<(1; zU=}XRQ&>NmF#wKkd^kCBHtiCI$P3d;Z+;6t$9#MEPnm7|h%?%lv3~BtbpMk-;m}>r zOT5W0?_*}fK7=@5S7265W6Mu2$21N$fq@_<0OC3a!mPc5f`RJMgTc>Gu zb`*rqT34*C?8>I%e8|F{W5olTmng#qyH@4DdiY?vf9I3w9&1`z0GKaP&@e2#9X0&( zriHLyV%glH-e^1fgzKIRFY6E)GWH;ZMqVnwib$O>RuI<6)KCR6vM(zt2&CxfZGdRGPMGU8BKt`^u zo$Ji{E){{wqx8mZK|8J==#-*j={N+tV5XK^7NQ4A=`X~CP>Ml?K69Qv3{ullH)b?>^PK)I=8Z5)DRw;s^K<_aciqd#h}l&Mm+(e!H! z5WaxL8=+u~NVVzy9rUZPgC2S6Ov40?ro07?LIzE^C^G-z;G{FcrUWk;r+=gw#MWhS zRJ_;=4F^fkR6#H*cW|50@W$9Tkkc4v%Ougf0;J7uq|Iu4X}D2$7zRF61m}P;k69tt zae2?yK-QpJMz4W(gm2xi@jBgdIlGf`Xt>#C`w-h>3<56Oc=F-jx*{Pw zSAC$N<*d@~S2bR+sE(kKraUhe(C0c}0!!a|Wc!evs_(P&^SNp7F2`T5!)5IOUq!L6A2Br{^=I_ zMPnFn$LP^+rh%4+wdm4fX$XxpWCSF-OvMmayN)szx%)-6Henrm?2qL6hf%uCuGQ@= zjBfb)Wrw@>m_2;R@gt;f&?pY}ctB(I{3b|wl}x|%XcV*$701$|F2}xybvw1KM2^TP z95P_tEnlyap|cPTxQI~3PAq{dD*Q9JYIjZeQG^9Mv(!2DTgy3+57u@O+0I?s1&I2N zTWi$WXLBuAYJpPPFQ4qv7Pfb;!CMQubWshvivk%EQt6cuib1rpiOGnfEn))X$zYLZ ziUp)Pp;3ZWMcA-xk)0Rc8!1kz`I(hM5t446j);T4wVj4&!jN+&E~)(4l7e!ILed(? zNYqCw%agrv|fs<7oNRUN(NG;o&?Y>=xM-8uV5EuEZV?)kRF!&ZfhEmp( zo#Y1R$?C!|0J;W;GBHHL4amgEh4yM|R4B zS$Yt+^FnGw2p1*dz|to(#1EorweqzJ#$Y|jgore|y`@hNCJBcw{fj;(%}t;9p?|=) zBKNp4Ld5*C9mlskT;Nl$ z)~<4D+}A^QY7SU%Z8+lT%;(S7Tk_X;O_zVhyF`MX?@}_TsdI3o4UIBe^o#Cjf&bvHP)-SG2zl>4y1nNOVh`mFdU-_7AaaIY;r@3!Ihs8_ZU%P9J^u_a?TTPA|XwO$^2#PPab( zgG_9#a#W9P^Z4KnYtxV!L-r7TAHzeNV0BgF4mpTV@T-|l%W7XCP5Bs8R)EWR!&e&e zN-(CIaHGgVQjV;1i_GZ2b}4gQG{;v|oI&5(n8-r7_ zV%LCRq0y%(CQZtBu*{RDGg@L96s! zbC5!6*sNyAPcWzPxze93Wg!CdHhVaC$#yg zot1UZvvNUn$x$s|fh>bJ*W&AdI(kJFPWT8OaZ(LNoq;Ibfd@eT^t*hcABDl-k&>8x zDo?(ln6|jiIdYsd%1vUM<#P{NNX?D&;9=_0b|$K#rmOI?!5Li6s(3>=;3nehtiVYX zV0)@1vP_L^O&PA#%B}Ija7lx=2$#6fvTW98%3IitN2PQBupNk)-y+lmRbml3?H2+_ z>Oe<=!Fce9PKkH6Am_N;nCZjHBOi}{sCIV_@TnvP@Fu&%MVqtfMt+O~cSW8&WWoI| z{5e{}Yn_XBba=ea4c$+x@v0Bqv(`Q{CiX37;iMYf7%-eyt+W~RSF_LPYK%E*GBVqT z;2$-Cqc)|KBW_dxnVJ1imrrrBt+qH|gG(CeS+f<|!~iU53Fdwvn>rfAHvar;U!HE? zxy!>8vbe_0ehRIq2u$DZ_`0=ZR$gb;y=-~v6&^f%J>=^o+m~tN z0tREtY8x|t8e2?@6>r+BWpiw)*ZRsAi7a;dGF~|{#IzvNusl^SQV^O{+V>IVIM`#B z6{GzUM(E{h&rNr4eK$IhcFC-qDF#xlS|rZH&%o| z?4(y?B$#-9%ot7qNmI|Z*&$=WHp~-z`<2;e$p)np+iyDXOxug#3zi`>K+X0Zp`S88 zkzUh%jE_ztW$WfKV5gGN$$AWjSn&{05(uO-BAUKdWffm3yBrp(x?FdN%8|Tz0v4@K zw}8?~;}SxFZESAWnMbz5X1axoWg~WXi7we zzeOkyS)|L)OV{DE#$58m1S~g3kSmOK=l~jGXP-(!FfHJOjaLwfXi|_E!w-gOdr;sYQ9h@LvBu}Dm|gQ+^l9}nc-N;BcEhb~@|mj5 z7Z}lBYr;8sQ`#@PW5@Iv#wL^oJk8OU(V>RD{k>Eu4SSh+MMIv8 zI}$-V{5vFpdv)t6tNc0YNKiA_GS#b$=JjeU+MVTUWPMz22qSEj2szmZq0>#T=6 zI=aOJ3Y@r(mfLmX$dnGCKdjMfTp1o2^<=7)RJ9*1_{!;m{+hH*b* z0P^_C7Y1Fg8f|5`je3EblEm0n2=y8%BDo2sp5sVQmOl+6=I3sOZ=q6^R$sZ?z=)_G zpU^cz>ZUf!Pwqe%p8d}@kxtUOO+znQf*DPx7Ju~TkZR`Q5l(bLP|yY*`JvlL@|12N zXB-49QNfpBg$%>KN!xr<@&GAZA*5MDK$?A#gD&U>gK(#706WGjGE{e&8)ub6 zH>AHta zi!3ADUc+>}Ve~uPd5`nu;U&EE&f?AE4X3X&Pt`dgiR}d)!T)8{zNLs<+rPSa~WHO_f-=4poKbk62IYrN%{!*^QCW?u6;8=m7mDJ-oyy;KG# zFRyaeA8WFHF{g1?$sz zXkvDImsf?GeD`AM2!Mf$J>zBXAnTg+?e)8x4V!HW-7dRmM+Vtsn{(QRg;9z?7t*>A--JS07 z8uw`TK61PVKaTFfIE*>TgOSfcZI~8+GzRZ7WD$V>j9b=ANr+aL23+5}I?XpN-rIVt z)Bx-Csaw1zbr$o0Gwcre4A&M9nin~&dxOvA9XM;gdxtf0Y<6H~c5MSX>Wc>~Rjy-# zuJjB|j}=nqKHFL44qI++a4lvnC873#TdvbB8p~hHHV8_H|53ap$P+^%JR=fv)QouH zgq^I<&<8ASQU9#$XX9s3&qQ0U;`b=0GvG$$y}pS7*MjNKZn_Gk#C8^LQ$@ji(v4>p zEvbY7&J&GDr9U;3 zd`i@^HNNG^;G^orBcpYI@QJHi%T(EcN>(O5Bot@9!|OaYBET%bNGiyYH@r@3qv*7; zwa9%8_8D4$x4x@%DaKMdh2g(np|))DHav>D40nQT<^YifWcA@+G~0l1Ib^DMQ}QTErt8srqMK-S%}N-@RtS1^dCe+(*O>`Kqrf1G#t`8ZUV)!5+j`^! zo-!f3Rq18|$N3t2)rr^A<(`)@(vp$OdN@1l26}Yosmm8!f9qzEEzW)1U_GC0uD{zCpr+-y9B-XkC0EqnP-adj7SLi zEC!qhH@luNU#>CCEacB-xV3yO*Uus&#b4jJh@4h@RceRZut`Ouvt_TG0Krv_8EfTYgR zwVEatqyjZ;de?-x#0q2vXHc4zk>4SR;pQI%<0LMcKqtl*`ZPx4>g(pY9j=_pvtTKo zu+B2#7Y##(ZRdu(p%5q2xPI4(k-&jnA)y^2jWK`*(LvOk5jn;k#JOw(uKRKQOvzmG zNZ&MXGVVw|n)p`XA&aC>p5xT_U*p`VZ%?b+-<)<&Uc*G>Rq1rKNfI8;rX?%=B3^OB zrhDmBZVF{MDNp$s1e%V1JMri7PXBrMO}k}m|8e}1?hk+s`>b*Ze;(aVZ+e6Lxv;+ zA27Z%(*zwqK_yW)X;eRwV5!Y+rMw!eZhSbxz)AVY$XZdxoMY0Uq|FuBL)NJtF}r?z zg0aK(`d9xZ-&lPOEzug#4synemQ4pxpIVU6s` zu{+XrRwh+L`Ffo8{^iV9-XK}97_A!Dsp#lXEJr@e7^4g0jX70t80)w+Sk{=0+vZT> z_2Y-r?N2|N9&olC%F%io`XlVj95kHGb5jO6p?kzbaP5v(Iz)&}=AP$}W?8{iq{o4t zhaMeNiO$WQCnCqQ7;u208$^-p8TaI}oB@t(7DhtPDr1@$f{u2o6jo|(==g^Ymt zq57!Ok&hRK7NQu>{UMG(HUR#X##_M^Mfg@Oz70kAQ4E3wBYZpMoGC5}q!1#NARvvh zh|&a-jSS||SPRx!wx(4#?1paxEhJK>rWdY)?sai zpwKvV>Ys*p;^ZeMGM4s}Qi*{H96xHM^7t)s$zQe;(`Omv_H?|uG|ODIvw&IzLTdbw z*dzLYTrAkmC{tiXuj(;IDG5!kjkZ9J^v;VQ%7Y}11BPmmGePvzRHCT}>yLTX>Ies0 zND0^z^lf|8yBiZ+U`&T{z%yTsd&3gM4@~>2^xi&ou#S;-{TtKH#cxlC8-HcGxAGO9 z)}LoqgS)F$)R#BgC2JxhZocU$#|SrEaFl7DX&8VEfCd{`e@3inkQ&$Vf!@id{3qqj z0q1arb?qB&UWU)Q=o4V&&W(N`iZiEi7SFgaOPev_G=n#8(%CeV9!V1q7kc>YZd|s5Lc@WCdt!`Hqxtz7->Y2)yZru|QUZ<_A>A#0*oY|l)QM@+0}$T>Sfzu0Hjszy&{ zz+GP@O~%Y@f*_oJM7}d~Ym_+;!xpjiLYZoep?8vIQBFG(xa479Ouldk4LxVlJo4lK zqiyHHkhNaD9632fxR`{*ade?$EZV>*&;@T|CQ=*P4?Xp?Z`m9i~iyjdang!)vNM1#<)(qdsMIR?qvL zS=PY;*;#p!*RFo zMfH{^)f7IX^5FKD-*r1|A9X~0{j8tWi2Vh+YJCnkGd@;dT%@X4EhWmW4<(Pv9Y_jm zuyP5XBB#}D&Ts>^Vm3Jt(@DoIlKjA$MyO>;Y@`i{s%*JNfaU-OK}An;D2N?SNffdh zWHGbDmi!_X8|f6y_Cw^%PX-mhYE7BtNjC^o8&hXcexcDe9#g(j8bxy8q!Uvf^SZ?mC)qPqQUF91J@$c#3j8!Fodf)6>Mp+XqK*2bQ2t^5J)5xuRWP3pU$Fp3tqd} z0$A~osk|<;8IgD3>J>&M7!HhWCV5Gzb|Me;Q$T=?mRBGl5S%m)VS`W%qF385uUB3F z!BBd_jlhA=Gu>haH&z!L+;iCN!>a2jIpbgZb+%5jDnC zmwEc_jO+BzhBX+KZvo$d_C@UQS>L?BjIQNe_OD;Kl&#lCbS3AOr~Q$Z`slRwp8#*x zg4R6Txb2^lXV#*gp8zL)moU>m45vlP=bT_0Umv0uzDcbD~d z&MHlTter)t8Om+Js!^1}7imVhsz8=t5K(TT>6<1eV6KsJ!7*s7F+ZqwJd=!YEr3F# z5Szvjq7@nW=llmWL}+;F-bIsmqyQmQL&uJq)L?a3e||N|q$yr@TZyN<<<9E0)5|Xd zr(^|diFO+7GsL9TylaMvE=OYisDuRd{UQeWX| zYSK&>-Jhi|&cZFSOeNyKw8kZzYxaEV>$Jp4OPql-=0 zto0e1Vv-m906WCkUic%?a+(qY*t;7~`o3OG`$8VSQgFoX%A+IK(_Ch4&2w!2`^vO^ z<*!W_p8tE(bmeb?^)@#~UaNS6laa=nmrc|ggK0iu>FXB$XlEH^4R@Z10&xxRbAPFl z&!h{`9pW?l|=*}>=xM4BiDjRq3o9Mb_>#*Fl>L9Payz(_wm zEd92|=VmJFE{brH=&no_VR~i&{1G+>e(6Dhn`gOVCk5HVpcbv=X|$sFkAKo@~<&_#=;5KhpleFjdz5H7_5f} zO1gGYd;#a4eFTXXt z|AT)@-jC7k`t;n}zl9OVYt&DFI$e9=i__H`FXYIbwXwNPXe+P$8rPUL11__q;GY z@uu8zN0%eB)1F?IrqceL`aInEkNRwc+;~Str#8>zFX{)0ys?$5LS+U5b!R%8DRLWv zAoc2K4i8qNnh%hFQH3Q<9zLxk!TgA0vm?^^#hkd^@wAIr+?E429oKyK(1LX@y<%ul+XH<1HqI{*M7y$|9v9$S*ElkTb;Q&hzXinGD?0A zmV>c{AEE-=I;x8^G>-^mIG60=a&e0ohEn&uCzh({O2#nh>+=1c#Jq9HJkuo z#LMaL7;mmUagEynpVT>8d5et+e|_4%@pq@q>%TkQp5Ek)B@8#dNw}DC>zk#9TN`ht zd582-JU*UljClIZ$e%QL=Xea@yc>Ae^+~x3_PPIA^neys<3fk?@Mod-IGpCA|2UYZ zr7N56S!EP%jrPOBcNz@gYPUI;&@VKk(bJ}4T>+9fLX0LZN@VrH!r3^DIcf4*_JnUv z9rERYE0^Blw362`2?n6h1c3PvuE`}&o2=u` zCe~V3?nUwkCTsb)VP3rU>(k4>{@+d)Z~PXsd2jKMw#h44UJ-KKMi%6!yqlSoJK*8~ zYmAFEEP9Vtr)tL&5F0G~+C(07ExR2Yyms9{dxL4-1(3Cbieg?Tqa;erLu;v z+}^S2n$N2?JOJL!1os=*qSMmKu>et+NT3qI)2Pl>q5vs}}AykEz&G>69zYfR3wh zYMgpZj2~J_%4jI{Twp1uKtgu?7Zp=#i@e5LTocoHqyAlMbIi`KBMi6$X4kekw{4RR z3db-1m%M8F+O)@}h3SY_rmQ2%Q6%0+vB_ezrTIWW(%Xanfwv>NX?DGON8`@vm7RUw zMMvDUOxFqj8Jz4aOQRcq($%o~pT)_iX*`LKc{+XJ+c)hO<(fyAP5)W0?*7WvxKEO? zVVd@nU>}!vmPXThoG%>=o#{yHYqTQ{-c5UoZyuMsCw&#~nl9U@_2op83x<77X!{qY z+pnlmr4*ME~^Mm8|Y_`d1QH>L|$Uj~O|61(ioJ&~me=uEs={NY) z*4^p-AN~P5fqyvN;~c&#m%jjy4c5y2l+6tvG7h2giL)aqhhJw%S0in4ze+mg&3)G~ zjZ!5HB)-~mmnq3H>NIFGya$qe^jYIN;-VK!KLW0Y<6`E~dz%Q-mG{H_hwN)%Jsu|& zZ?bcBd&6@CX(t%V9>$%Tfo~XsTfCG{x$2UZ|Lt2H;w9Pe{Ho+iOBgKafUPqmrKh^! zSoeaXCk4YbuaGfmcBxHuqF-l73Qzrm-(?ArbAO6{WzECq0w)p8ufJniqCRj$Q~&x^ zXad7oaP=%~J7LPH+6p(VK~+M43`847;EXSE4Og34rlrBr1Vm5j!WULLqz|Po;s7sY zTCxi8>?iPpMv^5L0h<33SlGUnsTOOnBWR z(Kt=3bx|E{4l^jUjDx$zn{G)<0}9yu`&0V0#7UNnY5i9qXrns0{KFf%3q1sPfl-*k z5%Q8``4w(Rg)mQ7bVmyti3@p7wb0fY?X&AU?XCp1tNEmuh8@BozigTk#n~ZSj`RV3 z@|S@J3eb*G#{%LLzLcQ-iFl`G&mX=z^rK5Z<5OX$h)3%v8_K8414ay*hYlQnV; zab)MDt7G8eA{FA?Uo852|Z5Y(ul z;d^D)Z?uy<$Rk_zduMOd8uaXck-1DM(;V`B!0a1+h3Uz_VruXw8oiVDoJk4d>EpcK zyZI95vhl(C+qdsicW>WjIhvn? zV+M-Pacy$g_JymjGb8pkOX_x}y@$70hxQOV*w>0zPy>c72j8yq#%k5$CXQSecgPwz zR0AB8;K%WAkX(iD-t@_Pe>`1%>C1dA!UKQ*kh7J4!Yg1lDDdg7D>uKw z7q0FyBljad+4aJ73^$Er(*sd*WyCLk$gDJ#lH~J$Qb4lskvQ;$DfZ*8%1SN_(hiD5 z7|PSUM_r|cKEc*kEiu3UK(5A8jc;g+Jje1D~j!~q9k+KM7#tMac-bqzD z=@x9LBc6>wr8jQ!QxuK(f~7JB;mDhqIG|cc4W-P`c!Q(5ssouH0{E%AVYrPoZH)_t zv`dWPC#5*1qnfXP@`^tfmz2@46-ie!WiU`cE{!f5PURc7^OoG^J*(>qUFco%&)}X2 zMrL=oNDJ}&>637pR}Fx&R1?;Dsl+dow^*;^kMZiKFiA{$>L}#UvFRLkmR08N>rg9VS2AfU7bW zcLx%;){`1?wh3{eM%!3F*y2f=bu_%HIO5gE5jP|rnt3b5bEr2j&TV_Z)ArHIx2NgF z|8d&8`uC>Yz1LyHu^^cx!ze3uHt*#{a?+D0%z&H27`9058`hmJ<}$7GD&@()p~a7d zR^N{hJtyt4uye-Zwd?SpB_C-#jaS3z3;yrJh0oyEd>1|fEW!<=P$N9|7XpW1!3e6e z;p?Pjo$4N;D^0KS@}4#AEWUWNYu?68w=H(Qc^!9bw7%?aov%-`U$}H;;Dq5EvON~* zA6}&|tbCOX4j+O49-m%f*C(It%IjMULY5DwZ#wpFFblQjOb$j)j5-M`gM9Q7CgObr zy;SqD2RmP;F^%{*HZK1*8 zue{0_wBMVeQ8(7v!c(Jgbz(i&!|(H%tUs9^@+woevdKs6w|H>dcwySQ`ej~weihsc z(;=UvIzHM(+y~6M`IQP}aybt2ZmKoIQ7ugtfm))@!pP#P2^2O2y!-QtLmm*4n0^7!3h%83jcjAOjT<+Ub;Bws_RFF&A+yb@LETtqdD)wMhA9Scpd z4r*l7AZKkGX-i*v?dIW5d(`I`myLInNupjVG7RLT(;wm7 zl!=xAGKe_%OD8@6{>PPXgCsrE+11&S9=;7%2u0hprmf#s!G=~+m9^5Ig;ThXzAC z7mv}brMY`>aXQ-m?P>d^zt8Cb+_;W78pQ9H%BEq`K7M8nVZMBmG)H- z>5CaB?Ej%Zn2j5zCXs8gjyS%=4Fwx4@Hfkw`r7)1>5VV^=JfF=cleg)0S9I?L&a;o ztZM_t5yj4&F~GMFhDpsCWh3tRCPBIK!gAVLy#v2pHXnS-?$Y~ia8(+}5J7+^i`qn|Hj~&Cr7qeUlYLjW6hU28sspdmMl23La6s)jEFNg~s>Drm{ zl7QmOF7vfRGXW9+ftLK75IWDW8&Kut_o)H~iU*JNcTNdl1F{0}7dKagGTiCuboQVK zu3uNL>r%jcQX?{mTc8V(#5>1@2ekChccw4%&&v0-xE-N+oKIiDu(~ZP!NscUj`0vKtxlYyVq8o(_fl;@~A!)SqE>~a|jH>?3Gf8TB z3Ko~d`ZH{_ko0|aiBD3(#z#5DaUF$jVHAp=6#c?guGE9+;Z^>K>c#cUr|fejwU~)N zfI`cP(f}KY@r-A2?nt(s!p(JgjM1|GaJsPl9ERCTWPQl1M83D`fZz>{X_9sNh`)Y@ zi$^2!X5*H9-wU$w({_mCr;9<+al{+v24jY^NTRP}80_=f)>nwnzwialV7q|<_X)4? zc3_)c9~d8g<3e}Y{2eeNJCx3)-%wfNWw( zBQ3MDp@9k@o{bOagtziBglf`&uvRAj!ss;obpE2&h%AUkCL&aZ{$pJMrbH5zwy@%G zg|GL`5pO^D3K=y2AkCNrSETdYJbfNM54O^>3pJghl{<{LGSXCbS2JF-5b*RooE$_*5qf4vZH@QiK?Jl_^G*r9#H4tmXq> zMp~dUU{-SBoXYD00uUMtgE0w}#_#x#Oonyw{Gt6gjErOxGB_xQbrge;%rqbUq)S0a z)A_CcgdHGlm-+8J{7#cm!V*Ztj=-Rl!P`NB+YKk3Pe+`$#BAIi>udJbIGFdPe=r?i z`j2_CWJb;>S}tkq>O(=1!HpM@Bl0APEMMzZy0YjZcYZ2<{;xFs zSMGr$u93J(ACZM8ye@6VrZQZkS@tE3&TYSG8=r?e|55*u8MwKTCbL>!Gz>K>7Q&`2 zZ*GASa*<{sLI)XI@Oq?B0N`WH87|j6jCJD(T(Xz+3;MJFs<<}DqTq#aC8yDX(XWQu zIL%cU(tP&R~Nw;$X$D3yp9Ood{vtXfp;2 zq<`(eQnp@H2IJYOHb$DdA}^j}QvnA)se&Zmfd7Fg{=(`gpEGbr4Akas^2%qObtNYr zAHdDxVEc_}A}RS8efb&n|$k(cf<@A7$XPwr+YvB3tq3jKOM7g$?u@<@hPn}KHYWk=Bv|% zYd0_ieT~YRKz6Pk^96(4IMJ04h)xRRUW=i3$gb8Mzlz0%hNEMQ&rJ*#zB#+bE>~Zx zp75dhYtOyJA-dbVj(mH1`SstLZh!K9zU=U4d~)l{Y#O*Ueek`1$p(wBvo`LpAPj$n7%wChtpEu1%M(U&|Z4hr4VDVf~zE=xGQ# zH>pLUTsE7!e;{KTO=^i;$yZp>e6UZN;e?hyX32Aaq%1=BC1kgS3j2G$lg5G$ARU(H zoW^NvIklUIJMB^bkr}uMizedpqtg9YYY8xJvgB3;>(8%YD2NBI2rEwm!OQ|7?$0RjI*4@*8=!o zwCs|CCN99>5xygxOmM56gjQh0E8I#OBZj=Z{3)u{ri3-AaudqnkPttk1DmFf6kXpy zHYiYlikoO&`G%X90Ffy?8~~6>Jnlsf5E~5Kz7p`1g(Hp!nXdfj)Be@}f_ET%V#wE~ zK153_t7wUIMU~*P`rLi4fnQAx*qv8y=*~1_r`;I0d)H3*xge)3l6VFmn_DJl*@4wXjEroVCVUwhj2KIxx7l0z;(M zlJSa_9iukEsq?I+Cq{Wxp-`^2AOGNw zG1A_`fc@6={XhLZ|p0#DFI5*_qatSKjPt$ZM8{j@))HxHkNn#UfQflFpI(_mFdV+x`0 z5V^?izB5FSDd1uu^&kimJE%CQTJXMXz)OVT-Regi%y`&3fHRz)m3r$Cif z6;!+nbtzMm63-?pPX7!H<4Xga2ZM;*@L40k>pM|~IXq!+i6$#DLKgBbE>po~1WD=9=YeEv=~{^STGbet)= zB=KWT<(6^QpxFhL30fi~%^Gmrb$Fm&=QC1!JVfp6T*H9-;&kiwJ=T^% zh*xvo{I@S$WEbWxuWg+Pg7&!U+qdU#ZoHXZV}bA%gD*EQi*>}YAV(OY-s_xA^UV_x z=rT6&dieN&SEMKRNxIKR>pz$teE35S@pkiraIB$SyEd)wyvVGa>*QV}=_0R6!Q*x4 zA#!bHSFOiv5T^{DF{vK2MvXiUSYtB+-(6kf74`0?7^u^9_hY`Fdgp!4s=GGbc>c}l z&L=;b9^U;hhT}f3jxSxmz-HY8*1~;+e3a+-9yA}aHqQ|)W-x{IKI}DjWfVo@DuFU< z2ujdNo_%xNE&Wid(Nop#0mg2|XKUMZ{@mywE$P(3z|mlwV0sWH^#ixB7ATfhCe8<0Dhw z;G>4&Tyn1G&*nThs%i0!%K*>As>t0iPejUMVx9Q&^+1~QMhcwwPN`MOV-Kp zN+}vbOW7JtTm{x5KYUgVR#-Uyhel_TWCknTl|ZMjC0le%QI6brN4Z*&=6x2|hMaz1 zHdw>Ow;Qi<%ODraW}qs^ejg(hKVv{(&X;Di_eEywRpW48mX4RvC|~Mjz_dXuo7R*B zrKMaNM9MFY@uE8#l=VA=C^44}tY%Xeq6LrK{B`-7&q#%SF%B;vi=VO(PA4h)>9|b` zKXigxVRc!B=@Q{5Xs2%I_T^5tb;PAFsJqYf6Ss_};Tg2m&c8*>C=CpF@zh?ORE8wXV9Rp$rj+<4=L9&8$Sg_CC=NkA` z)WE!r8}@SC%-G5732Wi(3x`LS`I5jE{<|0~AJa!U?3G!iO~*0sybP*YqYJuyyT`wI zH!>W!kasm1H+=i++SYXA70yS)pt-|gw;D_4VeMcLFl!b1B1e=87@iFT)#tnK>w_;J zJhmOcmQ4(7U|8kbqZ(A1X>x%*`5GSenvn5O1BP;~V4SjKf7-wGgXzJ?-@$m|EIRJM z$7~!}+kTn#d2dXYZhRwa)K)o8Ayh#Y{1}ie;brx zWrZ0pe2T!;_rSUbIi|`x^{Fnxi}BU^CDCy*_d%n$B;!X9$kyYt$Ax75I2v{|Sk165-UO`EP{sEi z()SS(qd()_&cd2>F}x&{@SyW(`;fk{`AJwbo#{GNSTZDf=4SvUbsjzgHjA>^WBnaj z+P9iX`@*S+jyGcqm?_BkhU-%9+6__)FT>2@c!Mop%RXOL5I2M^lGoC;LE*VTWef^y ze&Mtn`aiF$XcvzqUyX?3T{4wY%eW$aDYEFXV^1e80ZM)w5^%t_zocL35-Qj#TTxIy z6&EkOLW6-}VW^2;Fyb55r7@Z=n&~DQuYj@PlCZ!l1j-LXu|vBO!LRzNG->)*1*5;< zicxih!mVWC%r-440WAt9ceSe@O4i4r=M*}wS1F3NrRE7+J zpOI|c=%?P%ag0(%17DY*RgMwid4KDx)A9CyGTqzwUDBA|4?VeXYN;c0jur_xaeUq2 zS&`3wKi9yopa#0FTQmm*+q`#V(^;=o>kvrfIjBAt$}82~A7asc#7!9^#bYFx&Fa3A zE;9xU!-?bT98o0eShM%g*|iHfWcTJPU*b^Rm!|hW;K-5<&SeCsNK~K}wllsVpObSn zRyof2kY1x$8ysPjA34D<;~#6VFwC6tsr6vweFhJJWMF-<~!vux9tco$1Qe zUyGg}?!Ak(iNVKswZ<&rI_v7Hq9BYedW^UD;g7Czzt{IKeueW(eD1_=qqxR*anzv( zA1r|P4kK>it5cMgab5cZSn@RL)P6oyh*5gL>*nyUDI{D{@!Y?~)A zcz+D-!7~CEr^rDtzV**PT*^0{p#O_2Nzyrc!!Tp+Yy#qnJY0t@gXKTp&C}=M^Px?p z-0SZY(er`MhO8(pz|fr@Pr26kp?ZLt6vD>deAAH zN}KCdm`XJy9V<@P)(zx`9SmKzcD@7`uhwPZxjJ|L}W)0?@m4?YscwK(nWv@e*XKp27VmnD*q`U^-vy(7U4&UH-{4fIz} zN?y4gPdgW{F~jub>C;c|@Y+!#<4Pyg6wuMX^Q(~J(?Q3$ka!O;$$j*va6tjbuATcH zR9yIPxyq-Q2)0VOJwMW7oiM|-f|0b2;kUlBhbrz&d+f-)&u-BpzHjRKuyuA@9x!$s zZC#%>Uib>6u zA`1h~G19V==Xfdf7}`lKSK}yfA`Yi&%N)}${bzticbUb563jy{3`We<%$Vsj=0ET& z7tF}HabuI$;v0P7L3_b7=M=>Ih51g}9Vwspp3uN=w#0QMXNSv|cgknJNnKw3$6iG% zxXU7o8fO{ix~SCtogoy*&UmcM-pG;qDLC8tl2+Ix zHyxExtMiN-4n9AaJ%quYr3E&5Emy%e)iS7vsjo^L0bYyP8)*ZjOT!LaUInt43LkMZ z0_9Jj%g=AAErW$YFZJrqFJ;tdbElO0>>X1TXMKpf&sMLq?(Lf#7xH(ehpWGdSQu+M zx6`@Uf^VQgiq#3kdFEa?Wf{B|X}_$2F3ZsGq8Mk(cQ(HP&%&ABG#lo;v^l-AG|t0s z8ckE2rZEq9{^R_HeH_m@&8BglJZIC+#vAun#czFCPKR)(yVfy6)|jQ{Q&IJ8-%U0e zF#cm;9J5YrW5b)T28G?lQA=dP^v!maXJY;ff~5_|yuW<)OW)!fq5IQaz8$*ePH_4$ z)B!G|(YFfD>^MqkfEE2V>=4O8Mbhw_UvhG`O+1a96$~f$LZ0LcD+bY5V1|~dKOsyo=HGaoO3DW3`I+t~x?J11!P>{m*Kcyv z$~LpH=zz6^E1aFT&2ce#$VVqtpJAI8kupg|nOa!<;z=6iSf)1s2v2ZIng1Dh(@2Rs z@(7I?f&`wyEets8B6@lZl01Dko=SswPK_Rl)>!uj?wfP(+48PwWy1hP3FO6BDMOI} zYDs&;)bEZX{t7qjbTS^nPU!R+a_+)8TlVhM^N#je!ZsiA@~ST3sCuZxN<^r+I}oR_(RWhjkdas7mvl7IC=8tz)&H|M?F)-q)F2}Rp;ZAMp+Cq z!`(heQzvDx3084&2o`SWRG6j~H!oMo$2~+@5nBC6M#92*SY%DV&u8C+b(h>WU!{3t zI=b-Jr@LE!6()RX!JAUgG%%o`5pGf{M&@MLxfBpD7v3+uvn;wUQ~RcMS=;Ztxo@7% zG+o11Z25aO|8X?t_#K|}XuQsAx}84voBKSi@j9=4VLH$7+;!gBG~;ue4!1vt|5XXo zhf`+z3P)ei!MIqCV0U3h;nog2IXQOY=oypY}Yn z8!x}jrhseH-8;LmxWG{&7)=ZebaD8*(^W%_@h3A(c3Np-#0=of))bMb^ee4)R3DXUF%oI(o{X=?QDX;o@txZ15ck(GaEjc0> zBkS&u{(^&f?@U{K-DPvtFFRbFb~wB3;`P_3&5JKEq--*y#*7@F1asqt8z$D6)%4q{ z5QVp653ft%;cnRNEjC`cKJO46A979{8!PyL7UQpvt?>6q5|)LmWnEqANIG5IE;WMl zukI~}Asodptn1M|k-$aU1?yNHn1U)wHUapM7n&kEkONuwl^~4pz^5|TICt}qa%Zg} zYyI5taDY*J@4?-`Ucd1?GqKb^hXfyT4jvWH<`(S?jYT<^tay>R%j5kd^@a%f5{LlG zqUqzkf^gORlE&${(3G##LAcydPvYuIKCQC>f;W(oFb^*w&W2*Z`E*jJB*Pp$8*>Dh zg`;>9tDs;gfU^xW$`v}L{WI1z9MV}xC?iinQM^x7C{Y8cX4Mq1W>kc?&Qyf`T6#8k zT}`2r2IJzbzr2KbR1v2BY))x(P1_m4j%z%Gw^bA67%z;*i+(^Tsijll_?C@8Q24Q7 zYHVqUlwlWE%O3$po_GZZjU_}v>MV)CEaVYam$V8BWNLCozWhf@$z`tp)KTLK(Z*Sz zZTUqdze~f+Wb3*32xTV_HgYx3_>@O|3WO_tfjeSpcZ4(ElbNyuWpyNNw3&tBmbOKH z2$Zg(GjWFALp-{g=}FmARAsh4kQklBm?K=qFg`%hf%;;AJ6h#TqOD(>4tM_gw7LK8jQ)uKD!AyAg6Pj(>*8_w4LgT3&3MzAhdb|V+SzpT zb#uO^oy9vF57K~}hv)Q6KZ`fdKbzNd8qfT`TE01sVO{$b`{n+fm(QWp_RnXhGV|to zQD(0eI^|)<^-Ejy8X)D27YYSE%q`;i4L5l% z$7=c4TDHkomgL27bArL|A#*mQGmAMMBYcQbjC`~65Mzw;uWHb-(com8bKRJ!_tSi< zd=r#6op}{%{>hzpr<0HW1$Nf=ShBIfn%hg9Gk2W>Z7(y^^!jw+^4r`muT1-V8tjC% zZbzIyx5_8H9Cx^}^D5Ce1|DTRWVh_(EE{^+UcZWNYlL1$A6dtz;YwLnoE_}(%B=Bc zlGT_m99aBl#b3%k(`klyfE_c-SwE$_6>jdPS`)!TH`cD3JVFmR^9!?J5?2oC@$Wsr zKv3A2;>Hf^B^x71x7-@E51E1UUUvD)RnGoHC#3D~@;Q0zEm}mJBy6!Rn06+jqL_t&?bwq_Khzgdt zU9(uWVG~+rnt#Rxg|uW@6a)DE`ZP8`5=C5AqOvb$&j#T#oTRa2)L|Qx#)f~w)m5;O zGdZ$om;@_s+DMNSQN1+CHUSsjL}Eja#+KLLO)pTzS1dbdI1Sp7#|5Os9eSu&a{3Uj!_$t6WY}s|xd0gk|cLFyQ7CTX?QE<&vipQ}4QI3kYg4JoiefB}onFZ6l zRCXFc-FTx~cKM~Tm@WUnjbfjlF3X`#Qe1Al=6j{C&@*SB9dSIwiAQ7H`5rf7KDA{X zAM$FD&(|IL8W7z&Hk{CB?bkFKZsY{$$bMt<`PaV2_f0uJZ1*4rU41;AX5@P~Xys-N zz9;=_wprh`UaUqXYupOZkNC*z4oXcO$7jD5;pPV?BUYJ`aWqPW0qnS2W1x3FlgG#e zYwvc(b2s6YrsX>1Fy2*-Hr{)#XoO(=9UlSA?%5U3cVjkkn>B80CwHg2_kJ|(e)?U$ zwD2Kt7`D6;+}gRx>(iH~wVjvQtnoHR2}h$~pd9hNRYxtv;X0Z&n0b@EhQkH$_~rpK zRg{6aXV=w(yz*Hd3_RKt-y z13RRxc0oj!Z0~XK2mZ91$LHbGQ1jRtPblw5wcbzL*s1{|Gb#zc{N(j1G)<$dDWDd$ z@6By3X}}Cd;;DEybn6EJ&wh4ns|tibB%}cPC@QdG*a+Z3TJD2rl4cIBw5(=5pfbP$ zN2M9=$|L(TIq4z^wD=u2dlO8LaaAJ*iub87B`f%WXUTNZ@X~10P>Atn+NiAPj<9jV z(hmKRmh?qfJn8vYbd#S3`Y2vClmROudQvYj>L^G*%}U8($s&a=f%-$9@Ui|3keWIh z^*iK>%wkA@N{T^5UX=y9@LcCgTL51$>&(B*oI%=7P;rg7dV)$lYup2C-@(i4j||#` zM=K${+GtCgMYScdQjZYwC|=s8;gZV%Iffd-t@7l*#(=uP-@04xEvq*W+O=bTc8np9 z-!nXBFx+Esy|wk9@{#)gjBWil6ugAuh7jT47X_w=6Snb~mLCEM8~-JDmcz2ySoC$( za27>PSvya^FJ>Kb%mJ^FQo+(n-^Ow-kp1$@9Z3$@XSVyDX<-JBmT5wo5 z6oZt=sv~?{ndwiO(oZ#c|O{Wm*3=&+#A!KJA2a}uMIa?6Q+U>F@}6-?ZJAIAx2u-2f#YsZ}T#wWlC`+d&P0@ATg`c`}Hc8+h* zaEItB_3x|9x`!dL_X0W7rq@U}FLcny2?zB~ht5p-sK_72p|gbR=;H|4d?UNgF(oU^ zOzz+22#{Of&04lYUv;iu<5OLnA-DY^vvFUTE?#>R+-+u`9`Kr!Qt?paVZV-Nme!lF z_K8QRI6BMICa6~PY8iu)4APv2aqK558ZgRhiS3rjNQ|&Pd5-0N*VMNw&UzlROY}ed zoxeT(+Sk7|{n?-KeO6$0c6QJ-H29I0Mp#i-ck{{LC@*Z{IZfDj^x*^1?1dukDJbb%Ie?BUv$VK@S z-0~_^e9%lc12{2DNEutMx(|UBSM7!@@;cmkrIn7!=;@Buv++&@Z+I9gTa<`z@`>vP z9GKK_))26RCWRy&;WMxTS(UBI#i!t249G_|MipeEQScDAqHWlawAhr_;>QTEVun5i zO$FJpfUR{`N(BgM$&1XDmW9&9ks5CrcEvp8gA1O*Dg$f@nfKYSaoAwT~bUUS)g}WbQn9c##m@NWoRc+*zoh!z9n-oP+(+g)LW?r7MotGZQDmPaZ>{0@aCL zUl!7eJJ4|Bm4=@ja>ikgG}EmUcPV1uQAvD5hf9TeW*9eA?qbj)zTihbH|6k=j@Kb+ z(#TzcES>P!XQ>}aT3xxsCzE>SJc6Q4R-8C?5HG_zWwJ2R))_U=%evPE)(qvsHEzR- zF#E~2MMrX*l zyHaC}Me7=6gtz!;{LMGMjPdqhy3g#J^`XI*#rWQ*(r#+JiJFJb9g_sk=d=ikqrtYp z>&kT)`c2XmXZyU*P?_RpeB-DSjlA5893LoRE@1Qn$IlVtns|;|Icrs2^dMmnC#?Cc zf~ZTQfkgitM=>=pfOc854}I3K?SJ}1K3e~M43Lk}%fYn0U9zsOU7ohCy~WoZzJ~E~ zJ!`~VOL)R-Q)i)^aM|bEqZ`Pt;p!`8$H2^NqK7h2kXWlxx$-+?9AVq5GWX?!x#O%~ zxNwbKj)398>#euGI{m|c_&-no{Ga{r(@%f$ZV--nVDdY(hwL6*M;?v6`jnX#05ZbR zs)<6vTwXO5Bt!Ewl!UfM3a)e{TGZQsfht*DYN#%c{L%&1+&VjPH`wT~$=5g#dGbKv z6K3T!@O^UEP?w%Ur)$GA>)$AA?%9=9@ zhg*iJ)k4(jbiA@!4WZWtL!({LkC zHE2+oP3(lM^nxH9bymu*%UN&DtipTkobi3xz@Q51Q#>Z}&1j3N0Lle_wyO`wsb z1#Nq-n5Wq@Fw;(ja*b4&am0c%AX&e7!e@kx=Y0y<$~3Pw!&bNH%F=EQ6P=KuevpT| zCw?OiKw->p8YXv`;3=NQf2kdXvus^H%iTJ%+=l6U>CAqZo$$ua>(jyJ>wF*ddmM7h z=aiV~_6q^RxV~wdNr3|l;eDo6R_LoYUwLzS@Zcb?5Pc=)Lt(}>;hmKM-}BKN-%>5q zN52sOlA|^>Zpy$}iP0vM>=<<%WE_X$H$Ise|1g}S32kS~ob9VO2N^NPz0)>ob!#-y zbh_+e;1A^a^WLX(2$hwtMlAv)Wa5L4vrRFMSYx|)^xf&G0t zKKJ|*DYEoW^pN&!N(q{@+ylmQ8U5~m`0xLx>7W18e?0x;fAqgh+Ze>YJk>zFz~%$> z?U>-KoM~wSDk-jfDH}@vm0ws{uca$%$l0wV3rJugR;4Z|O5+&gY>PTRVC;B!_tvz- zj@2#J9QxIc4Q7D%);U&11MlFFwgsQjUL@=;U2T=fiO)a0Ym{?{GhD~UD5vFA9<{1; z-h=RP-qXBSI&tAE>|rM-&wG%!KI^~Xlkt=vhAQpZLXw~=aojoPM}w)TwJ0W#&e<~k z1q)vhy1ORDOG&DQmLVcIjUtjF8Lc<;83Aq3R(#^!K>&A9mG_Yi43{w!0o~yvuRI7R z=P&mUMO!$Z;B*cuW#m-207mT+sd}im_!)$fT9B$Y_eoToN`693i`(>yPSNn!xLopU zWkWb?9EK>#AGFoCK^vA5Du3Z=T$^FOGh1bB3oBn+L42$t2i;!dW~v5lj5P{rqm8ox z)5(^DuM}ho0RP2bND=;D?%o96)2yuTeC}Si_N}X{m+I=h>83#%M35F)RnSofbqMH3 zjhLVUQG;7DL~IaA1W61rnPf~%2FFP<(P(fBO+;A)Wa|ccgRbuCy}I_j>h5>G-`{!9 zt=lb~IQh(c(#^T|{lDiu%X6Odoaa2}IeU=FZ^Km_%mePj+Yl>m+Ew0o2jw9gjzbvf z3O};e#&sjfVUhs~ofda^&9e)vMxIVzcn4iJ+!xg};O!*T;084lIfo)28UuY58MD*# zIDWi+EO0@&BVf8imP~JsqHJY4QCd-xtp{3VWGl8sYy=lh>t~%;=yc{$@nbh3i&m<* z{%70dqqTngQ4S&HBJNF7wf($Jb@9dL*Nz=)YUlY&wGL%(13K77bg*@65!f_X7Slf^^=6 zXj!Iu8meNvVx)sgq)M7+nqK|R6%>?ypdn+S?bGXiUYksQ}qac4kn-@1c~+BY%E za}Z^Y<471iLE7Ns&P?Q$w8!|ZM6?BRYI6x?%T{ZGw7n85%}ss`O#5RG6FK2}Ilo&K z1_J3T?^1BMi<`qZ&QQ5#%9{dgr}{=M2X)MT8s?1|)#??-h9%oCn;Qc3yTz zsXH9#n2fVrV3K4Pp~Kn5vvuac5pH()Qmx(b%-Vd(*Tc4#GCFsLDbvK78D%X#dK#v=Pg_UW zQG1pv%D(g4UsIRu-d(@;p10OCwAsF$b$S~&QtuAb;X{wI7Hxff?bp4S5!2&!+nt}M z%-TA7%fnfqy!n+DeQB3&ZO}oCEJyjFndYKvGfl}9GwG^~r}P0vSAg>N*p+dVHFo=Q z(D5Y7~0EMI7q^fX?v>{j!**4T4p8E$qJ$$ z1SAeC3Q(p2BK5?cFpDWwTv~%|E-rrH2O*~Ony;~v)rkADbyE9s%q(Zc)!QU`g8n{LyE1DHd zWz4PpZTd7^6COf*t_Xv30?Yi1fG|1}TS>+59Ac~UW}<$}x-DFO|Cy$7fjVF%nnC~J~fR9MPFubx{nR_Lw|@Ld3d5;#w5*CdVn zN<8-lDMLko-yv8#Y55$OdvS;;PZPXT)%Re^r_x!@Z2mb2pt9tTB~dPXA4= z<$T38pT&NRkrUb;lp`BnkBgY0;ho})vkmLmb$b5Ab?DehMoqkk-P;IYD(iD(2n{M7 z17(EY9uZ;_{hALedG#pSDP*eBZXpC&lqWZk89 zwERh)=AK{d{SA=7k;d~(n=Ya>Ubg#+dfgBH&HB}Mzk{P(j@0+Q_HWdSU;c_ZbmCB* zIDNYQ)xY>BwU*m5e(XoyRPX)OchS#7v1ZzO6ot7r@2z~Gk>%1b1)l-86{3;&DmQ(b zq6zLpP~%$0Iye&~=86PmQ>MbnuSb#0GwptgGXvMJUC&y|Ep_(P$&Av?a38IQLod5# zRWbqB6EXxJx+P8c$RtGvlb1ikcisa&0~uwv@L?hhKb_X$6L6F*|0-r=d~~gy8}mrM z9Qo32E>~mWr--9^;hZog5XDUgD*@lCrE&wBxI>CwA2N<+Z#-C=4mwxzhL8{z!D{iC z@t(0TJPs17jlfBKD+U$M;c1vuGfv z+(xn0SQu7?x5tmNK^|HUuE8C~KikI&P@xVbAOg@SOpP^f0b1GoPlCd1Ph)=F+jju* z)4w~dnz+#epaTqX=>ku{jrt)6LHGlA1yoB@$bOs7Fg2-=!^ltb{5W-?^5#NXX<>!p z6@SBJ^fnfMRvQsA-$5)M!C%{uuN5D@9f~(>%VPrh6+8oFC2&F+ppCY{kE8JJ1l#@> z;nO9y^Dm9ll5?1)^d0r2b4Hs_v(aI0>07JTzPV0LTnbCln0J^>mdmDOjHkO}jY-YM z@v+(IzE91!dFjS{n`h}@{?&>)9pXpeNk-zJV`!xE=E27b5#t#5G^2QD&Ts_8rn>5y zGj+vf57yOJPuC64ys)10tc`Uc0qNTV7XG103d#AgjNL4wgc$*z>lv4Xn>qtr?4A5^ zgHP-|%Pzxg5fEwE(d5(1c2f&DGai#P9wd#=eePu4dduFr>#kWAW3R8f?`72EGzW;T zVI-E3pO&W~ALhkTLq?q(1^D@qLDO)^E))*y?Bi8yQ_};ORg3EFPzH5Xb~hGW29t`kxVxfxr_0Fi?3!+ z1Pb;M4yY}ZYf5d5PJ$biG|D<3`6kDS41A%S&|T$i!h>)Vz9E}7$X3QMfnnpV`Y-CZ zM5glrqc~+M54OBC**SzzMWuYcX&u9v;+o9h>T@u%zHq5bvT=YJz3Z#SZZkJkNn-(Bx}-#@Pjc84ybU^*fy zJyp0=#JUm1Wy;49%@qR;+tDNV5w@3S>;#gAS#yXzci!@DGeC{bPGef8rem49Z%P)J zM=^$atSR(5Gp6Vn`Jdz3u`{Pw&yk~{8eg^YGcM$V2FeV5GV&Vw$zQEpk`{g%vn2^v zWv_WOUw(zyc((JyssMl4D%}m3UI~KjT(7_!2@I3-V;E+J$o7KdMW>s_IP+?p9eWdO zO-56zdG4X3sjgXfjUlXq3WRnMW$XXiDCQp?L#Uw$EWn7AWlqmFDSI=S#x)H|LSeRC ztDRFU8Fw_~f4}Jk{)7-rQqR;xJ}_Mo)2_183&QUfSdV7UE>*%~|TY%pmKy2V{K zNJ0jRYlCI{m7uYh0>uW8$Vc2&C$HdW#q4wb;%_}{`57{093LEmci?Jow8tx^`H4A( z#Cslnui*Ijd=1|zu*4gFpa-lLSawLnE0C#lA(b5%c-%F7i}2j?m}{dhuCt>T;f?~* zQSWcTcDrv;@AAfkG^>PRbNDLxwa{&Z&9ixR7)&?XinvW%V$5l>NH~?<3NJ~P4&(L! zOU@SS`WIYW-|&rN^{lJM>$l2T-30Hy~(id_cgN zuGGE2CkX@8j#396q9Pn2%Qq^Ol#8$QN#Hk7Zg* z+nG%pcQ8t~xgI_65dJJmr+xA?{t;Va%1AApkADt+<@2w4=Vb3^aa;4 z&2jK+s;jaSIW{rrRaI>P{Fqa{$f`Nc85{H1lazCl=Pu6oswz9V3k(Y{vEItt0Y(|+ znE^NJ$1-V{-XO_>mpDHDTg+vJjLg6zBP>VgvR9=^ARedUbRSrvh@yyb)mCx}-ivgI zPM$bL-CR#|y97&u4@b2~y~%O!hNV3R_b3`9=Ed)=?E->AkUEh%LQ>LFeDLu1oDm1& zjnkf=WrXcJzvuhw{GAupyWakH>zb>tuUCHCchoOpzvu4TnKoTpfAEL@cfJ1gZ(`ZP zo_hc9{-2Ozj@1#TX)8=`wAQ1{sYD5H);dq6v~xa39v!8Vc20LMa2!cAu3*;Ck*1nX zM{(Cpdc>U%Mxt z1tFMsl+gx6T@<%vlr~4e*rSt?war|*yPK1*XJL#z58OphgTwP6(i|HbtaJ{{IZfhd z+bCR@^#njrp5<;b3T~PC@0vjSCatWuO3fo3hboEV(5J)D8UAi%@!!hRf7RcLtC2~y zw@P6#YRENiGP0GTu`a(bsptM^?5(X<=t4ZTlGXyOgk7z?nNCkPKmw@Y;crH^4AV*? zw9C*t>FN|2AAI;ow8SFjIfC@OB(1^6P(7bm&l$laJ>pwYF)j5*li-R!kqzj79>aBd zC58e?Ne3SGW~56Sb#slR!a>?K`f1P8^C7SyzY(-UIt}hXiMS2;4fcFJ&fXyPaDfo$ z4fiI-lcynVP)U9>3Yl^o!79M4vy($D_3iUHGN1xwu~b ziqX2}iY;~N=20d#x!bU~n{kfyO*BqpO#e)|h>g+Fz(P17Uf?4dH25yEA#_z*S$6&_ zdq7If9Aon~P6yMTb&3VvNuA&;JONW;OT8x&iebwf_X4D2Id3CJx|1}5&$+DXxtHy% z?_e-?@56I->s_1bLpP7r$3OBQYp}@Z#4ZA&?4KmzwM1f(bUMK9xK*iR9Uck6U-N&@9T&5XkNN%vOB(pi{(+2A|8o74g5K-vvM|~kwC6f?vT|1w;<@Z?rI3Kv@$kCa#qU_ z{P1u4Fc0wDiE0en*gylWiX-jRJPY$Z%vYK0pX4^ri>&MNxDnSKw!E8f6-(+bv=GBI zX{3{BOzMa@c3hS7tsQK2%8|3FHS6j#(BqCTe3rrVQ?+5kHmdu4?Y;kQ=s?Y8ZQR#B z=f-Rz_>EuxCxkg!uX)Yu>e(-RMLoE0Uw!Pue^Q_P#Gjxap^}j2sVH&aiNZEc+)3OQ zaGN{h@(RwpL(YU8l4LObyVYo^*w{=`N;`FkP#rk>WSt{#dBRSd16YpqN$*)`wg~?7 zoEOJ&akYLkoyv_%b?W4CrdUzr06NMUdoB@j7Qo%Di*r_Lm1(E??GQ*YTu8g)7Ki^2 zf7mHKbyI*N@Umdg4<0k>T&7!t-DT=7EjHbzGO$#XX^ji(S}xjs&K1wDjWFsX`yXQT zjr+%F03GJGT@0K-_6ke&PvvhdYhKn+FzP4nFdCsJ-2R4iO{e8O#I*yRhE)Rlr_fa! zz;uPG4THz23FyaFisl3Sff;+H@f~}Ao1&GQG}{}zDWH{b0BZIDB4{K&pVF^~)wjVI z;OD#BdBZSS_|MyL!Cy`@Tvw`+?>My|agwO3Oi#Nz1vfqt3c%gpcN_I<@g22lVT{@A zes?;GQ@#^uD3`eT)8O_|GL^VqmTfq{m8Nc)Cbg{EKi|3uLcqflT*OcM0UO>4et74d zZ#w|u*V69x7EUSZsTu9v@LeO-9oQXOZ<-+^N& zs8bh`U-F#g7yvj+Iy44tksggCdCWAjbj;`=VMIWl(rsWFY)^fYybKxg)$_Lcz1nV$ zAqT@PgynMgnY(N0Eip>R|HQtv}d z2?{9On(S{VC@Vbc&1uxuAxHMmuxI82K2)A8Fom|{v>NzLk@gHzY2WwPf24lxAN&pO zG@Y)Ge(0k~Co+j-&YnEV)El=ypcpNnbhvrT)WS-{k8+}LK}pCoA#iO24CA1Iv>^xK zOI`i;$HnpEDov!34zuCpS@LANsKU|y_$0Bc6*t@L&|YSWew=A#)Hd9eZP3BZM$``L z)+h|F5}2KqPMrcTiVPuy?0??D%=AqY9EZ1UmC$@2J^U!we^DlB{735rH@*a=c5~f# z|K8eu{zdiHxBqgz|3Ciw`qU>sQh)E~-%*!cdS$)$z3-~ir%u~1hjUIom1oP4t1onE8ipyI${u1gJq|oZdNIMErS?oW)}S5$9L>0B-#OLy$5P4re~D2N$$)}+3GLlTx4 zJTjqwafEZT%ZKj}cgSg1mKyFUhR0;SD{Eo4ff6f~m4ec}!;mQhR^ku#P7~exBbbC6 z;Mv+YoVr7Mx9|<8u5>hor_s8*nQimi6cO@={aj84m~m(gn4WF8wZSn&&%f_{H^;cg zfgk;PUZcnjZo*57tI}N!r}+h7((UlX!7bp%wrArAsy&){dm>#>@GXt4>rL=9{92lg zIl59QZN!iN$Kncs8s5!a2-0pval;{4h=1}DluY8W`R7|>ehe3+BAuEbb2LXqL_z4k z_X4^@Yi40RZSB))Y3(;q6=;}^Yl2ovO%hu5?v-`5zu~&O_NTfrFP#RAiXeH(9}Z4m zI#r}F+>9bO%QWfo;o7+7{(8Z)m+I$!^1AxFKef4Ddc$~apJsWr8a`8&Q-q%;>XLC0 zm{SvLAeXH;jSI^(>=@;fa49>=C&wNUy_b33Ls{6+HFpA1KF!hMIll9#@#_Gk-+faY)HBYN8=kH`!>zC^& zQ+o`!Ko2H^r%2aczcd0yT4MMsX$f9qcDoUkuLiR^AKLqOw|}L#L{BkE7^eJA+(mUf zMBFnprZX%u-&ND>u6%SKYr42vY7TyJ$r5G}a<~s9bXD)DhWPMvE@J?DBf|Kl*3r;LG*NPv6CEhzG=EaSKyj5Cp#s_Es`&)qPw`oQK zp{Y8FqjI)6-K=h}hRoyDF!eW6@Y0P8z!`amb~>03aL?$R^2h?h*e+|;&zyQ>%{?YBB%&%p%sDj7n+p}Jg4 z1sr+X!Gg-`w_jOT?7p%dKE0nU#rrv5d(3?`=^aNOAR!kb+w)cNOOGOmHRVmKCcB54 zHAEh)d8GnCjZTFA*0U=yJ&bQn|FwWEu;wx(mJsbnB8TZ$dl6YB&Nxj$92BwDC6efk zS;Qv~=@={WN?St$7b`}?L#O84D^r6JaE26C|LE_>rQ6feJNYF-V)eYI{NvmB4XbhF zXH%CKA`nF}2w|~4C;$l2H!C#Z01bfN7o7%#p@LJ(w@}4d0YQPp9ydONm!8_hYlt_v zBw}#DFJOm=3b#hFK+v^GxnLN8im`#Nv5=SMZcs;#GYUt#HUehU11(3EU0lA40chSW zGX`&Vqpx<0$MACwbaQz8_v)}uR=auoN_kxEW^awlwrhzIw*@vMtYQ7q5}L~+`#Cyb zsha8?g|o;QqeAzrMT4KF8JS;Z+Elu`*?4J~h39aL>>PPp-8*qD!d7`+F3G_eUV>MnqA07Ap{dlzW zw@7VArgkupxf44IJ<8ei#_8I;lU=6=4kMc=95ym!s)|8_WvQ#U3hw3`cUVH0L|IOOK(I(!-=~^g-s3UPI9V(2GXDUOuX*y4V zMLNK;LPVZ?8m90Uo@R7;nf13zDrMwG`RegT21ZizHXpnJurSCqFoT!r04b;8VR<{} zUy`>mx}#b11`OL<)AcZR+s6s!3>~SnEpzi=)-kJ;5MSBYXg0{%fqrvNBh!JKgm!M+ zm|VM|_C5M={qnnifp~NE{RiJvpZ>&$IQC?rUi?kpTEF&B|8d>?$N#Bb`~5#!&wbtt z>)LCtug`z}vviI|>ISA-ufO3rD9L9yOYS3e@1EPEFpW=5a9RDSw6)@y_Nns`Z>^oZ zunwlV$`4^I6~7@5z0Hsnve|*iY0+?K9RSPAcn)T{J9@FsGU_&hvOk3)=~U_3^_y#A zcCzLs&eC_C%NknYGi)Ean+D?SFB$P{n02qmes$OOeqz5d7G2&XFw+dGyptXdZl-cd zVjM~49ct^?*>cJDOKa!0UA6D%fx7q6y*1A{NZWUwUyq)6v<{y+jBsQM)!|y<5r1@k zyw)R>w@+@V8@66qH(q#s?cBJv9(nYUx}RPu<8_2(M-LJ)w>=EVNM|=oSR(D5Ij}>Z zr-Te6vX3`|ca$iVP?5kyfx+ICfyaJ#KM_uFwaCHV{Hz%IwYPUZ!Q-a}cX zvWPeTeT1?JSDXg-yb@%9r+pAu;`diI4Ou2A5MZNh)x7715V?EAU_tGtLkS9G^1dCPMsU{n>IT&c(sYE(?G5FcwW<)_g za-IT7AusgNZu!A-iyKHdV(+Q>8C_HHRzZuBMm<4pL=M9zD_K~TPdkhGY~sd>nfxbv z!W{}UQ&XmjaLUx1>#kHZX}CQ6GnZr&ewv2w`OlcD7rba&ZNF%#PR<{zM;^M5b#W-V zizsl)F^x#EUYOwy5a@2Wi;SQBt$p>QdpF&^e?OVt;?~V-JXi9CWpy1g!>K<8SYQN; zQLEDrF%b88cAy@s9hY3lNXR%H|1&kceg|ic&9XL;Q4sD2U1qe->#i1|ZJS|`I~v)v z2vOtPM^13FLHsCBZ(wL^pXI5g93!!L>v{F#KmBv{#y7sEj_yBQe|GZ+Xb%_a+g|zY z^+zB0!`gR%-JQ%LUH3I#S3mLQH`On`6%{I(p=AJ#_z`nqIdN9e<)`Si7YX z=j{$?Od#Pjy}Z4`UD)x$9n5eBOZhdd(Wdzqs5=gf7|W645l7|H$pj`HsU`=>Y?z(6 zD3&8CeW|3n+lK%?JZsz0mK3o596Xpq0VS0@(+3v*X;XX;^18LBez&JdOJG_b9LeN8 zJ2%hB)EdeLc?$lMay`Uv7SX{pkn)sP2DYZ#qOD z`N#)r-vjq$T73T_oB@cE`|8*HAnVRf*Ms-m4!(|*E!KK=+b*C8M%f}y=4*JRY^7r_ z?`EUD#U+2xMv9x4j=y0%4=`@P4}U-h--fn*bDgDwQ#s%l57+%IptP#!dXC&0t_ced zLE)twl8f36omZDGNj?V8p_oJ$V-84%bS>XV9m5A1EWW>jX%bV&LDg3SB*2_)!y;le z_stOPs*A3z%_v{{j_t4ejy!-MrYA}xcbbKg0A_GKG5d#7u@$XFYkFbSxAo(bA@;kX8XDH))M za7d+4fz#7xz%N>RPl~r;HDYFYtbBKO-u~HJGKc!-!Oj0T#zQ~8?Y{675l^Fh>U}TB&n8dA_ z$EVg{H7t6VA?TCwCHkWI_;<&3b@sIT^y*@~{%pp=5dw8|Z$16G4fVs{zoTCDEp+x6 zv~=3R)6KUsN9xYJIVzIJ0xZ$!E^^BL7}sif_hJ)Va06_`OhkAj!Yd7!X)CInI)Q%% zgu{Zg!I602COVd{eAeo#2m{mIrYcG)bDS*bRrOvLa+J?^^2ggFNk*9tay^jq2O?&t z#PvcRXA}i9m;ej;ZkkR9CzPOdIQoDI)y8;W@0er&2I}3 z|2b|~sL5TS)rL*mYt4p@_0Zk}jO^~NSw_cHFlON?73Un_>qr-Wt&TTBNmAELcDC(G zQ&Ng>Z;i5}C(3ZAQJpfqj1i%C{PH{N?%Tdx-}l|GugfmEq`v!qd2L;=V-tg^V|D*M zdur_(mIbjs>kWVF_4V)G_it+NU7xQX`{}pT&WpC!!zd;C*~mLu=*U#o#3}Ps7F=&j zr$t3Tc_Z&AeZ^A+(vGkrZ8;eNrE!Ffe&%SIPKi&HH}i$OYV=f{LB0_d5)e;WXXT)S zD!HL&Q+N#34z&%fEeHNpQdE9hy8%CoQ>Lb=cQnG@c`p9h=_{_lw`OE?FKMOgPW@nP zFLZTEbqxyLENfIpP;l7LL0(-?`g_0qZ|nT?&#$+?>z~%)0|)EgyZ6*@{no#zYp(g4 zdh0v?aUDQ0zU{Uz)Suk^yBq?%y+GXYU0Q#3SqYPkDskSKX`i`TwsWL z9Sc)gNFswxz(7+T=7`{2oTsqCAqe3vpE_^fdIcal6cqW6y}!eN&f&_Kdckbnbo=Aq z`nS-&wKd{dgwnLX;l04~t-FwO1Oab_MvU&Mq~Hn;CcpYIUgGm36RiA^pTrx02z&J- zT7Fv@llSf^1nlNt6xvj7b2Er0sI7z_+`z5>iF?a;Poqsg882}YMf`?vSh5CAWs!=L zxI;jbNgnyODk+%csXXM_YNP@uEc2eQeE4=CV#PmxhQLjK-+8p_-u~J!IDYna2AI-9 zcnTSx7zk&nI;xXQL2v&WRC-O)gD@kRs%Xu#3h69Ihn(8<9q=DhIgSviNVGy2;x?=r zakT3^yxZ54_qU_zTQc=`NM=V$HyrI}=_a}6aQCPX39K&5p{g~1e{EfRU%mRZJL{cq zyR2UN+^O0+ML3qUsccPTF^Hu`!{BiSBeNNCZ&Nt%2h1^PeK1KLH!2W_xR!_IOns*@ zvP!4;@YeLAOvgYso+xivd-*56eC23a;>!g#Hi%LIs4*H9PdS8y!>b-yl*Qi00T}HU ziknNpts69E@PZenyS!wC(GS-NGe82`KtW-g;>w~?ruAG;wQKW4z5LnRSP!&`k(}9j z_~Bc!IJ!7kwU&8Q5LNu7@01w<5Ibkn!axUvb9+S3FtDH`SuwgB?I~dfM0-K@cQT;g z;P3RH_o8z5XD!@Xmt1*4J^bJS^6x}EhK(Rjb$igZ1?w>wZG^OuD21&TWh!}`ZZs*C zCEc=X69>QbNJr45RmKN?{oH3iSAX<-zmvN>AAIP6`shbLS{Ge)ZEf7Xt$y!6{7x;* zoU9-Dv7f9hn>W?3yzMRZ+!wyAzW)b*w0`B6-(L6cy_*{%)}a)UKK#?ymMycEDjism zQW+8(l}{_!f@?`*;K|4>@C-@#R!9@pVmFQ(HRSP(05j|5@{bXfw?PgnccZi+qpf$L zhw&l@JM#$Kw3A5QfQ_Q%D4`oTR4l|n{2e{))4u2+5Mch~jLcupNUbQtCDVMgQDWy;q3UByGmt!bs+e*J0`HgkB2 zoOm|GTmA?EhM|NsvXPEIVcX0M^nf>#H-JHvfU-SVKwdcl@7gza2fN+V^^a3536TQM zmRp=2fzz&ZYk0ag(((g!pfUOhy&*)}C^0k*|C;e_E;K`#kyVzYG{&BQ_%_D!$rx9X zjCpY2?09WhyQQw!dR3jb`TTn1@V?r6^np6VZqr3H#8K9Ac%8>6%GNqg#`b)aC3cI9 zGHkjI;9EJ__?nBZt_wEo6RFd-g{7SJZ;@M2LYjE0qG_-47mVU#2o4&;HOMN;;~Ax`fr)=p;=)Xzpz_ve z>29{1w2fpmJ$ti`0xwN)U@N;(Q#mAr$wzW*Is$H-t^|@*UMkXatr7+!EFksPwcPF8yIPVZW#@O zZeuL|UF1YodBsgp_Ldc!j*zrg8L}$O(&&+-c=%wkCQEYt8trjWFwi)p`V%lg5xh3$ z@^j-Q>}li^TVOoI>zTI9`7)ub`E@5M%9sthN}I@Lbk^2VWBTIlCwBAFyyO_7A{RZm zeH?`|6S{7%O+z?RFMj%ly5YSW>eqjJwEp*hd2ikS2+GO$)kH#35eNrIBk(vj582D9 z^)0yj+)95BYxvZ4Fqoa6Uo{SjZy-gfe>q59WYeaCBr)IqDABoLz?k=ajQb zn97_3DI+Rq&`(;LRy)Flav_ej(MirWNMXkZnv<>@-Mn}9G&;kV@4hq1FG1I>T(@;b z1qK;$-<~__$iYYJ>z@6*dT{TT>!W}A$My1;zM{VVd0$)ac-#NV6=6H-jc@pw`ZxdP zpVt>Y^=CE3hDbBF%zDLf)|fgfri0nkJm2U1}=jVwyTLo#E?jx z$O0fwD%yCOKmXcPJ@oGixb7tFz`Ba8@@_qf+#+k{&d!`-M2?QDeBk+%(vOk7WjZ3W zOgFohb83xe88&n+C)3bmjB%y>%}_Ut7(4&%=!k;U0q6yg8DnmJjM<_sFzZ!YFRKeU zoNwRhLv{Dj`|2c`@B&-97wlz%B*lVUr+7q0=?inN;}E=ySlImBo!8d3Ef>_S_uo_d zP93PLx9qHg$g2Au-OuUJvmEcq5cmx7Cs_`xpfY_HP^QPLHVeH*(qO<$MJaNdSJ5pX6%@Esg|@ zJJwKA`fBv!5mw9B5XZon_G)!RxER$Cz=_Zzew5lY{&-U1 z(h7v;!gvBh62a>Uyq{Fg9arBz8kH9FY~|~2<$<8=P+CXjwVooF>c~B{W$WpB^{Xzb zH~;vy+QRa#+0#q45#3{+QMt_ncX$9}K%BpdSp$pO8#v~@FVYbi)lEiT-H>WHf8=M-0blP*6lc>ydegae z6nC*$`@|V<-tTLw(KQj?ipV#-_M&>u*IiWa`A7HH$Nua-7It5NLd+%y={mO`t3Q|~e{eHc>OBxtQsgHO7nENtobY~AdL~&Gq&%MY z*a{-9V+z+bT_69%pK@UB?)p1#{l)q}e(Qbp#n0bTuY1E!0%N&;`?vmety#Z`Q5QHb zI0PFpAnh(saibkmn#-RmH7W&7E)sMoOvCrD)pFQDk5mCX1ASEdFy54-B@Rw&_)L2I{LFCOdU1q2gm+geL9$i<>Zn z3hd?&yTk7u(J{Soa~)-U+`s-8@2%G{>h`9edV5`d$)$DP_O11|-thfd6SrsYy|DdA zS;bWT002M$Nkl1f|?`Peaqnj%AGu$kpBI&X%)i;u&%z{R6(8;Q! zlfULMUpQ!WgMvKLN=1wZ8Yi4VvGA`?TH)ARp3-beH~geK-F^nLpc{&$=iQFeIatS` z(~DEw%E8qCEF-U;A1CNh@9%Z<7POmlc0)HFDcOq7lE6fKnR=%v=-@UsHNEDh#wcjl zIam^5#k#iji|gv?+pnmLx9+S%Ge_&r17E6R^CwtO5;Vyypg?xR)P}ln(@rjeK2-;g z93-*jx|ju~&)aovZD5Mv%Lg8)yY}s66a%I1f(scHpQtySW8Ul{ps>GIq1(eN@VXb)*5;z~;THU2)AZ{rUC4aabK z_dl0c1FcTq51~hSN-`wMJ3J~#{dJEY?!jW9mGK6z<_;J1xUn>U{iJ3(R@$MR?YM#6 zChj?5dU_MX{3xfvZ+r!wG_VPi>a>zR9*;ExUXOKrl7DraJMsLE;z~2&vN*0=K2%qncc9+>GuPJ7zMfrJFn|+*Th>s&7=4=xf3Ks=T7&2a!4j1>cN|mC-4x~R z2=-RHlI+gWd4fA?vEbGw6*lC-63Uy3p5@-u70QkB!6kLig~b+niYC7{ZNvp{7iB9B znV{uSzW8O|nDEK7qZe`lAAUP}rd)$p7igFk$v0Su1zswYHnb)u52~~|7UBj5^;#Pa z6JS~OgYF^mgJbUzynYBl; zg{~=i%Ltw`cn)p0L{!Ffqf9JAxj?zpMzjQEqD{HbUr*zoGfl3@7kLQp?9?nrWVtKV z4HBnMo~(QJe5vmF@*VX}|I>HX#!cHe7UP|D#kDuo&%X8V)!TmlCKSzmnZe2^O?cBX zGG*cO8#ItN!1l-`O~b5V(`5QNtm3&6N;u)}0eA9{tn|E?iUe$OPq_bYOfUdP+i>e! zOb5c-R8%xF6EpyB?OAKzY|p$sAtQpqAdD~r0z_@Ut&LIauv?c*$MfITFx`BRi|#-1 zk(=wYpZ+*&>eto>{_`KyF%-KOeErMn&O2{qm+GyoH~i^3esZQh@WDUGnRDJ%>P}(l z?XfTImIZ(dTO9fEJxFbAEpL=Ya7&&I1|DIHxZlEa{J-t}qwW-OQ z?5mD>DK#4wM$U}Xma#2$?Ri(#mFMrS1E&wvU5D?f!*fSDAl>R?rVLLkGO%gw*1Bxd z1vP)1;bks?J#TtzUBCNjwGFNMj)(Wu9s3{1Xx+y38*1ly>>NT88aS zzI)_V{sK$*L{-_+VjAbtU9kgn#h!46F*rY(Azo+N9#+OrRWlfLh%%>r2%qTM2NrhJxa)_96|W=G!-aPnGA(XLh`#t=gG01=6a#fZse%mBFL$%soXiWl4C{WCP(B@471+S444*b zrT0iSMM7Lz7srSWwI+Dwgm>$NvXf0;UdS-VRXvMyqqU7)yDz(uyIwgE_^t;J)u9I- z1_?&vW{|}W3W@L}L!P5(apuhtH))_^sIoVxv@2!UNrKN)+Q@&>o5SO4jFFvFe+C~M zpj@FZ(|6{*$z=)pJT1%)i({I(bN$Pbl>Rk9OjN*ZO#ahDdySc#8=9c>Th z75*$`HpLM+XHK8Wbn5XF$Lr%C`w&xJOZA?A_N(>VzkPFk;f z%oJ*a++jb_TUrQ4eA-_?2M=K@a~&-drgqV&blYvW)O`=!ogJP(_``3ki+5j!()Rmx*PVCNTi@|3b@PAz<2rHTDB;}* zA|R>?)72jM8-ZeC!K~$hkHICS61yRMRtyLz{$t>m{!ad^clcSBwiopEuv3;RPzh~W zw^z!;Dd%x!CT-7VSQ_D|n>2D$1PAbf^_2buXfOpFM_$ctT*Jh_ac$z}dj*jp&I&ce ztueMGxe`oB!-p>`Nc-1D1@RM5Q3Rcuc1p~LO0qb<8wKuL@$?)xs+0j6aw{l{=~LQB z##TwlA9f#=291mC?cA|Wqn018A9&4q^~-PBRo7p{`c$qmnqXRB+|~O`8L(&-*~!i` zM$nz+%CQ>KmC++dH8Yi}SaMX)>-LDJa8hwn==xGw^7>Fj15LBdvvP*gRB3S|f;idZ zL&Is)9Xe3fI0+}42*^wuiO~yrrQ}258u6rg-;S`R!3Qr#))-}@iRPhGnYU}$=q=AP zmAOcsxbWM&MKJ{<1^9xz8$JLFWZ5{p;r;_vxYTJvyU<)Zjvxa~Z-jHpmKIpY1^yGn z@!GLP1pCvkVXg2D=hegeC+fcYp!*V|aF)ju@t3@|hs=4Um>CQon}2bN0KtxtFg{OZ zBmQJL4GiSTu=tvVnq2%e(@T3T;&zl63+%2pN%rjELr^kYLVDDLu2Jt@9{TxR7r#JuStYuQk>NpA2 zf1CN9P#S&G@Q94a9ef)aV$hTe*Ho#rdG=V8H%6Jfqg7>Kj1H2U2@Inm&^j3K96&&A zz}M7NR8<6$)#SCy@vrzcZTQJJ8CfEpSWBUFsE4=y8gXvz050Sw@zVI=2Mt170{L!j zGsOulrEJ0mNI+%1opouF9sf?*j*>5pccUI*1nx^;`chqb`P1rUFaP#>5o_T-_1VwV zUoed={Z!~ZJl78QoMopR5jl)y7?vPK6{2Ry)v7QpoPiB%rnC=_o*3aYFW=B9VQeRC zJ6icA$wr#Esi?_w-tDTAH^qi0%X5Z7Dvw%m-Q^U!l)XFGJV=wQwy=#67m?s!d1e_( z+N@94S^g%R>lG2RiW~qSH`cJ=_{!~9*Ojb$JF#%8zIgENIx=!HO4tO{@(L}P#>I** z4xIq_D9CJN?qlc13%Md|UG3vQyIc0(Q4gNlkJ88VC<1eum=`ezJWZyKoH_=gB6yi!mykcZL?U^E! z4z2Xaa9rg$eWg#{{`$Sz^umu1UN+(eNt{HtI2)XVS+Tcp%?&%Ih4alfQN%9r!Asez zyLnUK`{>tjv%rW=+OoVTq%kl*#B*rYxB;5rp+k4gFoevG5xoqy9G@?zS}2S4pC zY^PoCNhKEpk`ZnLJi`TF@T0|pxgHz8!Aam&48RxX2CCT`JXo2yK7IPbwp)ayS&J;r z8qzQw@-P(bP+@h)n`e0CBiup2RJ`~|b*m0kCJn=`2(LuMN8%58NdVo&4=#?lZ5N)G7aVeXN|%MnRN z3Y;ge6Hl>g*i<2V(D(^i78!BDoM#ILJ5o>y<0zW&HHxB&>X^Ili&xH|oQ2NMR^HC=a9KY~yXc7JMJwkG#T76U7C)Q<}OcdnXu` z^K3I|>s0?33Y4Q}##33CTCAd&%g%d>V#5;3X! zkmuF%*f5&pJw%jta&Bd=^IW#GTt~KU?WVf#!M*ic@B8O<;jT-`;$j_PbIDmcKGP^b z@_{!&O|u*ItZNmCXGW9{G1$e`)O-FwVfadjgzO_}`51Vp5e^^amd0}QkNB3IG_t#< zERd{jcIf4q-N%Ho(_z8RCsAlcj0qIESvGOZ&NE|yKIK(mYgJz9(4FQq4>x0kTZeYX zg1E^##_v9auVAXU9FA8es#1 zqx2fmvqd=1BUHVpdK!Vcj%UNV^>z5{zWUU@JL&=MJ)B3_c}X*;iquY+eaHB^I>1#L zXBH6X2)ikU3m0cRCuO8AV3*G`w(qLTH?VM*Goy}hiu=ihHFaS2Se@h)XM5K%s~I9h zl%|bI_{4PLVzd0D^uR&wyK~dvNRASU-Ax|1l>lJm&!hbmfO6FjT{=;*3m{gOzy77 z#)Cq~d#$%KAQpFTS*m~dE1PTUg|qb=|N3@1)mKp_-f{sV9?POe>3N$B%E_x)c&L(R zIej%4^C4M$D^MI}gK)09vO%{|bn3|?K^&EB5(7fWyT15qyH1-tv&|Ho?Lbqj8`-)H z>kyQ#Nzb53nsHbf(NPq2kHE>5Tcppa_d<&T=V(&q-Qk6`>oyR*$)r(aMH{iQP5{k+ zf;I`x7%vK_*MBKrG`Wiup|gl$GDcccOshq4B#t=sxR8Lbq>Yke8)CaT%dW;LmKnLp zLcSVjgN0W-dBCx$&)MsN0HW$o*cnVTeMCxHP~!*&aJ*U@XhOKsF0D4J+sE)co~k6u z?M$sQeW?PPb#Ukenes(h(TpM++)y?~-gCT&NSO>sWtS^Ts>>eISAh=m&?oH(S{T z4;9G;0y)a%fQk3Bx`WhmbK|G9^2)Cn%fnOzD2yP)JD=Wnfw3VE-Q`Qx%tpRyN0CG3 z&7Pq%44vFTyJhqCnrFxASvET?Fq7e)1N$=4*FIo)6<+bozHvOb=a$2EnfBPV>EgQL zf-4b@r|XWxch^34L%G}3ilRKqGzQTHAnVylpFj|uLYUuv^1<4B;$Yo+;(^*XbFj{g z%}|I6X6_Ay!MAeR{r0i7wT}b%mKd>{hS|qBy?w(ZhkagfQC+=jca5`_VDEuPm`2!F zyBF5grR%oUai$MEYDDU$FeL*2O~)8qNJHB>g`j=o6s2XwFAXn|F{`Yr;>D{KY_sO~ zF>nXQXoayV5Y_=3EvxJz(Zfl*hQHcZ3LM}KKQq^8b zcq%dcV_Nt`Fb<}{I`@v-5ISiRxmVhA=>m;JGo);)fz@#C_6ElkTgoQnUvLOQLpkU= z1c~1ezQGX+Ceg@e-ljV^VM)?MeBtApD0vU=aqvekYOfV{Ksvmd82t3Nwge$q>zOOU zhUmNnAdj1x!Z@Y}FioEpp!q#jf6bG5@vX4-Mg_(E281rF7Lbay`@)m;i@$hzz52zB zDj*vs#*Wit+{`op0?iROgsFVk*N|lm4m4ZNaUp0pp&uI&kOU)))U{oyTnEIF8M)k> zDavI=@DZ7zw}NuT!&5 z^Rmfg8D$5htvxEBXpaORn(*SUBS`=y zS6{Ubxx}HI3__mv7$a!pR9PBCN6&z12g+MKS|g5KWe&}B83nFw{P5sbk7J^7Mrg}N zDse_rwQKA3Xyj1v?1)VbBX+KN`3G-4zjkuA*ss3(-a3s!Tholj%@W8B4jc~3brvb6 z>|?Nflo!qCQ{8l4%-bJ~FAAV3NZOHp$^{5%{S13y3lHL!W?x!|#>i0IB3BH9-NxOu z*AWuREa;|8#VhHUoU&P&=-Vrz9F0}+a6~MRBVvM}A#U)95eE(Mh5)u%qJI zI!D6SaPxtR-ZY)7c?M(5TXsrg5#Oe~iHDiy2aD}VXl5R8RY{a*%|UA)4EvW&8$hwG zl3r53$O6NrZ{{kpnVB;j z$HaNN;AKI0h`9>#C`#Qja>0@5@ihp&U7L2-)6Tz&s0(%HvAy-+9P|4e8!$;mrbJ%C zf`mdk8YUmW$nMccmbk$F*q+G9qYMeo(&Oe-kQBU`_T=QPT*2ii-2S;uSqnEs9yT&0 zeF10ZOs`*8j~+W*_i@JD{*#Bfa^y^%zm4H(?pHl|g!{!PXh(xA7;B9S_?qEctZ>(> z8`&>GMOeR3viW9ebqLrBHi3H7yc?`@?qQ5Lo^yL_gznBeD1w1yM4&DR+B&!v%SvKC z^cSpxNA?fn#lg7zVOK~5M!sQA1rJ6FcplbD9vaB_8Nzriqyf*_g)6q27MFJ%_(=pS z7fJc9?oIu6~QMs5gaQcZyr zH5Dn-!>u8aP^;tNg&_n&L*s8?v`-<#T{n$jtkjMGnQk!kodD(+7X>5ll$nVK1K*f@ zH|taO*Sz+8sDSw4CWKQG=P<_y?XQ3Ewu|e#Uc@$cM!d$y4$zQbS4pC`@5SCmOM{3# zv;)Kv>oFWr%hfX+9WpY?uB%)jMPRzSc9hq%Bh!oOYk5;j!4g>D+%K9SPTC)<@M(+Onb0<(>kJh?P=hZb&o2cu*=1|@6tl7HZ8JE>FuV{l^^G;cL z79O(#HY2XeoCYVye|16`N zZ~3A1wSCRC+y-z58_0+_vAyM;G%~f?!6%I&iO0Kjzo*i>lS=}HY&xh+C?%WRXjG&K z^+|ZBN?66C4R<63WvtwA#G^H^0^?DR46S=YC`yOui35rj?mZ+jX9&ls7r29 zpq!OoO@=8qDHkwOCZ(-vhyfEfw3$UllIED=^bEA{gl<8Y8U*rdm;xL_F-#kkv`KWx zo8mKv>N3LRJ*A6GF*-fz-JmmPPtl=Zq?x#rjNZ-8G8%_6;x%t}P4GMY#OxQsHy2+_6e=;r8 zY&e=$S|OvGt~3svO^9l3+lAi(Q>l}59LLyIyK&1_P~faM6n19?LSK}xarPO=FXI<& zy`nC^;2JdDg}Uv*+v=g2{WUXb5hJV|)&(gOa@xbA04x|uJb}XuQ%y6BlFcwJI*A~2 zm~@=N%Wxl9Tab$+wuFG4CGNIOTkD$jW9*LiYWorH2s~Z)9D1bgIr>OF%2hlIz?F4~ixNuGt4IM0a4^R2e|q{CAY#O~h3}N2 zVPq6jZj3MlBi%lf8#tyvz;Ai+m$2d|3h`73H6aPW!L7ZG+Tu4L+LLAr*b)uS10~F` z2KeIbXX13{0hb2TfI!1d=N8TgezXeQlWG3`>gPXyLyzdMzAJcXUu`5uH{EZwn|`)I9JXDPC~vzS ztiS(D*VL=N4h8EJ<6~?(7@OJ&WtdimUe@&m<~!1EDG;0n^@X2@`8tK@F49a9F&(N? z8rP(051gYiU_Mo$B@VVkeAMXlNbNZ|S%3U{r|T1+-Cv*k+^Kr_ARhJ{Ifke<>?Q@L zC3y*qw31=*=Bln{-~qe;w$e)E2w4<7*Wj@3Be91Zw7pc{^pY#;+rMSHb|N@uQP>!dBu`8jyGem-j^=>5zzC*3 zPK~%@bd;%A=@A-`PwS-9G46zoJe8WD;z-RnBXA1~96rL8S`#RMV{GdBvDbqC+Eaq4PizUy@3r`#`pQKiPgdZXHkY!_6teG@QJn zOyI11&ZvZ$LdeJgo&(m-BOAy9{o%xvLqHKv#KW4nwdZJ+Y#ADR1Bq!O+o(9EJr$zNoKo z3FXisi?;~7u67b(=7GB^abArz&(33&Uf2ImPkX!%>+M*hhK9_sC+;?8*++0~c!*Qe zKN=!%4zNiHq#8}6UDG3^$D+LKw}^Lx>`)h5phG&2Om$bko#tseZp&*HQP|I*$jy>A zxnX1H_%)Y2i{n4e*1ZS!)P4@zof${dOs-MXl&VM?HBE;OqXk>Q{Eu-EXHLTg2s0iG zGc#qH7Li_tnm!Efl)-64^d3%SUp@|lF_m$AnYkGbtg}~eWcDPT{W%mt1Rz__MwkOm zgGqXxrhM8XL?nI`ixg?k=BoGV-0OVq+X^j87XD+HZdL5BXgvoaj2VGJ3CQTOf~E)Y zEqu*m)4)+~iU)Q}aKJ@-p9YZ0{flS(1=hkQ?g~_(8PX_3iHAt=G)*1v-!kz#h17VhkwvDqZvUS!s~DP`~fmF)yMS?26d@GjDf;D zeCZZv--gEc(rx(E>3_Icjmv6#N37%9{gS7_Q#)%hRNl63ytn@Guia2DVRz)oGly&Y zdhQ@SGgh;w=-E#z9vwA6e{i&muLvsMp3f%LZFrn=^g!DX*-(U!utb-VtV`_d%(-uX zP-x%s&`AB}Z{Asd{;@-K?*lt)@fg#g>n=vIVw#e*KH1{wLASP_8rOAbEHngO+XV|G zl{pvef`|VZnNyC%;Hdy#oqxlu9m3m?80kEP_4UOXj=FpAnY#OdBlY2r++Oed%`^2) zFTcKC{XJW2_f`Zg3YUkyPL6oc>kJLDb;&U_cAG*AcSd?Bv6EH~{Yj6uxd$azeijwv z=>pRz3kc+OlUteHWt7K(MNU#*W_tMtzh{DtUsu(8-g|qUIm^_q2mCU%ool9KU{UYc z!POnaJo1cnwxP{iOTM%>AE=+d0HA?1P#dMuy@fWG zY%A%SSw#e3MmFKmWqp%@17w1xO9@=3R!4Xo;S*lYBLpAIGtDPqoMuy*)F^))fl5Wd zF2}SDGOZD-OeyDEw)WOCII|ol}S*4`IpA?tWVs;tq!mF*6*oj-SF(1;Wm_e_S{o{ z^oPGwM~)rI8d%S(bAZbAaOyB*fcV0o+ypup%P^fazQvgwLRaOYX~7Fre(gr_2B73i zgy46}%6u;{;<|8_5mgv$jcX&J$J%w2A&UPTBXaYs0Uo<*>+^5A`=Px|BivsLQ|$1e zr#+71G)3-ZTi0(aN@R%!bK2}lsBwg%+N=pp(LlP5Ucv4?fXi9W!8Zxd;C>3iJbLDM zJu-K!_Rk)zgIvLL#Orog1-!^QI_H>EfGC0M7!H2+)?Ib!=!W_;PJ7?SP6;!hNm;CZ zry#+S_sWxiVm^e|&#zTsWB2>7_$1xL7kD#q!)m&kMy*(}x3u+XzWy4bg%3s+V!jQ- zA8-O{b>tzwlvTNjD1dQN*g_oWrgZZ!a1sZ|;ntoWjzb#Su(tGKiOb+jnTCmg7T8uo zO&nLfSK}*Fk~Z8J#vJmIyonW2&6l`!6G6qdegm4#K7==1goke)vfMC8gl5-2SPkz` zWdPQKHGi^^{I)a)H)7fci!m_xJ0fCr%CxEYnkV>reqwjkw*Q$#8L3GD5zMeAK7hdX zzEMt;rL$ZNmfgo_}~okmfg2t^{>6p5>0J+U zE5R4)s#3e91W6dqwvoXR6VSbN3=%P4O= z9tGl*uT!o1t2L>osR#;3(P9rYcHSz3$fsh16*oom5NB)?3&yAFkw+%#GhaAS@BdGG z>Y)Q`>-uL-)jAZ~1=a&j(POt^G#$FIY0v-$aGOV`lci$gy3oi39VKL4o+ET#IW~I+ z#f~ILad(8p`=~vf{khK>ufv?Tw|5V-Bg;%5p9arq0-A>=8nIw-&SSFOd?h#ibbqp$ zj<>jJ_wjnMGatqN2sDIF)=NCn(ITAZzFBxGGs+m14>!X&gQLRZ<61J68wUkwp_299 zUSz`<=_G03b+Vj$n_6R!6}MQBCBSp)FNaT4UYVjKZ5OF$QGbLFv4@CO1J`|uscYEg z&YlfUDsV1f4-SJoGA~WdAZ^m|klF>>(PuyZMfLMH{VX?l{3+K>9pdJKAE}EjzN|j; z$&aG=3&VyVo+B;u*yJ7==r{~*c3aEWQVD$$lW*eqXnI`Vx=34%{#F;IX>+n`HndA> zB?)63>w&+GzQPeg;HFGe{0K*YgtJZLqbbC83tM`DH`p8WKrxjDn3wKyg0NQ7Fe0Ww z;WxM#QX_sXUr|iu6&jjXzVf^38*hAZ{n|f#XWenz=j&O|dTxEgOJ7-c-F9o8IC+e< zxlu-J1kaLG8xfK(bb5e#}H zLP8z`5{)@qRgsT;>tydlS*dX+=?tQK9_??8xqDAqpQ8|UPtI^>CZ%?;@bcN4cV$HH z!yIFBifyb033W;^LxNNelm$(yBBDng;rikL1Nj0h!FV55Wgs^KAC+ zCMyYl_)ZYMhs^p3OErIS9R753bl70%vCTF9Lw<%h@frN3xxFE$q8oNXGdxvAhE&x=)rp`f%?_=ezrby3(FoR?KO30@%Xjw$sZ%gB6~ zKl2;8!`twbRr1yJrRHxK!>jD+A7=5Jhde~ENC(3tKFWfguFacZV?=g~Hm70}R!wXr z|6A(#nXUEdPu*X?|Ne)#QD#G3e$5mYRWL&7*>otAlT4k8q>WQUgaqU(zu~Q<;SvZn zT!TQfWfWd@%W=8JjZkWpePC6IlnKeX; zCYvPJj4ktwOV=n}O?r5ogY`alArrPqMF|L8XeQ+y_0Yu*bZcWEWXjR>PXYx;N4+9{)O%M}a=15cwXuK*K;8U}@KCnhk@2Zh?rMNGeGQWRM1=g*USB@XwA;80P8M zeNAoKwyl2W-~Ov8Y+tzLQ}v>kys{1-J6ea>WSLDN!07Czw} z9D{30+k}$`QP9&N1iUf+SO)E_O9BLd_5pvq9ToK)xfwQsx&dZ_Y4WkT*`=Fi=y|(U zJ`$IJ3J-gcX&3?2NCFW|BOtX(blxVEr-c$Ji%{iFPuF|s?evNk7x=Z}QifV3_@Xhe zC$omImoxHq&DIM!JoseYe(a$-OFWs!0?6CsVvkc2k2lG6_ZGGlzO|VD6~6{bSl#U$ zTnnHX;wC`AgQb}=0{=spKH6k>Qogk*uh{zka z&{I!8R-CZO5EgE8Pvp3@`~+Qpx*N7MCK7+KM zV=S3qVJuk(7S>T<*&9_+SVs8HK(lsGB_+WW;|L>U^8XCs?6~bYI99**f9F#6M_44a> zl>u_<`Za`KV-j3qdK?pUG>`Rw$B#|b$3AgSedN)F;wSmT4Q87tW9eX0wi;sPFr}AE-wkd8qc@`z5aTI$qm% zY~|>j;~u!}6fU@XhNA-? zc5KBzXy=U|Z?k7rj~xgaWCOyMoerPa^Q1ZEjRvc`=ZFfE2gV5FWxHD*%3hQx+! z1oxfrNdThp9iR|#NH^aWO^n{LYT-$^=E(0#*kr=e&=F$hjop*mIn=$Lv2G{#1n%Gl zh==OlV+UENXF)f{>2L%mzM5cT!Y%Q*Xg3`dsTRUe=l)uvEq1fx7vOwh>5oci%Qw&l zd!QJm{|)w*PKSB!Z8(h#Cc|_%(x)kUcmgB1uD~(|G5OZ76=Ew7%fw35;R#bWU3$>; z1mdPp;V}U32#z6*71)0B3?K%(X*L{8r^Ro0=#1^0F@3%geAPQJgBy4yQi~N_$$O8Z z5J}uP$a^M-TT2eRE-PFXe;hDFyx1FUnrpn`ns13>3cf+4{Xs*^lRw3+v{z`Q*UU(X zoejf9fI9-W^*d22RNy+C0lc7no(x9%bzE9?Zr{DGnSYfx{k$?jCCO2t36!|mTk6%X zy{>-Y^}BFub!UR1i*XtZ>2G7^8C8nM00ovKm`(7UX26Ot99X%;d6KS9o)27-r~d7`Kk?(k zXenFXGPvIw!ztl|2y5uLk|}e#GGnU<=U@efVR?+Ge*o~3uEJvV(PE>4^eVSSk6S=#nWV9t zhvw&VkM|pY;>YW`U;CoE@y3_b6;HdN?!WiTb>_s88t0gXIc^t8l1%4k#>Psa1LdKz z=nWgAjxr+;G|H}^=XVg1!IYo+=0{7u!;(1lcXP)f^{D|;#+Yu7(?L;@AC#L7UW;q} zQ$e`u%BSUckT2cwxjJ{Z}i^wMv|f2r={uG?E~{dB$RRj;kfFTJvEyW`fn>#p1C zk^AnUC&f`hr#RLF<=>8&=R$gQS9bfdHg1-6iW!swj_tg7jl)Nt@utEzf|4g)n_dFK z!CPrS-odY}zm|HK;H;(LE{$+ZHe$t@WQcdGK z0@|fn3~%A(4@c`|hG-Mew8$vL-ON=pFWhl;jc`ZcpFi@YI>Hd8JyV;Fl|l%>RX;-_ z?L`Vhn!O_ATerd8_c^N=weM!rE{yzkk9@BRxZ?ibauZHW(#+5Q!`+(zdU{m_p6Bhg zyjoL9Rb{DE_KlE*5JCckT|hy0#};ux6m7@06+s(e;?jzZLfhIsHjO$%ch9&YDj*`Z z5FmRXds0au&!n9D-uJ)Tx#ym9?)~mP-(3rrdFsvR zE@vxTh9lhsoks`Z4$WGYiY%3$tmyc=3QsWZN~2q3nZIFH*xsAw3pj_{IB{mCT6)YU zY17eFD1s}fRWN;3PcnUr$JQilIr$|`;x#Y+;3fYuuz)hU%PT3uEuYFr9`gk+prk1? zz@I^Sfa!pl*aRf+?e1NcyRUr*198Jh7<+*1dnqj?JKMI`JR{16BNS&+QW|-31dTc0e z`i&j&@BZ!KczDY~2Dr{32~(7dRb)Wsnw?ZcqzB@tlSyOa2XD_bw|2Vuk)I^03o|@F zN^z*f47ziS^WWf!-B-AIgrV`yyv)ZZ!#X!sPma=F+z94)zlZ1(rMPK4Gxp{z)`Z`+8mUS{CcNuxe3g7(P>vlADtG-15at0 zt&7dL@&bzUA$c_ZVYGeG=PyHr$qz+b}uLqdAPm@5gfvEMQgpPCG*S9MLB~VGXk0Tj!eVPiKN+y5KZaLED zh`iT?D?WhpLzWQNBWLGKnAi*-WZUd{i&w>UYc7b#kL-!t_dXs|l(u+m z-*WdQ?qg7-F^YTu0 zu8Xsx!68q*L{-`AWl;_#c7dhc@T%zLPp;;(nKCo8xjsgAPG7Y`)4x1C$ zMR|nL%!Sb7+BT<6ee!b^qO)-(FXh`FqSLKC|G`@Y+}ZQXB+~+{!ZpPxpNfoS=-sYP zk(y9Nt|?}^%_JKsY`-#FWs-S2?SxgP;IvUPo>8&cai-#qbJ5`Pvt#eBeR1bK$K(DD zlk8+$%2}5zM>)<;RM#At&dnW(uiigr>cuk)KC4z94`kiLZG=ZK3@~Lgh63VUkrOnY zr?2H>ch%y!?JG~l_z`&V;7#kZ8!qT5xWpAd3Z0udxX~tMB)<8NahYTq1qxhKo~eh*M|nBj%n%3)EHw`C&T1pnpZnmyihJ(9U0sixM3%mAlw%muHb>wwjEzH8_wsE=@pu+rb7~dTU zX0)(p2=fDakSD8z#Yki-QVB#jaT8-FV*UB&#|v+`F>Xcq9LIm~0Ou4w@A_EGEf8P+ zyU)a$HD|>e-}3hO(?9www~{;=`?zlE2$#LvCr@}boC2fqO+|y9mbR4DrlyPBQq`zj z(fYE#qrUo4p)!|jlFBslp0<1|E8^`3niRBq{emFIgFr0Wx|-jq@RTg!IdeoxDRSz% z^3e7L;gJr?Smj9Av=cAXLs!9fc#5`- zpki&#E;RL9nVC5TSY6_^2%9^jk{B~Jmg=wEu%~$E#NmN{zABD%tfWzW-s*GX)RED+ z{i%)d`1pPrBu3xt@iD1vBm?5oP({F-bY|_Z&?E$BLN#Kv4Fldn&UB`I#?xR5r#PPR z_YC;?>3eDC!!r!W zB(gO~>^)ob3Gd4+S|5JwJM#<6Ks*&*FShQ=KhfYZug)aB`YN9cZ((?(C&P4iJeXhN z%LuXqqQb=4;5)uL7%_Fp3LoT4bH|<47ZB2RtXrpA()n(>PDEm_W#USmVfb^lhSq^n zAWo`~AP8#&u1%p5D1YYD<<+wZde%*&DT#*~KfmS2Z)t^-;kZ!OP!#?QX*l6dP2 z8QI}I8{s|bW5gjFO9VWeBNVPN>R1McaxxpmSVfHj!wc=QnrU{XS~q7|Q#Z-z(|`QS z!|}fN-x&`+z*OHdj^%Jyr*~o1kxG2Fl9x)&a7SO0tra4Xi;Sn4`EJ83c*ELbm?q&m zVK|y1Q3>0^w9S*ad`x4Qvhu+#4%1{XKin>p9k&Xe3MUPG#;G3*!*xG1W5{*+YC6eb z)~Owg2OiuPmp^ZLtUirV#|gg59e!|nj=ZwoWyQC~5uO#AItl)iEuD;L9gT35#5F%E zfWz*5<74--OBTeFPjLWhK$XAz%6Z$9s6km?TslYK!=!m0L{>^WFXPmSC?701FUx_z< z=bPiVfAd#j_3Bk|{sot_PVD|T{mgZ7$KQQE7B1nCR23@gAoa~=v6M8uwqCRYUEh)) z#hE#oS~;X6OD-J}Wx#*k2+}=Oy}L9WI{OOaE7V`#N2p+$Zu6s?;_@qB5bu1~dl->h z8Q%e47hkqMe*Y7{7rS?EkN17xU#6n*v5)*>Ji*#HueSWox4t8ud)0NZe8sAGYWo%j zy1AWUF?WV?PMqyvj{0S8@??Hh_v>maLV$7VQaJ0L>p5*%b1ii-ko1wTGsX!4J_^V5 z9T@S&PXU@=<>`H{slcVum~TR+H~Bv~7ytl307*naR8|=KrM!B}a!S0m;gl7HRrqyR zy6iv6#(I$oYlkB-QJ0do%-S-q5pZ`9OYvimKawM7uYT?8+KTjH#<*0VNlD30vin`iY&!0J=i z#)_4v#euy~<>;Gr$z9MMtn6Sx=^@If znXEXOafh2UP3gj!xWv=`T3CZIsW|-ezw)n2FOP}eipgI`W(THE{_;2FYu_T0;fT^U;eth znw^lDPlv1rBdihin6_}Ye2S&?!H*?lN#+`Y%v};oe%38bNfdX=Ld%efu`2o)Af~+H zC`VycW z;@A`Mw(nUM@Bh)|F?4h~Mh97=m6{qbC@3zvb);&B75Bb=SsX~n^*CPJqfx%1bm@={ z{0Zbb%`VD4D0Kh&8o{ZfALm-UjhhV&t+)Y&e|`Ydv? z<5KXmtX571Zg3%MmukA&k;}y(I_K=A@!&&8W9x3ps*h1d%T)!OYYAflK{KVT&Xe`E z@zLa?UpM)kRHo6?y{kC_H}+L@=N*tKGZ=)_&TC8s4kf^mtv+qc55DW&@dy9;lksCr zf&J!3e<{vA@3Oe@rPs!1Kl4{8ci$g>_Gf<>Pww1-m{@1%-KExhaI^0hc~~mqu|REK z9Bsgv^cd#jp22Eess%DliSk?70@#WBURuV&Rah$!u%amWq9u-we@CBmy0i55uYD;V ze(-^~^ztiW+t#h|AAj%n{$_F+tw6^AO=YS57kQ3*_5J&eHB^ENXf*r9$U>3!KpNN4^}b zOX^`M`4dWhc&Gd$vdaTsCsW}}JB!5%{6 z=N?iT%uh?+;4?V$C>UR&4!B@5f$1ux%Beqjmj?4lQ?}`> zO8J%F#h5(#D@QLs`AF0qoK}L&57dn@*B*`OQufC9Q|=aTP?KktM;GCjZ#v1hz1WJN znoYupZTS$Ks5=p5I^hzg@!XYxeE9gI(&v|aQjkb&K%tKYJ>LlwjovQQNjhasCe}{h zoZWe!HLKV_K$PeZow&kax~|t3amWR8HS|A(9orihUN#=T{p+X4YNp>t=_F>kINtSD zY);Q<(>@ly=UNo{GN*CmSIYxA?eJXdW(VM%e7?iSLG7(8PfZTSFZ}xM`1c>*%u#Ar z((@4EKoDuGkvf14EuPUvZq;%dZyr2ZpRiRD`Om_XH{CAWa~jh!<=>7pZR669tvg}0 zN!Zbz@eQWoBYDF564+-Ou4#6|lYx31bz_)|Y2s8uTfB;sqhIP*-UuPC3D(^$TpI@t zPQ_PlMfq4d6xTd=F^lV|*TAVqyO_L>kv)%Cq1&peg*!<>7S`|q6bz@amD4Or^(^ZW z7jh!|6nM@!g%jZ!c>B`Lhu9^D0_VCv*4Tt2fhunrdCUzBV%39bbZa+vMx0DPT_L%K z@x;v(B_nXGAI!4n&35wMVsm2y6&0C}=jGy_ueRagN8_Ko#(VgIZ^U7a zwy@&=%+G%)9=Pw`_za5as#UAw10VX8xckmK;+H@4K9siG;@)rE74Q6kAC9dkeNS$C z0&z{p{sViG*I}mk%)@s$r|VTBapypW?SeF^9Qzfg;>5_aeB|g?F@r~{Wrp7_O^tvN zM!3_k0wbfa=!hRo8!zg&WcfXJdZK6a1iD0<}ZF0b-#Aja7q-!MyJW<+jy!<6`XWvM4vR+(Gd3Zg`&{N z%YfhuHh*PQQ2r8$E>)2FGNPP2BQn2wD&G_ZKS_ffHaJvv(sX`>lk^zaxpn##lUJA6 zF=QNJiOaYO%XF0a)441-lO%ImNxKe=G7zDO{TF9CjnFjOo4>*mDC1Eon;<(7(TTT| zvO=%B)<$L`d;oMXx_upmNqLH|n8`5lB~EPncb{sCfk$p0A>v4ra+v8mD`ro93mr1k zf0aQcCQX`Vciv~!k``a8AsRPFioEIoeW#|@ux%tb40Lqa{&@d;PmdQ}Ivk6p576(k zTMgwPuLiQtDJ6KPS){=(j;vSp;VnWxt!&&m%$E#iXqYB=x3`b?$It)zlko?CxF5M) zj<~4f=n|c9i?#u##B4_#(X@^#{8m0qzJ}Q;Lqo>#dO!+|1;qH?sH8N)2+qQ^e5KXL zJXK^l$FAmoJ0<`zi^QdE2kfXH>;5v0ucY~Cw1tt@tdAY?Mzb)Q1}705hm?vPN&$;u zz$qb1xRCxd&W+p4^1Fj^)fFpPp2oOND$~efh|@8rj#UE=u3A^f(vi$lk^Yap?u4@#wCZxaYp@D9T*5b(A#9#x?_TiZAA^IW#?H)j(h>`65oE z`(!eWHp^b(+{AF~%M{}d+EnPLO?9)utkb3%m0QamnS0B?0@htFS+*)(_xkUOkN&Hl zk5Q%@*REZYitwNO$sfiS|MoA~>%r)g%D9z<_hrEXWX5RoY#)bLb3Ixg9|?V)LFfU{ zv&*xZ3^C0=z`DXoc5=>Arq#OmE?`<)E6LyLNw^NSYg0{U`(!E0ws-Anr}BiYC;KOm zS{+hpLfIIGeiy@M|3K$(_~=;N@S>N+9Vn-3R-Ybk_|CV+uYcs1Q0kAxlF?RXFmN7C%?lPTh>pK29T2JKsj$X~b-4}T>>e~l}XaCkGG zsJhpcILkg$5J!An(ZzcreH~qy13%J6?a9APLwv#zZu_R(^OzYK1WH_PaMc=U*-DQJu1&Qv@_yYs-UXVS8B5xdEq-hZG5#8qJ{i@Reqd

4sFaX2z!9kBA*cZ*9{`(bsOZ0JnOW524R;>HaXS)7RjD-}F~G7lqE-p0wm=}F67 zB{3t-55H;s5wCRlG-F&oC~PVkPVYJ$E3C2`|WajPaFw(be2q^R-=UprEszU`S4y$T}a{ zarbR5yhoT$Wov}dG`oDy{%@17bSVcnF|Z4DZVen^)RS8FgWC#8yT*7JgK*qS3)5p9 z;(I@%Z##Buj;FRf#(9Ic#9g<4E%xl&9V3fS(op(lxV}so4=rH#G$WM#D2J1iR3yaH z&#^bIr<<9g{`E8cZ~Mx}%GBf`zJa)_mYu&-ybHM-HQNM;*8Xokiq`@x*7)Vmc3EZ# z?BZ;PYno90AtCF%a6s_m2fU!JO>>ld7mFbuc;Non%Yu5333>S|UK3xt?+3;{x{KkiWDcN3^WE&0JRTREcQJ?5zB)EM@?e}eb{Iu&EUtgaYvb+j z_@TJ-)-UG{Sa(!9*x*Kr83$c>_ls`Z*y?L?`4582o?kXJbL=FS*_w25y5-nkILLJN z4lR**Dqg#uiZ8@g3uU4#^3s4341OC5Oc(F8Wew(txiS~8?(FOIq#+lO+N`vkZ9YV# zf_0E%XCA!&8!UmCiJiN4#sQvh6eJ}bNohg9&=_pKc((RArDF@cT zu?rQig!@}j{t8k+=__w9tWCRSFqpJ$8mbtI7=cqz*v5Nkq8%&CKG*=JHq6>)b^u+w z?xHx27wh(|kHjM<_NLI?K<8!$zP0KMQec}YA=j@jVdk2I6c-JS%riQp2rLet4x_*t zuSvX#J29lg1nXtiIh2i0iv-Sed4`P{@n55L1yitd_zEjv z^5SD!lj+uB;{s61nS8R7>k`f9%Xrl{|C{ngqVf}HgP|nERm2vO^b05Mry2b|34sfD z1H-P+IaH!Z99^DzZMb?9yBMy-^ zn8ax&ncgoi^2tcC*`!DWP*~IbXGN@f6CEQ8iGT^$Ax^yhoCHRuq|KMgH5C(hbgSI3 z3)0<^F}5q#uRjzY{J=SJ>1mu}giTz8?26Rr_$-wnu+%Fi5aOadWal;UkyE0ISB%&!j(%1~7l)Rx$#m!x+DtzqZEyA%>I#uGV zYmAmn&#vJnh0Pq_b||jD>J&BuGwQ*Jk(Z8pgl{odU+5;$6@?6cKCbsS+JAENV{|^1b5RO|1zSb z47)SRY#eMYl)= zh>=kCAfmuA3gQXNC@AUyv$%&At&TP8uZhLW+?aBhbgUDlyr#U1mX|~YE?ccEkB(eP z{(Bxpn=@F|(BNy8sk6HVuiAIlK!U$DfEgfZ-2wv;+3qC```!O7iXVyPQ3eve>{HWmp%}m`}04Hd+)wAuDSX; z*33OOzVx?$8E2k#UVQKu{$+gjFaC_RbQ{6lmuv6r-@NKeg(yell8z#!Ikjy&^Rsl5r(z)xg;Zu4=Az;usY~}rYU(52i_VX6jNt#+uXgI$A2i`-|HOMa4hiMY(?%N4II2J7A z3OzoootiG0NkNZ;o-;%EnQ!YJ=tF@sSV4C`I5MQ{Oo6|%yU2u{6M6|hUnoTbt}3(= zM9Y-P7T!U2tkJGB(I4C%K~1m`5*Oa`9BfDtFD@3)RsIj?5Xu+49S2#ar)QURi8xC$p@D>#UlM0;hS4q=^!NU;r4}3 zT-mVO)ur?dOwf$(+!;9U$G8?hFF5m7Z;=vJgepx(K2TUpKn1KdaBt>of>ERSh#JCX zi{@l7^5fl}8kILYac@tUKF);;+v7d&UK8K_N;)x19Luweoi3j^1|!En>gc4?W(#;` z@U!zT*So3QYM#jlV8xe$kQsReRwck`J*8=-Hl2!RXC@|>Fx|N=mYflB<;ARto8Zvj z<4lV!a3sPqU@F_=RZ!Xn9hsA1HYV`d%OA=eQF(~6Q>vcPN8Zycsyp|b#qs!~Qyh=6 zjkR~HD__ab*sw@PD$mf>qn)rh(u92Wjr2%s6L>0cH+P#Dy0TEPz}W`OC||;9 zy!Opqn}h7a6h>vuh2vffuI}4EGQv5B>;zO%LliC_ay_TjYnpP(X8;bu9vB485Fx{_J26rX5E$gCi=vJ}t27tPj zlJcN7~3rc_BZsNS7nr_93Z^;wS|&DJ@*WC9p)Bv#ns!k<-;g4si?!m4~<) zMt8Ty?YDoGsnOv$!P#$To^?+A#812r+5Jvz+VpVz(7S#-mFbUh<=I0I-oxgT`{G^f zAl|xp8|V2w9!r;=$`tGkarXKP;)yMr*u^S+zRM*cDVT4PB!1@Gx@jsAh>#FyfXSa` zx&ZOE_(TGbjY+l*^Aw@Pf?qoYVmcC3fg_Sk><~D&!SEuTzie%AerVjhdrR!5lW{kQ z!q4?@4%vza*eYZ!-A|*^icO&5WR%0GneiN$@zRra5g-!F0b|;VOK1ILc=A>mCi8hl zknub{IX7V#tkIt+I~arTCrQTP@UNHxR9J%j>5`^Liqj;$L_(`@8BQWAY$e2M?#!f% zGcjpZ=JLxAA`(ocP(&n7OD86M=EZ+SS8Ri6c0qSAnXo7|KIsre362nXAclX>fa?ms z@^ATcP(4_a7$zRm^QL9_>%)=PUnxV=!$`sapFA0ssk;E-_>(uHDo#K=zS@Rcdu&Tf z8vIJ8;esW8d`WwUSVSkIk#QNZPWz}w4IZlzrUi^X?AsQXUDg*r{I=DMyfIp_z;lY6 zvYTdPoOcdz&u{bS8a`J`wpj>F^|9;9xMM83A7%t%4Bg2*Mi%vRpXrJC$Smd!R^x5TKd(deo`rcsGJx(%J`Sgc#OJl3xtjEgQj z1%+)zTy%cKb1td95+>C&lq%-WG`kVYgAYbLv@znId!LNW+s5M2$H$o}A=L@i=H-2? z?Beu#uV$W!I4dQAPSGl}M!Qp$`BeZer&HB^ePqLD`GVMgVpV+jBlpFsl`o1nT)UXr z?P?G0>b!camJLy7$zzu3y+O|8Q~8)-t)e?voxO1MMLDa)j8IuexJde4Kd>xr{_6JF zJIQF{!WHCCet_kBH{%*k2uObyj&)L*`7*ZdDO1|3tWQ}f^cTgxy^0{?kb~|1Fv^*0 zH$BW|mO%io%VMVp^^yaWQQ&-!J)GKQId;lkVMPfs2+NWHr$kvZIpZj7->O)&>|&ap zQ)0`;JJ@t`G)^2Gjnmg(?ovChvwDR0q|W&&+U5&Y0-L{RtzRhiy)cgda(Ar3OSUn@ z+TYoHX0O)qWm`1O`{%75gDBytQ1FgAYBS006NbB-VGN~f@sd;G*wF*AapME=$irU; zmOg;5t%WGGet<1ryzy* z;U%1OC>7GCu+kP+d(8?<$ut!~;KVCFVLUX~`aej$7((G@3=!oIx{yI`K5=x2dN7HO zk+@p=qBwE2zD0bKD6?)h>Sx4r23e1jPDRrGhy{6Z;YC-(t+(6~-}uID@gih*$;Fq( zul@QjFxb!S7mUI=y}fJ4j#ShZEnF3E=B9@mnd<)C-~C^C|JfA7N1Mw6+==X^i&+-rC7`>HAxRKyU>>k;Uo0yeiNa&74B3@(%pqg z5R6uNn;QmCR-7!H3Y-61K!D*$WuOvgx`ZQ7D~E9{oug!0Wqb=bX-C%r!m8WSngr^MBVP!ACq)K=C#iKKUJA54+VHHe1*?O0hTNc& z%}85gT7j^}B39CfsbCyGz+&bD@%`_(D$ZQWD8&f?nG%_$v6yf{c&-y;V*>A=w=U#6 zlkX}LK%1Ck{f{#DZod>EYsHo@ZTFu(vo}8e@q-+}bum+r;Lozrpa?K|Vn z+a}}whYrUqBNMS?E%ZRMIHXG>PK|=lW5qM9t0F**L>Z%p)LjSLMLM*tzfQ z`0y|8j!S;$+_>~Cx%IYL>OXi?&ITOeu?;|z?PKcBkvWeSag;CL0RX+K%VuPdn;*u< zr{jjpm&fbgcxHU^54JK=fC4AY9htTE5WiuK7n~=JX>@8gqg-kKQ^qM|A`Rom`Y7)* zm^S4w!S25q6q^wgwn>!~7W+@Jo7xdJ*RI*l0NtfSvM6_6r(~x*J3t5Rdb>%cv=^_A zQ`cTi|HG8&ai-i+p2iLx0LLlhNwpN#MOfp^Cbyz8IWJI>OC&;I%U$@;BR;v=k&+q`9GeEaL(68+1TGHtw) zqcR?izx>q0EDt@A)o22xXbB^7ZgfbZI<|9tvJE_R+Je(`d1jpVfl@3f_v~kr zs;(NsS0;t|Dth~>xpFqFBv*n^%C3Jd5g{!cz7PJn3B&O09|_ar?oUTCy(`zt&x?XRIk`J-e9?ErilxhA`}W6C>W<{j)lC~7W^)K5!mNcmynio3qv2dY z`zh@b;G>MOqtpEUlh{jIxItUCrrtp+{T-#vtGii6^DmQWa2ns-mPu}1@A#L@ z%DZTkiY%)Rhp}Bf%8)rXsX7`fOt@-T^;>Al$MQ`4+Lc-QJ5fbLXXH)hiqYg~wJ} zlkX6=)n6sYf^neBf|_Q%nxl28s2qtn{6L(#bVq#G+teu^z3T^&3u0+Gu;{=s0WEmNUhC z<=K7l+Uu9a%U-r3E*y`;u>(GF8?UTTWb@8gWST5Eie@-{$N=T-`CJ2ZjJClJ98A*T=4%14 z(^O6eil@)1D@5#ZBs1e2p)me`cKi_I+S29jZQl|bAA2k= zy!i51xoQ=gI`+n6n;wdv_-FqDUdLGPHWn9Na#{TT|NbB13xD;gxbLp7#kJSIIDYac ze>Sdt-V4cpHa0UoI83|Z<|U6(@$n8?&(rgrlFED!5QECG{fyI|$#2I)eyC@d(DwON zhQ1#1sC@E|w8=L=##x!=isB>O7t7a6c>UtgS0-(L1h%}Dk!w=39kL8a!;gK5)36Su zP;C@7;MrStWm0~p%e_4z^8w!J+M<(BFWOPhgzKZ-`Cc}RJicW!hj>50`Fm`B1J@0F zdA{xK|0MqO&;B^Re$Ut9Rj+(QTy*JW@ms(3t117{(WP<2OJ2?z#q;9ezWt1lvTOq7 z&$i8t5$5G>9j*VCLs|2OO$_crRR}2|*sRJ_LdlpST9`^xsbySPQk1f1O7W0UmV~S- zka_8>@B}Kb6~l|;y4B~g9%UqM+xAfGIDRlc;Eh*#Q|_5Eh1m%ad>Zlk^zy?ey+xR? zc+%hCDz4J`YkUV_6vpEBx9p<5;G5et1^3MG#8`o(2S(xnPJ!#xo#DF8+DbzQX$8+1 z0u#OmGk`)gcl2_U8XwA$eK-riy<9uy8 z*v_7lQO8#T&hc3Ei_awaZ$uKM7JoJk6O)fw}%A4>SFi&bxuIdSyhyWEpT6Q$}w zqg*((e5h(^XMDPq7If$am-N^Hw7RQYc^!bLP9TXBw*Tz8O$-w3qj{!Xq0ip0;$0$2?fQ~k}o@kaGdCE%V!2J+Y zbR^@`jB4z)Lwxg#PHn|7+jHp=yPdD5?Fv=FQHc}I zDC3vd@u37Be>|2gJrZw!`?|R4eP_mxym@6@bq=$SD0kjh&Ws~hy7J}AD3x~^x{y0H zD>}{8QvAv$-rzozEc?^U$JQA~f8PQc>qRt*9B9fHD2FnzgAoOg_wn?lGx7XO7sbo3 zTNPJxZreWesK>WIiaZ@w#5jzXH%H(;3WEyCv)?9AI34P6s#xC4s`00-9N4Lwqma4A z7dKPWk8Ru=;|rF>ORw>m0YqrWz%8=F9GkWujypG?cn+L3N6Q#Q0iV4Tq$GXPB%LRNk(T+!x`T*js#PvzQ3MvXGf3UnIt!TIFj7QD3l$a#v)2B4TRMOQXpB^Ee{kXj&bRy z?#yz^^uk4_vp#8UY~S`^9N+&qBcdmwpG_{~hq<0=+x;=jqVtvKTos3RZUeF9Z@G{k zBhBt^opnvA6_1KKLtP(aH@0*PP$e`f|L%CnOTUd< z2i_d-d+$$k_12Lbjr$W8=Bxa!;wFJ#zUd?JH=p}+t|hxQzVDqs6iZK8759Gq_EZSP zukz;CNv(gg(4dfdrp)uG4{RvrFJs^_DTQXsjL7U=WKSN9Yb~0uY6iCuh+|+kr zFQAyj+lcL;tZ4QFj<(@UWVxO|0X3X0uFJ%tY_N%r`0Th5zzAQG>zJ!wv-FgB>gc}M z!3GAp6&OY+e8E^z75pR_io+3tGF2cojoF2muiSM7WN7)t;Y%YEn9kB^F=u7Tbd^_y ziic_;th_-(2V%0${q(#Ps|l*=N=$;52onc>Yg*D#Y4d$053`8`~tv893yYzG!7>=pLy3!P# zjo$LruhCE_Zu6Eu=_fQB)r`(kBDHkWK}0NJxJ0qYzzw*`jzU*JXKn zeS%oUfmeyONwXuW`}`a;paZg&acoFlVh`LPjivQxvX5(cXt+Mfy{3Ql*Bek=UQl_a z;OJ0YAe0rfQ1DxFa}k&dn&*%;<2%6@e~g*|X*ozZ_JL?Q{4FKWpTwzm&IrPZdLs1hi{|vTg`#I7cqqfFDhZnVM0-(D{;6UJUFC?5x4y1 zNTRs{+ek4SwpmS&{J_U_+#mk#{c*!(*Th?IK*s1OBeRPj7p0A|n|tHnSS5gt*!4Vg z9oZhMU{9mixJJp&%w2;2)7!3!KmOuZW6x8pO&nrfqGCS^0Z9|?Rz}ivvY95QUSTRf zKQOAEX2r!l!1SlGnd!4`l~N?6MN7{LkJe#q{XLwx^%#~02l*&hM>dt;)L81Yb#=mdRSng0 z7{xV5;rOtTH`}-z2}2(4nq0(b4^xaR=6Pw-kU8 z{mE7bI_Fr*K=`oV^idAL0Z>r#MF1w_DrNGW-=iXG_|mRaG~tOOae=o((Zk*PAlI1L z2lp$J`~bAKvK_#t%+j_kkN`$I?RzWBSh=c5$_BfFzcW#fn@Awjm^P+g!D_sAl~YSM0gD?5KGZ#l{aqSXGA zFMb3jE)_LBU1EYV*M5&OKrkyOIF)?Oxp4*x++EuriA@K0$8i?nx@m3LwlUGT1CV5R zoI@W#P$Ep?MaR=jQ`n4~b3cn515|BRTIM!eop&P9&)>m6CNS^X#KW9r>@fi%=WKi3iVAWLw;GCwlmG$>=yoP z+0r4SDh>T;6}~w6JP|+n6HDSHS1mv_quUUMFCs^|r&P)r6>v(`-F%L048n82k&bpy zhOU##5i(+@QChxqPsFeM%7!?S7t*7E_>!g!>@0COGNLdoloY4*&K9{(|QB-XBBlf^qui4XrTr^WNnL78+75+hFz+T}G<&@*7U@RyCVWzN5kZH!^s z2Gy_3mXBCmr1OsoZm4djRk9Jcv&ZCBVdOJG!#xT9g>=NvKYuu0^Xm2S6bI%$^3dZn zQcQu;Y304HDbGa4Xe5Mc3_~e^F|zlOFz_RqoHoYF{@YP8I{k4Loo|0)YrNvMr?ba_ zj>YqnI0}TB8PZuUbT1^g^Uo1HH8R4@&7&-)RP-2C?W5d$ch{`xk6Rv^j!loSuEuFp z8&)}1S>|f4EEw6HFCM(ls|=)C0m4C3Rvb%aG504u)2_v)&q+ zeNV$xL4~!2>C5nrdES9RMsl;=biEX(AdGW1V$2?mes%`;v1ZX}Ruj!K?L9JjcFat$ z=5YVMIPHuJ*c`w?xoq57vHFTwb^2v-_y~g$#}A>OFN#&Cofn5WLo5e~>G!g5EJFpK z_02BWT8WYXP4=Z(htuwex84Y!{7UnjDHX!>&}PA7Jc=UwIP#leU004Q`GG{dYu9Xx z2U%w@#aVfaxn1Ds;X~Z-xjm=qyd~tim%K1;x%GAyY zh48&~tRg6Ns_VlNmC!tuaL%Q_@@%$BE^H$L-^qD~Fy-Zdg|I0?!kS*7GM9%NcX>#E zI7d6k*7v(EbC@z1r>!}A{rPduxfe3UdJkp6)H^cX z{P?5s`q#geS9(F*dDktui}*N7pF4$*A3c^+)>7T0UK*2k+pEDvH?`)>U|Au)OajjN zO=Nrvksqs9GMtIJ^tf}Oq3Nqe(oqnHpEZMV;qc0M@w!XnD2pS%y!HOr!y?^DuJN%D zhgjFrO=Q(61hze-$znA_Ob&hG?z|Z`_fr&>q?_9`bM6K^KdiaMDX1NIGsd+-ZFY-m zw&2NXOID;3Z9rE!5?D7+R+@^<_zqIxTM@cciPH*L{zOpe39vpIo&=`_Tn1P|bMlcc zgLAN*hS3foM_*vJXPeMQrK3A}F&)A4>xbFRH*@kTSmk3_A<|v`j;u_oTP4mvftyTC z_RObpH&iM}SxrXHk}p9OI0qiFp4{E|1eVZGMObI!9@kVx43d$agf~wJb2!?@<2khT?OQ z#)Qs{aK7uU!$# zRxgZuZrc(k_MyZrMv-bV$C+o!WxXkI`Z*DWGfRwvRts%SXEYH3o&$am4|mGOnY zeHalll<1+#bS8lDfY{zlqt!Xn|7`v!GELH>>6crkxp4s&sedR@|b0+ zS>r|=*((GMSq6NIA7Bzpl$vO9Hf@C=ln z^AY2scyi0l97VGumaMt~dPbQ!<<+EPr8sfx`@LT3p11C8;C#kUw zWcq)G_Ti2@zRvY;d*V%R|K7Yq^$VZ>bo}iXJ`=z2OTQLdcRUpjKJay>3m1Z$K@~=i ze1E1V7y(9sD+LK$=0pBo-Y)UdWzDRzNix!BTqlOc1VJ5tAdzPKK07*n?On}x*tGmr zJt>?Wk_wk|qOJFZQBN6Pz1)#XjQFz;F%PDirMvabwl6tSiK3<3G-*Ip<8Jgkgh|gd zqmmve;+b!5E*WRkYzEsy&Q;XJ%J{5Q0eRsT* zLzZ{!+{tE=M^V1%gG>+3F_kzsJzO;r(U}tJ{hrGKm?e@r{@r~DlX?_YMzkCXj6{3u|h0!lez0{_igDJ>o`YY7m zj=n-wl|VN*=VxQ(D8I6-{Kc7WyydQRnO4-A4p%C39l|d493H@uzMiaT>;&Q|g&N*O zrM$Eg0WX|?1{jtxm7St8Hp5NRU^=_PyF5D_1tUPgw=}{wrOiBiX{yX2LLh0GhgWz_ zoq1JuYoN2eRc4utG9qEJ@~w~*s*JTBR#rI3_h|~86w*O_lLF^Kz9;(#T*qU+Xv&eT z?J;pQzV~|;#~WU{I2I!>uMzEMBy5@yu}QBCf?retbSV`t6<6Z<$$L&w^u5-}kqtXe z_Y3^qry_p;f7`?~;&T8;8AGutO`~N#&z1%{z*J%sdYx-h01iT!Ct%rY{NH(-%r)@2lDxITmDsSeM?*(Lwu$@PXc;;MwprSZ1;`_@&a z4^<4N`qcncgE+&B7F+Q)X_-FpPi-hKSTZM<3Lf{k?A}pr6GZ_)FKkw-~W?kLV=Oi zHIca!o4TG?bkT`>)tT>uqfB@Sw<4Yr>(;*zg{3bJ?%5KDp1cnkPQ|h{=f&vC)8mPU zz7S&vH^rh;ua1>x+!zPIg?kcpFAPn^Pb@|BF}Kt*RF_C?Vfni<_*-Z?%@X z>{|=kDu5dvsz2!jNBmqrNf`fL1vSld^B|4+Aj*JsC`ZPuLohhV+AVQS`+))WW5*7m ze4NO2a_3)kX}tC|Z;DGVxq^)x3*$4N`-?crks`VAgZJVDJ6neseG;$NajCeZ0t7xk zf^rQU`PuGUW7|6h5y=(I=CNUJI-+6X9p_qsoX3>T|~+=Us3)rvxmI=U#DjJoM20v2)v&Sj6b4Q`Mfu zH^@kw<(Rb0<3oQpg<6f5i}#Xgh%Aw7*OQ6;!N{;}m9DvX1WtOVIx;kco#rXk4Macc}wWm1Qm{ZiJToi2Hq)Y+P2>Iv7eSR>)bh|p`8T3oUw=|{ABXkNt zx3)sJpz^D$qh5dGbbdjL*eLT%5d02L&xc8EU4Raz!KI9vvev~{-r`bv(g8l)m8L3q z;e znZTd)%Zsq)+1weKKZUE)y(@$u8``G0NxM*qBrW(O#3#e^m%L5KpLW%&vh`c5=Gd8^ z&bTs#OFpW>Gc+M3ULFi5EYGR4Irzt{z$pxwp%IMngR$;Rc5eRQl6davG^YERDzx(+ z8fScqbN?v|Jp3^Y;v6L=zZvIanm&{s)*HR)4J+f{f9!nDgJi^S;!rGdjT=){&Jy;IQ0_Eb z-c30;dxVc^hOSr}rm1(Zm2L+8cmG(Q>s_4dRN7h?<5cJdp|^i*Vbw3_9USB=F-8+- zCZ^*#XD^5=U$8j7{?MV=wH1YS&1p=hI@$nzS?MKEa!BY)r>uRMjI$3nPMP_*9!{N< zcJTy@myaJBj#s>Vlqo`|wyBQ{we<}+%66QG4j8%ysQkHLzaQnsHaAB3I;S&6-hJ$< zb*HUnnCCj8YrTv>rohDd3{|&Am+r4X7976&9^r!!XV@0Uz zC|i@@QCYJ+Pg)td^1QhmyvaU~ya-cK6vqB8uioo8sQ!YK)LriZ^>BZ8jZ2GX+KJnp zokO%iDx~?Yc6=po{N(FnC!pTIzjWlRNx9(NcRZzNk|yPJ`%}tcc~!{TNSYzG0rr1x z{!j^Ra#xYdDQTB4Kw~Ox#5>2(>~2{VkMDdUcCz!dFsYzbp58>` zDc4?Q;^>IsS%@%HQwh@aUo&7#;fb)wl>l@=i@)v+BDY#222LwnFT9GVHR?(#dHp6^ zd@Zh{Az4O-DxHa@a4EKw3VHCahamqjDhXW}TvlGbZ4Hh{*sV0yBvG0`5d6Lj^J!09 zCZ?RIS840c+)UdcmY(`D$-oS@q8OcD9WbDLTG5+z$w3@VUZw0+o`iO&O6J08>V=SI zmBIPO7e&)-V`V$j@T-Cc2}!4U$V1YhD>E&8awq=uWkXUp{C%borhS=ae)-ZDo@c`T zqxUN;r|3}NuDoJ0Ui#ciSOm*XCQlG|`kavfcR%-W;-W+QC1c8+iZziBk4FFiKmbWZ zK~%uB=?w3vtBRb39cC0{j1FueXHNarC+>>P+j*BcQXw}-%r93(nS$-?CR)o{@XC6SJnlS#)t^bG+?M%j2WJx*DC5ys2~xXvC*ow=*)9ii)L6drnm;NSUmHrsTW>^B z<@gUrZ9Hn(U6(U&p(=Lzwdk)0$gEO&j45sE3IKGRDUa#-UvglVXG$3Q*K- z>)Clc%R6?;%CqU{5Alxe0?%T0X}*+m7I(#=-FKs$b1vIKc5|+|AhvAS5|fNnl_`4K z0D}#lN=%Jj(E<`}TW#k*mC9wOAVGoQn)X3jAD)C73`^NW3ZUC|PBBtxobt3DsT5E0 zfieOuHDXqB=;aOXGh8pHVxlpSepqx0qq z!i$ztJZ*pVo2IUk;sX)+OT!e0EBg*u9QMnTPRXLcIf|!3)XzuU5ToDIETtGn=^S$1 zoga23inPtw>MfV{8Ollam85IUNdw zwhSqkSw@=j)(jH59>sqDE2ngUN%G0VO7N{7 znX3^u;Uy^vD#q~>-QiOZxXGx&h+iX)xv;1ETX@m>qLqDWgee;sZ~%!~>^#F?xGYj;??bR8jC z5yVrtOeD_aQ51SQIrz=E~J(L#zQeI0!J(K{i7d&Q}|7&y!(9%_s93VbxFMXs!^ulT)a$!4ah8u zzNb7gfsrhiH@K%UtFR3s!$DKP(VW)pTQuYghNj}?4TJI9zq>W|9c84cZ#fMS0QN;9 zY>)YCO0-=#9vYx56PGIG;2A?shwhCZc+=YW*srYz4S9gWkrruGp;5V?by0rew1JRH zPT40ji_jmf^62=7nO1ML+mr7+XnKBVvfYvXDF~6}8gUq)P2pheeB`MnlV6Y1V0Q~z=IXQ8Rktf#UF$&v1=+yP$ zEK5hK+zC6)xr@Wdc-g7X<%p55#MH5k#E-@)tIv<)M_4m?co%5>2$4_TN6O9WAd@vs zOF63)w7Nh=aFM+IlkbBxbj53;p1a3N@-i=+m)gGquJEeczFSlU>mc!tGNs`85FjIX zQ%M2Fc99G{6xjv&sbIo&r8B;g)#w!;Jm51w6*@=pYV<902W`EtDnTl5t_RBx5BVb9 zH07ppqa?S>FkSQFS~T^*tsG0@Iv&B*ieH_jh>3*2wXDA z3+2VleqGwR=KMdX3%hxhk;zus=7-I9H~O=HiO9lBdUY3M6oX$3Xoe@9ZW+X$72$V_fBtU?wvirqNoy@+U3=a-=H(Mcm~lU~^+-G;qWilbZ*|w(o=YY6%GxLPb|KBKZ3Zk*i`*qO; zWAWbqd0m`73a9RB^s5MK+UtUx9;6|4A-Ja<DrjA|kI+b0He^hjPHu?TykuE?^k>eA)r)|I zt^w9~jWI=PqixCebK3okUxKFhu|q+TYznum`YNR}*)9gjT}7hQH{Joh4KqJ!hYYD&s=UkcJrz)=rqbM(#ORtU?(nZe~d z-cK2G5kZX7wvG=Si9h|!7Bup6p^Nm=WnLQHqC6SQGwD!nh)LO9^V$WRLFk3;i{b?@ zIVVPzoEE2!u8s9vOE!Aqa2(mQi+%$cAzL>fBx97VwMlog@ua^PZ|)2v$ku@ZW2A?k z2NBsXS%8jGQ{r^awz00gq%gDmJi29)S)_5U+M1?53^U3)IsRlE+y5wd24dN&7sjH+ zBOK@QMf}w48K-%atz~BGmJ2)?{ z`9+%aO0x!k;>3a;l`W4LQ7Ox%B;-@d8-Bq-nP-*K4mb*=GLdVk#l-uPA2eypo9zW{ zODb>HP3b8()7l(3s-_Z_qgUO#t8%u8^UfwG#&YyMNBYneyt{Uq(KeM@@2+)|fYZQv zU@%jRPOpm7T*#7)(v@`OW&;^859KaY=10YsUZM<7cSnbT#106rlUt#!u%f?7;+Os zi_p=MbndL!xJ5#oI)K&6{y?K;hgcb!rkL>So>w51H%Hf&a0bdHtImqEm#=2@ZBuO8 z_Y`0I5XhRBw*ElnL59TPk6%Oz#e6GB|I?puYK#R0g$WgA^3$bBoR)fGd!|h=;n}EX zWzb(n**7!)>%_1sZ~=V5$r@*l>gCH*e&I44{QhOJWD;d+tURTa1tm@kEc4BHVV-G- zNIE-!M9{(L=D*}tc_caTZW0x=EH5&!?3xT)aP7ao2^o@nKxtl>vM$;;jt&aIyVY9;kmzB&s$v+hs zMy&d0H^xQlXX1B0dSzU&YCMkaqw!ugi1NlU1b)16arq#?16;W^+wF!O;PwRjPsmBy zvVkOV~4j>B#g9C2TosmQS9RI--#1@P&~k49b#nE-L5Kd3t0DfjC0o11<-Cp z3U1#W^1xkOsJ4(HWG8D)MvWJ3pQjnaNZ;OR6DNqa4qMsQ0+Syyu&%q~b_#{cWiRtv z=*-fFQw2{&R^>@r#VcYy&Qu0HCPM`-`%q&tPm-CG^lk|9jJkZ^j00eZQKzxv$2c;> za}U|QIp$_1rzSZcjS;yCrqFAt2MQw7+JkPIaNS(KlhBj4ePt<_nI-w@_b=(g$~@%( zUZpzUee7loRpOdVE6s!{d;eM+k@5H)jjKmV0nueFoP4+uLuJ>zGAe(CE1#iBN2M;9 z3NVW*?}TVxBMmOQ^QaIr0w=PDS0LOCtl364w~5VO!do@-eGo>!1z#s3+@=01EEeT4Dq;>L&LlW-lb4xvL@0(>8s^Y8j4Y zrOq}X<0x3{Cc|_C)1jlA>+~X`DJ1C z>B;P1oO8{hSa;!)m^!p59_8ZNZ5yAAMfF=gl z6b0$`@fEej^RNEP&N5Zulec^zLx7&ug~ODg83SIq5jzuT-*} z-bHzHG%ZsAaBXYW>!@ER>C}$ZFU$KtbO+NBe?=|=>ME+GajniAIt3YLBM-jCC zt|?fC&G7;dMy1-4&p7Beeom1Svt?EJDKo#v-2H2UoOTsS@{KM-cw!|^JOE@nhqvV* z^wW)C0u(tJhfZY0c=}UN6{fpYpmLj9EaEAlXxk>rT!tf+992VN3V4LYdTUQvofqM5 z*|jzHvK5*AJxRzki==H&+`T$OL+!fZK?1Bi0;HITZxuK*Fec+WhVaW+KDbliNEpJv zQJ5-Tw{R)fmZrmBIPfIQ9N&%q`F1HyCIr6?DT(Jl`85wuhBaj~<)w_wD2XaNl}-H0 zaL#RIB)zyB{$?kkq7i?WhQwBqx%8w?e1ri+(#*iHEGE-E<0(3;9GiR_u(Y(t^p;DO zjb&^=7nUzjc{2>BQ9ZYrnl9fa(zYS#w1JeqEHUDwAs-qOwSHLIuMWjAu^ zG<2gM(Se`@xs%a_@BHNl5}i%T(ywOzyjydY2DXp2aNZTVfP=L+bCCaEf00cK+zgO< z2fOJ2#mx`#c{rOLtzqV!1k#E5e%`y(a}%CCV7v5Vki+LJ`aIumnogPZ9fNcv1B|!~vC(3jsmf!@myyZ= zHfns}UFXF0*Pq9Z)a@W(z0?4mvAO{;IT9)UdyLb5)x$s{*4;B&Oe4yXogDA_)y^6*R{&q?J*`?Huf^@`^(56QaYyeWeEQAEsH zxSo9@{~G0kXi{#Rz~NPGM~)wkm%jR}_}=ec9s9OSaJJf5Ty^2P7<}^4Sh96PykvAB zUU%_@sQw-2y@nw}{V`Kua!T9jI&LV)BWsTE(ZgZ9l;Hr!uk^d_O~siJ zw3i_K2B1m4u2vN12G0x7}vHjfW@Gv;Rl?Q9{?lH2R8*!++o&|Q| z_=y-@dRi=8dMei~EtPDLi5tthj>9si2yt5pDy-vr4v=&NTkk zIxcZ`^-7q^6#a3W^?nm^g7s*g6*s|D<^=AEadxm1H%8bAraUv8ciCOruGh;<%@coERH9K1__ISqDzeXyeN&|| zOytFkVFKkkID#@%D}$3_3`^+7w1!Xtj-8bsp#bgH*q2v@?>`_FNKvKx&+IZdC8wp&7@G9r`ZW0 zG16w5q7h43C%xL=Yz!>y!nVv>XfH{JB!!(X2*4&t%Y=I8*(6K_FT*pslM{5MmtSDY zS2UZqIP%Gu4o18kA@y<>hQjKvWmCmpZhyNZILt#Cek)UX7JQF~O#gHxO+58Sq;&G9 zNxSoBeaX=7XP)qFV@;yfu~T$qxExUSo!bupolPk(nRGan2gZDRbwj))NKu(^o@rrS zGSbZZP)JnNG7#kmT(+9l!F7zly%A*vB__4g4q;`Te9X(@|GD!SH1`D5Fq^M1KGLIq z{==;Xmz}pGe&&bH;pizAA@dpN1j!T~kQa8_Gy0y=6e1@i4WHGF^F@t<($6{;HxTsG z7=Hft!}0I_19yCmFm+1fnmdj%PkJqt4Nuo?c_&T&WG@DNdakrKNv%X^qI)PLNqYT3p_DNgN#H z$f1gi^+cS0&%2A3*)4QOuHb?qH^CZbrLZ1Z?x?TuYYx=!W=3;7 zPUP71!np0uso1xlh4&*OBM%`>I94K*ayqR*z0n>=qEt?HDC3u+OuNA|{vBYRMij`BV@N=FqyAu*5KF`j*6<=gV_e4X_S-mGK>`Phl3 zJ(nUsp)+1)Y;U#YHHjyGt4Yrg%BD7T|r^|b~0 zxPb&Ek9@O#>N3iUUKBId(S#dSY4(G zTq(qvc@Z^5nYn?(FDsW936V>x0B{7aMgoyIcuZqeFO1S)?xxTfs=Hzh(bz=03ptx8_O9dZY9P3w}85d12joWu@jIFbW z*&R=XhPm9FKt0LL3c!06jKkmE94ZaB6+?{T6+fIF2U zDuXVK@j`vGX<TD?R}k@o}Qgq9JY~NfhaFdtwc|x#4F5DTL~qNAQf@aG(h*mGwF0qD)eAO zlb5zvmUZ{~4{s25Rg{AH@i^&K5`j2J@3tn zK~-L|CdssL5Vy^;v4JB#LW>=43KwEbScOldT=Kqg-97CG|LH~Tq8+sRIYzN&wq%|E zw8uAanC}FgUv{06{xYLh%iPT3uG6Vyt|>aiGJTXf>$+1C%bW?P>UIR>afy+y2~NXj zD^$C6&!P6EYg@bQ@~L*sb<=Iv9SiLMQ!rC>2phOhaOaNY_P9$9w#hww<+^ z$y%h}eqh!zg?0GIecV^S9$MJKKj$?=Oyw|2xhSRZYs!O<>4>~dVuJinplGR^I>qbE zftwfRkyp>Su(j`g(a!dkUtL^;FMI13KtLYt-2H=)JNa7~>VK2knM;g8d-k9)*& zd*pdHwyUloaP8?uhv{0}+9#W_-k^Y{aWb0t?5Aw!SdQiPt`F~S7w_2EzUNuzwskW{ z+N!6X)z+W-+P3P`pKUv?zp8Dy;`3^H()~QzO zjJIfTgf8rb^4HU`4S}$;~um_ze_(e#9*?6>Is- zaWC+TU;G1%Y)-VF`{|$I{NwxD+2>u@UjBWr;@Yw^+VB3CcVwh)b{&V@G8$;V<(j-D zJ_6kwkttng5b*O|x+jr~imo}2=T$_RDB;!D3()bbmE;kQDPS?hwp!wi5S z>6!HlHW|j}0OfsDx`3Z1Z&BQ27rMm^%OCvk@|c#cq^BMducwr>g0t{Sdx(K>DU;QX zD!Q~X9-y)~MsPUeuifJswG!yCu3LhMV3WY$!ge^-VZ$jF@vcRXU$E_T>g-axfsG3X z_##PN0cj5VWA~}@%d|CxA6!M@Km)$K>m@DStZ3HVr|j-;0go#vyvB5UJXX(R)j!^Z z9SUVU)njlP9%R^q!Y6_JC;E`w3L}REDf{$)!kOMmzJ{3K)s>$(D&mM7Ub7vUu;cSG zA_0d}okHY6~SKfNON~7q|9XSKML&q5fOJ%1lAUbh6G6)%BVG|)QWtZr} zLm2Q>8byO(0f#QvI!T_QG>(Bews&(7T8YeS(lEt(8-;~JpGT{(Z1n1TiY|A%$lvGz34O>X!nfpp_JuHC`K?;m{o$z zW-TCR6o~^{-6Sx}fpkmI?==!fxD8=x4%jJf9$@jacZ`4JvV-k|e}1ri;7_h>x7`W- z^NcV}u+~fM){zhw?k}PlE$wOl^WC?#O>6hDUTu4O&T}qmU;mWZ_T;bL$aE!lYI5Dw zDwM|wMsQ}CF<9ihuL(CH&{)X@a?Z^RD!R}{y)^r&e|AF9ew4-~dsb7&UiqCn+k5`- zhIZpV6tx8uHAj*RVowY`i&u4M!y_tL^| zkCt>kHrW-Fi)V^lbWSjle7u)I!7@0lgw6?HwbK7{v*R^LHd2HMEk-uo| z`r9&kZ09!0oAo_R#U5lzMt&?s(~}YU`yyyOeHX_NCvU^M5NVUv*+jrc6J%7G73ERo z&_1a5Ls9hj7$D+!lydjq5lZn(lTxT;V+R=s<+bA;3rT$95KsB7JQPEc+?{%sL&dPS^($)nWKs$qC5Q#I3%n7!b@@JBLW#!hzu$?-t9i* zIRN;n(8GV)u6>wm_{9-<3!r&GI%Ghh_ls85uD=vkc~|8k+VD@@kgJe<)+V%w5?grYngK3@vw{3aCKh;Sb@0 zXz_G;V8|G%yr#_$aKiGRIF&EoE5EVhu6H8`-Ux$Rd%QkX;ST_Y4FHyUm;n%M^uUM&K;Lk89*)B}%m%2V9G1F%c6IykrOWLNKX*<0&EMG9KK79VZU53ED63ORm!lfk@g6NF z(i)A(p}X0QoQ=1WyFt%tx81m={rN`@w?F^0eeEtb8l3mY4Q<;xCY6C-P1!GT9-9(w z5@o^ZA#XoWadTe5^&W5#8p$(gb2N|HhKDL)z$9&(gr#Dv=+XgR8O|M+t)vRb31Db_~Ek(xQCcJ9qz}N#bCK+y-$w4 zFb&Z$+QI0R=Q4R}{0)1i+DAUcPQe9~TX|}_Sb4Di#*Oqqcj^&!HoJB?$e2Zk?XKAa z?WQkX-k!)@_$9Yq(U#x;uC{pn?JRQVIF2*VX-jJuby+jt{_A@_$mr|soTtSasdUih z3d9k(fu3M)?F{jq{jpVT(6GlWv>n0PlFoXTt6mDGuC zAOi;lp(!#RPfSsOjl&HJnc9@#A#c@hcr3^aS8{Q2JLkOp*iXKp?b&lnn`7qgdCz+x zQ{(e(-@SXY)^7D`4((wby_n~?4P$h33T;FM$th>o>8)lrs~aAaEt;}v!=69qLpxO* zD6TqV5 za6vT&VFk#4ta6V9$JxIwqD03h-F+0V6+Z)3@@|)Q1M>EMa9Dy13oS5;PL25+oIG+pRCMR)WC1k)hS3CP#{au zGkb;qc%%XP%KwDFAwuHI6j4Pm!YiMbGF`2Uoswssbvlfjn!+nRFeGE+TGoz5@l}npqZ6;VaPsE|W@@^ET}jO+ zzMCqhc|VF$v5HZ{Z++fud*|H>4$d>#9_M|apD!dqMcYS#E7fSOwSaI&`LVLbd9;QUv5Be#xYjPD)K5YP93mEB`>TX@kYUZUoQcPPML zvYp12M&+KOv5ZXc!&^{2d4v~=ch^#yhaecNA>spCVo@Or>%pX45v{g#)aVj)3`*6C za*cYaB2C-+wimvvZQFiY`_Ui%-mGc-hHrRId+zhTjZ5F}ZJ+(zC)*GF(Cga%d+%*; z`^{fuV;1Y+*!+0-$h~dDrmc+3u~Y(GL%c^&?xs09C(Cunhxw8S|Fr!^asXHA9L-m zDcP1$FUf_IN(~h~}~REtvAN zm~3pvK2gB%)2^q|tVU@nWx^wzq2G!ZJ{6DUPvxZshZG*>OY>m)>-pEV`fBT{r{e3A z#KQEv0XM*QkZupb(d!%ra<}9I11-3293gqCvHSG&u;7A34j&iZP?bgu*YiMtq@~>aU?o_ji ze5P&~RdL`?rS!rJ*^9FcoUBO<4J4IxwNC~UzCcK9cTmlGv~Pdj=Ju~&{`7Xq zsU8t>ciYFI&#RgCK9wuVrr!S-ZSVj3XnW_c{a(BA+M6?dntL~+T!RkY(hZYWT0fn2 zI#xSuGM_erU2@ApAn`6!26xHns*{qYQCW!`wDIUU_7GBNL@LSzTRuvaloUP&tUT2& zhj$4!?0CC~JSN@<$9NcS$ud*o&8Uy9ENGPn$^ihZ#CfE+lnK{*s#KVPScT?(Zo^id zg3Sn_c#kuBNkbGDh!A@>$|>8{7Dm=y@a_MItLc8D?cRM$M#|pzC%@m`^v0iVH{5Vd zd+Jl4)gJfQ$F)EGz@Mg97jkAI8yi5l$Oea194+E#bEx1)J|x@x zMD|nu8F>Z5S-{nN%+4Bzhg<+`z2Jdf2*zS`uQTEp%7qnkQUYec4~#s|Xq>?vCiO%r z^(z``*XmIYsAipQ);m_Pvd_&Ao{Q-bBsELX%fs|4Q#tz)xj100$Pf8DFhnG&sKp){ zRqX&%-#2U-fw2i8`c)j<3&rhPt~-i+c@~xVh;@o!ouE(}lzv^S{qn|$9=2!!F z;3$Fct`sFAlEt($kS(E%aZ7BzkkgJ47})@0Ej}C9uT^lRU>Z){VuesnK@Fbyqw(8uK3)>cGI0~nplIsyh$8tsr<+2Q>6+b6-6Fp zm|yqG;wjq9R;H}aJd5e&W!D43BT$`GMjl9$wsmO{<=ed(+&z2tx`lT78JpTg|Kd6A zynpf1w)C|Zx4XBo*<|BHJ7voTro?x*cmKhM+y4D*eqp-KQj>*U4!THw(Svq*j`A$I z293_zyEc#d@qt@-JKpNVEgPAO1hJmu?;wMNHI`lKfpiX!ApH>{ZeU~Qy#pxS%w*ZR z>s?fXn>rM~jId{3Q>BjY%hkE0N8VYbgDovOcnuf_&G`TKM%o;&;(@2y;&S(@$FArne3Teuk2$sBA3AiP{Rks(pZLFj#<42f!)ve3 zy6?XIjAqZaSG?i}+8_Mh@3t$ycp2;EHbu#N^{aopJ@f0I1?TKYKh zSASjdo1{D-Jqj^{PY505qMIF58rw+wn>`>sT)1rMedYu+A_Qg52=q6?#Aa?$>BhcBtDo4 z7zxS@&q^9)hDMiNsU+%0_6|;&zF_m|?KTctypcOk4|}H>#F_VG*s)BEf~H1|@>+Q- z=Y#&HFo1|sG1$86IoAI{5ery8&dP0~v#DC8uzY@NrB$_+e}Ml%}N5*WS?o;F9G zG)24s1}=RwiKluHSr~(2>%^_m9?)X@65rU>0CmzNdQckX3XHB387sAU*Y@e@tvHA! z0H%l4T8`oqztCFl0Qpnkqt~yPLmrq`crn&CdQYMwP)rr51%E%EHAY1dxJf9R}i_{*d0^jt`GjxBQr*F1~+ zCui2@Yx$SB4dQ+8o97Tu=t8N%6Z>8|c+NFjWK>C--1+Jdmh@;`;-QkJt+D@*M%GKU zS*F{j?q)=abnUmcoZjxaebnny`n5hcgVpEiE&BeEvjx^d)E^?5@jt6Uma@nL5QW@IFkCE+5wKw1xe^ zfJ?e2x3R8kg0=acMYf)GKJbjR{8CRgA>X=dJx*us7rsgGOhoBGF^6vT+%wKVAzuyB zjF=8USSOvaoXool>`~4KyM<}!_Cao9 z`1CD%DCaHIof?_Up|I2u7rD<)uWpZd+>_cxD3I$Hvj2jzE!BbEaHRjGkdgs_tdgF~(NBLHvnPbZ}cYN{zxD5A`BofhOo z=8?W_*GDC-mvsmXA5fMCMb7iurWu{9HFgqeUPE%_-o)xAJll~%rmJ@jlFNSg@ z4f$P&j666&Sqsy9C19&9jT`Tfcd0bcHF73d!zPrgT^HVRw{^znz-O z8Fb*x?ks5bV(Tf65U3*$78tbiK-$Un-VZIc-~NvW+o5?jDXeBBOJcY{+y(BLI`;li zH(HF&rIRnfkWOg=yxVCzs>6s>M(d^tLvA4%p&y(J2&lE^w_A6&_HTdb=62Ch^m_VyCvWgW$WEZ3I{Ie4l!v1_re4Xcf*r!S-1YjnCmd}b`cu-eUsOTU z$UBc>F_LZ{m1M(R!0*Scg`z+2yZ0P!x8C8G{Wh{o*~&B0Q9-kSVP(qMc4_%#jZggG zF}ro`6kK0(!8sf=GP{co(+(Sc0CUn9^V;drO+M4fFwK3)WHB{&(BkUjJ{}+poR6?On@OUphNhHl+ji;AbxvXO61c%V|olo3p)V zy%{(?tXP?^vZz9Dzo0I&%A;f`?^Hqb8{3rqf`;r2258%#u`E{&R{9eC{GhXcG6Ijd zQ=kPoa_etQVHBAGbCZ3x=tk)X!N_gbDJ!p-5px$6x+BqY8+`5!7N z@0hBv<$kDN`JyQ$X~izz`mIVwemd}Y;ECJ=&f|kRUE(QJ-98rb{>c=e=SRVgQhoQ&ktt)OluiXx;2+5ljF_rp$`7{wdyIryTrMAZ^+0!FhKX@QW|G4?p zwu(f@&~5YqZ7hJb1xi&K>eJvA;NBQ|xf)gogPk;sr$H>aq*^AJsv=^B;Om!U^E{u$AlLh(?QKr2C4K}Z@OgepS{K1o#ZTw3ImcY-F^iF*6Kx%QiH z-PLyQV+Wd>0F0h=RY-@g!ffDx4&_|ujl)wMV4F;!I1UxHY;IL(i`&43KX+P2xsrZS zwzJw7F1@|I^&JP=8-H{?=gq;_wVbTawMa`IZL(@3Nw8$ZOr;*1pgab(${dVXMp^bS zSJXT%r04MBEqAp&cLCq;M#brfX_!vDm~L0W;6=E8qGyiyrd_MJnQJ)*+IpNw?&eHb zQcu3pY4|ZsIc9wk{GM3wW(~gTIj`^Z)7{)U$5a!eeD{<5rt>#w%xllD<-o{iPGGkF zUcayHx#q)d!^PLMsV80BPW!s2wcGX{Za??yztZ0I?svC44>5JRnyEGO?#>M=()8>) z?itZ;`Y7Y^EP8hrWue}V25MM423wIbZ>h@9e!}m(awak+%@JibwIm&8n9xNOV-)g z{sPyd303v2+g1O1?2!yj3}W@9t*^ngNIh~*Fv^J^Bk)Zn$+OEsPlp3G>0ozSM(;hb z*gX$YxMKWR*O&)C*i?>_4|z;_^4ggCsYnT(wXno<$EY7+?|a`Lw^LuXqrLjI|GeFF z{dMgn|L{B8yMO<8+ZR6nnfAS}cy$!YH@)%o?V1~|Y8PL0N&DG1{c;Zd{m7sHZ+5ow zQMzy#g>W@hVv(s<>cqkQlqY94G9x%cI9bo=pzXzvHEm7KYlCK$s3V@Si7lPst@)C0 z_|%Qgdh20`7V8i?ks-AkmO&BXX5&O9L!QBlJ9co|`#7cQj(hk(^l4;87j5=QUXYLx zcz9O5bEg7ZL>ld5{>Bnf=-WwW$Qi+B0-70Yzi7_lYLL@5ZErVn?%VAL_qI6}Mz2EX z2$cd1u0%-%DHmy0(*5;e+39qF>>cj7ErK5Yu#i0f&^TSay=g!6V}XNofK3Sx@7?cM z90gw$rFd20$NLwd!9CFYaPn#QQgAZ5o{Y-jE?i~DEzKcPyDFJ>sv}CB&CR1X>Rm`+ z%qo7V%!wblmP{Tv)w=-xX>7>yy?h3L-$#w6D!N#XH^z&yI>ZmYj!F8& z-|Zp2<8l0rzK4+m1tC^~eH_La7%Pl^8f~T?Pd#N_+c3-Bq81>0N*)K8EQT$H7R&TU z0OSiDSff}M4p5*s-*OM}Y1ryVrlaX;F+EzE5+1?9t1uRaPmasj-=6gN)7mx;oi&Zc zqX-<7rDde03;tJ4a9A)$Tv(QNmewBYyn1M+5P6@N+{ysVOndMDUTB~E%*|*XgYqT| z6h0_c9;zD!P5GljVtgOzQ6Zb>g{!%8gMOgl06Uoh37HiKUqxYi#DBYTVxt}3uUne#cXj?J8$hH z+R^I|wh#Tc_p#yRr`xx_==<8QzvEr)&V5HYAqM4MC6$?;3Hup3=tKMVJChUQS6z{@ibQaXag*bF&dq z$u`fOsxy?SYvO7|gu2O^Z97gkm$+`udp#|HNE7%7=z~1g`>}AS^&R~>I4q|WrUh!* z#2z9H?#MmdJ!sq}!ZH3}9dJ~-hFh(s!0mml9^icx+l%XK_JN(>*17B|ry(eujZ=tX zlR|9$9?oOb6*&6|(xfgfAg3H%bF?iZZa}FtsW3^TEKz=`__&_&%B!wuzw*m(YVT(Q z!;YP2w%tsZ|EG8Uhj#I!pU|H7_g~O{_1Au>-F)+P+=7BkA+{r=hYs8qMd|ztE^c4{ zb>GxZXB2M-@?U2DFvCbH`BK8KgwH0OiXbVbd`~5$HNbB+JT!tj*=(&;mIBHM!D|Ps@HNU z_J$Z`i;N_}F`&f?QiJg;{C-JWAqca+kn0W}ZBpYESgry*b6uU?DcJ>GbjU18WfLf;+OsRYCOD19qK zVF+&A$5PR;uh>0YJB=_EU?seU#8=*RSx#2hgxR>Hg@0>>-A6|8EWLpTDqEeB)Er** zFF>wgXJAquiblPc~7-1pj_Sj}78Ml`Cf?uXPdpvDm8T}H@ zHC!qOH{QhAsR!Bq$KvsPaWU|ahAmT6-ozm-{)kq$!a!xu$2DBbcebZLm1~Z`dzAGk z*_8$VQ8QAUsB`J36q0Hei>{dik85(4I0J2BX$|jd+OEBe?Z5x-)!^G%yy!(QEvD0hE_^n+VC^;&W4p|p1ssQeL2fW8dJd10IX}If zPV%d`W41Dh)$u2hljz5aG0XLB);-GVZC8J&z4yq6+TCnYxt(dW-A4|#x%rJOaNo{M zFxyGkm@q{rw~9Nr=TJ=S=(eyPYHscj=Zf9d-u$LFv|oA4FSYgSH?(*B`kUM5KK|kM znjd;ayO*iAXa3!9X+QgKeuhz-JKG=p{vWpYy!*G?)De!;Il?hId^D`0^PZcpX<64@ zPO>#R3(qsv1$mBgO6J35=D_pFptx21<$Vv(+1=PWtqMHGiPDE%a@!9F0|XGqU;@hz z5kr^tl=`O3%W)_mD)~n{aiohnlMgwgq+(H7Jj$lWA8;Fde*;eNRb32%5r|}I%D1!% z-_=bVjZ^ffRMs>h;qY2mXAk7D{%hB7YicM)PTUht9W%!2a{Xz?+j z{;+`JKaWlnU81KkL^XyeV#mQx3R%(SYj%;Y%tC*ds6x7T)T7T>=s7fcD36Ma|oXj4cK{xYA2B<7D_7L5WiAC(XZHh zy100a&tMy)Xk4MSupAq&{5l@(6B}b`%C^{6-r^klsRAxo`K+Fi;o|USC5>RQL16lD za=q|Q99c8UR}V1Hm9esIwUzUN7zma7Np7Gtfe-X!9Dq$z@SZ!t#o-u;L$ZD`XbYxE zrzE`by99w8J7WLKNS}{J|AAiX(2)00un+@iw-asTPapxAsvpPS(Z}vD=Azt^mg|h7 zMR$y$CiPU>t?Zg2N!ltrWl8+*Zx+nA{1xxW==LczBk!+>qbn97?=+03-QA%DU==H- z%1%3FJpx0auFhp3{^G*Qig)AX9f!QiIys~$PQSn%-oN@PQEg%5LjjKIY3YzjnXc3m zV-i*taq8C0A8Z?D?rj&H?_?=lWHhRFZHkYhaE(urMwyg`2yMU6UBARs7#+$I*F-7A zFZ~qPVqL`-^4doehcsCqm!TvRH9e6?ijRe*;;ItoL32*+Y7CP`;ZvZ(jPXSPHl;iunDGNr#P_qD2>B$uL-97R6rM# z9~WmMY)R83AMpq_UaZ+gI@sZ#>7kw2gXOFYES)j%N5NnPm1YgoU3c7ZPdjvEM_cB= z;bpEllV)WwkQhf&=%CGi4qc`#1Cn;67bT&xxdDP^8KE6ZEU{0DAIr)7YD>I#V#|{? ztJ`PpzP^3tz8iR1%Ly^-IFOlLhpb7QbIOZ>19wkaHQZ&on#<1*-*<0&(l>oud)n7N zwf(D~_<{BXI`mh(?B(s3|NU>#Pc5{!{L0U^>#q82JN5K)+K>Izzi2=4x*u!TT=~WJ z${&1fd%^GiQTyS4`f_$*-_#Br+y_#)$JE<2(``q&8DNT|Y1A*5`7p_s4fV9-oPA+LQI0kzFwupKjo^hwwNfvxZKp_ zx;WQ-O|q8Ndu*fR0IZU=NExrBI7^h}x z^(dQUq|>#c-fWWGk#W<|Ew$Rp#wcg|8mvJ1D)&B}{NwGS^ZqzGu4C=u3x1Mjq#-}r3Rvw_hQT)D{`EqMd+?JdLulEFQ}K1!#q`y*W`8zKv4S(l9)d>QqNUGF8#gep2#rd%srDiaSZg{Rwu%Zu)>8-U6a z_;Ax+pu5}&kmw*0>aW3=R!=JF4rzu+e)Q``Ki`q!Th$ZfDVq9dr>TmGnMz^zNS3BX z?v*?D6e3*iVq@lEuB$5L0!_yLUJx5&(pUMh2h(o|bNbQUYi_1Mn4YqI7FzfaNcnn* zh+mLS^V!*2kNtwI^0CfHnp0Oni3hr9W@6v>o&y{q@(7eMI++cO3anx`rz1!lF%n}0 zl`s2JrfD;+ssiJVRF-XIT9oB(89`K8jX;5RK;viLG5`TSc8x|6=3Z0}{aw9wQ@i1s z1MN%KF`Wz(=q9RjL6D_A7DD`ZDnc+h7$yKK@z`LIMLC4ET*z`I-23X?+-xt`h zvb-998!zU_k^Sv!pZ=J({?+S$q`mYXeQVpcIWhy9C> z{8{_+5B_o6b?f!*CtveDjFev5zT>6e13Q=7#gBPn+i~W(bn;B8!c#XlEV^FK@l!bJRzfKiLaV4G`qoIj`EmuN|Psx^vT1dOA+AtJeS?iFv{lCxO{gB*Y($3)$Y3U z4vyAY)7Guu+@ATY=d@q@wSO1-p8HMDX}|ibKi_V<^`^ECh5xE6xht1Lg12qj(%$j* zx3oG}l(l}qtGotDy_;{> z9UtZxBp=romVQvY3_nB$4q^v&X0l9XNT)g?-+?w!(;Lb{d7X_R609H^sKPBFMkia|#OU5K(8?LZL=I<(C!j zDckXcdV(VxAj>y~bWIa?>Y}!kh{qrCXkm&S!VVsK_RNR=0nc%1cj&Tv?CvX)2Qh$2 zPsc2zEL?dvtb99b^RYtyQs#P^R5Hg)lEn_N$i-AR!Uy67e85`n?w5py@BtNY3iZyr z47>NZ@K3?7Qb*k=8x(0;BJL@}|Kulr1Hps4@)%Fl5l8^4eh>IlY2-)H1V@i+{u4_y;0R1lR$Kgk z9mZrNUQ^i}+z2p7mh=;b>AVz_eF@V58#gU-7iTS;b-h{f!pf42S-6GANb(FMx%<^z zJ90$+?!0%Y9X`r>GljcKgm8|28P#|Dh!Gy#8pB*^F<+S##(nm{Ofh=1(C&M7Yo~Bc*Rsl+4cAdlY|&1U z#-wW#(;=?G4;TVm5G~yv5_-lC2K3lGuwNQ9^3RbEcoP%e>hBxz21PJy;dn)JX$KC> zw}TK>^_>Y_6y{lEt~7bY*9>V+Fumt(Ca!y;f{j1>X2Bw)O+g-PR z95^*-`i7(2J3Pe>(Pfta@R0-Wou$cnoXCjmgUtLRc}S-QSPVqKCqB~Oa z9?3?ADUKi6yYF76UiU%aRD0y3E@_W>%;Ve7|MDB#!TauRuYS$z+nHyc*$%M0>$1x} z$@;qg)sCQKC121A*>WthO2VvDF8lCYHh1dB;4Z~jE>)f?U|Exfmu%1@g&KG%kfAVfDZ{Bem7xeFTrd6-6mWV~q)tQm`G zE_+&G*>Ga|)bWFE>>F(J4lp@ppK_rZP3BM}5 z$4NCcdHzHsN6S<^eKhqez`$F`wVyYV`76eVqc8|>*-1-q3#pp`C!vymqlqtYL5)v0 zosRx)eACxoyZhq+huo`1qDvYC08VJ11X}EN#OmYWkJ9KZ`w0TWi?{o+mm_ixxQu=fn3JcpXS0O1l{P3HM z64Mn7>p5TDOc`E%OXKiwXmGR*q1O%}&G}D(Vuk+X!IyCZZ0B1K_g&+A5A!|#z^Agq z+YaxDq6S^(#;fn+&dpQX4A)_0sfbx&bk8+6lk_l1COqIfJ5)w_%$X$Q2KLr*Ks1f4uTve9R#vN^v`w>NDpxVCRC{5(Y6 z0SJY`x|LmiRH%8B63bx~qjC#$=#%a&WklyUe*UN1v!44M?MY91dRx!-?0Xnkc-yc3 zI%@&XXfOYs?`Jc^ySZof@3p5r{kiSqANweW1)tVl^rCNXul?bF!l=nXreg1E>!^Yg z>>i$G7xiIA@^Fwo?ThKR2|lKJUlO@8dZA+3@!3wgv99^r>z{dz>5gu9e{2g|Z*hS; z_wSAcpV(=?&=U#+gGWE*LVY-p2N}#pp!HkNZ?oGkY=`f@m(7MV?cn}<;Io*?^UPXq z@3@4+a*tB?u4WXJdN;8zvLYWdo}e9^WGWveMuRtIJs7Z4521;UOLjPg7$v+nWa6V5 zg)Kk`z~ktWYhkNzcfr0(D>T^{z`OR6n~SLbPOV!{lD!aANydt@cfJ0cf8=++Dl&gLX^CH?T!ET-(_U=-M|0NOp}|R zD3sNCg=a25u&qmhGYJ}NT-saSEL2HP?*R+szp||up^7EFf#Sy$!XdI--IkvCTE-#* zBD~;Dc7Pd{D5Jwb-oR_oDo)Z_;dV~ih&GB^Kqx}I2BXYzSXDpedCp~_C-#eE6~g7T z*-dTd=2O~UPF22{gL|E;^(-ypxlTlZCs9@+vnQhndT^8c3uU5-os7zW>Szc5f!T$u z<25g)&wCOoyWBzZfN6vEp=mQVfL(+3_4xeAtE5N3$X(F_o1F2pLcS5igwd1#@`xii zRE`W*~^ z$M~sHppdqrvF9y`7GR|lkFVg5yFI>7Mo~RNez$E$_-DN# zaMEnr1K;B@PD^-A;xa@dy{3z_zVCoM)r-+gcENg1R`_N;ciM|E*&5lad{yAn!PzG5 z9Ax866rk}0iF7g;iPv@ZF%n> z{8szGdw-83Bvxn5l(qUTZ~xEj*4u7uKlYj*=bW)K+Sh#DH@C-p^%L9Py}Q~!`_b31 z@ck34C7ogO!jZO#X}4wW+Pn`MzVUf4VXE$V?SK5i@3l`}`jONF2@W0ObPrTlCG9Ug zL)d}!{UOXCAJd?X)ZZT@nl*&%V|se!E*iyQ5Fu z&xQwks58JYt-5LRSGA4o;M}wKvu$?G`EC8CX_i=g5@pJdJir)WS>%`w&&YEP+kzh? zn6v|B59#=UY)y{tfo*@1o##<e?`UiO`@ z$cWr8{`?!;X*TtnsxZC_DR(je znkW^|G7a6n3PAQEM#P*1;D@qTV}6BmU<*H;Y!uV7u_u)v)2Y9H-3BO_CD`ZI|D6Roiu7AK#>Wp^~6*83{nLSf=A0yeJ>g6j|mQBNoky9lV8o z%s)VX>7Sm;Lm~n&u>2fD?htWA`Vc-MpYViX+a~49Nf9^S9vD+m4zZuN+8tJy1RG(;x%YQ_6Qx0xDgEUil@rR-BJv* zDP@$C!VE%W#Qcu0(-299r?!5;Z{EszX}N|9y!@F&EuuDEYK}>!6601lLA!pXZ0EJ@ zMuA)8`m7)wt0zS_-qL1z-Hf~SGu)>e!+V_L!#Ye4_g#2yf!MouUlv+VOfcdCIo6GQ zgpiLZ@=%MC$pim>%(=E`nodQ2x^_mTVjEh+WLMyN+TpuQ!M~FwhS9r(De#s0%HH%O6syOi6q?{VX?`SFBW;kJ%*zWqdU>fW z3{PZMDwXeekavS!OErmNBF_(V-q)ISn^Byn*j2rc=`t6)PquZefBKX6zOP-uj>Kse zuHVi&sz3alx3&*(ZlN4Kc=w*Rmg~8W&U57!{9MhZkHzgYukVR?RU0o zue>6gAtu_pPkB0ZYO39R8wwmqR&P9`t=@WWyY1$y81THWops?i zx81jXk*@;|w06Xwj|Z=4%XpMisF4$XEZ7D>U_A^kcn`_e(tNt{D-52LTl5PdiDJ0# zlAVuj%OB&V^NwIyu8!!duuXG}OGF4|lru31OTR?eey-s9*vJ1g<^9xfh9&3 zU-#OdY9IZx54N}ehhJ^4{HH(Ke&+RWZa@EvZ)jg)ck4P12#-=k`n}&6(I&kix8zwE z1YM<)6^+uD{H7hsa$)h|Jx!07@CCmf-*QQL#g89lE@q{;O;lbDP};QRYVo-F*mb9W zWjwF~7n1s|mmt8Ig1`=82r7jF?WS#;-PkVLdR9BgE}-k~zNH;xeTJiWtI3pw5-K4i zhvdoiQH>f;>u=U=zO66E=KuaQbr|hr4cjvFRnzUfbYA%~27@zj7I`Y69^>X1Boz=4 z{5xnmK!du+c>FPUH7L_FatH3VWpBNqQmE0>2-2Yr@g>7}mT}?_{wiXKH8fBn3F5it+ zew`+LPcqZ9B3YfxFn~2ejIQYAs~uV@<_L)Q6PbajqnJIi@ij`*C|UJ7`t50t7aR45 zPx4$PRm2rmlc=v*`AY)4r$1K4CmrC#7a?eEoXUwF9)^n`0VhxIz()k&=roM`J&?P@ zcKdo89jkUuyeddF07c?H%l|>371QvNB3+ULYsik}KgPHu<+8t34 z5AF<|4NMNtgPtG!3^Tt*(IoHeGIhOid4S)z!r>FaEaKj!7}3!={3L|eD4oVITF)QO zlg3EXD8EwG^M#Yq${BdzRN8`kPCXgIlOIPt%zMuzarl@yf)cjOD2@lWy1Q}tDEHj5 zL*@_v;9U$hFfGe8=rr-Bw{FO#;+~N_4!-{rHc6Upw`Ti`v`X_S@|f zfAQfQizNf6W|&fUuFdwY$*jVX-Zb2MRj|aOFjwSjLG+S~?%p2p~=Ly*Nkh z+Cs;}5pa2V=dSD8k;81Nnc3Lp_IwH69BiAy?FcRZ0P zeC{Q_=Q`k7Lm*9T?3ic6lB3OBewESD;)Sh&gefD7wAQ%-gO|r}7*xd!jV#QReSsrD zTK!P5mgg$0fU5ve=Ewotm#a8NM`!Tvh+O(C(_=lTZE}W@wxirGVc1Q#UdMBN=s0v} zu6^sbeS5q8jyu|?So`+8zyJJp?s*rseH>FFj?z{JY9!eIfgbxtD8Q@o<*hMg?!0erTj2EdN!DbrwlIZ~($gh!(vc}&mNCx2 zfa+j_=7CX|=Lbk)#A`rwcTy|_>C^7+p1}Bfhe=*YBgya+yvL;()(?Ime?ZuaM7z63 z^d5&}M8ha9d?b?9$MRZ1&>_cc!jngUD*YZ|Jds`=E4CPtAL9jGHL7u@R`^(2IK`T; zAY9=Sv2Bz^WKR>jM-EYgzX`$ro)T{{mXBB6*irV-jx%L2P-xtaw%`v8N(t^edI7SOQZRGn6y?&%Q+&XmnXiGR^ebF zJdiOlVch88A-=@-!%lCb8}oj#d!XPkwAo>-T2-Bg)6bKpqcCSgqRT`XBpN#+vy)xF?pO_#RUT1( zqWl4HG8zfD;*1w|DuQ0*PX5JE4z_vW2)C*)Gg1?|$Ow_s%r5y^;Bf;@uE%0=`$4XH zn`FAo!3NJjbCZc@=BX6G)x?>j^FMv(Iqe_3=)2oLd)-^we}C6+B5YX>?>a1Km}Ru4 z4)}$TPFrXS5(X4}XHhnuf`AIh8e2ctqQqQp&} z!Zb67EKls`>^;xjVLcl4F}arK_^7<)XdCKVEj6lgs%%)60^v8nrfJ}pKE$xWjC0FA zPyOT&2OE08?lfOVDt)rs>S4Rc(IT|j9Gc4#0u}gGtRH^ce|$^(#=rMmM&Dk8-*o$< zKmMQX^Pm4T@oY6FNJ`Qy{Xsa;2JiGUU2TsD5?=h(2k|y4Z$Iy-PY{~hBxtupSuwb6 zgDRvWQ|O8W-K67SSISN>Gu@GB>t~%ngk%a8crq@GDGd!nr*9&}^!O6G6i!XD_N}$k zHg0Zbb7?>R*Con#G+8#ZcGz%SBa4V=>f6sStzfVvVm!EaGR0BH(a zm7r*mE@`r}aLfLR!81h5aPsARF@NSw+Iu|V1GW#Uz|pZB5x!A{$^QkF$mK<3D9?B$F7A1K~Dz>FkY66q0W-hrhV9 zGsXbT7!Id5qhJjT=yX~2t3e8^p*$#e>zSZ-)*(Znm{1@U1O6hi^Lqd)x+(~F@nRH5 z0W5C*3b*BsOTQ#Ti0{0lfec8C0%KL$J$~v;(l8FdX}H+B@%kGzrtf3pP2sK(H6m@~ zcxXzig^{&h9^zCdalLc!tl+;NHp88IWh0You3W&6{7fA^Aqk3u?E_u|;TI7ojwa6lVJon8zT$Q|ZG$SJ&rm0sNoeghjNrs6R2CQ){KgQba zF&8EFEV&vRP;H64ktQfZ{Ulu}sL=&?qN3w~uV*mmPM)96hvb63pd=o0HX7w7NcD&Vzj`qM{jd34xEa2}oNa17VwW#LR?& zQW@alslJe^B54}-E#e4~v_W~|rZ&D#$?H;vhEdc{{F16~PX%Qv&`V@5SWsBJ>n zk^(cl>MH_?*ZUdgn%%5mL3q)v1c+GLlu+wfqjJ{j&28((t?i1NuWYv**bOrvj<0cd zO1Wt+mx4!NkcDIf;&?9x+%L&vJd%z+W^^cVV-8-iGc6le{~_FrL$d`cbUI?*wWYtz z2zy>r*~}YoKF6ExS3yGo4e6L3xgDYmb^u9Fe0X(Rpg>57VUk-;LQJPwS2Nf;my$Oc0^~v78JFmE9h4 zGSajCQXA?-B8Hxdmt;D!AxS$Xjp>Rz4I8^?ATq6@a$)VDvjVQ_Nf3u7d8{#hh1Glf z;*?_RrgT(#+8(c=;1A=1gs=);LGU-_N~SwK<8>Q?iXmS%W%6K@Un`4vl$xnHCqUQJ zv9alvM@p#&GX_T%>QHOZPGq7Qpc15yxCXrPT#&yC-ixOjlQVhocH3~t;$w8{*7cn9 z&RvSu*VKzn2;qa#$B{TY0XNvX19HZ@CaLEtvUlA@_#)SK1wR6Ni4hFb=yZr+9IgB_ ztqx;6rnXiOBXCjPXwcTxPQG1=#U$gRP||TIuWdUyEg%W7F+@e%i|kK!)AL@Ls?ij3 zYk%ZXE;-lEx`8t1F%lW|CGX1NC@RVX6+d|3Sz_^nXSL9tkvHqB%(HD%Zn=Jo8y@U5 zcAR}7XN7HRKl{&L15Z(a*?D@zqilw9iL5ui2rRdUWy}!3=?rkEEaI`t%kdiRTe?_2Mxo!Itgt7WM_6Kuev@&F`E^cYZ6g(O1-8#1s59MDIjh zm?Qtp6hx#kqAEsWL6%CSZqme#BAPd_Lu+=RC%gtMin0Rm_>sDwR33}VtW=pTxKq|t zh&#*3uV>mhJ#6i8%VT=-!=>lR$aS~=gAp7)l8V1ZM+=^%XL@C;J^8^j?`Omw*@#p>F8)AUS@*L;K zF)t?2jc)k}e`Ha16OF=g9OGU=+Ldz~L`K;ct#^k5y(%*_Mje3hwGe9QC zX^4qmQRRYg*$gqzQ$G62Wo_xu0@HD2o%9Gw9SWVM1|lmiBgQEw->vxK5A(1MI2nvI zg#p;mpL}+@(gym=mtlujLw-jr)bjx{9e9HYkK1-h%}(8Uh;-$-^}>7%Qpx_*nb>MR zLz?0errU!5WlYbG_{L4oi{>v{_UD5c9Wbv$Tk&Jb z<{&-kqDd?3jEEA!&t?b9!gf<@aLCd@MgUJ%1x`L#Nz-K;&HbKk_}H!~QdJG<}4$oe=CePvIuN0J_u~a~Q zC9>kBzS;LGkNHEb3CbFkExs(fG=Ld1W!}=!d)k4$_mKb_IgT7{hxc8I`(`%q%yQJE zL{GKNJ1^#lu;q5c)$ga7EVQ*7&tr$`g>BD`m!s@Z2AtQYqTN_S>TFx43;mKYITRH~ zP~B1L$rPQTH9%8u(KQH(fp6wp6WJWwKGjbq^JPeCL&S4-D&y(hr>3FIpHhM6&kO7) zr2~`EFz~8at<(O5ABSNO!o z1J|>y_C^n`X3Omptnm$Rk|v|&eE3BkLVwn{D=&LU{Na{kdK{zmtmf*mkUKL8nb?=N&(ATHtyN(c=j3f}vOCygcAf;_{>2 z@t9Uv*F6$q%J(;d)Rze58U_q-<8BcaU=SoP{M33Tf`m1i0n{69Un}3ZhH@;Q0lrs` z0WOI7p^2nUg*S$YI(!NN!CUpZgj6~>a0w#BiYfRJB_axdbmJ?n#Ibx55QPqBr?sLd z^rZNa9!Zw5-E>Lc-yg0mj-FrfiYJI4Ak%>drV2yg;^Bj)FK5$b0xn+PD}u5jDS=m% zXhaqC0LcUKUA6 zrp}pH@NSCfUp}jY$~@e4Co@oo8FdS2gA;Tk(e;Y6b%xnN&OKJMe1sn#~~oP@^ASPt6tQy%y^%3ptmRR^4o zmpo6=c{+7vj_3U{#zgM;6=7W-;T;@N=!D||MqOi(wQ!nMbgGBw{109GrM70Y{ zTI9-zu1cf#JJR)#rpHaP{ybD1X&YcH#F#-r#hq%4zqVl2YO4AgD++wtZ6FidAN%A9 z@>~>Hf2uya#xjR`JI&8AA*d{E-`!UbVHYO~p;(}lE$8~UbKANtk7|4G_#D`70*c+) zYyJ-WW`vG^G+wT2+t7~O$JFeqwK<#bFe7}9-mPLfdw%HoDHs(p4#-p|irP^WHFs#vWF!uC)elT`8S6~1N4jlZ-B_OalW*cD zz>kI+(Z(w@5fFZt@@CzW-$wAy_}yNEvgRStt56{48TlqgK5Rn^h1Lg|BFqU! zB!!FeYlX8eo93W{DSvoF9FvB{N_cjmmUj{1ug3T}&jU3 zn>7PBbAHYo1@Er^MHD?QreKRPRmc`a?lO7eY>Zw$IeCNTU+HD-uX zFT?Ns;{hDxez92@036~U6H)J>k{s_p{$3qA3>$;(?%)SFUks#~N;hOo7^_NPM}&-F z){n31R8|QbI<8J@+HTJ}!PeDksD;CdQW-lDS2_%|5u9160W3OfT;;sz-c3cJJt$O1D=z_S|Rzbj-X@sCEELW9Glh{ zgYVxaj|HVh4j$OHD?8cX_uUd0E0+#y@}2+3qx*8-#iexaS<)>D_Tjn}nDckEi!RvM z9{p8YQm>GL)FBih!k3s%TUJQ}K^=h16dp=alqTfhlDq3b=o%N)ws!3`t?fU=!Kutj zR9+L6AB^da8}pcoJrI|#@FQ*AhPCXjGYw^i(=D!Vv+Wt#cIKKNyzx^`ORK(7aw5Z3 zI2%Wiu;5&waIzXn8!zXfUMXTiwF2GFIHNa!qJ3p;67NpmWz+=(uKlYG8$=B3kmvAs zEb}V6BG2jmOmQ8&4+Ugho7}dY!+hrmQ;rQZMb244%EKdMQdYd{^PmQRQaH><)$oC$ z2Z<(}rN~44*kjOAV)YrvW3AL*JAEy70f0avwPc?_F{vfE3`AK`!C6-6m_*syO#+N8 zPcRDU$Qo1s8#X_Z^9?t&L-V`gx7W;V;;z`U+TFXpME%~_wsJ`F`c2#0UAu11dcwnp zP~MnoZCq_O;cj3Q6*o;RFCK}05uJd(&^*{BYP#~4mtOw72qj!W>mWir_(@!U$XwbC zMM~XJ0n0gc06IHpyA)U)E{_R^!DBh6T!62cN2fJW?u}23Qh7};34yad98};mPMuao z@vZR9&CS6_XCs)_&2+I#8lravk8xdYv53rY^xn}!*W)>&XB~^dTRUZ4WDhhIrnW|9@0=&JOAM* z90@NdF%%}^rliuTLO@U{d5kkKPx}~?SAMkP4wiuE(re&l{6+x@s|F=&J4~UWxU4MD z6a`~o^a_F#yT&`DMe>#Nwxn13{j!CT=1Ji?unC@4z5Tu-)|Aqs^-H>TZ85|3XBt2vpcW`7XkQYg?vWSp;pdosn zAE+R3flRtPY}2Mq?ZQoGa8}cNyLR8Mc5s0qSGQ%cf_H)|M-+6fd-H~d+{gpY5D>h$ z5hR^|mLEx(e;(qE0(iHe>~PH#ij#|1Wmvp~PooC;VDbuV;^61GF`dPRC7qO;g~dCF zOyi+UXdi&!YmEE}A60UoNkti}@D|CD7p7SWSGbW2wDKVm`K(+iIGbGINtnPPk)PPY z@nHIEz4>c^zSx3e*&nDsi8FpZ%y-Rz#EN63(~iN43&tq`^&UVIEbeYr+~!Fr@T8-% z{DktSB8P8_T~q7{2Xzk6QC1!@yjY`x{n4t57MB`kg~-# z4Zkv?HpVV8ktsR)9Ct1+A*7e6P+7V|RFAvLB6OxA^0_EF;)H*oo8u1DnYA0)tw=fB z|1b(NKabL$^hC~o;*5v`0d5PNGzS6m;H;GHssNQZ_#WEy^lT5(Ub1c!&tUMRTH(~z z*cYjgX+|Fc3yC0sx{Nl2;)afJ zgb}@k>@sI)D7akOS`q@vat3+oBO#`lh&$;&8ws!fhtK06w_i#U&iXDse!~ii4 z6{|R8>TW=f8%r*kvDO*6|&$d6M^Eb$ce; z?mGog`jUw0`VbtWtny_TrH-Gx5yl^F)@1EGbEd6ZLtPDj>}WjB!8LC34|vxu9c98+ zne8=O${b|h6!P2pKgGxO?mHQQn|D%o5N0NEG8tK6afdfU-s3^Npfj=mK>X5PmBA{_ z^3A-P32m9hgWnPhvK*ANlU0FpjhuiUJ5i%`!r()kSq}A`V&oMKFWtE56Tl0&{$p%$ACL#OnyFSwA2IAA??U3fR9ayH|ZJ_i721I$c{)w!UEsx zQ|Jr>t6n7(%n{1qsaRx71q}>4O+7uv!- z?Y_INW_RlKA#lq%7ZRU)m9M-E9&s}Q)2QcOaABL71;_q5rlz5adsrFh%g0CRcyef7 zio@k+-pv7g3%_}{y_n}@j3C3hTN^lgpA0$}3~$7tk`y@c7@h=-z+)Oo2f33Z3no+W zL=)8Ub?us#H@tc%F$6buH_9{XTs9?8VW04XuW66{>Py<2f8k%Z9jBhwp8taHXm{TA z4ef&;dSAQ##;a2wH*8qXdO3Ee(xz3|vbkZPQJSo$Gn`!~<<`P{kT7EBm|0cM0Gk~( zPiQ4WNBOdx@(UDUw6vNAz=Fda%Ujwd+b?Q+rVg|#@3|J)!hAXK8KI_Px_QAxyl%fX zbCE691m#!oN)7%pCcK3lC{&^9+IJY?J4|qzOChuXrOLlWg+6Up#nMVfR-xQEWAUS9 zd(pI8oe`v~AZD6Hmf~?2IAfP)kl|bM#q094o+_KJ9|1p&Rel&|X~_$XQ>KZ?uZ&as z4!s5@U2JK!63A`MU}?((U$wi5UtlbbG2mhn$(e?cO}S`Ee*u04c9diTgH*)g=4~D5Nx%&-Vv?8gMWwxmNB)6C+h|!+B8@&0N0}1?%$5-y85L%l%{E@A zZne({BcB=U{lQHq%k+r(i1IAL5Q81;CakAVH{!toOj>iEl{SG3>HgUTA{%hKW z7p`wV@&o6$4Qsrlp3aOTKpY{o&HHhcx{1Ba)k@NdN}44Dgzss7@wRSu479!^+w< z3age5Gm6OcHAK7K&}BSF83oOFD12~@(LAF@D|MsF-!fxNE{e6CpjT^osIQ&wJn}-h zFSnPF1^#(CI)N!y%ggGae6X0Tsfw8rK*}d;u60;R^}t3L`;{mbfhSuj{8G%sqI`-m z0p;1ql)ovQI34*>A==%Duc#B3{vD_I3#w$d^;rmUXd4s8*XRds_(5A*SZ4Z@UDwLc zD0owA+nNn)+QQ+_w>`Iiye-ecDW>PAH=otcIp>M(nlFCHj+Wt1;MVV8!~lIjg1?Qk z=8mjlZQ&Ezmd)pKknT*oYxn0Eabkh!{2q2^u1Q^;)vvV+002M$NklhulzjOu;A9 z<+T_pZ+zFpHp9LEZ!`fhDHr^$K(6^H;gk@<2He0-(r_1;;&AQ5go&|5cP))^Efs!Z3q~uNf|ImHbQmN-*s+Gr-Nl7|<;w5SI4FL?Rgtlq>N42^}zR?xzoR0%7-8!gIP$SWjKf}q=G7B}8K{s`-cRuL4O35dq@JKuYQ z7qfI!3cL)aXTkC#NmC45$N{ECF!3j1#Wuw9!zP0G3btdes%>YNvj7v=q2vl~kU3&& zk)WHi0cJ!+2zPO|=&5Eg<|QM#9So1^{89FCST=%|vPZo(3H%+vn4$2f)kQ0r?232j z4`8Jcy5dab5E~DO{?_~@IbcDYuVv(3g1x=8gX!2e%A9;TG`%>ZXe6tqA*UL zAjcq3JD!z zXdwYYAV^07_`oM(6oLXq{p$nN=L12Ch$x^SARz&gM{gm$F=>;`Os4nA^qD)im-qdy zea@YmfIjvA2=VjDzW4mjIs5Fg_S$Q&z4qE`?;X=89*B3m?V@~ z4@!C`UHPNQyTg-ibd)gM8{=pWUWZsoHcBU?{mOMWY=}d9$qTtD;Wk3uA=PD3*z{TF zT|TDh;2D#)1NPaRJq@9Sa^?!PHU$uZkiC#N7%65i#n15 zWDr<5!}Brgs&XYl1(QSQDKs>~%0lR&1=#!1?xcY?n#toPPm4oG4zdP;?hGUOJmN^Y zsp_V_kDz(WMIY3o+m0xdwQ;wOa&I{5K!G@?XS1SBLIf94NS>5VTGMeWUUESH{&iRL z&)n>pV5V4jC25deW>=*vr%dpxwiTA5BAE?>sN>3aSg zIdCe1uh3Gm?09J(w*N$q4;i!);AkU+NjA>+8Y791@{-}J59PT|z_hIl+e|v1Gamsl zvfsH$D;c!LSsl6v;;E2gy4Dr2uX*L`V!`}*F>BWBSiEFe?AfzBj`kmo>u>l0`#Q3v zh~*?L5GM;|P(p0}8zEE?)kI)z$E+s@ zbu!XH1hW=Kf-^Z@WB!~4>?}SMn|E(U73y+h$&riBv1mkuhFqvr1q=(GyJMS{&o+p> z`^62mZN}UoL0}?##Xma`o;va?X@QKboj;~>rObg%JRmm9!nA)+i^jiZ` zT@%{Yb_{77J?4cD)lN!{s#=|)MmLnCGgNwq4hETG%Jo2&1^6sCPdxjOFM8mzWZY}N z`ko&j|M^d2fI5_*s7H}cRf1tPrq*_G(oj~6#3&8*kAc-N*4Q=t6ua}_(h8(9rpF0l>?9YjuNGfqnY~p)^ld+PorV*V9b~~J7&%7 zilYzR6}<=6#+-R8V&K4@=s%CFQ%^)dv>s6+p@GVQ7KC6g zKucsVS)ZW?Mr1kY#y{%@xbhG1ioLenq~OjHm2y_4A&k7`kyp}Mre`<;q%-~MxvHD= zrruMSs=gVb#-ZOevpU+-7pI^3q&V@E)8YgF^ZlHmdo(V;{EB$-i(kPe(Ni+Uym^b` zlvB{W#+%(`<6gHL!UdeaqC^c~IC6YoBh;!F&1vtyn zf?bBwkAqNNC!^%$6KBU{wh!5OU`y;fx-TVVNx)TEj`u8&szwusu5P?-Pi)ynWDX2Z+Y(pkt6>yY9@7Cr zI^TV^oJ_&e!uZFNMPer$-x*8ic9AX}8;dAxvn_>KiK8CS_99IyY+5w!meFn^FpeUu z<<<@0dgnG&jYHE}4ODu~(#OYy$?>{3zBs;l)t6({gAampjOUqF za(Zb1ele6&Of`XkME(npD5$ncm-8<9WW5tWEsX~4@^!Z!(PqiP+%R=l2JajdmeG3uYv8~Y!+2mc*zJ(p=)mw5ploIU$y zMVp6V18^0hl~?S$YUiURNgCk6qGTbxk9T8n#vb@u$EJMg%};uPCld6MoSaV0TzFS_ z>+8qn4{h6<|J&Q*Ygd0IZn*v@nbU2z-4f@V^Hjn##}pQI{Pty+$CtnOPnh{o3j&w} zf{WwltSja$f zI3C=y8HuttZ4gGB`cS&E5Fjt(;g^~z^2-hqAQAvF=D+ymv+qB9Wg2#{U|T+4PrDW5 z%QV%xQCV$32oa1hLh~@tj^k%hf~ApaEt(0?qE5V%fZ1@xhNiC0_ATgdNVz?C;(|6b1zh24t-Af)Q-o z*q0ve2M2_n9IP3}Gg6TQ=*&nDINWx^{V;dXG5z?4QTAQKlO64|0jX(2&`LNhhti+0 zd)4K#QDgwZ>Ca)|r=HjwGvRgp*djL~e5oyJLCY-fmiUYIUOP`(o(<17hUv&?1DZmn zi(9%HV)H&a)xG4;mJd1l9gi!s{AD}K>LpxiE5(`b?8O!g&Y$$8r^bpCPQo8UdZwA& zQ|##BLwPo*nz+=6$Y{kMx8z>bP0iEts4e?9|B_*u5y9z82GJ(DxIw%zPRtd%P0&nk zxLVhX{Y3(lvHU4%J@S`4f9{|DWSM=CG{v6-XqEP(jpxXbXn@ROa4&~3m|jl|_w0;;zU?vpw3Fhv z&L-N}=GeaaM=`|4((ThvWVU2>?A@@7Zg~PLR%hmJ7Ea|lC9d7O`})}zshw)FE)*Ka zJF>B#w!NC8IIYW)cfik*Ro|tH@mEPILsXCB1C$yq{eTGxj{^W_Ei*GRG<=d!-kZ^y zedqh%jvH?J3De0PO!XfZo40I^E5G`s*tv6OT>6Ip77yNcU)*u)btw=Jps_pmsn3pO zOVCmxTeT7Y!WUl5d9%||cJ_g1ddaK;dsq5G@`7UV6AyeTS~sUqDLM$VaCVqOFuE>m z-UZ&-c<`Hl5-sua*}y!BLLc*Gfp~j zrRp@D8a4Y-(!op3Fa2H$e#I8k#Gx-bxk0X0=(2^3(y*QPv>_oLvRd>Z;AJ|%$g1@? zipgU?_smtG@dq5X;bIEbOns?9s_Cu3g#XwF%21cUuzs?jx+-pYdHnZL!N9nxDRn`v zwO?I7b-g5=@x4*u4I#}bR9zi@fKwKZtcvLa_r;svurU7RZ=W15d}b>q76ceRihuwC zAs{($G>WtKZnEjhPqoKTBP4(zm7PjSG>_0i$Q!5{%`HFeVY>i2w9csz9soFokoF9o zqZ=NQ@GH2ujXB}BENs&>Ywm=2!YQ*;P)scm{@Q`2<{P@sz1~cU=M)ZXPqZ7iL}<1H zBxCoRy7S(F*tgedDFryv_Nwv4<1gI_kFJKxW&ePSG=(707dy6ZjH#2sB?Iw+o+D^5 zCdbpC{UY|(nns7`sTpV>8QhgYqXLYB$0||VyY<4AH7Y;IK_C_DoqoybSB1H>pCYLI z&w*+a0(tT;;uL3^JljvoA-va@@8JGt{G}uO69m3$!lSTl9=iITFMR+1>uP@M{Xg^f zGx>`@;SyYMTvh-VKXF$2w?R%?D6=X#qKR%$+lwiJi+14M3?vRSmD+t^O*A3UG!ApV zTr=7^7WW+9wt+NRq{BXPyLN1i9_rWWas&qAnkjpxVU{q=5pd*!yrH(a;Es=vaFiH) zi&Oh7L)G^}Tvb^T&yRRc{!Wa%8?nlR@>^phgr|+udB#%Drb*4>SHh?T^u&qn+qT5F zzI6?2ny}r(PocL1o3?i7`P^qdI~L7b7*~Gf3*e=N?N$!P6V5n0UjORXBdqnuxzBn| zyyeYrk7qpP>BwJ9lUuRD;M9da zMk<@szV&xU6DreeCeT0F8Y`>GrA7;&2|%N;++{?N+AD=a@`9eT?HJxLjrz%a49ne1 zWMoBTqJ%>V%@w95oPZBUC*?c2V@CY?q9?`NX*1)hRX4|VJ0FY=8v5+XUGap|o*1{Y zso<7Fdmt$g@8(QZJrsL3STXrKR@eSFJ9{`}u1F;uGTp!JEI$sgvl@N1p-R-X;8wq9 zz{kQ*RVHE9)pwtzS>L^TrPGhUs;H%ostuft4?3Ri;)Z= z63`_9&UV_c&-QappR44^&7sZP7o3#kzE7kGyb=-yu+-xaDm-me@_3jm6rb#H zvk}4=i0)%y1P_Bg)AhTM%R&mkK9oFseB$`Thtt zIwPE8HL#a&?Pvoq5*|1hv!)Nm3(r3>UjFjNIOU`cXLQmos@;0%~8XX4bV9T~u+_Dhz6wu`==w%-&7q0N7}awzuigR4xN#S|?7gG)#+fG_M_zP-Fe>-lY-bheD1#BFMw5PNaPn3K4$ow^t;*4q z6Z`eYZ}q0VvGTKZnKNd@%*9LM zl3#mQJm=iAe%izmb@{O=rJ*%vGMpJpRK^ z)_+~re{bYMGeS8YmOA!0nC#3fT=HDPBbemkBihLj^1%ppo6W2|9d^|!@^`g5;cR(SEs`pox|rs1TuvQqqJwX<;SsNR*(>8;zWDiA zyKZ$%nL0IIc;SU{ z1E55hv`{f3f|U+cSZg9Qb*dUkv;}FskriG_r47srxAhLi(#~n|x<%*2>1{LPD;rnE z_x5j$ZH%H9p@JM`I;4fx;vO!Jm~C)O*5#CnJ2Z;|hu2n{zd~)<%vk{k}*;5#naCNR z5R9_i<4<+4qpr5rmT2x9h)$UIiU~7v`gCjGf#~tb5o{gV$%=Gk(C`4-aTA6TBiLm0 zGF?W$OMS9qI1Q@-KZZR3C?H%=fwO1sDjkfVmbB&|gvkp`uVa-N) z+fo77#DK*Xm0#E(Ih8gJsx@m+iK5Dc=kC3$WBTm5#GJ!=ogIY3-#PROW(D4UI*hsV zmd9btqPlmZx$A_sIa4(|0Y^;*-4#E#rB)sZW`V7OL?2x7E6?M8O9kRVeE%tKnj{!Q znn;*TyF3>=N@=PQK}|U5A^DtI+@h7rW}p2b^n#^j&S>u1xg(Y?J|UJbS{6sT568=1 z@+#VQUtE9752J(4x}Wna&xy}{?o;ujAOC=~hGW_C;}e&gHm`>kkz=4i#y zt{_1Kx5O2VQfW!67FvW9=2KVDIP^X2QG`4Gu{vBv+tAK-AuO#`pmfpxNOy0{@0uDH zEId7yvK#dEo7cv-x8D<36xzhoNy&V$?C$a9bdy8RW)>PaoiOB1=i2jlMx4x4 z0iOsBM|)!KKu0`t`l5K&{F9<_QfGXA)lcHKeOsfC<6CUBE~0CNkXsx)u>VjBbkLke z+Y>n6Wvbg!V0*C-;jkaN@qlX?K^nLSq3U__V|1r_y+G~V%UgNQ8}O%h^=YiY^?Ql& z`0pXWOmvzUxgy;F7tl;7-ABcl@rq!07wjloUFry%Bn0R9vhhCMrwW$PZLHHiJs&`4Vx zW-xdN-uc%bdl1tS77Gk9bxL1EXs%-&P3|i0^sb}l@mqQjBv_!z@_v5y$INMm;(`mF z3hlazp4Q>i5kgwhth@$h?U|?Wc-kxGArNe7%knUI-7v=EOy{iK-w^9InJ|cwZoMdH z;*Hy5!dbtVmUSGuhENuxAaCcQ7!SLu`cafqjIbPS7s9m?sr4JRaqwf@aQI zZJ#^`n&#vT76$blJ%p73)3m8kKs=&7loh`wE?C^fYc}c{`26&*|5WuLE{evTpqU)N zmt>1aKJ(+9!e|d?-PJ4UO{OGOBH1prwe*1dE3f?Lc` z8hg>YvIp1m=bRW1_U?_NXxCiiFv@0+?q_N~Hd>$B89a2Nk$qCjl(=xlQnr^Fjc+}) zG44Fv&F^jO9Ua58;vsllT2?!y>(LetdffX>lAjl4w>nFd^9k<-N6>0Hw_+kws zcc*6^+;StiLQ_q2R4%pCPeD(AW|vYs!Sv~g#}CFc&cNmue$|Q~f~uJ$fy<-}24Ob9 z(;5-#()J1-ocmN#rcv6_C{rlP8uvXg5Np;F98;Kd<|iKOd-46{{t8bGQ&ti$t`cS= z)Q|j+-;cY$+Zu0t-Rq-!|9;F0(ahVH5yS?u8G8CNpC51h^|y20WLw%CH8Pm%K6(&C zaNePVA#EO+g38TeEjIWZ1~CJD-QcbgItML``_ijqe}qGseG<1$x9o-P_dnhkYiC^yjW{Q%r>(7UE}K zk`K~8yr-++&q~SX3hpeQ0}nhMlsmP;U9LKmLK_2f2I!urYM3VWk5kJnJR_PO@Z6|v z9Pe&>(+n{Wqrn~+*vU8IxNQvDBg3m{^0HAM?LEkLi1VXs#v=Uou^{aLI}JEEXQj23 zfEzEo33rx7eEMwt{D)i>KI4-H#1?`%{RmY>(|7h`O8_j@RIo82j9=pT0MMkjW@miH)Gwp zHF4{$EQa7LLt~o~?1X z_b?qGfB=`|s|Lf(du59QXtspk@3}eys6}7_pY%_#9y@Fw0PdKv*M=hYvQVH_r`V(d~QXk8$mPJhQxQ$?8PVs z+vDDiTd_&$iAF|S?iB68#c9B5y4b3Cj8@ViWv}P}fGi;2x#uRr!d5wpQJe&V6Mi2o zgf)fBSAMjgQCO-!m#@cu6JJ$$pZ{~t)N5DrsvsLjw5D!2iO?Hx@h(wHXo9Y*i%{RK zSqVRLr~oXfl<@hB$9TUCD^h&OZ{%giDc#^T$g_A#!3(N)^{FQlwV-BSMDKMGyLhkQ z9Lk$^Zi@RitdCRXv66Vv(l`S<$3BGd!?bZW^oGtnXBTFgTZatBME1A(l?e;t;-#m> zJXWRkGfJP2-R6|7_s2~PK6+X_fVqP~2MmP@lgZoHCr(NU$M97cXra<2L(WX}IiD zKpF^hRIP^APKP*Xw2ipJtJreF>!fGsAhpRnyI)fyTh+Lw!ofaH{NSX68&(a+-+l6a z;5dT}l*GtMp5O?@yF!}j*e6L-CYt5pUD)JF3Z`s%(mZ-NE_}h6(Peld8(d9bo-vFN z0hme0mdRE5F7A;7dkDE3f{ym{0YS78q4I$T_hLLw87D9oYkKBc-%b9p;Ksvb!&DgW zq>v2idJk-8G3w0R>#b|*pXXPHAAgrC%b~mK#ZJf>4+_g=s0OM{?_n&l06sA;Q zs&Q%;kk4k#xXSdEQ_R(}TZBB2-tXceuL=eN8@HXacP-})H~oZT$yh+eLebgt7RQt+ z(=t7$N|!G^F%~XZ7@z#Rk486}CR>&jOHYWmzWJ^3p}+cH3_v_pNCJclI9*@>h9?Ys zn&?VNfPLTQbxeyb2Oo;<>_^m(0OrPAE+KAU)ri91Afu$LFbXU3Qan>INU4S-DUJKY z$qZG8I%`nuRk-8KHS1pnMy+M%YNb#ND>03T8GSaQb!mnPPh>U9c`MF{=P!I>tlPFV zzPa(9*gAy$2}gZ7GZtuJX0!6^h1AeLyo)px#fIFf(2__l#KFH6y;WWmVj; zV-1T=dSmzY9_*Fo#IMXcDYow0i(x3nK&<3(8-Y=n`)F@pOa$LAn0G?FXxj0yK^%T9}?olSA$AqF5loOg)kZxH65`v@iMK$ivLZec7-B7=u4obUM8 z&d2(y%3XhY{Qe8Xz<6BOM5kX}7ur5Lm&l6ql-l3}{e632tTSng7+n}A0~em+0KzuU z7@zjxhP{l8ZSSb+v{rFoBi$x#xThL_@`Bv(oVdfDl7P>k8ap;H-M+s$KJfkrV%JU> z^Mtv;0&pIV?%AsCaCx;!H9grN^TNLd%d#MBWh27uW8uOH@uCYRrL5OVLk2S900m4+ z0aM=OHalKPt}s@GP0QgT3)`ehyWN=Qt^BD{9s;F%WT;&f4)c2~xf&ktDry{FAsFWh zla}^4c<4aPn8B1Vg>w6l*-V*!_^+ErIFI3H$zuAXQ^i(&Z{n4WJw-PCOPKF#eu2#q?|PhO8{vgMu7= z2?J2Iaq@aM7B1Y9YNGB_DUu0OAj?FBm_|vPdZ-;~)kT*S2*{NY@~!?co}cQlWAFOs zfBhwkM?U3V;4XPc!{hre(@x$$mWde{Q4iCr-it@&2jQl`WFW%S8Rc-;Z?-5MyxE&c z@MAxzUL;*gJ+>ycenOL)X=FMmcnDzGl)=imqs+RsP)D3jGUNSIeX?_l>eC85f z-&^R1x+Xz`m=oDH@4fHN_|&IA5zCgYh%Fm8#0BTSIPP3|d#u~IhSjU>&@Ni&OMji| z)T?6E1GmH_7r$Nsu8b~I)0Mz7DnWJE#ox)CYt{ziS@Y1Q=w>-^9@5Ot;2O}mq7U#G zTr#?C!>ACaG4P1MRY;Ua6;&cnSvX3Tv}?&~qT-I9grsIn1Z4%9h9&1mNql&id2g&( zo-_BPxMa!MasR&UarJ|@BD@{S-I)y8OZZ7`z}>+P(Ct%VC4#__Bi%6(O<}ic*^Yu- zX{{0N-9$sv)=v>bODeExGD$p%3Xm-yF3xYVQ3aynY2_7o`KGoGrmFBXHg{fywLIB@ z7%h`25Bi&>c~n^^UJ^%ok~pblB=bxfF-`GByzE^3UV@O054gMui_g3yoE7Z(jZ`yi zCQ=e3l5qRknvoETU{mVOa-~g!@K<4on8_Q*n3=R?5-HGCu0X|8I9w@On28A2+!eZG zigXg4<*Acq#reyhz}b3J;GS zjv@daZH%R)aM9%1@sx!p#GSqS;;P+u#ikKPYIKG-WAOUC(G_vwlH+66*4FsOwl%@d zL~KQa&1S0RE68n}S(YpTPdFZhHwSZ8h@R#1!=DZNxckLppvHezjx>o-Vtv$C2O9Pv z44YX1gQ_OAUY%XqKfy4ex8}B9=n+jm_3p63>6@k@79}`iU@~+nGTtGy01YiiV2rFb z<-Afg2V+fzHYsh7zzZ8-914DY{R~FXn0@r4YvY!i&OML#ZuIKMlEtwycXH91ob&+hF* ztLC1Dt(Z0pFg4o4bnp!~{wP_8>2KP)DQ2_4VT7Yh#)U zE!~uvvH$SC*nm*gizKWdFsv{`0rHdsB#svQ-Sh`#aLTMF>RQ8FY7Rz41*-BVo~shw z|Eu7GcMQ{~!%&|-cTv3HgtKDjo&)i<2k(y6?DBjV17f&zR=AbWPTVs(CdVXT{OQ5n z9CN6IPlxRkFDpDUUjs_8RtMQz{?rOu2MTwTQK3%TxZHNaNTn75wP@_Jj6`-{PKnKx zmf|pv5n^DlJUk#7xmYU7nUef@&)ATf#z`R86Lfza(p#1(H%g zQDQ$~qs?Qns(6_x39+FAo9qt8WbpiqxhvwCOHbk~@t(N)zMsU(gS%pIQhWAi^j$Rl zhS-Sx!;`UpoI=U$n_FNOt=;``O3aK`EIc#LoIWGIyLnZ7i9Lq)Fb&o>8AEoA)O*kr z_wQ!sb~@UpOkYeHTH;#>aa#tvW0dLCQPhHETN)sY9?h+dePMfwoG#-{fFHZHaa+&b5HJh;Ps}YUZ z2vaf*EIPn8gnEW2;P1+@GIh!-FN8ZciO|Y4X3&GaU92GdI%qVv5wWpa9^0haF;>qTe#-R2@tRBK zVY55TKu4`ZSGMQm2sFNI{dA1BaTIPOEQP5KNKJM!2jSFqG|E0=e{8R?9bm9Km6`jtx!&?I6dC| z?mvhVPC5yJ^?q8;NDa#b#L^V;0!+i9GDv)Ami8<~^Us2uwdktpJLgjX;od;i|V7^(f`EPzsMJk^SWPUEF1^F2gS@eptk^hUOja3svI6Iofwz~gzSnqMU9$$L}-E$wS`jwTQ?TkYr}CKQgv z(de6LoQ9 z%-4#e6vm9kv(XZh5RvH-waBF6I2#FI=}EeV%xw}BnyHs;lQu31uyVAWgc;UGW20P3 zZJ9}#6m@$Rna79=K(YZDkpRL8-po@WJ`*=jqTAoXCfQqcC}vTo=gmJkp0;pVY(BIj zzP;tXSbL;D1{fLlGBw)3vjC$^2 z)9zz_--_MoKQpT zXJy{|fA1{_#~rLhY^Qtaiu>=nH9qmlPsckaPDdcy8tFKOhwrHy}0(PUyVcT!*$x(=fy8Lib1ufDbWz*(QXHuCQ-|0#4Rp^ppoz-Iyl%a&!^7_lb!U)-) z(w6ciQy?M3mCMx1Gbf*_7I*p|=WYU-VbVwSY#j0|;WOC7e=_-!M+&=?qu|3hr4KXB zl7wj*);s^eSG1DAlK~7*=0>$r3~T3^^-$`f4kr*PsQX}Cct`e!DzA_Y6y~&>G>vAK zwU21RK`@0@PjMK~ObsQ+7r*>D%8&fR!iC#b-Vs}NY>kUpz1liqQvCS(AHugXBgU%O zjHnrDrH5KFZ9&YQI4#!qaF$>1{^(ERGAaci+J;(UM$-fiia#~ZUwn3)gkW|5Ll4Fl z_v)g`IPZWADO6Sn1q9$6a9Vlf1^Sc$pte_fY->+uk zpU$E5FFxZbv4bQ)yu_py9 z*+{7h3D2+!Y(^qyg*jgAGg42@H*E|(6h22ee0M3n-9^b#WEfZ6f%d!zlLTM<{m@EX znB*W#&rKc`h8iHyQ96zWm{{A9!C2ljDPDx;?1cG?#`=a#q`4c#8MbYQVfiz-pzP;D=h8ipng zv=7JE_pXVXW;ff^OrF{jfAZ-K@mC*tkfT-+ zkVe_7L?=5)7=bMto9#yaffKg8>OfhD{FHRnwol@ntU^p=WWO+BDHZQgR) z6amwLVop23D`7|UA#8`CT@kBPvzuhF)lZ(QH;l#|cdvmTJ+08K!keJLlM;^lULg48 z|JL>EUD(nIFuWARI+%6as-})9?0$e&1_48XtD$ppJnQ@mW8M0-?5ovH2lpxr!`tIC zpZ+-f4-+4G5qxTtso3AW{14b+W;DL=*-ynxrl&sf$&bfJKK}PPAQry^gmECJ@a7gI zL-c(N>AG*^W%0Z z?Uc6{_!LgsS@hS3y|H`gX(w0~|CuJV2wyJg7q~McJ_(2YMIo_lS-h3I;3T}>#|SWe ziSFHw(wJsp>8fv*xPG*}89?GNtRM8N?>zpDtI|!O+0cfsp(Gx8CB%HsR1%hwXW9#9 zp4NqMS(Xw%k(gPN{#YD6a0d34-^+8UQ#|QY-Dh%0A(r&iEm;;G6Aoom<0TnDyAvl* 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 z

$Q1qN#6r8gJ`@D=leQrg2_QcVjN60^ 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 zalea

VYZz&xWZ-|^u9k;e(+Vf|+5v3s{8YyC@4B(Yh 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 B%^&~( literal 0 HcmV?d00001 diff --git a/packages/plugin-pyth-data/eslint.config.mjs b/packages/plugin-pyth-data/eslint.config.mjs new file mode 100644 index 0000000000..806d9c7858 --- /dev/null +++ b/packages/plugin-pyth-data/eslint.config.mjs @@ -0,0 +1,17 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [ + ...eslintGlobalConfig, + { + files: ["src/**/*.ts"], + rules: { + // Disable problematic rules + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-call": "off", + }, + }, +]; diff --git a/packages/plugin-pyth-data/package.json b/packages/plugin-pyth-data/package.json new file mode 100644 index 0000000000..791da07b6c --- /dev/null +++ b/packages/plugin-pyth-data/package.json @@ -0,0 +1,58 @@ +{ + "name": "@elizaos/plugin-pyth-data", + "version": "1.0.0", + "description": "Pyth Network data plugin for Eliza", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsup --format esm --dts", + "test": "vitest", + "lint": "eslint src --ext .ts", + "clean": "rimraf dist", + "build:schemas": "openapi-zod-client ./schema.json --output src/types/zodSchemas.ts", + "pull:schema": "curl -o schema.json -z schema.json https://hermes.pyth.network/docs/openapi.json", + "prebuild": "pnpm run pull:schema && pnpm run build:schemas" + }, + "dependencies": { + "@elizaos/core": "^0.1.7", + "@pythnetwork/client": "^2.22.0", + "@solana/web3.js": "^1.98.0", + "@zodios/core": "^10.9.6", + "ajv": "^8.12.0", + "buffer": "6.0.3", + "chalk": "^5.4.1", + "cli-table3": "^0.6.5", + "cross-fetch": "^4.0.0", + "eventsource": "^3.0.2", + "jstat": "^1.9.6", + "ora": "^8.1.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^20.8.2", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "eslint": "^8.50.0", + "openapi-zod-client": "^1.18.1", + "rimraf": "^5.0.5", + "tsup": "^8.0.0", + "typescript": "^5.2.2", + "vitest": "^1.0.0" + }, + "peerDependencies": { + "@elizaos/core": "^0.1.7" + }, + "engines": { + "node": ">=16.0.0" + }, + "keywords": [ + "eliza", + "plugin", + "pyth", + "oracle", + "price-feed" + ], + "author": "Eliza Team", + "license": "MIT" +} diff --git a/packages/plugin-pyth-data/schema.json b/packages/plugin-pyth-data/schema.json new file mode 100644 index 0000000000..a036280f02 --- /dev/null +++ b/packages/plugin-pyth-data/schema.json @@ -0,0 +1 @@ +{"openapi":"3.0.3","info":{"title":"hermes","description":"Hermes is an agent that provides Verified Prices from the Pythnet Pyth Oracle.","license":{"name":""},"version":"0.8.1"},"paths":{"/api/get_price_feed":{"get":{"tags":["rest"],"summary":"**Deprecated: use /v2/updates/price/{publish_time} instead**","description":"**Deprecated: use /v2/updates/price/{publish_time} instead**\n\nGet a price update for a price feed with a specific timestamp\n\nGiven a price feed id and timestamp, retrieve the Pyth price update closest to that timestamp.","operationId":"get_price_feed","parameters":[{"name":"id","in":"query","description":"The id of the price feed to get an update for.","required":true,"schema":{"$ref":"#/components/schemas/PriceIdInput"}},{"name":"publish_time","in":"query","description":"The unix timestamp in seconds. This endpoint will return the first update whose\npublish_time is >= the provided value.","required":true,"schema":{"type":"integer","format":"int64"},"example":1717632000},{"name":"verbose","in":"query","description":"If true, include the `metadata` field in the response with additional metadata about the\nprice update.","required":false,"schema":{"type":"boolean"}},{"name":"binary","in":"query","description":"If true, include the binary price update in the `vaa` field of each returned feed. This\nbinary data can be submitted to Pyth contracts to update the on-chain price.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Price update retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RpcPriceFeed"}}}}},"deprecated":true}},"/api/get_vaa":{"get":{"tags":["rest"],"summary":"**Deprecated: use /v2/updates/price/{publish_time} instead**","description":"**Deprecated: use /v2/updates/price/{publish_time} instead**\n\nGet a VAA for a price feed with a specific timestamp\n\nGiven a price feed id and timestamp, retrieve the Pyth price update closest to that timestamp.","operationId":"get_vaa","parameters":[{"name":"id","in":"query","description":"The ID of the price feed to get an update for.","required":true,"schema":{"$ref":"#/components/schemas/PriceIdInput"}},{"name":"publish_time","in":"query","description":"The unix timestamp in seconds. This endpoint will return the first update whose\npublish_time is >= the provided value.","required":true,"schema":{"type":"integer","format":"int64"},"example":1690576641}],"responses":{"200":{"description":"Price update retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVaaResponse"}}}},"404":{"description":"Price update not found","content":{"text/plain":{"schema":{"type":"string"}}}}},"deprecated":true}},"/api/get_vaa_ccip":{"get":{"tags":["rest"],"summary":"**Deprecated: use /v2/updates/price/{publish_time} instead**","description":"**Deprecated: use /v2/updates/price/{publish_time} instead**\n\nGet a VAA for a price feed using CCIP\n\nThis endpoint accepts a single argument which is a hex-encoded byte string of the following form:\n` `","operationId":"get_vaa_ccip","parameters":[{"name":"data","in":"query","required":true,"schema":{"$ref":"#/components/schemas/GetVaaCcipInput"}}],"responses":{"200":{"description":"Price update retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVaaCcipResponse"}}}}},"deprecated":true}},"/api/latest_price_feeds":{"get":{"tags":["rest"],"summary":"**Deprecated: use /v2/updates/price/latest instead**","description":"**Deprecated: use /v2/updates/price/latest instead**\n\nGet the latest price updates by price feed id.\n\nGiven a collection of price feed ids, retrieve the latest Pyth price for each price feed.","operationId":"latest_price_feeds","parameters":[{"name":"ids[]","in":"query","description":"Get the most recent price update for this set of price feed ids.\n\nThis parameter can be provided multiple times to retrieve multiple price updates,\nfor example see the following query string:\n\n```\n?ids[]=a12...&ids[]=b4c...\n```","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceIdInput"}},"example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},{"name":"verbose","in":"query","description":"If true, include the `metadata` field in the response with additional metadata about\nthe price update.","required":false,"schema":{"type":"boolean"}},{"name":"binary","in":"query","description":"If true, include the binary price update in the `vaa` field of each returned feed.\nThis binary data can be submitted to Pyth contracts to update the on-chain price.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Price updates retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RpcPriceFeed"}}}}}},"deprecated":true}},"/api/latest_vaas":{"get":{"tags":["rest"],"summary":"**Deprecated: use /v2/updates/price/latest instead**","description":"**Deprecated: use /v2/updates/price/latest instead**\n\nGet VAAs for a set of price feed ids.\n\nGiven a collection of price feed ids, retrieve the latest VAA for each. The returned VAA(s) can\nbe submitted to the Pyth contract to update the on-chain price. If VAAs are not found for every\nprovided price ID the call will fail.","operationId":"latest_vaas","parameters":[{"name":"ids[]","in":"query","description":"Get the VAAs for this set of price feed ids.\n\nThis parameter can be provided multiple times to retrieve multiple price updates,\nfor example see the following query string:\n\n```\n?ids[]=a12...&ids[]=b4c...\n```","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceIdInput"}},"example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"}],"responses":{"200":{"description":"VAAs retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}},"example":["UE5BVQEAAAADuAEAAAADDQC1H7meY5fTed0FsykIb8dt+7nKpbuzfvU2DplDi+dcUl8MC+UIkS65+rkiq+zmNBxE2gaxkBkjdIicZ/fBo+X7AAEqp+WtlWb84np8jJfLpuQ2W+l5KXTigsdAhz5DyVgU3xs+EnaIZxBwcE7EKzjMam+V9rlRy0CGsiQ1kjqqLzfAAQLsoVO0Vu5gVmgc8XGQ7xYhoz36rsBgMjG+e3l/B01esQi/KzPuBf/Ar8Sg5aSEOvEU0muSDb+KIr6d8eEC+FtcAAPZEaBSt4ysXVL84LUcJemQD3SiG30kOfUpF8o7/wI2M2Jf/LyCsbKEQUyLtLbZqnJBSfZJR5AMsrnHDqngMLEGAAY4UDG9GCpRuPvg8hOlsrXuPP3zq7yVPqyG0SG+bNo8rEhP5b1vXlHdG4bZsutX47d5VZ6xnFROKudx3T3/fnWUAQgAU1+kUFc3e0ZZeX1dLRVEryNIVyxMQIcxWwdey+jlIAYowHRM0fJX3Scs80OnT/CERwh5LMlFyU1w578NqxW+AQl2E/9fxjgUTi8crOfDpwsUsmOWw0+Q5OUGhELv/2UZoHAjsaw9OinWUggKACo4SdpPlHYldoWF+J2yGWOW+F4iAQre4c+ocb6a9uSWOnTldFkioqhd9lhmV542+VonCvuy4Tu214NP+2UNd/4Kk3KJCf3iziQJrCBeLi1cLHdLUikgAQtvRFR/nepcF9legl+DywAkUHi5/1MNjlEQvlHyh2XbMiS85yu7/9LgM6Sr+0ukfZY5mSkOcvUkpHn+T+Nw/IrQAQ7lty5luvKUmBpI3ITxSmojJ1aJ0kj/dc0ZcQk+/qo0l0l3/eRLkYjw5j+MZKA8jEubrHzUCke98eSoj8l08+PGAA+DAKNtCwNZe4p6J1Ucod8Lo5RKFfA84CPLVyEzEPQFZ25U9grUK6ilF4GhEia/ndYXLBt3PGW3qa6CBBPM7rH3ABGAyYEtUwzB4CeVedA5o6cKpjRkIebqDNSOqltsr+w7kXdfFVtsK2FMGFZNt5rbpIR+ppztoJ6eOKHmKmi9nQ99ARKkTxRErOs9wJXNHaAuIRV38o1pxRrlQRzGsRuKBqxcQEpC8OPFpyKYcp6iD5l7cO/gRDTamLFyhiUBwKKMP07FAWTEJv8AAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAGp0GAUFVV1YAAAAAAAUYUmIAACcQBsfKUtr4PgZbIXRxRESU79PjE4IBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKqqMJFwAAAAAAqE/NX////+AAAAABkxCb7AAAAAGTEJvoAAAKqIcWxYAAAAAAlR5m4CP/mPsh1IezjYpDlJ4GRb5q4fTs2LjtyO6M0XgVimrIQ4kSh1qg7JKW4gbGkyRntVFR9JO/GNd3FPDit0BK6M+JzXh/h12YNCz9wxlZTvXrNtWNbzqT+91pvl5cphhSPMfAHyEzTPaGR9tKDy9KNu56pmhaY32d2vfEWQmKo22guegeR98oDxs67MmnUraco46a3zEnac2Bm80pasUgMO24="]}}}},"deprecated":true}},"/api/price_feed_ids":{"get":{"tags":["rest"],"summary":"**Deprecated: use /v2/price_feeds instead**","description":"**Deprecated: use /v2/price_feeds instead**\n\nGet the set of price feed IDs.\n\nThis endpoint fetches all of the price feed IDs for which price updates can be retrieved.","operationId":"price_feed_ids","responses":{"200":{"description":"Price feed ids retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RpcPriceIdentifier"}}}}}},"deprecated":true}},"/v2/price_feeds":{"get":{"tags":["rest"],"summary":"Get the set of price feeds.","description":"Get the set of price feeds.\n\nThis endpoint fetches all price feeds from the Pyth network. It can be filtered by asset type\nand query string.","operationId":"price_feeds_metadata","parameters":[{"name":"query","in":"query","description":"Optional query parameter. If provided, the results will be filtered to all price feeds whose symbol contains the query string. Query string is case insensitive.","required":false,"schema":{"type":"string","nullable":true},"example":"bitcoin"},{"name":"asset_type","in":"query","description":"Optional query parameter. If provided, the results will be filtered by asset type. Possible values are crypto, equity, fx, metal, rates. Filter string is case insensitive.","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/AssetType"}],"nullable":true},"example":"crypto"}],"responses":{"200":{"description":"Price feeds metadata retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceFeedMetadata"}}}}}}}},"/v2/updates/price/latest":{"get":{"tags":["rest"],"summary":"Get the latest price updates by price feed id.","description":"Get the latest price updates by price feed id.\n\nGiven a collection of price feed ids, retrieve the latest Pyth price for each price feed.","operationId":"latest_price_updates","parameters":[{"name":"ids[]","in":"query","description":"Get the most recent price update for this set of price feed ids.\n\nThis parameter can be provided multiple times to retrieve multiple price updates,\nfor example see the following query string:\n\n```\n?ids[]=a12...&ids[]=b4c...\n```","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceIdInput"}},"example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},{"name":"encoding","in":"query","description":"Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is `hex`.","required":false,"schema":{"$ref":"#/components/schemas/EncodingType"}},{"name":"parsed","in":"query","description":"If true, include the parsed price update in the `parsed` field of each returned feed. Default is `true`.","required":false,"schema":{"type":"boolean"}},{"name":"ignore_invalid_price_ids","in":"query","description":"If true, invalid price IDs in the `ids` parameter are ignored. Only applicable to the v2 APIs. Default is `false`.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Price updates retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceUpdate"}}}},"404":{"description":"Price ids not found","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v2/updates/price/stream":{"get":{"tags":["rest"],"summary":"SSE route handler for streaming price updates.","description":"SSE route handler for streaming price updates.","operationId":"price_stream_sse_handler","parameters":[{"name":"ids[]","in":"query","description":"Get the most recent price update for this set of price feed ids.\n\nThis parameter can be provided multiple times to retrieve multiple price updates,\nfor example see the following query string:\n\n```\n?ids[]=a12...&ids[]=b4c...\n```","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceIdInput"}},"example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},{"name":"encoding","in":"query","description":"Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is `hex`.","required":false,"schema":{"$ref":"#/components/schemas/EncodingType"}},{"name":"parsed","in":"query","description":"If true, include the parsed price update in the `parsed` field of each returned feed. Default is `true`.","required":false,"schema":{"type":"boolean"}},{"name":"allow_unordered","in":"query","description":"If true, allows unordered price updates to be included in the stream.","required":false,"schema":{"type":"boolean"}},{"name":"benchmarks_only","in":"query","description":"If true, only include benchmark prices that are the initial price updates at a given timestamp (i.e., prevPubTime != pubTime).","required":false,"schema":{"type":"boolean"}},{"name":"ignore_invalid_price_ids","in":"query","description":"If true, invalid price IDs in the `ids` parameter are ignored. Only applicable to the v2 APIs. Default is `false`.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Price updates retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceUpdate"}}}},"404":{"description":"Price ids not found","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v2/updates/price/{publish_time}":{"get":{"tags":["rest"],"summary":"Get the latest price updates by price feed id.","description":"Get the latest price updates by price feed id.\n\nGiven a collection of price feed ids, retrieve the latest Pyth price for each price feed.","operationId":"timestamp_price_updates","parameters":[{"name":"publish_time","in":"path","description":"The unix timestamp in seconds. This endpoint will return the first update whose\npublish_time is >= the provided value.","required":true,"schema":{"type":"integer","format":"int64"},"example":1717632000},{"name":"ids[]","in":"query","description":"Get the most recent price update for this set of price feed ids.\n\nThis parameter can be provided multiple times to retrieve multiple price updates,\nfor example see the following query string:\n\n```\n?ids[]=a12...&ids[]=b4c...\n```","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceIdInput"}},"example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},{"name":"encoding","in":"query","description":"Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is `hex`.","required":false,"schema":{"$ref":"#/components/schemas/EncodingType"}},{"name":"parsed","in":"query","description":"If true, include the parsed price update in the `parsed` field of each returned feed. Default is `true`.","required":false,"schema":{"type":"boolean"}},{"name":"ignore_invalid_price_ids","in":"query","description":"If true, invalid price IDs in the `ids` parameter are ignored. Only applicable to the v2 APIs. Default is `false`.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Price updates retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceUpdate"}}}},"404":{"description":"Price ids not found","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v2/updates/publisher_stake_caps/latest":{"get":{"tags":["rest"],"summary":"Get the most recent publisher stake caps update data.","description":"Get the most recent publisher stake caps update data.","operationId":"latest_publisher_stake_caps","parameters":[{"name":"encoding","in":"query","description":"Get the most recent publisher stake caps update data.\nOptional encoding type. If true, return the message in the encoding specified by the encoding parameter. Default is `hex`.","required":false,"schema":{"$ref":"#/components/schemas/EncodingType"}},{"name":"parsed","in":"query","description":"If true, include the parsed update in the `parsed` field of each returned feed. Default is `true`.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Publisher stake caps update data retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatestPublisherStakeCapsUpdateDataResponse"}}}}}}},"/v2/updates/twap/{window_seconds}/latest":{"get":{"tags":["rest"],"summary":"Get the latest TWAP by price feed id with a custom time window.","description":"Get the latest TWAP by price feed id with a custom time window.\n\nGiven a collection of price feed ids, retrieve the latest Pyth TWAP price for each price feed.","operationId":"latest_twaps","parameters":[{"name":"window_seconds","in":"path","description":"The time window in seconds over which to calculate the TWAP, ending at the current time.\nFor example, a value of 300 would return the most recent 5 minute TWAP.\nMust be greater than 0 and less than or equal to 600 seconds (10 minutes).","required":true,"schema":{"type":"integer","format":"int64","minimum":0},"example":"300"},{"name":"ids[]","in":"query","description":"Get the most recent TWAP (time weighted average price) for this set of price feed ids.\nThe `binary` data contains the signed start & end cumulative price updates needed to calculate\nthe TWAPs on-chain. The `parsed` data contains the calculated TWAPs.\n\nThis parameter can be provided multiple times to retrieve multiple price updates,\nfor example see the following query string:\n\n```\n?ids[]=a12...&ids[]=b4c...\n```","required":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PriceIdInput"}},"example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},{"name":"encoding","in":"query","description":"Optional encoding type. If true, return the cumulative price updates in the encoding specified by the encoding parameter. Default is `hex`.","required":false,"schema":{"$ref":"#/components/schemas/EncodingType"}},{"name":"parsed","in":"query","description":"If true, include the calculated TWAP in the `parsed` field of each returned feed. Default is `true`.","required":false,"schema":{"type":"boolean"}},{"name":"ignore_invalid_price_ids","in":"query","description":"If true, invalid price IDs in the `ids` parameter are ignored. Only applicable to the v2 APIs. Default is `false`.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"TWAPs retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwapsResponse"}}}},"404":{"description":"Price ids not found","content":{"text/plain":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"AssetType":{"type":"string","enum":["crypto","fx","equity","metal","rates","crypto_redemption_rate"]},"BinaryUpdate":{"type":"object","required":["encoding","data"],"properties":{"data":{"type":"array","items":{"type":"string"}},"encoding":{"$ref":"#/components/schemas/EncodingType"}}},"EncodingType":{"type":"string","enum":["hex","base64"]},"GetVaaCcipInput":{"type":"string","format":"binary"},"GetVaaCcipResponse":{"type":"object","required":["data"],"properties":{"data":{"type":"string"}}},"GetVaaResponse":{"type":"object","required":["vaa","publishTime"],"properties":{"publishTime":{"type":"integer","format":"int64","example":1690576641},"vaa":{"type":"string","description":"The VAA binary represented as a base64 string.","example":"UE5BVQEAAAADuAEAAAADDQC1H7meY5fTed0FsykIb8dt+7nKpbuzfvU2DplDi+dcUl8MC+UIkS65+rkiq+zmNBxE2gaxkBkjdIicZ/fBo+X7AAEqp+WtlWb84np8jJfLpuQ2W+l5KXTigsdAhz5DyVgU3xs+EnaIZxBwcE7EKzjMam+V9rlRy0CGsiQ1kjqqLzfAAQLsoVO0Vu5gVmgc8XGQ7xYhoz36rsBgMjG+e3l/B01esQi/KzPuBf/Ar8Sg5aSEOvEU0muSDb+KIr6d8eEC+FtcAAPZEaBSt4ysXVL84LUcJemQD3SiG30kOfUpF8o7/wI2M2Jf/LyCsbKEQUyLtLbZqnJBSfZJR5AMsrnHDqngMLEGAAY4UDG9GCpRuPvg8hOlsrXuPP3zq7yVPqyG0SG+bNo8rEhP5b1vXlHdG4bZsutX47d5VZ6xnFROKudx3T3/fnWUAQgAU1+kUFc3e0ZZeX1dLRVEryNIVyxMQIcxWwdey+jlIAYowHRM0fJX3Scs80OnT/CERwh5LMlFyU1w578NqxW+AQl2E/9fxjgUTi8crOfDpwsUsmOWw0+Q5OUGhELv/2UZoHAjsaw9OinWUggKACo4SdpPlHYldoWF+J2yGWOW+F4iAQre4c+ocb6a9uSWOnTldFkioqhd9lhmV542+VonCvuy4Tu214NP+2UNd/4Kk3KJCf3iziQJrCBeLi1cLHdLUikgAQtvRFR/nepcF9legl+DywAkUHi5/1MNjlEQvlHyh2XbMiS85yu7/9LgM6Sr+0ukfZY5mSkOcvUkpHn+T+Nw/IrQAQ7lty5luvKUmBpI3ITxSmojJ1aJ0kj/dc0ZcQk+/qo0l0l3/eRLkYjw5j+MZKA8jEubrHzUCke98eSoj8l08+PGAA+DAKNtCwNZe4p6J1Ucod8Lo5RKFfA84CPLVyEzEPQFZ25U9grUK6ilF4GhEia/ndYXLBt3PGW3qa6CBBPM7rH3ABGAyYEtUwzB4CeVedA5o6cKpjRkIebqDNSOqltsr+w7kXdfFVtsK2FMGFZNt5rbpIR+ppztoJ6eOKHmKmi9nQ99ARKkTxRErOs9wJXNHaAuIRV38o1pxRrlQRzGsRuKBqxcQEpC8OPFpyKYcp6iD5l7cO/gRDTamLFyhiUBwKKMP07FAWTEJv8AAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAGp0GAUFVV1YAAAAAAAUYUmIAACcQBsfKUtr4PgZbIXRxRESU79PjE4IBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKqqMJFwAAAAAAqE/NX////+AAAAABkxCb7AAAAAGTEJvoAAAKqIcWxYAAAAAAlR5m4CP/mPsh1IezjYpDlJ4GRb5q4fTs2LjtyO6M0XgVimrIQ4kSh1qg7JKW4gbGkyRntVFR9JO/GNd3FPDit0BK6M+JzXh/h12YNCz9wxlZTvXrNtWNbzqT+91pvl5cphhSPMfAHyEzTPaGR9tKDy9KNu56pmhaY32d2vfEWQmKo22guegeR98oDxs67MmnUraco46a3zEnac2Bm80pasUgMO24="}}},"LatestPublisherStakeCapsUpdateDataResponse":{"type":"object","required":["binary"],"properties":{"binary":{"$ref":"#/components/schemas/BinaryUpdate"},"parsed":{"type":"array","items":{"$ref":"#/components/schemas/ParsedPublisherStakeCapsUpdate"},"nullable":true}}},"ParsedPriceFeedTwap":{"type":"object","required":["id","start_timestamp","end_timestamp","twap","down_slots_ratio"],"properties":{"down_slots_ratio":{"type":"string","description":"The % of slots where the network was down over the TWAP window.\nA value of zero indicates no slots were missed over the window, and\na value of one indicates that every slot was missed over the window.\nThis is a float value stored as a string to avoid precision loss."},"end_timestamp":{"type":"integer","format":"int64","description":"The end unix timestamp of the window"},"id":{"$ref":"#/components/schemas/RpcPriceIdentifier"},"start_timestamp":{"type":"integer","format":"int64","description":"The start unix timestamp of the window"},"twap":{"$ref":"#/components/schemas/RpcPrice"}}},"ParsedPriceUpdate":{"type":"object","required":["id","price","ema_price","metadata"],"properties":{"ema_price":{"$ref":"#/components/schemas/RpcPrice"},"id":{"$ref":"#/components/schemas/RpcPriceIdentifier"},"metadata":{"$ref":"#/components/schemas/RpcPriceFeedMetadataV2"},"price":{"$ref":"#/components/schemas/RpcPrice"}}},"ParsedPublisherStakeCap":{"type":"object","required":["publisher","cap"],"properties":{"cap":{"type":"integer","format":"int64","minimum":0},"publisher":{"type":"string"}}},"ParsedPublisherStakeCapsUpdate":{"type":"object","required":["publisher_stake_caps"],"properties":{"publisher_stake_caps":{"type":"array","items":{"$ref":"#/components/schemas/ParsedPublisherStakeCap"}}}},"PriceFeedMetadata":{"type":"object","required":["id","attributes"],"properties":{"attributes":{"type":"object","additionalProperties":{"type":"string"}},"id":{"$ref":"#/components/schemas/RpcPriceIdentifier"}}},"PriceIdInput":{"type":"string","description":"A price id is a 32-byte hex string, optionally prefixed with \"0x\".\nPrice ids are case insensitive.\n\nExamples:\n* 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43\n* e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43\n\nSee https://pyth.network/developers/price-feed-ids for a list of all price feed ids.","example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},"PriceUpdate":{"type":"object","required":["binary"],"properties":{"binary":{"$ref":"#/components/schemas/BinaryUpdate"},"parsed":{"type":"array","items":{"$ref":"#/components/schemas/ParsedPriceUpdate"},"nullable":true}}},"RpcPrice":{"type":"object","description":"A price with a degree of uncertainty at a certain time, represented as a price +- a confidence\ninterval.\n\nThe confidence interval roughly corresponds to the standard error of a normal distribution.\nBoth the price and confidence are stored in a fixed-point numeric representation, `x *\n10^expo`, where `expo` is the exponent. For example:","required":["price","conf","expo","publish_time"],"properties":{"conf":{"type":"string","description":"The confidence interval associated with the price, stored as a string to avoid precision loss","example":"509500001"},"expo":{"type":"integer","format":"int32","description":"The exponent associated with both the price and confidence interval. Multiply those values\nby `10^expo` to get the real value.","example":-8},"price":{"type":"string","description":"The price itself, stored as a string to avoid precision loss","example":"2920679499999"},"publish_time":{"type":"integer","format":"int64","description":"When the price was published. The `publish_time` is a unix timestamp, i.e., the number of\nseconds since the Unix epoch (00:00:00 UTC on 1 Jan 1970).","example":1717632000}}},"RpcPriceFeed":{"type":"object","required":["id","price","ema_price"],"properties":{"ema_price":{"$ref":"#/components/schemas/RpcPrice"},"id":{"$ref":"#/components/schemas/RpcPriceIdentifier"},"metadata":{"allOf":[{"$ref":"#/components/schemas/RpcPriceFeedMetadata"}],"nullable":true},"price":{"$ref":"#/components/schemas/RpcPrice"},"vaa":{"type":"string","description":"The VAA binary represented as a base64 string.","example":"UE5BVQEAAAADuAEAAAADDQC1H7meY5fTed0FsykIb8dt+7nKpbuzfvU2DplDi+dcUl8MC+UIkS65+rkiq+zmNBxE2gaxkBkjdIicZ/fBo+X7AAEqp+WtlWb84np8jJfLpuQ2W+l5KXTigsdAhz5DyVgU3xs+EnaIZxBwcE7EKzjMam+V9rlRy0CGsiQ1kjqqLzfAAQLsoVO0Vu5gVmgc8XGQ7xYhoz36rsBgMjG+e3l/B01esQi/KzPuBf/Ar8Sg5aSEOvEU0muSDb+KIr6d8eEC+FtcAAPZEaBSt4ysXVL84LUcJemQD3SiG30kOfUpF8o7/wI2M2Jf/LyCsbKEQUyLtLbZqnJBSfZJR5AMsrnHDqngMLEGAAY4UDG9GCpRuPvg8hOlsrXuPP3zq7yVPqyG0SG+bNo8rEhP5b1vXlHdG4bZsutX47d5VZ6xnFROKudx3T3/fnWUAQgAU1+kUFc3e0ZZeX1dLRVEryNIVyxMQIcxWwdey+jlIAYowHRM0fJX3Scs80OnT/CERwh5LMlFyU1w578NqxW+AQl2E/9fxjgUTi8crOfDpwsUsmOWw0+Q5OUGhELv/2UZoHAjsaw9OinWUggKACo4SdpPlHYldoWF+J2yGWOW+F4iAQre4c+ocb6a9uSWOnTldFkioqhd9lhmV542+VonCvuy4Tu214NP+2UNd/4Kk3KJCf3iziQJrCBeLi1cLHdLUikgAQtvRFR/nepcF9legl+DywAkUHi5/1MNjlEQvlHyh2XbMiS85yu7/9LgM6Sr+0ukfZY5mSkOcvUkpHn+T+Nw/IrQAQ7lty5luvKUmBpI3ITxSmojJ1aJ0kj/dc0ZcQk+/qo0l0l3/eRLkYjw5j+MZKA8jEubrHzUCke98eSoj8l08+PGAA+DAKNtCwNZe4p6J1Ucod8Lo5RKFfA84CPLVyEzEPQFZ25U9grUK6ilF4GhEia/ndYXLBt3PGW3qa6CBBPM7rH3ABGAyYEtUwzB4CeVedA5o6cKpjRkIebqDNSOqltsr+w7kXdfFVtsK2FMGFZNt5rbpIR+ppztoJ6eOKHmKmi9nQ99ARKkTxRErOs9wJXNHaAuIRV38o1pxRrlQRzGsRuKBqxcQEpC8OPFpyKYcp6iD5l7cO/gRDTamLFyhiUBwKKMP07FAWTEJv8AAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAGp0GAUFVV1YAAAAAAAUYUmIAACcQBsfKUtr4PgZbIXRxRESU79PjE4IBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKqqMJFwAAAAAAqE/NX////+AAAAABkxCb7AAAAAGTEJvoAAAKqIcWxYAAAAAAlR5m4CP/mPsh1IezjYpDlJ4GRb5q4fTs2LjtyO6M0XgVimrIQ4kSh1qg7JKW4gbGkyRntVFR9JO/GNd3FPDit0BK6M+JzXh/h12YNCz9wxlZTvXrNtWNbzqT+91pvl5cphhSPMfAHyEzTPaGR9tKDy9KNu56pmhaY32d2vfEWQmKo22guegeR98oDxs67MmnUraco46a3zEnac2Bm80pasUgMO24=","nullable":true}}},"RpcPriceFeedMetadata":{"type":"object","required":["emitter_chain"],"properties":{"emitter_chain":{"type":"integer","format":"int32","example":26,"minimum":0},"prev_publish_time":{"type":"integer","format":"int64","example":1717632000,"nullable":true},"price_service_receive_time":{"type":"integer","format":"int64","example":1717632000,"nullable":true},"slot":{"type":"integer","format":"int64","example":85480034,"nullable":true,"minimum":0}}},"RpcPriceFeedMetadataV2":{"type":"object","properties":{"prev_publish_time":{"type":"integer","format":"int64","example":1717632000,"nullable":true},"proof_available_time":{"type":"integer","format":"int64","example":1717632000,"nullable":true},"slot":{"type":"integer","format":"int64","example":85480034,"nullable":true,"minimum":0}}},"RpcPriceIdentifier":{"type":"string","example":"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"},"TwapsResponse":{"type":"object","required":["binary"],"properties":{"binary":{"$ref":"#/components/schemas/BinaryUpdate"},"parsed":{"type":"array","items":{"$ref":"#/components/schemas/ParsedPriceFeedTwap"},"description":"The calculated TWAPs for each price ID","nullable":true}}}}},"tags":[{"name":"hermes","description":"Pyth Real-Time Pricing API"}]} \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/actions/actionGetLatestPriceUpdates.ts b/packages/plugin-pyth-data/src/actions/actionGetLatestPriceUpdates.ts new file mode 100644 index 0000000000..d8599e0141 --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actionGetLatestPriceUpdates.ts @@ -0,0 +1,252 @@ +import { Action, elizaLogger } from "@elizaos/core"; +import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; +import { HermesClient } from "../hermes/HermesClient"; +import { DataError, ErrorSeverity, DataErrorCode } from "../error"; +import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; +import { ValidationSchemas } from "../types/types"; +import { validateSchema } from "../utils/validation"; +import { schemas } from "../types/zodSchemas"; +import { z } from "zod"; + +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[PriceUpdates] ${message}`, data); + console.log(`[PriceUpdates] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; + +interface GetLatestPriceUpdatesContent extends Content { + text: string; + priceIds: string[]; + options?: { + encoding?: "hex" | "base64"; + parsed?: boolean; + }; + success?: boolean; + data?: { + updates?: Array<{ + price_feed_id: string; + price: number; + conf: number; + expo: number; + publish_time: number; + ema_price?: { + price: number; + conf: number; + expo: number; + }; + }>; + error?: string; + }; +} + +export const getLatestPriceUpdatesAction: Action = { + name: "GET_LATEST_PRICE_UPDATES", + similes: ["FETCH_LATEST_PRICES", "GET_CURRENT_PRICES", "CHECK_PRICE_FEED"], + description: "Retrieve latest price updates from Pyth Network", + examples: [[ + { + user: "user", + content: { + text: "Get latest BTC/USD price updates", + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + options: { + encoding: "base64", + parsed: true + } + } as GetLatestPriceUpdatesContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Here is the latest BTC/USD price", + success: true, + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + data: { + updates: [{ + price_feed_id: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + price: 42000000000, + conf: 100000000, + expo: -8, + publish_time: 1641034800, + ema_price: { + price: 41950000000, + conf: 95000000, + expo: -8 + } + }] + } + } as GetLatestPriceUpdatesContent + } as ActionExample + ]], + + validate: async (_runtime: IAgentRuntime, message: Memory): Promise => { + logGranular("Validating GET_LATEST_PRICE_UPDATES action", { + content: message.content + }); + + try { + const content = message.content as GetLatestPriceUpdatesContent; + + // Validate against schema + try { + await validateSchema(content, ValidationSchemas.GET_LATEST_PRICE); + logGranular("Schema validation passed"); + } catch (error) { + logGranular("Schema validation failed", { error }); + if (error instanceof DataError) { + elizaLogger.error("Schema validation failed", { + errors: error.details?.errors + }); + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { error } + ); + } + + // Validate priceIds array + if (!content.priceIds || !Array.isArray(content.priceIds)) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "priceIds must be an array of strings", + ErrorSeverity.HIGH + ); + } + + if (content.priceIds.length === 0) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "priceIds array cannot be empty", + ErrorSeverity.HIGH + ); + } + + // Validate each price ID is a valid hex string + content.priceIds.forEach((id, index) => { + if (!/^[0-9a-fA-F]+$/.test(id)) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + `Invalid price ID at index ${index}: ${id}`, + ErrorSeverity.HIGH + ); + } + }); + + return true; + } catch (error) { + logGranular("Validation failed", { error }); + elizaLogger.error("Validation failed for GET_LATEST_PRICE_UPDATES", { + error: error instanceof Error ? error.message : String(error) + }); + return false; + } + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State, + _options: { [key: string]: unknown } = {}, + callback?: HandlerCallback + ): Promise => { + logGranular("Executing GET_LATEST_PRICE_UPDATES action"); + + try { + const messageContent = message.content as GetLatestPriceUpdatesContent; + const { priceIds, options = {} } = messageContent; + + // Get Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + // Get network configuration + const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); + + // Initialize Hermes client + const hermesClient = new HermesClient(networkConfig.hermes); + + logGranular("Initialized HermesClient", { + endpoint: networkConfig.hermes + }); + + try { + // Get latest price updates + const updates = await hermesClient.getLatestPriceUpdates(priceIds, { + parsed: true, + encoding: options?.encoding as "hex" | "base64" | undefined + }); + + logGranular("Successfully retrieved price updates", { + count: updates.parsed?.length || 0 + }); + + // Create callback content + const callbackContent: GetLatestPriceUpdatesContent = { + text: `Retrieved ${updates.parsed?.length || 0} price updates`, + success: true, + priceIds, + data: { + updates: (updates as z.infer).parsed?.map((update) => ({ + price_feed_id: update.id, + price: Number(update.price.price), + conf: Number(update.price.conf), + expo: Number(update.price.expo), + publish_time: update.price.publish_time, + ema_price: update.ema_price ? { + price: Number(update.ema_price.price), + conf: Number(update.ema_price.conf), + expo: Number(update.ema_price.expo) + } : undefined + })) || [] + } + }; + + // Call callback with results + if (callback) { + await callback(callbackContent); + } + + return true; + } catch (error) { + logGranular("Failed to process price updates request", { error }); + if (error instanceof DataError) { + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Failed to process price updates request", + ErrorSeverity.HIGH, + { originalError: error } + ); + } + } catch (error) { + logGranular("Failed to get latest price updates", { error }); + if (error instanceof DataError) { + throw error; + } + throw new DataError( + DataErrorCode.NETWORK_ERROR, + "Failed to get latest price updates", + ErrorSeverity.HIGH, + { originalError: error } + ); + } + } +}; + +export default getLatestPriceUpdatesAction; diff --git a/packages/plugin-pyth-data/src/actions/actionGetLatestPublisherCaps.ts b/packages/plugin-pyth-data/src/actions/actionGetLatestPublisherCaps.ts new file mode 100644 index 0000000000..85f617a898 --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actionGetLatestPublisherCaps.ts @@ -0,0 +1,201 @@ +import { Action, elizaLogger } from "@elizaos/core"; +import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; +import { HermesClient } from "../hermes/HermesClient"; +import { DataError, ErrorSeverity, DataErrorCode } from "../error"; +import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; +import { ValidationSchemas } from "../types/types"; +import { validateSchema } from "../utils/validation"; +import { schemas } from "../types/zodSchemas"; +import { z } from "zod"; + +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[PublisherCaps] ${message}`, data); + console.log(`[PublisherCaps] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; + +interface GetLatestPublisherCapsContent extends Content { + text: string; + success?: boolean; + data?: { + caps?: Array<{ + publisher: string; + cap: number; + timestamp: number; + }>; + error?: string; + }; +} + +export const getLatestPublisherCapsAction: Action = { + name: "GET_LATEST_PUBLISHER_CAPS", + similes: ["FETCH_PUBLISHER_CAPS", "GET_PUBLISHER_LIMITS", "CHECK_PUBLISHER_CAPS"], + description: "Retrieve latest publisher caps from Pyth Network", + examples: [[ + { + user: "user", + content: { + text: "Get latest publisher caps" + } as GetLatestPublisherCapsContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Latest publisher caps", + success: true, + data: { + caps: [{ + publisher: "0x1234567890abcdef1234567890abcdef12345678", + cap: 1000000, + timestamp: 1641034800 + }] + } + } as GetLatestPublisherCapsContent + } as ActionExample + ]], + + validate: async (_runtime: IAgentRuntime, message: Memory): Promise => { + logGranular("Validating GET_LATEST_PUBLISHER_CAPS action", { + content: message.content + }); + + try { + const content = message.content as GetLatestPublisherCapsContent; + + // Validate against schema + try { + await validateSchema(content, ValidationSchemas.GET_LATEST_PUBLISHER_CAPS); + logGranular("Schema validation passed"); + } catch (error) { + logGranular("Schema validation failed", { error }); + if (error instanceof DataError) { + elizaLogger.error("Schema validation failed", { + errors: error.details?.errors + }); + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { error } + ); + } + + return true; + } catch (error) { + logGranular("Validation failed", { error }); + elizaLogger.error("Validation failed for GET_LATEST_PUBLISHER_CAPS", { + error: error instanceof Error ? error.message : String(error) + }); + return false; + } + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State, + _options: { [key: string]: unknown } = {}, + callback?: HandlerCallback + ): Promise => { + logGranular("Executing GET_LATEST_PUBLISHER_CAPS action"); + + try { + // Get Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + // Get network configuration + const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); + + // Initialize Hermes client + const hermesClient = new HermesClient(networkConfig.hermes); + + logGranular("Initialized HermesClient", { + endpoint: networkConfig.hermes + }); + + // Create message content + const messageContent: GetLatestPublisherCapsContent = { + text: "Get latest publisher caps", + success: false + }; + + try { + // Validate input + await validateSchema( + messageContent, + ValidationSchemas.GET_LATEST_PUBLISHER_CAPS + ); + + // Get publisher caps with options + const caps = await hermesClient.getLatestPublisherCaps({ + parsed: true + }); + + const parsedCaps = caps as z.infer; + + logGranular("Successfully retrieved publisher caps", { + count: parsedCaps.parsed?.[0]?.publisher_stake_caps.length || 0 + }); + + // Create callback content + const callbackContent: GetLatestPublisherCapsContent = { + text: `Retrieved ${parsedCaps.parsed?.[0]?.publisher_stake_caps.length || 0} publisher caps`, + success: true, + data: { + caps: parsedCaps.parsed?.[0]?.publisher_stake_caps.map((cap) => ({ + publisher: cap.publisher, + cap: cap.cap, + timestamp: Date.now() // Since timestamp is not in the response + })) || [] + } + }; + + // Call callback with results + if (callback) { + await callback(callbackContent); + } + + return true; + } catch (error) { + logGranular("Failed to process publisher caps request", { error }); + if (error instanceof DataError) { + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Failed to process publisher caps request", + ErrorSeverity.HIGH, + { originalError: error } + ); + } + } catch (error) { + logGranular("Failed to get publisher caps", { error }); + if (error instanceof DataError) { + throw error; + } + throw new DataError( + DataErrorCode.NETWORK_ERROR, + "Failed to get publisher caps", + ErrorSeverity.HIGH, + { originalError: error } + ); + } + } +}; + +export default getLatestPublisherCapsAction; diff --git a/packages/plugin-pyth-data/src/actions/actionGetLatestTwaps.ts b/packages/plugin-pyth-data/src/actions/actionGetLatestTwaps.ts new file mode 100644 index 0000000000..02b5931d3f --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actionGetLatestTwaps.ts @@ -0,0 +1,354 @@ +import { Action, elizaLogger } from "@elizaos/core"; +import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; +import { HermesClient } from "../hermes/HermesClient"; +import { DataError, ErrorSeverity, DataErrorCode } from "../error"; +import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; +import { ValidationSchemas } from "../types/types"; +import { validateSchema } from "../utils/validation"; +import { schemas } from "../types/zodSchemas"; +import { z } from "zod"; + +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[Twaps] ${message}`, data); + console.log(`[Twaps] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; +interface GetLatestTwapsContent extends Content { + text: string; + priceIds: string[]; + windows: number[]; + success?: boolean; + data?: { + twaps?: Array<{ + priceId: string; + windows: Array<{ + window: number; + value: number; + confidence: number; + timestamp: number; + }>; + }>; + error?: string; + }; +} + +// Transform TWAP data to match interface +const transformTwapResponse = (response: z.infer) => { + if (!response.parsed) return []; + return response.parsed.map((item) => ({ + priceId: item.id, + windows: [{ + window: item.end_timestamp - item.start_timestamp, + value: Number(item.twap.price), + confidence: Number(item.twap.conf), + timestamp: item.end_timestamp + }] + })); +}; + +export const getLatestTwapsAction: Action = { + name: "GET_LATEST_TWAPS", + similes: ["FETCH_TIME_WEIGHTED_PRICES", "GET_AVERAGE_PRICES", "CHECK_TWAP_VALUES"], + description: "Retrieve Time-Weighted Average Prices (TWAPs) for specified price IDs and time windows from Pyth Network", + examples: [[ + { + user: "user", + content: { + text: "Get latest TWAPs for BTC/USD", + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + windows: [300, 900] + } as GetLatestTwapsContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Latest TWAPs for BTC/USD", + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + windows: [300, 900], + success: true, + data: { + twaps: [{ + priceId: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + windows: [ + { + window: 300, + value: 42000, + confidence: 100, + timestamp: 1641034800 + }, + { + window: 900, + value: 41950, + confidence: 95, + timestamp: 1641034800 + } + ] + }] + } + } as GetLatestTwapsContent + } as ActionExample + ], [ + { + user: "user", + content: { + text: "Get latest TWAPs for ETH/USD and BTC/USD", + priceIds: [ + "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6" + ], + windows: [300, 900] + } as GetLatestTwapsContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Latest TWAPs for ETH/USD and BTC/USD", + priceIds: [ + "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6" + ], + windows: [300, 900], + success: true, + data: { + twaps: [ + { + priceId: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + windows: [ + { + window: 300, + value: 42000, + confidence: 100, + timestamp: 1641034800 + }, + { + window: 900, + value: 41950, + confidence: 95, + timestamp: 1641034800 + } + ] + }, + { + priceId: "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6", + windows: [ + { + window: 300, + value: 2500, + confidence: 50, + timestamp: 1641034800 + }, + { + window: 900, + value: 2495, + confidence: 45, + timestamp: 1641034800 + } + ] + } + ] + } + } as GetLatestTwapsContent + } as ActionExample + ]], + + async validate(runtime: IAgentRuntime, message: Memory): Promise { + logGranular("Validating GET_LATEST_TWAPS action", { + content: message.content + }); + + try { + const content = message.content as GetLatestTwapsContent; + + // Validate against schema + try { + await validateSchema(content, ValidationSchemas.GET_LATEST_TWAPS); + logGranular("Schema validation passed"); + } catch (error) { + logGranular("Schema validation failed", { error }); + if (error instanceof DataError) { + elizaLogger.error("Schema validation failed", { + errors: error.details?.errors + }); + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { error } + ); + } + + // Validate Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + if (!content.priceIds || !Array.isArray(content.priceIds)) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "priceIds must be an array of strings", + ErrorSeverity.HIGH + ); + } + + if (content.priceIds.length === 0) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "priceIds array cannot be empty", + ErrorSeverity.HIGH + ); + } + + // Validate each price ID is a valid hex string + content.priceIds.forEach((id, index) => { + if (!/^[0-9a-fA-F]+$/.test(id)) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + `Invalid price ID at index ${index}: ${id}`, + ErrorSeverity.HIGH + ); + } + }); + + if (!content.windows || !Array.isArray(content.windows)) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "windows must be an array of numbers", + ErrorSeverity.HIGH + ); + } + + if (content.windows.length === 0) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "windows array cannot be empty", + ErrorSeverity.HIGH + ); + } + + // Validate each window is a positive number + content.windows.forEach((window, index) => { + if (typeof window !== 'number' || window <= 0) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + `Invalid window at index ${index}: ${window}. Must be a positive number.`, + ErrorSeverity.HIGH + ); + } + }); + + logGranular("GET_LATEST_TWAPS validation successful", { + priceIds: content.priceIds, + windows: content.windows + }); + + return true; + } catch (error) { + elizaLogger.error("Validation failed for GET_LATEST_TWAPS", { + error: error instanceof Error ? error.message : String(error) + }); + return false; + } + }, + + async handler( + runtime: IAgentRuntime, + message: Memory, + state: State | undefined, + _options: { [key: string]: unknown } = {}, + callback?: HandlerCallback + ): Promise { + logGranular("Executing GET_LATEST_TWAPS action"); + + try { + const content = message.content as GetLatestTwapsContent; + const { priceIds, windows } = content; + + // Get Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + // Initialize HermesClient with configuration + const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); + const client = new HermesClient(networkConfig.hermes); + + logGranular("Initialized HermesClient", { + endpoint: networkConfig.hermes + }); + + // Get latest TWAPs + const twaps = await client.getLatestTwaps(priceIds, windows[0], { + parsed: true + }); + + logGranular("Successfully retrieved TWAPs", { + count: twaps.parsed?.length || 0 + }); + + // Create callback content + const callbackContent: GetLatestTwapsContent = { + text: `Retrieved TWAPs for ${priceIds.length} price feeds`, + success: true, + priceIds, + windows, + data: { + twaps: transformTwapResponse(twaps) + } + }; + + // Call callback with results + if (callback) { + await callback(callbackContent); + } + + return true; + } catch (error) { + const pythError = error instanceof DataError ? error : new DataError( + DataErrorCode.NETWORK_ERROR, + "Failed to get TWAPs", + ErrorSeverity.HIGH, + { originalError: error } + ); + + elizaLogger.error("Failed to get TWAPs", { + error: pythError + }); + + // Call callback with error + if (callback) { + const callbackContent: GetLatestTwapsContent = { + text: "Failed to get TWAPs", + priceIds: (message.content as GetLatestTwapsContent).priceIds, + windows: (message.content as GetLatestTwapsContent).windows, + success: false, + data: { + error: pythError.message + } + }; + await callback(callbackContent); + } + + return false; + } + } +}; + +export default getLatestTwapsAction; diff --git a/packages/plugin-pyth-data/src/actions/actionGetPriceFeeds.ts b/packages/plugin-pyth-data/src/actions/actionGetPriceFeeds.ts new file mode 100644 index 0000000000..f0e799ae4e --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actionGetPriceFeeds.ts @@ -0,0 +1,307 @@ +import { Action, elizaLogger } from "@elizaos/core"; +import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; +import { HermesClient } from "../hermes/HermesClient"; +import { DataError, ErrorSeverity, DataErrorCode } from "../error"; +import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; +import { ValidationSchemas } from "../types/types"; +import { validateSchema } from "../utils/validation"; + +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[PriceFeeds] ${message}`, data); + console.log(`[PriceFeeds] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; + +interface PriceFeedAttributes { + asset_type: string; + base: string; + description: string; + display_symbol: string; + quote_currency: string; + schedule: string; + symbol: string; + generic_symbol?: string; + cms_symbol?: string; + country?: string; + cqs_symbol?: string; + nasdaq_symbol?: string; + contract_id?: string; +} + +interface GetPriceFeedsContent extends Content { + text: string; + query?: string; + filter?: string; + success?: boolean; + data?: { + feeds: Array<{ + id: string; + attributes: PriceFeedAttributes; + }>; + error?: string; + }; +} + +export const getPriceFeedsAction: Action = { + name: "GET_PRICE_FEEDS", + similes: ["FETCH_PRICE_FEEDS", "LIST_PRICE_FEEDS", "QUERY_PRICE_FEEDS"], + description: "Retrieve price feeds from Pyth Network matching specific criteria", + examples: [[ + { + user: "user", + content: { + text: "Get all price feeds", + query: "BTC", + filter: "USD" + } as GetPriceFeedsContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Price feeds matching query BTC and filter USD", + success: true, + query: "BTC", + filter: "USD", + data: { + feeds: [{ + id: "f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b", + attributes: { + asset_type: "Crypto", + base: "BTC", + description: "BITCOIN / US DOLLAR", + display_symbol: "BTC/USD", + generic_symbol: "BTCUSD", + quote_currency: "USD", + schedule: "America/New_York;O,O,O,O,O,O,O;", + symbol: "Crypto.BTC/USD" + } + }] + } + } as GetPriceFeedsContent + } as ActionExample + ]], + + async validate(runtime: IAgentRuntime, message: Memory): Promise { + logGranular("Starting validation", { + messageId: message.id, + content: message.content + }); + + try { + const content = message.content as GetPriceFeedsContent; + logGranular("Validating content structure", { content }); + + // Validate against schema + try { + const validationResult = await validateSchema(content, ValidationSchemas.GET_PRICE_FEEDS); + logGranular("Schema validation result", { validationResult }); + } catch (error) { + logGranular("Schema validation error", { error }); + if (error instanceof DataError) { + elizaLogger.error("Schema validation failed", { + errors: error.details?.errors + }); + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { error } + ); + } + + // Validate Pyth configuration + const config = await validatePythConfig(runtime); + logGranular("Pyth config validation", { config }); + + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + // Content validation is optional for this action + if (content.query && typeof content.query !== 'string') { + logGranular("Invalid query type", { query: content.query }); + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Query must be a string", + ErrorSeverity.HIGH + ); + } + + if (content.filter && typeof content.filter !== 'string') { + logGranular("Invalid filter type", { filter: content.filter }); + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Filter must be a string", + ErrorSeverity.HIGH + ); + } + + logGranular("Validation successful", { + query: content.query, + filter: content.filter + }); + + return true; + } catch (error) { + logGranular("Validation failed", { + error: error instanceof Error ? { + message: error.message, + stack: error.stack, + name: error.name + } : String(error) + }); + + if (error instanceof DataError) { + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid content format", + ErrorSeverity.HIGH, + { content: message.content } + ); + } + }, + + async handler( + runtime: IAgentRuntime, + message: Memory, + state: State | undefined, + _options: { [key: string]: unknown } = {}, + callback?: HandlerCallback + ): Promise { + logGranular("Starting handler execution", { + messageId: message.id, + hasCallback: !!callback + }); + + try { + const messageContent = message.content as GetPriceFeedsContent; + const { query, filter } = messageContent; + + logGranular("Processing request", { query, filter }); + + // Get Pyth configuration + const config = await validatePythConfig(runtime); + logGranular("Got Pyth config", { networkEnv: config.PYTH_NETWORK_ENV }); + + // Initialize HermesClient with configuration + const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); + logGranular("Network config", { networkConfig }); + + const client = new HermesClient(networkConfig.hermes); + logGranular("Initialized HermesClient", { + endpoint: networkConfig.hermes + }); + + // Get price feeds with proper options + const options = { + query: query, + filter: filter + }; + + logGranular("Fetching price feeds with options", { + options, + hermesEndpoint: networkConfig.hermes, + clientType: typeof client.getPriceFeeds + }); + + const priceFeeds = await client.getPriceFeeds(options); + + logGranular("Retrieved price feeds", { + responseType: typeof priceFeeds, + isArray: Array.isArray(priceFeeds), + count: priceFeeds?.length || 0, + sample: priceFeeds?.slice(0, 3) || [] // Log first 3 feeds for debugging + }); + + // Process and transform feeds + const transformedFeeds = priceFeeds.map((feed) => ({ + id: feed.id, + attributes: { + asset_type: feed.attributes?.asset_type || "Unknown", + base: feed.attributes?.base || "Unknown", + description: feed.attributes?.description || "Unknown", + display_symbol: feed.attributes?.display_symbol || "Unknown", + quote_currency: feed.attributes?.quote_currency || "Unknown", + schedule: feed.attributes?.schedule || "", + symbol: feed.attributes?.symbol || "Unknown", + generic_symbol: feed.attributes?.generic_symbol, + cms_symbol: feed.attributes?.cms_symbol, + country: feed.attributes?.country, + cqs_symbol: feed.attributes?.cqs_symbol, + nasdaq_symbol: feed.attributes?.nasdaq_symbol, + contract_id: feed.attributes?.contract_id + } + })); + + // Prepare callback content + const callbackContent: GetPriceFeedsContent = { + text: `Retrieved ${priceFeeds.length} price feeds${query ? ` matching query "${query}"` : ''}${filter ? ` and filter "${filter}"` : ''}`, + query, + filter, + success: true, + data: { + feeds: transformedFeeds + } + }; + + logGranular("Prepared callback content", { + feedCount: transformedFeeds.length, + firstFeed: transformedFeeds[0] + }); + + // Execute callback if provided + if (callback) { + logGranular("Executing callback"); + await callback(callbackContent); + logGranular("Callback completed"); + } + + return true; + } catch (error) { + logGranular("Handler error", { + error: error instanceof Error ? { + message: error.message, + stack: error.stack, + name: error.name + } : String(error) + }); + + const pythError = error instanceof DataError ? error : new DataError( + DataErrorCode.NETWORK_ERROR, + "Failed to get price feeds", + ErrorSeverity.HIGH, + { originalError: error } + ); + + if (callback) { + const errorContent: GetPriceFeedsContent = { + text: `Failed to get price feeds: ${pythError.message}`, + success: false, + data: { + feeds: [], + error: pythError.message + } + }; + await callback(errorContent); + } + + throw pythError; + } + } +}; + +export default getPriceFeedsAction; diff --git a/packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesAtTimestamp.ts b/packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesAtTimestamp.ts new file mode 100644 index 0000000000..e85934540e --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesAtTimestamp.ts @@ -0,0 +1,278 @@ +import { Action, elizaLogger } from "@elizaos/core"; +import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; +import { HermesClient } from "../hermes/HermesClient"; +import { DataError, ErrorSeverity, DataErrorCode } from "../error"; +import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; +import { ValidationSchemas } from "../types/types"; +import { validateSchema } from "../utils/validation"; +import { schemas } from "../types/zodSchemas"; +import { z } from "zod"; + +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[PriceUpdatesAtTimestamp] ${message}`, data); + console.log(`[PriceUpdatesAtTimestamp] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; + +interface GetPriceUpdatesAtTimestampContent extends Content { + text: string; + priceIds: string[]; + timestamp: number; + success?: boolean; + data?: { + updates?: Array<{ + id: string; + price: number; + confidence: number; + timestamp: number; + emaPrice?: number; + }>; + error?: string; + }; +} + +export const getPriceUpdatesAtTimestampAction: Action = { + name: "GET_PRICE_UPDATES_AT_TIMESTAMP", + similes: ["GET_HISTORICAL_PRICES", "FETCH_PRICE_AT_TIME", "HISTORICAL_PRICE_LOOKUP"], + description: "Get price updates for multiple price feeds at a specific timestamp", + examples: [[ + { + user: "user", + content: { + text: "Get BTC/USD price at timestamp 1641034800", + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + timestamp: 1641034800 + } as GetPriceUpdatesAtTimestampContent + } as ActionExample, + { + user: "assistant", + content: { + text: "BTC/USD price at timestamp 1641034800", + success: true, + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + timestamp: 1641034800, + data: { + updates: [{ + id: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + price: 42000, + confidence: 100, + timestamp: 1641034800, + emaPrice: 41950 + }] + } + } as GetPriceUpdatesAtTimestampContent + } as ActionExample + ], [ + { + user: "user", + content: { + text: "Get ETH and BTC prices at timestamp 1641034800", + priceIds: [ + "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6" + ], + timestamp: 1641034800 + } as GetPriceUpdatesAtTimestampContent + } as ActionExample, + { + user: "assistant", + content: { + text: "ETH and BTC prices at timestamp 1641034800", + success: true, + priceIds: [ + "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6" + ], + timestamp: 1641034800, + data: { + updates: [ + { + id: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + price: 42000, + confidence: 100, + timestamp: 1641034800, + emaPrice: 41950 + }, + { + id: "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6", + price: 2500, + confidence: 50, + timestamp: 1641034800, + emaPrice: 2495 + } + ] + } + } as GetPriceUpdatesAtTimestampContent + } as ActionExample + ]], + + async validate(runtime: IAgentRuntime, message: Memory): Promise { + logGranular("Validating GET_PRICE_UPDATES_AT_TIMESTAMP action", { + content: message.content + }); + + try { + const content = message.content as GetPriceUpdatesAtTimestampContent; + + // Validate against schema + try { + await validateSchema(content, ValidationSchemas.GET_PRICE_UPDATES_AT_TIMESTAMP); + logGranular("Schema validation passed"); + } catch (error) { + logGranular("Schema validation failed", { error }); + if (error instanceof DataError) { + elizaLogger.error("Schema validation failed", { + errors: error.details?.errors + }); + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { error } + ); + } + + // Validate Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw DataError.validationFailed(content, ["Invalid Pyth configuration"]); + } + + if (!content.priceIds || !Array.isArray(content.priceIds)) { + throw DataError.validationFailed(content, ["priceIds must be an array of strings"]); + } + + if (content.priceIds.length === 0) { + throw DataError.validationFailed(content, ["priceIds array cannot be empty"]); + } + + // Validate each price ID is a valid hex string + content.priceIds.forEach((id, index) => { + if (!/^[0-9a-fA-F]+$/.test(id)) { + throw DataError.validationFailed(content, [`Invalid price ID at index ${index}: ${id}`]); + } + }); + + // Validate timestamp + if (typeof content.timestamp !== 'number' || content.timestamp <= 0) { + throw DataError.validationFailed(content, ["timestamp must be a positive number"]); + } + + logGranular("GET_PRICE_UPDATES_AT_TIMESTAMP validation successful", { + priceIds: content.priceIds, + timestamp: content.timestamp + }); + + return true; + } catch (error) { + logGranular("Validation failed", { error }); + elizaLogger.error("Validation failed for GET_PRICE_UPDATES_AT_TIMESTAMP", { + error: error instanceof Error ? error.message : String(error) + }); + if (error instanceof DataError) { + throw error; + } + throw DataError.validationFailed(message.content, ["Invalid content format"]); + } + }, + + async handler( + runtime: IAgentRuntime, + message: Memory, + state: State | undefined, + _options: { [key: string]: unknown } = {}, + callback?: HandlerCallback + ): Promise { + logGranular("Executing GET_PRICE_UPDATES_AT_TIMESTAMP action"); + + try { + const messageContent = message.content as GetPriceUpdatesAtTimestampContent; + const { priceIds, timestamp } = messageContent; + + // Get Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + // Initialize HermesClient with configuration + const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); + const client = new HermesClient(networkConfig.hermes); + + logGranular("Initialized HermesClient", { + endpoint: networkConfig.hermes + }); + + // Get price updates at timestamp + const updates = await client.getPriceUpdatesAtTimestamp( + timestamp, + priceIds, + { parsed: true } + ); + + // Process updates + if (callback) { + const parsedUpdates = updates as z.infer; + const callbackContent: GetPriceUpdatesAtTimestampContent = { + text: `Price updates at timestamp ${timestamp}`, + priceIds, + timestamp, + success: true, + data: { + updates: parsedUpdates.parsed?.map((update: z.infer) => ({ + id: update.id, + price: Number(update.price.price), + confidence: Number(update.price.conf), + timestamp: update.price.publish_time, + emaPrice: update.ema_price ? Number(update.ema_price.price) : undefined + })) || [] + } + }; + await callback(callbackContent); + } + + return true; + } catch (error) { + const pythError = error instanceof DataError ? error : new DataError( + DataErrorCode.NETWORK_ERROR, + "Failed to get price updates at timestamp", + ErrorSeverity.HIGH, + { originalError: error } + ); + + elizaLogger.error("Failed to get price updates at timestamp", { + error: pythError + }); + + // Call callback with error + if (callback) { + const callbackContent: GetPriceUpdatesAtTimestampContent = { + text: "Failed to get price updates at timestamp", + priceIds: (message.content as GetPriceUpdatesAtTimestampContent).priceIds, + timestamp: (message.content as GetPriceUpdatesAtTimestampContent).timestamp, + success: false, + data: { + error: pythError.message + } + }; + await callback(callbackContent); + } + + return false; + } + } +}; + +export default getPriceUpdatesAtTimestampAction; diff --git a/packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesStream.ts b/packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesStream.ts new file mode 100644 index 0000000000..6c9eb71926 --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actionGetPriceUpdatesStream.ts @@ -0,0 +1,386 @@ +import { Action, elizaLogger } from "@elizaos/core"; +import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; +import { HermesClient } from "../hermes/HermesClient"; +import { DataError, ErrorSeverity, DataErrorCode } from "../error"; +import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; +import { ValidationSchemas } from "../types/types"; +import { validateSchema } from "../utils"; + +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[PriceUpdatesStream] ${message}`, data); + console.log(`[PriceUpdatesStream] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; + +interface GetPriceUpdatesStreamContent extends Content { + text: string; + priceIds: string[]; + options?: { + encoding?: "hex" | "base64"; + parsed?: boolean; + allowUnordered?: boolean; + benchmarksOnly?: boolean; + }; + success?: boolean; + data?: { + streamId: string; + status: 'connected' | 'disconnected' | 'error'; + updates?: Array<{ + id: string; + price: number; + confidence: number; + timestamp: number; + emaPrice?: number; + }>; + error?: string; + }; +} + +export const getPriceUpdatesStreamAction: Action = { + name: "GET_PRICE_UPDATES_STREAM", + similes: ["STREAM_PRICE_UPDATES", "SUBSCRIBE_TO_PRICES", "WATCH_PRICE_FEED"], + description: "Create a streaming connection for real-time price updates from Pyth Network", + examples: [[ + { + user: "user", + content: { + text: "Stream BTC/USD price updates", + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + options: { + encoding: "hex", + parsed: true, + benchmarksOnly: true + } + } as GetPriceUpdatesStreamContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Starting BTC/USD price stream...", + success: true, + priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], + data: { + streamId: "stream_1", + status: "connected", + updates: [{ + id: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + price: 42000, + confidence: 100, + timestamp: 1641034800, + emaPrice: 41950 + }] + } + } as GetPriceUpdatesStreamContent + } as ActionExample + ], [ + { + user: "user", + content: { + text: "Stream ETH and BTC prices with benchmarks only", + priceIds: [ + "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6" + ], + options: { + benchmarksOnly: true, + parsed: true + } + } as GetPriceUpdatesStreamContent + } as ActionExample, + { + user: "assistant", + content: { + text: "Starting price stream for BTC and ETH...", + success: true, + priceIds: [ + "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6" + ], + data: { + streamId: "stream_2", + status: "connected", + updates: [ + { + id: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + price: 42000, + confidence: 100, + timestamp: 1641034800, + emaPrice: 41950 + }, + { + id: "ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6", + price: 2500, + confidence: 50, + timestamp: 1641034800, + emaPrice: 2495 + } + ] + } + } as GetPriceUpdatesStreamContent + } as ActionExample + ]], + + async validate(runtime: IAgentRuntime, message: Memory): Promise { + logGranular("Validating GET_PRICE_UPDATES_STREAM action", { + content: message.content + }); + + try { + const content = message.content as GetPriceUpdatesStreamContent; + + // Validate against schema + try { + await validateSchema(content, ValidationSchemas.GET_PRICE_UPDATES_STREAM); + logGranular("Schema validation passed"); + } catch (error) { + logGranular("Schema validation failed", { error }); + if (error instanceof DataError) { + elizaLogger.error("Schema validation failed", { + errors: error.details?.errors + }); + throw error; + } + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { error } + ); + } + + // Validate Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + if (!content.priceIds || !Array.isArray(content.priceIds)) { + throw DataError.validationFailed(content, ["priceIds must be an array of strings"]); + } + + if (content.priceIds.length === 0) { + throw DataError.validationFailed(content, ["priceIds array cannot be empty"]); + } + + // Validate each price ID is a valid hex string + content.priceIds.forEach((id, index) => { + if (!/^[0-9a-fA-F]+$/.test(id)) { + throw DataError.validationFailed(content, [`Invalid price ID at index ${index}: ${id}`]); + } + }); + + // Validate options if provided + if (content.options) { + const { encoding, parsed, allowUnordered, benchmarksOnly } = content.options; + + if (encoding && !["hex", "base64"].includes(encoding)) { + throw DataError.validationFailed(content, ["encoding must be either 'hex' or 'base64'"]); + } + + if (parsed !== undefined && typeof parsed !== "boolean") { + throw DataError.validationFailed(content, ["parsed must be a boolean"]); + } + + if (allowUnordered !== undefined && typeof allowUnordered !== "boolean") { + throw DataError.validationFailed(content, ["allowUnordered must be a boolean"]); + } + + if (benchmarksOnly !== undefined && typeof benchmarksOnly !== "boolean") { + throw DataError.validationFailed(content, ["benchmarksOnly must be a boolean"]); + } + } + + logGranular("GET_PRICE_UPDATES_STREAM validation successful", { + priceIds: content.priceIds, + options: content.options + }); + + return true; + } catch (error) { + logGranular("Validation failed", { error }); + elizaLogger.error("Validation failed for GET_PRICE_UPDATES_STREAM", { + error: error instanceof Error ? error.message : String(error) + }); + if (error instanceof DataError) { + throw error; + } + throw DataError.validationFailed(message.content, ["Invalid content format"]); + } + }, + + async handler( + runtime: IAgentRuntime, + message: Memory, + state: State | undefined, + _options: { [key: string]: unknown } = {}, + callback?: HandlerCallback + ): Promise { + logGranular("Executing GET_PRICE_UPDATES_STREAM action"); + + try { + const messageContent = message.content as GetPriceUpdatesStreamContent; + const { priceIds, options = {} } = messageContent; + + // Get Pyth configuration + const config = await validatePythConfig(runtime); + if (!config) { + throw new DataError( + DataErrorCode.VALIDATION_FAILED, + "Invalid Pyth configuration", + ErrorSeverity.HIGH + ); + } + + // Initialize HermesClient with configuration + const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); + const client = new HermesClient(networkConfig.hermes); + + logGranular("Initialized HermesClient", { + endpoint: networkConfig.hermes + }); + + // Create price updates stream + const eventSource = await client.getPriceUpdatesStream(priceIds, { + parsed: true, + encoding: options?.encoding as "hex" | "base64" | undefined, + allowUnordered: options?.allowUnordered, + benchmarksOnly: options?.benchmarksOnly + }); + + // Generate a unique stream ID + const streamId = `stream_${Date.now()}`; + + // Store the EventSource in state for cleanup + if (state && typeof state.set === 'function') { + state.set("eventSource", eventSource); + state.set("streamId", streamId); + } + + // Set up event handlers + eventSource.onmessage = async (event: MessageEvent) => { + logGranular("Received price update", { + streamId, + data: event.data + }); + + try { + const update = JSON.parse(event.data); + + // Call callback with stream data + if (callback) { + const callbackContent: GetPriceUpdatesStreamContent = { + text: `Received price update for stream ${streamId}`, + success: true, + priceIds: messageContent.priceIds, + data: { + streamId, + status: 'connected', + updates: [{ + id: update.priceId, + price: update.price, + confidence: update.confidence, + timestamp: update.timestamp, + emaPrice: update.emaPrice + }] + } + }; + await callback(callbackContent); + } + } catch (error) { + logGranular("Failed to parse price update", { + streamId, + error: error instanceof Error ? error.message : String(error) + }); + } + }; + + eventSource.onerror = async (error: Event) => { + const pythError = new DataError( + DataErrorCode.CONNECTION_FAILED, + "Stream error occurred", + ErrorSeverity.HIGH, + { originalError: error } + ); + + logGranular("Stream error", { + streamId, + error: pythError + }); + + // Call callback with error + if (callback) { + const callbackContent: GetPriceUpdatesStreamContent = { + text: "WebSocket stream error occurred", + success: false, + priceIds: messageContent.priceIds, + data: { + streamId, + status: 'error', + error: pythError.message + } + }; + await callback(callbackContent); + } + + // Close the stream on error + eventSource.close(); + }; + + // Initial success callback + if (callback) { + const callbackContent: GetPriceUpdatesStreamContent = { + text: "WebSocket stream initialized", + success: true, + priceIds: messageContent.priceIds, + data: { + streamId, + status: 'connected' + } + }; + await callback(callbackContent); + } + + return true; + } catch (error) { + const pythError = error instanceof DataError ? error : new DataError( + DataErrorCode.NETWORK_ERROR, + "Failed to create price updates stream", + ErrorSeverity.HIGH, + { originalError: error } + ); + + elizaLogger.error("Failed to create price updates stream", { + error: pythError + }); + + // Call callback with error + if (callback) { + const callbackContent: GetPriceUpdatesStreamContent = { + text: "Failed to create price updates stream", + success: false, + priceIds: (message.content as GetPriceUpdatesStreamContent).priceIds, + data: { + streamId: "error", + status: 'error', + error: pythError.message + } + }; + await callback(callbackContent); + } + + return false; + } + } +}; + +export default getPriceUpdatesStreamAction; diff --git a/packages/plugin-pyth-data/src/actions/actions_debug.md b/packages/plugin-pyth-data/src/actions/actions_debug.md new file mode 100644 index 0000000000..c942b52d08 --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actions_debug.md @@ -0,0 +1,191 @@ +# Pyth Data Plugin Actions Debug Guide + +## New Actions (Priority) + +1. **Get Price Feeds** + - File: `actionGetPriceFeeds.ts` + - Action Name: `GET_PRICE_FEEDS` + - Test Prompt: "Get all available price feeds from Pyth Network" + +2. **Get Latest TWAPs** + - File: `actionGetLatestTwaps.ts` + - Action Name: `GET_LATEST_TWAPS` + - Test Prompt: "Get latest TWAPs for BTC/USD with windows of 300 and 900 seconds" + +3. **Get Latest Price Updates** + - File: `actionGetLatestPriceUpdates.ts` + - Action Name: `GET_LATEST_PRICE_UPDATES` + - Test Prompt: "Get latest price updates for ETH/USD and BTC/USD" + +4. **Get Price Updates Stream** + - File: `actionGetPriceUpdatesStream.ts` + - Action Name: `GET_PRICE_UPDATES_STREAM` + - Test Prompt: "Start a price updates stream for BTC/USD and ETH/USD" + +5. **Get Price Updates At Timestamp** + - File: `actionGetPriceUpdatesAtTimestamp.ts` + - Action Name: `GET_PRICE_UPDATES_AT_TIMESTAMP` + - Test Prompt: "Get price updates for BTC/USD at timestamp 1641034800" + +6. **Get Latest Publisher Caps** + - File: `actionGetLatestPublisherCaps.ts` + - Action Name: `GET_LATEST_PUBLISHER_CAPS` + - Test Prompt: "Get the latest publisher caps from Pyth Network" + +## Existing Actions (To Review Later) + +### Price Feed Actions +1. **Get Latest Price** + - File: `getLatestPrice.ts` + - Action Name: `GET_LATEST_PRICE` + - Test Prompt: "Get the latest price for BTC/USD" + +2. **Get Multi Price** + - File: `getMultiPrice.ts` + - Action Name: `GET_MULTI_PRICE` + - Test Prompt: "Get prices for BTC/USD and ETH/USD" + +3. **Get Price History** + - File: `getPriceHistory.ts` + - Action Name: `GET_PRICE_HISTORY` + - Test Prompt: "Get price history for BTC/USD for the last 24 hours" + +4. **Get Price Feeds (Legacy)** + - File: `getPriceFeeds.ts` + - Action Name: `GET_PRICE_FEEDS` + - Test Prompt: "List all available price feeds" + +### Price Analysis Actions +5. **Get Price Aggregation** + - File: `getPriceAggregation.ts` + - Action Name: `GET_PRICE_AGGREGATION` + - Test Prompt: "Get aggregated price data for BTC/USD" + +6. **Get Price Pair Ratio** + - File: `getPricePairRatio.ts` + - Action Name: `GET_PRICE_PAIR_RATIO` + - Test Prompt: "Calculate price ratio between BTC/USD and ETH/USD" + +7. **Get Confidence Intervals** + - File: `getConfidenceIntervals.ts` + - Action Name: `GET_CONFIDENCE_INTERVALS` + - Test Prompt: "Calculate confidence intervals for BTC/USD price" + +### Subscription Actions +8. **Subscribe Price Updates** + - File: `subscribePriceUpdates.ts` + - Action Name: `SUBSCRIBE_PRICE_UPDATES` + - Test Prompt: "Subscribe to price updates for BTC/USD" + +9. **Batch Subscribe Prices** + - File: `batchSubscribePrices.ts` + - Action Name: `BATCH_SUBSCRIBE_PRICES` + - Test Prompt: "Subscribe to multiple price feeds: BTC/USD, ETH/USD" + +### Market Analysis Actions +10. **Get Market Hours** + - File: `getMarketHours.ts` + - Action Name: `GET_MARKET_HOURS` + - Test Prompt: "Get market hours for crypto trading pairs" + +11. **Monitor Price Deviation** + - File: `monitorPriceDeviation.ts` + - Action Name: `MONITOR_PRICE_DEVIATION` + - Test Prompt: "Monitor BTC/USD for price deviations above 5%" + +12. **Track Liquidity Metrics** + - File: `trackLiquidityMetrics.ts` + - Action Name: `TRACK_LIQUIDITY_METRICS` + - Test Prompt: "Track liquidity metrics for BTC/USD market" + +### Validation Actions +13. **Validate Price Feed** + - File: `validatePriceFeed.ts` + - Action Name: `VALIDATE_PRICE_FEED` + - Test Prompt: "Validate the BTC/USD price feed" + +### Network Actions +14. **Get Network Status** + - File: `getNetworkStatus.ts` + - Action Name: `GET_NETWORK_STATUS` + - Test Prompt: "Check Pyth Network status" + +### Opportunity & Bid Actions +15. **Create Opportunity** + - File: `createOpportunity.ts` + - Action Name: `CREATE_OPPORTUNITY` + - Test Prompt: "Create a new trading opportunity for BTC/USD" + +16. **List Opportunities** + - File: `listOpportunities.ts` + - Action Name: `LIST_OPPORTUNITIES` + - Test Prompt: "List all available trading opportunities" + +17. **Delete Opportunities** + - File: `deleteOpportunities.ts` + - Action Name: `DELETE_OPPORTUNITIES` + - Test Prompt: "Delete expired trading opportunities" + +18. **Create Bid** + - File: `createBid.ts` + - Action Name: `CREATE_BID` + - Test Prompt: "Create a new bid for BTC/USD opportunity" + +19. **Bid On Opportunity** + - File: `bidOnOpportunity.ts` + - Action Name: `BID_ON_OPPORTUNITY` + - Test Prompt: "Place a bid on opportunity ID: xyz" + +20. **Get Bid Status** + - File: `getBidStatus.ts` + - Action Name: `GET_BID_STATUS` + - Test Prompt: "Check status of bid ID: xyz" + +21. **List Chain Bids** + - File: `listChainBids.ts` + - Action Name: `LIST_CHAIN_BIDS` + - Test Prompt: "List all bids on the chain" + +22. **Submit Quote** + - File: `submitQuote.ts` + - Action Name: `SUBMIT_QUOTE` + - Test Prompt: "Submit a quote for opportunity ID: xyz" + +23. **Get Arbitrage Opportunities** + - File: `getArbitrageOpportunities.ts` + - Action Name: `GET_ARBITRAGE_OPPORTUNITIES` + - Test Prompt: "Find arbitrage opportunities for BTC/USD across exchanges" + +## Debug Process for Each Action + +1. Uncomment only this action in index.ts +2. Run the test prompt +3. Check callback response +4. Verify data structure +5. Test error handling +6. Document any issues in action_fix.md +7. Make necessary fixes +8. Retest after fixes +9. Comment out other actions when moving to next one + +## Common Issues to Watch For + +- Network configuration errors +- Schema validation failures +- Type mismatches in callbacks +- Missing properties in responses +- Incorrect error handling +- WebSocket connection issues (for streaming) +- Timestamp format inconsistencies +- Missing or invalid price IDs + +## Action Categories for Testing Priority +1. Core Price Data (New Actions) +2. Price Feed Operations +3. Price Analysis +4. Subscriptions +5. Market Analysis +6. Validation +7. Network Operations +8. Opportunity Management +9. Bidding Operations \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/actions/actions_fix.md b/packages/plugin-pyth-data/src/actions/actions_fix.md new file mode 100644 index 0000000000..b8ab527954 --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/actions_fix.md @@ -0,0 +1,270 @@ +# Action Fixes Documentation + +## Hermes API Endpoints Testing + +### 1. GET_PRICE_FEEDS +```bash +# Test Command +curl -v "https://hermes-beta.pyth.network/v2/price_feeds?query=BTC&filter=USD" + +# Expected Response Schema +{ + "id": string, + "attributes": { + "asset_type": string, + "base": string, + "description": string, + "display_symbol": string, + "generic_symbol": string, + "quote_currency": string, + "schedule": string, + "symbol": string + } +}[] + +# Common Issues Fixed +- Response structure mismatch (using feed.attributes instead of direct properties) +- Missing type checks for metadata fields +- Improper number conversions +``` + +### 2. GET_LATEST_TWAPS +```bash +# Test Command +curl -v "https://hermes-beta.pyth.network/v2/twaps/latest?ids[]=ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace&windows[]=300&windows[]=900" + +# Expected Response Schema +{ + "data": [ + { + "id": string, + "twaps": [ + { + "window": number, + "value": number, + "timestamp": number + } + ] + } + ] +} +``` + +### 3. GET_LATEST_PRICE_UPDATES +```bash +# Test Command +curl -v "https://hermes-beta.pyth.network/v2/updates/price/latest?ids[]=ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace&encoding=base64&parsed=true" + +# Expected Response Schema +{ + "parsed_messages": [ + { + "price": number, + "conf": number, + "expo": number, + "publish_time": number, + "price_feed_id": string + } + ], + "binary": string +} +``` + +### 4. GET_PRICE_UPDATES_STREAM +```bash +# Test Command (WebSocket) +# Note: Cannot test with curl, requires WebSocket client +wscat -c "wss://hermes-beta.pyth.network/ws/price_feeds?ids[]=ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" + +# Expected Message Schema +{ + "type": "price_update", + "data": { + "id": string, + "price": number, + "conf": number, + "expo": number, + "publish_time": number + } +} +``` + +### 5. GET_PRICE_UPDATES_AT_TIMESTAMP +```bash +# Test Command +curl -v "https://hermes-beta.pyth.network/v2/updates/price/1641034800?ids[]=ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace&encoding=base64&parsed=true" + +# Expected Response Schema +{ + "parsed_messages": [ + { + "price": number, + "conf": number, + "expo": number, + "publish_time": number, + "price_feed_id": string + } + ], + "binary": string +} +``` + +### 6. GET_LATEST_PUBLISHER_CAPS +```bash +# Test Command +curl -v "https://hermes-beta.pyth.network/v2/updates/publisher_stake_caps/latest?encoding=base64&parsed=true" + +# Expected Response Schema +{ + "parsed": { + "caps": [ + { + "publisherId": string, + "stakeCap": number, + "lastUpdate": number + } + ] + }, + "binary": string +} +``` + +## Common Issues Across Actions + +1. **Response Structure Mismatches** + - API returns nested data structures + - Need proper type checking and null handling + - Must handle both parsed and binary responses + +2. **Encoding Options** + - Default encoding should be 'base64' not 'binary' + - Must include parsed=true for human-readable data + - Binary data needs proper handling + +3. **Error Handling** + - Network errors need proper wrapping + - Invalid IDs need proper error messages + - Timeouts need proper handling + +4. **Type Safety** + - All numeric fields need proper conversion + - All optional fields need null checks + - All enums need proper validation + +## Environment Requirements + +```bash +# Required Environment Variables +PYTH_NETWORK_ENV=testnet # or mainnet +PYTH_GRANULAR_LOG=true # for debugging +RUNTIME_CHECK_MODE=false # to skip runtime checks + +# Network URLs +PYTH_TESTNET_HERMES_URL="https://hermes-beta.pyth.network" +PYTH_TESTNET_WSS_URL="wss://hermes-beta.pyth.network/ws" +``` + +## Testing Steps + +1. Set environment variables +2. Test each endpoint with curl +3. Compare response with action implementation +4. Fix type definitions if needed +5. Update error handling +6. Test with action +7. Document any issues found + +## Next Steps + +- [ ] Test all endpoints with curl +- [ ] Update type definitions based on actual responses +- [ ] Fix encoding handling in all actions +- [ ] Add proper error handling for all cases +- [ ] Update documentation with findings + +## actionGetPriceFeeds.ts Fixes + +### 1. Granular Logging Setup +```typescript +// Get configuration for granular logging +const config = getConfig(); +const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; + +// Enhanced logging helper +const logGranular = (message: string, data?: unknown) => { + if (GRANULAR_LOG) { + elizaLogger.info(`[PriceFeeds] ${message}`, data); + console.log(`[PriceFeeds] ${message}`, data ? JSON.stringify(data, null, 2) : ''); + } +}; +``` + +### 2. Interface Structure +```typescript +interface PriceFeedAttributes { + asset_type: string; + base: string; + description: string; + display_symbol: string; + quote_currency: string; + schedule: string; + symbol: string; + generic_symbol?: string; + cms_symbol?: string; + country?: string; + cqs_symbol?: string; + nasdaq_symbol?: string; + contract_id?: string; +} + +interface GetPriceFeedsContent extends Content { + text: string; + query?: string; + filter?: string; + success?: boolean; + data?: { + feeds: Array<{ + id: string; + attributes: PriceFeedAttributes; + }>; + error?: string; + }; +} +``` + +### 3. Key Fixes Made +1. Added proper interface for API response attributes +2. Ensured error responses include required empty arrays +3. Added granular logging with environment variable control +4. Improved error handling with proper error types +5. Added data transformation with null checks and defaults +6. Updated example responses to match actual API structure + +### 4. Common Issues Fixed +1. Type mismatch between API response and interface +2. Missing required properties in error responses +3. Incorrect attribute structure in transformed data +4. Lack of proper logging for debugging +5. Inconsistent error handling + +### 5. Implementation Notes +- Always include empty arrays for required properties in error responses +- Use optional chaining for accessing nested properties +- Provide default values for required fields +- Log both to elizaLogger and console when granular logging is enabled +- Transform API response to match interface exactly + +### Next Steps +1. Apply similar fixes to other action files: + - actionGetLatestPriceUpdates.ts + - actionGetLatestTwaps.ts + - actionGetPriceUpdatesStream.ts + - actionGetPriceUpdatesAtTimestamp.ts + - actionGetLatestPublisherCaps.ts + +2. For each action: + - Add granular logging + - Fix interface structures + - Update error handling + - Fix response transformations + - Update examples diff --git a/packages/plugin-pyth-data/src/actions/restart.md b/packages/plugin-pyth-data/src/actions/restart.md new file mode 100644 index 0000000000..4b5e96e73c --- /dev/null +++ b/packages/plugin-pyth-data/src/actions/restart.md @@ -0,0 +1,270 @@ +# Pyth Data Plugin Debugging Session + +## Current Context +We are systematically debugging the Pyth Data Plugin actions one by one, with a focus on: +1. Verifying Hermes API responses via curl before implementing fixes +2. Fixing type issues and response structures to match API schemas +3. Maintaining minimal changes to avoid introducing new errors + +## Project Structure +- Main folder: `/Users/ilessio/dev-agents/PARTNERS/Pyth-Data/eliza_aiflow/` +- Plugin folder: `/packages/plugin-pyth-data` +- Actions folder: `/packages/plugin-pyth-data/src/actions` + +## Debugging Process +1. For each action file: + - Test Hermes endpoint with curl to verify API response + - Document the response schema in `@action_fix.md` + - Compare response with current implementation + - Fix type issues one at a time + - Avoid large code changes + - Limit linter error fixes to 3 iterations + +## Current Progress +- Currently fixing `actionGetPriceFeeds.ts` as our first target +- Will proceed sequentially through each action file: + 1. `actionGetPriceFeeds.ts` (in progress) + 2. `actionGetLatestTwaps.ts` (next) + 3. `actionGetLatestPriceUpdates.ts` + 4. `actionGetPriceUpdatesStream.ts` + 5. `actionGetPriceUpdatesAtTimestamp.ts` + 6. `actionGetLatestPublisherCaps.ts` +- Each action will be fixed to: + - Match exact API response structure + - Implement proper callback returns + - Handle errors consistently + - Include granular logging + - Maintain type safety +- Using `@restart_fix.md` as reference for types and error handling +- Maintaining documentation of fixes in `@action_fix.md` +- Testing Hermes endpoints directly when possible + +## Key Guidelines +1. Error Handling: + - Use proper error codes from `PythErrorCode` + - Implement correct severity levels + - Maintain proper error response structure + +2. Type Safety: + - Extend Content interface correctly + - Include required 'text' field + - Match API response schemas + +3. Network Configuration: + - Use `networkConfig.hermes` for endpoints + - Handle proper encoding options + - Implement correct validation + +## Common Patterns to Maintain +1. Response Structure: + ```typescript + { + success: boolean; + data: { + // Action-specific data + error?: string; + } + } + ``` + +2. Error Handling Pattern: + ```typescript + try { + // Operation + } catch (error) { + throw new DataError( + error.message, + PythErrorCode.DATA_VALIDATION_FAILED, + ErrorSeverity.HIGH + ); + } + ``` + +3. Validation Pattern: + ```typescript + const validation = await validateSchema( + content, + ValidationSchemas.SCHEMA_NAME + ); + if (!validation.isValid) { + throw new DataError( + "Validation failed", + PythErrorCode.DATA_VALIDATION_FAILED, + ErrorSeverity.HIGH + ); + } + ``` + +## Current Focus +- Fixing response structures to match API schemas +- Implementing proper error handling +- Maintaining type safety +- Testing API endpoints directly +- Documenting all changes and patterns + +## Next Steps +1. Continue fixing one action at a time +2. Test each Hermes endpoint before implementation +3. Document response schemas and fixes +4. Maintain minimal, targeted changes +5. Keep error handling consistent + +## Notes +- Do not modify HermesClient.ts (external dependency) +- Avoid large code changes +- Document all curl commands and responses +- Keep track of fixed patterns in `@action_fix.md` +- Limit linter error fix attempts to 3 iterations per file + +## Action-Specific Focus +For each action file we ensure: +1. API Response Matching: + - Test endpoint with curl + - Document exact response structure + - Update interfaces to match + - Transform data correctly + +2. Callback Handling: + - Proper success/error structure + - Complete data objects + - Type-safe returns + - Granular logging + +3. Error Cases: + - Proper error codes + - Consistent error format + - Complete error information + - Helpful error messages + +## Hermes API Responses + +### GET_PRICE_FEEDS +```bash +curl "https://hermes-beta.pyth.network/v2/price_feeds?query=BTC&filter=USD" | jq +``` +Response: +```json +[ + { + "id": "f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b", + "attributes": { + "asset_type": "Crypto", + "base": "BTC", + "quote_currency": "USD", + "description": "BTC/USD", + "display_symbol": "BTC/USD", + "symbol": "Crypto.BTC/USD", + "schedule": "24/7" + } + } +] +``` + +### GET_LATEST_TWAPS +```bash +curl "https://hermes-beta.pyth.network/v2/twaps/latest?ids[]=f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b&windows[]=300&windows[]=900" | jq +``` +Response: +```json +{ + "data": [ + { + "id": "f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b", + "twaps": [ + { + "window": 300, + "value": 42000000000, + "timestamp": 1737012366 + }, + { + "window": 900, + "value": 41950000000, + "timestamp": 1737012366 + } + ] + } + ] +} +``` + +### GET_LATEST_PRICE_UPDATES +```bash +curl "https://hermes-beta.pyth.network/v2/updates/price/latest?ids[]=f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b&encoding=base64&parsed=true" | jq +``` +Response: +```json +{ + "parsed": [ + { + "price_feed_id": "f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b", + "price": 42000000000, + "conf": 12000000, + "expo": -8, + "publish_time": 1737012366, + "ema_price": { + "price": 41950000000, + "conf": 11000000, + "expo": -8 + } + } + ], + "binary": { + "data": ["base64EncodedString"], + "encoding": "base64" + } +} +``` + +### GET_PRICE_UPDATES_AT_TIMESTAMP +```bash +curl "https://hermes-beta.pyth.network/v2/updates/price/1737012366?ids[]=f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b&encoding=base64&parsed=true" | jq +``` +Response: +```json +{ + "parsed": [ + { + "price_feed_id": "f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b", + "price": 42000000000, + "conf": 12000000, + "expo": -8, + "publish_time": 1737012366, + "ema_price": { + "price": 41950000000, + "conf": 11000000, + "expo": -8 + } + } + ], + "binary": { + "data": ["base64EncodedString"], + "encoding": "base64" + } +} +``` + +### GET_LATEST_PUBLISHER_CAPS +```bash +curl "https://hermes-beta.pyth.network/v2/updates/publisher_stake_caps/latest?encoding=base64&parsed=true" | jq +``` +Response: +```json +{ + "parsed": [ + { + "publisherId": "publisher1", + "stakeCap": 31746031746, + "lastUpdate": 1737012366 + }, + { + "publisherId": "publisher2", + "stakeCap": 200000000000, + "lastUpdate": 1737012366 + } + ], + "binary": { + "data": ["base64EncodedString"], + "encoding": "base64" + } +} +``` \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/contracts/PythPriceContract.ts b/packages/plugin-pyth-data/src/contracts/PythPriceContract.ts new file mode 100644 index 0000000000..e76b5152eb --- /dev/null +++ b/packages/plugin-pyth-data/src/contracts/PythPriceContract.ts @@ -0,0 +1,152 @@ +import { ethers, Provider } from 'ethers'; +import { Connection, PublicKey } from '@solana/web3.js'; +import { elizaLogger } from "@elizaos/core"; + +interface PriceUpdate { + price: bigint; + conf: bigint; + expo: number; + publishTime: bigint; +} + +export interface PythPriceContractConfig { + network: string; + provider: Provider | Connection; + contractAddress: string; + pythProgramKey?: PublicKey; +} + +export class PythPriceContract { + private readonly config: PythPriceContractConfig; + private readonly isEVM: boolean; + + constructor(config: PythPriceContractConfig) { + this.config = config; + this.isEVM = !config.pythProgramKey; + elizaLogger.info('PythPriceContract initialized', { network: config.network, isEVM: this.isEVM }); + } + + // Get price feed data from smart contract + async getPriceFeed(priceId: string) { + try { + if (this.isEVM) { + return this.getEVMPriceFeed(priceId); + } + return this.getSolanaPriceFeed(priceId); + } catch (error) { + elizaLogger.error("Failed to get price feed from contract", { + error: error instanceof Error ? error.message : String(error), + priceId + }); + throw error; + } + } + + // Get EVM chain price feed + private async getEVMPriceFeed(priceId: string) { + const abi = [ + "function getPrice(bytes32 id) view returns (PythStructs.Price memory price)", + "function getEmaPrice(bytes32 id) view returns (PythStructs.Price memory price)", + "function getValidTimePeriod() view returns (uint validTimePeriod)" + ]; + + const contract = new ethers.Contract( + this.config.contractAddress, + abi, + this.config.provider as Provider + ); + + const price = await contract.getPrice(priceId); + return { + price: price.price, + conf: price.conf, + expo: price.expo, + publishTime: price.publishTime + }; + } + + // Get Solana chain price feed + private async getSolanaPriceFeed(priceId: string) { + if (!this.config.pythProgramKey) { + throw new Error("Pyth program key required for Solana price feeds"); + } + + const connection = this.config.provider as Connection; + const priceAccount = new PublicKey(priceId); + + const accountInfo = await connection.getAccountInfo(priceAccount); + if (!accountInfo) { + throw new Error(`No account info found for price ID: ${priceId}`); + } + + // Parse Pyth price account data + // This is a simplified version - actual implementation would need proper parsing + return { + price: accountInfo.data.readBigInt64LE(0), + conf: accountInfo.data.readBigInt64LE(8), + expo: accountInfo.data.readInt32LE(16), + publishTime: accountInfo.data.readBigInt64LE(20) + }; + } + + // Validate price feed + async validatePriceFeed(priceId: string, options: { + minConfidence?: number; + maxAge?: number; + } = {}) { + const feed = await this.getPriceFeed(priceId); + const now = Math.floor(Date.now() / 1000); + + return { + isValid: true, + price: feed.price, + confidence: feed.conf, + age: now - Number(feed.publishTime), + validationResults: { + confidence: options.minConfidence ? feed.conf >= options.minConfidence : true, + age: options.maxAge ? (now - Number(feed.publishTime)) <= options.maxAge : true + } + }; + } + + // Subscribe to price updates + async subscribeToPriceUpdates(priceId: string, callback: (price: PriceUpdate) => void) { + elizaLogger.info('Subscribing to price updates', { priceId, network: this.config.network }); + + if (this.isEVM) { + const contract = new ethers.Contract( + this.config.contractAddress, + ["event PriceUpdate(bytes32 indexed id, int64 price, uint64 conf, int32 expo, uint publishTime)"], + this.config.provider as Provider + ); + + contract.on("PriceUpdate", (id, price, conf, expo, publishTime) => { + if (id === priceId) { + elizaLogger.debug('Received EVM price update', { priceId, price: price.toString() }); + callback({ + price: BigInt(price.toString()), + conf: BigInt(conf.toString()), + expo: Number(expo), + publishTime: BigInt(publishTime.toString()) + }); + } + }); + } else { + // For Solana, set up websocket subscription + const connection = this.config.provider as Connection; + const priceAccount = new PublicKey(priceId); + + connection.onAccountChange(priceAccount, (accountInfo) => { + const price = accountInfo.data.readBigInt64LE(0); + const conf = accountInfo.data.readBigInt64LE(8); + const expo = accountInfo.data.readInt32LE(16); + const publishTime = accountInfo.data.readBigInt64LE(20); + + elizaLogger.debug('Received Solana price update', { priceId, price: price.toString() }); + callback({ price, conf, expo, publishTime }); + }); + } + + elizaLogger.info('Price update subscription established', { priceId }); + } +} \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/environment.ts b/packages/plugin-pyth-data/src/environment.ts new file mode 100644 index 0000000000..16eac9b341 --- /dev/null +++ b/packages/plugin-pyth-data/src/environment.ts @@ -0,0 +1,142 @@ +import { IAgentRuntime, elizaLogger } from "@elizaos/core"; +import { z } from "zod"; + +// Environment Variables +let ENV: string = "mainnet"; + +// Pyth Network Configuration +const PYTH_NETWORKS = { + mainnet: { + hermes: process.env.PYTH_MAINNET_HERMES_URL || "https://hermes.pyth.network", + wss: process.env.PYTH_MAINNET_WSS_URL || "wss://hermes.pyth.network/ws", + pythnet: process.env.PYTH_MAINNET_PYTHNET_URL || "https://pythnet.rpcpool.com", + contractRegistry: process.env.PYTH_MAINNET_CONTRACT_REGISTRY || "https://pyth.network/developers/price-feed-ids", + programKey: process.env.PYTH_MAINNET_PROGRAM_KEY || "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH" + }, + testnet: { + hermes: process.env.PYTH_TESTNET_HERMES_URL || "https://hermes-beta.pyth.network", + wss: process.env.PYTH_TESTNET_WSS_URL || "wss://hermes-beta.pyth.network/ws", + pythnet: process.env.PYTH_TESTNET_PYTHNET_URL || "https://pythnet.rpcpool.com", + contractRegistry: process.env.PYTH_TESTNET_CONTRACT_REGISTRY || "https://pyth.network/developers/price-feed-ids#testnet", + programKey: process.env.PYTH_TESTNET_PROGRAM_KEY || "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH" + } +} as const; + +// Log environment information +elizaLogger.info("Environment sources", { + shellVars: Object.keys(process.env).filter(key => key.startsWith('PYTH_')), +}); + +export const pythEnvSchema = z.object({ + PYTH_NETWORK_ENV: z.enum(["mainnet", "testnet"]).default("mainnet"), + PYTH_MAX_RETRIES: z.string().transform(Number).default("3"), + PYTH_RETRY_DELAY: z.string().transform(Number).default("1000"), + PYTH_TIMEOUT: z.string().transform(Number).default("5000"), + PYTH_MAX_PRICE_AGE: z.string().transform(Number).default("60000"), + PYTH_CONFIDENCE_INTERVAL: z.string().transform(Number).default("0.95"), + PYTH_UPDATE_INTERVAL: z.string().transform(Number).default("500"), + PYTH_MAX_SUBSCRIPTIONS: z.string().transform(Number).default("100"), + PYTH_BATCH_SIZE: z.string().transform(Number).default("10"), + PYTH_CACHE_DURATION: z.string().transform(Number).default("300000"), + PYTH_GRANULAR_LOG: z.boolean().default(true), + PYTH_COMMITMENT_LEVEL: z.enum(["processed", "confirmed", "finalized"]).default("confirmed"), + PYTH_ENABLE_WEBSOCKET: z.boolean().default(true), + PYTH_RECONNECT_INTERVAL: z.string().transform(Number).default("5000"), + PYTH_MAX_CONNECTION_ATTEMPTS: z.string().transform(Number).default("5"), + PYTH_ENABLE_PRICE_CACHING: z.boolean().default(true), + PYTH_ENABLE_METRICS: z.boolean().default(false), + PYTH_LOG_LEVEL: z.enum(["error", "warn", "info", "debug"]).default("info"), + PYTH_MAX_SLIPPAGE: z.string().transform(Number).default("0.05"), + PYTH_MIN_LIQUIDITY_RATIO: z.string().transform(Number).default("0.1"), + PYTH_MIN_DATA_POINTS: z.string().transform(Number).default("10"), + PYTH_OUTLIER_THRESHOLD: z.string().transform(Number).default("2.0"), + PYTH_WS_HEARTBEAT_INTERVAL: z.string().transform(Number).default("30000"), + PYTH_WS_MAX_MISSED_HEARTBEATS: z.string().transform(Number).default("3"), + PYTH_WS_RECONNECT_DELAY: z.string().transform(Number).default("1000"), + RUNTIME_CHECK_MODE: z.boolean().default(false), +}); + +export type PythConfig = z.infer; + +export function getConfig( + env: string | undefined | null = ENV || + process.env.PYTH_NETWORK_ENV +): PythConfig { + ENV = env || "mainnet"; + + return { + PYTH_NETWORK_ENV: (env as "mainnet" | "testnet") || "mainnet", + PYTH_MAX_RETRIES: Number(process.env.PYTH_MAX_RETRIES || "3"), + PYTH_RETRY_DELAY: Number(process.env.PYTH_RETRY_DELAY || "1000"), + PYTH_TIMEOUT: Number(process.env.PYTH_TIMEOUT || "5000"), + PYTH_MAX_PRICE_AGE: Number(process.env.PYTH_MAX_PRICE_AGE || "60000"), + PYTH_CONFIDENCE_INTERVAL: Number(process.env.PYTH_CONFIDENCE_INTERVAL || "0.95"), + PYTH_UPDATE_INTERVAL: Number(process.env.PYTH_UPDATE_INTERVAL || "500"), + PYTH_MAX_SUBSCRIPTIONS: Number(process.env.PYTH_MAX_SUBSCRIPTIONS || "100"), + PYTH_BATCH_SIZE: Number(process.env.PYTH_BATCH_SIZE || "10"), + PYTH_CACHE_DURATION: Number(process.env.PYTH_CACHE_DURATION || "300000"), + PYTH_GRANULAR_LOG: process.env.PYTH_GRANULAR_LOG === "true" || false, + PYTH_COMMITMENT_LEVEL: (process.env.PYTH_COMMITMENT_LEVEL as "processed" | "confirmed" | "finalized") || "confirmed", + PYTH_ENABLE_WEBSOCKET: process.env.PYTH_ENABLE_WEBSOCKET === "true" || true, + PYTH_RECONNECT_INTERVAL: Number(process.env.PYTH_RECONNECT_INTERVAL || "5000"), + PYTH_MAX_CONNECTION_ATTEMPTS: Number(process.env.PYTH_MAX_CONNECTION_ATTEMPTS || "5"), + PYTH_ENABLE_PRICE_CACHING: process.env.PYTH_ENABLE_PRICE_CACHING === "true" || true, + PYTH_ENABLE_METRICS: process.env.PYTH_ENABLE_METRICS === "true" || false, + PYTH_LOG_LEVEL: (process.env.PYTH_LOG_LEVEL as "error" | "warn" | "info" | "debug") || "info", + PYTH_MAX_SLIPPAGE: Number(process.env.PYTH_MAX_SLIPPAGE || "0.05"), + PYTH_MIN_LIQUIDITY_RATIO: Number(process.env.PYTH_MIN_LIQUIDITY_RATIO || "0.1"), + PYTH_MIN_DATA_POINTS: Number(process.env.PYTH_MIN_DATA_POINTS || "10"), + PYTH_OUTLIER_THRESHOLD: Number(process.env.PYTH_OUTLIER_THRESHOLD || "2.0"), + PYTH_WS_HEARTBEAT_INTERVAL: Number(process.env.PYTH_WS_HEARTBEAT_INTERVAL || "30000"), + PYTH_WS_MAX_MISSED_HEARTBEATS: Number(process.env.PYTH_WS_MAX_MISSED_HEARTBEATS || "3"), + PYTH_WS_RECONNECT_DELAY: Number(process.env.PYTH_WS_RECONNECT_DELAY || "1000"), + RUNTIME_CHECK_MODE: process.env.RUNTIME_CHECK_MODE === "true" || false, + }; +} + +export async function validatePythConfig( + runtime: IAgentRuntime +): Promise { + try { + const envConfig = getConfig( + runtime.getSetting("PYTH_NETWORK_ENV") ?? undefined + ); + + const config = { + PYTH_NETWORK_ENV: process.env.PYTH_NETWORK_ENV || runtime.getSetting("PYTH_NETWORK_ENV") || envConfig.PYTH_NETWORK_ENV, + PYTH_MAX_RETRIES: process.env.PYTH_MAX_RETRIES || runtime.getSetting("PYTH_MAX_RETRIES") || envConfig.PYTH_MAX_RETRIES.toString(), + PYTH_RETRY_DELAY: process.env.PYTH_RETRY_DELAY || runtime.getSetting("PYTH_RETRY_DELAY") || envConfig.PYTH_RETRY_DELAY.toString(), + PYTH_TIMEOUT: process.env.PYTH_TIMEOUT || runtime.getSetting("PYTH_TIMEOUT") || envConfig.PYTH_TIMEOUT.toString(), + PYTH_MAX_PRICE_AGE: process.env.PYTH_MAX_PRICE_AGE || runtime.getSetting("PYTH_MAX_PRICE_AGE") || envConfig.PYTH_MAX_PRICE_AGE.toString(), + PYTH_CONFIDENCE_INTERVAL: process.env.PYTH_CONFIDENCE_INTERVAL || runtime.getSetting("PYTH_CONFIDENCE_INTERVAL") || envConfig.PYTH_CONFIDENCE_INTERVAL.toString(), + PYTH_UPDATE_INTERVAL: process.env.PYTH_UPDATE_INTERVAL || runtime.getSetting("PYTH_UPDATE_INTERVAL") || envConfig.PYTH_UPDATE_INTERVAL.toString(), + PYTH_MAX_SUBSCRIPTIONS: process.env.PYTH_MAX_SUBSCRIPTIONS || runtime.getSetting("PYTH_MAX_SUBSCRIPTIONS") || envConfig.PYTH_MAX_SUBSCRIPTIONS.toString(), + PYTH_BATCH_SIZE: process.env.PYTH_BATCH_SIZE || runtime.getSetting("PYTH_BATCH_SIZE") || envConfig.PYTH_BATCH_SIZE.toString(), + PYTH_CACHE_DURATION: process.env.PYTH_CACHE_DURATION || runtime.getSetting("PYTH_CACHE_DURATION") || envConfig.PYTH_CACHE_DURATION.toString(), + PYTH_GRANULAR_LOG: process.env.PYTH_GRANULAR_LOG === "true" || false, + PYTH_COMMITMENT_LEVEL: process.env.PYTH_COMMITMENT_LEVEL || runtime.getSetting("PYTH_COMMITMENT_LEVEL") || envConfig.PYTH_COMMITMENT_LEVEL, + PYTH_ENABLE_WEBSOCKET: process.env.PYTH_ENABLE_WEBSOCKET === "true" || true, + PYTH_RECONNECT_INTERVAL: process.env.PYTH_RECONNECT_INTERVAL || runtime.getSetting("PYTH_RECONNECT_INTERVAL") || envConfig.PYTH_RECONNECT_INTERVAL.toString(), + PYTH_MAX_CONNECTION_ATTEMPTS: process.env.PYTH_MAX_CONNECTION_ATTEMPTS || runtime.getSetting("PYTH_MAX_CONNECTION_ATTEMPTS") || envConfig.PYTH_MAX_CONNECTION_ATTEMPTS.toString(), + PYTH_ENABLE_PRICE_CACHING: process.env.PYTH_ENABLE_PRICE_CACHING === "true" || true, + PYTH_ENABLE_METRICS: process.env.PYTH_ENABLE_METRICS === "true" || false, + PYTH_LOG_LEVEL: process.env.PYTH_LOG_LEVEL || runtime.getSetting("PYTH_LOG_LEVEL") || envConfig.PYTH_LOG_LEVEL, + PYTH_MAX_SLIPPAGE: process.env.PYTH_MAX_SLIPPAGE || runtime.getSetting("PYTH_MAX_SLIPPAGE") || envConfig.PYTH_MAX_SLIPPAGE.toString(), + PYTH_MIN_LIQUIDITY_RATIO: process.env.PYTH_MIN_LIQUIDITY_RATIO || runtime.getSetting("PYTH_MIN_LIQUIDITY_RATIO") || envConfig.PYTH_MIN_LIQUIDITY_RATIO.toString(), + PYTH_MIN_DATA_POINTS: process.env.PYTH_MIN_DATA_POINTS || runtime.getSetting("PYTH_MIN_DATA_POINTS") || envConfig.PYTH_MIN_DATA_POINTS.toString(), + PYTH_OUTLIER_THRESHOLD: process.env.PYTH_OUTLIER_THRESHOLD || runtime.getSetting("PYTH_OUTLIER_THRESHOLD") || envConfig.PYTH_OUTLIER_THRESHOLD.toString(), + PYTH_WS_HEARTBEAT_INTERVAL: process.env.PYTH_WS_HEARTBEAT_INTERVAL || runtime.getSetting("PYTH_WS_HEARTBEAT_INTERVAL") || envConfig.PYTH_WS_HEARTBEAT_INTERVAL.toString(), + PYTH_WS_MAX_MISSED_HEARTBEATS: process.env.PYTH_WS_MAX_MISSED_HEARTBEATS || runtime.getSetting("PYTH_WS_MAX_MISSED_HEARTBEATS") || envConfig.PYTH_WS_MAX_MISSED_HEARTBEATS.toString(), + PYTH_WS_RECONNECT_DELAY: process.env.PYTH_WS_RECONNECT_DELAY || runtime.getSetting("PYTH_WS_RECONNECT_DELAY") || envConfig.PYTH_WS_RECONNECT_DELAY.toString(), + RUNTIME_CHECK_MODE: process.env.RUNTIME_CHECK_MODE === "true" || false, + }; + + return pythEnvSchema.parse(config); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to validate Pyth configuration: ${errorMessage}`); + } +} + +// Export network configurations +export const getNetworkConfig = (env: string = ENV) => PYTH_NETWORKS[env as keyof typeof PYTH_NETWORKS]; diff --git a/packages/plugin-pyth-data/src/error/base.ts b/packages/plugin-pyth-data/src/error/base.ts new file mode 100644 index 0000000000..a564a78157 --- /dev/null +++ b/packages/plugin-pyth-data/src/error/base.ts @@ -0,0 +1,124 @@ +// Base error codes +export enum PythErrorCode { + // WebSocket connection errors + WS_CONNECTION_TIMEOUT = 'WS_CONNECTION_TIMEOUT', + WS_CONNECTION_REFUSED = 'WS_CONNECTION_REFUSED', + WS_INVALID_URL = 'WS_INVALID_URL', + WS_HEARTBEAT_FAILED = 'WS_HEARTBEAT_FAILED', + WS_SSL_ERROR = 'WS_SSL_ERROR', + WS_INVALID_STATE_TRANSITION = 'WS_INVALID_STATE_TRANSITION', + WS_MESSAGE_PARSE_ERROR = 'WS_MESSAGE_PARSE_ERROR', + WS_RATE_LIMIT_EXCEEDED = 'WS_RATE_LIMIT_EXCEEDED', + WS_INVALID_MESSAGE_FORMAT = 'WS_INVALID_MESSAGE_FORMAT', + + // Runtime errors + RUNTIME_INITIALIZATION = 'RUNTIME_INITIALIZATION', + RUNTIME_CONFIGURATION = 'RUNTIME_CONFIGURATION', + RUNTIME_VALIDATION = 'RUNTIME_VALIDATION', + + // State management errors + STATE_INVALID_TRANSITION = 'STATE_INVALID_TRANSITION', + STATE_PERSISTENCE = 'STATE_PERSISTENCE', + STATE_UNEXPECTED_DISCONNECT = 'STATE_UNEXPECTED_DISCONNECT', + STATE_RECONNECTION_FAILED = 'STATE_RECONNECTION_FAILED', + STATE_MAX_RECONNECT_EXCEEDED = 'STATE_MAX_RECONNECT_EXCEEDED', + + // Data handling errors + DATA_INVALID_SUBSCRIPTION = 'DATA_INVALID_SUBSCRIPTION', + DATA_SUBSCRIPTION_LIMIT = 'DATA_SUBSCRIPTION_LIMIT', + DATA_VALIDATION_FAILED = 'DATA_VALIDATION_FAILED', + DATA_SEQUENCE_ERROR = 'DATA_SEQUENCE_ERROR', + DATA_TRANSFORM_ERROR = 'DATA_TRANSFORM_ERROR', + DATA_SCHEMA_ERROR = 'DATA_SCHEMA_ERROR', + DATA_STALE_PRICE = 'DATA_STALE_PRICE', + DATA_CHAIN_ERROR = 'DATA_CHAIN_ERROR', + DATA_PARSE_ERROR = 'DATA_PARSE_ERROR', + DATA_PRICE_UNAVAILABLE = 'DATA_PRICE_UNAVAILABLE', + DATA_CONFIDENCE_TOO_LOW = 'DATA_CONFIDENCE_TOO_LOW', + DATA_CONTRACT_ERROR = 'DATA_CONTRACT_ERROR', + + // Generic errors + UNKNOWN = 'UNKNOWN', + INTERNAL = 'INTERNAL', + NETWORK = 'NETWORK', + NETWORK_ERROR = 'NETWORK_ERROR', + TIMEOUT = 'TIMEOUT', + INVALID_PARAMETER = 'INVALID_PARAMETER', + SUBSCRIPTION_LIMIT_EXCEEDED = 'SUBSCRIPTION_LIMIT_EXCEEDED', + WEBSOCKET_ERROR = 'WEBSOCKET_ERROR', + MESSAGE_PROCESSING_ERROR = 'MESSAGE_PROCESSING_ERROR', + CONNECTION_FAILED = 'CONNECTION_FAILED', + HANDLER_FAILED = 'HANDLER_FAILED', + + // Generic Errors + UNKNOWN_ERROR = 'UNKNOWN_ERROR' +} + +export enum ErrorSeverity { + LOW = 'LOW', // Non-critical errors that don't affect core functionality + MEDIUM = 'MEDIUM', // Errors that affect some functionality but system can continue + HIGH = 'HIGH', // Critical errors that require immediate attention + FATAL = 'FATAL' // System cannot continue operation +} + +// Error detail types +export interface ErrorDetails { + [key: string]: unknown; +} + +// Extended error interface +export interface IPythError { + code: PythErrorCode; + message: string; + severity: ErrorSeverity; + timestamp: number; + details?: ErrorDetails; + originalError?: Error; + name: string; +} + +// Base Pyth error class +export class PythError extends Error implements IPythError { + public readonly timestamp: number; + public readonly name: string = 'PythError'; + + constructor( + public readonly code: PythErrorCode, + public readonly message: string, + public readonly severity: ErrorSeverity = ErrorSeverity.MEDIUM, + public readonly details?: ErrorDetails, + public readonly originalError?: Error + ) { + super(message); + this.timestamp = Date.now(); + Object.setPrototypeOf(this, new.target.prototype); + } + + toJSON(): object { + return { + name: this.name, + code: this.code, + message: this.message, + severity: this.severity, + timestamp: this.timestamp, + details: this.details, + stack: this.stack, + originalError: this.originalError ? { + name: this.originalError.name, + message: this.originalError.message, + stack: this.originalError.stack + } : undefined + }; + } +} + +// Error utility functions +export const createError = ( + code: PythErrorCode, + message: string, + severity?: ErrorSeverity, + details?: ErrorDetails, + originalError?: Error +): PythError => { + return new PythError(code, message, severity, details, originalError); +}; \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/error/data.ts b/packages/plugin-pyth-data/src/error/data.ts new file mode 100644 index 0000000000..f03ca13ce3 --- /dev/null +++ b/packages/plugin-pyth-data/src/error/data.ts @@ -0,0 +1,158 @@ +import { PythError, ErrorSeverity, PythErrorCode, ErrorDetails } from './base'; + +// Data Error Codes +export enum DataErrorCode { + // Validation Errors + VALIDATION_FAILED = 'DATA_VALIDATION_FAILED', + INVALID_SUBSCRIPTION = 'DATA_INVALID_SUBSCRIPTION', + SUBSCRIPTION_LIMIT = 'DATA_SUBSCRIPTION_LIMIT', + SCHEMA_ERROR = 'DATA_SCHEMA_ERROR', + + // Processing Errors + SEQUENCE_ERROR = 'DATA_SEQUENCE_ERROR', + TRANSFORM_ERROR = 'DATA_TRANSFORM_ERROR', + PARSE_ERROR = 'DATA_PARSE_ERROR', + + // Price Feed Errors + PRICE_UNAVAILABLE = 'DATA_PRICE_UNAVAILABLE', + CONFIDENCE_TOO_LOW = 'DATA_CONFIDENCE_TOO_LOW', + STALE_PRICE = 'DATA_STALE_PRICE', + + // Chain-Specific Errors + CHAIN_DATA_ERROR = 'DATA_CHAIN_ERROR', + CONTRACT_ERROR = 'DATA_CONTRACT_ERROR', + + // Network and Connection Errors + NETWORK_ERROR = 'NETWORK_ERROR', + WEBSOCKET_ERROR = 'WEBSOCKET_ERROR', + MESSAGE_PROCESSING_ERROR = 'MESSAGE_PROCESSING_ERROR', + CONNECTION_FAILED = 'CONNECTION_FAILED', + HANDLER_FAILED = 'HANDLER_FAILED', + SUBSCRIPTION_LIMIT_EXCEEDED = 'SUBSCRIPTION_LIMIT_EXCEEDED' +} + +// Validation error type +type ValidationError = string | { + field: string; + message: string; + value?: unknown; +}; + +export class DataError extends PythError { + public readonly name: string = 'DataError'; + + constructor( + code: PythErrorCode | DataErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.MEDIUM, + details?: ErrorDetails, + originalError?: Error + ) { + super(code as PythErrorCode, message, severity, details, originalError); + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory methods + static invalidSubscription(subscription: unknown, reason: string): DataError { + return new DataError( + DataErrorCode.INVALID_SUBSCRIPTION, + 'Invalid subscription parameters', + ErrorSeverity.MEDIUM, + { subscription, reason } + ); + } + + static subscriptionLimitExceeded( + currentCount: number, + maxLimit: number + ): DataError { + return new DataError( + DataErrorCode.SUBSCRIPTION_LIMIT, + `Subscription limit exceeded (${currentCount}/${maxLimit})`, + ErrorSeverity.HIGH, + { currentCount, maxLimit } + ); + } + + static validationFailed( + data: unknown, + validationErrors: ValidationError[] + ): DataError { + return new DataError( + DataErrorCode.VALIDATION_FAILED, + 'Data validation failed', + ErrorSeverity.MEDIUM, + { data, validationErrors } + ); + } + + static sequenceError( + expected: number, + received: number, + details?: ErrorDetails + ): DataError { + return new DataError( + DataErrorCode.SEQUENCE_ERROR, + `Message sequence error: expected ${expected}, received ${received}`, + ErrorSeverity.MEDIUM, + { expected, received, ...details } + ); + } + + static transformError(data: unknown, targetFormat: string, error?: Error): DataError { + return new DataError( + DataErrorCode.TRANSFORM_ERROR, + 'Failed to transform data', + ErrorSeverity.MEDIUM, + { data, targetFormat }, + error + ); + } + + static schemaError(data: unknown, schema: Record, error?: Error): DataError { + return new DataError( + DataErrorCode.SCHEMA_ERROR, + 'Data does not match schema', + ErrorSeverity.HIGH, + { data, schema }, + error + ); + } + + static stalePriceError( + symbol: string, + lastUpdateTime: number, + maxAge: number + ): DataError { + return new DataError( + DataErrorCode.STALE_PRICE, + `Price data for ${symbol} is stale`, + ErrorSeverity.HIGH, + { + symbol, + lastUpdateTime, + maxAge, + currentTime: Date.now() + } + ); + } + + static chainError( + chain: string, + operation: string, + error?: Error + ): DataError { + return new DataError( + DataErrorCode.CHAIN_DATA_ERROR, + `Chain-specific error on ${chain} during ${operation}`, + ErrorSeverity.HIGH, + { chain, operation }, + error + ); + } +} + +// Type guard +export const isDataError = (error: unknown): error is DataError => { + return error instanceof DataError; +}; \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/error/error.txt b/packages/plugin-pyth-data/src/error/error.txt new file mode 100644 index 0000000000..5413ab6eb5 --- /dev/null +++ b/packages/plugin-pyth-data/src/error/error.txt @@ -0,0 +1,405 @@ +// WebSocket States +export enum WebSocketState { + CONNECTING = 'CONNECTING', + CONNECTED = 'CONNECTED', + RECONNECTING = 'RECONNECTING', + DISCONNECTED = 'DISCONNECTED', + ERROR = 'ERROR' +} + +// Base error codes +export enum PythErrorCode { + // WebSocket Connection Errors + WS_CONNECT_TIMEOUT = 'WS_CONNECT_TIMEOUT', + WS_CONNECTION_REFUSED = 'WS_CONNECTION_REFUSED', + WS_INVALID_URL = 'WS_INVALID_URL', + WS_SSL_ERROR = 'WS_SSL_ERROR', + + // WebSocket Runtime Errors + WS_HEARTBEAT_FAILURE = 'WS_HEARTBEAT_FAILURE', + WS_MESSAGE_PARSE_ERROR = 'WS_MESSAGE_PARSE_ERROR', + WS_RATE_LIMIT_EXCEEDED = 'WS_RATE_LIMIT_EXCEEDED', + WS_INVALID_MESSAGE_FORMAT = 'WS_INVALID_MESSAGE_FORMAT', + + // State Management Errors + STATE_UNEXPECTED_DISCONNECT = 'STATE_UNEXPECTED_DISCONNECT', + STATE_RECONNECTION_FAILED = 'STATE_RECONNECTION_FAILED', + STATE_MAX_RECONNECT_EXCEEDED = 'STATE_MAX_RECONNECT_EXCEEDED', + STATE_INVALID_TRANSITION = 'STATE_INVALID_TRANSITION', + + // Data Handling Errors + DATA_INVALID_SUBSCRIPTION = 'DATA_INVALID_SUBSCRIPTION', + DATA_SUBSCRIPTION_LIMIT = 'DATA_SUBSCRIPTION_LIMIT', + DATA_VALIDATION_FAILED = 'DATA_VALIDATION_FAILED', + DATA_SEQUENCE_ERROR = 'DATA_SEQUENCE_ERROR', + DATA_TRANSFORM_ERROR = 'DATA_TRANSFORM_ERROR', + DATA_SCHEMA_ERROR = 'DATA_SCHEMA_ERROR', + + // Generic errors (to be expanded) + UNKNOWN_ERROR = 'UNKNOWN_ERROR', + INVALID_PARAMETER = 'INVALID_PARAMETER', + NETWORK_ERROR = 'NETWORK_ERROR' +} + +// Error severity levels +export enum ErrorSeverity { + LOW = 'LOW', // Non-critical errors that don't affect core functionality + MEDIUM = 'MEDIUM', // Errors that affect some functionality but system can continue + HIGH = 'HIGH', // Critical errors that require immediate attention + FATAL = 'FATAL' // System cannot continue operation +} + +// Extended error interface +export interface IPythError { + code: PythErrorCode; + message: string; + severity: ErrorSeverity; + timestamp: number; + details?: any; + originalError?: Error; + name: string; +} + +// Base Pyth error class +export class PythError extends Error implements IPythError { + public readonly timestamp: number; + public readonly name: string = 'PythError'; + + constructor( + public readonly code: PythErrorCode, + public readonly message: string, + public readonly severity: ErrorSeverity = ErrorSeverity.MEDIUM, + public readonly details?: any, + public readonly originalError?: Error + ) { + super(message); + this.timestamp = Date.now(); + + // Ensure proper prototype chain for instanceof checks + Object.setPrototypeOf(this, new.target.prototype); + } + + // Create error object for logging + toJSON(): object { + return { + name: this.name, + code: this.code, + message: this.message, + severity: this.severity, + timestamp: this.timestamp, + details: this.details, + stack: this.stack, + originalError: this.originalError ? { + name: this.originalError.name, + message: this.originalError.message, + stack: this.originalError.stack + } : undefined + }; + } +} + +// WebSocket specific error class +export class WebSocketError extends PythError { + public readonly name: string = 'WebSocketError'; + + constructor( + code: PythErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.HIGH, + details?: any, + originalError?: Error + ) { + super(code, message, severity, details, originalError); + + // Ensure proper prototype chain for instanceof checks + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory method for connection timeout + static connectionTimeout(details?: any): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_CONNECT_TIMEOUT, + 'WebSocket connection timed out', + ErrorSeverity.HIGH, + details + ); + } + + // Factory method for connection refused + static connectionRefused(details?: any): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_CONNECTION_REFUSED, + 'WebSocket connection refused', + ErrorSeverity.HIGH, + details + ); + } + + // Factory method for invalid URL + static invalidUrl(url: string): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_INVALID_URL, + `Invalid WebSocket URL: ${url}`, + ErrorSeverity.HIGH, + { url } + ); + } + + // Factory method for SSL/TLS errors + static sslError(details?: any): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_SSL_ERROR, + 'SSL/TLS connection error', + ErrorSeverity.HIGH, + details + ); + } +} + +// Runtime error class +export class RuntimeError extends PythError { + public readonly name: string = 'RuntimeError'; + + constructor( + code: PythErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.HIGH, + details?: any, + originalError?: Error + ) { + super(code, message, severity, details, originalError); + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory method for heartbeat failure + static heartbeatFailure(details?: any): RuntimeError { + return new RuntimeError( + PythErrorCode.WS_HEARTBEAT_FAILURE, + 'WebSocket heartbeat check failed', + ErrorSeverity.HIGH, + details + ); + } + + // Factory method for message parsing error + static messageParseError(rawMessage: string, parseError: Error): RuntimeError { + return new RuntimeError( + PythErrorCode.WS_MESSAGE_PARSE_ERROR, + 'Failed to parse WebSocket message', + ErrorSeverity.MEDIUM, + { rawMessage }, + parseError + ); + } + + // Factory method for rate limit exceeded + static rateLimitExceeded(details?: any): RuntimeError { + return new RuntimeError( + PythErrorCode.WS_RATE_LIMIT_EXCEEDED, + 'WebSocket rate limit exceeded', + ErrorSeverity.MEDIUM, + details + ); + } + + // Factory method for invalid message format + static invalidMessageFormat(message: any, expectedFormat: string): RuntimeError { + return new RuntimeError( + PythErrorCode.WS_INVALID_MESSAGE_FORMAT, + 'Invalid WebSocket message format', + ErrorSeverity.MEDIUM, + { + receivedMessage: message, + expectedFormat + } + ); + } +} + +// State Management error class +export class StateError extends PythError { + public readonly name: string = 'StateError'; + + constructor( + code: PythErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.HIGH, + details?: any, + originalError?: Error + ) { + super(code, message, severity, details, originalError); + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory method for unexpected disconnection + static unexpectedDisconnect(details?: any): StateError { + return new StateError( + PythErrorCode.STATE_UNEXPECTED_DISCONNECT, + 'WebSocket unexpectedly disconnected', + ErrorSeverity.HIGH, + details + ); + } + + // Factory method for reconnection failure + static reconnectionFailed(attempts: number, error?: Error): StateError { + return new StateError( + PythErrorCode.STATE_RECONNECTION_FAILED, + 'Failed to reconnect WebSocket', + ErrorSeverity.HIGH, + { attempts }, + error + ); + } + + // Factory method for maximum reconnection attempts exceeded + static maxReconnectExceeded(maxAttempts: number): StateError { + return new StateError( + PythErrorCode.STATE_MAX_RECONNECT_EXCEEDED, + `Maximum reconnection attempts (${maxAttempts}) exceeded`, + ErrorSeverity.FATAL, + { maxAttempts } + ); + } + + // Factory method for invalid state transition + static invalidTransition( + fromState: WebSocketState, + toState: WebSocketState, + details?: any + ): StateError { + return new StateError( + PythErrorCode.STATE_INVALID_TRANSITION, + `Invalid state transition from ${fromState} to ${toState}`, + ErrorSeverity.HIGH, + { + fromState, + toState, + ...details + } + ); + } +} + +// Data Handling error class +export class DataError extends PythError { + public readonly name: string = 'DataError'; + + constructor( + code: PythErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.MEDIUM, + details?: any, + originalError?: Error + ) { + super(code, message, severity, details, originalError); + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory method for invalid subscription + static invalidSubscription(subscription: any, reason: string): DataError { + return new DataError( + PythErrorCode.DATA_INVALID_SUBSCRIPTION, + 'Invalid subscription parameters', + ErrorSeverity.MEDIUM, + { subscription, reason } + ); + } + + // Factory method for subscription limit exceeded + static subscriptionLimitExceeded( + currentCount: number, + maxLimit: number + ): DataError { + return new DataError( + PythErrorCode.DATA_SUBSCRIPTION_LIMIT, + `Subscription limit exceeded (${currentCount}/${maxLimit})`, + ErrorSeverity.HIGH, + { currentCount, maxLimit } + ); + } + + // Factory method for data validation failure + static validationFailed( + data: any, + validationErrors: any[] + ): DataError { + return new DataError( + PythErrorCode.DATA_VALIDATION_FAILED, + 'Data validation failed', + ErrorSeverity.MEDIUM, + { data, validationErrors } + ); + } + + // Factory method for sequence error + static sequenceError( + expected: number, + received: number, + details?: any + ): DataError { + return new DataError( + PythErrorCode.DATA_SEQUENCE_ERROR, + `Message sequence error: expected ${expected}, received ${received}`, + ErrorSeverity.MEDIUM, + { expected, received, ...details } + ); + } + + // Factory method for transform error + static transformError( + data: any, + targetFormat: string, + error?: Error + ): DataError { + return new DataError( + PythErrorCode.DATA_TRANSFORM_ERROR, + 'Failed to transform data', + ErrorSeverity.MEDIUM, + { data, targetFormat }, + error + ); + } + + // Factory method for schema error + static schemaError( + data: any, + schema: any, + error?: Error + ): DataError { + return new DataError( + PythErrorCode.DATA_SCHEMA_ERROR, + 'Data does not match schema', + ErrorSeverity.HIGH, + { data, schema }, + error + ); + } +} + +// Error utility functions +export const isWebSocketError = (error: any): error is WebSocketError => { + return error instanceof WebSocketError; +}; + +export const isRuntimeError = (error: any): error is RuntimeError => { + return error instanceof RuntimeError; +}; + +export const isStateError = (error: any): error is StateError => { + return error instanceof StateError; +}; + +export const isDataError = (error: any): error is DataError => { + return error instanceof DataError; +}; + +export const createError = ( + code: PythErrorCode, + message: string, + severity?: ErrorSeverity, + details?: any, + originalError?: Error +): PythError => { + return new PythError(code, message, severity, details, originalError); +}; diff --git a/packages/plugin-pyth-data/src/error/index.ts b/packages/plugin-pyth-data/src/error/index.ts new file mode 100644 index 0000000000..cc12a00a10 --- /dev/null +++ b/packages/plugin-pyth-data/src/error/index.ts @@ -0,0 +1,3 @@ +export * from './base'; +export * from './websocket'; +export * from './data'; \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/error/runtime.ts b/packages/plugin-pyth-data/src/error/runtime.ts new file mode 100644 index 0000000000..87766547c9 --- /dev/null +++ b/packages/plugin-pyth-data/src/error/runtime.ts @@ -0,0 +1,71 @@ +import { PythError, ErrorSeverity, PythErrorCode, ErrorDetails } from './base'; + +export class RuntimeError extends PythError { + public readonly name: string = 'RuntimeError'; + + constructor( + code: PythErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.HIGH, + details?: ErrorDetails, + originalError?: Error + ) { + super(code, message, severity, details, originalError); + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory methods + static initializationError(component: string, reason: string, error?: Error): RuntimeError { + return new RuntimeError( + PythErrorCode.RUNTIME_INITIALIZATION, + `Failed to initialize ${component}: ${reason}`, + ErrorSeverity.HIGH, + { component, reason }, + error + ); + } + + static configurationError(component: string, reason: string, error?: Error): RuntimeError { + return new RuntimeError( + PythErrorCode.RUNTIME_CONFIGURATION, + `Configuration error in ${component}: ${reason}`, + ErrorSeverity.HIGH, + { component, reason }, + error + ); + } + + static validationError(component: string, reason: string, error?: Error): RuntimeError { + return new RuntimeError( + PythErrorCode.RUNTIME_VALIDATION, + `Validation error in ${component}: ${reason}`, + ErrorSeverity.HIGH, + { component, reason }, + error + ); + } + + static stateTransitionError( + component: string, + fromState: string, + toState: string, + reason: string + ): RuntimeError { + return new RuntimeError( + PythErrorCode.STATE_INVALID_TRANSITION, + `Invalid state transition in ${component} from ${fromState} to ${toState}: ${reason}`, + ErrorSeverity.HIGH, + { component, fromState, toState, reason } + ); + } + + static statePersistenceError(component: string, operation: string, error?: Error): RuntimeError { + return new RuntimeError( + PythErrorCode.STATE_PERSISTENCE, + `Failed to ${operation} state for ${component}`, + ErrorSeverity.HIGH, + { component, operation }, + error + ); + } +} \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/error/websocket.ts b/packages/plugin-pyth-data/src/error/websocket.ts new file mode 100644 index 0000000000..d8fc5c5eea --- /dev/null +++ b/packages/plugin-pyth-data/src/error/websocket.ts @@ -0,0 +1,90 @@ +import { PythError, ErrorSeverity, PythErrorCode, ErrorDetails } from './base'; + +// WebSocket States +export enum WebSocketState { + CONNECTING = 'CONNECTING', + CONNECTED = 'CONNECTED', + RECONNECTING = 'RECONNECTING', + DISCONNECTED = 'DISCONNECTED', + ERROR = 'ERROR' +} + +// WebSocket specific error class +export class WebSocketError extends PythError { + public readonly name: string = 'WebSocketError'; + + constructor( + code: PythErrorCode, + message: string, + severity: ErrorSeverity = ErrorSeverity.HIGH, + details?: ErrorDetails, + originalError?: Error + ) { + super(code, message, severity, details, originalError); + Object.setPrototypeOf(this, new.target.prototype); + } + + // Factory methods + static connectionTimeout(details?: ErrorDetails): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_CONNECTION_TIMEOUT, + 'WebSocket connection timed out', + ErrorSeverity.HIGH, + details + ); + } + + static connectionRefused(details?: ErrorDetails): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_CONNECTION_REFUSED, + 'WebSocket connection refused', + ErrorSeverity.HIGH, + details + ); + } + + static invalidUrl(url: string): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_INVALID_URL, + `Invalid WebSocket URL: ${url}`, + ErrorSeverity.HIGH, + { url } + ); + } + + static sslError(details?: ErrorDetails): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_SSL_ERROR, + 'SSL/TLS connection error', + ErrorSeverity.HIGH, + details + ); + } + + static heartbeatFailure(details?: ErrorDetails): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_HEARTBEAT_FAILED, + 'WebSocket heartbeat check failed', + ErrorSeverity.HIGH, + details + ); + } + + static invalidStateTransition( + fromState: WebSocketState, + toState: WebSocketState, + details?: ErrorDetails + ): WebSocketError { + return new WebSocketError( + PythErrorCode.WS_INVALID_STATE_TRANSITION, + `Invalid state transition from ${fromState} to ${toState}`, + ErrorSeverity.HIGH, + { fromState, toState, ...details } + ); + } +} + +// Type guard +export const isWebSocketError = (error: unknown): error is WebSocketError => { + return error instanceof WebSocketError; +}; \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/hermes/HermesClient-copy.ts b/packages/plugin-pyth-data/src/hermes/HermesClient-copy.ts new file mode 100644 index 0000000000..32949f613e --- /dev/null +++ b/packages/plugin-pyth-data/src/hermes/HermesClient-copy.ts @@ -0,0 +1,317 @@ +import EventSource from "eventsource"; +import { schemas } from "../types/zodSchemas"; +import { z } from "zod"; +import { camelToSnakeCaseObject } from "./utils"; + +// Accessing schema objects +export type AssetType = z.infer; +export type BinaryPriceUpdate = z.infer; +export type EncodingType = z.infer; +export type PriceFeedMetadata = z.infer; +export type PriceIdInput = z.infer; +export type PriceUpdate = z.infer; +export type TwapsResponse = z.infer; +export type PublisherCaps = z.infer< + typeof schemas.LatestPublisherStakeCapsUpdateDataResponse +>; + +const DEFAULT_TIMEOUT: DurationInMs = 5000; +const DEFAULT_HTTP_RETRIES = 3; + +export type UnixTimestamp = number; +export type DurationInSeconds = number; +export type HexString = string; +export type DurationInMs = number; + +export type HermesClientConfig = { + /* Timeout of each request (for all of retries). Default: 5000ms */ + timeout?: DurationInMs; + /** + * Number of times a HTTP request will be retried before the API returns a failure. Default: 3. + * + * The connection uses exponential back-off for the delay between retries. However, + * it will timeout regardless of the retries at the configured `timeout` time. + */ + httpRetries?: number; + /** + * Optional headers to be included in every request. + */ + headers?: HeadersInit; +}; + +export class HermesClient { + private baseURL: string; + private timeout: DurationInMs; + private httpRetries: number; + private headers: HeadersInit; + + /** + * Constructs a new Connection. + * + * @param endpoint endpoint URL to the price service. Example: https://website/example/ + * @param config Optional HermesClientConfig for custom configurations. + */ + constructor(endpoint: string, config?: HermesClientConfig) { + this.baseURL = endpoint; + this.timeout = config?.timeout ?? DEFAULT_TIMEOUT; + this.httpRetries = config?.httpRetries ?? DEFAULT_HTTP_RETRIES; + this.headers = config?.headers ?? {}; + } + + private async httpRequest( + url: string, + schema: z.ZodSchema, + options?: RequestInit, + retries = this.httpRetries, + backoff = 100 + Math.floor(Math.random() * 100), // Adding randomness to the initial backoff to avoid "thundering herd" scenario where a lot of clients that get kicked off all at the same time (say some script or something) and fail to connect all retry at exactly the same time too + externalAbortController?: AbortController + ): Promise { + const controller = externalAbortController ?? new AbortController(); + const { signal } = controller; + options = { + ...options, + signal, + headers: { ...this.headers, ...options?.headers }, + }; // Merge any existing options with the signal and headers + + // Set a timeout to abort the request if it takes too long + const timeout = setTimeout(() => controller.abort(), this.timeout); + + try { + const response = await fetch(url, options); + clearTimeout(timeout); // Clear the timeout if the request completes in time + if (!response.ok) { + const errorBody = await response.text(); + throw new Error( + `HTTP error! status: ${response.status}${ + errorBody ? `, body: ${errorBody}` : "" + }` + ); + } + const data = await response.json(); + return schema.parse(data); + } catch (error) { + clearTimeout(timeout); + if ( + retries > 0 && + !(error instanceof Error && error.name === "AbortError") + ) { + // Wait for a backoff period before retrying + await new Promise((resolve) => setTimeout(resolve, backoff)); + return this.httpRequest(url, schema, options, retries - 1, backoff * 2); // Exponential backoff + } + throw error; + } + } + + /** + * Fetch the set of available price feeds. + * This endpoint can be filtered by asset type and query string. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param options Optional parameters: + * - query: String to filter the price feeds. If provided, the results will be filtered to all price feeds whose symbol contains the query string. Query string is case insensitive. Example: "bitcoin". + * - filter: String to filter the price feeds by asset type. Possible values are "crypto", "equity", "fx", "metal", "rates". Filter string is case insensitive. + * + * @returns Array of PriceFeedMetadata objects. + */ + async getPriceFeeds(options?: { + query?: string; + filter?: string; + }): Promise { + const url = new URL("v2/price_feeds", this.baseURL); + if (options) { + this.appendUrlSearchParams(url, options); + } + return await this.httpRequest( + url.toString(), + schemas.PriceFeedMetadata.array() + ); + } + + /** + * Fetch the latest publisher stake caps. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed publisher caps. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the publisher caps in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the parsed publisher caps should be included in the response. Default is false. + * + * @returns PublisherCaps object containing the latest publisher stake caps. + */ + async getLatestPublisherCaps(options?: { + encoding?: EncodingType; + parsed?: boolean; + }): Promise { + const url = new URL("v2/updates/publisher_stake_caps/latest", this.baseURL); + if (options) { + this.appendUrlSearchParams(url, options); + } + return await this.httpRequest( + url.toString(), + schemas.LatestPublisherStakeCapsUpdateDataResponse + ); + } + + /** + * Fetch the latest price updates for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update using the options object. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns PriceUpdate object containing the latest updates. + */ + async getLatestPriceUpdates( + ids: HexString[], + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL("v2/updates/price/latest", this.baseURL); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.PriceUpdate); + } + + /** + * Fetch the price updates for a set of price feed IDs at a given timestamp. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param publishTime Unix timestamp in seconds. + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns PriceUpdate object containing the updates at the specified timestamp. + */ + async getPriceUpdatesAtTimestamp( + publishTime: UnixTimestamp, + ids: HexString[], + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL(`v2/updates/price/${publishTime}`, this.baseURL); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.PriceUpdate); + } + + /** + * Fetch streaming price updates for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type, whether the results should include parsed updates, + * and if unordered updates or only benchmark updates are allowed. + * This will return an EventSource that can be used to listen to streaming updates. + * If an invalid hex-encoded ID is passed, it will throw an error. + * + * @param ids Array of hex-encoded price feed IDs for which streaming updates are requested. + * @param options Optional parameters: + * - encoding: Encoding type. If specified, updates are returned in the specified encoding. Default is hex. + * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false. + * - allowUnordered: Boolean to specify if unordered updates are allowed to be included in the stream. Default is false. + * - benchmarksOnly: Boolean to specify if only benchmark prices should be returned. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns An EventSource instance for receiving streaming updates. + */ + async getPriceUpdatesStream( + ids: HexString[], + options?: { + encoding?: EncodingType; + parsed?: boolean; + allowUnordered?: boolean; + benchmarksOnly?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL("v2/updates/price/stream", this.baseURL); + ids.forEach((id) => { + url.searchParams.append("ids[]", id); + }); + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return new EventSource(url.toString(), { headers: this.headers }); + } + + /** + * Fetch the latest TWAP (time weighted average price) for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the calculated TWAP using the options object. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param window_seconds The time window in seconds over which to calculate the TWAP, ending at the current time. + * For example, a value of 300 would return the most recent 5 minute TWAP. Must be greater than 0 and less than or equal to 600 seconds (10 minutes). + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the TWAP binary data in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the calculated TWAP should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns TwapsResponse object containing the latest TWAPs. + */ + async getLatestTwaps( + ids: HexString[], + window_seconds: number, + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL( + `v2/updates/twap/${window_seconds}/latest`, + this.baseURL + ); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.TwapsResponse); + } + + private appendUrlSearchParams( + url: URL, + params: Record + ) { + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + url.searchParams.append(key, String(value)); + } + }); + } +} diff --git a/packages/plugin-pyth-data/src/hermes/HermesClient.ts b/packages/plugin-pyth-data/src/hermes/HermesClient.ts new file mode 100644 index 0000000000..2e4f7d0af8 --- /dev/null +++ b/packages/plugin-pyth-data/src/hermes/HermesClient.ts @@ -0,0 +1,319 @@ +const EventSourceModule = await import('eventsource'); +const EventSource = EventSourceModule.default; + +import { schemas } from "../types/zodSchemas"; +import { z } from "zod"; +import { camelToSnakeCaseObject } from "./utils"; + +// Accessing schema objects +export type AssetType = z.infer; +export type BinaryPriceUpdate = z.infer; +export type EncodingType = z.infer; +export type PriceFeedMetadata = z.infer; +export type PriceIdInput = z.infer; +export type PriceUpdate = z.infer; +export type TwapsResponse = z.infer; +export type PublisherCaps = z.infer< + typeof schemas.LatestPublisherStakeCapsUpdateDataResponse +>; + +const DEFAULT_TIMEOUT: DurationInMs = 5000; +const DEFAULT_HTTP_RETRIES = 3; + +export type UnixTimestamp = number; +export type DurationInSeconds = number; +export type HexString = string; +export type DurationInMs = number; + +export type HermesClientConfig = { + /* Timeout of each request (for all of retries). Default: 5000ms */ + timeout?: DurationInMs; + /** + * Number of times a HTTP request will be retried before the API returns a failure. Default: 3. + * + * The connection uses exponential back-off for the delay between retries. However, + * it will timeout regardless of the retries at the configured `timeout` time. + */ + httpRetries?: number; + /** + * Optional headers to be included in every request. + */ + headers?: HeadersInit; +}; + +export class HermesClient { + private baseURL: string; + private timeout: DurationInMs; + private httpRetries: number; + private headers: HeadersInit; + + /** + * Constructs a new Connection. + * + * @param endpoint endpoint URL to the price service. Example: https://website/example/ + * @param config Optional HermesClientConfig for custom configurations. + */ + constructor(endpoint: string, config?: HermesClientConfig) { + this.baseURL = endpoint; + this.timeout = config?.timeout ?? DEFAULT_TIMEOUT; + this.httpRetries = config?.httpRetries ?? DEFAULT_HTTP_RETRIES; + this.headers = config?.headers ?? {}; + } + + private async httpRequest( + url: string, + schema: z.ZodSchema, + options?: RequestInit, + retries = this.httpRetries, + backoff = 100 + Math.floor(Math.random() * 100), // Adding randomness to the initial backoff to avoid "thundering herd" scenario where a lot of clients that get kicked off all at the same time (say some script or something) and fail to connect all retry at exactly the same time too + externalAbortController?: AbortController + ): Promise { + const controller = externalAbortController ?? new AbortController(); + const { signal } = controller; + options = { + ...options, + signal, + headers: { ...this.headers, ...options?.headers }, + }; // Merge any existing options with the signal and headers + + // Set a timeout to abort the request if it takes too long + const timeout = setTimeout(() => controller.abort(), this.timeout); + + try { + const response = await fetch(url, options); + clearTimeout(timeout); // Clear the timeout if the request completes in time + if (!response.ok) { + const errorBody = await response.text(); + throw new Error( + `HTTP error! status: ${response.status}${ + errorBody ? `, body: ${errorBody}` : "" + }` + ); + } + const data = await response.json(); + return schema.parse(data); + } catch (error) { + clearTimeout(timeout); + if ( + retries > 0 && + !(error instanceof Error && error.name === "AbortError") + ) { + // Wait for a backoff period before retrying + await new Promise((resolve) => setTimeout(resolve, backoff)); + return this.httpRequest(url, schema, options, retries - 1, backoff * 2); // Exponential backoff + } + throw error; + } + } + + /** + * Fetch the set of available price feeds. + * This endpoint can be filtered by asset type and query string. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param options Optional parameters: + * - query: String to filter the price feeds. If provided, the results will be filtered to all price feeds whose symbol contains the query string. Query string is case insensitive. Example: "bitcoin". + * - filter: String to filter the price feeds by asset type. Possible values are "crypto", "equity", "fx", "metal", "rates". Filter string is case insensitive. + * + * @returns Array of PriceFeedMetadata objects. + */ + async getPriceFeeds(options?: { + query?: string; + filter?: string; + }): Promise { + const url = new URL("v2/price_feeds", this.baseURL); + if (options) { + this.appendUrlSearchParams(url, options); + } + return await this.httpRequest( + url.toString(), + schemas.PriceFeedMetadata.array() + ); + } + + /** + * Fetch the latest publisher stake caps. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed publisher caps. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the publisher caps in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the parsed publisher caps should be included in the response. Default is false. + * + * @returns PublisherCaps object containing the latest publisher stake caps. + */ + async getLatestPublisherCaps(options?: { + encoding?: EncodingType; + parsed?: boolean; + }): Promise { + const url = new URL("v2/updates/publisher_stake_caps/latest", this.baseURL); + if (options) { + this.appendUrlSearchParams(url, options); + } + return await this.httpRequest( + url.toString(), + schemas.LatestPublisherStakeCapsUpdateDataResponse + ); + } + + /** + * Fetch the latest price updates for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update using the options object. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns PriceUpdate object containing the latest updates. + */ + async getLatestPriceUpdates( + ids: HexString[], + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL("v2/updates/price/latest", this.baseURL); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.PriceUpdate); + } + + /** + * Fetch the price updates for a set of price feed IDs at a given timestamp. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param publishTime Unix timestamp in seconds. + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the price update in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns PriceUpdate object containing the updates at the specified timestamp. + */ + async getPriceUpdatesAtTimestamp( + publishTime: UnixTimestamp, + ids: HexString[], + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL(`v2/updates/price/${publishTime}`, this.baseURL); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.PriceUpdate); + } + + /** + * Fetch streaming price updates for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type, whether the results should include parsed updates, + * and if unordered updates or only benchmark updates are allowed. + * This will return an EventSource that can be used to listen to streaming updates. + * If an invalid hex-encoded ID is passed, it will throw an error. + * + * @param ids Array of hex-encoded price feed IDs for which streaming updates are requested. + * @param options Optional parameters: + * - encoding: Encoding type. If specified, updates are returned in the specified encoding. Default is hex. + * - parsed: Boolean to specify if the parsed price update should be included in the response. Default is false. + * - allowUnordered: Boolean to specify if unordered updates are allowed to be included in the stream. Default is false. + * - benchmarksOnly: Boolean to specify if only benchmark prices should be returned. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns An EventSource instance for receiving streaming updates. + */ + async getPriceUpdatesStream( + ids: HexString[], + options?: { + encoding?: EncodingType; + parsed?: boolean; + allowUnordered?: boolean; + benchmarksOnly?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL("v2/updates/price/stream", this.baseURL); + ids.forEach((id) => { + url.searchParams.append("ids[]", id); + }); + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return new EventSource(url.toString(), { headers: this.headers }); + } + + /** + * Fetch the latest TWAP (time weighted average price) for a set of price feed IDs. + * This endpoint can be customized by specifying the encoding type and whether the results should also return the calculated TWAP using the options object. + * This will throw an error if there is a network problem or the price service returns a non-ok response. + * + * @param ids Array of hex-encoded price feed IDs for which updates are requested. + * @param window_seconds The time window in seconds over which to calculate the TWAP, ending at the current time. + * For example, a value of 300 would return the most recent 5 minute TWAP. Must be greater than 0 and less than or equal to 600 seconds (10 minutes). + * @param options Optional parameters: + * - encoding: Encoding type. If specified, return the TWAP binary data in the encoding specified by the encoding parameter. Default is hex. + * - parsed: Boolean to specify if the calculated TWAP should be included in the response. Default is false. + * - ignoreInvalidPriceIds: Boolean to specify if invalid price IDs should be ignored instead of returning an error. Default is false. + * + * @returns TwapsResponse object containing the latest TWAPs. + */ + async getLatestTwaps( + ids: HexString[], + window_seconds: number, + options?: { + encoding?: EncodingType; + parsed?: boolean; + ignoreInvalidPriceIds?: boolean; + } + ): Promise { + const url = new URL( + `v2/updates/twap/${window_seconds}/latest`, + this.baseURL + ); + for (const id of ids) { + url.searchParams.append("ids[]", id); + } + + if (options) { + const transformedOptions = camelToSnakeCaseObject(options); + this.appendUrlSearchParams(url, transformedOptions); + } + + return this.httpRequest(url.toString(), schemas.TwapsResponse); + } + + private appendUrlSearchParams( + url: URL, + params: Record + ) { + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + url.searchParams.append(key, String(value)); + } + }); + } +} diff --git a/packages/plugin-pyth-data/src/hermes/examples/HermesClient.ts b/packages/plugin-pyth-data/src/hermes/examples/HermesClient.ts new file mode 100644 index 0000000000..20d9e1f0e5 --- /dev/null +++ b/packages/plugin-pyth-data/src/hermes/examples/HermesClient.ts @@ -0,0 +1,107 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +import { HermesClient, PriceUpdate } from "../HermesClient"; + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +const argv = yargs(hideBin(process.argv)) + .option("endpoint", { + description: + "Endpoint URL for the price service. e.g: https://endpoint/example", + type: "string", + required: true, + }) + .option("price-ids", { + description: + "Space separated price feed ids (in hex without leading 0x) to fetch." + + " e.g: f9c0172ba10dfa4d19088d...", + type: "array", + required: true, + }) + .help() + .alias("help", "h") + .parserConfiguration({ + "parse-numbers": false, + }) + .parseSync(); + +/** + * Extracts the endpoint and basic authorization headers from a given URL string. + * + * @param {string} urlString - The URL string containing the endpoint and optional basic auth credentials. + * @returns {{ endpoint: string; headers: HeadersInit }} An object containing the endpoint URL and headers. + */ +function extractBasicAuthorizationHeadersFromUrl(urlString: string): { + endpoint: string; + headers: HeadersInit; +} { + const url = new URL(urlString); + const headers: HeadersInit = {}; + + if (url.username && url.password) { + headers["Authorization"] = `Basic ${btoa( + `${url.username}:${url.password}` + )}`; + url.username = ""; + url.password = ""; + } + + return { endpoint: url.toString(), headers }; +} + +async function run() { + const { endpoint, headers } = extractBasicAuthorizationHeadersFromUrl( + argv.endpoint + ); + const connection = new HermesClient(endpoint, { headers }); + + const priceIds = argv.priceIds as string[]; + + // Get price feeds + console.log(`Price feeds matching "btc" with asset type "crypto":`); + const priceFeeds = await connection.getPriceFeeds({ + query: "btc", + filter: "crypto", + }); + console.log(priceFeeds); + + // Latest price updates + console.log(`Latest price updates for price IDs ${priceIds}:`); + const priceUpdates = await connection.getLatestPriceUpdates(priceIds); + console.log(priceUpdates); + + // Get the latest 5 second TWAPs + console.log(`Latest 5 second TWAPs for price IDs ${priceIds}`); + const twapUpdates = await connection.getLatestTwaps(priceIds, 5); + console.log(twapUpdates); + + // Streaming price updates + console.log(`Streaming latest prices for price IDs ${priceIds}...`); + const eventSource = await connection.getPriceUpdatesStream(priceIds, { + encoding: "hex", + parsed: true, + allowUnordered: false, + benchmarksOnly: true, + }); + + eventSource.onmessage = (event: MessageEvent) => { + console.log("Received price update:", event.data); + const _priceUpdate = JSON.parse(event.data) as PriceUpdate; + }; + + eventSource.onerror = (error: Event) => { + console.error("Error receiving updates:", error); + eventSource.close(); + }; + + await sleep(5000); + + // To stop listening to the updates, you can call eventSource.close(); + console.log("Closing event source."); + eventSource.close(); +} + +run(); diff --git a/packages/plugin-pyth-data/src/hermes/package-hermes.json b/packages/plugin-pyth-data/src/hermes/package-hermes.json new file mode 100644 index 0000000000..3686b8d034 --- /dev/null +++ b/packages/plugin-pyth-data/src/hermes/package-hermes.json @@ -0,0 +1,58 @@ +{ + "name": "@pythnetwork/hermes-client", + "version": "1.3.0", + "description": "Pyth Hermes Client", + "author": { + "name": "Pyth Data Association" + }, + "homepage": "https://pyth.network", + "main": "lib/HermesClient.js", + "types": "lib/HermesClient.d.ts", + "files": [ + "lib/**/*" + ], + "repository": { + "type": "git", + "url": "https://github.com/pyth-network/pyth-crosschain", + "directory": "apps/hermes/client/js" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:typescript": "tsc", + "build:schemas": "openapi-zod-client ./schema.json --output src/zodSchemas.ts", + "pull:schema": "curl -o schema.json -z schema.json https://hermes.pyth.network/docs/openapi.json", + "example": "node lib/examples/HermesClient.js", + "format": "prettier --write \"src/**/*.ts\"", + "test:lint": "eslint src/", + "prepublishOnly": "pnpm run build:typescript && pnpm run test:lint", + "preversion": "pnpm run test:lint", + "version": "pnpm run format && git add -A src" + }, + "keywords": [ + "pyth", + "oracle" + ], + "license": "Apache-2.0", + "devDependencies": { + "@types/eventsource": "^1.1.15", + "@types/jest": "^29.4.0", + "@types/node": "^20.14.2", + "@types/yargs": "^17.0.10", + "@typescript-eslint/eslint-plugin": "^5.21.0", + "@typescript-eslint/parser": "^5.21.0", + "eslint": "^8.14.0", + "jest": "^29.4.0", + "openapi-zod-client": "^1.18.1", + "prettier": "^2.6.2", + "ts-jest": "^29.0.5", + "typescript": "^4.6.3", + "yargs": "^17.4.1" + }, + "dependencies": { + "@zodios/core": "^10.9.6", + "eventsource": "^2.0.2", + "zod": "^3.23.8" + } +} diff --git a/packages/plugin-pyth-data/src/hermes/utils.ts b/packages/plugin-pyth-data/src/hermes/utils.ts new file mode 100644 index 0000000000..bac6b59f43 --- /dev/null +++ b/packages/plugin-pyth-data/src/hermes/utils.ts @@ -0,0 +1,13 @@ +function camelToSnakeCase(str: string): string { + return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); +} + +export function camelToSnakeCaseObject( + obj: Record +): Record { + const result: Record = {}; + Object.keys(obj).forEach((key) => { + result[camelToSnakeCase(key)] = obj[key]; + }); + return result; +} diff --git a/packages/plugin-pyth-data/src/index.ts b/packages/plugin-pyth-data/src/index.ts new file mode 100644 index 0000000000..47711030a6 --- /dev/null +++ b/packages/plugin-pyth-data/src/index.ts @@ -0,0 +1,19 @@ +import { Plugin } from "@elizaos/core"; +// import getLatestPriceUpdatesAction from "./actions/actionGetLatestPriceUpdates"; +import getPriceFeedsAction from "./actions/actionGetPriceFeeds"; +const actions = [ + // getLatestPriceUpdatesAction, + getPriceFeedsAction, +]; + + +const pythDataPlugin: Plugin = { + name: "pyth-data", + description: "Pyth Data Plugin for price feeds and market data", + actions: actions, + evaluators: [] +}; + +// Export for both CommonJS and ESM +export { pythDataPlugin }; +export default pythDataPlugin; diff --git a/packages/plugin-pyth-data/src/types/eventsource.d.ts b/packages/plugin-pyth-data/src/types/eventsource.d.ts new file mode 100644 index 0000000000..5e9432809a --- /dev/null +++ b/packages/plugin-pyth-data/src/types/eventsource.d.ts @@ -0,0 +1,41 @@ +declare module 'eventsource' { + interface EventSourceInit { + headers?: HeadersInit; + https?: { + rejectUnauthorized?: boolean; + ca?: string | Buffer | Array; + cert?: string | Buffer; + key?: string | Buffer; + passphrase?: string; + }; + withCredentials?: boolean; + proxy?: string; + } + + interface EventSourceStatic { + new(url: string | URL, eventSourceInitDict?: EventSourceInit): EventSource; + readonly prototype: EventSource; + readonly CONNECTING: 0; + readonly OPEN: 1; + readonly CLOSED: 2; + } + + interface EventSource { + readonly CONNECTING: 0; + readonly OPEN: 1; + readonly CLOSED: 2; + readonly readyState: 0 | 1 | 2; + readonly url: string; + readonly withCredentials: boolean; + onopen: ((event: Event) => void) | null; + onmessage: ((event: MessageEvent) => void) | null; + onerror: ((event: Event) => void) | null; + addEventListener(type: string, listener: EventListener): void; + removeEventListener(type: string, listener: EventListener): void; + dispatchEvent(event: Event): boolean; + close(): void; + } + + const EventSource: EventSourceStatic; + export = EventSource; +} \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/types/jstat.d.ts b/packages/plugin-pyth-data/src/types/jstat.d.ts new file mode 100644 index 0000000000..48cdeea5f8 --- /dev/null +++ b/packages/plugin-pyth-data/src/types/jstat.d.ts @@ -0,0 +1,8 @@ +declare module 'jstat' { + const jStat: { + normal: { + inv: (p: number, mean: number, std: number) => number; + }; + }; + export default jStat; +} \ No newline at end of file diff --git a/packages/plugin-pyth-data/src/types/types.ts b/packages/plugin-pyth-data/src/types/types.ts new file mode 100644 index 0000000000..d9c9b30ccf --- /dev/null +++ b/packages/plugin-pyth-data/src/types/types.ts @@ -0,0 +1,1584 @@ +import { WebSocketState } from '../error/websocket'; +import { Content } from '@elizaos/core'; + +// WebSocket Configuration Types +export interface WebSocketConfig { + url: string; + options: { + reconnectInterval: number; // Time between reconnection attempts + maxReconnectAttempts: number; // Maximum number of reconnection attempts + pingInterval: number; // Heartbeat interval + pongTimeout: number; // Time to wait for pong response + connectTimeout: number; // Initial connection timeout + }; +} + +export interface WebSocketMessage { + type: string; + data: Record; + timestamp: number; +} + +export interface WebSocketSubscription { + id: string; + symbol: string; + updateCriteria: { + minChange?: number; // Minimum price change to trigger update + minInterval?: number; // Minimum time between updates + maxInterval?: number; // Maximum time between updates + }; +} + +export interface WebSocketStatus { + state: WebSocketState; + lastMessageTime?: number; + reconnectAttempts: number; + subscriptions: string[]; +} + +// Provider interface +export interface Provider { + connect(): Promise; + disconnect(): Promise; + isConnected(): boolean; + getStatus(): WebSocketStatus; +} + + + +// Provider type +export interface PythDataProvider extends Provider { + type: string; + version: string; + name: string; + description: string; + initialize: () => Promise; + validate: () => Promise; + process: () => Promise; +} + +// Registry type for Akash +export type PythDataRegistryTypes = [string, unknown][]; + +// Deployment related types +export interface PythDataDeploymentId { + owner: string; + dseq: string; +} + + +// Lease related types +export interface PythDataLeaseId { + owner: string; + dseq: string; + provider: string; + gseq: number; + oseq: number; +} + +// Provider types +export interface PythDataProviderInfo { + owner: string; + hostUri: string; + attributes: Array<{ + key: string; + value: string; + }>; +} + +// Bid types +export interface PythDataBidId { + owner: string; + dseq: string; + gseq: number; + oseq: number; + provider: string; +} + +export interface PythDataBid { + id: PythDataBidId; + state: string; + price: { + denom: string; + amount: string; + }; +} + +// Provider configuration +export interface PythDataConfig { + PYTH_DATA_MNEMONIC: string; + RPC_ENDPOINT: string; + CHAIN_ID?: string; + GAS_PRICE?: string; + GAS_ADJUSTMENT?: number; + CERTIFICATE_PATH?: string; +} + +// Message types +export interface PythDataMessage { + type: string; + value: unknown; +} + +// Response types +export interface PythDataTxResponse { + code: number; + height: number; + txhash: string; + rawLog: string; + data?: string; + gasUsed: number; + gasWanted: number; +} + +// Provider state types +export interface PythDataProviderState { + isInitialized: boolean; + lastSync: number; + balance?: string; + address?: string; + certificate?: { + cert: string; + privateKey: string; + publicKey: string; + }; +} + +// Memory room constants +export const PYTH_DATA_MEMORY_ROOMS = { + WALLET: "00000000-0000-0000-0000-000000000001", + DEPLOYMENT: "00000000-0000-0000-0000-000000000002", + LEASE: "00000000-0000-0000-0000-000000000003", + CERTIFICATE: "00000000-0000-0000-0000-000000000004" +} as const; + +// Data Handling Types +export interface DataValidationConfig { + schema: Record; + strictMode?: boolean; + additionalProperties?: boolean; +} + +export interface DataTransformConfig { + inputFormat: string; + outputFormat: string; + transformations?: Array<{ + type: string; + params: Record; + }>; +} + +export interface DataSubscriptionConfig { + maxSubscriptions: number; + maxUpdatesPerSecond: number; + batchSize: number; + validateData: boolean; +} + +export interface DataSequenceConfig { + trackSequence: boolean; + maxGapSize: number; + replayMissing: boolean; +} + +// Data validation result +export interface ValidationResult { + isValid: boolean; + errors?: Array>; + warnings?: Array>; +} + +// Data transformation result +export interface TransformResult { + success: boolean; + data?: T; + error?: Error; + metadata?: { + transformationTime: number; + steps: Array<{ + name: string; + duration: number; + success: boolean; + }>; + }; +} + +// Subscription status +export interface SubscriptionStatus { + id: string; + active: boolean; + lastUpdate: number; + updateCount: number; + errorCount: number; + validationStats: { + total: number; + valid: number; + invalid: number; + }; +} + +// Price Feed Validation Schemas +export interface PriceValidationSchema { + // Base price feed schema + price: { + type: 'number'; + minimum: 0; + description: 'The current price'; + }; + confidence: { + type: 'number'; + minimum: 0; + maximum: 1; + description: 'Confidence interval (0-1)'; + }; + expo: { + type: 'number'; + description: 'Price exponent'; + }; + publishTime: { + type: 'number'; + minimum: 0; + description: 'Unix timestamp of publication'; + }; +} + +// Validation schemas for specific actions +export const ValidationSchemas = { + GET_LATEST_PRICE: { + type: 'object', + required: ['symbol'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Za-z0-9./]+$', + description: 'The symbol to get price for' + }, + options: { + type: 'object', + properties: { + includeEma: { + type: 'boolean', + description: 'Whether to include EMA price' + } + } + } + } + }, + + VALIDATE_PRICE_FEED: { + type: 'object', + required: ['symbol'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Za-z0-9./]+$', + description: 'The symbol to validate' + }, + options: { + type: 'object', + properties: { + validateConfidence: { + type: 'boolean', + description: 'Whether to validate confidence level' + }, + validateUpdateTime: { + type: 'boolean', + description: 'Whether to validate update time' + }, + minConfidence: { + type: 'number', + minimum: 0, + maximum: 1, + description: 'Minimum required confidence level' + }, + maxUpdateAge: { + type: 'number', + minimum: 0, + description: 'Maximum age of last update in seconds' + }, + timeout: { + type: 'number', + minimum: 0, + description: 'Request timeout in milliseconds' + } + } + } + } + }, + + GET_MULTI_PRICE: { + type: 'object', + required: ['symbols'], + properties: { + symbols: { + type: 'array', + items: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + minItems: 1, + maxItems: 100, + description: 'Array of asset symbols' + }, + options: { + type: 'object', + properties: { + includeEma: { + type: 'boolean' + }, + timeout: { + type: 'number', + minimum: 1000, + maximum: 30000 + } + } + } + } + }, + + SUBSCRIBE_PRICE_UPDATES: { + type: 'object', + required: ['symbols'], + properties: { + symbols: { + type: 'array', + items: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + minItems: 1, + maxItems: 50 + }, + options: { + type: 'object', + properties: { + updateInterval: { + type: 'number', + minimum: 100, + maximum: 60000 + }, + includeEma: { + type: 'boolean' + }, + batchUpdates: { + type: 'boolean' + } + } + } + } + }, + + GET_PRICE_FEEDS: { + type: 'object', + properties: { + assetClass: { + type: 'string', + enum: ['crypto', 'forex', 'equity', 'commodity'] + }, + chain: { + type: 'string', + enum: ['solana', 'evm', 'aptos', 'starknet', 'ton', 'fuel'] + }, + options: { + type: 'object', + properties: { + includeMetadata: { + type: 'boolean' + }, + validateAvailability: { + type: 'boolean' + } + } + } + } + }, + + GET_PRICE_HISTORY: { + type: 'object', + required: ['symbol', 'options'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + options: { + type: 'object', + required: ['interval', 'duration'], + properties: { + interval: { + type: 'number', + minimum: 60, + description: 'Interval in seconds' + }, + duration: { + type: 'number', + minimum: 3600, + maximum: 2592000, + description: 'Duration in seconds (max 30 days)' + } + } + } + } + }, + + GET_PRICE_PAIR_RATIO: { + type: 'object', + required: ['baseSymbol', 'quoteSymbol'], + properties: { + baseSymbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + quoteSymbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + options: { + type: 'object', + properties: { + includeConfidence: { + type: 'boolean' + }, + normalize: { + type: 'boolean' + } + } + } + } + }, + + GET_PRICE_UPDATES_STREAM: { + type: 'object', + required: ['text', 'priceIds'], + properties: { + text: { + type: 'string', + description: 'Description of the price update stream request' + }, + priceIds: { + type: 'array', + items: { + type: 'string', + pattern: '^[0-9a-fA-F]+$' + }, + minItems: 1, + maxItems: 100, + description: 'Array of price feed IDs to stream' + }, + options: { + type: 'object', + properties: { + encoding: { + type: 'string', + enum: ['hex', 'binary'], + description: 'Encoding format for price updates' + }, + parsed: { + type: 'boolean', + description: 'Whether to parse the price updates' + }, + allowUnordered: { + type: 'boolean', + description: 'Whether to allow unordered updates' + }, + benchmarksOnly: { + type: 'boolean', + description: 'Whether to only include benchmark updates' + } + } + }, + success: { + type: 'boolean', + description: 'Whether the operation was successful' + }, + data: { + type: 'object', + properties: { + streamId: { + type: 'string', + description: 'Unique identifier for the stream' + }, + status: { + type: 'string', + enum: ['connected', 'disconnected', 'error'], + description: 'Current status of the stream' + }, + updates: { + type: 'array', + items: { + type: 'object', + required: ['id', 'price', 'confidence', 'timestamp'], + properties: { + id: { + type: 'string', + description: 'Price feed ID' + }, + price: { + type: 'number', + description: 'Current price value' + }, + confidence: { + type: 'number', + description: 'Confidence level' + }, + timestamp: { + type: 'number', + description: 'Update timestamp' + }, + emaPrice: { + type: 'number', + description: 'EMA price value' + } + } + } + }, + error: { + type: 'string', + description: 'Error message if status is error' + } + } + } + } + }, + + MONITOR_PRICE_DEVIATION: { + type: 'object', + required: ['symbol', 'options'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + options: { + type: 'object', + required: ['deviationThreshold', 'baselineType'], + properties: { + deviationThreshold: { + type: 'number', + minimum: 0.1, + maximum: 100 + }, + baselineType: { + type: 'string', + enum: ['fixed', 'moving_average', 'reference_price'] + }, + baselineValue: { + type: 'number' + }, + movingAveragePeriod: { + type: 'number', + minimum: 60 + }, + referenceSymbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + alertInterval: { + type: 'number', + minimum: 1000 + } + } + } + } + }, + + GET_ARBITRAGE_OPPORTUNITIES: { + type: 'object', + required: ['symbols'], + properties: { + symbols: { + type: 'array', + items: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + minItems: 2, + maxItems: 10 + }, + minSpread: { + type: 'number', + minimum: 0.1, + maximum: 100 + }, + maxPathLength: { + type: 'number', + minimum: 2, + maximum: 5 + }, + minConfidence: { + type: 'number', + minimum: 0.1, + maximum: 1 + } + } + }, + + TRACK_LIQUIDITY_METRICS: { + type: 'object', + required: ['symbol'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + options: { + type: 'object', + properties: { + window: { + type: 'string', + pattern: '^[0-9]+(h|d)$' + }, + metrics: { + type: 'array', + items: { + type: 'string', + enum: ['volume', 'volatility', 'spread', 'depth'] + } + }, + updateInterval: { + type: 'number', + minimum: 1000 + } + } + } + } + }, + + GET_NETWORK_STATUS: { + type: "object", + properties: { + options: { + type: "object", + properties: { + includeFeeds: { + type: "boolean", + description: "Whether to include status of individual feeds" + }, + chain: { + type: "string", + description: "Target blockchain network" + }, + timeout: { + type: "number", + description: "Request timeout in milliseconds" + } + } + } + } + }, + + GET_PRICE_AGGREGATION: { + type: "object", + required: ["symbol", "windows"], + properties: { + symbol: { + type: "string", + pattern: "^[A-Z0-9/]+$", + description: "Asset symbol (e.g., 'BTC/USD')" + }, + windows: { + type: "array", + items: { + type: "object", + required: ["duration", "type"], + properties: { + duration: { + type: "number", + minimum: 60, + description: "Window duration in seconds" + }, + type: { + type: "string", + enum: ["twap", "vwap", "ohlc"], + description: "Type of aggregation" + } + } + }, + minItems: 1 + }, + options: { + type: "object", + properties: { + timeout: { + type: "number", + minimum: 1000, + description: "Request timeout in milliseconds" + } + } + } + } + }, + + BATCH_SUBSCRIBE_PRICES: { + type: 'object', + required: ['subscriptions'], + properties: { + subscriptions: { + type: 'array', + minItems: 1, + maxItems: 100, + items: { + type: 'object', + required: ['symbol'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Z0-9]+/[A-Z0-9]+$', + description: 'Trading pair symbol (e.g., BTC/USD)' + }, + updateInterval: { + type: 'number', + minimum: 100, + description: 'Update interval in milliseconds' + }, + minChange: { + type: 'number', + minimum: 0, + maximum: 1, + description: 'Minimum price change to trigger update' + } + } + } + }, + options: { + type: 'object', + properties: { + batchInterval: { + type: 'number', + minimum: 1000, + description: 'Interval for batching updates in milliseconds' + }, + includeEma: { + type: 'boolean', + description: 'Include EMA price in updates' + }, + timeout: { + type: 'number', + minimum: 1000, + description: 'Connection timeout in milliseconds' + } + } + } + } + }, + + CREATE_BID: { + type: "object", + required: ["amount", "chain_id", "permission_key", "target_calldata", "target_contract"], + properties: { + amount: { + type: "string", + description: "Amount of bid in wei" + }, + chain_id: { + type: "string", + description: "The chain id to bid on" + }, + permission_key: { + type: "string", + description: "The permission key to bid on" + }, + target_calldata: { + type: "string", + description: "Calldata for the contract call" + }, + target_contract: { + type: "string", + description: "The contract address to call" + } + } + }, + + GET_BID_STATUS: { + type: "object", + required: ["bid_id", "chain_id"], + properties: { + bid_id: { + type: "string", + description: "The ID of the bid to check" + }, + chain_id: { + type: "string", + description: "The chain ID where the bid was placed" + } + } + }, + + LIST_CHAIN_BIDS: { + type: "object", + required: ["chain_id"], + properties: { + chain_id: { + type: "string", + description: "The chain ID to list bids for" + }, + from_time: { + type: "string", + format: "date-time", + description: "Optional timestamp to filter bids from" + } + } + }, + + CREATE_OPPORTUNITY: { + type: "object", + required: [ + "chain_id", + "permission_key", + "buy_tokens", + "sell_tokens", + "target_call_value", + "target_calldata", + "target_contract", + "version" + ], + properties: { + chain_id: { + type: "string", + description: "The chain ID where the opportunity will be executed" + }, + permission_key: { + type: "string", + description: "The permission key required for execution" + }, + buy_tokens: { + type: "array", + items: { + type: "object", + required: ["token", "amount"], + properties: { + token: { + type: "string", + description: "Token address to buy" + }, + amount: { + type: "string", + description: "Amount of tokens to buy" + } + } + } + }, + sell_tokens: { + type: "array", + items: { + type: "object", + required: ["token", "amount"], + properties: { + token: { + type: "string", + description: "Token address to sell" + }, + amount: { + type: "string", + description: "Amount of tokens to sell" + } + } + } + }, + target_call_value: { + type: "string", + description: "Value to send with the contract call" + }, + target_calldata: { + type: "string", + description: "Calldata for the target contract call" + }, + target_contract: { + type: "string", + description: "Contract address to call" + }, + version: { + type: "string", + enum: ["v1"], + description: "API version" + } + } + }, + + DELETE_OPPORTUNITIES: { + type: "object", + required: ["chain_id", "chain_type", "permission_account", "program", "router", "version"], + properties: { + chain_id: { + type: "string", + description: "The chain ID for the opportunity" + }, + chain_type: { + type: "string", + enum: ["svm"], + description: "The type of chain" + }, + permission_account: { + type: "string", + description: "The permission account for the opportunity" + }, + program: { + type: "string", + enum: ["swap_kamino", "limo"], + description: "The program type" + }, + router: { + type: "string", + description: "The router account for the opportunity" + }, + version: { + type: "string", + enum: ["v1"], + description: "API version" + } + } + }, + + LIST_OPPORTUNITIES: { + type: "object", + properties: { + chain_id: { + type: "string", + description: "Optional chain ID to filter opportunities" + }, + mode: { + type: "string", + enum: ["live", "historical"], + default: "live", + description: "Mode to fetch opportunities in" + }, + permission_key: { + type: "string", + description: "Optional permission key to filter by" + }, + from_time: { + type: "string", + format: "date-time", + description: "Optional timestamp to filter opportunities from" + }, + limit: { + type: "integer", + minimum: 0, + maximum: 100, + default: 20, + description: "Maximum number of opportunities to return" + } + } + }, + + SUBMIT_QUOTE: { + type: "object", + required: [ + "chain_id", + "input_token_mint", + "output_token_mint", + "router", + "specified_token_amount", + "user_wallet_address", + "version" + ], + properties: { + chain_id: { + type: "string", + description: "The chain ID for creating the quote" + }, + input_token_mint: { + type: "string", + description: "The token mint address of the input token" + }, + output_token_mint: { + type: "string", + description: "The token mint address of the output token" + }, + router: { + type: "string", + description: "The router account to send referral fees to" + }, + specified_token_amount: { + type: "object", + required: ["amount", "side"], + properties: { + amount: { + type: "number", + description: "The amount of tokens" + }, + side: { + type: "string", + enum: ["input", "output"], + description: "Whether this is the input or output amount" + } + } + }, + user_wallet_address: { + type: "string", + description: "The user wallet address which requested the quote" + }, + version: { + type: "string", + enum: ["v1"], + description: "API version" + } + } + }, + + BID_ON_OPPORTUNITY: { + type: "object", + required: [ + "opportunity_id", + "amount", + "deadline", + "executor", + "nonce", + "permission_key", + "signature" + ], + properties: { + opportunity_id: { + type: "string", + description: "The ID of the opportunity to bid on" + }, + amount: { + type: "string", + description: "The bid amount in wei" + }, + deadline: { + type: "string", + description: "The latest unix timestamp in seconds until which the bid is valid" + }, + executor: { + type: "string", + description: "The executor address" + }, + nonce: { + type: "string", + description: "The nonce of the bid permit signature" + }, + permission_key: { + type: "string", + description: "The opportunity permission key" + }, + signature: { + type: "string", + description: "The bid permit signature" + } + } + }, + + GET_PRICE_UPDATES_AT_TIMESTAMP: { + type: 'object', + required: ['text', 'priceIds', 'timestamp'], + properties: { + text: { + type: 'string', + description: 'Description of the price update request' + }, + priceIds: { + type: 'array', + items: { + type: 'string', + pattern: '^[0-9a-fA-F]+$' + }, + minItems: 1, + maxItems: 100, + description: 'Array of price feed IDs to fetch' + }, + timestamp: { + type: 'number', + minimum: 0, + description: 'Unix timestamp to fetch prices at' + }, + success: { + type: 'boolean', + description: 'Whether the operation was successful' + }, + data: { + type: 'object', + properties: { + updates: { + type: 'array', + items: { + type: 'object', + required: ['id', 'price', 'confidence', 'timestamp'], + properties: { + id: { + type: 'string', + description: 'Price feed ID' + }, + price: { + type: 'number', + description: 'Price value at timestamp' + }, + confidence: { + type: 'number', + description: 'Confidence level' + }, + timestamp: { + type: 'number', + description: 'Update timestamp' + }, + emaPrice: { + type: 'number', + description: 'EMA price value' + } + } + } + }, + error: { + type: 'string', + description: 'Error message if operation failed' + } + } + } + } + }, + + GET_LATEST_TWAPS: { + type: 'object', + required: ['text', 'priceIds', 'windows'], + properties: { + text: { + type: 'string', + description: 'Description of the TWAP request' + }, + priceIds: { + type: 'array', + items: { + type: 'string', + pattern: '^[0-9a-fA-F]+$' + }, + minItems: 1, + maxItems: 100, + description: 'Array of price feed IDs to fetch TWAPs for' + }, + windows: { + type: 'array', + items: { + type: 'number', + minimum: 1 + }, + minItems: 1, + maxItems: 10, + description: 'Array of time windows in seconds' + }, + success: { + type: 'boolean', + description: 'Whether the operation was successful' + }, + data: { + type: 'object', + properties: { + twaps: { + type: 'array', + items: { + type: 'object', + required: ['priceId', 'windows'], + properties: { + priceId: { + type: 'string', + description: 'Price feed ID' + }, + windows: { + type: 'array', + items: { + type: 'object', + required: ['window', 'value', 'confidence', 'timestamp'], + properties: { + window: { + type: 'number', + description: 'Time window in seconds' + }, + value: { + type: 'number', + description: 'TWAP value' + }, + confidence: { + type: 'number', + description: 'Confidence level' + }, + timestamp: { + type: 'number', + description: 'Update timestamp' + } + } + } + } + } + } + }, + error: { + type: 'string', + description: 'Error message if operation failed' + } + } + } + } + }, + + GET_LATEST_PUBLISHER_CAPS: { + type: 'object', + required: ['text'], + properties: { + text: { + type: 'string', + description: 'Description of the publisher caps request' + }, + success: { + type: 'boolean', + description: 'Whether the operation was successful' + }, + data: { + type: 'object', + properties: { + caps: { + type: 'array', + items: { + type: 'object', + required: ['publisher', 'cap', 'timestamp'], + properties: { + publisher: { + type: 'string', + description: 'Publisher address' + }, + cap: { + type: 'number', + minimum: 0, + description: 'Publisher cap value' + }, + timestamp: { + type: 'number', + minimum: 0, + description: 'Last update timestamp' + } + } + } + }, + error: { + type: 'string', + description: 'Error message if operation failed' + } + } + } + } + } +}; + +// Response validation schemas +export const ResponseSchemas = { + PriceResponse: { + type: 'object', + required: ['success'], + properties: { + success: { + type: 'boolean' + }, + data: { + type: 'object', + properties: { + price: { + type: 'number', + minimum: 0 + }, + confidence: { + type: 'number', + minimum: 0, + maximum: 1 + }, + expo: { + type: 'number' + }, + publishTime: { + type: 'number', + minimum: 0 + }, + emaPrice: { + type: 'object', + properties: { + price: { + type: 'number', + minimum: 0 + }, + confidence: { + type: 'number', + minimum: 0, + maximum: 1 + }, + expo: { + type: 'number' + }, + publishTime: { + type: 'number', + minimum: 0 + } + } + } + } + }, + error: { + type: 'string' + } + } + } +}; + +// WebSocket Message Format Validation +export const WebSocketSchemas = { + // Base message format that all WS messages must follow + BaseMessage: { + type: 'object', + required: ['type', 'timestamp'], + properties: { + type: { + type: 'string', + enum: ['price_update', 'heartbeat', 'subscription_success', 'error'] + }, + timestamp: { + type: 'number', + minimum: 0 + } + } + }, + + // Price update message format + PriceUpdateMessage: { + type: 'object', + required: ['type', 'timestamp', 'data'], + properties: { + type: { + type: 'string', + const: 'price_update' + }, + timestamp: { + type: 'number', + minimum: 0 + }, + data: { + type: 'object', + required: ['symbol', 'price', 'confidence', 'publishTime'], + properties: { + symbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + }, + price: { + type: 'number', + minimum: 0 + }, + confidence: { + type: 'number', + minimum: 0, + maximum: 1 + }, + publishTime: { + type: 'number', + minimum: 0 + } + } + } + } + }, + + // Subscription message format + SubscriptionMessage: { + type: 'object', + required: ['type', 'timestamp', 'data'], + properties: { + type: { + type: 'string', + const: 'subscription_success' + }, + timestamp: { + type: 'number', + minimum: 0 + }, + data: { + type: 'object', + required: ['subscriptionId', 'symbol'], + properties: { + subscriptionId: { + type: 'string' + }, + symbol: { + type: 'string', + pattern: '^[A-Z0-9/]+$' + } + } + } + } + } +}; + +// Chain-Specific Validation Schemas +export const ChainValidationSchemas = { + // Solana-specific validation (based on Pyth documentation) + solana: { + priceAccount: { + type: 'object', + required: ['version', 'type', 'size', 'priceType', 'exponent', 'currentSlot', 'validSlot'], + properties: { + version: { + type: 'number', + const: 2 + }, + type: { + type: 'number', + const: 0 + }, + size: { + type: 'number', + const: 3232 + }, + priceType: { + type: 'string', + enum: ['price'] + }, + exponent: { + type: 'number' + }, + currentSlot: { + type: 'number', + minimum: 0 + }, + validSlot: { + type: 'number', + minimum: 0 + } + } + } + }, + + // EVM-specific validation (based on Pyth documentation) + evm: { + priceUpdateData: { + type: 'object', + required: ['id', 'price', 'conf', 'expo', 'publishTime'], + properties: { + id: { + type: 'string', + pattern: '^0x[a-fA-F0-9]{64}$' // Price feed ID format + }, + price: { + type: 'string', // BigNumber format + pattern: '^-?[0-9]+$' + }, + conf: { + type: 'string', // BigNumber format + pattern: '^[0-9]+$' + }, + expo: { + type: 'number' + }, + publishTime: { + type: 'number', + minimum: 0 + } + } + }, + updateFee: { + type: 'object', + required: ['value', 'unit'], + properties: { + value: { + type: 'string', + pattern: '^[0-9]+$' + }, + unit: { + type: 'string', + enum: ['wei', 'gwei', 'ether'] + } + } + } + }, + + // Aptos-specific validation (based on Pyth documentation) + aptos: { + priceInfo: { + type: 'object', + required: ['price_identifier', 'price', 'confidence', 'sequence_number'], + properties: { + price_identifier: { + type: 'string', + pattern: '^0x[a-fA-F0-9]{64}$' + }, + price: { + type: 'string', + pattern: '^-?[0-9]+$' + }, + confidence: { + type: 'string', + pattern: '^[0-9]+$' + }, + sequence_number: { + type: 'number', + minimum: 0 + } + } + } + } +}; + +// Chain-specific configuration validation +export const ChainConfigSchemas = { + // Solana configuration + solana: { + type: 'object', + required: ['endpoint', 'commitment'], + properties: { + endpoint: { + type: 'string', + format: 'uri' + }, + commitment: { + type: 'string', + enum: ['processed', 'confirmed', 'finalized'] + } + } + }, + + // EVM configuration + evm: { + type: 'object', + required: ['rpcUrl', 'pythContractAddress'], + properties: { + rpcUrl: { + type: 'string', + format: 'uri' + }, + pythContractAddress: { + type: 'string', + pattern: '^0x[a-fA-F0-9]{40}$' + }, + gasLimit: { + type: 'number', + minimum: 21000 + } + } + } +}; + +// WebSocket Types +export interface WebSocketErrorEvent { + type: string; + message?: string; + error?: Error; + target: WebSocket; +} + +export interface WebSocketCallback { + (message: WebSocketCallbackMessage): void; +} + +export interface WebSocketCallbackMessage extends Content { + type: 'success' | 'error' | 'update'; + text: string; + data?: { + batchId?: string; + activeSubscriptions?: Array<{ + symbol: string; + status: 'active' | 'pending' | 'failed'; + error?: string; + }>; + updates?: Array<{ + symbol: string; + price: number; + confidence: number; + timestamp: number; + emaPrice?: number; + }>; + }; + error?: Error; +} diff --git a/packages/plugin-pyth-data/src/types/zodSchemas.ts b/packages/plugin-pyth-data/src/types/zodSchemas.ts new file mode 100644 index 0000000000..e570156d5e --- /dev/null +++ b/packages/plugin-pyth-data/src/types/zodSchemas.ts @@ -0,0 +1,323 @@ +import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core"; +import { z } from "zod"; + +const AssetType = z.enum([ + "crypto", + "fx", + "equity", + "metal", + "rates", + "crypto_redemption_rate", +]); +const asset_type = AssetType.nullish(); +const RpcPriceIdentifier = z.string(); +const PriceFeedMetadata = z + .object({ attributes: z.record(z.string()), id: RpcPriceIdentifier }) + .passthrough(); +const PriceIdInput = z.string(); +const EncodingType = z.enum(["hex", "base64"]); +const BinaryUpdate = z + .object({ data: z.array(z.string()), encoding: EncodingType }) + .passthrough(); +const RpcPrice = z + .object({ + conf: z.string(), + expo: z.number().int(), + price: z.string(), + publish_time: z.number().int(), + }) + .passthrough(); +const RpcPriceFeedMetadataV2 = z + .object({ + prev_publish_time: z.number().int().nullable(), + proof_available_time: z.number().int().nullable(), + slot: z.number().int().gte(0).nullable(), + }) + .partial() + .passthrough(); +const ParsedPriceUpdate = z + .object({ + ema_price: RpcPrice, + id: RpcPriceIdentifier, + metadata: RpcPriceFeedMetadataV2, + price: RpcPrice, + }) + .passthrough(); +const PriceUpdate = z + .object({ + binary: BinaryUpdate, + parsed: z.array(ParsedPriceUpdate).nullish(), + }) + .passthrough(); +const ParsedPublisherStakeCap = z + .object({ cap: z.number().int().gte(0), publisher: z.string() }) + .passthrough(); +const ParsedPublisherStakeCapsUpdate = z + .object({ publisher_stake_caps: z.array(ParsedPublisherStakeCap) }) + .passthrough(); +const LatestPublisherStakeCapsUpdateDataResponse = z + .object({ + binary: BinaryUpdate, + parsed: z.array(ParsedPublisherStakeCapsUpdate).nullish(), + }) + .passthrough(); +const ParsedPriceFeedTwap = z + .object({ + down_slots_ratio: z.string(), + end_timestamp: z.number().int(), + id: RpcPriceIdentifier, + start_timestamp: z.number().int(), + twap: RpcPrice, + }) + .passthrough(); +const TwapsResponse = z + .object({ + binary: BinaryUpdate, + parsed: z.array(ParsedPriceFeedTwap).nullish(), + }) + .passthrough(); + +export const schemas = { + AssetType, + asset_type, + RpcPriceIdentifier, + PriceFeedMetadata, + PriceIdInput, + EncodingType, + BinaryUpdate, + RpcPrice, + RpcPriceFeedMetadataV2, + ParsedPriceUpdate, + PriceUpdate, + ParsedPublisherStakeCap, + ParsedPublisherStakeCapsUpdate, + LatestPublisherStakeCapsUpdateDataResponse, + ParsedPriceFeedTwap, + TwapsResponse, +}; + +const endpoints = makeApi([ + { + method: "get", + path: "/v2/price_feeds", + alias: "price_feeds_metadata", + description: `Get the set of price feeds. + +This endpoint fetches all price feeds from the Pyth network. It can be filtered by asset type +and query string.`, + requestFormat: "json", + parameters: [ + { + name: "query", + type: "Query", + schema: z.string().nullish(), + }, + { + name: "asset_type", + type: "Query", + schema: asset_type, + }, + ], + response: z.array(PriceFeedMetadata), + }, + { + method: "get", + path: "/v2/updates/price/:publish_time", + alias: "timestamp_price_updates", + description: `Get the latest price updates by price feed id. + +Given a collection of price feed ids, retrieve the latest Pyth price for each price feed.`, + requestFormat: "json", + parameters: [ + { + name: "publish_time", + type: "Path", + schema: z.number().int(), + }, + { + name: "ids[]", + type: "Query", + schema: z.array(PriceIdInput), + }, + { + name: "encoding", + type: "Query", + schema: z.enum(["hex", "base64"]).optional(), + }, + { + name: "parsed", + type: "Query", + schema: z.boolean().optional(), + }, + { + name: "ignore_invalid_price_ids", + type: "Query", + schema: z.boolean().optional(), + }, + ], + response: PriceUpdate, + errors: [ + { + status: 404, + description: `Price ids not found`, + schema: z.void(), + }, + ], + }, + { + method: "get", + path: "/v2/updates/price/latest", + alias: "latest_price_updates", + description: `Get the latest price updates by price feed id. + +Given a collection of price feed ids, retrieve the latest Pyth price for each price feed.`, + requestFormat: "json", + parameters: [ + { + name: "ids[]", + type: "Query", + schema: z.array(PriceIdInput), + }, + { + name: "encoding", + type: "Query", + schema: z.enum(["hex", "base64"]).optional(), + }, + { + name: "parsed", + type: "Query", + schema: z.boolean().optional(), + }, + { + name: "ignore_invalid_price_ids", + type: "Query", + schema: z.boolean().optional(), + }, + ], + response: PriceUpdate, + errors: [ + { + status: 404, + description: `Price ids not found`, + schema: z.void(), + }, + ], + }, + { + method: "get", + path: "/v2/updates/price/stream", + alias: "price_stream_sse_handler", + description: `SSE route handler for streaming price updates.`, + requestFormat: "json", + parameters: [ + { + name: "ids[]", + type: "Query", + schema: z.array(PriceIdInput), + }, + { + name: "encoding", + type: "Query", + schema: z.enum(["hex", "base64"]).optional(), + }, + { + name: "parsed", + type: "Query", + schema: z.boolean().optional(), + }, + { + name: "allow_unordered", + type: "Query", + schema: z.boolean().optional(), + }, + { + name: "benchmarks_only", + type: "Query", + schema: z.boolean().optional(), + }, + { + name: "ignore_invalid_price_ids", + type: "Query", + schema: z.boolean().optional(), + }, + ], + response: PriceUpdate, + errors: [ + { + status: 404, + description: `Price ids not found`, + schema: z.void(), + }, + ], + }, + { + method: "get", + path: "/v2/updates/publisher_stake_caps/latest", + alias: "latest_publisher_stake_caps", + description: `Get the most recent publisher stake caps update data.`, + requestFormat: "json", + parameters: [ + { + name: "encoding", + type: "Query", + schema: z.enum(["hex", "base64"]).optional(), + }, + { + name: "parsed", + type: "Query", + schema: z.boolean().optional(), + }, + ], + response: LatestPublisherStakeCapsUpdateDataResponse, + }, + { + method: "get", + path: "/v2/updates/twap/:window_seconds/latest", + alias: "latest_twaps", + description: `Get the latest TWAP by price feed id with a custom time window. + +Given a collection of price feed ids, retrieve the latest Pyth TWAP price for each price feed.`, + requestFormat: "json", + parameters: [ + { + name: "window_seconds", + type: "Path", + schema: z.number().int().gte(0), + }, + { + name: "ids[]", + type: "Query", + schema: z.array(PriceIdInput), + }, + { + name: "encoding", + type: "Query", + schema: z.enum(["hex", "base64"]).optional(), + }, + { + name: "parsed", + type: "Query", + schema: z.boolean().optional(), + }, + { + name: "ignore_invalid_price_ids", + type: "Query", + schema: z.boolean().optional(), + }, + ], + response: TwapsResponse, + errors: [ + { + status: 404, + description: `Price ids not found`, + schema: z.void(), + }, + ], + }, +]); + +export const api = new Zodios(endpoints); + +export function createApiClient(baseUrl: string, options?: ZodiosOptions) { + return new Zodios(baseUrl, endpoints, options); +} diff --git a/packages/plugin-pyth-data/src/utils/index.ts b/packages/plugin-pyth-data/src/utils/index.ts new file mode 100644 index 0000000000..b6674c4592 --- /dev/null +++ b/packages/plugin-pyth-data/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './validation'; + diff --git a/packages/plugin-pyth-data/src/utils/validation.ts b/packages/plugin-pyth-data/src/utils/validation.ts new file mode 100644 index 0000000000..ac1c28ca43 --- /dev/null +++ b/packages/plugin-pyth-data/src/utils/validation.ts @@ -0,0 +1,114 @@ +import { elizaLogger } from "@elizaos/core"; +import { DataError, PythErrorCode, ErrorSeverity } from "../error"; +import Ajv, { ErrorObject } from "ajv"; + +const ajv = new Ajv({ + allErrors: true, + verbose: true, + coerceTypes: true, + useDefaults: true +}); + +/** + * Validates data against a JSON schema + * @param data Data to validate + * @param schema JSON schema to validate against + * @returns Promise True if validation succeeds + * @throws DataError if validation fails + */ +export async function validateSchema(data: unknown, schema: object): Promise { + try { + const validate = ajv.compile(schema); + const valid = validate(data); + + if (!valid) { + const errors = validate.errors || []; + elizaLogger.error("Schema validation failed", { + errors, + data, + schema + }); + + throw new DataError( + PythErrorCode.DATA_VALIDATION_FAILED, + "Schema validation failed", + ErrorSeverity.HIGH, + { + errors: errors.map((err: ErrorObject) => ({ + path: err.schemaPath, + message: err.message, + params: err.params + })), + data, + schema + } + ); + } + + return true; + } catch (error) { + if (error instanceof DataError) { + throw error; + } + + elizaLogger.error("Validation error", { + error: error instanceof Error ? error.message : String(error), + data, + schema + }); + + throw new DataError( + PythErrorCode.DATA_SCHEMA_ERROR, + "Schema validation error", + ErrorSeverity.HIGH, + { + error: error instanceof Error ? error.message : String(error), + data, + schema + } + ); + } +} + +/** + * Validates a symbol string format + * @param symbol Symbol to validate + * @returns boolean True if symbol is valid + */ +export function validateSymbol(symbol: string): boolean { + return /^[A-Z0-9/]+$/.test(symbol); +} + +/** + * Validates a timestamp is within acceptable range + * @param timestamp Timestamp to validate + * @param maxAge Maximum age in milliseconds + * @returns boolean True if timestamp is valid + */ +export function validateTimestamp(timestamp: number, maxAge?: number): boolean { + const now = Date.now(); + if (timestamp > now) return false; + if (maxAge && (now - timestamp) > maxAge) return false; + return true; +} + +/** + * Validates price data format + * @param price Price data to validate + * @returns boolean True if price data is valid + */ +export function validatePriceData(price: { + price: string | number; + confidence: string | number; + timestamp: number; +}): boolean { + // Price and confidence must be positive numbers + const priceNum = typeof price.price === 'string' ? parseFloat(price.price) : price.price; + const confNum = typeof price.confidence === 'string' ? parseFloat(price.confidence) : price.confidence; + + if (isNaN(priceNum) || priceNum < 0) return false; + if (isNaN(confNum) || confNum < 0 || confNum > 1) return false; + + // Timestamp must be valid + return validateTimestamp(price.timestamp); +} \ No newline at end of file diff --git a/packages/plugin-pyth-data/tsconfig.json b/packages/plugin-pyth-data/tsconfig.json new file mode 100644 index 0000000000..83bd7f810d --- /dev/null +++ b/packages/plugin-pyth-data/tsconfig.json @@ -0,0 +1,38 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "module": "ESNext", + "target": "ESNext", + "lib": [ + "ESNext", + "DOM" + ], + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "declaration": true, + "sourceMap": true, + "types": [ + "vitest/globals", + "node" + ], + "baseUrl": ".", + "preserveSymlinks": true, + "allowSyntheticDefaultImports": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "test", + "../../packages/core/**/*", + "src/examples/**/*" + ] +} \ No newline at end of file diff --git a/packages/plugin-pyth-data/tsup.config.ts b/packages/plugin-pyth-data/tsup.config.ts new file mode 100644 index 0000000000..4727abcefd --- /dev/null +++ b/packages/plugin-pyth-data/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm"], + dts: true, + splitting: false, + sourcemap: true, + clean: true, + treeshake: true, + minify: true +}); diff --git a/packages/plugin-pyth-data/vitest.config.ts b/packages/plugin-pyth-data/vitest.config.ts new file mode 100644 index 0000000000..2b76c16878 --- /dev/null +++ b/packages/plugin-pyth-data/vitest.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + exclude: ['node_modules', 'dist', '.idea', '.git', '.cache'], + root: '.', + reporters: ['verbose'], + coverage: { + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'test/fixtures/', + 'test/setup/' + ] + }, + setupFiles: ['./test/setup/vitest.setup.ts'] + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}); \ No newline at end of file