From 7fbaf437d6ed0d34a974238e28a6231379a958b9 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 25 Jul 2024 20:13:34 +0000 Subject: [PATCH] tests --- .devcontainer/devcontainer.json | 2 +- .devcontainer/setup.sh | 8 +- .github/workflows/test.yml | 48 + .gitignore | 3 + tests/integration/.env | 252 ++++++ .../.v039fk_lnbits_integration_test_folder | 1 + tests/integration/data.zip | Bin 0 -> 69158 bytes tests/integration/start.sh | 44 + tests/integration/stop.sh | 6 + tests/integration/strfry.conf | 138 +++ tests/integration/test_all.py | 838 ++++++++++++++++++ 11 files changed, 1335 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 tests/integration/.env create mode 100644 tests/integration/.v039fk_lnbits_integration_test_folder create mode 100644 tests/integration/data.zip create mode 100644 tests/integration/start.sh create mode 100644 tests/integration/stop.sh create mode 100644 tests/integration/strfry.conf create mode 100644 tests/integration/test_all.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7a26cec..ba522e8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,7 +24,7 @@ "customizations": { "vscode": { "settings": { - "python.pythonPath": "/opt/python/bin/python3.9" + // "python.pythonPath": "/opt/python/bin/python3.9" } } }, diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 53ad44d..41e341c 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,5 +1,6 @@ #!/bin/bash CONTAINER_WORKSPACE_FOLDER=$1 +cd $CONTAINER_WORKSPACE_FOLDER sudo apt update -y sudo apt install -y python3.9-distutils curl sudo apt-get install -y docker.io @@ -8,14 +9,13 @@ curl -fsSL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh sudo bash /tmp/nodesource_setup.sh sudo apt-get install -y nodejs if [ ! -d lnbits ] ; then - sudo git clone https://github.com/lnbits/lnbits.git; + git clone https://github.com/lnbits/lnbits.git; fi -sudo chown 1000:1000 -Rvf lnbits cd lnbits git checkout 0.12.8 poetry env use python3.9 -VENV_PATH=$(poetry env info -p) -sudo ln -s $VENV_PATH /opt/python +# export VENV_PATH=$(poetry env info -p) +# sudo ln -s $VENV_PATH /opt/python make bundle poetry install --no-interaction mkdir -p lnbits/extensions/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..010a30b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,48 @@ +name: Tests + +on: + push: + pull_request: + + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + + - name: Run setup script + run: | + /bin/bash .devcontainer/setup.sh ${{ github.workspace }} + + + - name: Run unit tests + run: | + cd lnbits + poetry_env_path=$(poetry env info --path) + source $poetry_env_path/bin/activate + cd .. + pytest tests/unit/*.py -s + + - name: Setup integration tests + run: bash tests/integration/start.sh + + - name: Run integration tests + run: | + cd lnbits + poetry_env_path=$(poetry env info --path) + source $poetry_env_path/bin/activate + cd .. + pytest tests/unit/*.py -s \ No newline at end of file diff --git a/.gitignore b/.gitignore index bee8a64..3f09e69 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ __pycache__ +lnbits +tests/integration/strfry-data +tests/integration/lnbits_itest_data \ No newline at end of file diff --git a/tests/integration/.env b/tests/integration/.env new file mode 100644 index 0000000..0f1cedc --- /dev/null +++ b/tests/integration/.env @@ -0,0 +1,252 @@ +#For more information on .env files, their content and format: https://pypi.org/project/python-dotenv/ + +###################################### +########### Admin Settings ########### +###################################### + +# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available. +# Warning: Enabling this will make LNbits ignore most configurations in file. Only the +# configurations defined in `ReadOnlySettings` will still be read from the environment variables. +# The rest of the settings will be stored in your database and you will be able to change them +# only through the Admin UI. +# Disable this to make LNbits use this config file again. +LNBITS_ADMIN_UI=true + +# Change theme +LNBITS_SITE_TITLE="LNBits_NWC_SP" +LNBITS_SITE_TAGLINE="free and open-source lightning wallet" +LNBITS_SITE_DESCRIPTION="The world's most powerful suite of bitcoin tools. Run for yourself, for others, or as part of a stack." +# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, cyber +LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador, cyber" +# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg" + +HOST=0.0.0.0 +PORT=5000 + +###################################### +########## Funding Source ############ +###################################### + +# which fundingsources are allowed in the admin ui +LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet" + +LNBITS_BACKEND_WALLET_CLASS=FakeWallet +# VoidWallet is just a fallback that works without any actual Lightning capabilities, +# just so you can see the UI before dealing with this file. + +# How many times to retry connectiong to the Funding Source before defaulting to the VoidWallet +# FUNDING_SOURCE_MAX_RETRIES=4 + +# Invoice expiry for LND, CLN, Eclair, LNbits funding sources +LIGHTNING_INVOICE_EXPIRY=3600 + +# Set one of these blocks depending on the wallet kind you chose above: + +# ClicheWallet +CLICHE_ENDPOINT=ws://127.0.0.1:12000 + +# SparkWallet +SPARK_URL=http://localhost:9737/rpc +SPARK_TOKEN=myaccesstoken + +# CoreLightningWallet +CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc" + +# CoreLightningRestWallet +CORELIGHTNING_REST_URL=http://127.0.0.1:8185/ +CORELIGHTNING_REST_MACAROON="/path/to/clnrest/access.macaroon" # or BASE64/HEXSTRING +CORELIGHTNING_REST_CERT="/path/to/clnrest/tls.cert" + +# LnbitsWallet +LNBITS_ENDPOINT=https://demo.lnbits.com +LNBITS_KEY=LNBITS_ADMIN_KEY + +# LndWallet +LND_GRPC_ENDPOINT=127.0.0.1 +LND_GRPC_PORT=10009 +LND_GRPC_CERT="/home/bob/.lnd/tls.cert" +LND_GRPC_MACAROON="/home/bob/.lnd/data/chain/bitcoin/mainnet/admin.macaroon" # or HEXSTRING +# To use an AES-encrypted macaroon, set +# LND_GRPC_MACAROON="eNcRyPtEdMaCaRoOn" + +# LndRestWallet +LND_REST_ENDPOINT=https://127.0.0.1:8080/ +LND_REST_CERT="/home/bob/.lnd/tls.cert" +LND_REST_MACAROON="/home/bob/.lnd/data/chain/bitcoin/mainnet/admin.macaroon" # or HEXSTRING +# To use an AES-encrypted macaroon, set +# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" + +# LNPayWallet +LNPAY_API_ENDPOINT=https://api.lnpay.co/v1/ +# Secret API Key under developers tab +LNPAY_API_KEY=LNPAY_API_KEY +# Wallet Admin in Wallet Access Keys +LNPAY_WALLET_KEY=LNPAY_ADMIN_KEY + +# AlbyWallet +ALBY_API_ENDPOINT=https://api.getalby.com/ +ALBY_ACCESS_TOKEN=ALBY_ACCESS_TOKEN + +# ZBDWallet +ZBD_API_ENDPOINT=https://api.zebedee.io/v0/ +ZBD_API_KEY=ZBD_ACCESS_TOKEN + +# PhoenixdWallet +PHOENIXD_API_ENDPOINT=http://localhost:9740/ +PHOENIXD_API_PASSWORD=PHOENIXD_KEY + +# OpenNodeWallet +OPENNODE_API_ENDPOINT=https://api.opennode.com/ +OPENNODE_KEY=OPENNODE_ADMIN_KEY + +# FakeWallet +FAKE_WALLET_SECRET="ToTheMoon1" +LNBITS_DENOMINATION=sats + +# EclairWallet +ECLAIR_URL=http://127.0.0.1:8283 +ECLAIR_PASS=eclairpw + +# LnTipsWallet +# Enter /api in LightningTipBot to get your key +LNTIPS_API_KEY=LNTIPS_ADMIN_KEY +LNTIPS_API_ENDPOINT=https://ln.tips + +###################################### +####### Auth Configurations ########## +###################################### +# Secret Key: will default to the hash of the super user. It is strongly recommended that you set your own value. +AUTH_SECRET_KEY="secret" +AUTH_TOKEN_EXPIRE_MINUTES=525600 +# Possible authorization methods: user-id-only, username-password, google-auth, github-auth, keycloak-auth +AUTH_ALLOWED_METHODS="user-id-only, username-password" +# Set this flag if HTTP is used for OAuth +# OAUTHLIB_INSECURE_TRANSPORT="1" + +# Google OAuth Config +# Make sure that the authorized redirect URIs contain https://{domain}/api/v1/auth/google/token +GOOGLE_CLIENT_ID="" +GOOGLE_CLIENT_SECRET="" + +# GitHub OAuth Config +# Make sure that the authorization callback URL is set to https://{domain}/api/v1/auth/github/token +GITHUB_CLIENT_ID="" +GITHUB_CLIENT_SECRET="" + +# Keycloak OAuth Config +# Make sure that the valid redirect URIs contain https://{domain}/api/v1/auth/keycloak/token +KEYCLOAK_CLIENT_ID="" +KEYCLOAK_CLIENT_SECRET="" +KEYCLOAK_DISCOVERY_URL="" + + +###################################### + +# uvicorn variable, uncomment to allow https behind a proxy +# IMPORTANT: this also needs the webserver to be configured to forward the headers +# http://docs.lnbits.org/guide/installation.html#running-behind-an-apache2-reverse-proxy-over-https +# FORWARDED_ALLOW_IPS="*" + +# Server security, rate limiting ips, blocked ips, allowed ips +LNBITS_RATE_LIMIT_NO="200" +LNBITS_RATE_LIMIT_UNIT="minute" +LNBITS_ALLOWED_IPS="" +LNBITS_BLOCKED_IPS="" + +# Allow users and admins by user IDs (comma separated list) +# if set new users will not be able to create accounts +LNBITS_ALLOWED_USERS="" +LNBITS_ADMIN_USERS="" +# ID of the super user. The user ID must exist. +# SUPER_USER="" + +# Extensions only admin can access +LNBITS_ADMIN_EXTENSIONS="ngrok, admin" + +# Start LNbits core only. The extensions are not loaded. +# LNBITS_EXTENSIONS_DEACTIVATE_ALL=true + +# Disable account creation for new users +# LNBITS_ALLOW_NEW_ACCOUNTS=false + +# Enable Node Management without activating the LNBITS Admin GUI +# by setting the following variables to true. +LNBITS_NODE_UI=false +LNBITS_PUBLIC_NODE_UI=false +# Enabling the transactions tab can cause crashes on large Core Lightning nodes. +LNBITS_NODE_UI_TRANSACTIONS=false + +LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" + +# Ad space description +# LNBITS_AD_SPACE_TITLE="Supported by" +# csv ad space, format ";;, ;;", extensions can choose to honor +# LNBITS_AD_SPACE="https://shop.lnbits.com/;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-light.png;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-dark.png" +# LNBITS_SHOW_HOME_PAGE_ELEMENTS=true # if set to true, the ad space will be displayed on the home page +# LNBITS_CUSTOM_BADGE="USE WITH CAUTION - LNbits wallet is still in BETA" +# LNBITS_CUSTOM_BADGE_COLOR="warning" + +# Hides wallet api, extensions can choose to honor +LNBITS_HIDE_API=false + +# LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions-trial.json" +# GitHub has rate-limits for its APIs. The limit can be increased specifying a GITHUB_TOKEN +# LNBITS_EXT_GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxx + +# Path where extensions will be installed (defaults to `./lnbits/`). +# Inside this directory the `extensions` and `upgrades` sub-directories will be created. +# LNBITS_EXTENSIONS_PATH="/path/to/some/dir" + +# Extensions to be installed by default. If an extension from this list is uninstalled then it will be re-installed on the next restart. +# The extension must be removed from this list in order to not be re-installed. +LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos" + +# Database: to use SQLite, specify LNBITS_DATA_FOLDER +# to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://... +# to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://... +# for both PostgreSQL and CockroachDB, you'll need to install +# psycopg2 as an additional dependency +LNBITS_DATA_FOLDER="./data" +# LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename" + +# the service fee (in percent) +LNBITS_SERVICE_FEE=0.0 +# the wallet where fees go to +# LNBITS_SERVICE_FEE_WALLET= +# the maximum fee per transaction (in satoshis) +# LNBITS_SERVICE_FEE_MAX=1000 +# disable fees for internal transactions +# LNBITS_SERVICE_FEE_IGNORE_INTERNAL=true + +# value in millisats +LNBITS_RESERVE_FEE_MIN=2000 +# value in percent +LNBITS_RESERVE_FEE_PERCENT=1.0 + +# limit the maximum balance for each wallet +# throw an error if the wallet attempts to create a new invoice + +# LNBITS_WALLET_LIMIT_MAX_BALANCE=1000000 +# LNBITS_WALLET_LIMIT_DAILY_MAX_WITHDRAW=1000000 +# LNBITS_WALLET_LIMIT_SECS_BETWEEN_TRANS=60 + +# Limit fiat currencies allowed to see in UI +# LNBITS_ALLOWED_CURRENCIES="EUR, USD" + +###################################### +###### Logging and Development ####### +###################################### + +DEBUG=true +DEBUG_DATABASE=false +BUNDLE_ASSETS=true + +# logging into LNBITS_DATA_FOLDER/logs/ +ENABLE_LOG_TO_FILE=true + +# https://loguru.readthedocs.io/en/stable/api/logger.html#file +LOG_ROTATION="100 MB" +LOG_RETENTION="3 months" + +# for database cleanup commands +# CLEANUP_WALLETS_DAYS=90 \ No newline at end of file diff --git a/tests/integration/.v039fk_lnbits_integration_test_folder b/tests/integration/.v039fk_lnbits_integration_test_folder new file mode 100644 index 0000000..3e4aa3c --- /dev/null +++ b/tests/integration/.v039fk_lnbits_integration_test_folder @@ -0,0 +1 @@ +yes v039fk_lnbits_integration_test_folder \ No newline at end of file diff --git a/tests/integration/data.zip b/tests/integration/data.zip new file mode 100644 index 0000000000000000000000000000000000000000..e89b53ec073079d9ab15fdf7a1bef52f9bd25948 GIT binary patch literal 69158 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

p2W&D(}9R&Te@#rR0LoT6@{c z{8^SIkCpV5zV0=A#$n&^|NC+E|BnlfUVJ(KM(U6H&snUG|7T?6t5X+FiAheH!#UDm}~Q;?66XD!D4f z>sRXab4~60cii``rsiUchaWk9y;wH);Qcb~BZ^Ev1o>XSTc^25p66Zckt>$BSq*m9 zZsAXO_w~S5h97sO?nF23p0`A#wf2zKk+!4zOcNJ4MdzEJ6;oARB>B!Se)1QMdowG} zdE8NXq(95@huXZVh3V6u@txbIDnGSTAVb-t+bwxd$(2uO=2^9q0{&dB->GBpeXGhQ z1*7jGKP8sDdST={u}Gi({_RZar<@|JKj1~XChhOX))x(Q3@=aBFcRf7b^D^>G zl}vt8q5Ii0Gnu}8E$G|0wdMBIol4W94lBwr->z*xsN_}jJ1h3gj>wfuoM(OBH1#i! zpY7CGm)^BgG*{hRr*clpWRYWe;^Yl?LY;(lofJ=n%m5EzA`18ee>*m39H(B!gw3D))+bg!UphbYxRJuuMrYR<4C|I|xVCZg!8>o5E_Uu;QSilZ zPWi=%?81ZA_mWDFu70BwX1U~UOb}aG=1#?qV|#kiCZ0O;K>jbwbyXfMwW;6y7OJ+E z&COZ(v+aDFj^Ab8$xW`W9WT`UT2;k%&BMUoHoi?mRev|H{kjc@7O&mvUD%?%yT|SM z)HlJB%>9#t}I z{)>*U4R?rJp!8fl*3>EUrHs@bi^lBS8E2H&n23GedWKKvo<+{wEcfNl^-gN*|J;{o zQ~7zZd-e>M>P6e~WFJfDi2JehY^o^ml}mkZzvlbDDMh=Na^Kls=b!ZX{F!psbJ1pu z_44jA@6^mEOq!$4Jn?7wE9RKoo%^3$xm_UejHQ{RWL=+N6y(CQh!)5WQ9hwA3C7xoO^85!wUwmQDqv+Vqbi=R)d_cpz) zq2{n8+UcKN)E@bQhnveTyx;Pp?9swgnf6jw<0e-HMan69aeJN6wL9a!@~h0-)Zezj zjMHP+dfXLExxDE6sj&I`US;05x)$s@;qLj1f1l=hK3=j&b9K&DrPqmTjur1mL@TtJQLIc6oKnt(;>|G!#kQ2$QF11_i z$VrvItPht@-D9h2cGoCA*ZXYeZfRU< zec{gLTpx?mAsw7grTpJ}ZCdY`rx6G_T?6I6_++H`9C2hDV}GZ&TPxJHs0y|vXhhFur7N%ZHag1`o`}i zZdWI(%y?)o$T{alVx@WY@ps?8EZ@+YXvvWi6>)8u9rsn0Mir^x?kz93o|t@W+0KUO zZ&SHcJN%YUpBK1guF^4flO1zcOt<*f@14MNF=2L|%#Mk%GSj&Z)_6J?aAoTMG*Rv0 zu8NT2@tY|xvvk(}Qye@3Py4yq-dtn%$q)YZcS#J3;Fm)V?|K%j@lmNY{a86O=FtO( z%2t8MWQH>>iZwG!G~!Oj_K-Og9Pd?gar2v3FIWO%-ru>%{PxbmyKWO| z=Xd#BS?ZE-@xtST?oP$bhU>p~DCWz}obr1j&z+b@efRi&pF1zi++fJX{Vanq_u<7A zw@lvM=q>z_$Y0-I_<8wQbmgKlHPf{TfwW3-0kSz_&kVtqV39mq8w3|H-^qT z6Jl~}W2@Fw=gOaEftxJzn6>0DIjc=dUU&V>&X;#z-`bfce|Vn%0=H|WI*V9a7ToGM zl*ARC^+-9r@zAt$cO|E-sM-5(yK>um_P&ggLsqvuzFF~cOE0*taQfEV`I-!jmv~Db z$(t{Ev241QpyK=U9hc7^e92I<`BqO^#)C({>{vAR^-089mfLRJaea$g%I(`W5%Wu* zXyz;2=hG`cD!pn)_%Bn<{JVdbW;yc|y;J$QBa1UBO6#WdA6pL|-Adz6E1u1{T>H)W z#iYft^0_+=Z`5#E7YnXDx@D!K$=B@{-Q>LH2S^uLBaEFRl7NAxU%wj=bigz z*)a9Cm(StZsdp#JM6Kt0CC2XXQ7861bJa}St#|BAjhMTyxE)fuEErp%Kbdj4#rZF- zz4Lwqlr|QA32=`y2xhmOy;}BFM1t0ho^5kapLUobx^_o%{>AY7MLlASj0+}7&yAky zPI{-eRtv54(7e)r$LG*&e+7k ze4q27T7KW3Z~52w1@E6wk$NFF?f!~f!%mrgo;Pdq^;g)um%Y$$_V|^y_?@+`jcRY# zO9mZVr0cQS*x8y#^+;*=sW!4_T5m&L=OJek5{hz>A`rGS~L>F6-WwXLw=5-xUYV z&RAwlD)DD;_-Yc<6{>mOrFi|ZV&T*@neX$|PUg7VJk)S))|S(fmsZ{;z3g+wD}Ft` zSu4$M+FYG^WwjKCr<|Qrt^aGP9%0H>`9hhmGG%xRmzqnp6r5$ z4ULw*S@d%*?~$yxTD(meu6lpIFwEjTtg>u(QCjRn1)UpbpGy4LCd#t4c~8Bq@T>Ef4u+r*^iksJhiUPVBfX6|JK%@9Ww5>xVv@~Z@Z(yyX?|4KH*U1r$VwPAD(wy zRkSozqIMpibN@7^FZ&u3Zk?=tS^rw$hw$9wc9%Z!;>{e~m4}vviU} z&Yd|;vG*2#Q0MDim-qzqrWU_mP?g^)Gf&zkKxXo;@I#x`tBQO29a@jxzSc4^N%O$r z2c70Qa?>-G1=tE{UzS}jd_hRw)BWNTd&N)hc9yn?t=ka6c2_^F^1`utOOCyq>?)fC zWceiz+-8V7^=OLCRf(kXTATQp4S#;kKhm*G^H5f#bnEVk8|^OsZ_0C7uzTd@J7`*Sfy)(Es>0^tr zY=`B;#q~emmGHebg1#X)%(R}KUl=AQ}(aj$bLoV6p{? z<(UUW=LaP;-hF=iTUtWtoLTp07H(-WOHf~UdH(iksdg5M|E*Hl;-59wW!ilGtSikO zy@e@q^DEygS}s$QM1P6B|Jf>_p)@)jI?W@|@1pXJDjCj11tm}O=o30|kg3q9q*AOqYjy3Tsn_kzuI4P*CAobE zZ=AI9J$;w0jf>^>pJZ6#b*W(enLE??C%m?kKl`UI?%fIteI6s%jE0;>16JRB-vb+r z9+hmI{;_1irR|?mIK!1Rx+kQ_3Migd`J&i1^VG!V+r}mePMzKRBXctLX|H8lCHrSx zRs9Z)4NtV3UcGeXlYRU*+3m$kmzF0IuX^|*o{0*ZJk&dfNk1Y7T^`q{Kww3d()_T8m>-t_f z&-=B}A;zY&|J*ki=$_>)6=JKooA%LfaqQlmf4i$ow`@3&`Cn$udFjm?ITTH?9W?fQw|R05{HUH3xk@STX3iL;wN_#J;!HUGck zIn!Brp?}^mEcxmk>YvW$ye&id?8*MgcazRYP477Le7b9=i|@GwkA0P1ivHr`h(i6}AhFyD}Jxw*s z{NXpd=%bZMdWYjwfM%5WFc_nE&=s7#yg5!!cYF}LZ~I~-X)AAX%X zsBu5p)F?T*{ekH2qDh_7j>$zm&mN}E_6RgiIx_pw)SCfmUwJvV)n1gZJdwEaXYbvgvXC-Q#Ulp-g^?AS5`{+=P zO~KkLPV&!u_WFz5nmOLOE9ZtR5|eUrI_ll~Piq?I)TSqocAq|U=<2sQ-1~gr3oKvf zz3F?M>j9g8*^BP~{l~+4<;C?!i#spZ))l1$r`PJSNq;|^p~@gUT{}Q~uk`aeb-!&Y z9lKN-89qK%y0G+P_&Se|&w5s@?p)K6nEGS?j5S_Y=jtwexs3JHwbJsVzGa$U>Ybj? zO$;h2p4I5C}eynN}`B)t^ z<;5G<1s+#j?Qg`#v+1u<)vTUz=&j?@>mjKL!gK!XIUn`2dsAUm+_f}V=!E7O+o00f zTGFnyCm;RrW0IYCkkR>d{{Ay>7h9Y0vltob2+nq?U=~cWVAX8>vU1{-dCL8p#DDMI z;>Y)Ei^H_J=Pc)WYVB6t{POd%(}~Ac$X5jAJl+@)t985SW+|(|D_xTt68}wA9({2V z^OfUQ%wt}#V1m|z$sbnFEzfz~-k!ZAz-vpixYdperah^q^ID1|OW&^V>Rsb28@y=V z1D5@tVuF7jewfwR_}Nd5T~1}+(HTpsXYk#X-j@}kBe=poIl{m4RPWXs>-Fz2olicR z%P`G)d1`~>RsVcbpBV+U^SI7?F)Hshj-4fQLt=%quJiQs2TSJ{^;?F=PK@1h+P7Hbg?D~(jZJ)b4O_Ba!sJUyC{o|0(7Y9#Wo;kT}+O$n7hVR~QYF zEh}Hc{A}Ig?nJ+xlV>y6SNp4Q+={whaO>>J7K^iSJgKc^m95H#vE8j)ZNI zO8K*Af>#yAO!t0H?wWVxsMz;aI}Lm*7_);~Dr3djk7iz4vv}>vw>7FWy5@4< z*nNIw#EBQPwVvcY`~O>O=4Fc>eGev^-3nixH|kcK@NVTZ*4iO4LC9_Ws-4319tLhl zzTDQ(U=5Ip{r|o3n5WXw-Sy1flFN&>-HKi^(QbkK>5?aR=YGHU<=>W{lJTnUXPJ&x zeh^@M?&4%5d@1W;T}Uc}aQY*q`u?UlE?*LEzLaJVm~hTN@6_?))9-Wi_hfWt{EX%n zF%Lddzh3cSpYI)$S!YkLvCBzT{Jo{yMpJc_uod^Ie+6Z?&PlkQywUu)WcM)x??bKr zFP7f$dZMYhZAElvrEsW0;Da)Y&KqYApGo{anb+@n)&HdncbFTsb{sh=Q2j@_H!$t_ zykxs4=@+l|YSrJrXL;JK{kPu?X->uVrO!StPdY9BGU~=X_ZJ?=Pw(2E^I2#0Kj{T8 zj>^@~SSY4kk#9Hg%04@e_xkys_Tk|P&2HyK4zVejIDhfEP*i`q^e@A6OTpYlPbRU? zG4QqQ8~lqe|L%?{@LaiLak<0EklxTo zjn9Hltk^f%J;ysr=zr!{cC}ZUOSe1ZicL9Qbl2vup^9ql>UHB%n04rg7O*IwB2>xHr8t-6J40wUbb zhNc%p$8KJFvG5tsr%k6-*X%s@aZdK9o$DW7lvnf#4$b+wHF0`LarAG=CYdW=XI;+c z&fUtH-O0$2)St4qWUFKSyK?!KJQLB1B>i$XJspqG}NUwUG!N4HG z{Kv{_)+sCd?Rz&LV6c7t=OO2myV9;7rQ`%3e&uK9X(?SR>As!q(2d7zXJ;(^8gjTt zdY^~egwis8=Y{p_^V9TJK1kNqX3tv4bGzkAx#>%@FT7h)PeklHZxy>DvMg#JujPqe zxr{Swk~Xq@J#t>|X^Y#Hp4B}O9;}IH{>_qDSi;NSvfUylJ4WM8N5Kti3(Z~sV(&P* zYbm5~{P5ZCr(TeIeCuB;#jflAFWyT$y^`89cg557ZyWD!moLk@wCKsti| zoZi_Zs~Y5geb>9HEg8DencLJ?(X*TTWcB=CtDj$9Gyf)|lF?Mol5sF)h78z-`61-JPmvB5$S{}NL)8vfV^c9-yKA)y6S$y?Wy9B$y z13%H{n!oZ*PG2gJYthtW>9PCpa_?sD7vj2?ocwtY%}kPxRCsgEXMe$=%E;2B3VVf@ zI|K_QA2h`ly-*9k8U6ghXYtd;|N3+47;RTtwu&yvv$bf*l#UGe|LWI|4G*Sd9kQ%B zd+WjXwViyK9Qvt!I-XS`-onoWAN9&gDCjBZHQo7>^?$zRqX(IPYInF@aZTOXZ|~f6 ziSw~_clW8lm8%yoUbuMS&IiBE7Eex@GDY};LqFr++6Nmd7w(*x;`}0={cn6d--`r? z+Xtp^`#oKp@jLrFtB0?C-o2wLA^FeFur~MetW}5Het#?7ReRCl;QjZu?AvW^?)=`F zn!fwL6?^M6u`Tm{ed6Qz_pbZ+szvvI+_!AuTTu4?@-v^mPK<2-B+5dUmFYbyyfpc8 zuH(I$r?Gc_f8Bp;lA88rlfRc5b{sf+5ey-|PE- z(tk&_BnH;VT{&OZG98 zN5nsD;?_QW`25~?Pqp6{6boNCwfpwe=H(CDn$z7o7CSaNGBMX3XIFZ{yymdGqPzqv zJ9ou)u|u0VtGMs2{`_@*aY3o{WRB=JUn~AGe`RuDYP{9b)zZafmhd(2ExSp;fm7oA zdastVpWrh#;_k1I;rRJh{($|QmI7m8@v3(z;+w9$U$?gQ>*Fu1*QbSD4RY+6!}ial zJIz-ttjd-{!S5AMpWpV^uZxZywfk|#_d>LVpBqneTU%Qb2S+VCFR$#6=scE(zh0S{ zZJGV7wg30!?C09nnWs7Wo8~`${qdjiVuh{E%;ox5$~;>XKA*p@aNtAdU#~ljj-Pm+ z$-lFIBEM&9?3Jf0Sj+#esR@jgyT86w>P)T^?~~uDHVk|IUrhNwmpzv+H~RJQ=)~P` zbD#cl@;712V{CbHUHCz^wljm``(}5x-i8O;za74Qo&Cx20O4R}#$N@ljr^@tKJCLVbPpepU}EL^{(SPQ zU$sjmPVoP@bep9=!;xe0^w-SH%py0K7oB%@Xtd&JY57nuT=iRUX~mBImX@wP?f+is z$}DF7c!QaViG}wVck`FW;t!lt&bvA^WI5#A@@`)TUgce49_l~(lk{y+NT`$99X z_umV7PhGvs`|99aTTRyWOOE%}u1lC49mksR`ug)5rkzu^d#>NBbe5N)VeXFUeKY)9 zzeY^ZSh7W_rJzNWQ%h#?!4oI{6;3!5>oqez_4?Pkyo9Vf*=e5~jyMUOz5b?ZPmAcS zNx%FTS2VDNWgk|TzBo%{XT^6B!v{V*$v!hCGg{uaZJ4k=_3iz(OZum8N)QKl@kz$*XQWeEp(fx617F^Bbn{-ic@Jdun()`PN6KS=Y6s z;zVPXWSAA)DevF^%6e_`ks>LgDI! z3s%dd8lol~6JYXWxF6=j{iHE@hJ>jMPy3UVnLX1d`M-8Q^QrEEfl!U$@+&hc3?ozX}<_A0}&dY{Vps6f+eAqDb%Q#oes zUikIE-=x`6cO|y)Hs^9Ooof4$9we}7p3#llN89rq6&M>B;%?mHEz8*X?^nS&BMpI}(V*!KOpQK4nAh?c^qDyEs`Y?|NhxE3%n-reNORbsZ{XO&k&b%%oV zAN!U)!BY$zChOeTmJ2(%;OAB_^X~8awKwO^+*=<1IpmsmTFP9jiGpWuG3)&430s!+ zFwAyY!&~p_Exrv6b;dU5qt_~>vVT2LVjQm|I>G#!K%cNy&4nWs7gdV`gJjOA&%5*J zmh^1T$4okcS*JQaaJS~~x9c>lWAU_4?cFHX?VD?7clP}F)`z=gOcKZZv z=bzjHdm46>l*sK?J^5l>9+ryO5uD3cKya7EB{@>|a8MF%Vo{0o?}!HnB_)+6Po?-D{E zH(YI7e|p`f-X=ZSSYl^&aKo zzA)iV#&?!|9KL?R^LPB%EW9OQ=Fg4Xohw8B+z;ISxpVd2LI#ycPD>}-v$G7>Oj!H$ zv$X2u>xIWuSkEkE5D02}!u+vPBXIk<2@mUXSXz{drhb3>RM6$zUe1&(p9$wD&tz{4 zQVDtJtUt-$zo-5JcS7n{j}>zchtzI%(2-*|-_ystsP2Nn&Q#6LCG&4Go&VnX(#7qF zQE1P1PSs;>6aLT8{=xR_Zt?lOcD_nmml|GqSzar#k8_jC{Z@zlQ>zwm6l5JS-w?JW z<#Az+UqSIfk7=4q942MGjp`G4cR2nS%raX z!>legec$Hv_vt3TI&?C0*8=&h=ZmM-UB7={(RXu>fNA@&4R-^SgD$+bdQp(JezvR_ z|E_y`pXFWSnAE{;XwFk9(wu31%VdfDBCZQdHLis2XL($7MRs|e^7Hv2H5==;3R~N~ zS;X?9@vPa8RkdAF7wm2v*y%cLo?`u;;|@tto0+e42JYausPgN6{AELx%thmGJuTig zbI$)b`MaIpTV~?%od+B$wpBbd+^}iOh5J%1sowp1RqB7IDutYF5snF7(^IFv^}0;K zi-?bNH@v8mh<;+#Upp&%i^h>DzJ^L`QZxPY^}<%|y}SQ`(ed9Y+qWj|&WTqI)8$CI zoy_?1{L-ltdpd)<7cZas@IUWOnLl;>v(|^4pK>Tz>2Oa_?1d-y1Kv1I{=RWrz^l&Z zi@uoa=HIIN{V2*O=FR%kTV=nN{=Dz}xZZw08@JV~O*i*co%1}lDNucT3ZIHM>uru7 zzqIx2e!B`pmH#>=_#wGY>hR1XKiz*kWLTs7&ALS=)o<@}xz;In7O(~SoYp=wOXb7) zNgFqr2r27TGS(%m_!{>@j5}w0InRcfOtz68Y=O78&F=rjwKJ;Lv5x~r?UA^S=lKVMajlHDOcT7^d&N!8r0Hft31=ijz5GI^Eh{pgI_f7!sTXGAUvt!-Vs zy^wk5?QpKLzrPM%`tmV;&eCFQ7K3-;2LislJbh2;>vHKA>Fl{@Co9`*zQ2pJYSSEV zKi2Ez*%l|3p8n^0>d?>1a~+qH*IVWnSqO_JF*JBZyo#GISk#~FXd+a2^^#obE6V9pL8J_VZ=$?W0K#*2T?keDYx9tLl>ceeLfoG=w)Kzhn*J zFG^wdGgcM)clp;F>xE~ST9y`V4KwU}^SWKI`slGeZ*>h%TWyX_`;y(?9(CkLdv^T0 z$)`S)E>iq*)FtTs_m$eA0&`b}*qIeRo^GhMA)Q^Qq^?Es=BCUg9J98B-REa{Bx;%F zyHDf!l9bZpzjNlbSd}V;sFb{mJeA$j+N;ab_U@R)!8KQX3!}GZ9+Ik!Ol!aPMJhdR zLqflZU_Z|9N zr*ZPnok{gOF9u!FnfP+ujP?kN#_E0^=F7gy_O@waT1myvX0Li*dZzfJlcMj*>&Xw^ zPmhe`6F>9(^|XkSS*m{)I`6M9TqZly;CaH+qUSZ@vn3|PmPjAw+_CJuuHg22UpmAm zpZ}LvD$QRf8{gn|Zw|-I{V#7OE#_}g4tuRQcfGKIh0ghT(NmA@4F8m2^8BG@*vp8^ z9v8%1`>!RqKk2%%?$f^9y9yj7wr5tqs+v2i|AE@QCYJSG#~$4Xn7^#gc3tqKU+1f0 z%g*lKkYvy7+jv`ZVM3<1^gfLrTbI{8Tu^)`x#jXnR~{xom#B5^yXH+dvd=VIHErRM zG;=f2-wu;j_1`vLwn}f?C-!-T;SD0kL#BlWpICPO)Uz6GE7Msj%P)$taBDZNU&D4y zNItLPOkq&2-W~f-O?S?iz4C5)-WuBE@%5_Ux*DamVhsP{=0;yie=up5?s-l>hvOb= z*ct!3w^_11o|&~kU^<6jM$g7QU#|C@E8RKCAa(j}1s6r5nb9R2pZca9{jWb&BdRP{ zL7h{(U&uW=`?Zw)Q#E$~$(>mq9oy@-?G})_!hbZu`RqT@JcTc+US+9nM;EYL`-u7R zS?rphqh%l6-yy5=Yftw{lXZOS-dFru_F&;PyL&TqE)|^o=6i4r|Eh^@JAJPWS@pYH`+C*nG>j5G zE4+NawtexEKXM;8wW{ns&uqCPkuma{x$2Qdr*>~pS4q)kM&~jQER_|nQv>` z{{=5CbT)So&*oBV-Jfu&SJltp?Y4&z(yAh!7kJNo=kj@`eD&DHy_(AJF6Fyquj*{j zs&&0SWBHvcR*P7JpB84c*Tq=)_QuZsc+Z~KaufTa?@wN&onKxkeE8et3K75ho{aUY z`AXMZeZuf6Etf$())0?*)swGl6_iQJ6u3EFWR4!oMA{F0rNqW|zA!{~CKQa30$ilez zjLgL=vo1AEj=DH0s^Sx2Nwm#*1=WFpIk=%9#i zJC605`A%Tv%G+|P<#yN8lhRTvO?-FW6lIxV)N!ZZLMLW+Zn7Z%+XtHLEG3`MiC5TN z3+lRDRIuiGuS?BkUBiD_T6PU@nC&hpd^?=6FjS&n_>ugLuwv(r-9od#B;T?1E(eoXGbMAUM zznXCIN!l!iA9>8Xs*)TZW!$*+>%s9=YgBu~by+j}KVHvynYQN0(~qVL-yJ&I6}#uN z*_qeNXRJDAJhz^YCH#ciU8#BhmrHoBUmc`Twrtsl<^H}+Uw&2dNv0Ovidg)yOvBW_ z=&5VV&RqX4&d_?PlUDr&oZo#PO;=tfb?nUH1li+HN-IU)EL&>OvoDlA`-Ia!+4G$& zSJ(8)x+Kc)JMf#G@o2WwhTRtbm_J9RzRN#$qc{0-z{5Ldex5q1D`msa|@74@;{K2sff%Pn)KQ0@MNdiIo@`mOVipJH5kM}KGa zg4W{?m)LVJRav#TukJXT4?DY4=FhL0>~HnlF0(BV+_mSs>NAm=tHu{DAL?Fywbor|LeMcrfHwYOZT(iY#0EdawRQhJdM6X^m?>r3(w*O8lsnc4W4zU;T53hM6B+ ztzYe)8#ST9e#b7YbwS#Edl%T-E_=Z5y`^raR@#wcQ#}k;rHf5*oFMzyLi6R_m*>k* zx4gRSI_=T^dIr^{c0Y=G`+b{jHj5rom~d;4#Vp6it1o^MfA#Zaide_iwqPUHyKDO2 zRC@;JR1+g2!mFag5Nzbt)nU*sd^$T>Fr>TXxpd7bkVn=O4H% zo$_=~^UQ?{yJR)O4P3M=Hn?&ptu)=huhp#5F6y?g&dZb8-$G9zz^QWW*=O>r)f(Ma zPuQ(&uzIr^g(^FqJg>nhc3EaSaK&kE6 zBHld3^+K;dbCxtK?G}hly4exjo+r3FL>3o9WA&T7G=jB`uPN;hmQ6gkw zY_C<*7F=+#y_{*S)Anww!UNU2e*Ih}W$Jsw)_!lKDVx={r@1?}KRfZY?)ce%a?hU_ ziB5a_Ro6P`;5W-N6>I)ltuDIFHi0L&<(P6SM98WHS4vyA*B9S9rO@;ymXZBi+Zn1#34;uX^s`KX?9~kE=_#E>>D! zU%&Eu%;`6I{7ekBY#$V*^0w~1d-r-y=x_c#6D{kBHSV*AsH{~45de%0Ub{I{L^3$Lus zSKl|ib6@Jj{EIJ}q`vCbr)Zx4r#l#r3!gmZbdI6BF7C{UX;XO|w^&*(Tysd%bH+xO zbE~wse7a=+f7uzc(>$8CdUK~w$$!)HJ9k}c!4ey`BA3Vw$t4Va4*S>aht$T zw9I)}WUT13`hMAm$_<}0|6Ddd>cIFY#^_9`KvMLk-E5WbUUs;0-dEpl!qrl$|Jmn3 zR^z=BGLvN%{$P9;=D~K=r%kimsDx+Nw4+P&1lKt-_1)^w|BXQSghd5)jNvcS) zrK`b|LjhT;lh$rIF~O7L+`2J4##-{jAwoci`B=**;Izow77-WaR=3her^tNee<+`4~Tg5$fL zF34ZKdv6iDz`iu8%$v8fC)Llrxay3st+y)!7?nt7{a!dcPv~Kd*Rn zbiOx#wch2W@-y~qmbZT>mGkIK58L5{S&S<_UHJQ;aBomqv+oX7FU~m@tlm|^8u_Nz za-t;;RHpG>fBlVNE34kpkN0|h%snuZUA9wp_4moL{i_|tgqGd-ca2Z%z6Td)`_$X- z?XsWl;qr2s&EPMonYruyayFyQzLPY%k8N6O%u)Alru@Rfm(Qec89PmjdizOs>+*m% z=WKX`75vrb-C7cv>AH4_*qaI|PuAb1yMv}!F)O?~x$~1k&-G0^Kek(4e8%wp_x zCvj`IhVrWL<4=;*tZX_frSCpFH*1cN#)h2f!oO^V_N-O0PGigFXgJ+zyE4k%$Fw?FtRYL_`$E|@$dYM zANyUk|FiNh^V;++n#aI4{T0Vty#v$V*StViwQn{3Jd(t3aiLU)>8D)8wL?E6xeVljd2?ziUo-v9blO&zYcs{-=XV`-BA#hg^fA(M=EQ=D(&Nv{ahQ z_AdCbsOOLJ>qeQ&+Bu(7-)!~ieJrw+`H{i*BkA!Io)qzL^_KWA`)NBRx+25(xBHcK ztOjR3O*<9)Ps}#Jj!i)I^YV{T6^jk0TxP6Uv8V9Z@(Io=nhGXKa&~Y3HQYQWz9jQw z`SmSZQt$7K(24!B?LfM|9iK>nOeKGWUG3Vdx8t&eIo-`B8(T|@9?aVvwxW#VZ-R#C z#^p0g4*q`K7HO<~;N^3z8&6{+@bIY4gATqwIX7u6AqWFf{ym@_+xQ z$XhdKRQR{{tpED;HEbp|G3A88pD(h^I7K( z*1uf8KG}+4&z$tQm)E}E~VA8F-> z-gO`QuUoNU`-z0tbIuBh@L7nie0%Ors`0I<@6#mur8uuSPuh6=4BJgR=jqZ{bX@~D zVm}xi{ive$e$CPe|84(Y`aS!T)x%D~$LFlM_{|ot!be9T?z^2}gsgk)&Q>zWM-Dn2K6yFcna^sV)? zYrevUi-NO{YCPO&Q<-vx~*zu zUmgV?&wAb&@lj9k)dtfTZZ`?jBP(AuW^Vax8L``GZgB54u^+~clG}QgDR0sJ)bAD_OK`YI`HG!}xapM}yYQy$|1tuG#ow zO85U=e#(o)w#OXKOX}ZUZN$kUY`%~0+=HceET(Q%IuOAxc=p&wg>{QJK0VXj@qXTh zhBt}2=NEO0aJFA+9aY-r}uYfotRZ z=I*cUVJ#=#-&YdMn#H5Oc%g-g`nIg<35r@v=r%k^6m)tH`(@IXA3H(%?!eL?D@~N=G?SnOGS0RPCu`twrrazzkS-2*2VVAm%QH0&6R&TVzaSTm0r>domZE$ z#PZi0=~wd?xqOmP$;ht0^eyxDmW7Lp&P?l+<=)Gzr(1pevEM$v`q0{&jcndo;{O&- zmbJ`_S-NhD?-K4frwh{YKY9WyZQJCno|k;+vzgs4yLx_myY!S^yGv)o7N@;uzaG3` zS>}vWtd}qCt^K{_-7U2Prj~p@uJT?ohZdPGm1y}^7?cx|eOX#(#+NUt0zO*z1e7#% zL}%U3e0ZQf>Sm%y+|;*oT7x8O{?4+LdOs!ZC+nBY$QLVTTuHel*MEG^(nLM`))?pF z0`-435=+>(1(h6*d*VB}FI6~Zv+|C+KME%Q*?QsjueCE{|K3Wu>vi+y56|g0CrBVShee`C)UpZs|(Dki&J;8|+X%B8I~Z|h2=l=PE+Ge^vd zv`%pCHoKLba`n#~gJTRIxA^T<@Y{Bm`O1bLdTO&4v^orKr+i#^q)uZ1t$f&1Lcaw@n-jyc?6b5 zJ_!Amc+&dFHJgZ6FQVjsznI@*+Vb(W^V)wL3r~x`zH~)suXe_q_1mPE$he#;`xl;Q z)ZHI{&%(7^KXW!$d0{pKO(Pi*1L$4WR=06D&vBW_2e_jFdT|aM-ja&nKu! zWXjgni6T+0t+UZFdVj9S^Sy*4z~isrR!Z8ZzFeZ5p7r zP0hZ1jrIEKH|8rh-uv?Ol+^0^^HnRv*#Er=4)ENXZV@lb_DNBv~}S?{b!)tOvt zzB!98qTubLZ1Wjcoz-(S%$(L7UQ;qDYyH28XRhw|TqgBj?(e4Qzqj@G)|Gwxw0`dO zjnVtJGsWj;En^Bc(x35bkCes#H#65b?AvLplXq&xoF*&Q*V%%WDu&8CKkiP|6OUcK zT)_X}-ap^wc+6ir^~cSx>pyRh@7KAsD?HUhXj{+stpXoSJu|NV-fEDw*?0avg-KGS zJWb*(7UHgbx2pbau*ujX(0g!S&(*2=cb`=KtGoR`$UR_kk=vx^zt6r}Wxrm$YK7~| zjyLiSnr-eX?5h2Lop&2sNZD!mb;`H>OS^TWt?}$u(*;*kojm=lH@SQ}T558Re_BY< zjhH29g^^~kv37l8_cWrN{jmh(4cdqvAd~q-B zZ?bGc_x+ab`<^>yE!K;cJr{i;`rNvn?8RB$PZ#MP(23q0+9L9~aJv4#^&N@ooI9Sx z&(=6tzxRc~>&%zu4}D?NH76!C7pW?cu zxBNMu+IzWph1erTv!Gapi$^Ccf7$t)+wXnBw#yRMI;V_SBAg9_yZ3)SGRZ;a!lpxY z8?3%h`?x?dG58+4!dW-l-qd3O@0T9`#HVoaR`xa{hw>}N3%g?6h0mW(+V*^jQBu4o zXVa+^CgsG!^qF<{)@Ov(@z&^UJSbDerr=bacw+TChvnze9j+h#d~xREva;Sy&%Mt* zm>*U7<}b5I?8BxV>COChmlSwYru#fos6RYMY2JMAnl`~3D!p?}_YK5Z8jPRv^QT;^5a|2;f$zZ7|2D8+3K>p3AF#S+`N zVBO5(w<+)T-P!t4sAjEr$A?`?7X+qXo9&Xa*wI^YN#$=JwMS1ce4Z)!^x|$WtF)>K zT@sf?LqGkK@(*=xX7;@Ob=}$Y6SDO`YgV2LUQWjqnKjlXEs62<3<0vR2?`bT|3vS+>cGA|@; zVWz>!mV^~~i`H*z+PFT#?+)wS%4il7YuVgvtEj`L_G#4rZS~8yUT>xclitValV` z6Jk3a*j_x54|c-(=smGv3#!C;m_Gz@hx%LwC#r;zSGP&wOF|A zwu`Nm=^KXy%NWxptI61If4p0=eA5#pbLoouJ&H}jmao)xJ}x&iQ)6or2u^Vf_OKG` ziq!FnteLpu=IWC-=H{N@i>{x3%Cp@+bKN=jj*7akf&ba^V<2?!ad?qjmM5k z#N{n$<+&59kRSQE{&nEtA9tEJg&hf#-I=kv)2+8^Uh5 z?cdAT`83odGmNgq-U&YWB;aJ&zaIbjD)EmQ*2jD~V|Mn>v-y+m^&e5(8WM6gU+GxS zYu7Tzv)KwW{-2${vdN;w$Y=VNnaVe1BHJ&_mbf6rw7R185HI&+`C016=HE1L4vn&K zoI2GnOChf{K5t6k=DhzmKJ4+}?YnKqUGmlJfBFK;A0LeVTsk3Yn7|TZ#5wh@Fjtbk zr?TlwL2GP+#k$Sg6}k4MHQMAjCM$>q zZl5*Bj-7VYbU$E~hyGlRnr?Vz9MMS@Hv;9#nD)6O- zU3l%3UHc>APW-hf?%k1s^ke!`E|&e?ObEcx7o3*U60muazUeSZZwgc*)`7(jC_q7_wNOx_b5T zZ=KCrH}=1oM zS!wa1>zg{Kt!d2`YhHCCA>MYHJIhAX@7w2{dCv27i}PRC=X>K48rUX+j@5{;63?m^1p&6mQ`}4)!!#L{R#5F=e+Gj zOMmj?^D{p#cr^b;p4Kmex2xxclom+;Gc0WDSX|%pqx$!A>xlHJ)4%cDOK1yi-ppnl zvMFimpEi-F75pEW=LsBOei*~)saDpjTDRr?hP>j`X|vVzzn^3JvdnVl^#gCSn&bYm z&D5D~=NA-Uu;Eh8LY+CgpB!M4RApD!Tw!@i!|aM+-olNglMO<&U3`4?WS>@P9$xMG ziNDH|A$ay`mXes&M;KHd+1&Dt;EjDL_{KHDH~O=N`Gk$-Mz>54CEcEV)M3G*1AAou zy>$wRK3T^i{hUp5#ZHL@ZwC7DZS#Us`27W6 z%<$M$$Nj{0?(C-wau2s={rGY56!W%8%>rNan5N%znseQIPILL*6zhD(>9=b|nr1f# zFvR&?(ur?m{k~~__iUF>IVhjyZ&`gWNWl8klzp6y zzuzg%^j3C1X?8E^=sst=rrGT$Y~|5>fX5yNOiFd?KoMpb-5WL-@b2iWS z>(vJqw10WwTUU1S2+N|SlLIO}jW%C6%2IYQ)bf;N>bku(vr~1}UD%bnt+{;pmW78_ z*SJn!Jl*34>(W>6CmDro+)`>j^Yf?HZ_fe;*8ixPmn-yq`dR6{lNi2V|H`YD+SpJx`=8B(FoVmtpO<#u`{N8nwa zs?Cm8QimSASDSe(U-0vueHN!I7q6Xs*2vMLU$^Ms-Tk-rX}7Gqe_PTqeEqI(pZnJ; z=FW_mzRB4A`j)0;29T^yJ7>TVS8!M}FO zO&{F74GgDlYDjw*xb$1^qDOuU_pI*us5ryg{OIZ#Dqj84{^wjd7yOjvn!~7QeeJTm zqQ)tc{1>^X5kI7Wo`|pY7-*vhtaRYth`k^-K#pE$5qYxvH7qKzlLTj7;#uZU zC@?Eu&BQ1^qcdmM<8?gcTY@;2KYpJ@bw3zEF9i>JzgL7fy3Kd2h-VLwh%^=)kbK>~}db zExM2B{|m_dQMmY|lJ52}Pfd@Bm5W`KL$5`?7o8gExZ!~NQybA~u7|yK+}^R2wO4#d z)K}lSz*1>LgY(IER*QR2s^TZ{seuQ@%u>adt;{eqWAHdI-O&A8~+ zvQMG?_5bpZkPyU5zkdvlTFgBx?_c4e=sVLiNbTB`(~eV52IDK(G9%5INb45qAjU1%}k z=PH*P_olGfo=z+GwYFK-eT%H!D*o9pX|CFtoGB5qJBrulc}$Fs@m;IZ`R4W}^<9UR ze_xaGWO?O~BRI#W{PMh`R=ZL!J}~F-Jnr0IpU+D3Yi@WayT5J2qi%^Aa;KIr59;Db;W==v za8K^)%uUPVCoW29(UsSmK26Rb{@72W-Y>PalgzcZKk+=^vbSZga?aTuK0Z5D42zU| zUUr?8nVp=qPknxkgwV$`SwZi5kF4K)O=;i$4_fcvY?!0RzRGWb@m+1*CYhw|5*GJP zP0@ODq^RWZ#EVbFKVE)ZvxHTTRry9FXWg?^o1U?MKfg4+YNN>O^=n>i*b-9sTJPyT zuCkns!Uxyy@QGTmhE;8r>DS<3lL*5)){|1R&qx0(@N_hY@m>Dn$~*Ck8PjAYU&?-M zx%T3&WQk7Wn`_FZE=g(*c{ZnbN=>He1##QP%a)3#KD>Of<<}a8Q_%sL*T1aV)n(w& zcv;^3yO;G+-JiRk)^=A^iF^!L753x0rq~y8r59&?zDT&*@1Ak(htL)~H9osp%}39K zYb@OS%GW(N>Z$3Dz%$~WT5)G4RK3r>y>-7=^-Pzy3Prmd!`#aE$8aqWTEF{)=Bj1q zKFlTZf6`pZH^k?qbBg;}HE-9JiaBRIZdG%h^@M3SiTJNqB&mLdz-NJTT zbk|p3h_>73yS4r0ocg1h*Zu!*DdQ_#+mqVD(VlZ;TB^i}M#=dp*=4si_WCG4o^avo zd|}>gxlFxTOP5=&+^PRoF+Ny7ze}p_p2qK3hTIQ59_vL?jvHrveV*&4b~#6N>$TrH z-!E`H+bd_Qo5*d|duo1@bGmz!lc|v2k?zZSxiue7^UVLo8h5@drRDp%Cn39yQ=36iFL6F=ME=jE6PkMty-yE zGcn-jk}NhJp?jraC&d|;Nlopw`*7&`<;goPufJR@5b;BX-*b}jhp5S`e>1F$PI3xh z-1@mz-2Lb3rBA0%sH@o*8CaY0ev`oCUj2l(Jf9gJz0cfe)3#AuqH@LhLys3oa~mGD zu@an_Y4=NY_rgqO{_x^|w%c-|I$loJ-O^E7z|emEEtAO>>0idOTx_oH+s@g}S-bB` zR=B5YisYgjX|l&=oWF2@{r@@VrnSqLTe=lB+`DU@UZfxvA^$7vvZA@G;;z>UDW7E8 zzwZ!mbiJ}C=+AWli)Quwxcg=K%yS#&*@!+dI^E~zb>8?M zU!K{%t?&HSHg+wkJ6Sp9Ue4TCw$l`I#XD?yI-{SMq*OM>?_0Ul*i&%glUer^Cznlr zdhe>NiD*k1Ye;0ov(D0*SJf*8)TZ6;&OW2;G+VYjq#$x<%%p(W;C*SU9$Yl*JGMao zWkPINpyy#LFO9G>T{=N;yjzRd^mi@d4Y*>B{ z+5KQwgVb=@^`+m<9=-TnlBMo1#W``35trg7FF)xCwcG#B{-yU>S_AuVhbOdD+dweA$)h5lV*_tu@LQejU~?zj9`ZZEVYmwCNg|@x zs{NeB{oK5OeLD&=ZI(|x_xMcZmVg?K&CBGNn#7f(u-}!U z7v$9|ZtEPjqjv6+X#pE28>TLe&fg#RspV^GWFgy@kiE06JpNs>NbZDbb+gP%-3zBu zW8SUYBBA6PEH+WX=BMeY2G48f&L0%{aN6Z5lZD~T^p#hCt&Un~Q9Sz=OWUFoM*m%> z@GN7mpQo@Zkdw2L;Eq1AVPx)T{tH$JaN0{YV))fbho*D|h<^Pp()`9KR%!$_+EJ~+DRCT_Q zx0vu}V{PK4ds*iTBRu2WR&JiqHT%c*`(Hns=DV(Nw_{Zmk#06lI z$*IlEtPf5&8W(CBT69EgR$`UeQC(zVYhkdXy_nrL!SeRU+(YZX9^TfW^C|e(#LD*% zQXi>!WnX;8zMa`bVEw**GtUIc+MDKOrkkrT3yIvU?fKVH@=97yPtxQl!;Mo{TScz? zR9XA|^C7Qx9H{;{)jRyhliEpA z-@~*NRj#Zq-S+m7boSE&D)v%sHi--}=fBp}ryT!RP^?+9wtt_+=|x-YJGI`PpEdo< z`wQxfJ3k9I-}|MuXv_Y@Age#F)4%*bW!L{p-iY57w%Rz3&8 z3&pH2y5n`9GW~h*v+H2)Kf%JNKZe?DbKZWCt}i$2Nxoz@DW!QocYebB%c`M|h2Q-D z8}syedaFsc)eB*V@3ST;->A6T_V5@BbE3@;ZNR z9!p(L&(FDczdfnjGx68-SHC_#RCB%PC+TNawT(mIWuO<#F2JW-fD z`PsD4-4|b}ergrI?_m3x?Qp|PErnUj7i`*R>?*d(`&<32M@8;Y8viCFY;5tmAnwC@ zqBbDx?a4|dGhQ_jN%?>$G5&vkDZb{NYMEhJA?UavuVB0ROMTP#3-SvdOm9>EQZuE~ z=DFGpE4FQY>@k5m`I*)#CJ6BCo^tc}!J9kMTKeOTn{rOoT_wluuDp76gRsT=!&cil z7btEL+rRf~u8{co9ryCyzns}@%oETmzWL*U@W5l2nFT`_C*EvzJ-pc1+sL-6@PWY2 z!`)%m7e7w@tUE>{%d zOl3Eos+^>L{K?`?f4)u&o2R@)(#WemXUqL#i$=Q7h!XF+m8VRXd&Taow9EseO(ksB?@mv%R(Q{kFG^x*Jx_a%@2MjXB=-6AlD`s7aY%B#|eLkgg@@#D1EAgvr(wy#VTfg#Tuyr1l z_L{a>{^PS>JJ;;Jm$g9l#1WO=6Z#ETpBo&{a&z#vIzJ7l zyH8Puul&umZIUvvcN^Dk*PO38Lvlgf%l`J@DGuvTuRB_EN4j+iZ^%jYwCoLA9`Y$4 zcUs=M(^%U4j+0jMfnTBzqBu^uKK{5Y%t2?#Uq$2GRXI&*B|r8te}AAcq3Dv?+oG*C z)wRlo%okn0?eaNM?l)T_-E|g!xkZ}Rq-_S@pS3$~Z8DiNeTV7QsI03$7nO#2vfD3t z*<1XjRIgcl&vQ=qzh#q)+E2|ra3YsTWWe3mY4a@T7G4bZ&Xc3qjt;! zqgxAiE54uU@aAM}-PZ5#mF=fi)=4DT@h%lNa5ER*V%;Bdt;p|GX_R{}r`o4g^|lOk zm1~;a?l?B=3eQ`$!fZwRv_(AA_rEi}6g1_(6X&B7wwr=m=7#ziMQbg*_1agqU5{an zuEo_$j*UO}=ty2ayMlFwRYLWPL)NLU{^XeMFb-K|BJ**6A)9yb{2lkc3-ms#vAMZv zN|sZemSBU?X~|7Ya&sS5%xe*S)6Q)sQm4`GS52OmC9UBUJCwN^TB;SvquNavXU zKbKG0ejsU<*A5fe*#c$88_WM35)O7i|l@O zNKCf;W3S#a4$cr=fi|mN%Rj6qxUIbAUXu5EHaA?AugmOlj^Y)|iN~(>>-A56F{SES zZ2ubRq-#2Xyc5IR7}A8-gua~ESeIMEUYKw#H}yj3>K$%19V;*L$E>zl{BPE!hKm{O zUFs{&7X2_h6tQ9F<>(2gUw!78n0j=l|J?J}H5|Nm@Sd|=ey=co;oo(fb%9PlSPEI| z*4m`)`LjOl!hDrA2|sV1zWZuwN&L0QdtK6}YP1)g#%TC&uv}arfnH61wfe_U`M;mwcb*{OV+} z-l?q+y|sNx_%l!E_jXECOZG&^I>s^ki?c3V9lP3$k>8iA>~u@Bk?aYD7X|!^9zUGE z*L>Soshno5o;Xcz(d!kLZI->Zp4oIOtaJ^7=bf|! z&Soht)qjh#SlN&4+3y+nJ>q~&(bO<&Czjh2osJ4Evyb0c9H3uxrfjZcTZ^!5p8g4w zz#G;4760$De|3A!;l=U(wOHvAtp0!55=ZdA->7<+$rJXj{e@K4%UfdYu&Myy;#fTixETTcs9SaCQ3~ULw2C=){_h3qRh=sf_9r4Gw&Kbdk;C(P%3B6 zp*G&DuUn_ycy#IpgY2wnW{QthH7wiLEc}{kkvEm~k<8p{-xqz{XV;P$KJ~24{3i#6 z|2;L-EfC%G;NfD{ysb^!ZW@<(HeQ+$k$9@W_koU=gxB4Q*%pRetIV2^ z)AuxDS34MINGS5n%Ii5}4dT#rM?8-H~OqMVI?4EdLrN*U@ zt=G4kI$JJh)4ARh_x+Jj@*$RwoX0*zopV}RKG$Z>OpU!x~$^nbv7vD zhn&++nZTo09c~oAy7yeaAm!$)uzicWm^vi6cz6!aTUagrlP(-o4{%d?2J^anPxtoYSv>w?+T=o8bGqPip^Ovufhjx|pkXFZ-XJweDrK z<%3(=cd7;F>vr*Syk6VCQMNozX6J`@6ZhwxHdy<7#@dc)2@hVLR-19X{A6?y>%%!a zr`UR(zIXhVapB8XZ}x^H9l!ig%f>T8T-{!|a@zJYR<5}rJ9Jl`J+N;@?r!e9ZM&zx za=pTTp}UFqy`Y1BkNPRM3EPd%um0ParrsgFZpyLS59aFIRDJDY`}#P}(a>H_(v8VO zT~9Ab@<8=wW)}vQ9f5z=`ULN=l3SM|dQ3+x^wYO<*_A(RYPjED6@LCm)##L?8oSiZ z_uNyu*N7QuxYwS&vMkGg(Y`F+ja3KltZ`J4xnDBH@#bTvmjR)*r(d|7otF1k@7&*` zJg=VEZk_G$eZm0_?w!0dF0VM!zv+@LZ&J$*r>JeWn5S=S^tj(-&D64FhMO|yZ{GJ& z_f?jOXt*&muAd||aT%9EMQgLPWq4WLw7WaZ&s_2?u?}`#!Kqf~nBP}$^K4`I+?d$F zLowec9XWRWN7!q-^Qm=dyQWV&a7^leMIrmDHxZo9bygeaP8N3J@_hJ0V~<#2e5OqK zXR}#btB#kQUGs+j%B-7RT}u}_MT#ywRiQcm!j*H+PUa>&6e=`K5{_b$2s1OyNqv3e zP`YLEIYyz#GVfiSN4Gb8E(+Gx3|_eH!tw75q7HAd@-cp@`2OAdbjJ-BCGI{oo1|vJ zAe0`Uyd|*b3}YnF-$!2Mb`Q14T}@%26yr$3;W%6%oo`CtRS_@gGs1WLce#( z{PgEr%da#?Prdp1ns|54C!ec*zjpl6y0qJe{ppljQ)WlHf8@1yPmwQ{WcxnD?B$$R z@hiS@tNE2!Has&qwA-XqKz5rz&#jkh{nmXc-{lz^@}|iqYOU11^qcRU7Iswk+PUU@ z+vTM3Yt@&RyLT@yeU_*$}<*2OEA5{u2J zMg}jqpf!m}*Q+K(F!XD%5Xa) z(+(kl=9=`goQwc;~vTEYcsZc z-rBH_>wSRh%U$xm#%7$Ou>wD^rmviSmx*~UMEq|8F zsg=zZiOeP?x_PrLvO@A3D^(^Nd1XrZ zP3d}{l2!bCd&LFJJGcB^-Cfagcdd%f;Y%|&Z$BNjbnBlN;T)sn(t4cBrytmAI z?T^*o=ek$Bz0r7m&!o!;9z8JO6TBPfL%#wJO0>d=8&i(afITb+wm_ zIW3k}u3x#`n zR>ZEjv2KOtf%9&;fj%eHG`F)lq^MV=J%6pzx*$d?{Nvvl=WA|9pZ-&`YGU=pci%K- zzj)b}C-nICtt#(VSzi}@U3hhm*WH|Xs~rUV*xnV|R~PxS-%|Xz=HqXJH%c?No=Se4 z7iUp!yVCY!#D|d0S7)X^4?4gma?bCf0{7exOS_|Q$h5dl5^KHqJF)GK*VCJuMT%Uf zujX_67_zGM^76~v%Zn~l>10{XlB&}yR}791CZFBaQ=VpjW^HR&wyn;d zW4V0k7Yu@mtvq8nWN(Z5?mfA=B-XllO(?UU5t@Ok0I=a_!v z9WL8yQXRZ0WSRJs0C9G1cEd-@f8_6(;=Fb3nV&D3=WlKPbxLU$>-m;_Kb~u6H_v(b zeanhTy!}_Qlw>R8?51C>+ST;A^+f9$W6f616~_+DOShPD>dK6W<60YJF1b1#d2=;H z{lTsFwX?q6n(A7+X3<62DPNt^Ob(x@kVsz_s%S7dVCBYu^|`X)yP2+W@6Oz$Wo75` zRB8Q|-RBherH;gGE6-hODzv6guWIL0kAT;wW!=OI>^5k#2-+Gr#SeSRh z>|xvHU2QL47$kB(E4`FvTR3T@&nq1tVdq88e?7FedKwtX*3D4r?D=Z@dt2Pgl*p@zS|| zN)ZnyF?>n+{3Ts!`jSP#{0UrY(HCDOF`M3b@MU|%gOXXLUR&m^EqYxMvt!r3SuIVM z7ab~D#`eT_KjWi{=kwX0=7eslO1(e8a4?!9^GKl`*X>$7)x0>*U_-`~3F%x-zPCEt=+FTp1#b?&b8xjUm|UFF&i6*8^-^GS{9sT>09!8k!bSGiyT#n5ea$6_fLRo2`WjL<&klqJSgDx7-XN9EN0l+)Qa zELJ(~>}u&;sp)d?&GWx3oJ~Cv-IE#%FPvO(WOr3qqp!yM%+0yyKRt-wewlBgcboRk zrBh`D8>goC9(@$@U9;n}byq>x6HTo*C2m}g7s>T;E&ceYQAuUrie;bAG~MS)Ig=aO zvtX`ES?`xga};MUIH;#!;jI~R-@eH)O;vC8pBqgUU)>HQ&F1%f$=tX6kWShr_9YD8 zygcuw9~0|(JYhqpK1)yMtra&*O#gZw;*5_xu0Lbd5s%X46<@b=u@o;4*Yjt~-pzW9 zrJ8kO^umaf-@YofD%z#r61nePRJ(Uo_r^K@r(AM*G-2{fwkB~)l^mHQv7IaYBn~(@ z$m|ygT=3+o>9h0Pij&e7ZGOs7(_y2MQpe2}=@Qv7y~F#t7ias@ryEb)({Eh2n|*hs zaQ6~5wsr2`P4u;-wKdOZedyF$J3-yQCMdki**|t=l$S$y)N_Zs#almLh)q8C?M_~Y zf?qvP@68*VdRXg4)E6tK2I%X)sX3-6;2FAcpPj%OsV^>*M3j<`QPWYamy5c zJve!JUzoNlo198zgoR1ZxqT{?ot?HbUgya!)A^`gzrscG^9;w(KX+bV(98^}(`w(o zdgsaIfj4_DN$Aaeo%TfhVZ{oIYtefX=EX<&r(HUEB`aczB?7sV<@7q3^ z+4k38JZuyWtUvhli`Q+jm^g6nb>+nI3 z#=1ow=aky6Ub&ooxclYG$Y~}sd@p|HeRS#g$Ax>Hx!)96E4Q(BUHZ-@E8KZ0cZ#p? zO!<>5-J5>wpYdM&pN-!#k*M7KS!(m@TRvGAH_228xNcTSfAr?kG_Mrc?SqH59x|%jfO$wEHD;53G zGAXk2bHW6BudCszOL-4%h>p>mwSAwUXft}msZ zYo6AeJZ#!#v3A$PX?AUfYIfUyFQ3o0^>nr6Vd>7(g63(bJ^NqU2p>~;zU1QVPp=IW zubeqsJpahSRk=QPtTSe_m(*HrNIJZ7rO65YMUoE1^Tk(jU0zzW`*DY|CfnSMl^J)m zk6*E16v;XxRM2y3aq^ALOQt3F>s5+oH`}vYotcG(E`D$;_4~U%8c99tqw_D zUtiW};&&-M%eHN|+J|Lkv76+U)$LgklFPs2*Pr&{PF|HgoR-g;{s`MP)M}pQZ@qit z=ilQJ;&FcGU&}kr58nT+X+qSoSiW_Cwn>TZZ~H%C-G^^yU##C~dOhRh_p+#CrVT!d zjZ-X=73}7;T3tQ);nxnc)n$`^{5f&ypk40w8~e9^p2`1o&g+8BpX|c!*!_vBI=$i5 zWkJ!e-u~y>H#r-4thuODndg$Muruzix#~Qlz|#?ySsR(T1#bGxcRb0RnkK&J$b}8v zWn9h4`>HP8nQ+$I;oZ5|OE)5x?pbU(KWWq6IQ!@=OzRBoR=34Y+xWp<_nmBzz1GhI z>o+EHUEMVOAk%awpFmh=x-9hZ_le1U9gSLd3!H@P7nRy}u${7+b^KgGjaaDgn=`_x?E!1w9FerHRsfijJyJ&R}bwEepdhT|ECPax65{w zZM7=iuGW6G(0>IaFcGVTM;au-Dk~_)#1BnNA|%#-e$6*C*$M$b$9t4QDZt{`L*3h%j5fp zt!X}b{vR$%&WvTdqMRdJm~`o-HJ7rl(vyt^zb2iZpYhD2V|x1%7xm?0WvL;rKD4al zeH6Pho1MK{UjO@!kLxGL{c2vMzho|tk@K^;-}#f{6XO26wdfP-C(O0vO>Gy|3)$Zqb;(txNm}aJRzsJVSu3*cY>=|}W33%?!eF0hw3lcX=gzD1 z#N#z*ikwo_ek+v5b9j4A-CX-!kCsl!)p-)ta8=Uh%W=;0vA3$ES($e(P%)2*-T9*} zbGdqn)wFGYs-}GAOkqqB{BS7i$BESFPxCd(oRlxVk}#0AEl~fUG$EnuA~|B&{g2y_1PCBTQ*DyetW`Im(@w^&x2=u zTbue5UP}~Q{@iM6FE1Y{FSbg(gY0gVeR-vhTR^;2vU2$QH zLf)niVXM}6hm?EeMf#a_r_0Lk(LJ+h+T6WiYq!mR{-UNPPjBA-qbVY1`P8mno1@fg zX!PsyiZX+WW#PG&)2vfJS7%1ejF5D_!)c*h;$^|1^J0Cn`h+cx{8M(-t4N9+dE|VZ zF;mftBh1e0BIkZ_J7LLH>;Ino?b%(w^5mS~la?M>dPbq9orf!P-hWyC+h!{qD#h1M zcHE=rth+4V{g|P3>8}H8?`Pg|JIFP0h3Tv-B5n;`-?=}|63zI2V4<{m+S;88XFo0t z%|E#C2+QG(yRRHReMcnkx+6pIp`({V<{#R!$Zo-oEyV@bmzJ1g{|kC?}ipn ztkYa#AJh@hr~ChV%)Yvr!HubAzb2or{FL(Xu0hxDFFHq@Qv9EAz52#{Wx zZ*D!`&fS+3@--Ll&*)5N%`e_`H0j{==z>GF4R08qmWKLucYm$?XSFq%g`wczBCY>Z zUw>X@w6!#*>*s~P@3Pcy1_)FLis~D0aV+b)z30l=D6v9obtYA<842Fc)DF%P+_m_A z+^%c4m=(n5uDD@oc~xVf_Vd0?-dUWdBzPuOe1AE4hDl|4nYvrqaf`>cTi0DZ_Gd9` zAAjP#A9djcvz{*aoHJ?f%fd|kOF=vDul3D3aI)XBX5!VdDK90a<<9hJ?B2`exhmk3 zhD`u}^-Xye=Y<|K{+Qbr(K0leNqN@cKKlUO^Mz8y zFFT&KoLa%1GVSy(h4)|DtyA=`Grj&U8hY&8HUD`I`WJ( zT-{(vLDoq(KB&6iIQClY+@1~fDpTE?`#&Eyf1vmN;G2*h664Ua{_gqKw?$#67c>P$$?J+&>Peo|ouD~?zg|$p5fimZEKO6~ zS38wYV}0eeO69(pRHl^K*8-NnJM5}!KO}E0ne}KZ<5Pvy53ynzX+=u+gCb_uCvFMO z-}CF7!qwt$#{KOKtS)!!k3JDRBo?^pR}tv~YyNYmMq$2;b#UbR)5p&TDlvePHI|(hc&i7jqU@79|7xL>+ zZiUS3WjsrL1b1v~dn0&^ zVspg*Jh{Y47O&G!Hd$KMZc{MRI9jmr{PxhiAEInOTUuDRrcKy8wbUf~s!y-u^u3BJ zH*0_Sy!)NKz4aqr-vQ>y@o!?>gC? z^v%8zHEVN#}F!i_igA6EJv_h6M-(DbZH+q^%Y5|Pz7xcaY^8&~(Pb`f=j%MnJ) zx;fbFHJ|IOjG*UzA=;8P$a#utWMkgpW~_i1hGq! z*P3P;Z+-uF8pGXr_s=$HepzxrobCT%t%X--oV0c;xfM8Zes>l7-MyQmtkV;pTMDk= z;GJf4=ft&#E-$hgu4r~2JHW76(|Px^wfWDdRa@F>bZqPB%swrnl$9+Py!ZRX2@^M; z&<>d>qmuUhW1zdjgDt-HKQ*3@) z@#gf&IqMrP7kkd*nl4@BRvMGHLjGv$k3-ti)b5n+Ti?o=9c}S-=g#D)ymoi%fSp^xwrUKi?GGdFrd@!p54L z;T^SY7Qy$~JpYKQ7IwAfm(NP@X!4G-OZcVA)qQThljy_M98>nK-dXnM$OXe>&Pzq5 zl3()gy~v-fQ@sB6H64fhPvcezXf~yuo;bIiCv5A}i<*v4y)|CsJ9ht@7*y-!niSbRr)ysOP3u#7&M{5w!eR$5!H2G9y_p6F z*IlWVZaa{w#nnF{X8z_9%jZ|pBkjG)8pB$0+dTRk7rp#ivZcBATKlbO3@UwJ-ug%9 z7HYVzY|cx3k+|9_uP#JPDfjT9yRLq0&7EggTW?t9_Hv$nfk(w6`RpUyQyS;;AJ#8A zV7Q_n=I^vMD?UAZ#LVUr@BYW^Or7tVgJ18wJklPklE|JFtI%v_Ht~c@x}Sw>tclvT zz3wczt!bu{BDhaYTJ8Qh%H~`K!_=P_yaZH?lTPskCY7$;{jSr^RLZziRmF)tu3zM{@7-Vg$5PQJlc)FF+s%?;m#S77xD{(h@Ukp! z{*o1zs;(#5WLvml{qwy0$>kAoHz%#FpHr4l{f1@#Qw8Pl$!c|CSmSg~?Bo_+a&S|hvxfPVDFthpZ=Kq7ZK83VkPy$- zs$B_(56zf!ZO!K63a7qCESxBGCT7KwIfqvAE@_{tKc{|5uF9&&;P?D*RJ%PlZ~wpI zWZPfey9sONi6s3<2{G$gA0@)f`lMizz|*Z07d;gFsP@m=*ze=-eK`+LzGAzYtfR{v z7jWj%%G2zT|I?2P*vrh^skKA!dzj|4St^2FHLriHp7_t@$$_$hS6k%MBAdEbu3fUh z{lnH=R;M<`6;4m(_XOLN*G>I#U3r6sqdRZ^4VlB2TFL_i=YL!Cl&_{T_8H%;_kL49 zWSu|sILNg{(bL4*EvkFl%DQ=%Y(%u;-mwY@2?~o_zTx5>(7n;y6BdA?a^i@$D0;a`;<^OfIi zpL6)4s%m`u=C}SO6=Icn4112we`KfUe#)|RGtaC=cG>fvoT+&I%>3ZWEBi&CsqES5 zSLppUMt-A9f1A0_!dF4D5t=7%2)lb%1aE2IteJDE|I@z!hxYw57HY=EFrJy~UJ{R_zq)`!Aj;lm+2-`@ zlTy#wg46Ppdo32+oMFzrcV5M-ef?95HLurh^h;6}`Ey$5m3isuvQx8LHf!~Ql{Z^u%SN3WA8oab=04Eh&=WEL&it+Wdjmgb-dw(AmHCCmjFO@0Nj=Xq zwI>B8o@{mCa8!|>`rYD~+4Xbb;mUc;edp3t&DP&(E0R&`K7PdiaeLo}o_h=5i+R}&Q0&gL)OV3T^K zELr|na{2rd{wb$D1KyquWWP4UznxF*+kxYEmbKrxGhZmeeR3)9t+FD1f$;0!7RcD< zJ8nr;*!QFERy>34@-sD$#it25+NJ8*IyQch-x2&WtfVgM%_r|&THoFWw(RBAy12G6 z|F5OhrH5%;JAb`&YoB&?+Lz^rjW1v9o+KmjWBvWUddb|*53l!qz0dyY|Gt06W8c)P zME~2{@X;%wTGt>lVrSGY$^Z9N7KKlG8}BX0ch2nDHa@=TYv_afPIphT#&2K9*dQvx!0=gg3g=(0 zAjU2)4ThO-eEv_9(!5r4{qJk8BkLa?u5;b0uy+v;&zd)DV|aP~eKtS$UP3hFT-Lkw zn1i-L5PJOmVwYoeujTHU|AJRf)3m$$ z?ypor_7eMN@AofEm=iu>?cUj&-`acL-ta5fX#Y8xdfS3{&!a5sRjie(wTq3#uh+j@ z_}5td@tgJ9$3N^Z{?fAEQky$nIb55)dU}19tX8|t`p4_*oD}}rX}5U974t1i+g9)V zcW1!Gc+ppiKebOC{Tja8=U={P|8Fj(n(3|(&vj6Udhj%bCTzJaH;PK!7 z`})H#SZa-fz5C-*ZQf<3qpV_5WAm*M$G&4PyLr-Q&^z?H}SJW9!rZ z$_4(}HG6-%$p4FHr`NOmSKIW%|MUCpd#>A8@buO{`M>Athw@+FbuQVz$UIxW=)cm; zFXg|!%TBDn@Ko==%m43g|M&I%Zr7R4_v3iz-TO=LU!V7H!nRHSbpGxWTB^d%`uDwM z3%^4@FoYU74YGdFxF zn)Tt2eSkMRN5VOMnUhNx7@n_XUK-9IBhGz<$dD8QHuZyiC*oV=vW zlH&NxlGNgo_>{zwME%eZUIzB{`HRwExU_J=B}WR|2FgYBE=wJ2>y{-U(0`HRx<*ar$axd;{s z3l;_jh6xM|nke?2k;dk=)QXb$yz=CNqWrSVl++>=Yhj@YqVZUp5%)9VwUNOyCME_3 zh5!aMZ&oYdvbLlkzZk_nQ3ZnD1jSQY9BK*?NAl)d$G1w@?90hdFNP!&0fj|rM+k@I zqwb#(-Py0YCn_;8+*D^|P(ZR|Nu!f8PD@f!lSy`I|Bf3|sy(Feswf=IDmgHb`29TL(&zzN&Z=CMX0ipye^-Jn+`BGZ~w8rFm7E z1-d1vxdo7L1f@q%ID%*#J_A{mjyJNNV700$vj7@d!h}-_=uGa 0 else 0, + "budgets": budgets + }) + assert resp.status_code == 201 + nwc = resp.json() + + async with httpx.AsyncClient() as client: + resp = await client.get(f'http://localhost:5002/nwcprovider/api/v1/pairing/{keypair["priv"]}') + assert resp.status_code == 200 + pairing = resp.json() + return { + "pubkey": keypair["pub"], + "privkey": keypair["priv"], + "pairing": pairing, + "nwc": nwc + } + + + +async def delete_nwc(w:str, pubkey:str): + + api_key = wallets[w]["admin_key"] + async with httpx.AsyncClient() as client: + resp = await client.delete(f'http://localhost:5002/nwcprovider/api/v1/nwc/{pubkey}?api-key={api_key}') + assert resp.status_code == 200 + return resp.json() + + +class NWCWallet : + def __init__(self, pairing_url): + # Extract from Pairing url nostr+walletconnect://provider_pub?relay=relay&secret=secret + self.pairing_url = pairing_url + self.provider_pub_hex = pairing_url.split("://")[1].split("?")[0] + self.relay = pairing_url.split("relay=")[1].split("&")[0] + self.secret = pairing_url.split("secret=")[1] + self.ws = None + self.connected = False + self.shutdown = False + self.event_queue = [] + self.subscriptions_count = 0 + self.sub_id="" + self.private_key = secp256k1.PrivateKey(bytes.fromhex(self.secret)) + self.private_key_hex = self.secret + self.public_key = self.private_key.pubkey + self.public_key_hex = self.public_key.serialize().hex()[2:] + + + async def close(self): + self.shutdown = True + await self.ws.close() + self.connected = False + + async def _wait_for_connection(self): + while not self.connected: + await asyncio.sleep(0.2) + + async def start(self): + asyncio.create_task(self._run()) + await self._wait_for_connection() + + + + def _is_shutting_down(self): + return self.shutdown + + def _get_new_subid(self) -> str: + subid = "lnbitsnwcstest"+str(self.subscriptions_count) + self.subscriptions_count += 1 + maxLength = 64 + chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + n = maxLength - len(subid) + if n > 0: + for i in range(n): + subid += chars[random.randint(0, len(chars) - 1)] + return subid + + async def _run(self): + while True: + try: + async with websockets.connect(self.relay) as ws: + self.ws = ws + self.connected = True + self.sub_id = self._get_new_subid() + res_filter = { + "kinds": [23195], + "authors": [self.provider_pub_hex], + "since": int(time.time()) + } + await self.ws.send(self._json_dumps(["REQ", self.sub_id, res_filter])) + while not self._is_shutting_down() and not ws.closed: + try: + reply = await ws.recv() + try: + await self._on_message(ws, reply) + except Exception as e: + pass + except Exception as e: + logger.debug("Error receiving message: " + str(e)) + break + except Exception as e: + logger.debug("Error connecting to relay: " + str(e)) + pass + self.connected = False + if not self._is_shutting_down(): + await asyncio.sleep(0.2) + else: + break + + def _encrypt_content(self, content: str, pubkey_hex: str, iv_seed: Optional[int] = None) -> str: + pubkey = secp256k1.PublicKey( + bytes.fromhex("02" + pubkey_hex), True) + shared = pubkey.tweak_mul(bytes.fromhex( + self.private_key_hex)).serialize()[1:] + if not iv_seed: + iv = Random.new().read(AES.block_size) + else: + iv = hashlib.sha256(iv_seed.to_bytes(32, byteorder='big')).digest() + iv = iv[:AES.block_size] + aes = AES.new(shared, AES.MODE_CBC, iv) + content_bytes = content.encode("utf-8") + content_bytes = pad(content_bytes, AES.block_size) + encrypted_b64 = base64.b64encode( + aes.encrypt(content_bytes)).decode("ascii") + ivB64 = base64.b64encode(iv).decode("ascii") + encrypted_content = encrypted_b64 + "?iv=" + ivB64 + return encrypted_content + + def _decrypt_content(self, content: str, pubkey_hex: str) -> str: + pubkey = secp256k1.PublicKey( + bytes.fromhex("02" + pubkey_hex), True) + shared = pubkey.tweak_mul(bytes.fromhex( + self.private_key_hex)).serialize()[1:] + (encrypted_content_b64, iv_b64) = content.split("?iv=") + encrypted_content = base64.b64decode( + encrypted_content_b64.encode("ascii")) + iv = base64.b64decode(iv_b64.encode("ascii")) + aes = AES.new(shared, AES.MODE_CBC, iv) + decrypted_bytes = aes.decrypt(encrypted_content) + decrypted_bytes = unpad(decrypted_bytes, AES.block_size) + decrypted = decrypted_bytes.decode("utf-8") + return decrypted + + async def _on_message(self, ws, message: str): + logger.debug("Received message: " + message) + msg = json.loads(message) + if msg[0] == "EVENT": # Event message + sub_id = msg[1] + event = msg[2] + nwc_pubkey = event["pubkey"] + content = self._decrypt_content(event["content"], nwc_pubkey) + content = json.loads(content) + self.event_queue.append({ + "created_at": event["created_at"], + "content": content, + "result": content["result"] if "result" in content else None, + "error": content["error"] if "error" in content else None, + "method": content["result_type"], + "tags": event["tags"] + }) + + def _json_dumps(self, data: Union[Dict, list]) -> str: + if isinstance(data, Dict): + data = {k: v for k, v in data.items() if v is not None} + return json.dumps(data, separators=(',', ':'), ensure_ascii=False) + + def _sign_event(self, event: Dict) -> Dict: + signature_data = self._json_dumps([ + 0, + self.public_key_hex, + event["created_at"], + event["kind"], + event["tags"], + event["content"] + ]) + + event_id = hashlib.sha256(signature_data.encode()).hexdigest() + event["id"] = event_id + event["pubkey"] = self.public_key_hex + signature = (self.private_key.schnorr_sign( + bytes.fromhex(event_id), None, raw=True)).hex() + event["sig"] = signature + return event + + + async def sendEvent(self, method,params): + await self._wait_for_connection() + event = { + "created_at": int(time.time()), + "kind": 23194, + "tags":[ + ["p", self.provider_pub_hex], + ], + "content": json.dumps({ + "method": method, + "params": params + + }) + } + logger.debug("Sending event: " + str(event)) + event["content"] = self._encrypt_content(event["content"], self.provider_pub_hex) + self._sign_event(event) + logger.debug("Sending event (encrypted): " + str(event)) + await self.ws.send(self._json_dumps(["EVENT", event])) + + async def waitFor(self, result_type, callback=None, on_error_callback=None, timeout=10): + now = time.time() + while True: + for i in range(len(self.event_queue)): + e = self.event_queue[i] + event_time = e["created_at"] + if e["method"] == result_type: + if event_time > now - timeout: + if not callback or callback(e["result"], e["tags"]): + self.event_queue.pop(i) + if e["error"]: + if on_error_callback: + on_error_callback(e["error"], e["tags"]) + + return e["result"], e["tags"], e["error"] + else: + return e["result"], e["tags"], None + await asyncio.sleep(1) + if timeout > 0 and time.time() > now + timeout: + raise Exception("Timeout") + + + +@pytest.mark.asyncio +async def test_create(): + await check_services() + nwc = await create_nwc("wallet1", "test_create", ["pay"], [], 0) + logger.info(nwc) + assert nwc["nwc"]["data"]["expires_at"] == 0 + assert nwc["nwc"]["data"]["permissions"] == "pay" + assert nwc["nwc"]["data"]["description"] == "test_create" + assert nwc["nwc"]["data"]["last_used"] > time.time() - 10 + assert nwc["nwc"]["data"]["last_used"] < time.time() + 10 + assert nwc["nwc"]["data"]["created_at"] > time.time() - 10 + assert nwc["nwc"]["data"]["created_at"] < time.time() + 10 + assert len(nwc["nwc"]["budgets"]) == 0 + + +@pytest.mark.asyncio +async def test_make_invoice(): + await check_services() + nwc = await create_nwc("wallet1", "test_make_invoice", ["invoice"], [], 0) + wallet1 = NWCWallet(nwc["pairing"]) + await wallet1.start() + await wallet1.sendEvent("make_invoice", {"amount": 1, "description": "test 123", "expiry": 1000}) + result, tags, error = await wallet1.waitFor("make_invoice") + logger.info(error) + assert error , "Expected internal error, because amount is too low" + + await wallet1.sendEvent("make_invoice", {"amount": 123000, "description":"test 123", "expiry": 1000}) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + assert result["type"] == "incoming" + assert result["description"] == "test 123" + assert result["amount"] == 123000 + assert result["preimage"] + assert result["created_at"] < time.time() + 10 + assert result["created_at"] > time.time() - 10 + assert result["expires_at"] < time.time() + 1000 + 10 + assert result["expires_at"] > time.time() + assert result["invoice"] + + invoice = result["invoice"] + decoded_invoice = bolt11.decode(invoice) + assert decoded_invoice.amount_msat == 123000 + + await wallet1.close() + + +@pytest.mark.asyncio +async def test_lookup_invoice(): + await check_services() + nwc = await create_nwc("wallet1", "test_lookup_invoice_make", ["invoice"], [], 0) + nwc2 = await create_nwc("wallet1", "test_lookup_invoice_lookup", ["lookup"], [], 0) + + wallet1 = NWCWallet(nwc["pairing"]) + await wallet1.start() + + + await wallet1.sendEvent("make_invoice", {"amount": 123000, "description": "test 123", "expiry": 1000}) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + assert result["type"] == "incoming" + assert result["description"] == "test 123" + assert result["amount"] == 123000 + assert result["preimage"] + assert result["created_at"] < time.time() + 10 + assert result["created_at"] > time.time() - 10 + assert result["expires_at"] < time.time() + 1000 + 10 + assert result["expires_at"] > time.time() + assert result["invoice"] + + wallet2 = NWCWallet(nwc2["pairing"]) + await wallet2.start() + + await wallet2.sendEvent("lookup_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet2.waitFor("lookup_invoice") + assert not error + assert result["type"] == "incoming" + assert result["description"] == "test 123" + assert result["amount"] == 123000 + assert result["preimage"] + assert result["created_at"] < time.time() + 10 + assert result["created_at"] > time.time() - 10 + assert result["expires_at"] < time.time() + 1000 + 10 + assert result["expires_at"] > time.time() + assert result["invoice"] + + await wallet1.close() + await wallet2.close() + + +@pytest.mark.asyncio +async def test_get_info(): + await check_services() + nwc = await create_nwc("wallet1", "test_get_info", ["info"], [], 0) + + + wallet1 = NWCWallet(nwc["pairing"]) + await wallet1.start() + + + await wallet1.sendEvent("get_info", {}) + result, tags, error = await wallet1.waitFor("get_info") + assert not error + assert result["alias"] == "LNBits_NWC_SP" + assert result["color"] == "" + assert result["network"] == "mainnet" + assert result["block_height"] == 0 + assert result["block_hash"] == "" + assert result["methods"] == ["get_info"] + + await wallet1.close() + + +@pytest.mark.asyncio +async def test_permisions(): + await check_services() + nwc = await create_nwc("wallet1", "test_permisions1", ["info"], [], 0) + nwc2 = await create_nwc("wallet1", "test_permisions2", [ "pay", "invoice"], [], 0) + nwc3 = await create_nwc("wallet1", "test_permisions3", ["info" , "pay", "invoice"], [], 0) + + + wallet1 = NWCWallet(nwc["pairing"]) + wallet2 = NWCWallet(nwc2["pairing"]) + wallet3 = NWCWallet(nwc3["pairing"]) + await wallet1.start() + + + await wallet1.sendEvent("get_info", {}) + result, tags, error = await wallet1.waitFor("get_info") + assert not error + + await wallet1.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123", + "expiry": 1000 + }) + result, tags, error = await wallet1.waitFor("make_invoice") + assert error + + await wallet1.close() + await wallet2.start() + + await wallet2.sendEvent("get_info", {}) + result, tags, error = await wallet2.waitFor("get_info") + assert error + + + await wallet2.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123", + "expiry": 1000 + }) + result, tags, error = await wallet2.waitFor("make_invoice") + assert not error + + await wallet2.close() + await wallet3.start() + + await wallet3.sendEvent("get_info", {}) + result, tags, error = await wallet3.waitFor("get_info") + assert not error + assert "make_invoice" in result["methods"] + assert "pay_invoice" in result["methods"] + assert "get_info" in result["methods"] + + await wallet3.close() + + +@pytest.mark.asyncio +async def test_pay_invoice_and_balance(): + await check_services() + nwc = await create_nwc("wallet1", "test_pay_invoice_and_balance", ["invoice", "balance"], [], 0) + nwc2 = await create_nwc("wallet2", "test_pay_invoice_and_balance", ["pay", "balance"], [], 0) + + + wallet1 = NWCWallet(nwc["pairing"]) + await wallet1.start() + + + await refresh_wallet_balances() + wallet1_balance = wallets["wallet1"]["balance_msats"] + wallet2_balance = wallets["wallet2"]["balance_msats"] + + + await wallet1.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123" + }) + + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + assert result["invoice"] + + invoice = result["invoice"] + wallet2 = NWCWallet(nwc2["pairing"]) + await wallet2.start() + + await wallet2.sendEvent("pay_invoice", { + "invoice": invoice + }) + result, tags, error = await wallet2.waitFor("pay_invoice") + assert not error + assert result["preimage"] + + await refresh_wallet_balances() + wallet1_balance_new = wallets["wallet1"]["balance_msats"] + wallet2_balance_new = wallets["wallet2"]["balance_msats"] + + assert wallet1_balance_new == wallet1_balance + 123000 + assert wallet2_balance_new == wallet2_balance - 123000 + + await wallet1.sendEvent("get_balance", {}) + result, tags, error = await wallet1.waitFor("get_balance") + assert not error + assert result["balance"] == wallet1_balance_new + + await wallet2.sendEvent("get_balance", {}) + result, tags, error = await wallet2.waitFor("get_balance") + assert not error + assert result["balance"] == wallet2_balance_new + + await wallet1.close() + await wallet2.close() + + +@pytest.mark.asyncio +async def test_multi_pay_invoices(): + nwc1 = await create_nwc("wallet1", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0) + nwc2 = await create_nwc("wallet2", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0) + nwc3 = await create_nwc("wallet3", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0) + + wallet1 = NWCWallet(nwc1["pairing"]) + wallet2 = NWCWallet(nwc2["pairing"]) + wallet3 = NWCWallet(nwc3["pairing"]) + + await wallet1.start() + await wallet2.start() + await wallet3.start() + + await refresh_wallet_balances() + wallet1_balance = wallets["wallet1"]["balance_msats"] + wallet2_balance = wallets["wallet2"]["balance_msats"] + wallet3_balance = wallets["wallet3"]["balance_msats"] + + await wallet1.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123" + }) + + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + assert result["invoice"] + invoice1 = result["invoice"] + + await wallet1.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123" + }) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + assert result["invoice"] + invoice2 = result["invoice"] + + await wallet2.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123" + }) + result, tags, error = await wallet2.waitFor("make_invoice") + assert not error + assert result["invoice"] + invoice3 = result["invoice"] + + await wallet3.sendEvent("multi_pay_invoice", { + "invoices":[ + {"id":"invoice1", "invoice": invoice1, "amount": 123000}, + {"id":"invoice2", "invoice": invoice2, "amount": 123000}, + { "invoice": invoice3} + ] + }) + result, tags, error = await wallet3.waitFor("multi_pay_invoice") + assert not error + d_tag = [t[1] for t in tags if t[0] == "d"][0] + if d_tag=="invoice1": + assert result["preimage"] + elif d_tag=="invoice2": + assert result["preimage"] + elif d_tag==invoice3: + assert result["preimage"] + else: + assert False + + + await refresh_wallet_balances() + wallet1_balance_new = wallets["wallet1"]["balance_msats"] + wallet2_balance_new = wallets["wallet2"]["balance_msats"] + wallet3_balance_new = wallets["wallet3"]["balance_msats"] + + assert wallet1_balance_new == wallet1_balance + 123000 + 123000 + assert wallet2_balance_new == wallet2_balance + 123000 + assert wallet3_balance_new == wallet3_balance - 123000 - 123000 - 123000 + + + + await wallet1.sendEvent("get_balance", {}) + result, tags, error = await wallet1.waitFor("get_balance") + assert not error + assert result["balance"] == wallet1_balance_new + + await wallet2.sendEvent("get_balance", {}) + result, tags, error = await wallet2.waitFor("get_balance") + assert not error + assert result["balance"] == wallet2_balance_new + + await wallet3.sendEvent("get_balance", {}) + result, tags, error = await wallet3.waitFor("get_balance") + assert not error + assert result["balance"] == wallet3_balance_new + + await wallet1.close() + await wallet2.close() + await wallet3.close() + + + + + + +@pytest.mark.asyncio +async def test_insufficient_balance(): + nwc1 = await create_nwc("wallet1", "test_insufficient_balance", ["invoice", "pay", "balance"], [], 0) + nwc2 = await create_nwc("wallet2", "test_insufficient_balance", ["invoice", "pay", "balance"], [], 0) + await refresh_wallet_balances() + wallet1_balance = wallets["wallet1"]["balance_msats"] + amount_to_spend = wallet1_balance + 1000 + wallet1 = NWCWallet(nwc1["pairing"]) + wallet2 = NWCWallet(nwc2["pairing"]) + await wallet1.start() + await wallet2.start() + + await wallet2.sendEvent("make_invoice", { + "amount": amount_to_spend, + "description": "test 123" + }) + result, tags, error = await wallet2.waitFor("make_invoice") + assert not error + assert result["invoice"] + invoice = result["invoice"] + + await wallet1.sendEvent("pay_invoice", { + "invoice": invoice + }) + result, tags, error = await wallet1.waitFor("pay_invoice") + logger.info(error) + logger.info(result) + logger.info(amount_to_spend) + + assert error + # The proper error code should be INSUFFICIENT_BALANCE + # but we use the more generic PAYMENT_FAILED in our implementation for simplicity + #assert error["code"] == "INSUFFICIENT_BALANCE" + assert error["code"] == "PAYMENT_FAILED" + + await wallet1.close() + await wallet2.close() + + + +@pytest.mark.asyncio +async def test_expiry(): + nwc = await create_nwc("wallet3", "test_expiry", ["invoice", "pay", "balance"], [], 1) + await asyncio.sleep(2) + wallet3 = NWCWallet(nwc["pairing"]) + await wallet3.start() + await wallet3.sendEvent("make_invoice", { + "amount": 123000, + "description": "test 123" + }) + result, tags, error = await wallet3.waitFor("make_invoice") + assert error + assert error["code"] == "UNAUTHORIZED" , "Expected UNAUTHORIZED error, because the NWC expired" + await wallet3.close() + + + + +@pytest.mark.asyncio +async def test_budget(): + nwc1 = await create_nwc("wallet1", "test_expiry", ["invoice", "pay", "balance"], [], 0) + nwc3 = await create_nwc("wallet3", "test_expiry", ["invoice", "pay", "balance"], [ + { + "budget_msats": 100000, + "refresh_window": 3600, + "created_at": time.time() + } + ], 0) + wallet1 = NWCWallet(nwc1["pairing"]) + wallet3 = NWCWallet(nwc3["pairing"]) + await wallet3.start() + await wallet1.start() + await wallet1.sendEvent("make_invoice", { + "amount": 101000, + "description": "Invalid" + }) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + + await wallet3.sendEvent("pay_invoice", { + "invoice": result["invoice"] + }) + result, tags, error = await wallet3.waitFor("pay_invoice") + assert error + assert error["code"] == "QUOTA_EXCEEDED" , "Expected QUOTA_EXCEEDED error, because the budget was exceeded" + + await wallet1.sendEvent("make_invoice", { + "amount": 99000, + "description": "Valid" + }) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + + await wallet3.sendEvent("pay_invoice", { + "invoice": result["invoice"] + }) + result, tags, error = await wallet3.waitFor("pay_invoice") + assert not error, "Expected successful payment, because the budget was not exceeded" + assert result["preimage"] + + await wallet1.sendEvent("make_invoice", { + "amount": 100000-99000+1000, + "description": "Invalid" + }) + + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + + await wallet3.sendEvent("pay_invoice", { + "invoice": result["invoice"] + }) + result, tags, error = await wallet3.waitFor("pay_invoice") + assert error + assert error["code"] == "QUOTA_EXCEEDED" , "Expected QUOTA_EXCEEDED error, because the budget was exceeded" + + await wallet3.close() + await wallet1.close() + + +@pytest.mark.asyncio +async def test_budget_refresh(): + nwc1 = await create_nwc("wallet1", "test_expiry", ["invoice", "pay", "balance"], [], 0) + nwc3 = await create_nwc("wallet3", "test_expiry", ["invoice", "pay", "balance"], [ { + "budget_msats": 100000, + "refresh_window": 5, + "created_at": time.time() + }], 0) + wallet1 = NWCWallet(nwc1["pairing"]) + wallet3 = NWCWallet(nwc3["pairing"]) + await wallet3.start() + await wallet1.start() + await wallet1.sendEvent("make_invoice", { + "amount": 100000, + "description": "Invalid" + }) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + + await wallet1.sendEvent("make_invoice", { + "amount": 100000, + "description": "Invalid" + }) + result2, tags, error = await wallet1.waitFor("make_invoice") + assert not error + + await wallet3.sendEvent("pay_invoice", { + "invoice": result["invoice"] + }) + result, tags, error = await wallet3.waitFor("pay_invoice") + assert not error, "Expected successful payment, because the budget was not exceeded" + + + await wallet3.sendEvent("pay_invoice", { + "invoice": result2["invoice"] + }) + result, tags, error = await wallet3.waitFor("pay_invoice") + assert error + assert error["code"] == "QUOTA_EXCEEDED", "Expected QUOTA_EXCEEDED error, because the budget was exceeded" + + await asyncio.sleep(5) + await wallet1.sendEvent("make_invoice", { + "amount": 100000, + "description": "Valid" + }) + result, tags, error = await wallet1.waitFor("make_invoice") + assert not error + + + await wallet3.sendEvent("pay_invoice", { + "invoice": result["invoice"] + }) + result, tags, error = await wallet3.waitFor("pay_invoice") + assert not error, "Expected successful payment, because the budget was refreshed" + + await wallet3.close() + await wallet1.close() + + + + +