From e219562506a19d9fde91ee0f3f9979df905fb839 Mon Sep 17 00:00:00 2001 From: Alice Date: Thu, 22 Aug 2019 19:13:56 +0200 Subject: [PATCH] Add coinpayments --- ccvpn/settings.py | 8 + lambdainst/views.py | 2 +- payments/backends/__init__.py | 1 + payments/backends/coinpayments.py | 301 ++++++++++++++++++++++++++++++ payments/urls.py | 1 + payments/views.py | 12 ++ static/img/spinner.gif | Bin 0 -> 40859 bytes 7 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 payments/backends/coinpayments.py create mode 100644 static/img/spinner.gif diff --git a/ccvpn/settings.py b/ccvpn/settings.py index b6063cc..6c3f417 100644 --- a/ccvpn/settings.py +++ b/ccvpn/settings.py @@ -249,6 +249,14 @@ PAYMENTS_BACKENDS = { 'currency': 'EUR', # 'title': '', }, + 'coinpayments': { + 'enabled': False, + 'merchant_id': '', + 'secret': '', + 'currency': 'EUR', + # 'api_base': '', + # 'title': '', + }, } PAYMENTS_CURRENCY = ('eur', '€') diff --git a/lambdainst/views.py b/lambdainst/views.py index b1e1aad..49b723b 100644 --- a/lambdainst/views.py +++ b/lambdainst/views.py @@ -174,7 +174,7 @@ def index(request): ref_url=ref_url, twitter_link=twitter_url + urlencode(twitter_args), subscription=request.user.vpnuser.get_subscription(include_unconfirmed=True), - backends=sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id), + backends=sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_display_name), subscr_backends=sorted((b for b in ACTIVE_BACKENDS.values() if b.backend_has_recurring), key=lambda x: x.backend_id), diff --git a/payments/backends/__init__.py b/payments/backends/__init__.py index 99efdb8..b08334d 100644 --- a/payments/backends/__init__.py +++ b/payments/backends/__init__.py @@ -6,4 +6,5 @@ from .bitcoin import BitcoinBackend from .stripe import StripeBackend from .coinbase import CoinbaseBackend from .coingate import CoinGateBackend +from .coinpayments import CoinPaymentsBackend diff --git a/payments/backends/coinpayments.py b/payments/backends/coinpayments.py new file mode 100644 index 0000000..e86c5e4 --- /dev/null +++ b/payments/backends/coinpayments.py @@ -0,0 +1,301 @@ +import math +from decimal import Decimal + +from django.shortcuts import redirect +from django.utils.translation import ugettext_lazy as _ +from django.urls import reverse +from constance import config as site_config + +from django.conf import settings as project_settings +from .base import BackendBase + + +import hmac +import hashlib +import requests +import logging +import json +from urllib.parse import urlencode +logger = logging.getLogger(__name__) + + +class CoinPaymentsError(Exception): + pass + + +class CoinPayments: + def __init__(self, pkey, skey, api_url=None): + self.public_key = pkey + self.secret_key = skey.encode('utf-8') + self.api_url = api_url or 'https://www.coinpayments.net/api.php' + + def _sign(self, params): + body = urlencode(params).encode('utf-8') + mac = hmac.new(self.secret_key, body, hashlib.sha512) + return body, mac.hexdigest() + + def _request(self, cmd, params): + params.update({ + 'cmd': cmd, + 'key': self.public_key, + 'format': 'json', + 'version': 1, + }) + print(params) + post_body, mac = self._sign(params) + + headers = { + 'HMAC': mac, + 'Content-Type': 'application/x-www-form-urlencoded', + } + + r = requests.post(self.api_url, data=post_body, + headers=headers) + try: + r.raise_for_status() + j = r.json() + except Exception as e: + raise CoinPaymentsError(str(e)) from e + + if j.get('error') == 'ok': + return j.get('result') + else: + raise CoinPaymentsError(j.get('error')) + + def create_transaction(self, **params): + assert 'amount' in params + assert 'currency1' in params + assert 'currency2' in params + return self._request('create_transaction', params) + + def get_account_info(self, **params): + return self._request('get_basic_info', params) + + def get_rates(self, **params): + return self._request('rates', params) + + def get_balances(self, **params): + return self._request('balances', params) + + def get_deposit_address(self, **params): + assert 'currency' in params + return self._request('get_deposit_address', params) + + def get_callback_address(self, **params): + assert 'currency' in params + return self._request('get_callback_address', params) + + def get_tx_info(self, **params): + assert 'txid' in params + return self._request('get_tx_info', params) + + def get_tx_info_multi(self, ids=None, **params): + if ids is not None: + params['txid'] = '|'.join(str(i) for i in ids) + assert 'txid' in params + return self._request('get_tx_info_multi', params) + + def get_tx_ids(self, **params): + return self._request('get_tx_ids', params) + + def create_transfer(self, **params): + assert 'amount' in params + assert 'currency' in params + assert 'merchant' in params or 'pbntag' in params + return self._request('create_transfer', params) + + def create_withdrawal(self, **params): + assert 'amount' in params + assert 'currency' in params + assert 'address' in params or 'pbntag' in params + return self._request('create_withdrawal', params) + + def create_mass_withdrawal(self, **params): + assert 'wd' in params + return self._request('create_mass_withdrawal', params) + + def convert(self, **params): + assert 'amount' in params + assert 'from' in params + assert 'to' in params + return self._request('convert', params) + + def get_withdrawal_history(self, **params): + return self._request('get_withdrawal_history', params) + + def get_withdrawal_info(self, **params): + assert 'id' in params + return self._request('get_withdrawal_info', params) + + def get_conversion_info(self, **params): + assert 'id' in params + return self._request('get_conversion_info', params) + + def get_pbn_info(self, **params): + assert 'pbntag' in params + return self._request('get_pbn_info', params) + + def get_pbn_list(self, **params): + return self._request('get_pbn_list', params) + + def update_pbn_tag(self, **params): + assert 'tagid' in params + return self._request('update_pbn_tag', params) + + def claim_pbn_tag(self, **params): + assert 'tagid' in params + assert 'name' in params + return self._request('claim_pbn_tag', params) + + +class IpnError(Exception): + pass + + +def ipn_assert(request, remote, local, key=None, delta=None): + if (delta is None and remote != local) or (delta is not None and not math.isclose(remote, local, abs_tol=delta)): + logger.debug("Invalid IPN %r: local=%r remote=%r", + key, local, remote) + raise IpnError("Unexpected value: %s" % key) + + +def ipn_assert_post(request, key, local): + remote = request.POST.get(key) + ipn_assert(request, remote, local, key=key) + + +class CoinPaymentsBackend(BackendBase): + backend_id = 'coinpayments' + backend_verbose_name = _("CoinPayments") + backend_display_name = _("Cryptocurrencies") + backend_has_recurring = False + + def __init__(self, settings): + self.merchant_id = settings.get('merchant_id') + self.currency = settings.get('currency', 'EUR') + self.api_base = settings.get('api_base', None) + self.title = settings.get('title', 'VPN Payment') + self.secret = settings.get('secret', '').encode('utf-8') + + if self.merchant_id and self.secret: + self.backend_enabled = True + + def new_payment(self, payment): + ROOT_URL = project_settings.ROOT_URL + params = { + 'cmd': '_pay', + 'reset': '1', + 'want_shipping': '0', + 'merchant': self.merchant_id, + 'currency': self.currency, + 'amountf': '%.2f' % (payment.amount / 100), + 'item_name': self.title, + 'ipn_url': ROOT_URL + reverse('payments:cb_coinpayments', args=(payment.id,)), + 'success_url': ROOT_URL + reverse('payments:view', args=(payment.id,)), + 'cancel_url': ROOT_URL + reverse('payments:cancel', args=(payment.id,)), + } + + payment.status_message = _("Waiting for CoinPayments to confirm the transaction... " + + "It can take up to a few minutes...") + payment.save() + + form = '
' + for k, v in params.items(): + form += '' % (k, v) + form += ''' + redirecting... + +
+ + ''' + return form + + def handle_ipn(self, payment, request): + sig = request.META.get('HTTP_HMAC') + if not sig: + raise IpnError("Missing HMAC") + + mac = hmac.new(self.secret, request.body, hashlib.sha512).hexdigest() + + # Sanity checks, if it fails the IPN is to be ignored + ipn_assert(request, sig, mac, 'HMAC') + ipn_assert_post(request, 'ipn_mode', 'hmac') + ipn_assert_post(request, 'merchant', self.merchant_id) + + try: + status = int(request.POST.get('status')) + except ValueError: + raise IpnError("Invalid status (%r)" % status) + + # Some states are final (can't cancel a timeout or refund) + if payment.status not in ('new', 'confirmed', 'error'): + m = "Unexpected state change for %s: is %s, received status=%r" % ( + payment.id, payment.status, status + ) + raise IpnError(m) + + # whatever the status, we can safely update the text and save the tx id + payment.status_text = request.POST.get('status_text') or payment.status_text + payment.backend_extid = request.POST.get('txn_id') + + received_amount = request.POST.get('amount1') + if received_amount: + payment.paid_amount = float(received_amount) * 100 + + # And now the actual processing + if status == 1: # A payment is confirmed paid + if payment.status != 'confirmed': + if payment.paid_amount != payment.amount: + ipn_assert(request, payment.paid_amount, payment.amount, 'paid', + delta=10) + vpnuser = payment.user.vpnuser + vpnuser.add_paid_time(payment.time) + vpnuser.on_payment_confirmed(payment) + vpnuser.save() + + # We save the new state *at the end* + # (it will be retried if there's an error) + payment.status = 'confirmed' + payment.status_message = None + payment.save() + + elif status > 1: # Waiting (that's further confirmation about funds getting moved) + # we have nothing to do, except updating status_text + payment.save() + return + + elif status == -1: # Cancel / Time out + payment.status = 'cancelled' + payment.save() + + elif status == -2: # A refund + if payment.status == 'confirmed': # (paid -> refunded) + payment.status = 'refunded' + # TODO + + elif status <= -3: # Unknown error + payment.status = 'error' + payment.save() + + def callback(self, payment, request): + try: + self.handle_ipn(payment, request) + return True + except IpnError as e: + payment.status = 'error' + payment.status_message = ("Error processing the payment. " + "Please contact support.") + payment.backend_data['ipn_exception'] = repr(e) + payment.backend_data['ipn_last_data'] = repr(request.POST) + payment.save() + logger.warn("IPN error: %s", e) + raise + + diff --git a/payments/urls.py b/payments/urls.py index e81a558..ea737cf 100644 --- a/payments/urls.py +++ b/payments/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ url(r'^callback/coingate/(?P[0-9]+)$', views.callback_coingate, name='cb_coingate'), url(r'^callback/stripe/(?P[0-9]+)$', views.callback_stripe, name='cb_stripe'), url(r'^callback/coinbase/$', views.callback_coinbase, name='cb_coinbase'), + url(r'^callback/coinpayments/(?P[0-9]+)$', views.callback_coinpayments, name='cb_coinpayments'), url(r'^callback/paypal_subscr/(?P[0-9]+)$', views.callback_paypal_subscr, name='cb_paypal_subscr'), url(r'^callback/stripe_subscr/(?P[0-9]+)$', views.callback_stripe_subscr, name='cb_stripe_subscr'), diff --git a/payments/views.py b/payments/views.py index f26e110..b5a4d1b 100644 --- a/payments/views.py +++ b/payments/views.py @@ -119,6 +119,18 @@ def callback_coinbase(request): return HttpResponseBadRequest() +@csrf_exempt +def callback_coinpayments(request, id): + """ CoinPayments payment callback """ + backend = require_backend('coinpayments') + + p = Payment.objects.get(id=id) + if backend.callback(p, request): + return HttpResponse() + else: + return HttpResponseBadRequest() + + @csrf_exempt def callback_paypal_subscr(request, id): """ PayPal Subscription IPN """ diff --git a/static/img/spinner.gif b/static/img/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..332cab3cd3eb36f116c89f92631b0f828ef2f6d0 GIT binary patch literal 40859 zcmdqKXH=8v_C7oViXe!HWmHhVYR5~F9!~hD40-<9E?1~CVZ-(9k=|LRNT#i?{ z#$R+#xa5&=*)x$f-pNRZjLhGUzcyO+S5)wn%e@Z$d9iw8q5dxu{>9Dem^rz{>7X3FDE~|f_eMHe4KduapK*_*Y7^QfqDON^8KgD51-zC z`1J1MANaig_{aNCFrPo*^T+3pe|-M*$Cp1oqxt;hFXqeF|Mky*{>vZn|L|Lx*xDEw zSeuZPRJebIe-Gjxr}1(B^5GW+^p6AmGx>%0>n{_0qU3YgH-dQQ$~iWkJ6RJdBuU92 zm*&((E>!dBZ7j`gh!r!65H&B$YlNK|-O4jOH*4&LYIea<1%xQ%CJ(GM~#A93}SQ zxCFnPZYL*Q=*t9cZAHswdD;W+sR?GiMhN95n_R2WzqVbRV4ny z(guP3BKc3^o0lHQ3tTO)Ka(rMqFTYcAv{c>oWE`!)c4kbV zK=9C}5BgkrTZne$jk2;c)ra--c9HT9t~Mn)89rZ9JdzWoIX03TXDT1u0y zRBd;vtlO)+{*a<$pu)xwd8bGsJxw}RtnBLL$hoc-U?e2dL_}M!I;Q123^U3H0 z%+s;gFbF@Ny&k|J^!eoQi%A5buimmC6u|S%dmKCgEdeSyXbFJ%1^^Qu3^oBdIpB#S z=Z8;!0CECma^}+?pK;{;^5yIQ4wW281=xG~bFk;uU^Qs8`{AYY{ti19i(7Q@ghVdd zmVMdhW-qg3+VqIU0DB}!(=87=a;)X}dV5aG>iJN^*Vu#$$Rz>nT{24AT+UP3l=6kcA;Gut6kU5EdYYj=tJa<_}5 zW!x8uJLzh>Mbi2cb=1n^cIo*o8W6G{b-ZS}O*pgpkPo|8{p1J)Y&v``(%rml! z4H{>hXB4$2`E6*{4U{mts!Ir~UGM$aDhh+W$1K=uh(AUDD!PB-{WkYh-}|pJ&N>fM z(sJGmwMm4Y+fmI&C=IV5AO51#7pK5Alh=Y=AohW0S-W;cG+~x6E!!+bEG*on!&*4p zZd&TxnZym5K^8$(M=GKZ&aa+pC2{c8oJjqO4v7vW201WXhij{!Q-JhQn&dLwF$P)U z()BKjSVm)?u)3LWXP30aCW(d8*4q+k3TBUZXD5Yw@y(81@ghf9QNxfVlKcW z`eq=oLknVd^+JiztM59Re%KcQ<7(~< z)O8O4(!st6Fo)3X(OBnWm}l&L@j2{^_XlB3eD!z)0USWv;P@K=wUL)-zJs>c-&Vtk zw;x_(=!R{9Xc5GL3{dO`2m}23^cfAl0)F@L%a=c}jqnR#+n@eSUH1O;!ymsf^_Kzi z9{e2S>Fu!^tnurw2gr*{J!#R(lN9N&?a4*&c8{D2x4H-sfIK-#)2K8`j)@{)ug)3I z<=)hAa~r;rqWMZX%y)X83a9QQL|obLGsDJXxsO9;s{aBX8X4IbN! z3dXmXk)4Ij82r@L=^4{+5;t_0XPk3Pc^Pm!LA!Kuo4|$V)lrKa8dqfA5b*sF>96D@ zsgY`arQxJ<>m_QuzYHb5H)Ie&-j#<;qsG~p3<83@YTW=sqpP1}!>&7eKNiFxZnM~0tXW=Be*lA;Z|gXs~D5AJcDR1`DkwjeI7RBMws@=ITY zep#c0kWzwmPPj(EXoHh^)&Or<>;?7CM#bmj!cx+uRS8jyu#B+q_>>CQHffu$Aq;uS zi+k-!(f&PxaRz-k37{rSdz=0GD+~I*{)?bb&)7nXx=GDutCIbG`Hja2PSG+h3~73zlxwntYpS?gnz&oK z7<1!%X!*1P(ng)nHz3&Kb;AYK%LyS6xN$%`*XOI|YK$q4kauiVDy z@!dk!Zg(FTGOTy7+m$r|AAZP2yvMN2!5YVcJA`=sH@a9@hcjfL#|`&b47vIKNXr8> z_H@(?}781fTf$YT=&wF9%c0ImO@_@RQ`#dv6-^(?E@)k($TCdLFPm;qWX+5!V*9ZZ& zZI93CZTDSZyzY6LgJ1K0@3lO>@$|aQxQOi9%#or$MD%^Ndwd<;JXs@kl>C(_T zb#5a|-xme-v)Xjxcc&Cg94!zeuJWhQ%N#rWq@Rrtqu)L|A+{i>?&{_7s5@szHr)KW ze?i=bG~{Ep^&P!P)WFti=lZsF6XIc z_MUpz;XI<(r< z+{tjI(sv{mpl`I`8`J&rFTy^3eSHH114BbY0~4~oh3yKOtB$>|=BC4Ht`RC8$CW&j z6unXvyffr{GKoG2@_^mqL)@H=XfD7XhJC>h}r`DaoDX7u-nwD=oW|_0)f=}v3cz->?p`V zKtMgfyP%>*AhpMen_$laY8!g08BIwu?0h)Qg)<@0Tv+&$D?6|=VQ2cK&dkeb?K=r; z-}Sq|Zi|81)<57-TMPjo0jUM-yL#&>@K?@;SAAyymb_XR>^cam9$+8v+UEPi80w8+ zq;{<1(HPta0sipK4?qw{KsXa}ZiM)O5S<3u)D{ke*dhq*_TBgdx(>o=kaZmd>|;UR zlq=u|bOnU_AGQL1M`Ur-!xll#E%47EhQ0%vAe#Rf76JHh^aJesu;8MfwNT>2G^>q^%!b<2jSdqsKlwisUGtc@_$;s8 z&Vi;wr(Sx_N+NC+Y~V9`w|4-@&TD;Na^yUsU3Jtn^MH?beL_lKKbl#P3x!e|#7=4W zTgvk>grAsY3f<#7*zV=ar^&O^h9ZAwQq4-4?;RII<7|eoxMrNFZ=*cd)4)jmt6n*L z1lPBIZ3o{*&x?o(z1KNgnL)0$SaoBhs3}lR$H+}mY(FDU_REg`@ObHh$(hI1-?`>X z*}sd=jgc#Gi;PMZQ1?jvl91!7BIoj^NnUK`CD-jGnxVp0bplx{RK-%r#96Tlys2(UlM1^?=#}*a5Z0 zCKpf-I4+#^0PjEn!8bmDJfOD5hfcxLXMW@~tb41`x`zuQWn{|4mbS&0w(@uV!ep+*7gAW0pA7eLzg{F5P{1czWo8y1q4Lw z7U3O&Jhbuk0Pqd9JQ!~6MY0G2zDGzI>3jl9ABZE*?)0(skpZ;#Js3jRhu`-ekG{gi zk^b@51J98-GK_QIiMP*QW6H>zZ^{S~N6_kr5Rh}+`^JD-@W)a>@H^fow*2uIJom3* z@PGO)K;F%tgS_4f^T8VHwI#v6-W}_6R(JE%hcEJcQbE~Yz?_`M6SWl9y&Q8!UROts zvmRfscmD823N_xAmMS2Z3hSO#B;$Nai@CK$W)Sg>V_``!)x$!AlB&OXO9W*F(ln&j z2cJ-DXxq?e;Vtaiyj!U!TTh4I%4lxGxX#vg(q?WQxt;XoaNLVb(owq*7pzxkFd($S z+w+cT%>H=oZ8birx#+kz&o%Uz_e`xS3S+1FX{oi#>p$CXi76T*XT{8XDB{$fXIos**Hj&eI4@4Ojl~ImOcA;F;%LaDbJ+-b>N{u1?X|y5S z=-TJ^kCntv)11X3w%m==I#2izab#IE?FnJRa&Cm| z>uSJh$YUwJUP+gM%>tE$jSiP7a8!d+9Y@p!;)=6YAg-{>Ve4FD=&2^qC;-d=z2ZtS zrGmv{+kwi$I)|wih|?xjqDjSQE$fzd9o_PP*%sB@EB;>l1<4nfEv$ARQ((0ADSpL+ z`yCtJ5USzp98xMUV8gKuU+Mtfy4ctTs>MAPwjts7(Fm?xVAwV`fvFbgy2m;2jRMHM zWN8=pcE`rHslo+YvcPV97`gr9Eo^%DviA|;+(%5Z078oo=f?N>^H-p_ES5V3`mmkt ze?qvazXWLa@aNF31TA=0wnaO9y{*%-EjxKC!+SiO%gKIe%-gr~`Ii9N#h5ds0PQxa z^YxnN7hWb)`W+-@u=IY_mVALL8b_x6sU>{`cU=$DwE z;oWs%TW?idd@JFEdfO7!m43bpwrGY#JM4LU&igA*Cy-m;aZSeymgPe=V*fd)DcR_v z)pf5bda~AZVp(V}HFv4bT^rZX5J0;Wx#JVYMN(Eq^`bp5qjUSnSu1DusR7!p>R)*1 z(&X6evrjx5u4TGT^fME*_N=^e_43wI>#cr!l~T+6q}NREt1)BLIG(*CVL_5KuU>XI zS-aNa$ge}f!g4cS@8nezJY>WlbXMrYaoHLBNm*(qwsb}t^q&)MIXFD6##u#dD>uK~ zyh~keQqk(dk@{6nB;u5_kI-lOJiZs|WL|LlUZdiRN#7>vtJ>X?1l{6+sN-=_gl0xg zm6{-Nb$c>R!SvBcdt$QNs9@}hPdS~68rzVRajAw9TU&HHnot@=5X#_=?&nv$JgZPD z{)Kdc>z|~IwfEQP{|mJYoYd-@S*=_{*Kyph=@y~xoj}^0rn=>X%GPWZH?$4bA^)(AvfajAlJvcu&G%+7*uZy?QX zg9R?_cR)LIugj|5n~hWnxYuE9m4k4It>O$7a1QJ&k!M;F+F_zBU|SL#>nd*nVNHdp z>V&HuKIwpS7rVgb0u&aGcsW) ztu5Vy4Vbm18MC%vXgAv4i$qy~Iu3IMn+vwdu`YNlr_10=Smto07FRKlKsydcJQjt; zk9Z){0+D_38fmmI-+aJrF1Sn!juy1Tu|W>t4Zq#tUKUv6fWqRy_8Bv>;0zUKt5Z?x zFFv0tPE7gy&%cTP-=_XQ0rP%R%g8fEm}gfEnD;6*+wvxcd18QhDa_W}`7P+67voZ0 z+X(K7&w~e3!3%RXUAiu$jRFU zud=$BgE6mzQwp|5GKEG>^C*4i-LEHad`6Iqndr|Evr?>jq*B$%(hHmN% zJ<4p}v*YQ7nyw4wJQj%>p^sx!`7KF_VygnAbOuZ!lTN!zO5X^5S>bZ_)7uGQn%o~B zcZN9&c*pY}sd)1Kxa=SMd^f3`*ge~6@9$gXPAci7g`F0T@p(y4Lsy03VWrC?s0@`YByqgaE8WTD9Y??#8S zj_}Bha5g-zZ@R_1f6HpO!oV1Gi{)l(v@JGhu69$i-L7oEUtvR_+@=VEbDRuPFH$7k z(j?u|CEPObk#x_L_JDm4=@tNgD%%k7A&UxfspJC!!e~yxLC`P&2jPNe0TvaE%hE#1 zfWiWK1q1}$!Z8{)J*3yVF%WqL?Atum$H(EXCvJc=3)puA^X|cF2UHbc4(KWDb#Rvh z`yQ-w@yLOK4s^H)1<>sT9O#f0Z={PYMD%@bnd22U`lJ)Yh_`efaGTY<(bI zARiwxq#)D-u@)RCKwfLR2Y|D}Ue^qS^}z^-v*IQc)@p~@`hdJ*_d4Y4Ltr=b0(tsI zkf#sn7NZj|pj+Vlm1Rre9PYr@$3EQQV8>#vxODL+{>qUq*w}|odAN3qd-tX+ao^wz zpb&OE07rb&`}Tt=pH0ENzxWhj?)lHHb_2DD$kJ;OkzI_4Y+rb|r*j46U=H(hf{f)d zfVpe8a`Iud^Ii$&yn@236zX|fTDU-QI;?i4{PBq?7C>ZsV71#+SaO)UZMmHs5Ly2S z%6SBHif+O8MY;*`YH?{Jghv%^OUqUr^j#43jwjZ^^I*2u5O-Muz3#xcrszA9at{;n z8HWnu8=s1FS$9M!95b5`3-wZ8XJF~Ojafe{lSrbL?=`TGV;BWWPdqq3eD~Tey}H6n zZ*7ErH-FH+@ssIT_o-pm!$p~Y?0zZYqO<;G*goUoj$0zikDf0m*(P|$Ob^FsstXZFhBC^{yrfR&tfks`J9S9uN0rsw04y_pBUn11u9-M1G-a45eK zq{f}%?Pq9M&?PeEtc&S4bq0h4~igIN@yIJ4+jQ4CMgNTq$ zt-{{GO_|)M{i2kO+lQqRJQhX#ACm2-e+BgE=^HOMwbrp%ueApLx_7BM9#(M*RdS6{ z^hlKVOq27+y@&p1pIMe~qfjT?YgMxg@Q*0=>^#smxU{Ve(L1#I@I&F(mfo~RO@GrxC4)foE zI*Xg{FdB<#w75ut8&>`snVq`fVTU@5y|OkujKE?hWe$PGw>=h_#mvjF^Zh0C;RuH| zHeAX@ACboy|8ffc;h+9B^u7H#=&SXI4R5vc^}W8{DvQOfZt--7J9s*)k@uuA2mFVq z=!REz(boi^&r_SPm#?7kni=)19?e@|EDf>RXNQ2y9PjFJk4yb((tw@lM%5%qRav&B?KL-cgjQfubaRpE72wqCreZr$+v5Lyxpg zg7ackuLzkxj$hN6{C_~-vwsNmL2iSjYoNN4tZcGQ!NN^$^)`a-K52(w$&FFsP7E=Z zBr%tiWk{o7$rPYeEOSkVpavS)^xQMRhK!6U$cmSvyge7Lc_g^z`J!tcobqs47M41E zfa1bHZUbz{Ky*Q@K%j@kHSA%>i$>xs7*jx+@Jzbwh5J!3Sr#4fFhLf)cPuALJ}yVp z0qz|wY6xUD0J8-`i#zSWUks}q>~%1h->wrwyeXiEa1S9K<}yZ7ENBtnz{3(7$ZL#= z61{+RKxNUiegi#Z#k+;PDFAw)%)*Yx5oB=>%FTy}$8v%iu+bqK3ZB)#7%Wm_vCsw> z@_uBqIF)_z7KJvj+y-!3FdcJb*%uh0g*6Xtdh7*{6V#Z(Y$2}k3HCZbIoR1axCWoV zSMVJWE6~6-5Bmh$>%RTkR80E|IQyAz!Pa;8C!sGGK_9&qpie06q*Vvc{cvvtef!dw z;|Vft2>L3U7}99#(?wg~ur8Tej>s*x^=ZCg>_X($w7a271QFhE;^~$N3hiHRLJSPVv~@CIpIca3tg$C58-*R^|jxQQ{!1o zxMx9a9SIIw`G-q*qwppGEd1gRo0*Ru2XO=wE7$hH%+6gNWxz0#5r4# zZ6^Wx&RwUsXmD*rTVH;)uuN6Sn-)Uu=du`*LA7v{MW+IjFQQnV*{pl_HqAW}gT80} zI?c(?ep!^~MR;1>NdULXknY844x*HY88rR~t{$BZjrvK$0mx#Nm5d1$oP-+sg#8_kI}ebK zgM(86j`KNZ!-HQ$gXy_AG9uE;`918D{{;g9`$m5f`w{^AtUX$f0ONJYS>4Vv6rSpt zO)@{2!2CQ=AB`Aq{X}2BJD8MLfpl9SdzDPRYE089DH72Eld`R*%qivM*ei^j88$Lk zsSez!2SXwfRFAfg8P5+xYu~00-uwpnHPUWkY7ZSUMi*Um3H&^uP^1u(S>E``x~Fx9 z6tgb!OYhc3i`JpGaKpu1heD4VX%{k!7MVxfq#Fs|+0xEPliEm&5s|P^=TmL#pXOk` zUMW1W)>PG1cPwmX+}meGVuCclKCzfPX9w78pN^d83q5)+=~aump;gwVN{2ILD;$U0 z^G=yXD)faL_2{`i(UsgM?0n>;c3$*Rp0ODWd446sCX(PzzgfXI#Xj63@}J0gtijCa z6E}G9izM~%q@Gu{rr5*bnIV%UY82VbdlEt_*Lm}(N*7;29If}&9)`U3?oR#{wb_9) zRVUTC-IQ|k{Vf^UV!V;@=Q_%xm2Aqw7>ed&z-V%6P9?Q+ak`|cn7&qRrW4S?|+B$ z)4%@Y&qxTk_mgzDH4kuajnC~cz`gr9*4;cW!Y_J0Q6nEY!4!}-ixC6d>*Sm30Xv_s zAzv>O=q{OBV@z`pD9!}ByHYJaF}a0oy&BQo#|0%pRJz43WLen+F`_|mV@L#7QXe25ZUTHgALOOF8W0N~#Ao~u9u^ZUtV#^t494YdN(Uo$}$)9b~iqqFgz{d*!DBy&A*czYNj7 zx#?!ip+9O~JiZz_Ty^E+o3LAjm#wUR-*DRg;fQ3E#^|lwtFsbqtjte-70bSa-1!gDmYmGoF3&zbzLPWa@?hZ({Y!bRR-0TS1mH!aGw zM~vbX}lI$t>2FR3v^ON?GE1&Srbqga7GF&4+N%b@{J~e7fYRFBL z(dD=u!t+-)w-R8ryTY&t;0o6f(!Q>g~`)niEat9?#YnAmHL*z{pQfaaF3nFfZ(k! za_DLK=j-k&fD{^>`S9c!p1Q^Dc^pXt{CGfjksUb}x$-zU4Dcd@Z3Q{;up1vNd+ekI z7PG)?$5_mw1&RpJnGeK_3WRuxl{dj|2bVrzylCZvI)ZC=a(=&qFMDjm@qL!v4qfpO z)^XxxAk_k={dUX4m@TJ>V2U>di_n5piyU_>uN@fe(EV-%A}on1b>4HfBtYCJmzp7fxC0)S9+(#v zUbdk2IMVn8#tn(}WkZ7Za!qvjmEO#4EFl@YlO(yA;L0=IEi$w}Ugu3-97FH7;(;BjM@f53izx~A zjGE=CT^TYu>*n`|G|RYyn4xW8pTY2pkUq}H81}XI^M6}(Y1cZTCA)gsAKvnRcDMY2 z-^)jHF8bQH^%un&HB}whb8pA3>Y*L?B+P3lonr~acw(rJ@to-kBBR_WhrclISc`Ja zEHWimh^6omI<)=y7tS(0$xA*XHZt>2_x0Binln~NwrRc)=rRjBbR@VT~f|N(THEs@a;(dcA|q{o}AQ+GO3ssA4Vi2h|hhc zlGb(x=Epa?<@U5EiuEYSvsAboM-WP%<}TTGfD6*LS-kV-F6L;qoYk(` znr%_}_|Igrc*DRCaa&IAf&+J^rlw|QW@Iwi%)-iewf!plt-6~IYSBZ~Jz_{+2`WCR z%9}G#=ynUZ^jL9QFtEUN42kYoo(USEeQ_ zFc||?uV4magm`$Q4D%Ip?sqKFg5yTPGZi@Kp=1RsR`CJ9-%YI!z>asweuum6e&DU& z!5zCX0MFX8SSkj7oShECI*h&I92QI8LI4-f;QlRAvVid~(GTG6&CfB}$!U~9Bb&Xi zQhdF4q)FR!@hpz;_7YR0My4>;r}K6oxZ~RYM&@=$&JmBH2jPXDE2-4S&S1+M%Lce3 zrN@JS0dO}ML{xJsDlxNIA5;edhW9bZoDG`lk0_)T(!NXONuoy zvK8LCxbiFZiq^JiaV_Z&4GK6SwM>bzliNc6^e>XuYHhsptaL{c2trCx%=VrXTx`hed1w-ymfR8U_pyTNn^=aXkZD@JeC9vkgVz3zWHF8}|V8 zfYstUE`T0N*|Ocqc+zY)SoXlPg7r;-We=bayvXc?1=b&coGU0&fpc3(Rd7Hr=Ne1I z#dTbExE5tAP-V)uSS@sfD6(2rv~#v&ENc$N&$(IWFPX2)ZZS*&boYJ?2;qaY^= zOQywYrk+oZKu5|;tRMxGE5Lb&P!1bbh<3`=4$)QCM#svMVU1FFuhh3t8A_C4j*}mk zw*US$2MMSC%3osI-+U9`?k5XX=1EfqYwQ9(m!QL)?Amr7&4?baC#2P}NleFMytkwQ z?iLr@6r#hOXZ3^8LRr1l)MtBX-X#PH6FA&C7RWwRPR_b=DG&~KdTSgMQ!6x|%_kjg zUu=Tf1-f+)1y?^iKN?SpV~PaP%PGa&V8i=M^yRHNaiU3(fIe0C$$+ z%MXQ2Hi?UOZTT8?(9V3PZ0KS28Ustl3xK;sZw&4n^BKv_(#PXZ4DZ+8RdOESZoQCl zcu)J65oA?)%(kld9bdmX0$Ek^MY7JetyA7A7;RGRRw8xQwlL#$gwYPiozs6i-nzi~ zNU3&k=uw{fIl{{PC#oqLf}?bv;B`_Tjw|xt4|LMJG9Kt;P=9VtgJR4quBT#RcN+LZ zUU|8;D3njmSgoN>DytP4ji00@fR_( zPEPRRAF5SM-SC#LGSW4%(xy0Qt)^?xHbd|NEGocV6XU1Jpx- zTHK#}20~>B^>E0;ZOND?8LQjFwmnpzf<-Ryq9{zlMfxogGf-m`I`9Gf!R810EjU)t z@`ow~uw*TGlCkUsYE6OQ1+pSz6;W*L!(|NIpohc^yh;UwLKfE@MesMZC2H{bBtC36 zGOAViRu?si>Y{KQog6xtyo@_QTk7O2iQ9m=kK& znZ66IB#qp1*vxq1l{8H_nO>JAsHRr{kY_D!e(0Ki^HcGz9X!#sb>^zX&>;0LfIOu$ z^|MaTUtn2YrB6S@xGW)~nvgXdV7zNwqxjO>O+q@UJ?$G`Ajs=yL*8+D&!wuiS?>=S zF3MVcGlS$dB&wS`+2qjVTG?S}p(J>#v~GcuHHoGKgf`OXpy*z)CE`oE#1t?5($Jye zL$0cm68qBmMEPTCya4Ih!ip#nf9K&SapA>Wj6)5-T(DBT{tI`s{?LHgP07`IPwPlr zv(&<6zb4+M#$4xhZ`XV2#1{#|>D80xOn&-y!v{sLs<^VXS7fTGh-!iRvy# z^OaqDP}(6#(lJ7OQ;e7sW0`Z}QkUcDTz>z$a=i+u`b_fGE zC9~qR;7f)Y6GYTXKv`7j-2CLAa7PAt7l7nn@!W1HI@g&b{ls>Vrq+>^)6JMxAas=7B%KgoqYQB z?a7cgdp0R&V_^7HS~9VDapU?E!gm+X*uI#!Mu&gfW~rtU4GTZghPPGnJL$CDA~OB) zI%>=~rrv?E7V9;mI>DwdgcG$BjSN$t!yRwWZ~Ygf8dYRY$WA_Tjf%EFzzBJ+r7n-jT(c$I6hCrs=n-2d zwcqo*sHCRgdR|E`bNaFQuEvr=D#^i}PUhWDQm9smgpJty1ze`Q8YO##&D*;N7}2}iCh`Ed^Jl=LUYrg{w;E+S;iZU zkFf4N{|kxs+q(BpWVoElQ2d~W);%M09kMMnNNHGmlI(UWuMbe%6ejNyLv&3bxF^ea zq{(<>K<)w!stHBh7D|;lF$^HK(4`DE6{J>xQ4a(QJbVj|J&qc$@ON;BWzkrTDnEQa#h1c?!9OG0-+$--0r%=)-LnrByy5G8ZbPxd zLmrQaRBz`>+w?MKhDmrQ!ac*+{W!zr>)nUp-j{4zPYFSq3hQ304B?q-@~JCh7{gub zuqxGdI5JuD=tE6x-CMirSa7wxsa&FSY`HPEh>T&s!xvW3(kuQyz)U<)aZKcg!7_|L;g{L4flrIHS1>O z2`t(BnQOLMgkBLZ`3=#}Mf0X!e2jjyx+4QSLZ|n=6n|!_8 zJa&UnA7i=MN=YFRi?Uy1NpyWCoiZxAPg2ib_oTF*-aGAZ%Gm9__NkuLMrN_;Rkdv? z>pZ;sqz?YFXqg^`^wHB$NAUYr0o0WIO|ktqHHLzOvC7D7m6_!Vsy(Q*x^_O$7^(r) zQS@-ED>PBrJ5|XCX%>p8D3sF@$`&uSo&=`~;2yFjvw;rTRIs8@Jd%MgdkFV1k1}2( zg@T2EzgN(;KUabJJ|hs?1}-r zVIC`xVo_dP(gm_Rb#SLLegov--c&m>sJh6n!Wp#q{nxGUFIT}Iz6pr;lPTLP$1DbG zHXfIF4$T6NicWnzF%ddG*%#L5UtvxTHpR;W;%y?ijdXY9oH)qWyNKy|c|CR39oofN zV@0s(?cb1~k=C-o`WQ;tK0IB*q%tf#s9wu=7O^%x)7@EDivc&3*M%KAzWKt|q*EEw zuWr2WmLx)0m9pxnddNam#mfR&v#O&$5*jyTZalw*9A1%SDjD5m*qiJk*?N>3o3VMz zPNQvSc)LV538kIuuWmJsN-WwZ;jkoYFwN@AB4gWKWt+|F*66++?Ot|^e)mM?a9Uc@HY@BtBtF*jbm)njOuAg?~*ZRd> zya@t?kD2O{(a-%2r_ax}ldODGe|&p|TJ$1nPEltZx23sB;`4!`Tp6Xd zh4~4#f_b4a`Hi7N$=sH?O=2sWJ*61iB-~toG^K>ejB#|@_y2sIR@=yuwAw>)-9F;R zP+8|#8G5{wTe75k8Z<&-10A4P`#ZMK0g*+CT6PtlnlCF(3rYoE_s$Vx!FP=G3J@y- zuwu}|NU1=bDwrJ)4tcACksWVp#uiwta~vyo=9zc}6@#Mm8CDF+elY{J3}oKeorQ`) zkv}CL)qvuXMbw!VtUIM0#cbIfpqyp_IN@>Xpdeby@gqaJ76ojvI0n`!0L^&cj4Hrl zabF&=Se9J{%h=-9JaDYQatGxCEIZzmW<2mCqogfXi-&ntSY@E>Iw<5-K_Uj*i^s0Q zWA&xrp<3AKaJL=67{1eS);hp24y2(&9=7qpPhqD6NW+8+B+lYKV#Hrr-6`KlEr;E* zI4&oKh6l}lKrr@~f3w*E$i4hIowmfzV$jGjf$$XQ>;r|3k9imo=00MT_BrR6x8IK1 zAdsWW*@gkgW%&c0We#7pr}Ed)_RaEP0?5&A65gh?thYY&5Xz#Ko-Qe*`p7S!j$GYd z7(uy;(iVo%}x&fPE9m#o#{r`J?8DQK=fNIJl^NMR@4A>_Bk{0TaK z#c}8LE|(3Sqj|54HsuLQta7h0P5lqhS*?ZrA@^uEW?GhixaxhK!JRI%=1%r-ko_*> zq*Ir6xd~a`0b9yEGq~bCV9{Aobj7=+<8N2r&)+KZ!lm4*FfDOjXvu#xLId)Sn~G#c z`CA_TsxvR_2-l}M!iRa4xMPiK?%i-+JUt+IR^aaEeLRaj=BrySJ@kv;YPDFE4`lMB z<=&gB>qK`>ljN$C5|VK`Sjuyt=1HNG`Ju+WwbJjsB#zBX>wRM$<0RGBsbr|@(k)^J zF0Mt?v=m`U!_K|46KVGQX2OahidUkJl5`p8j#OGv=(J7pa>Kjz&h*TQs{pN77L&U*E=YZ z;wZF0UQw|C`*epC3wEOldKxJ61`;h$7icvM>~wg$3OsPZk!rEjEh};hL>7zOA}<~w z9wM?>u>jU5(2ey8u=BPxEG~=R@Nh;8{qIv`Ta3<5DFp@n7B{f4Ni8dZ0j3ow8Nhih zY5_%^Q7CWAN}pkgGq528=;2RWK=(01Jaz{j$Crl-6`ZY&!%#VepHucVyz?EL#orC` zr{J6wu#b&e|LwnkX8-L+|L6As?s|R>?phN8?lz^VPXOF4R&u(_QxK8rPl5~(BU_IGp~7zIxUD4&t*<6?Q`5MveYO+ z=gq0O%X)Xm2CZ#I4dP9O%k%dp*O;gBLpNTsy<5nwO^cuf-dT;_7_Je|D}Vfr@i|>3 zZquuuNMWbmc|E=sfjxZji1YA;a_qg&`zHHd^&I=7EiRR?ww1Cez5a1pjluRSs-Xl) zhni)FCy8$4Bfkm?cLvM|>F!Y3ukyi^Aa=~pS)1!4X@-c${Jf}Tfkkz#QUdi!Pq#O1 zDXkTb<8^D2=GtY*vuFoFvP0!$3B$=e@Jdy!%=kf?$^5KyM$OS~Dt!!PLo1hVQM2|N zF(e-q=2}V|?;^?NwrgFamuo({7fwmP(pazW64Mm3Olw;bjTE3SERkX-m`9a8HnK45 zIJrJKPCAMA?^x`V7oPrkPyJ{ZSSVZ2ns25+EYlfI0e4l!GCH(2>lT^`;f27FfazVz$6mIbC^}=@`|2;*pE}xhQIOFpo8d@{wu*jItbP?ZW|t5^>x5LPs(3KSI}<7*z0FEH{tr5_KKs9<&#)Sv=^GyHl7 z);!#(0#)zdoX99k3-e8yz(fhQONH%K0q8^M2iqO%CWnz!*z*3A5Boo{>+gTzzwLN{ zct82V+4y;2u})blzkp1euH^E7=S;*!pKQ$y%nIhbcVmf)fOruMDRV%)v%!45ADOZ% zH&CUQSUSv-D*?pwIR$!!g_CtCdf_bVOvyRwZkydy!nLjC@WR>MQ_1=z3{sDYOv?g| zxEf#L((YMK=QmXPF1RgIoZ#S7AmFVz{dEex?&>(nxRPA%;UIqWQ2YGWr!rk3D$#nC z7OEb|;DGwAM05!L(yh+Zeem7iOYZnR5=b9n9D~{B;h->CZh*6m6 zUT1M}V&@a#4Xw9@RQHp48wg^*c8ZC8{ME=zqCZOp|aU$`Rj$N)c!J59_z>4J03*T(M{{ zAzUKCTqc_H!WmZnp0Rsu%HtRRj@*TgiIw_lch$AKlr|hua0-({iUotiw#XTx4jTT-w!=5(-@Ph%p7Mcct?BWIRC}fN6efV2H zfPWxY0I!8?EfQ$)MUVaR1+ZJpiifK6V9|qv9dxUpx_9<7W+-pQDt$+fTtM|ZE?~gk z2LpZ;IN{+M2BNvx-iJ6Y+oOUSR&cSl2HrDc4Kl0Lp%WfJAPlER3b!ePhJn9lh7sI8 z>@hR6@!|D9=Oc_&e?WqOIfPW`0`7;6k&c{GkUUA{& zja235wA5L0X8`{^o+aE#wV+$aWBs6KOU_f%{JNvk^>;6}dt?GNW8f;;NE9V?MS z1o!K??%emWeT%bxkBZNA7d)BoJNroI)nY zsEh>vc0)TsKx|Iu(V65x6#{QSZSSvUYCHxuyb?5n8uQ?cBNb6HF>WJPYHmAj)Jbve z>I>IDS#;8L$(M`q0&<^I{lj7$5B9Ya=wYf6%bC2)5Gha9kJBUe8i$82@3`*bzFF21 z9=~|uPpnUV0gqo0Qm_I-i5{NA#f&S+vd40*V2#O~DtI{av1&xo3%RV5AMRep zn?%1sAggc+?_og=EI18jHHosxFouiYMR7SDqO8{*r)axBEQmNjqPzyXMwEk&?5<>V z1Z1HjZq~!YXj4A^0E&PAC4gW5&(U4mBmh5;Gmff$-a9hOyr1!mMKCvKU!-4XWoGaQ z+*1YcyYy*t)C+9Mg?zoE#hzE_)K&9syw4C+TtKu{HT+h<7Er~yR~H7?*VJ!bs2z7No7gVXAzJ3Tn=j2z zdQF0k+S$03H?VN_Co^@(%a>Jj)5g|tm%3W0Q@mB7# zi7kGiaSIEA`C^RaQhDMhDn|N}pJ@iAacD)xnW%lM@hj04EER|N*)<k?{sgjYAmdE_^V}D?K?bNhDjG#q81O;a&oo6Y@r?$Z&1Na z$It|dCu;E}jvX{(o$au9%+MRPtUgc-#Bl3f^=Zn6)4BiazI(cAd{u*XT1A8eRYJNWS2nP* z8TwL6v{%Lo9{N->cK7rX&tdH=A11<@lC@^HKT63r+rMa~WSx(Ox!L+6>!bbF$Cs`M zh$9J$ei~gDY83nVeU^l{4oz&ni}sxkm7|2B&?C#fta-AG_O`5HNz1~4D6z2C$tanb zK3t3gG32T!5$7YS;rhvL5*@OBttUg(xMmq;9g-3kmRy+8%H6zp^AXZ)qE5SbxW!cg zbJMVcVoeQt?e}gCP>GhLfv7jO7wZ$>JW1)7mD?$tyM#O3d`K^FAWWRn@>$h9jO(IK zoLZJjwd4U)38qvw??zv~zbk+L7vwHD62=cZ9TW+0lC|dM<`fFW!oq?|rBbYDW;Tw- zYuybUwytvYU*WWWx$7|vpG4A@47F`pnmbQw`{gdj-mm2}h2qi%OW@ecgcT2Gxmdmy zDDD{$aIKJrivk#U1_QFT*y|X`pobL%$&3L*05%5RVBQxW>UDy*NhVFZ`fIk6kW> zdy{c#gT->$kqj`WAUOj=$8TaTp3T5SjX!U8AZ`5VwV!WKdH;}3gQFjcMZmVBSyWF7iw*2aPuW z9^l}&Pw0VWU;}-r2*D~VJIz;n)`orTKNl}y61ye(v!v5X@z9*!Jx;RX`#Q50Zdd7#(_rux zg#|2I(fL?%%pjgDJ~qELS}J|sV6+@pF&9mARyy!|e>S2Y+Y;eOK#$df@i)C%G$IX7ntE7O=Lc2gfKK_0!o6cgdnc@97ZE;^7J9~y6wME%8mK_i4hrfut zE9hYibkVzwRY_q)7Aqb=iUzh7veRhTQ4hW6jkT??0~zS92RSr&(i=($P@Cv5?$KkH zub_UZH}J$6_>^%^3+f?2g;MW1Z##mm1#`0?xC4}fE${ynYX4@x`(J(pKv(l~%Ux@d zQ}C+(2CS~{Sq;#ro-75cQ=PLa^EMXP3!iSAb78E?58 zE4C8tJhgXRE-|(2c|K~HLaMPVzu)RsX`PrctGAvcXK~bS=SQx^XGF?9AL}pnF}!DO zv7rYUAoPOYU>QDehUSVdHy>jAP{&n82yuf%V!T(BBb z{z+zIRSj)b9eveRR27qT$`lu6D{m#*PQ`Wbjzy51GsqF3MWhfs(g_e2Lxlin~$4TESbIEP^&TeXjMUgI=*;zkqPYl5XDSn+?vMzM+~c%lpj48wR80QTlKI=lT) zfdU`}E)z~@f}M+PgscRxRMhYZNioR9Vtd#Wm&q+o*&Q@Ku_!9m6lT4XWd2o8v= zV$oQ*x#8Eg3vG{}-jns}B9@q7b5&Fn0CzV0aWaq^5I+Sw9p06IFZ_Lz)9#^7-w>9+uONLtR0U{mwbeuKHx3+MwqXAC@QqVX+VB_^WD|Ga>Wvj&AhT&1zFZPOV)^mM&`Z-7IwFPZi+gs zvcaV`fwx91RN?eGA792)w$s;4C*(JAv@x%V-IkfmofLg_)r+kmQEmfpr6Mg$b{0Dd1WL~GlNCk2r&jh}ucZC4K=3`a`Qbj77A5h`?=NI8fQyX}Ft*E{<}m5eSbin^lDtt_;bXPTB^y1h z7rfwLSJ8VKv_!)A?!+guZ*`p8LHE>pOf-p_$u)4Br z935|W05{UkGP(4)%0p{soYQgM;jl0Tc+R__m7{^ZkDg1ojz@x)Pll#{zIsrpT1b^@ zSiMR_vr<%tLQJ20+$6d9DYEg?WD};#B+QUWm<%~+3%iaMxM3E8U>jy3J1M~D1OPWs0B0f~ z*Urue-vA}6q$U0-- z*d6>>JkSD^TcGw2XX<_M<0FK=~>k{R@ zd0~-<0Py^PHR^WBKFN~7Gr3<)29DjWnmtu?j<;QaB4}ZX{H8VgeXLer$(Vj1EgZNo z1vD`mv90CN?Q5^YEhaQ9(cpW+6Ti+RsYonjdiV631>3wbx3O-Tm~=GejoF=PUYnoA zDPFFb=5+j__T{Bfy*Vweg;MQnP3AlKUXonu)+^lxIp=-uD~@|TAf)O!p* zb!aPpWmITruwe0OWUUjLq{iL%(JhA8Q<$@*N8!grC2p;v`37wqLA{Q*9h^FY@62I( zsHxz~+@&TFw)wbpzDEbY=3