diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index f59ec20aa..000000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 457cb84a4..000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: "Bug Report \U0001FAB2 " -about: 'Report a bug ' -title: '' -labels: Bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Platform:** - - OS: [e.g. iOS 15.1, Android 14] - - Device: [e.g. iPhone 14, Galaxy S21] - - Cake Wallet Version: [e.g. 4.12.1] - - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index f8cc8f9ca..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Feature or Enhancement Request ✨ - url: https://github.com/cake-tech/cake_wallet/discussions/new?category=feature-requests - about: Suggest an idea for Cake Wallet - - name: Not sure where to start? - url: https://docs.cakewallet.com - about: Start by reading checking out the guides! - - name: Need help? - url: https://cakewallet.com/#contact - about: Use our live chat or send a support email! diff --git a/.github/assets/Logo_CakeWallet.png b/.github/assets/Logo_CakeWallet.png deleted file mode 100644 index 459a6b37c..000000000 Binary files a/.github/assets/Logo_CakeWallet.png and /dev/null differ diff --git a/.github/assets/NOTICE.txt b/.github/assets/NOTICE.txt deleted file mode 100644 index 9719639a1..000000000 --- a/.github/assets/NOTICE.txt +++ /dev/null @@ -1,48 +0,0 @@ -Notice for linux-badge.svg: - -1: -This is the Linux-penguin again... - -Originally drewn by Larry Ewing (http://www.isc.tamu.edu/~lewing/) -(with the GIMP) the Linux Logo has been vectorized by me (Simon Budig, -http://www.home.unix-ag.org/simon/). - -This happened quite some time ago with Corel Draw 4. But luckily -meanwhile there are tools available to handle vector graphics with -Linux. Bernhard Herzog (bernhard@users.sourceforge.net) deserves kudos -for creating Sketch (http://sketch.sourceforge.net), a powerful free -tool for creating vector graphics. He converted the Corel Draw file to -the Sketch native format. Since I am unable to maintain the Corel Draw -file any longer, the Sketch version now is the "official" one. - -Anja Gerwinski (anja@gerwinski.de) has created an alternate version of -the penguin (penguin-variant.sk) with a thinner mouth line and slightly -altered gradients. It also features a nifty drop shadow. - -The third bird (penguin-flat.sk) is a version reduced to three colors -(black/white/yellow) for e.g. silk screen printing. I made this version -for a mug, available at the friendly folks at -http://www.kernelconcepts.de/ - they do good stuff, mail Petra -(pinguin@kernelconcepts.de) if you need something special or don't -understand the german :-) - -These drawings are copyrighted by Larry Ewing and Simon Budig -(penguin-variant.sk also by Anja Gerwinski), redistribution is free but -has to include this README/Copyright notice. - -The use of these drawings is free. However I am happy about a sample of -your mug/t-shirt/whatever with this penguin on it... - -Have fun - Simon Budig - - -Simon.Budig@unix-ag.org -http://www.home.unix-ag.org/simon/ - -Simon Budig -Am Hardtkoeppel 2 -D-61279 Graevenwiesbach - -2: -Attribution: lewing@isc.tamu.edu Larry Ewing and The GIMP \ No newline at end of file diff --git a/.github/assets/app-store-badge.svg b/.github/assets/app-store-badge.svg deleted file mode 100755 index 072b425a1..000000000 --- a/.github/assets/app-store-badge.svg +++ /dev/null @@ -1,46 +0,0 @@ - - Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.github/assets/devices.png b/.github/assets/devices.png deleted file mode 100644 index 7bdccc5b5..000000000 Binary files a/.github/assets/devices.png and /dev/null differ diff --git a/.github/assets/f-droid-badge.png b/.github/assets/f-droid-badge.png deleted file mode 100644 index 2c9521de1..000000000 Binary files a/.github/assets/f-droid-badge.png and /dev/null differ diff --git a/.github/assets/google-play-badge.png b/.github/assets/google-play-badge.png deleted file mode 100644 index 9667c568d..000000000 Binary files a/.github/assets/google-play-badge.png and /dev/null differ diff --git a/.github/assets/linux-badge.svg b/.github/assets/linux-badge.svg deleted file mode 100755 index 8416e1bb1..000000000 --- a/.github/assets/linux-badge.svg +++ /dev/null @@ -1,1071 +0,0 @@ - -linux-badgeGET IT ONLinuxlinux-badge diff --git a/.github/assets/mac-store-badge.svg b/.github/assets/mac-store-badge.svg deleted file mode 100755 index c36a76a5a..000000000 --- a/.github/assets/mac-store-badge.svg +++ /dev/null @@ -1,51 +0,0 @@ - - Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 272f7bbee..18ad16e4b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,4 +11,3 @@ Please include a summary of the changes and which issue is fixed / feature is ad - [ ] Format code - [ ] Look for code duplication - [ ] Clear naming for variables and methods -- [ ] Manual tests in accessibility mode (TalkBack on Android) passed diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml deleted file mode 100644 index 47b08c44d..000000000 --- a/.github/workflows/automated_integration_test.yml +++ /dev/null @@ -1,305 +0,0 @@ -name: Automated Integration Tests - -on: - # pull_request: - # branches: [main, CW-659-Transaction-History-Automated-Tests] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" - -jobs: - Automated_integration_test: - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - api-level: [29] - # arch: [x86, x86_64] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} - - steps: - - name: is pr - if: github.event_name == 'pull_request' - run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - - - name: is not pr - if: github.event_name != 'pull_request' - run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true - - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: "temurin" - java-version: "17" - - name: Configure placeholder git details - run: | - git config --global user.email "CI@cakewallet.com" - git config --global user.name "Cake Github Actions" - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.27.0" - channel: stable - - - name: Install package dependencies - run: | - sudo apt update - sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get - - - - name: Install go and gomobile - run: | - # install go > 1.23: - wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - go install golang.org/x/mobile/cmd/gomobile@latest - gomobile init - - - name: Build mwebd - run: | - # paths are reset after each step, so we need to set them again: - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - cd /opt/android/cake_wallet/scripts/android/ - ./build_mwebd.sh --dont-install - - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - - - name: Generate key properties - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - - - name: Generate localization - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - - - name: Add secrets - run: | - cd /opt/android/cake_wallet - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorMoneroApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const exolixMoneroApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const swapTradeExchangeMarkup = '${{ secrets.SWAPTRADE_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart - echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart - echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart - # end of test secrets - echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart - echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - - - name: Rename app - run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - - - name: Build - run: | - cd /opt/android/cake_wallet - flutter build apk --release --split-per-abi - - # - name: Rename apk file - # run: | - # cd /opt/android/cake_wallet/build/app/outputs/flutter-apk - # mkdir test-apk - # cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk - # cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk - - # - name: Upload Artifact - # uses: kittaakos/upload-artifact-as-is@v0 - # with: - # path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/ - - # - name: Send Test APK - # continue-on-error: true - # uses: adrey/slack-file-upload-action@1.0.5 - # with: - # token: ${{ secrets.SLACK_APP_TOKEN }} - # path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/${{env.BRANCH_NAME}}.apk - # channel: ${{ secrets.SLACK_APK_CHANNEL }} - # title: "${{ env.BRANCH_NAME }}.apk" - # filename: ${{ env.BRANCH_NAME }}.apk - # initial_comment: ${{ github.event.head_commit.message }} - - - name: 🦾 Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - # arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - working-directory: /opt/android/cake_wallet - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - working-directory: /opt/android/cake_wallet - script: | - chmod a+rx integration_test_runner.sh - ./integration_test_runner.sh diff --git a/.github/workflows/cache_dependencies.yml b/.github/workflows/cache_dependencies.yml new file mode 100644 index 000000000..5373bbc87 --- /dev/null +++ b/.github/workflows/cache_dependencies.yml @@ -0,0 +1,57 @@ +name: Cache Dependencies + +on: + workflow_dispatch: + push: + branches: [ main ] + +jobs: + test: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '8.x' + + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: '3.10.x' + channel: stable + + - name: Install package dependencies + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + + - name: Execute Build and Setup Commands + run: | + sudo mkdir -p /opt/android + sudo chown $USER /opt/android + cd /opt/android + git clone https://github.com/cake-tech/cake_wallet.git --branch main + cd cake_wallet/scripts/android/ + ./install_ndk.sh + source ./app_env.sh cakewallet + ./app_config.sh + + - name: Cache Externals + id: cache-externals + uses: actions/cache@v3 + with: + path: | + /opt/android/cake_wallet/cw_haven/android/.cxx + /opt/android/cake_wallet/cw_haven/ios/External + /opt/android/cake_wallet/cw_monero/android/.cxx + /opt/android/cake_wallet/cw_monero/ios/External + /opt/android/cake_wallet/cw_shared_external/ios/External + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} + + - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} + name: Generate Externals + run: | + cd /opt/android/cake_wallet/scripts/android/ + source ./app_env.sh cakewallet + ./build_all.sh + ./copy_monero_deps.sh diff --git a/.github/workflows/no_http_imports.yaml b/.github/workflows/no_http_imports.yaml deleted file mode 100644 index dad6821ac..000000000 --- a/.github/workflows/no_http_imports.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: No http imports - -on: [pull_request] - -jobs: - PR_test_build: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - name: Check for http package usage - if: github.event_name == 'pull_request' - run: | - GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)" - [[ "x$GIT_GREP_OUT" == "x" ]] && exit 0 - echo "$GIT_GREP_OUT" - echo "There are .dart files which use http imports" - echo "Using http package breaks proxy integration" - echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart" - exit 1 - \ No newline at end of file diff --git a/.github/workflows/no_print_in_dart.yaml b/.github/workflows/no_print_in_dart.yaml deleted file mode 100644 index 507793bd8..000000000 --- a/.github/workflows/no_print_in_dart.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: No print statements in dart files - -on: [pull_request] - -jobs: - PR_test_build: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - name: Check for print() statements in dart code (use printV() instead) - if: github.event_name == 'pull_request' - run: | - GIT_GREP_OUT="$(git grep ' print(' | (grep .dart: || test $? = 1) | (grep -v print_verbose.dart || test $? = 1) | (grep -v print_verbose_dummy.dart || test $? = 1) || true)" - [[ "x$GIT_GREP_OUT" == "x" ]] && exit 0 - echo "$GIT_GREP_OUT" - echo "There are .dart files which use print() statements" - echo "Please use printV from package:cw_core/utils/print_verbose.dart" - exit 1 diff --git a/.github/workflows/no_restricted_imports.yaml b/.github/workflows/no_restricted_imports.yaml deleted file mode 100644 index 03c3de018..000000000 --- a/.github/workflows/no_restricted_imports.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: No restricted imports in lib directory - -on: [pull_request] - -jobs: - check_restricted_imports: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v4 - - name: Check for restricted imports in lib directory - if: github.event_name == 'pull_request' - run: | - RESTRICTED_PACKAGES=( - "cw_bitcoin" - "cw_bitcoin_cash" - "cw_ethereum" - "cw_evm" - "cw_haven" - "cw_mweb" - "cw_nano" - "cw_polygon" - "cw_solana" - "cw_tron" - "cw_wownero" - "cw_zano" - ) - - FOUND_RESTRICTED=false - - for package in "${RESTRICTED_PACKAGES[@]}"; do - GREP_RESULT=$(find lib -type f -name "*.dart" -exec grep -l "import.*package:$package" {} \; || true) - - if [ -n "$GREP_RESULT" ]; then - echo "Found restricted import of '$package' in the following files:" - echo "$GREP_RESULT" - FOUND_RESTRICTED=true - fi - done - - if [ "$FOUND_RESTRICTED" = true ]; then - echo "Error: Restricted package imports found in lib/ directory" - echo "Please remove these imports as they are not allowed in the lib/ directory" - exit 1 - else - echo "No restricted imports found. All good!" - fi \ No newline at end of file diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml new file mode 100644 index 000000000..5434429b2 --- /dev/null +++ b/.github/workflows/pr_test_build.yml @@ -0,0 +1,179 @@ +name: PR Test Build + +on: + pull_request: + branches: [main] + +jobs: + PR_test_build: + runs-on: ubuntu-20.04 + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + + steps: + - name: Free Up GitHub Actions Ubuntu Runner Disk Space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: "8.x" + + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: "3.10.x" + channel: stable + + - name: Install package dependencies + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + + - name: Execute Build and Setup Commands + run: | + sudo mkdir -p /opt/android + sudo chown $USER /opt/android + cd /opt/android + git clone https://github.com/cake-tech/cake_wallet.git --branch $GITHUB_HEAD_REF + cd cake_wallet/scripts/android/ + ./install_ndk.sh + source ./app_env.sh cakewallet + chmod +x pubspec_gen.sh + ./app_config.sh + + - name: Cache Externals + id: cache-externals + uses: actions/cache@v3 + with: + path: | + /opt/android/cake_wallet/cw_haven/android/.cxx + /opt/android/cake_wallet/cw_haven/ios/External + /opt/android/cake_wallet/cw_monero/android/.cxx + /opt/android/cake_wallet/cw_monero/ios/External + /opt/android/cake_wallet/cw_shared_external/ios/External + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} + + - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} + name: Generate Externals + run: | + cd /opt/android/cake_wallet/scripts/android/ + source ./app_env.sh cakewallet + ./build_all.sh + ./copy_monero_deps.sh + + - name: Install Flutter dependencies + run: | + cd /opt/android/cake_wallet + flutter pub get + + - name: Generate KeyStore + run: | + cd /opt/android/cake_wallet/android/app + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + + - name: Generate key properties + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + + - name: Generate localization + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_localization.dart + + - name: Build generated code + run: | + cd /opt/android/cake_wallet + cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + flutter packages pub run build_runner build --delete-conflicting-outputs + + - name: Add secrets + run: | + cd /opt/android/cake_wallet + touch lib/.secrets.g.dart + touch cw_ethereum/lib/.secrets.g.dart + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart + echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart + echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart + echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart + echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart + echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart + echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart + echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart + echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart + echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart + + - name: Rename app + run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties + + - name: Build + run: | + cd /opt/android/cake_wallet + flutter build apk --release + +# - name: Push to App Center +# run: | +# echo 'Installing App Center CLI tools' +# npm install -g appcenter-cli +# echo "Publishing test to App Center" +# appcenter distribute release \ +# --group "Testers" \ +# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ +# --release-notes ${GITHUB_HEAD_REF} \ +# --app Cake-Labs/Cake-Wallet \ +# --token ${{ secrets.APP_CENTER_TOKEN }} \ +# --quiet + + - name: Rename apk file + run: | + cd /opt/android/cake_wallet/build/app/outputs/apk/release + mkdir test-apk + cp app-release.apk test-apk/$GITHUB_HEAD_REF.apk + + - name: Upload Artifact + uses: kittaakos/upload-artifact-as-is@v0 + with: + path: /opt/android/cake_wallet/build/app/outputs/apk/release/test-apk/ + + - name: Send Test APK + continue-on-error: true + uses: adrey/slack-file-upload-action@1.0.5 + with: + token: ${{ secrets.SLACK_APP_TOKEN }} + path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk + channel: ${{ secrets.SLACK_APK_CHANNEL }} + title: "${{github.head_ref}}.apk" + filename: ${{github.head_ref}}.apk + initial_comment: ${{ github.event.head_commit.message }} diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml deleted file mode 100644 index f7c226ce4..000000000 --- a/.github/workflows/pr_test_build_android.yml +++ /dev/null @@ -1,317 +0,0 @@ -name: Cake Wallet Android - -on: [pull_request] - -defaults: - run: - shell: bash -jobs: - PR_test_build: - runs-on: linux-amd64 - container: - image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - MONEROC_CACHE_DIR_ROOT: /opt/generic_cache - BRANCH_NAME: ${{ github.head_ref || github.ref_name }} - ANDROID_AVD_HOME: /root/.android/avd - volumes: - - /opt/cw_cache_android/root/.cache:/root/.cache - - /opt/cw_cache_android/root/.android/avd/:/root/.android/avd - - /opt/cw_cache_android/root/.ccache:/root/.ccache - - /opt/cw_cache_android/root/.pub-cache/:/root/.pub-cache - - /opt/cw_cache_android/root/.gradle/:/root/.gradle - - /opt/cw_cache_android/root/.android/:/root/.android - - /opt/cw_cache_android/root/go/pkg:/root/go/pkg - - /opt/cw_cache_android/opt/generic_cache:/opt/generic_cache - - /dev/kvm:/dev/kvm - strategy: - matrix: - api-level: [29] - - steps: - - name: Fix github actions messing up $HOME... - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: configure git - run: | - git config --global --add safe.directory '*' - git config --global user.email "ci@cakewallet.com" - git config --global user.name "CakeWallet CI" - - name: Get the full commit message - run: | - FULL_MESSAGE="$(git log -1 --pretty=%B)" - echo "message<> $GITHUB_ENV - echo "$FULL_MESSAGE" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Add secrets - run: | - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - if [[ "x${{ secrets.SALT }}" == "x" ]]; - then - echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart - else - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - fi - if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; - then - echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart - else - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.KEY }}" == "x" ]]; - then - echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart - else - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; - then - echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart - else - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; - then - echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart - else - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; - then - echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart - else - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; - then - echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart - else - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - fi - echo "const changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorMoneroApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const exolixMoneroApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const swapTradeExchangeMarkup = '${{ secrets.SWAPTRADE_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart - echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart - echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - # for tests - echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart - # end of test secrets - echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart - echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart - echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - - - name: prepare monero_c and cache - run: | - export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') - echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment - mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" - pushd scripts - ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" - ./prepare_moneroc.sh - popd - pushd scripts/monero_c - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true - - rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" - rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" - mkdir -p contrib/depends || true - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" - popd - - - name: Generate KeyStore - run: | - pushd /opt/generic_cache - if [[ ! -f key.jks ]]; - then - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - else - echo "$PWD/key.jks exist, not generating" - fi - popd - cp /opt/generic_cache/key.jks android/app - - - name: Execute Build and Setup Commands - run: | - pushd scripts/android - source ./app_env.sh cakewallet - ./app_config.sh - popd - - - name: Build monero_c - run: | - pushd scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - popd - - - name: Install Flutter dependencies - run: | - flutter pub get - - - name: Build mwebd - run: | - set -x -e - export MWEBD_HASH=$(cat scripts/android/build_mwebd.sh | grep 'git reset --hard' | xargs | awk '{ print $4 }') - echo MWEBD_HASH=$MWEBD_HASH >> /etc/environment - pushd scripts/android - gomobile init; - ./build_mwebd.sh --dont-install - popd - - - name: Build Decred - run: | - set -x -e - pushd scripts/android - ./build_decred.sh - popd - - - name: Build generated code - run: | - flutter --version - flutter clean - rm -rf .dart_tool - rm pubspec.lock - flutter pub get - ./model_generator.sh async - - - name: Generate key properties - run: | - dart run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - - - name: Generate localization - run: | - dart run tool/generate_localization.dart - - - name: Rename app - run: | - sanitized_branch_name=${BRANCH_NAME#origin/} # Remove 'origin/' prefix if it exists - sanitized_branch_name=${sanitized_branch_name:0:16} # Take only the first 16 characters - sanitized_branch_name=$(echo "$sanitized_branch_name" | tr '[:upper:]' '[:lower:]') # Convert to lowercase - sanitized_branch_name=$(echo "$sanitized_branch_name" | sed 's/[^a-z0-9]//g') # Remove all special characters - - echo -e "id=com.cakewallet.test_${sanitized_branch_name}\nname=${BRANCH_NAME}" > android/app.properties - - - name: Build - run: | - flutter build apk --dart-define=hasDevOptions=true --release --split-per-abi - - - name: Rename apk file - run: | - cd build/app/outputs/flutter-apk - mkdir test-apk - cp app-arm64-v8a-release.apk test-apk/${BRANCH_NAME}.apk - cp app-x86_64-release.apk test-apk/${BRANCH_NAME}_x86.apk - - - name: Find APK file - id: find_apk - run: | - set -x - apk_file=$(ls build/app/outputs/flutter-apk/test-apk/${BRANCH_NAME}.apk || exit 1) - echo "APK_FILE=$apk_file" >> $GITHUB_ENV - - - name: Upload artifact to slack - if: ${{ !contains(env.message, 'skip slack') }} - continue-on-error: true - uses: adrey/slack-file-upload-action@1.0.5 - with: - token: ${{ secrets.SLACK_APP_TOKEN }} - path: ${{ env.APK_FILE }} - channel: ${{ secrets.SLACK_APK_CHANNEL }} - initial_comment: ${{ env.message }} - - - name: cleanup - run: rm -rf build/app/outputs/flutter-apk/test-apk/ - - - name: Upload Artifact to github - uses: actions/upload-artifact@v4 - with: - path: ${{ github.workspace }}/build/app/outputs/flutter-apk - name: "android apk" diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml deleted file mode 100644 index f057b19e5..000000000 --- a/.github/workflows/pr_test_build_linux.yml +++ /dev/null @@ -1,317 +0,0 @@ -name: Cake Wallet Linux - -on: [pull_request] - -defaults: - run: - shell: bash -jobs: - PR_test_build: - runs-on: linux-amd64 - container: - image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - MONEROC_CACHE_DIR_ROOT: /opt/generic_cache - BRANCH_NAME: ${{ github.head_ref || github.ref_name }} - DESKTOP_FORCE_MOBILE: Y - volumes: - - /opt/cw_cache_linux/root/.cache:/root/.cache - - /opt/cw_cache_linux/root/.ccache:/root/.ccache - - /opt/cw_cache_linux/root/.pub-cache/:/root/.pub-cache - - /opt/cw_cache_linux/root/go/pkg:/root/go/pkg - - /opt/cw_cache_linux/opt/generic_cache:/opt/generic_cache - - steps: - - name: Fix github actions messing up $HOME... - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - name: configure git - run: | - git config --global --add safe.directory '*' - git config --global user.email "ci@cakewallet.com" - git config --global user.name "CakeWallet CI" - - name: Get the full commit message - run: | - FULL_MESSAGE="$(git log -1 --pretty=%B)" - echo "message<> $GITHUB_ENV - echo "$FULL_MESSAGE" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Add secrets - run: | - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - if [[ "x${{ secrets.SALT }}" == "x" ]]; - then - echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart - else - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - fi - if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; - then - echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart - else - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.KEY }}" == "x" ]]; - then - echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart - else - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; - then - echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart - else - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; - then - echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart - else - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; - then - echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart - else - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - fi - if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; - then - echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart - else - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - fi - echo "const changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorMoneroApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const exolixMoneroApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const swapTradeExchangeMarkup = '${{ secrets.SWAPTRADE_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart - echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart - echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - # tests - echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart - # end of test secrets - echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart - echo "const kryptonimApiKey = '${{ secrets.KRYPTONIM_API_KEY }}';" >> lib/.secrets.g.dart - echo "const walletGroupSalt = '${{ secrets.WALLET_GROUP_SALT }}';" >> lib/.secrets.g.dart - - - name: prepare monero_c and cache - run: | - export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') - echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment - mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" - pushd scripts - ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" - ./prepare_moneroc.sh - popd - pushd scripts/monero_c - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true - mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true - - rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" - rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" - mkdir -p contrib/depends || true - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" - ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" - popd - - - name: Execute Build and Setup Commands - run: | - pushd scripts/linux - source ./app_env.sh cakewallet - ./app_config.sh - popd - - - name: Build monero_c - run: | - pushd scripts/linux/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - popd - - - name: Install Flutter dependencies - run: | - flutter pub get - - - name: Build generated code - run: | - ./model_generator.sh async - - - name: Generate localization - run: | - dart run tool/generate_localization.dart - - - name: Build linux - run: | - flutter build linux --dart-define=hasDevOptions=true --release - - - name: Compress release - run: | - pushd build/linux/x64/release - zip -r cakewallet_linux.zip bundle - popd - - - name: Upload Artifact to github - uses: actions/upload-artifact@v4 - with: - path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip - name: cakewallet_linux - - - name: Prepare virtual desktop - if: ${{ contains(env.message, 'run tests') }} - run: | - nohup Xvfb :99 -screen 0 720x1280x16 & - echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV - dbus-daemon --system --fork - nohup NetworkManager & - nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv & - - # Note for people adding tests: - # - Tests are ran on Linux, with some things being mocked out. - # - Screen recording is being provided for the entire length of the test, you can download it in github articats. - # - Screen recordeding is encrypted, look at step "Stop screen recording, encrypt and upload", and add your key if you want - # Reason for encryption is the fact that we restore the wallet from seed, and we don't want to leak that, while there - # isn't much in those wallets anyway, we still wouldn't like to leak it to anyone who is able to access github. - - - name: Test [confirm_seeds_flow_test] - if: ${{ contains(env.message, 'run tests') }} - timeout-minutes: 20 - run: | - xmessage -timeout 30 "confirm_seeds_flow_test" & - rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet - exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/confirm_seeds_flow_test.dart - - name: Test [create_wallet_flow_test] - if: ${{ contains(env.message, 'run tests') }} - timeout-minutes: 20 - run: | - xmessage -timeout 30 "create_wallet_flow_test" & - rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet - exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/create_wallet_flow_test.dart - - name: Test [exchange_flow_test] - if: ${{ contains(env.message, 'run tests') }} - timeout-minutes: 20 - run: | - xmessage -timeout 30 "exchange_flow_test" & - rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet - exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/exchange_flow_test.dart - - name: Test [restore_wallet_through_seeds_flow_test] - if: ${{ contains(env.message, 'run tests') }} - timeout-minutes: 20 - run: | - xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" & - rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet - exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart - - name: Test [cw_monero] - timeout-minutes: 2 - run: cd cw_monero && flutter test - - name: Stop screen recording, encrypt and upload - if: always() - run: | - if [[ ! -f "/opt/screen_grab.mkv" ]]; - then - exit 0; - fi - killall ffmpeg - sleep 5 - killall -9 ffmpeg || true - sleep 5 - # Feel free to add your own public key if you wish - gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 6B3199AD9B3D23B8 # konstantin@cakewallet.com - gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 35C8DBAFB8D9ACAC # cyjan@mrcyjanek.net - gpg --trust-model always --encrypt --output /opt/screen_grab.mkv.gpg \ - --recipient 6B3199AD9B3D23B8 \ - --recipient 35C8DBAFB8D9ACAC \ - /opt/screen_grab.mkv - rm /opt/screen_grab.mkv - mv /opt/screen_grab.mkv.gpg ./screen_grab.mkv.gpg - - name: Upload Artifact to github - if: always() - continue-on-error: true - uses: actions/upload-artifact@v4 - with: - path: ${{ github.workspace }}/screen_grab.mkv.gpg - name: tests_screen_grab diff --git a/.gitignore b/.gitignore index 84a7ecdcd..c735d4058 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ .history .svn/ .fvm/ -.fvmrc # IntelliJ related *.iml @@ -87,20 +86,13 @@ cw_monero/cw_monero/android/.cxx/ **/*.g.dart android/key.properties -android/app/key.jks **/tool/.secrets-prod.json **/tool/.secrets-test.json **/tool/.secrets-config.json -**/tool/.evm-secrets-config.json **/tool/.ethereum-secrets-config.json -**/tool/.solana-secrets-config.json -**/tool/.nano-secrets-config.json -**/tool/.tron-secrets-config.json **/lib/.secrets.g.dart -**/cw_evm/lib/.secrets.g.dart -**/cw_solana/lib/.secrets.g.dart -**/cw_tron/lib/.secrets.g.dart +**/cw_ethereum/lib/.secrets.g.dart vendor/ @@ -127,42 +119,17 @@ cw_shared_external/ios/External/ cw_haven/ios/External/ cw_haven/android/.externalNativeBuild/ cw_haven/android/.cxx/ -cw_zano/ios/External/ + lib/bitcoin/bitcoin.dart lib/monero/monero.dart lib/haven/haven.dart lib/ethereum/ethereum.dart lib/bitcoin_cash/bitcoin_cash.dart lib/nano/nano.dart -lib/polygon/polygon.dart -lib/solana/solana.dart -lib/tron/tron.dart -lib/wownero/wownero.dart -lib/zano/zano.dart -lib/decred/decred.dart - -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png ios/Runner/Info.plist android/app/src/main/res/mipmap-* android/app/src/main/res/drawable/ic_launcher.png @@ -179,51 +146,3 @@ assets/images/app_logo.png macos/Runner/Info.plist macos/Runner/DebugProfile.entitlements macos/Runner/Release.entitlements -macos/Runner/Runner.entitlements -lib/core/secure_storage.dart - -lib/core/secure_storage.dart - -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -macos/Runner/Configs/AppInfo.xcconfig -macos/Runner.xcodeproj/project.pbxproj - - -integration_test/playground.dart - -# Monero.dart (Monero_C) -scripts/monero_c -# iOS generated framework bin -ios/MoneroWallet.framework/MoneroWallet -ios/WowneroWallet.framework/WowneroWallet -ios/ZanoWallet.framework/ZanoWallet -*_libwallet2_api_c.dylib - -.flatpak-builder -cake_wallet.flatpak -flatpak-build/ - -# macOS -**/Flutter/ephemeral/ -**/Pods/ -**/macos/Flutter/GeneratedPluginRegistrant.swift -**/macos/Flutter/ephemeral -**/xcuserdata/ - -# Windows -**/windows/flutter/ephemeral/ -**/windows/flutter/generated_plugin_registrant.cc -**/windows/flutter/generated_plugin_registrant.h -**/windows/flutter/generated_plugins.cmake - -# Linux -**/linux/flutter/ephemeral/ -**/linux/flutter/generated_plugin_registrant.cc -**/linux/flutter/generated_plugin_registrant.h -**/linux/flutter/generated_plugins.cmake diff --git a/.metadata b/.metadata index c7b8dc9f8..cdddb9350 100644 --- a/.metadata +++ b/.metadata @@ -1,11 +1,11 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: "367f9ea16bfae1ca451b9cc27c1366870b187ae2" - channel: "stable" + revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + channel: stable project_type: app @@ -13,17 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 - base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 - - platform: windows - create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 - base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - platform: macos - create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 - base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 - - platform: linux - create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 - base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2 + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 # User provided section diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 151b7af20..000000000 --- a/Dockerfile +++ /dev/null @@ -1,190 +0,0 @@ -# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly - -# Heavily inspired by cirrusci images -# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile -# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/34/Dockerfile -# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/34-ndk/Dockerfile -# https://github.com/cirruslabs/docker-images-flutter/blob/master/sdk/Dockerfile - -FROM docker.io/debian:12 - -LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet - -# Set necessary environment variables -# Set Go version to latest known-working version -ENV GOLANG_VERSION=1.24.1 - -# Pin Flutter version to latest known-working version -ENV FLUTTER_VERSION=3.27.0 - -# Pin Android Studio, platform, and build tools versions to latest known-working version -# Comes from https://developer.android.com/studio/#command-tools -ENV ANDROID_SDK_TOOLS_VERSION=13114758 -# Comes from https://developer.android.com/studio/releases/build-tools -ENV ANDROID_PLATFORM_VERSION=35 -ENV ANDROID_BUILD_TOOLS_VERSION=34.0.0 - -# If we ever need to migrate the home directory... -RUN sed -i 's|^root:[^:]*:[^:]*:[^:]*:[^:]*:/root:|root:x:0:0:root:/root:|' /etc/passwd -# mkdir -p /root && rm -rf /root && cp -a /root /root -ENV HOME=/root -ENV ANDROID_HOME=/opt/android-sdk-linux \ - LANG=en_US.UTF-8 \ - LC_ALL=en_US.UTF-8 \ - LANGUAGE=en_US:en - -# Set Android SDK paths -ENV ANDROID_SDK_ROOT=$ANDROID_HOME \ - PATH=${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/emulator - -# Upgrade base image -RUN apt-get update \ - && apt-get upgrade -y - -# Install all build dependencies -RUN set -o xtrace \ - && cd /opt \ - && apt-get install -y --no-install-recommends --no-install-suggests \ - # Core dependencies - bc build-essential curl default-jdk git jq lcov libglu1-mesa libpulse0 libsqlite3-dev libstdc++6 locales openssh-client ruby-bundler ruby-full software-properties-common sudo unzip wget zip \ - # for x86 emulators - libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libnspr4 libnss3-dev libsqlite3-dev libxtst6 libxss1 lftp sqlite3 xxd \ - # Linux desktop dependencies - clang cmake libgtk-3-dev ninja-build pkg-config \ - # monero_c dependencies - autoconf automake build-essential ccache gperf libtool llvm \ - # extra stuff for KVM - bridge-utils libvirt-clients libvirt-daemon-system qemu-kvm udev \ - # Linux test dependencies - ffmpeg network-manager x11-utils xvfb psmisc \ - # aarch64-linux-gnu dependencies - g++-aarch64-linux-gnu gcc-aarch64-linux-gnu \ - # x86_64-linux-gnu dependencies - g++-x86-64-linux-gnu gcc-x86-64-linux-gnu \ - # flatpak dependencies - flatpak flatpak-builder binutils elfutils patch unzip xz-utils zstd \ - && apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ - && sh -c 'echo "en_US.UTF-8 UTF-8" > /etc/locale.gen' \ - && locale-gen \ - && update-locale LANG=en_US.UTF-8 - -ENV FLATPAK_RUNTIME_VERSION=24.08 -RUN flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo \ - && flatpak install -y flathub org.freedesktop.Platform//${FLATPAK_RUNTIME_VERSION} \ - && flatpak install -y flathub org.freedesktop.Sdk//${FLATPAK_RUNTIME_VERSION} - -# Install nodejs for Github Actions -RUN curl -fsSL https://deb.nodesource.com/setup_23.x | bash - && \ - apt-get install -y --no-install-recommends nodejs && \ - apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Install Go -ENV PATH=${PATH}:/usr/local/go/bin:${HOME}/go/bin -ENV GOROOT=/usr/local/go -ENV GOPATH=${HOME}/go -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "x86_64" ]; then \ - wget https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz -O go.tar.gz; \ - elif [ "$ARCH" = "aarch64" ]; then \ - wget https://go.dev/dl/go${GOLANG_VERSION}.linux-arm64.tar.gz -O go.tar.gz; \ - else \ - echo "Unsupported architecture: $ARCH"; exit 1; \ - fi && \ - rm -rf /usr/local/go && \ - tar -C /usr/local -xzf go.tar.gz && \ - rm go.tar.gz && \ - go install golang.org/x/mobile/cmd/gomobile@latest && \ - gomobile init - -RUN git config --global user.email "czarek@cakewallet.com" \ - && git config --global user.name "CakeWallet CI" - - -# Install Android SDK commandline tools and emulator -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" != "x86_64" ]; then exit 0; fi \ - && wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip -O android-sdk-tools.zip \ - && mkdir -p ${ANDROID_HOME}/cmdline-tools/ \ - && unzip -q android-sdk-tools.zip -d ${ANDROID_HOME}/cmdline-tools/ \ - && mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \ - && chown -R root:root $ANDROID_HOME \ - && rm android-sdk-tools.zip \ - && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ - && yes | sdkmanager --licenses \ - && wget -O /usr/bin/android-wait-for-emulator https://raw.githubusercontent.com/travis-ci/travis-cookbooks/master/community-cookbooks/android-sdk/files/default/android-wait-for-emulator \ - && chmod +x /usr/bin/android-wait-for-emulator \ - && sdkmanager platform-tools \ - && mkdir -p ${HOME}/.android \ - && touch ${HOME}/.android/repositories.cfg \ - - -# Handle emulator not being available on linux/arm64 (https://issuetracker.google.com/issues/227219818) -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" != "x86_64" ]; then exit 0; fi \ - && sdkmanager emulator - -# Pre-install extra Android SDK dependencies in order to not have to download them for each build -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" != "x86_64" ]; then exit 0; fi \ - && yes | sdkmanager \ - "platforms;android-$ANDROID_PLATFORM_VERSION" \ - "build-tools;$ANDROID_BUILD_TOOLS_VERSION" \ - "platforms;android-33" \ - "platforms;android-34" \ - "platforms;android-35" \ - "build-tools;33.0.2" \ - "build-tools;33.0.1" \ - "build-tools;33.0.0" \ - "build-tools;35.0.0" - -# Install extra NDK dependency for sp_scanner -ENV ANDROID_NDK_VERSION=27.2.12479018 -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" != "x86_64" ]; then exit 0; fi \ - && yes | sdkmanager "ndk;$ANDROID_NDK_VERSION" \ - "ndk;27.0.12077973" - -# Install dependencies for tests -# Comes from https://github.com/ReactiveCircus/android-emulator-runner -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" != "x86_64" ]; then exit 0; fi \ - && yes | sdkmanager \ - "system-images;android-29;default;x86_64" \ - "system-images;android-31;default;x86_64" \ - "platforms;android-29" \ - "platforms;android-31" - -# Fake the KVM status so the Android emulator doesn't complain (that much) -RUN (addgroup kvm || true) && \ - adduser root kvm && \ - mkdir -p /etc/udev/rules.d/ && \ - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | tee /etc/udev/rules.d/99-kvm4all.rules - -# Install rustup, rust toolchains, and cargo-ndk -ENV PATH=${HOME}/.cargo/bin:${PATH} -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \ - cargo install cargo-ndk && \ - for toolchain in stable nightly; \ - do \ - for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu; \ - do \ - rustup target add --toolchain $toolchain $target; \ - done \ - done - -# Download and install Flutter -ENV HOME=${HOME} -ENV FLUTTER_HOME=${HOME}/sdks/flutter/${FLUTTER_VERSION} -ENV FLUTTER_ROOT=$FLUTTER_HOME -ENV PATH=${PATH}:${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin - -RUN git clone --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} && \ - cd ${FLUTTER_HOME} && \ - git fetch -a - -RUN yes | flutter doctor --android-licenses \ - && flutter doctor \ - && chown -R root:root ${FLUTTER_HOME} - -# Download and pre-cache necessary Flutter artifacts to speed up builds -RUN flutter precache diff --git a/LICENSE.md b/LICENSE.md index 09bb6208b..4268b9710 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2025 Cake Labs LLC +Copyright (c) 2018-2023 Cake Labs LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PRIVACY.md b/PRIVACY.md index a5c8eddfb..88f180c5e 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,11 +1,11 @@ Privacy Policy -Last modified: January 24, 2024 +Last modified: August 9, 2023 Introduction ============ - Cake Labs LLC ("Cake Labs", "Company", or "We") respects your privacy and are committed to protecting it through our compliance with this policy. + Cake Labs LLC ("Cake Labs", "Company", or "We") respect your privacy and are committed to protecting it through our compliance with this policy. This policy describes the types of information we may collect from you or that you may provide when you use the App (our "App") and our practices for collecting, using, maintaining, protecting, and disclosing that information. @@ -13,7 +13,7 @@ Introduction - On this App. - In email, text, and other electronic messages between you and this App. It does not apply to information collected by: - - Us offline or through any other means, including on any other App operated by the Company or any third party (including our affiliates and subsidiaries); or + - Us offline or through any other means, including on any other App operated by Company or any third party (including our affiliates and subsidiaries); or - Any third party (including our affiliates and subsidiaries), including through any application or content (including advertising) that may link to or be accessible from or on the App. Please read this policy carefully to understand our policies and practices regarding your information and how we will treat it. If you do not agree with our policies and practices, you have the choice to not use the App. By accessing or using this App, you agree to this privacy policy. This policy may change from time to time. Your continued use of this App after we make changes is deemed to be acceptance of those changes, so please check the policy periodically for updates. @@ -112,12 +112,12 @@ Data Security In any situation, Cake Labs takes no responsibility for interception of personal data by any outside individual, group, corporation, or institution. You should understand this and take any and all appropriate actions to secure your own data. -Other Websites and Third-Party Services ---------------------------------------- +Links to Other Websites +----------------------- The App may contain links to other websites that are not operated by us. If you click on a Third-Party Service link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit. We have no control over and assume no responsibility for the content, privacy policies or practices of any third-party sites or services. - The App includes several optional Third-Party Services, which may not be available to all users. If you use Third-Party Services, you must agree to their respective Privacy Policies. When using certain optional features in the app such as buying and selling, you may be asked to provide information to a Third-Party Service. You will need to read and accept the privacy policy for that third party. This Third-Party Service may ask for your name, your photo ID, your social security number or other similar number, mailing address, cryptocurrency address, or other information. They may ask you to take a selfie image. Information shared with a Third-Party Service is subject to their respective Privacy Policies. + The App includes several optional Third-Party Services, which may not be available to all users. If you use Third-Party Services, you must agree to their respective Privacy Policies. Changes to Our Privacy Policy ----------------------------- diff --git a/README.md b/README.md index ea796dbf2..7b739f980 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,15 @@ -
+# Cake Wallet for Mobile and Desktop -![logo](.github/assets/Logo_CakeWallet.png) +## Open Source Multi-Currency Wallet -
+## Links -![devices](.github/assets/devices.png) - -
- -[](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=iphone) -[](https://play.google.com/store/apps/details?id=com.cakewallet.cake_wallet) -[](https://fdroid.cakelabs.com) -[](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=mac) -[](https://github.com/cake-tech/cake_wallet/releases) - -
- -# Cake Wallet - -[Cake Wallet](https://cakewallet.com) is an open-source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux. - -Cake Wallet includes support for several cryptocurrencies, including: -* Monero (XMR) -* Bitcoin (BTC) -* Ethereum (ETH) -* Litecoin (LTC) -* Bitcoin Cash (BCH) -* Polygon (POL) -* Solana (SOL) -* Tron (TRX) -* Nano (XNO) -* Zano (ZANO) -* Decred (DCR) -* Wownero (WOW) +* Website: https://cakewallet.com +* App Store (iOS / MacOS): https://cakewallet.com/ios +* Google Play: https://cakewallet.com/gp +* F-Droid: https://fdroid.cakelabs.com +* APK: https://github.com/cake-tech/cake_wallet/releases +* Linux: https://github.com/cake-tech/cake_wallet/releases ## Features @@ -47,7 +24,7 @@ Cake Wallet includes support for several cryptocurrencies, including: * Create several wallets * Select your own custom nodes/servers * Address book -* Backup to an external location or iCloud +* Backup to external location or iCloud * Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles * Set desired network fee level * Store local transaction notes @@ -84,6 +61,10 @@ Cake Wallet includes support for several cryptocurrencies, including: * Automatically generate new addresses * Specify multiple recipients for batch sending +### Haven Specific Features + +* Send, receive, and store XHV and all xAssets like xUSD, xEUR, xAG, etc. + # Monero.com by Cake Wallet for Android and iOS ## Open Source Monero-Only Wallet @@ -98,17 +79,6 @@ Cake Wallet includes support for several cryptocurrencies, including: * F-Droid: https://fdroid.cakelabs.com * APK: https://github.com/cake-tech/cake_wallet/releases -### APK Verification - -APK releases on GitHub, Accrescent, and F-Droid use the same key. They can easily be verified using [apksigner](https://developer.android.com/tools/apksigner#options-verify) or [AppVerifier](https://github.com/soupslurpr/AppVerifier). - -See below for Cake Wallet's SHA-256 signing certificate hash: - -``` -com.cakewallet.cake_wallet -C5:40:53:AB:0F:10:D9:54:17:62:A3:DA:76:65:AE:3D:BA:5E:7C:74:3A:B4:F1:08:A5:34:9D:62:AC:10:6E:F5 -``` - # Support We have 24/7 free support. Please contact support@cakewallet.com @@ -171,9 +141,7 @@ The only parts to be translated, if needed, are the values m and s after the var 4. Add the language to `lib/entities/language_service.dart` under both `supportedLocales` and `localeCountryCode`. Use the name of the language in the local language and in English in parentheses after for `supportedLocales`. Use the [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) for `localeCountryCode`. You must choose one country, so choose the country with the most native speakers of this language or is otherwise best associated with this language. -5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make it transparent. Or you can use another program like Photoshop. - -6. Add the new language code to `tool/utils/translation/translation_constants.dart` +5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 digit localeCountryCode. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make transparent. Or you can use another program like Photoshop. ## Add a new fiat currency diff --git a/analysis_options.yaml b/analysis_options.yaml index bd35233ba..396904041 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,5 @@ include: package:lints/recommended.yaml - analyzer: exclude: [ build/**, @@ -11,19 +10,7 @@ analyzer: lib/generated/*.dart, cw_monero/ios/External/**, cw_shared_external/**, - shared_external/**, - lib/bitcoin/cw_bitcoin.dart, - lib/bitcoin_cash/cw_bitcoin_cash.dart, - lib/ethereum/cw_ethereum.dart, - lib/haven/cw_haven.dart, - lib/monero/cw_monero.dart, - lib/nano/cw_nano.dart, - lib/polygon/cw_polygon.dart, - lib/solana/cw_solana.dart, - lib/tron/cw_tron.dart, - lib/wownero/cw_wownero.dart, - lib/zano/cw_zano.dart, - ] + shared_external/**] language: strict-casts: true strict-raw-types: true @@ -85,4 +72,4 @@ linter: # - unawaited_futures # - unnecessary_getters_setters # - unrelated_type_equality_checks -# - valid_regexps +# - valid_regexps \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 4a8045bb3..946c53697 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,9 +1,3 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) { } } +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -22,6 +21,9 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { @@ -35,27 +37,16 @@ if (appPropertiesFile.exists()) { } android { - compileSdkVersion 35 - buildToolsVersion "35.0.0" + compileSdkVersion 33 lintOptions { disable 'InvalidPackage' } - compileOptions { - coreLibraryDesugaringEnabled true - - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - - namespace "com.cakewallet.cake_wallet" - defaultConfig { applicationId appProperties['id'] minSdkVersion 24 - targetSdkVersion 34 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -81,14 +72,16 @@ android { buildTypes { release { signingConfig signingConfigs.release + + shrinkResources false + minifyEnabled false + useProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } - debug { - signingConfig signingConfigs.release - } } - ndkVersion "27.0.12077973" + ndkVersion "25.1.8937393" } flutter { @@ -99,10 +92,5 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' -} -configurations { - implementation.exclude module:'proto-google-common-protos' - implementation.exclude module:'protolite-well-known-types' - implementation.exclude module:'protobuf-javalite' + implementation 'com.unstoppabledomains:resolution:5.0.0' } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index a733bae9e..d24d7f10a 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -5,98 +5,4 @@ -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } --dontwarn io.flutter.embedding.** --dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication - -# start reown --dontwarn com.github.luben.zstd.BufferPool --dontwarn com.github.luben.zstd.ZstdInputStream --dontwarn com.github.luben.zstd.ZstdOutputStream --dontwarn com.google.api.client.http.GenericUrl --dontwarn com.google.api.client.http.HttpHeaders --dontwarn com.google.api.client.http.HttpRequest --dontwarn com.google.api.client.http.HttpRequestFactory --dontwarn com.google.api.client.http.HttpResponse --dontwarn com.google.api.client.http.HttpTransport --dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder --dontwarn com.google.api.client.http.javanet.NetHttpTransport --dontwarn java.awt.Color --dontwarn java.awt.Dimension --dontwarn java.awt.Graphics2D --dontwarn java.awt.Graphics --dontwarn java.awt.Image --dontwarn java.awt.Point --dontwarn java.awt.Polygon --dontwarn java.awt.Shape --dontwarn java.awt.color.ColorSpace --dontwarn java.awt.geom.AffineTransform --dontwarn java.awt.image.BufferedImage --dontwarn java.awt.image.ColorModel --dontwarn java.awt.image.ComponentColorModel --dontwarn java.awt.image.ComponentSampleModel --dontwarn java.awt.image.DataBuffer --dontwarn java.awt.image.DataBufferByte --dontwarn java.awt.image.DataBufferInt --dontwarn java.awt.image.DataBufferUShort --dontwarn java.awt.image.ImageObserver --dontwarn java.awt.image.MultiPixelPackedSampleModel --dontwarn java.awt.image.Raster --dontwarn java.awt.image.RenderedImage --dontwarn java.awt.image.SampleModel --dontwarn java.awt.image.SinglePixelPackedSampleModel --dontwarn java.awt.image.WritableRaster --dontwarn java.beans.BeanInfo --dontwarn java.beans.FeatureDescriptor --dontwarn java.beans.IntrospectionException --dontwarn java.beans.Introspector --dontwarn java.beans.PropertyDescriptor --dontwarn java.lang.reflect.InaccessibleObjectException --dontwarn javax.imageio.IIOImage --dontwarn javax.imageio.ImageIO --dontwarn javax.imageio.ImageWriteParam --dontwarn javax.imageio.ImageWriter --dontwarn javax.imageio.metadata.IIOMetadata --dontwarn javax.imageio.stream.ImageOutputStream --dontwarn javax.swing.JComponent --dontwarn javax.swing.JFileChooser --dontwarn javax.swing.JFrame --dontwarn javax.swing.JPanel --dontwarn javax.swing.ProgressMonitor --dontwarn javax.swing.SwingUtilities --dontwarn org.brotli.dec.BrotliInputStream --dontwarn org.joda.time.Instant --dontwarn org.objectweb.asm.AnnotationVisitor --dontwarn org.objectweb.asm.Attribute --dontwarn org.objectweb.asm.ClassReader --dontwarn org.objectweb.asm.ClassVisitor --dontwarn org.objectweb.asm.FieldVisitor --dontwarn org.objectweb.asm.Label --dontwarn org.objectweb.asm.MethodVisitor --dontwarn org.objectweb.asm.Type --dontwarn org.tukaani.xz.ARMOptions --dontwarn org.tukaani.xz.ARMThumbOptions --dontwarn org.tukaani.xz.DeltaOptions --dontwarn org.tukaani.xz.FilterOptions --dontwarn org.tukaani.xz.FinishableOutputStream --dontwarn org.tukaani.xz.FinishableWrapperOutputStream --dontwarn org.tukaani.xz.IA64Options --dontwarn org.tukaani.xz.LZMA2InputStream --dontwarn org.tukaani.xz.LZMA2Options --dontwarn org.tukaani.xz.LZMAInputStream --dontwarn org.tukaani.xz.LZMAOutputStream --dontwarn org.tukaani.xz.MemoryLimitException --dontwarn org.tukaani.xz.PowerPCOptions --dontwarn org.tukaani.xz.SPARCOptions --dontwarn org.tukaani.xz.SingleXZInputStream --dontwarn org.tukaani.xz.UnsupportedOptionsException --dontwarn org.tukaani.xz.X86Options --dontwarn org.tukaani.xz.XZ --dontwarn org.tukaani.xz.XZInputStream --dontwarn org.tukaani.xz.XZOutputStream --dontwarn us.hebi.matlab.mat.ejml.Mat5Ejml --dontwarn us.hebi.matlab.mat.format.Mat5 --dontwarn us.hebi.matlab.mat.format.Mat5File --dontwarn us.hebi.matlab.mat.types.Array --dontwarn us.hebi.matlab.mat.types.MatFile$Entry --dontwarn us.hebi.matlab.mat.types.MatFile -# end reown \ No newline at end of file +-dontwarn io.flutter.embedding.** \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index f880684a6..dc767a55d 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,4 +1,5 @@ - + diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 8283a7c8c..77a555db8 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -1,33 +1,12 @@ - + - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + android:requestLegacyExternalStorage="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - result.success(bytes)); break; + case "getUnstoppableDomainAddress": + int version = Build.VERSION.SDK_INT; + if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { + getUnstoppableDomainAddress(call, result); + } else { + handler.post(() -> result.success("")); + } + break; case "setIsAppSecure": isAppSecure = call.argument("isAppSecure"); if (isAppSecure) { @@ -57,14 +65,6 @@ public class MainActivity extends FlutterFragmentActivity { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } break; - case "disableBatteryOptimization": - disableBatteryOptimization(); - handler.post(() -> result.success(null)); - break; - case "isBatteryOptimizationDisabled": - boolean isDisabled = isBatteryOptimizationDisabled(); - handler.post(() -> result.success(isDisabled)); - break; default: handler.post(() -> result.notImplemented()); } @@ -73,21 +73,20 @@ public class MainActivity extends FlutterFragmentActivity { } } - private void disableBatteryOptimization() { - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + packageName)); - startActivity(intent); - } - } + private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + DomainResolution resolution = new Resolution(); + Handler handler = new Handler(Looper.getMainLooper()); + String domain = call.argument("domain"); + String ticker = call.argument("ticker"); - private boolean isBatteryOptimizationDisabled() { - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return pm.isIgnoringBatteryOptimizations(packageName); + AsyncTask.execute(() -> { + try { + String address = resolution.getAddress(domain, ticker); + handler.post(() -> result.success(address)); + } catch (Exception e) { + System.out.println("Expected Address, but got " + e.getMessage()); + handler.post(() -> result.success("")); + } + }); } - } \ No newline at end of file diff --git a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java index 83a790683..8c13d1f8d 100644 --- a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java @@ -14,15 +14,15 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.view.WindowManager; -import android.content.Intent; -import android.net.Uri; -import android.os.PowerManager; -import android.provider.Settings; + +import com.unstoppabledomains.resolution.DomainResolution; +import com.unstoppabledomains.resolution.Resolution; import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; + final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { @@ -47,13 +47,13 @@ public class MainActivity extends FlutterFragmentActivity { random.nextBytes(bytes); handler.post(() -> result.success(bytes)); break; - case "disableBatteryOptimization": - disableBatteryOptimization(); - handler.post(() -> result.success(null)); - break; - case "isBatteryOptimizationDisabled": - boolean isDisabled = isBatteryOptimizationDisabled(); - handler.post(() -> result.success(isDisabled)); + case "getUnstoppableDomainAddress": + int version = Build.VERSION.SDK_INT; + if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { + getUnstoppableDomainAddress(call, result); + } else { + handler.post(() -> result.success("")); + } break; default: handler.post(() -> result.notImplemented()); @@ -63,21 +63,20 @@ public class MainActivity extends FlutterFragmentActivity { } } - private void disableBatteryOptimization() { - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + packageName)); - startActivity(intent); - } - } + private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + DomainResolution resolution = new Resolution(); + Handler handler = new Handler(Looper.getMainLooper()); + String domain = call.argument("domain"); + String ticker = call.argument("ticker"); - private boolean isBatteryOptimizationDisabled() { - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return pm.isIgnoringBatteryOptimizations(packageName); + AsyncTask.execute(() -> { + try { + String address = resolution.getAddress(domain, ticker); + handler.post(() -> result.success(address)); + } catch (Exception e) { + System.out.println("Expected Address, but got " + e.getMessage()); + handler.post(() -> result.success("")); + } + }); } - } \ No newline at end of file diff --git a/android/app/src/main/java/com/monero/app/MainActivity.java b/android/app/src/main/java/com/monero/app/MainActivity.java index e6306d27b..73914c43c 100644 --- a/android/app/src/main/java/com/monero/app/MainActivity.java +++ b/android/app/src/main/java/com/monero/app/MainActivity.java @@ -14,15 +14,15 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.view.WindowManager; -import android.content.Intent; -import android.net.Uri; -import android.os.PowerManager; -import android.provider.Settings; + +import com.unstoppabledomains.resolution.DomainResolution; +import com.unstoppabledomains.resolution.Resolution; import java.security.SecureRandom; public class MainActivity extends FlutterFragmentActivity { final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; + final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; boolean isAppSecure = false; @Override @@ -48,6 +48,14 @@ public class MainActivity extends FlutterFragmentActivity { random.nextBytes(bytes); handler.post(() -> result.success(bytes)); break; + case "getUnstoppableDomainAddress": + int version = Build.VERSION.SDK_INT; + if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { + getUnstoppableDomainAddress(call, result); + } else { + handler.post(() -> result.success("")); + } + break; case "setIsAppSecure": isAppSecure = call.argument("isAppSecure"); if (isAppSecure) { @@ -56,14 +64,6 @@ public class MainActivity extends FlutterFragmentActivity { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } break; - case "disableBatteryOptimization": - disableBatteryOptimization(); - handler.post(() -> result.success(null)); - break; - case "isBatteryOptimizationDisabled": - boolean isDisabled = isBatteryOptimizationDisabled(); - handler.post(() -> result.success(isDisabled)); - break; default: handler.post(() -> result.notImplemented()); } @@ -72,21 +72,20 @@ public class MainActivity extends FlutterFragmentActivity { } } - private void disableBatteryOptimization() { - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - if (!pm.isIgnoringBatteryOptimizations(packageName)) { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + packageName)); - startActivity(intent); - } - } + private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + DomainResolution resolution = new Resolution(); + Handler handler = new Handler(Looper.getMainLooper()); + String domain = call.argument("domain"); + String ticker = call.argument("ticker"); - private boolean isBatteryOptimizationDisabled() { - String packageName = getPackageName(); - PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return pm.isIgnoringBatteryOptimizations(packageName); + AsyncTask.execute(() -> { + try { + String address = resolution.getAddress(domain, ticker); + handler.post(() -> result.success(address)); + } catch (Exception e) { + System.out.println("Expected Address, but got " + e.getMessage()); + handler.post(() -> result.success("")); + } + }); } - } \ No newline at end of file diff --git a/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so b/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so deleted file mode 120000 index 6cdcd70a2..000000000 --- a/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/monero/aarch64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/arm64-v8a/libwownero_libwallet2_api_c.so b/android/app/src/main/jniLibs/arm64-v8a/libwownero_libwallet2_api_c.so deleted file mode 120000 index 8f6150ee3..000000000 --- a/android/app/src/main/jniLibs/arm64-v8a/libwownero_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/wownero/aarch64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so b/android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so deleted file mode 120000 index 49ddd0f47..000000000 --- a/android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/zano/aarch64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so b/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so deleted file mode 120000 index c0d56dd3b..000000000 --- a/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/monero/armv7a-linux-androideabi_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libwownero_libwallet2_api_c.so b/android/app/src/main/jniLibs/armeabi-v7a/libwownero_libwallet2_api_c.so deleted file mode 120000 index 5a71e87b1..000000000 --- a/android/app/src/main/jniLibs/armeabi-v7a/libwownero_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/wownero/armv7a-linux-androideabi_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so b/android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so deleted file mode 120000 index 43f9b98b2..000000000 --- a/android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/zano/armv7a-linux-androideabi_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/x86_64/.gitkeep b/android/app/src/main/jniLibs/x86_64/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so b/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so deleted file mode 120000 index 654be50b9..000000000 --- a/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/monero/x86_64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/x86_64/libwownero_libwallet2_api_c.so b/android/app/src/main/jniLibs/x86_64/libwownero_libwallet2_api_c.so deleted file mode 120000 index bb3da908f..000000000 --- a/android/app/src/main/jniLibs/x86_64/libwownero_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/wownero/x86_64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so b/android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so deleted file mode 120000 index 8c37d73c2..000000000 --- a/android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so +++ /dev/null @@ -1 +0,0 @@ -../../../../../../scripts/monero_c/release/zano/x86_64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_launcher_adaptive_back.xml b/android/app/src/main/res/drawable/ic_launcher_adaptive_back.xml deleted file mode 100644 index ca3826a46..000000000 --- a/android/app/src/main/res/drawable/ic_launcher_adaptive_back.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index f880684a6..dc767a55d 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,4 +1,5 @@ - + diff --git a/android/build.gradle b/android/build.gradle index d42aa24b4..bd4ebd770 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,8 +1,21 @@ +buildscript { + ext.kotlin_version = '1.6.21' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.google.gms:google-services:4.3.8' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + allprojects { repositories { google() - mavenCentral() - maven { url "https://jitpack.io" } + jcenter() } } diff --git a/android/gradle.properties b/android/gradle.properties index d130e538a..38c8d4544 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx4096M +org.gradle.jvmargs=-Xmx1536M android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 0cc4e42b9..733c691d3 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 468f9b55f..5a2f14fb1 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,26 +1,15 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() +include ':app' - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - repositories { - google() - mavenCentral() - gradlePluginPortal() - } +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.1" apply false - id "org.jetbrains.kotlin.android" version "2.0.21" apply false - id "com.google.gms.google-services" version "4.3.8" apply false +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory } - -include ":app" \ No newline at end of file diff --git a/assets/banano_node_list.yml b/assets/banano_node_list.yml deleted file mode 100644 index dc6852867..000000000 --- a/assets/banano_node_list.yml +++ /dev/null @@ -1,5 +0,0 @@ -- - uri: kaliumapi.appditto.com - path: /api - useSSL: true - is_default: true \ No newline at end of file diff --git a/assets/bitcoin_cash_electrum_server_list.yml b/assets/bitcoin_cash_electrum_server_list.yml index 948e5f3dc..d76668169 100644 --- a/assets/bitcoin_cash_electrum_server_list.yml +++ b/assets/bitcoin_cash_electrum_server_list.yml @@ -1,10 +1,3 @@ - uri: bitcoincash.stackwallet.com:50002 - is_default: true - useSSL: true -- - uri: bch.aftrek.org:50002 - useSSL: true -- - uri: node.minisatoshi.cash:50002 - useSSL: true + is_default: true \ No newline at end of file diff --git a/assets/bitcoin_electrum_server_list.yml b/assets/bitcoin_electrum_server_list.yml index 83da6a0b2..2b6649271 100644 --- a/assets/bitcoin_electrum_server_list.yml +++ b/assets/bitcoin_electrum_server_list.yml @@ -1,9 +1,2 @@ - - uri: btc-electrum.cakewallet.com:50002 - useSSL: true - isDefault: true -- - uri: electrs.cakewallet.com:50001 -- - uri: fulcrum.sethforprivacy.com:50002 - useSSL: true + uri: electrum.cakewallet.com:50002 \ No newline at end of file diff --git a/assets/decred_node_list.yml b/assets/decred_node_list.yml deleted file mode 100644 index cb171e701..000000000 --- a/assets/decred_node_list.yml +++ /dev/null @@ -1,6 +0,0 @@ -- - uri: default-spv-nodes - is_default: true -- - uri: dcrd.sethforprivacy.com:9108 - useSSL: true \ No newline at end of file diff --git a/assets/ethereum_server_list.yml b/assets/ethereum_server_list.yml index ed425c3c7..125085d88 100644 --- a/assets/ethereum_server_list.yml +++ b/assets/ethereum_server_list.yml @@ -1,14 +1,10 @@ - - uri: ethereum-rpc.publicnode.com - useSSL: true - isDefault: true + uri: ethereum.publicnode.com - uri: eth.llamarpc.com - uri: rpc.flashbots.net - uri: eth-mainnet.public.blastapi.io -- - uri: eth.nownodes.io - uri: ethereum.publicnode.com \ No newline at end of file diff --git a/assets/faq/faq_de.json b/assets/faq/faq_de.json index 14d703e4f..be9b567b7 100644 --- a/assets/faq/faq_de.json +++ b/assets/faq/faq_de.json @@ -1,7 +1,7 @@ [ { "question" : "Was ist der Unterschied zwischen verfügbarem Guthaben und vollständigem Guthaben?", - "answer" : "Nachdem Sie eine Transaktion getätigt oder Monero erhalten haben, muss die Transaktion noch bestätigt werden. In ungefähr 20 Minuten sollte Ihr \"verfügbares Guthaben\" aktualisiert werden!\nWenn Sie Monero senden, verringert sich manchmal Ihr verfügbares Guthaben um mehr als den Betrag, den Sie gesendet haben. Dies ist normal und zum Schutz Ihrer Privatsphäre erforderlich. Ihr \"vollständiges Guthaben\" sollte in 20 Minuten wieder normal sein.\n" + "answer" : "Nachdem Sie eine Transaktion getätigt oder Monero erhalten haben, muss die Transaktion noch bestätigt werden. In ungefähr 20 Minuten sollte Ihr \"verfügbares Guthaben\" aktualisiert werden!\nWenn Sie Monero senden, verringert sich manchmal Ihr verfügbares Guthaben um mehr als den Betrag, den Sie gesendet haben. Dies ist normal und zum Schutz Ihrer Privatsphäre erforderlich. Ihr \"vollständiges Gleichgewicht\" sollte in 20 Minuten wieder normal sein.\n" }, { "question" : "Wie sende ich Monero an eine Börse, für die eine Zahlungs-ID erforderlich ist?", @@ -12,24 +12,24 @@ "answer" : "Obwohl unser Support Sie bei diesem Problem nicht direkt unterstützen kann, ist es ein sehr häufiges Problem, mit dem die meisten Börsen vertraut sind. Wenden Sie sich einfach an den Support der Börse, erklären Sie, dass Sie vergessen haben, Ihre Zahlungs-ID anzugeben, und senden Sie ihnen dann Ihre Transaktions-ID als Nachweis. Sie finden die Transaktions-ID, indem Sie auf die Transaktion in Ihrem Wallet-Bildschirm tippen.\n" }, { - "question" : "Was bedeuten \"Seed\" und \"Schlüssel\"?", - "answer" : "Ihre Schlüssel verschlüsseln die privaten Informationen in Ihrer Brieftasche und ermöglichen es Ihnen, Münzen auszugeben und eingehende Transaktionen anzuzeigen.\nIhr Startwert ist nur eine Version Ihres privaten Schlüssels, die so geschrieben wurde, dass Sie sie leichter notieren können. Ihr Same und Schlüssel sind tatsächlich dasselbe, nur in verschiedenen Formen!\nGeben Sie niemals Ihren Seed oder Schlüssel an jemanden weiter. Ihr Geld wird gestohlen, wenn Sie Ihren Seed oder Schlüssel herausgeben. Bitte notieren Sie sich jedoch Ihren Seed und bewahren Sie ihn an einem sicheren Ort auf (so können Sie Ihr Wallet wiederherstellen, wenn Sie Ihr Telefon verlieren.)\n" + "question" : "Was bedeuten \"Samen\" und \"Schlüssel\"?", + "answer" : "Ihre Schlüssel verschlüsseln die privaten Informationen in Ihrer Brieftasche und ermöglichen es Ihnen, Münzen auszugeben und eingehende Transaktionen anzuzeigen.\nIhr Startwert ist nur eine Version Ihres privaten Schlüssels, die so geschrieben wurde, dass Sie sie leichter notieren können. Ihr Same und Schlüssel sind tatsächlich dasselbe, nur in verschiedenen Formen!\nGeben Sie niemals Ihren Samen oder Schlüssel an jemanden weiter. Ihr Geld wird gestohlen, wenn Sie Ihren Samen oder Schlüssel herausgeben. Bitte notieren Sie sich jedoch Ihren Samen und bewahren Sie ihn an einem sicheren Ort auf (so können Sie Ihre Brieftasche wiederherstellen, wenn Sie Ihr Telefon verlieren.)\n" }, { "question" : "Wie viele Geldbörsen kann ich erstellen?", "answer" : "Es gibt keine Grenzen! Sie können so viele Brieftaschen erstellen, wie Sie möchten.\n" }, { - "question" : "Wie kann ich mein Wallet wiederherstellen?", - "answer" : "Tippen Sie auf das Menü •••, wählen Sie „Wallest“ und dann „Wallet wiederherstellen“. Geben Sie dann Ihren Seed (oder Ihre Schlüssel) und optional ein Datum vor der ersten Transaktion in Ihrem Wallet ein (dies beschleunigt den Synchronisierungsvorgang) .) Möglicherweise müssen Sie die App 15 bis 30 Minuten geöffnet lassen, um Ihr Wallet vollständig wiederherzustellen.\n" + "question" : "Wie kann ich meine Brieftasche wiederherstellen?", + "answer" : "Tippen Sie auf das Menü •••, wählen Sie „Brieftaschen“ und dann „Brieftasche wiederherstellen“. Geben Sie dann Ihren Startwert (oder Ihre Schlüssel) und optional ein Datum vor der ersten Transaktion in Ihrer Brieftasche ein (dies beschleunigt den Synchronisierungsvorgang) .) Möglicherweise müssen Sie die App 15 bis 30 Minuten geöffnet lassen, um Ihr Portemonnaie vollständig wiederherzustellen.\n" }, { - "question" : "Was kann ich tun, wenn ich meinen Seed verliere?", - "answer" : "Wenn Sie Ihren Seed vergessen haben, haben Sie ihn wahrscheinlich irgendwo aufgeschrieben. Bitte überprüfen Sie Ihre Notizen und schauen Sie sich auf Ihrem Computer um. Wenn Sie es nirgendwo finden, haben Sie möglicherweise Cake Wallet gesichert (in diesem Fall können Sie es aus diesem Backup wiederherstellen.) Wenn keines dieser Probleme auftritt, können wir leider nichts tun.\n" + "question" : "Was kann ich tun, wenn ich meinen Samen verliere?", + "answer" : "Wenn Sie Ihren Samen vergessen haben, haben Sie ihn wahrscheinlich irgendwo aufgeschrieben. Bitte überprüfen Sie Ihre Notizen und schauen Sie sich auf Ihrem Computer um. Wenn Sie es nirgendwo finden, haben Sie möglicherweise Cake Wallet gesichert (in diesem Fall können Sie es aus diesem Backup wiederherstellen.) Wenn keines dieser Probleme auftritt, können wir leider nichts tun.\n" }, { - "question" : "Sammeln Sie Informationen zu meinem Wallet?", - "answer" : "Cake Wallet sammelt oder zeichnet keine Informationen über Ihr Wallet auf. Ihre Privatsphäre ist uns wichtig.\n" + "question" : "Sammeln Sie Informationen zu meiner Brieftasche?", + "answer" : "Cake Wallet sammelt oder zeichnet keine Informationen über Ihre Brieftasche auf. Ihre Privatsphäre ist uns wichtig.\n" }, { "question" : "Kann ich eine Transaktion stornieren?", @@ -37,7 +37,7 @@ }, { "question" : "Was sind Subadressen und wie verwende ich sie?", - "answer" : "Eine Unteradresse ist im Grunde eine eindeutige Adresse, die Sie jederzeit generieren können. An sie gesendete Münzen landen weiterhin in Ihrer Hauptwallet, aber die Person, die die Coins sendet, kann Ihre Hauptadresse nicht ermitteln. Unteradressen beginnen immer mit „8“.\nSie können eine neue Unteradresse im Empfangsbildschirm erstellen, indem Sie auf das „+“ neben der Schaltfläche Unteradressen tippen. Geben Sie einen Namen für die Unteradresse ein und tippen Sie auf \"Hinzufügen\". Dann tippen Sie einfach auf den Namen der Subadresse, wenn Sie ihn verwenden möchten!\nWenn Sie paranoid sind, sollten Sie wahrscheinlich jedes Mal, wenn Sie Monero erhalten, eine neue Unteradresse erstellen.\n" + "answer" : "Eine Unteradresse ist im Grunde eine eindeutige Adresse, die Sie jederzeit generieren können. An sie gesendete Münzen landen weiterhin in Ihrer Hauptbrieftasche, aber die Person, die die Münzen sendet, kann Ihre Hauptadresse nicht ermitteln. Unteradressen beginnen immer mit „8“.\nSie können eine neue Unteradresse im Empfangsbildschirm erstellen, indem Sie auf das „+“ neben der Schaltfläche Unteradressen tippen. Geben Sie einen Namen für die Unteradresse ein und tippen Sie auf \"Hinzufügen\". Dann tippen Sie einfach auf den Namen der Subadresse, wenn Sie ihn verwenden möchten!\nWenn Sie paranoid sind, sollten Sie wahrscheinlich jedes Mal, wenn Sie Monero erhalten, eine neue Unteradresse erstellen.\n" }, { "question" : "Was ist eine Transaktions-ID?", @@ -48,11 +48,11 @@ "answer" : "Wenn Sie Ihren Monero nicht erhalten haben, möchten Sie möglicherweise auf das Menü ••• tippen und auf Reconnect (Neu verbinden) klicken. Wenn dies nicht funktioniert, gehen Sie in das Einstellungsmenü, tippen Sie auf das Feld \"Aktueller Knoten\" und wählen Sie einen Knoten mit einem grünen Punkt daneben aus.\n" }, { - "question" : "Ich habe in der App keine Coins aus dem Umtausch erhalten. Was kann ich tun?", - "answer" : "Wenn Sie Probleme mit einem Austausch haben, besteht die beste Option, den Austausch selbst zu kontaktieren. Wir haben uns mit Chechenow, Simpleswap, Sideshift und Trocador zusammengetan. Am besten wechseln Sie zu https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ und kontaktieren Sie ihre Unterstützung.\n" + "question" : "Ich habe in der App keine Münzen aus dem Umtausch erhalten. Was kann ich tun?", + "answer" : "Wenn Sie Probleme mit einem Austausch haben, wenden Sie sich am besten an den Austausch. Wir sind eine Partnerschaft mit XMR.TO, Morph und ChangeNow eingegangen. Rufen Sie daher am besten http://xmr.to, http://changenow.io oder http://morphtoken.com auf und wenden Sie sich an deren Support.\n" }, { "question" : "Wie kontaktiere ich den Cake Wallet-Support?", "answer" : "Senden Sie eine E-Mail an support@cakewallet.com, schließen Sie sich dem Telegramm unter @cakewallet_bot an oder twittern Sie @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_en.json b/assets/faq/faq_en.json index 741d31798..aae40802c 100644 --- a/assets/faq/faq_en.json +++ b/assets/faq/faq_en.json @@ -49,10 +49,10 @@ }, { "question" : "I didn't receive my coins from the exchange in the app. What can I do?", - "answer" : "If you're having issues with an exchange, the best option is to contact the exchange itself. We're partnered with ChangeNow, SimpleSwap, SideShift and Trocador. So your best bet is to go to https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ and contact their support.\n" + "answer" : "If you're having issues with an exchange, the best option is to contact the exchange itself. We're partnered with XMR.TO, Morph and ChangeNow, so your best bet is to go to http://xmr.to, http://changenow.io, or http://morphtoken.com and contact their support.\n" }, { "question" : "How do I contact Cake Wallet support?", "answer" : "Email support@cakewallet.com, join the Telegram at @cakewallet_bot, or tweet @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_es.json b/assets/faq/faq_es.json index 968853a0b..28074662c 100644 --- a/assets/faq/faq_es.json +++ b/assets/faq/faq_es.json @@ -49,10 +49,10 @@ }, { "question" : "No recibí mis monedas del intercambio en la aplicación. ¿Que puedo hacer?", - "answer" : "Si tiene problemas con un intercambio, la mejor opción es comunicarse con el intercambio en sí. Estamos asociados con ChangeNow, SimpleSwap, SideShift y Trocador. Entonces, su mejor opción es ir a https://changenow.io, https://simplewap.io/, https://sideshift.ai/, https://trocador.app/ y contactar su soporte.\n" + "answer" : "Si tiene problemas con un intercambio, la mejor opción es ponerse en contacto con el intercambio en sí. Estamos asociados con XMR.TO, Morph y ChangeNow, por lo que su mejor opción es ir a http://xmr.to, http://changenow.io o http://morphtoken.com y contactar a su soporte.\n" }, { "question" : "¿Cómo contacto al soporte de Cake Wallet?", "answer" : "¡Envíe un correo electrónico a support@cakewallet.com, únase al Telegram en @cakewallet_bot o envíe un tweet a @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_fr.json b/assets/faq/faq_fr.json index 6b6ac5227..cc4e52873 100644 --- a/assets/faq/faq_fr.json +++ b/assets/faq/faq_fr.json @@ -50,7 +50,7 @@ }, { "question" : "Je n'ai pas reçu mes fonds en provenance de la plateforme d'échange dans l'application. Que puis-je faire ?", - "answer" : "Si vous rencontrez des problèmes avec un échange, la meilleure option est de contacter l'échange lui-même. Nous sommes en partenariat avec Changenow, Simpleswap, Sideshift et le Trocador. Donc, votre meilleur pari est d'aller sur https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ et contactez leur support.\n" + "answer" : "Si vous avez des soucis avec une plateforme d'échange, le mieux est de contacter la plateforme d'échange directement. Nous avons des partenariats avec XMR.TO, Morph et ChangeNow, donc essayez http://xmr.to, http://changenow.io, ou http://morphtoken.com et contactez leur support.\n" }, { "question" : "Comment puis-je contacter le support de Cake Wallet ?", diff --git a/assets/faq/faq_hi.json b/assets/faq/faq_hi.json index f1ede3374..cd9eb3fb9 100644 --- a/assets/faq/faq_hi.json +++ b/assets/faq/faq_hi.json @@ -49,10 +49,10 @@ }, { "question" : "मुझे ऐप में एक्सचेंज से मेरे सिक्के नहीं मिले। मैं क्या कर सकता हूँ?", - "answer" : "यदि आप एक एक्सचेंज के साथ समस्याएं कर रहे हैं, तो सबसे अच्छा विकल्प एक्सचेंज से संपर्क करना है। हम चंगेनो, सिम्प्लेवैप, सिडशिफ्ट और ट्रोकैडर के साथ भागीदारी कर रहे हैं। तो आपका सबसे अच्छा दांव https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ पर जाना और उनके समर्थन से संपर्क करना है।\n" + "answer" : "यदि आप एक एक्सचेंज के साथ समस्या कर रहे हैं, तो सबसे अच्छा विकल्प एक्सचेंज से संपर्क करना है। हम XMR.TO, Morph और ChangeNow के साथ भागीदारी कर रहे हैं, इसलिए आपका सबसे अच्छा दांव http://xmr.to, http://changenow.io, या http://morphtoken.com पर जाना है और उनके समर्थन से संपर्क करना है।\n" }, { "question" : "मैं केक वॉलेट से कैसे संपर्क करूं?", "answer" : "ईमेल support@cakewallet.com, @cakewallet_bot पर टेलीग्राम में शामिल हों, या @CakeWalletXMR पर ट्वीट करें!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_ja.json b/assets/faq/faq_ja.json index 6d83e61d3..8a5b48763 100644 --- a/assets/faq/faq_ja.json +++ b/assets/faq/faq_ja.json @@ -49,10 +49,10 @@ }, { "question" : "アプリの取引所からコインを受け取りませんでした。 私に何ができる?", - "answer" : "交換に問題がある場合、最良の選択肢は、交換自体に連絡することです。 Changenow、SimpleSwap、Sideshift、Trocadorと提携しています。したがって、あなたの最善の策は、https://changenow.io、https://simpleswap.io/、https://sideshift.ai/、https://trocador.app/に行くことです。\n" + "answer" : "取引所に問題がある場合、最良の選択肢は取引所自体に連絡することです。 XMR.TO、Morph、ChangeNowと提携しているため、最善の策はhttp://xmr.to、http://changenow.io、またはhttp://morphtoken.comにアクセスしてサポートに連絡することです。\n" }, { "question" : "Cake Walletサポートに連絡するにはどうすればよいですか?", "answer" : "support@cakewallet.comにメールを送信するか、@cakewallet_botで電報に参加するか、@CakeWalletXMRにツイートしてください。\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_ko.json b/assets/faq/faq_ko.json index 640567284..7d6f36589 100644 --- a/assets/faq/faq_ko.json +++ b/assets/faq/faq_ko.json @@ -49,10 +49,10 @@ }, { "question" : "앱의 거래소에서 동전을받지 못했습니다. 내가 무엇을 할 수 있을지?", - "answer" : "교환에 문제가있는 경우 가장 좋은 선택은 Exchange 자체에 연락하는 것입니다. 우리는 Changenow, Simpleswap, Sideshift 및 Trocador와 파트너 관계를 맺고 있습니다. 따라서 가장 좋은 방법은 https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/로 이동하여 지원에 연락하는 것입니다.\n" + "answer" : "교환에 문제가있는 경우 교환기에 연락하는 것이 가장 좋습니다. 우리는 XMR.TO, Morph 및 ChangeNow와 파트너 관계를 맺고 있으므로 가장 좋은 방법은 http://xmr.to, http://changenow.io 또는 http://morphtoken.com으로 이동하여 지원 부서에 문의하는 것입니다.\n" }, { "question" : "Cake Wallet 지원팀에 연락하려면 어떻게해야합니까?", "answer" : "support@cakewallet.com로 이메일을 보내거나 @cakewallet_bot에서 전보에 가입하거나 @CakeWalletXMR을 트윗하십시오!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_nl.json b/assets/faq/faq_nl.json index 47cf8fdf0..bb4f70216 100644 --- a/assets/faq/faq_nl.json +++ b/assets/faq/faq_nl.json @@ -49,10 +49,10 @@ }, { "question" : "Ik heb mijn munten niet ontvangen van de beurs in de app. Wat kan ik doen?", - "answer" : "Als u problemen heeft met een uitwisseling, is de beste optie om contact op te nemen met de uitwisseling zelf. We werken samen met ChangeNow, SimpleSwap, SideShift en Trocador. Dus het beste is om naar https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ te gaan en contact op te nemen met hun ondersteuning.\n" + "answer" : "Als u problemen ondervindt met een uitwisseling, kunt u het beste contact opnemen met de uitwisseling zelf. We werken samen met XMR.TO, Morph en ChangeNow, dus u kunt het beste naar http://xmr.to, http://changenow.io of http://morphtoken.com gaan en contact opnemen met hun ondersteuning.\n" }, { "question" : "Hoe neem ik contact op met Cake Wallet-ondersteuning?", "answer" : "E-mail support@cakewallet.com, word lid van het Telegram op @cakewallet_bot of tweet @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_pl.json b/assets/faq/faq_pl.json index b41841cd8..1934f4d1a 100644 --- a/assets/faq/faq_pl.json +++ b/assets/faq/faq_pl.json @@ -13,7 +13,7 @@ }, { "question" : "Co oznaczają słowa „seed” i „keys”?", - "answer" : "Twoje klucze i fraza seed zawierają prywatne informacje o twoim portfelu i pozwalają wysyłać kryptowalutę oraz zobaczyć przychodzące transakcje.\nFraza „seed” to wersja twojego klucza prywatnego napisana w sposób, który łatwiej Ci zapisać. Wasze frazy seed i klucze są w rzeczywistości takie same, tylko w różnych formach zapisu!\nNigdy nie dawaj nikomu swojej frazy seed ani swoich kluczy. Twoje fundusze zostaną skradzione, jeśli upublicznisz frazę seed lub klucze. Zapisz jednak swoją frazę seed i przechowuj ją w bezpiecznym miejscu (pozwoli to przywrócić portfel, jeśli zgubisz telefon).\n" + "answer" : "Twoje klucze kodują prywatne informacje w twoim portfelu i pozwalają wydać monety i zobaczyć przychodzące transakcje.\nTwoje ziarno to tylko wersja twojego klucza prywatnego napisana w sposób, który łatwiej Ci zapisać. Wasze nasiona i klucze są w rzeczywistości takie same, tylko w różnych formach!\nNigdy nie dawaj nikomu swojego ziarna ani kluczy. Twoje fundusze zostaną skradzione, jeśli wydasz swoje nasiona lub klucze. Zapisz jednak swoje ziarno i przechowuj je w bezpiecznym miejscu (pozwoli to przywrócić portfel, jeśli zgubisz telefon).\n" }, { "question" : "Ile portfeli mogę utworzyć?", @@ -24,11 +24,11 @@ "answer" : "Stuknij menu •••, wybierz „Portfele”, a następnie „Przywróć portfel”. Następnie wprowadź dane początkowe (lub klucze) i opcjonalnie wprowadź datę przed pierwszą transakcją w portfelu (przyspieszy to proces synchronizacji .) Może być konieczne pozostawienie aplikacji otwartej przez 15-30 minut, aby całkowicie przywrócić portfel.\n" }, { - "question" : "Co mogę zrobić, jeśli zapomniałem frazę seed?", - "answer" : "Jeśli zapomniałeś swoją frazę seed, prawdopodobnie gdzieś je zapisałeś. Sprawdź swoje notatki i rozejrzyj się po komputerze. Jeśli nie możesz go nigdzie znaleźć, być może utworzono kopię zapasową Cake Wallet (w takim przypadku będziesz mógł przywrócić dane z tej kopii zapasowej). Jeśli żadna z tych czynności nie działa, niestety nic nie możemy zrobić.\n" + "question" : "Co mogę zrobić, jeśli stracę nasiona?", + "answer" : "Jeśli zapomniałeś o nasieniu, prawdopodobnie gdzieś je zapisałeś. Sprawdź swoje notatki i rozejrzyj się po komputerze. Jeśli nie możesz go nigdzie znaleźć, być może utworzono kopię zapasową Cake Wallet (w takim przypadku będziesz mógł przywrócić dane z tej kopii zapasowej). Jeśli żadna z tych czynności nie działa, niestety nic nie możemy zrobić.\n" }, { - "question" : "Czy zbieracie jakieś informacje o moim portfelu?", + "question" : "Czy zbierasz jakieś informacje o moim portfelu?", "answer" : "Portfel Cake NIE gromadzi ani nie rejestruje żadnych informacji o Twoim portfelu. Dbamy o Twoją prywatność.\n" }, { @@ -37,7 +37,7 @@ }, { "question" : "Co to są „podadresy” i jak z nich korzystać?", - "answer" : "Podadres jest w unikalnym adresem, który można wygenerować w dowolnym momencie. Monety wysłane do niego nadal będą pojawiać się w głównym portfelu, ale osoba wysyłająca monety nie zna Twojego głównego adresu. Podadresy zawsze zaczynają się od „8”.\nMożesz utworzyć nowy podadres na ekranie Odbieranie, dotykając „+” obok przycisku Podadresy. Wprowadź nazwę podadresu i dotknij „Dodaj”. Następnie dotknij nazwy podadresu, gdy chcesz go użyć!\nJeśli jesteś paranoikiem, prawdopodobnie za każdym razem, gdy otrzymasz Monero, powinieneś utworzyć nowy podadres.\n" + "answer" : "Podadres jest w zasadzie unikalnym adresem, który można wygenerować w dowolnym momencie. Monety wysłane do niego nadal będą pojawiać się w głównym portfelu, ale osoba wysyłająca monety nie może podać Twojego głównego adresu. Podadresy zawsze zaczynają się od „8”.\nMożesz utworzyć nowy podadres na ekranie Odbieranie, dotykając „+” obok przycisku Podadresy. Wprowadź nazwę podadresu i dotknij „Dodaj”. Następnie dotknij nazwy podadresu, gdy chcesz go użyć!\nJeśli jesteś paranoikiem, prawdopodobnie za każdym razem, gdy otrzymasz Monero, powinieneś utworzyć nowy podadres.\n" }, { "question" : "Co to jest ID transakcji?", @@ -49,10 +49,10 @@ }, { "question" : "Nie otrzymałem moich monet z wymiany w aplikacji. Co mogę zrobić?", - "answer" : "Jeśli masz problemy z wymianą, najlepszą opcją jest skontaktowanie się z samą wymianą. Współpracujemy z Changenow, Simpleswap, Sideshift i Trocador. Więc najlepszym rozwiązaniem jest przejście na https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ i skontaktować się z ich obsługą.\n" + "answer" : "Jeśli masz problemy z wymianą, najlepszym rozwiązaniem jest skontaktowanie się z samą giełdą. Współpracujemy z XMR.TO, Morph i ChangeNow, więc najlepiej postawić się na stronie http://xmr.to, http://changenow.io lub http://morphtoken.com i skontaktować się z ich wsparciem.\n" }, { "question" : "Jak skontaktować się z obsługą Cake Wallet?", "answer" : "Wyślij e-mail na adres support@cakewallet.com, dołącz do telegramu na @cakewallet_bot lub tweet @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_pt.json b/assets/faq/faq_pt.json index 66d3a6aaf..06ddab25e 100644 --- a/assets/faq/faq_pt.json +++ b/assets/faq/faq_pt.json @@ -49,10 +49,10 @@ }, { "question" : "Não recebi minhas moedas da troca no aplicativo. O que eu posso fazer?", - "answer" : "Se você estiver com problemas com uma troca, a melhor opção é entrar em contato com a própria troca. Estamos em parceria com ChangeNow, SimpleSwap, Sideshift e Trocador. Portanto, sua melhor aposta é ir para https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ e entre em contato com seu suporte.\n" + "answer" : "Se você estiver tendo problemas com uma troca, a melhor opção é entrar em contato com a troca. Somos parceiros do XMR.TO, Morph e ChangeNow, portanto, sua melhor aposta é ir para http://xmr.to, http://changenow.io ou http://morphtoken.com e entrar em contato com o suporte deles.\n" }, { "question" : "Como entro em contato com o suporte da Cake Wallet?", "answer" : "Envie um e-mail para support@cakewallet.com, participe do Telegram em @cakewallet_bot ou envie um tweet para @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_ru.json b/assets/faq/faq_ru.json index af5ba32a6..4b8b18e32 100644 --- a/assets/faq/faq_ru.json +++ b/assets/faq/faq_ru.json @@ -49,10 +49,10 @@ }, { "question" : "Я не получил свои монеты после обмена в приложении. Что я могу сделать?", - "answer" : "Если у вас есть проблемы с обменом, лучший вариант - связаться с самой биржей. Мы сотрудничаем с Changenow, Simpleswap, SideShift и Trocador. Так что лучше всего пойти по адресу https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ и свяжитесь с их поддержкой.\n" + "answer" : "Если у вас возникли проблемы с обменом, лучше всего связаться с провайдером обмена. Мы сотрудничаем с XMR.TO, Morph и ChangeNow, поэтому вам лучше всего зайти на http://xmr.to, http://changenow.io или http://morphtoken.com и связаться с их поддержкой.\n" }, { "question" : "Как мне связаться со службой поддержки Cake Wallet?", "answer" : "По электронной почте support@cakewallet.com, присоединитесь к Telegram по адресу @cakewallet_bot или отправьте твит @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_uk.json b/assets/faq/faq_uk.json index 3e209d2ad..c481e0538 100644 --- a/assets/faq/faq_uk.json +++ b/assets/faq/faq_uk.json @@ -49,10 +49,10 @@ }, { "question" : "Я не отримав свої монети після обміну в додатку. Що я можу зробити?", - "answer" : "Якщо у вас є проблеми з обміном, найкращим варіантом є зв’язок із самою біржею. Ми співпрацюємо з Changenow, Simplewap, Sideshift та Trocador. Тож найкраща ставка - перейти на https://changenow.io, https://simpleswap.io/, https://sideshift.ai/, https://trocador.app/ та звернутися до їх підтримки.\n" + "answer" : "Якщо у вас виникли проблеми з обміном, найкраще зв'язатися з провайдером обміну. Ми співпрацюємо з XMR.TO, Morph і ChangeNow, тому вам найкраще зайти на http://xmr.to, http://changenow.io або http://morphtoken.com і зв'язатися з їх підтримкою.\n" }, { "question" : "Як мені зв'язатися зі службою підтримки Cake Wallet?", "answer" : "По електронній пошті support@cakewallet.com, приєднайтеся до Telegram за адресою @cakewallet_bot або надішліть твіт @CakeWalletXMR!\n" } -] +] \ No newline at end of file diff --git a/assets/faq/faq_zh.json b/assets/faq/faq_zh.json index 8debe1873..22977f22c 100644 --- a/assets/faq/faq_zh.json +++ b/assets/faq/faq_zh.json @@ -49,10 +49,10 @@ }, { "question" : "我没有从应用程序中的交易所收到硬币。 我能做什么?", - "answer" : "如果您对交易所有问题,最好的选择是与交易所本身联系。我们与ChangeNow,SimplesWap,SideShift和Trocador合作。因此,最好的选择是访问https://changenow.io,https://simpleswap.io/,https://sideshift.ai/,https://trocador.app/并联系他们的支持。\n" + "answer" : "如果您对交易所有疑问,最好的选择是与交易所本身联系。 我们与XMR.TO,Morph和ChangeNow合作,因此最好的选择是访问http://xmr.to、http://changenow.io或http://morphtoken.com,并与他们的支持部门联系。\n" }, { "question" : "如何联系蛋糕钱包支持?", "answer" : "电子邮件support@cakewallet.com,通过@cakewallet_bot加入电报,或在@CakeWalletXMR上发布推文!\n" } -] +] \ No newline at end of file diff --git a/assets/images/2.0x/decred.png b/assets/images/2.0x/decred.png deleted file mode 100644 index 2f4919cec..000000000 Binary files a/assets/images/2.0x/decred.png and /dev/null differ diff --git a/assets/images/2.0x/decred_menu.png b/assets/images/2.0x/decred_menu.png deleted file mode 100644 index 4a41efef1..000000000 Binary files a/assets/images/2.0x/decred_menu.png and /dev/null differ diff --git a/assets/images/2fa.png b/assets/images/2fa.png deleted file mode 100644 index 36c99beab..000000000 Binary files a/assets/images/2fa.png and /dev/null differ diff --git a/assets/images/2fa_warning_dark.svg b/assets/images/2fa_warning_dark.svg deleted file mode 100644 index c9fcad341..000000000 --- a/assets/images/2fa_warning_dark.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/2fa_warning_light.svg b/assets/images/2fa_warning_light.svg deleted file mode 100644 index 087d8e99b..000000000 --- a/assets/images/2fa_warning_light.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/3.0x/decred.png b/assets/images/3.0x/decred.png deleted file mode 100644 index b2c9ac818..000000000 Binary files a/assets/images/3.0x/decred.png and /dev/null differ diff --git a/assets/images/3.0x/decred_menu.png b/assets/images/3.0x/decred_menu.png deleted file mode 100644 index e55b3fb5c..000000000 Binary files a/assets/images/3.0x/decred_menu.png and /dev/null differ diff --git a/assets/images/apple_pay_logo.png b/assets/images/apple_pay_logo.png deleted file mode 100644 index 346007e3b..000000000 Binary files a/assets/images/apple_pay_logo.png and /dev/null differ diff --git a/assets/images/apple_pay_round_dark.svg b/assets/images/apple_pay_round_dark.svg deleted file mode 100644 index 82443bfb4..000000000 --- a/assets/images/apple_pay_round_dark.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/assets/images/apple_pay_round_light.svg b/assets/images/apple_pay_round_light.svg deleted file mode 100644 index 2beb1248f..000000000 --- a/assets/images/apple_pay_round_light.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/assets/images/avdo_icon.png b/assets/images/avdo_icon.png deleted file mode 100644 index c02e2760d..000000000 Binary files a/assets/images/avdo_icon.png and /dev/null differ diff --git a/assets/images/bank.png b/assets/images/bank.png deleted file mode 100644 index 9dc68147a..000000000 Binary files a/assets/images/bank.png and /dev/null differ diff --git a/assets/images/bank_dark.svg b/assets/images/bank_dark.svg deleted file mode 100644 index 670120796..000000000 --- a/assets/images/bank_dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/images/bank_light.svg b/assets/images/bank_light.svg deleted file mode 100644 index 804716289..000000000 --- a/assets/images/bank_light.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/images/birthday_cake.png b/assets/images/birthday_cake.png index 293cd10f6..84b084fba 100644 Binary files a/assets/images/birthday_cake.png and b/assets/images/birthday_cake.png differ diff --git a/assets/images/bluetooth.png b/assets/images/bluetooth.png deleted file mode 100644 index 8268b742c..000000000 Binary files a/assets/images/bluetooth.png and /dev/null differ diff --git a/assets/images/bonk_icon.png b/assets/images/bonk_icon.png deleted file mode 100644 index c59537eab..000000000 Binary files a/assets/images/bonk_icon.png and /dev/null differ diff --git a/assets/images/btc_lock_dark.png b/assets/images/btc_lock_dark.png deleted file mode 100644 index f5b3d7e27..000000000 Binary files a/assets/images/btc_lock_dark.png and /dev/null differ diff --git a/assets/images/btc_lock_light.png b/assets/images/btc_lock_light.png deleted file mode 100644 index 4320d96e3..000000000 Binary files a/assets/images/btc_lock_light.png and /dev/null differ diff --git a/assets/images/buy.png b/assets/images/buy.png index 32c116e6b..ff4549d5a 100644 Binary files a/assets/images/buy.png and b/assets/images/buy.png differ diff --git a/assets/images/buy_sell.png b/assets/images/buy_sell.png deleted file mode 100644 index 0fbffe56f..000000000 Binary files a/assets/images/buy_sell.png and /dev/null differ diff --git a/assets/images/cake_logo.png b/assets/images/cake_logo.png index abbb4e62b..8a85bf225 100644 Binary files a/assets/images/cake_logo.png and b/assets/images/cake_logo.png differ diff --git a/assets/images/cake_logo_dark.svg b/assets/images/cake_logo_dark.svg deleted file mode 100644 index 095077443..000000000 --- a/assets/images/cake_logo_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/cake_logo_light.svg b/assets/images/cake_logo_light.svg deleted file mode 100644 index 767e205d1..000000000 --- a/assets/images/cake_logo_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/cakewallet_android_icon.png b/assets/images/cakewallet_android_icon.png old mode 100644 new mode 100755 index 7f15c62f5..a96724f1c Binary files a/assets/images/cakewallet_android_icon.png and b/assets/images/cakewallet_android_icon.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml b/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml index 345888d26..90f958096 100644 --- a/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml +++ b/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher.png index 89c9b0571..2bcdb4427 100644 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher.png and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png new file mode 100644 index 000000000..a025d330c Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 000000000..32891557d Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_background.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_background.png deleted file mode 100644 index 19669488f..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_background.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_foreground.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 1411a5da5..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_monochrome.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_monochrome.png deleted file mode 100644 index 1411a5da5..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher.png index 8f1d1c28b..4c4131065 100644 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher.png and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png new file mode 100644 index 000000000..9f0bf4907 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 000000000..af6a1e312 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_background.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_background.png deleted file mode 100644 index 75025cfd5..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_background.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_foreground.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index e8c47adb3..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_monochrome.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_monochrome.png deleted file mode 100644 index e8c47adb3..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher.png index f775a8fac..f857a604e 100644 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher.png and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 000000000..65a3f3a1c Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 000000000..5e783f26f Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_background.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_background.png deleted file mode 100644 index 9784f16c8..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_foreground.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 6ba8eb301..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_monochrome.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 6ba8eb301..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher.png index 31458fa02..7fe35095c 100644 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher.png and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 000000000..66e3bf6b9 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 000000000..2c0ecc492 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_background.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_background.png deleted file mode 100644 index 04ef206c8..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_foreground.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index cc93d633b..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_monochrome.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index cc93d633b..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher.png index 158afbbf9..7f632717c 100644 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher.png and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_background.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png similarity index 91% rename from assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_background.png rename to assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png index 66a5487a2..9e40681b1 100644 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_background.png and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 000000000..a2104217c Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_foreground.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 0ecd56e8c..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_monochrome.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 0ecd56e8c..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/assets/images/cakewallet_app_logo.png b/assets/images/cakewallet_app_logo.png new file mode 100644 index 000000000..64682cd1d Binary files /dev/null and b/assets/images/cakewallet_app_logo.png differ diff --git a/assets/images/cakewallet_icon_1024.png b/assets/images/cakewallet_icon_1024.png index 35cf42245..64682cd1d 100644 Binary files a/assets/images/cakewallet_icon_1024.png and b/assets/images/cakewallet_icon_1024.png differ diff --git a/assets/images/cakewallet_icon_120.png b/assets/images/cakewallet_icon_120.png index 6e45f6423..1a2c1b99c 100644 Binary files a/assets/images/cakewallet_icon_120.png and b/assets/images/cakewallet_icon_120.png differ diff --git a/assets/images/cakewallet_icon_180.png b/assets/images/cakewallet_icon_180.png index da585ca42..ff69a866a 100644 Binary files a/assets/images/cakewallet_icon_180.png and b/assets/images/cakewallet_icon_180.png differ diff --git a/assets/images/cakewallet_logo.png b/assets/images/cakewallet_logo.png index c465fa26c..bf6896ad2 100644 Binary files a/assets/images/cakewallet_logo.png and b/assets/images/cakewallet_logo.png differ diff --git a/assets/images/card.svg b/assets/images/card.svg deleted file mode 100644 index 95530cdc9..000000000 --- a/assets/images/card.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/card_dark.svg b/assets/images/card_dark.svg deleted file mode 100644 index 2e5bcf986..000000000 --- a/assets/images/card_dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/assets/images/cards.png b/assets/images/cards.png deleted file mode 100644 index b263bc742..000000000 Binary files a/assets/images/cards.png and /dev/null differ diff --git a/assets/images/chainflip.png b/assets/images/chainflip.png deleted file mode 100644 index e588e6361..000000000 Binary files a/assets/images/chainflip.png and /dev/null differ diff --git a/assets/images/contact.png b/assets/images/contact.png deleted file mode 100644 index 5cf96694b..000000000 Binary files a/assets/images/contact.png and /dev/null differ diff --git a/assets/images/contact_icon.svg b/assets/images/contact_icon.svg deleted file mode 100644 index 6dbfcd5f4..000000000 --- a/assets/images/contact_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/dcr_icon.png b/assets/images/dcr_icon.png index 757cd0388..609873611 100644 Binary files a/assets/images/dcr_icon.png and b/assets/images/dcr_icon.png differ diff --git a/assets/images/decred.png b/assets/images/decred.png deleted file mode 100644 index 0b12f2ef0..000000000 Binary files a/assets/images/decred.png and /dev/null differ diff --git a/assets/images/decred_icon.png b/assets/images/decred_icon.png deleted file mode 100644 index 9391abc3d..000000000 Binary files a/assets/images/decred_icon.png and /dev/null differ diff --git a/assets/images/decred_menu.png b/assets/images/decred_menu.png deleted file mode 100644 index 5c67923c5..000000000 Binary files a/assets/images/decred_menu.png and /dev/null differ diff --git a/assets/images/deuro_icon.png b/assets/images/deuro_icon.png deleted file mode 100644 index 4dc068ff8..000000000 Binary files a/assets/images/deuro_icon.png and /dev/null differ diff --git a/assets/images/dfx_dark.png b/assets/images/dfx_dark.png deleted file mode 100644 index 6ac112eae..000000000 Binary files a/assets/images/dfx_dark.png and /dev/null differ diff --git a/assets/images/dfx_light.png b/assets/images/dfx_light.png deleted file mode 100644 index a045d3e68..000000000 Binary files a/assets/images/dfx_light.png and /dev/null differ diff --git a/assets/images/digibyte.png b/assets/images/digibyte.png deleted file mode 100644 index 0045c6852..000000000 Binary files a/assets/images/digibyte.png and /dev/null differ diff --git a/assets/images/discord.png b/assets/images/discord.png deleted file mode 100644 index 23fe37a36..000000000 Binary files a/assets/images/discord.png and /dev/null differ diff --git a/assets/images/discourse.png b/assets/images/discourse.png deleted file mode 100644 index b8bab2c5d..000000000 Binary files a/assets/images/discourse.png and /dev/null differ diff --git a/assets/images/dollar_coin.svg b/assets/images/dollar_coin.svg deleted file mode 100644 index 22218f332..000000000 --- a/assets/images/dollar_coin.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/images/flags/abw.png b/assets/images/flags/abw.png deleted file mode 100644 index d049d3a43..000000000 Binary files a/assets/images/flags/abw.png and /dev/null differ diff --git a/assets/images/flags/afg.png b/assets/images/flags/afg.png deleted file mode 100644 index f2ea25144..000000000 Binary files a/assets/images/flags/afg.png and /dev/null differ diff --git a/assets/images/flags/ago.png b/assets/images/flags/ago.png deleted file mode 100644 index b04d7dfa6..000000000 Binary files a/assets/images/flags/ago.png and /dev/null differ diff --git a/assets/images/flags/aia.png b/assets/images/flags/aia.png deleted file mode 100644 index 193f0ff41..000000000 Binary files a/assets/images/flags/aia.png and /dev/null differ diff --git a/assets/images/flags/and.png b/assets/images/flags/and.png deleted file mode 100644 index fc5d9a89b..000000000 Binary files a/assets/images/flags/and.png and /dev/null differ diff --git a/assets/images/flags/are.png b/assets/images/flags/are.png index 2df30486a..ae68c4ff2 100644 Binary files a/assets/images/flags/are.png and b/assets/images/flags/are.png differ diff --git a/assets/images/flags/arg.png b/assets/images/flags/arg.png index d87458455..c5bd233d2 100644 Binary files a/assets/images/flags/arg.png and b/assets/images/flags/arg.png differ diff --git a/assets/images/flags/arm.png b/assets/images/flags/arm.png deleted file mode 100644 index 0e4c356e2..000000000 Binary files a/assets/images/flags/arm.png and /dev/null differ diff --git a/assets/images/flags/asm.png b/assets/images/flags/asm.png deleted file mode 100644 index fd3818eda..000000000 Binary files a/assets/images/flags/asm.png and /dev/null differ diff --git a/assets/images/flags/atf.png b/assets/images/flags/atf.png deleted file mode 100644 index af77e45d5..000000000 Binary files a/assets/images/flags/atf.png and /dev/null differ diff --git a/assets/images/flags/atg.png b/assets/images/flags/atg.png deleted file mode 100644 index d9a3d9f9e..000000000 Binary files a/assets/images/flags/atg.png and /dev/null differ diff --git a/assets/images/flags/aus.png b/assets/images/flags/aus.png index 2364fa9ef..c8837731c 100644 Binary files a/assets/images/flags/aus.png and b/assets/images/flags/aus.png differ diff --git a/assets/images/flags/aut.png b/assets/images/flags/aut.png deleted file mode 100644 index 1be1ff483..000000000 Binary files a/assets/images/flags/aut.png and /dev/null differ diff --git a/assets/images/flags/aze.png b/assets/images/flags/aze.png deleted file mode 100644 index 834b1e696..000000000 Binary files a/assets/images/flags/aze.png and /dev/null differ diff --git a/assets/images/flags/bel.png b/assets/images/flags/bel.png deleted file mode 100644 index 1c06c5fa7..000000000 Binary files a/assets/images/flags/bel.png and /dev/null differ diff --git a/assets/images/flags/bes.png b/assets/images/flags/bes.png deleted file mode 100644 index b00bfb1f5..000000000 Binary files a/assets/images/flags/bes.png and /dev/null differ diff --git a/assets/images/flags/bgd.png b/assets/images/flags/bgd.png index 802233d1c..0f8c5cfe5 100644 Binary files a/assets/images/flags/bgd.png and b/assets/images/flags/bgd.png differ diff --git a/assets/images/flags/bgr.png b/assets/images/flags/bgr.png index fc53406b4..a89509f1f 100644 Binary files a/assets/images/flags/bgr.png and b/assets/images/flags/bgr.png differ diff --git a/assets/images/flags/bhr.png b/assets/images/flags/bhr.png deleted file mode 100644 index 135c254cb..000000000 Binary files a/assets/images/flags/bhr.png and /dev/null differ diff --git a/assets/images/flags/blz.png b/assets/images/flags/blz.png deleted file mode 100644 index 06b23f161..000000000 Binary files a/assets/images/flags/blz.png and /dev/null differ diff --git a/assets/images/flags/bmu.png b/assets/images/flags/bmu.png deleted file mode 100644 index 6e253a8e6..000000000 Binary files a/assets/images/flags/bmu.png and /dev/null differ diff --git a/assets/images/flags/bol.png b/assets/images/flags/bol.png deleted file mode 100644 index 4996ddbcc..000000000 Binary files a/assets/images/flags/bol.png and /dev/null differ diff --git a/assets/images/flags/bra.png b/assets/images/flags/bra.png index 6c4ba8968..ecac6f5a3 100644 Binary files a/assets/images/flags/bra.png and b/assets/images/flags/bra.png differ diff --git a/assets/images/flags/brn.png b/assets/images/flags/brn.png deleted file mode 100644 index bd1d1cc9a..000000000 Binary files a/assets/images/flags/brn.png and /dev/null differ diff --git a/assets/images/flags/btn.png b/assets/images/flags/btn.png deleted file mode 100644 index 962e5e5bb..000000000 Binary files a/assets/images/flags/btn.png and /dev/null differ diff --git a/assets/images/flags/bvt.png b/assets/images/flags/bvt.png deleted file mode 100644 index 4acde7fbf..000000000 Binary files a/assets/images/flags/bvt.png and /dev/null differ diff --git a/assets/images/flags/bwa.png b/assets/images/flags/bwa.png deleted file mode 100644 index 5b7eff92a..000000000 Binary files a/assets/images/flags/bwa.png and /dev/null differ diff --git a/assets/images/flags/cad.png b/assets/images/flags/cad.png index a96ea16f6..106cea5b9 100644 Binary files a/assets/images/flags/cad.png and b/assets/images/flags/cad.png differ diff --git a/assets/images/flags/cck.png b/assets/images/flags/cck.png deleted file mode 100644 index d255ab91a..000000000 Binary files a/assets/images/flags/cck.png and /dev/null differ diff --git a/assets/images/flags/che.png b/assets/images/flags/che.png index eab82b708..427db0fbc 100644 Binary files a/assets/images/flags/che.png and b/assets/images/flags/che.png differ diff --git a/assets/images/flags/chl.png b/assets/images/flags/chl.png index 4328f0d08..73a38f406 100644 Binary files a/assets/images/flags/chl.png and b/assets/images/flags/chl.png differ diff --git a/assets/images/flags/chn.png b/assets/images/flags/chn.png index d326f7afa..7a03dd26e 100644 Binary files a/assets/images/flags/chn.png and b/assets/images/flags/chn.png differ diff --git a/assets/images/flags/cmr.png b/assets/images/flags/cmr.png deleted file mode 100644 index 2bc6ad13c..000000000 Binary files a/assets/images/flags/cmr.png and /dev/null differ diff --git a/assets/images/flags/cok.png b/assets/images/flags/cok.png deleted file mode 100644 index 49386516d..000000000 Binary files a/assets/images/flags/cok.png and /dev/null differ diff --git a/assets/images/flags/col.png b/assets/images/flags/col.png index 798cd6374..9a0fc6ac1 100644 Binary files a/assets/images/flags/col.png and b/assets/images/flags/col.png differ diff --git a/assets/images/flags/cpv.png b/assets/images/flags/cpv.png deleted file mode 100644 index 0683d931f..000000000 Binary files a/assets/images/flags/cpv.png and /dev/null differ diff --git a/assets/images/flags/cri.png b/assets/images/flags/cri.png deleted file mode 100644 index 029bbfc49..000000000 Binary files a/assets/images/flags/cri.png and /dev/null differ diff --git a/assets/images/flags/cuw.png b/assets/images/flags/cuw.png deleted file mode 100644 index 92a36b728..000000000 Binary files a/assets/images/flags/cuw.png and /dev/null differ diff --git a/assets/images/flags/cxr.png b/assets/images/flags/cxr.png deleted file mode 100644 index e644a49ea..000000000 Binary files a/assets/images/flags/cxr.png and /dev/null differ diff --git a/assets/images/flags/cyp.png b/assets/images/flags/cyp.png deleted file mode 100644 index ba5246809..000000000 Binary files a/assets/images/flags/cyp.png and /dev/null differ diff --git a/assets/images/flags/czk.png b/assets/images/flags/czk.png index e4578e86c..a6c13a773 100644 Binary files a/assets/images/flags/czk.png and b/assets/images/flags/czk.png differ diff --git a/assets/images/flags/deu.png b/assets/images/flags/deu.png index 9bebd76f3..95b88a0ea 100644 Binary files a/assets/images/flags/deu.png and b/assets/images/flags/deu.png differ diff --git a/assets/images/flags/dji.png b/assets/images/flags/dji.png deleted file mode 100644 index 185c5322b..000000000 Binary files a/assets/images/flags/dji.png and /dev/null differ diff --git a/assets/images/flags/dma.png b/assets/images/flags/dma.png deleted file mode 100644 index 7f61af95e..000000000 Binary files a/assets/images/flags/dma.png and /dev/null differ diff --git a/assets/images/flags/dnk.png b/assets/images/flags/dnk.png index e02d42bfe..69dd1b2b8 100644 Binary files a/assets/images/flags/dnk.png and b/assets/images/flags/dnk.png differ diff --git a/assets/images/flags/dza.png b/assets/images/flags/dza.png deleted file mode 100644 index 342284223..000000000 Binary files a/assets/images/flags/dza.png and /dev/null differ diff --git a/assets/images/flags/ecu.png b/assets/images/flags/ecu.png deleted file mode 100644 index efb3acb43..000000000 Binary files a/assets/images/flags/ecu.png and /dev/null differ diff --git a/assets/images/flags/egy.png b/assets/images/flags/egy.png index 692e7de78..062ee21cf 100644 Binary files a/assets/images/flags/egy.png and b/assets/images/flags/egy.png differ diff --git a/assets/images/flags/esp.png b/assets/images/flags/esp.png index 6b12aff49..0193a6a44 100644 Binary files a/assets/images/flags/esp.png and b/assets/images/flags/esp.png differ diff --git a/assets/images/flags/est.png b/assets/images/flags/est.png deleted file mode 100644 index c2e417b93..000000000 Binary files a/assets/images/flags/est.png and /dev/null differ diff --git a/assets/images/flags/eth.png b/assets/images/flags/eth.png deleted file mode 100644 index 5b4970a67..000000000 Binary files a/assets/images/flags/eth.png and /dev/null differ diff --git a/assets/images/flags/eur.png b/assets/images/flags/eur.png index 2500968a9..1312b0200 100644 Binary files a/assets/images/flags/eur.png and b/assets/images/flags/eur.png differ diff --git a/assets/images/flags/fin.png b/assets/images/flags/fin.png deleted file mode 100644 index b6bc2f9a9..000000000 Binary files a/assets/images/flags/fin.png and /dev/null differ diff --git a/assets/images/flags/fji.png b/assets/images/flags/fji.png deleted file mode 100644 index 4700ad579..000000000 Binary files a/assets/images/flags/fji.png and /dev/null differ diff --git a/assets/images/flags/flk.png b/assets/images/flags/flk.png deleted file mode 100644 index 66ff172c2..000000000 Binary files a/assets/images/flags/flk.png and /dev/null differ diff --git a/assets/images/flags/fra.png b/assets/images/flags/fra.png index 2f432f4af..91dce8ff2 100644 Binary files a/assets/images/flags/fra.png and b/assets/images/flags/fra.png differ diff --git a/assets/images/flags/fro.png b/assets/images/flags/fro.png deleted file mode 100644 index 2c3ed5f6b..000000000 Binary files a/assets/images/flags/fro.png and /dev/null differ diff --git a/assets/images/flags/fsm.png b/assets/images/flags/fsm.png deleted file mode 100644 index b8aedd34e..000000000 Binary files a/assets/images/flags/fsm.png and /dev/null differ diff --git a/assets/images/flags/gab.png b/assets/images/flags/gab.png deleted file mode 100644 index c8e1cbd1f..000000000 Binary files a/assets/images/flags/gab.png and /dev/null differ diff --git a/assets/images/flags/gbr.png b/assets/images/flags/gbr.png index 8b53f8851..151f06db5 100644 Binary files a/assets/images/flags/gbr.png and b/assets/images/flags/gbr.png differ diff --git a/assets/images/flags/geo.png b/assets/images/flags/geo.png deleted file mode 100644 index 46c83a589..000000000 Binary files a/assets/images/flags/geo.png and /dev/null differ diff --git a/assets/images/flags/ggi.png b/assets/images/flags/ggi.png deleted file mode 100644 index fbc403f16..000000000 Binary files a/assets/images/flags/ggi.png and /dev/null differ diff --git a/assets/images/flags/ggy.png b/assets/images/flags/ggy.png deleted file mode 100644 index a882b4a59..000000000 Binary files a/assets/images/flags/ggy.png and /dev/null differ diff --git a/assets/images/flags/gha.png b/assets/images/flags/gha.png index 6e38331bb..8d6801e81 100644 Binary files a/assets/images/flags/gha.png and b/assets/images/flags/gha.png differ diff --git a/assets/images/flags/glp.png b/assets/images/flags/glp.png deleted file mode 100644 index 8bd0a69bf..000000000 Binary files a/assets/images/flags/glp.png and /dev/null differ diff --git a/assets/images/flags/gmb.png b/assets/images/flags/gmb.png deleted file mode 100644 index fa641ca1a..000000000 Binary files a/assets/images/flags/gmb.png and /dev/null differ diff --git a/assets/images/flags/grc.png b/assets/images/flags/grc.png deleted file mode 100644 index d7b37b0c7..000000000 Binary files a/assets/images/flags/grc.png and /dev/null differ diff --git a/assets/images/flags/grd.png b/assets/images/flags/grd.png deleted file mode 100644 index 7138a28d7..000000000 Binary files a/assets/images/flags/grd.png and /dev/null differ diff --git a/assets/images/flags/grl.png b/assets/images/flags/grl.png deleted file mode 100644 index 53e45988b..000000000 Binary files a/assets/images/flags/grl.png and /dev/null differ diff --git a/assets/images/flags/gtm.png b/assets/images/flags/gtm.png index 8841a352a..2083ad806 100644 Binary files a/assets/images/flags/gtm.png and b/assets/images/flags/gtm.png differ diff --git a/assets/images/flags/guf.png b/assets/images/flags/guf.png deleted file mode 100644 index 07a2d5070..000000000 Binary files a/assets/images/flags/guf.png and /dev/null differ diff --git a/assets/images/flags/gum.png b/assets/images/flags/gum.png deleted file mode 100644 index 828c5f3d9..000000000 Binary files a/assets/images/flags/gum.png and /dev/null differ diff --git a/assets/images/flags/guy.png b/assets/images/flags/guy.png deleted file mode 100644 index 5845c6db9..000000000 Binary files a/assets/images/flags/guy.png and /dev/null differ diff --git a/assets/images/flags/hau.png b/assets/images/flags/hau.png index 2bfb0a71c..7583b5daf 100644 Binary files a/assets/images/flags/hau.png and b/assets/images/flags/hau.png differ diff --git a/assets/images/flags/hkg.png b/assets/images/flags/hkg.png index 8c4cd78ae..85925604e 100644 Binary files a/assets/images/flags/hkg.png and b/assets/images/flags/hkg.png differ diff --git a/assets/images/flags/hmd.png b/assets/images/flags/hmd.png deleted file mode 100644 index 8c2931c4e..000000000 Binary files a/assets/images/flags/hmd.png and /dev/null differ diff --git a/assets/images/flags/hrv.png b/assets/images/flags/hrv.png index 43c936b1e..9c87c5d0e 100644 Binary files a/assets/images/flags/hrv.png and b/assets/images/flags/hrv.png differ diff --git a/assets/images/flags/hun.png b/assets/images/flags/hun.png index 397910aa3..9722561a8 100644 Binary files a/assets/images/flags/hun.png and b/assets/images/flags/hun.png differ diff --git a/assets/images/flags/idn.png b/assets/images/flags/idn.png index d1f01d8b5..52c965921 100644 Binary files a/assets/images/flags/idn.png and b/assets/images/flags/idn.png differ diff --git a/assets/images/flags/ind.png b/assets/images/flags/ind.png index 45d4a7109..ef721a2aa 100644 Binary files a/assets/images/flags/ind.png and b/assets/images/flags/ind.png differ diff --git a/assets/images/flags/iot.png b/assets/images/flags/iot.png deleted file mode 100644 index 2863320f5..000000000 Binary files a/assets/images/flags/iot.png and /dev/null differ diff --git a/assets/images/flags/irl.png b/assets/images/flags/irl.png deleted file mode 100644 index 2126054d3..000000000 Binary files a/assets/images/flags/irl.png and /dev/null differ diff --git a/assets/images/flags/irn.png b/assets/images/flags/irn.png index ec59e48c3..151a03919 100644 Binary files a/assets/images/flags/irn.png and b/assets/images/flags/irn.png differ diff --git a/assets/images/flags/isl.png b/assets/images/flags/isl.png index d24f29abb..ed545e905 100644 Binary files a/assets/images/flags/isl.png and b/assets/images/flags/isl.png differ diff --git a/assets/images/flags/isr.png b/assets/images/flags/isr.png index f28dff1ad..9f815dcbd 100644 Binary files a/assets/images/flags/isr.png and b/assets/images/flags/isr.png differ diff --git a/assets/images/flags/ita.png b/assets/images/flags/ita.png index 8af7190b8..768f5a181 100644 Binary files a/assets/images/flags/ita.png and b/assets/images/flags/ita.png differ diff --git a/assets/images/flags/jam.png b/assets/images/flags/jam.png deleted file mode 100644 index 97bce2de3..000000000 Binary files a/assets/images/flags/jam.png and /dev/null differ diff --git a/assets/images/flags/jey.png b/assets/images/flags/jey.png deleted file mode 100644 index e144d060e..000000000 Binary files a/assets/images/flags/jey.png and /dev/null differ diff --git a/assets/images/flags/jor.png b/assets/images/flags/jor.png deleted file mode 100644 index 6e5d2bbb8..000000000 Binary files a/assets/images/flags/jor.png and /dev/null differ diff --git a/assets/images/flags/jpn.png b/assets/images/flags/jpn.png index b5c6fb4c4..a13ef4178 100644 Binary files a/assets/images/flags/jpn.png and b/assets/images/flags/jpn.png differ diff --git a/assets/images/flags/kaz.png b/assets/images/flags/kaz.png deleted file mode 100644 index db52cf078..000000000 Binary files a/assets/images/flags/kaz.png and /dev/null differ diff --git a/assets/images/flags/ken.png b/assets/images/flags/ken.png deleted file mode 100644 index 2570c185d..000000000 Binary files a/assets/images/flags/ken.png and /dev/null differ diff --git a/assets/images/flags/kir.png b/assets/images/flags/kir.png deleted file mode 100644 index c8b69d702..000000000 Binary files a/assets/images/flags/kir.png and /dev/null differ diff --git a/assets/images/flags/kor.png b/assets/images/flags/kor.png index ccc181522..36e867ea8 100644 Binary files a/assets/images/flags/kor.png and b/assets/images/flags/kor.png differ diff --git a/assets/images/flags/kwt.png b/assets/images/flags/kwt.png deleted file mode 100644 index f21563b5f..000000000 Binary files a/assets/images/flags/kwt.png and /dev/null differ diff --git a/assets/images/flags/lbn.png b/assets/images/flags/lbn.png deleted file mode 100644 index 2a9ae1dd0..000000000 Binary files a/assets/images/flags/lbn.png and /dev/null differ diff --git a/assets/images/flags/lie.png b/assets/images/flags/lie.png deleted file mode 100644 index 0b8c77442..000000000 Binary files a/assets/images/flags/lie.png and /dev/null differ diff --git a/assets/images/flags/lka.png b/assets/images/flags/lka.png deleted file mode 100644 index 2b6492daa..000000000 Binary files a/assets/images/flags/lka.png and /dev/null differ diff --git a/assets/images/flags/ltu.png b/assets/images/flags/ltu.png deleted file mode 100644 index dec6babea..000000000 Binary files a/assets/images/flags/ltu.png and /dev/null differ diff --git a/assets/images/flags/lux.png b/assets/images/flags/lux.png deleted file mode 100644 index 9cc1c65b5..000000000 Binary files a/assets/images/flags/lux.png and /dev/null differ diff --git a/assets/images/flags/lva.png b/assets/images/flags/lva.png deleted file mode 100644 index b3312700d..000000000 Binary files a/assets/images/flags/lva.png and /dev/null differ diff --git a/assets/images/flags/mar.png b/assets/images/flags/mar.png index dda3a0131..65b31c892 100644 Binary files a/assets/images/flags/mar.png and b/assets/images/flags/mar.png differ diff --git a/assets/images/flags/mco.png b/assets/images/flags/mco.png deleted file mode 100644 index 6c12bd624..000000000 Binary files a/assets/images/flags/mco.png and /dev/null differ diff --git a/assets/images/flags/mex.png b/assets/images/flags/mex.png index f81102912..9531a3ea2 100644 Binary files a/assets/images/flags/mex.png and b/assets/images/flags/mex.png differ diff --git a/assets/images/flags/mlt.png b/assets/images/flags/mlt.png deleted file mode 100644 index 12809815f..000000000 Binary files a/assets/images/flags/mlt.png and /dev/null differ diff --git a/assets/images/flags/mmr.png b/assets/images/flags/mmr.png index c2f9fec10..7fc6e1661 100644 Binary files a/assets/images/flags/mmr.png and b/assets/images/flags/mmr.png differ diff --git a/assets/images/flags/mnp.png b/assets/images/flags/mnp.png deleted file mode 100644 index 3e6c538e4..000000000 Binary files a/assets/images/flags/mnp.png and /dev/null differ diff --git a/assets/images/flags/mrt.png b/assets/images/flags/mrt.png deleted file mode 100644 index 0fd8d757e..000000000 Binary files a/assets/images/flags/mrt.png and /dev/null differ diff --git a/assets/images/flags/msr.png b/assets/images/flags/msr.png deleted file mode 100644 index 2d2af3aef..000000000 Binary files a/assets/images/flags/msr.png and /dev/null differ diff --git a/assets/images/flags/mtq.png b/assets/images/flags/mtq.png deleted file mode 100644 index 1897c94e7..000000000 Binary files a/assets/images/flags/mtq.png and /dev/null differ diff --git a/assets/images/flags/mwi.png b/assets/images/flags/mwi.png deleted file mode 100644 index 7ddfbb17b..000000000 Binary files a/assets/images/flags/mwi.png and /dev/null differ diff --git a/assets/images/flags/mys.png b/assets/images/flags/mys.png index d151a3e4f..022476291 100644 Binary files a/assets/images/flags/mys.png and b/assets/images/flags/mys.png differ diff --git a/assets/images/flags/myt.png b/assets/images/flags/myt.png deleted file mode 100644 index c149a2a79..000000000 Binary files a/assets/images/flags/myt.png and /dev/null differ diff --git a/assets/images/flags/ner.png b/assets/images/flags/ner.png deleted file mode 100644 index 87bb8211f..000000000 Binary files a/assets/images/flags/ner.png and /dev/null differ diff --git a/assets/images/flags/nfk.png b/assets/images/flags/nfk.png deleted file mode 100644 index 6e68aee4f..000000000 Binary files a/assets/images/flags/nfk.png and /dev/null differ diff --git a/assets/images/flags/nga.png b/assets/images/flags/nga.png index 26efc073e..ebfd82449 100644 Binary files a/assets/images/flags/nga.png and b/assets/images/flags/nga.png differ diff --git a/assets/images/flags/niu.png b/assets/images/flags/niu.png deleted file mode 100644 index acb36780d..000000000 Binary files a/assets/images/flags/niu.png and /dev/null differ diff --git a/assets/images/flags/nld.png b/assets/images/flags/nld.png index 220937426..62dbc2058 100644 Binary files a/assets/images/flags/nld.png and b/assets/images/flags/nld.png differ diff --git a/assets/images/flags/nor.png b/assets/images/flags/nor.png index dfb43c5d5..bd226c0a6 100644 Binary files a/assets/images/flags/nor.png and b/assets/images/flags/nor.png differ diff --git a/assets/images/flags/nzl.png b/assets/images/flags/nzl.png index 306fdc778..11c6ade9c 100644 Binary files a/assets/images/flags/nzl.png and b/assets/images/flags/nzl.png differ diff --git a/assets/images/flags/omn.png b/assets/images/flags/omn.png deleted file mode 100644 index 6b6cd8b3b..000000000 Binary files a/assets/images/flags/omn.png and /dev/null differ diff --git a/assets/images/flags/pak.png b/assets/images/flags/pak.png index b8c966ea1..1462650e4 100644 Binary files a/assets/images/flags/pak.png and b/assets/images/flags/pak.png differ diff --git a/assets/images/flags/per.png b/assets/images/flags/per.png deleted file mode 100644 index 490a26441..000000000 Binary files a/assets/images/flags/per.png and /dev/null differ diff --git a/assets/images/flags/phl.png b/assets/images/flags/phl.png index 3c2ce7bf3..b453f3933 100644 Binary files a/assets/images/flags/phl.png and b/assets/images/flags/phl.png differ diff --git a/assets/images/flags/plw.png b/assets/images/flags/plw.png deleted file mode 100644 index 6f6ff993a..000000000 Binary files a/assets/images/flags/plw.png and /dev/null differ diff --git a/assets/images/flags/pol.png b/assets/images/flags/pol.png index 1188eccd1..30d5a9371 100644 Binary files a/assets/images/flags/pol.png and b/assets/images/flags/pol.png differ diff --git a/assets/images/flags/pri.png b/assets/images/flags/pri.png deleted file mode 100644 index cb0c54cd6..000000000 Binary files a/assets/images/flags/pri.png and /dev/null differ diff --git a/assets/images/flags/prt.png b/assets/images/flags/prt.png index 268676679..ff5a25fa9 100644 Binary files a/assets/images/flags/prt.png and b/assets/images/flags/prt.png differ diff --git a/assets/images/flags/pyf.png b/assets/images/flags/pyf.png deleted file mode 100644 index 66a5da6b8..000000000 Binary files a/assets/images/flags/pyf.png and /dev/null differ diff --git a/assets/images/flags/qat.png b/assets/images/flags/qat.png deleted file mode 100644 index 1e8461e91..000000000 Binary files a/assets/images/flags/qat.png and /dev/null differ diff --git a/assets/images/flags/rou.png b/assets/images/flags/rou.png index db1e24ca2..49b36b438 100644 Binary files a/assets/images/flags/rou.png and b/assets/images/flags/rou.png differ diff --git a/assets/images/flags/rus.png b/assets/images/flags/rus.png index 460c7b813..2633dcbd0 100644 Binary files a/assets/images/flags/rus.png and b/assets/images/flags/rus.png differ diff --git a/assets/images/flags/saf.png b/assets/images/flags/saf.png index 9accc6b5f..3b9cbded8 100644 Binary files a/assets/images/flags/saf.png and b/assets/images/flags/saf.png differ diff --git a/assets/images/flags/sau.png b/assets/images/flags/sau.png index 255dabedd..97951983a 100644 Binary files a/assets/images/flags/sau.png and b/assets/images/flags/sau.png differ diff --git a/assets/images/flags/sgp.png b/assets/images/flags/sgp.png index a677561d4..5782ea144 100644 Binary files a/assets/images/flags/sgp.png and b/assets/images/flags/sgp.png differ diff --git a/assets/images/flags/slb.png b/assets/images/flags/slb.png deleted file mode 100644 index d63061a0b..000000000 Binary files a/assets/images/flags/slb.png and /dev/null differ diff --git a/assets/images/flags/slv.png b/assets/images/flags/slv.png deleted file mode 100644 index e597e45b3..000000000 Binary files a/assets/images/flags/slv.png and /dev/null differ diff --git a/assets/images/flags/svk.png b/assets/images/flags/svk.png deleted file mode 100644 index 06bed756e..000000000 Binary files a/assets/images/flags/svk.png and /dev/null differ diff --git a/assets/images/flags/svn.png b/assets/images/flags/svn.png deleted file mode 100644 index a791163bd..000000000 Binary files a/assets/images/flags/svn.png and /dev/null differ diff --git a/assets/images/flags/swe.png b/assets/images/flags/swe.png index e5ee36d2f..ef73086f6 100644 Binary files a/assets/images/flags/swe.png and b/assets/images/flags/swe.png differ diff --git a/assets/images/flags/tha.png b/assets/images/flags/tha.png index a99cd4d48..1bdb04d00 100644 Binary files a/assets/images/flags/tha.png and b/assets/images/flags/tha.png differ diff --git a/assets/images/flags/tkm.png b/assets/images/flags/tkm.png deleted file mode 100644 index 0c3ff8755..000000000 Binary files a/assets/images/flags/tkm.png and /dev/null differ diff --git a/assets/images/flags/ton.png b/assets/images/flags/ton.png deleted file mode 100644 index 84cf20ef5..000000000 Binary files a/assets/images/flags/ton.png and /dev/null differ diff --git a/assets/images/flags/tur.png b/assets/images/flags/tur.png index e86b5a85e..166c6313a 100644 Binary files a/assets/images/flags/tur.png and b/assets/images/flags/tur.png differ diff --git a/assets/images/flags/tuv.png b/assets/images/flags/tuv.png deleted file mode 100644 index 15478f191..000000000 Binary files a/assets/images/flags/tuv.png and /dev/null differ diff --git a/assets/images/flags/twn.png b/assets/images/flags/twn.png index 34a2b37db..4af8ba78d 100644 Binary files a/assets/images/flags/twn.png and b/assets/images/flags/twn.png differ diff --git a/assets/images/flags/ukr.png b/assets/images/flags/ukr.png index c4fe57fcc..61071e338 100644 Binary files a/assets/images/flags/ukr.png and b/assets/images/flags/ukr.png differ diff --git a/assets/images/flags/ury.png b/assets/images/flags/ury.png deleted file mode 100644 index c41e2780a..000000000 Binary files a/assets/images/flags/ury.png and /dev/null differ diff --git a/assets/images/flags/usa.png b/assets/images/flags/usa.png index 30fc880b7..a8c44ce75 100644 Binary files a/assets/images/flags/usa.png and b/assets/images/flags/usa.png differ diff --git a/assets/images/flags/vat.png b/assets/images/flags/vat.png deleted file mode 100644 index d6c99cc1f..000000000 Binary files a/assets/images/flags/vat.png and /dev/null differ diff --git a/assets/images/flags/ven.png b/assets/images/flags/ven.png index c189b0545..fcc25ef2b 100644 Binary files a/assets/images/flags/ven.png and b/assets/images/flags/ven.png differ diff --git a/assets/images/flags/vir.png b/assets/images/flags/vir.png deleted file mode 100644 index a57f924b2..000000000 Binary files a/assets/images/flags/vir.png and /dev/null differ diff --git a/assets/images/flags/vnm.png b/assets/images/flags/vnm.png index d313c9912..3cbbf878f 100644 Binary files a/assets/images/flags/vnm.png and b/assets/images/flags/vnm.png differ diff --git a/assets/images/flags/vut.png b/assets/images/flags/vut.png deleted file mode 100644 index 3c4d6e429..000000000 Binary files a/assets/images/flags/vut.png and /dev/null differ diff --git a/assets/images/flip_icon.png b/assets/images/flip_icon.png deleted file mode 100644 index e588e6361..000000000 Binary files a/assets/images/flip_icon.png and /dev/null differ diff --git a/assets/images/gmt_icon.png b/assets/images/gmt_icon.png deleted file mode 100644 index 25c8a00b6..000000000 Binary files a/assets/images/gmt_icon.png and /dev/null differ diff --git a/assets/images/google_pay_icon.png b/assets/images/google_pay_icon.png deleted file mode 100644 index a3ca38311..000000000 Binary files a/assets/images/google_pay_icon.png and /dev/null differ diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png deleted file mode 100644 index fa39f241f..000000000 Binary files a/assets/images/hardware_wallet/ledger_flex.png and /dev/null differ diff --git a/assets/images/hardware_wallet/ledger_nano_s.png b/assets/images/hardware_wallet/ledger_nano_s.png deleted file mode 100644 index 02777aeb6..000000000 Binary files a/assets/images/hardware_wallet/ledger_nano_s.png and /dev/null differ diff --git a/assets/images/hardware_wallet/ledger_nano_x.png b/assets/images/hardware_wallet/ledger_nano_x.png deleted file mode 100644 index e9328a5ef..000000000 Binary files a/assets/images/hardware_wallet/ledger_nano_x.png and /dev/null differ diff --git a/assets/images/hardware_wallet/ledger_stax.png b/assets/images/hardware_wallet/ledger_stax.png deleted file mode 100644 index 06c9c848e..000000000 Binary files a/assets/images/hardware_wallet/ledger_stax.png and /dev/null differ diff --git a/assets/images/hero/cw_welcome_dark.svg b/assets/images/hero/cw_welcome_dark.svg deleted file mode 100644 index 5479cb1ee..000000000 --- a/assets/images/hero/cw_welcome_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/images/hero/cw_welcome_light.svg b/assets/images/hero/cw_welcome_light.svg deleted file mode 100644 index ece7d1f84..000000000 --- a/assets/images/hero/cw_welcome_light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/images/history.svg b/assets/images/history.svg deleted file mode 100644 index f308ab7e3..000000000 --- a/assets/images/history.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/assets/images/hnt_icon.png b/assets/images/hnt_icon.png deleted file mode 100644 index 8b64b76dd..000000000 Binary files a/assets/images/hnt_icon.png and /dev/null differ diff --git a/assets/images/home_screen_setting_icon.svg b/assets/images/home_screen_setting_icon.svg deleted file mode 100644 index 7b3aa7b4c..000000000 --- a/assets/images/home_screen_setting_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png deleted file mode 100644 index 3fd15f3ce..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x~ipad.png deleted file mode 100644 index 3fd15f3ce..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@3x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@3x.png deleted file mode 100644 index b6ff994f6..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20~ipad.png deleted file mode 100644 index 4be1a2317..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png deleted file mode 100644 index 219f2c6be..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png deleted file mode 100644 index ae94bb0ac..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x~ipad.png deleted file mode 100644 index ae94bb0ac..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@3x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@3x.png deleted file mode 100644 index 0ef9d3bbf..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png deleted file mode 100644 index 219f2c6be..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png deleted file mode 100644 index 9fdb32376..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png deleted file mode 100644 index 9fdb32376..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png deleted file mode 100644 index 485f8b37b..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png deleted file mode 100644 index 3fd15f3ce..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@2x~car.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@2x~car.png deleted file mode 100644 index 485f8b37b..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@2x~car.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png deleted file mode 100644 index 50148e6dc..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png deleted file mode 100644 index 8f290ada2..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x.png deleted file mode 100644 index 485f8b37b..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png deleted file mode 100644 index b11d7332f..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@3x.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@3x.png deleted file mode 100644 index 50148e6dc..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png deleted file mode 100644 index 1fce95553..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png and /dev/null differ diff --git a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png deleted file mode 100644 index 7d4a82186..000000000 Binary files a/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png deleted file mode 100644 index 7ea540caf..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png deleted file mode 100644 index 7ea540caf..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png deleted file mode 100644 index 6ac773754..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png deleted file mode 100644 index 57864a9b3..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-29.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-29.png deleted file mode 100644 index 27f817dfc..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-29.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x.png deleted file mode 100644 index 0455b4409..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x~ipad.png deleted file mode 100644 index 0455b4409..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-29@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-29@3x.png deleted file mode 100644 index 1b8a73481..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-29@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png deleted file mode 100644 index 27f817dfc..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x.png deleted file mode 100644 index 963612d0c..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png deleted file mode 100644 index 963612d0c..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png deleted file mode 100644 index b6da404cb..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-40~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-40~ipad.png deleted file mode 100644 index 7ea540caf..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-40~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png deleted file mode 100644 index b6da404cb..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-60@3x~car.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-60@3x~car.png deleted file mode 100644 index 37f7651a5..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-60@3x~car.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png deleted file mode 100644 index 21aa12463..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon@2x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon@2x.png deleted file mode 100644 index b6da404cb..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon@2x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon@2x~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon@2x~ipad.png deleted file mode 100644 index b6b63a61e..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon@2x~ipad.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png b/assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png deleted file mode 100644 index 37f7651a5..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png b/assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png deleted file mode 100644 index 0c977110a..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png and /dev/null differ diff --git a/assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png b/assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png deleted file mode 100644 index 849c5612a..000000000 Binary files a/assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png and /dev/null differ diff --git a/assets/images/kaspa_icon.png b/assets/images/kaspa_icon.png deleted file mode 100644 index 5201174ef..000000000 Binary files a/assets/images/kaspa_icon.png and /dev/null differ diff --git a/assets/images/kryptonim_dark.png b/assets/images/kryptonim_dark.png deleted file mode 100644 index 646d550ba..000000000 Binary files a/assets/images/kryptonim_dark.png and /dev/null differ diff --git a/assets/images/kryptonim_light.png b/assets/images/kryptonim_light.png deleted file mode 100644 index 85e64a3f2..000000000 Binary files a/assets/images/kryptonim_light.png and /dev/null differ diff --git a/assets/images/letsexchange_icon.svg b/assets/images/letsexchange_icon.svg deleted file mode 100644 index 104b43a6b..000000000 --- a/assets/images/letsexchange_icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_1024.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_1024.png deleted file mode 100644 index c465fa26c..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_1024.png and /dev/null differ diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_128.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_128.png deleted file mode 100644 index e79db4d99..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_128.png and /dev/null differ diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_16.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_16.png deleted file mode 100644 index 2d030845b..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_16.png and /dev/null differ diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_256.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_256.png deleted file mode 100644 index eef607c2c..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_256.png and /dev/null differ diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_32.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_32.png deleted file mode 100644 index a09896830..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_32.png and /dev/null differ diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_512.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_512.png deleted file mode 100644 index a98c5faf8..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_512.png and /dev/null differ diff --git a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_64.png b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_64.png deleted file mode 100644 index d8141a250..000000000 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_64.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_1024.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_1024.png deleted file mode 100644 index f3f91af95..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_1024.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_128.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_128.png deleted file mode 100644 index 48a1906e1..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_128.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_16.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_16.png deleted file mode 100644 index 5104ea7d3..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_16.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_256.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_256.png deleted file mode 100644 index 6ae8b769b..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_256.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_32.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_32.png deleted file mode 100644 index 60c30c609..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_32.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_512.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_512.png deleted file mode 100644 index 7072c07e7..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_512.png and /dev/null differ diff --git a/assets/images/macos_icons/monero_macos_icons/monero_macos_64.png b/assets/images/macos_icons/monero_macos_icons/monero_macos_64.png deleted file mode 100644 index 74aa4e2ad..000000000 Binary files a/assets/images/macos_icons/monero_macos_icons/monero_macos_64.png and /dev/null differ diff --git a/assets/images/meld_logo.svg b/assets/images/meld_logo.svg deleted file mode 100644 index 1d9211d64..000000000 --- a/assets/images/meld_logo.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/menu.svg b/assets/images/menu.svg deleted file mode 100644 index 0a4cc3784..000000000 --- a/assets/images/menu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/monero.com_android_icon.png b/assets/images/monero.com_android_icon.png index 9e1fa0a65..af47453c1 100644 Binary files a/assets/images/monero.com_android_icon.png and b/assets/images/monero.com_android_icon.png differ diff --git a/assets/images/monero.com_logo.png b/assets/images/monero.com_logo.png index 9e1fa0a65..ecc703781 100644 Binary files a/assets/images/monero.com_logo.png and b/assets/images/monero.com_logo.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml b/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml index c8bd4b26c..90f958096 100644 --- a/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml +++ b/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher.png b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher.png index 1785f4b05..583765d8f 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher.png and b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png index 5b0fde827..a025d330c 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png and b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png index 884745809..646a13ff0 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png and b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index af8126ea9..000000000 Binary files a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png and /dev/null differ diff --git a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher.png b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher.png index 12c31ef57..50e1973f5 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher.png and b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png index 5d25e42e7..9f0bf4907 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png and b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png index 28bbd57b8..2230fe0ec 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png and b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 0778b841f..000000000 Binary files a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png and /dev/null differ diff --git a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher.png b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher.png index 2d8878946..505971bbb 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher.png and b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png index c4b66dc58..65a3f3a1c 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png and b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png index 811848c27..3d414fcd3 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png and b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 318914c59..000000000 Binary files a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png and /dev/null differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher.png b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher.png index 2ceba55ad..1ad794a7b 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher.png and b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png index 75dc0219d..66e3bf6b9 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png and b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png index e078655c9..cab647d0c 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png and b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index b1165abe8..000000000 Binary files a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png and /dev/null differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher.png b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher.png index beca2d29b..43ab3ac30 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher.png and b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png index 46b1e2cb1..9e40681b1 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png and b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png index 255b1b71f..a790c3a74 100644 Binary files a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png and b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index ea11a01d3..000000000 Binary files a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png and /dev/null differ diff --git a/assets/images/moonpay.png b/assets/images/moonpay.png index 088c93d59..b02af6c00 100644 Binary files a/assets/images/moonpay.png and b/assets/images/moonpay.png differ diff --git a/assets/images/moonpay_dark.png b/assets/images/moonpay_dark.png deleted file mode 100644 index 21de98eb4..000000000 Binary files a/assets/images/moonpay_dark.png and /dev/null differ diff --git a/assets/images/moonpay_light.png b/assets/images/moonpay_light.png deleted file mode 100644 index 3d3de2e4f..000000000 Binary files a/assets/images/moonpay_light.png and /dev/null differ diff --git a/assets/images/mweb_logo.png b/assets/images/mweb_logo.png deleted file mode 100644 index 92317203e..000000000 Binary files a/assets/images/mweb_logo.png and /dev/null differ diff --git a/assets/images/nanogpt.png b/assets/images/nanogpt.png deleted file mode 100644 index 958400452..000000000 Binary files a/assets/images/nanogpt.png and /dev/null differ diff --git a/assets/images/notif.svg b/assets/images/notif.svg deleted file mode 100644 index b1ff5b4fa..000000000 --- a/assets/images/notif.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/notification_icon.svg b/assets/images/notification_icon.svg deleted file mode 100644 index 360d0b4e6..000000000 --- a/assets/images/notification_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/passphrase_dark.png b/assets/images/passphrase_dark.png deleted file mode 100644 index f72d1e1a1..000000000 Binary files a/assets/images/passphrase_dark.png and /dev/null differ diff --git a/assets/images/passphrase_key.svg b/assets/images/passphrase_key.svg deleted file mode 100644 index c577dc30a..000000000 --- a/assets/images/passphrase_key.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/passphrase_light.png b/assets/images/passphrase_light.png deleted file mode 100644 index f86f68156..000000000 Binary files a/assets/images/passphrase_light.png and /dev/null differ diff --git a/assets/images/payjoin.png b/assets/images/payjoin.png deleted file mode 100644 index 1ba3dccdb..000000000 Binary files a/assets/images/payjoin.png and /dev/null differ diff --git a/assets/images/qr-cake.png b/assets/images/qr-cake.png deleted file mode 100644 index 7c54dedb0..000000000 Binary files a/assets/images/qr-cake.png and /dev/null differ diff --git a/assets/images/ray_icon.png b/assets/images/ray_icon.png deleted file mode 100644 index 0d48e54a6..000000000 Binary files a/assets/images/ray_icon.png and /dev/null differ diff --git a/assets/images/receive.png b/assets/images/receive.png deleted file mode 100644 index 180a4e5b3..000000000 Binary files a/assets/images/receive.png and /dev/null differ diff --git a/assets/images/revolut.png b/assets/images/revolut.png deleted file mode 100644 index bbe342592..000000000 Binary files a/assets/images/revolut.png and /dev/null differ diff --git a/assets/images/seed_verified.png b/assets/images/seed_verified.png deleted file mode 100644 index e03706193..000000000 Binary files a/assets/images/seed_verified.png and /dev/null differ diff --git a/assets/images/seed_verified_dark.png b/assets/images/seed_verified_dark.png deleted file mode 100644 index cbefa5d8c..000000000 Binary files a/assets/images/seed_verified_dark.png and /dev/null differ diff --git a/assets/images/seed_verified_light.png b/assets/images/seed_verified_light.png deleted file mode 100644 index a51eddcd7..000000000 Binary files a/assets/images/seed_verified_light.png and /dev/null differ diff --git a/assets/images/seed_warning_dark.svg b/assets/images/seed_warning_dark.svg deleted file mode 100644 index 0a47254ff..000000000 --- a/assets/images/seed_warning_dark.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/seed_warning_light.svg b/assets/images/seed_warning_light.svg deleted file mode 100644 index cab27ec31..000000000 --- a/assets/images/seed_warning_light.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/send.png b/assets/images/send.png new file mode 100644 index 000000000..aef504999 Binary files /dev/null and b/assets/images/send.png differ diff --git a/assets/images/send2.png b/assets/images/send2.png deleted file mode 100644 index 85fc570a8..000000000 Binary files a/assets/images/send2.png and /dev/null differ diff --git a/assets/images/setup_2fa_img.png b/assets/images/setup_2fa_img.png deleted file mode 100644 index ce6f0d733..000000000 Binary files a/assets/images/setup_2fa_img.png and /dev/null differ diff --git a/assets/images/skrill.svg b/assets/images/skrill.svg deleted file mode 100644 index b264b57eb..000000000 --- a/assets/images/skrill.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/images/status_website_image.png b/assets/images/status_website_image.png deleted file mode 100644 index 017bb64e1..000000000 Binary files a/assets/images/status_website_image.png and /dev/null differ diff --git a/assets/images/stealthex.png b/assets/images/stealthex.png deleted file mode 100644 index 311d47b74..000000000 Binary files a/assets/images/stealthex.png and /dev/null differ diff --git a/assets/images/swap.png b/assets/images/swap.png deleted file mode 100644 index fe3fc0893..000000000 Binary files a/assets/images/swap.png and /dev/null differ diff --git a/assets/images/swap_trade.png b/assets/images/swap_trade.png deleted file mode 100644 index cfa32b382..000000000 Binary files a/assets/images/swap_trade.png and /dev/null differ diff --git a/assets/images/tbtc.png b/assets/images/tbtc.png deleted file mode 100644 index bd4323edf..000000000 Binary files a/assets/images/tbtc.png and /dev/null differ diff --git a/assets/images/thorchain.png b/assets/images/thorchain.png deleted file mode 100644 index 674b60f82..000000000 Binary files a/assets/images/thorchain.png and /dev/null differ diff --git a/assets/images/ton_icon.png b/assets/images/ton_icon.png deleted file mode 100644 index 90f9968cc..000000000 Binary files a/assets/images/ton_icon.png and /dev/null differ diff --git a/assets/images/tor_logo.svg b/assets/images/tor_logo.svg deleted file mode 100644 index ebd00324d..000000000 --- a/assets/images/tor_logo.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/trocador.png b/assets/images/trocador.png index 37e643de4..67c9f221c 100644 Binary files a/assets/images/trocador.png and b/assets/images/trocador.png differ diff --git a/assets/images/usb.png b/assets/images/usb.png deleted file mode 100644 index 1163c573d..000000000 Binary files a/assets/images/usb.png and /dev/null differ diff --git a/assets/images/usd_round_dark.svg b/assets/images/usd_round_dark.svg deleted file mode 100644 index f329dd617..000000000 --- a/assets/images/usd_round_dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/assets/images/usd_round_light.svg b/assets/images/usd_round_light.svg deleted file mode 100644 index f5965c597..000000000 --- a/assets/images/usd_round_light.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/assets/images/usdtbsc_icon.png b/assets/images/usdtbsc_icon.png deleted file mode 100644 index 9f2cda237..000000000 Binary files a/assets/images/usdtbsc_icon.png and /dev/null differ diff --git a/assets/images/wallet_group_confirmed_dark.png b/assets/images/wallet_group_confirmed_dark.png deleted file mode 100644 index a047cb29c..000000000 Binary files a/assets/images/wallet_group_confirmed_dark.png and /dev/null differ diff --git a/assets/images/wallet_group_confirmed_light.png b/assets/images/wallet_group_confirmed_light.png deleted file mode 100644 index 851d32300..000000000 Binary files a/assets/images/wallet_group_confirmed_light.png and /dev/null differ diff --git a/assets/images/wallet_group_empty_dark.png b/assets/images/wallet_group_empty_dark.png deleted file mode 100644 index e613d876e..000000000 Binary files a/assets/images/wallet_group_empty_dark.png and /dev/null differ diff --git a/assets/images/wallet_group_empty_light.png b/assets/images/wallet_group_empty_light.png deleted file mode 100644 index f795648ae..000000000 Binary files a/assets/images/wallet_group_empty_light.png and /dev/null differ diff --git a/assets/images/wallet_group_options_dark.png b/assets/images/wallet_group_options_dark.png deleted file mode 100644 index 479aac57c..000000000 Binary files a/assets/images/wallet_group_options_dark.png and /dev/null differ diff --git a/assets/images/wallet_group_options_light.png b/assets/images/wallet_group_options_light.png deleted file mode 100644 index 308930520..000000000 Binary files a/assets/images/wallet_group_options_light.png and /dev/null differ diff --git a/assets/images/wallet_name.png b/assets/images/wallet_name.png new file mode 100644 index 000000000..f586682bd Binary files /dev/null and b/assets/images/wallet_name.png differ diff --git a/assets/images/wallet_name_light.png b/assets/images/wallet_name_light.png new file mode 100644 index 000000000..0199c1b30 Binary files /dev/null and b/assets/images/wallet_name_light.png differ diff --git a/assets/images/wallet_new.png b/assets/images/wallet_new.png deleted file mode 100644 index 47c43bfca..000000000 Binary files a/assets/images/wallet_new.png and /dev/null differ diff --git a/assets/images/wallet_type.png b/assets/images/wallet_type.png new file mode 100644 index 000000000..4e0eba8b5 Binary files /dev/null and b/assets/images/wallet_type.png differ diff --git a/assets/images/wallet_type_light.png b/assets/images/wallet_type_light.png new file mode 100644 index 000000000..e36c0d3aa Binary files /dev/null and b/assets/images/wallet_type_light.png differ diff --git a/assets/images/wallet_type_wallet_dark.png b/assets/images/wallet_type_wallet_dark.png deleted file mode 100644 index b840f5547..000000000 Binary files a/assets/images/wallet_type_wallet_dark.png and /dev/null differ diff --git a/assets/images/wallet_type_wallet_light.png b/assets/images/wallet_type_wallet_light.png deleted file mode 100644 index ee759a109..000000000 Binary files a/assets/images/wallet_type_wallet_light.png and /dev/null differ diff --git a/assets/images/wallets.png b/assets/images/wallets.png deleted file mode 100644 index 62ea20039..000000000 Binary files a/assets/images/wallets.png and /dev/null differ diff --git a/assets/images/welcome.png b/assets/images/welcome.png new file mode 100644 index 000000000..f1132d253 Binary files /dev/null and b/assets/images/welcome.png differ diff --git a/assets/images/welcome_dark_theme.svg b/assets/images/welcome_dark_theme.svg deleted file mode 100644 index 8f60c931a..000000000 --- a/assets/images/welcome_dark_theme.svg +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/welcome_light.png b/assets/images/welcome_light.png new file mode 100644 index 000000000..6feff85d1 Binary files /dev/null and b/assets/images/welcome_light.png differ diff --git a/assets/images/welcome_light_theme.svg b/assets/images/welcome_light_theme.svg deleted file mode 100644 index 178b2853d..000000000 --- a/assets/images/welcome_light_theme.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/assets/images/welcome_wallet_dark.png b/assets/images/welcome_wallet_dark.png deleted file mode 100644 index 771a600d3..000000000 Binary files a/assets/images/welcome_wallet_dark.png and /dev/null differ diff --git a/assets/images/welcome_wallet_light.png b/assets/images/welcome_wallet_light.png deleted file mode 100644 index 2a738be0b..000000000 Binary files a/assets/images/welcome_wallet_light.png and /dev/null differ diff --git a/assets/images/wownero_icon.png b/assets/images/wownero_icon.png deleted file mode 100644 index a3da77b9e..000000000 Binary files a/assets/images/wownero_icon.png and /dev/null differ diff --git a/assets/images/wownero_menu.png b/assets/images/wownero_menu.png deleted file mode 100644 index a3da77b9e..000000000 Binary files a/assets/images/wownero_menu.png and /dev/null differ diff --git a/assets/images/wyre-icon.png b/assets/images/wyre-icon.png new file mode 100644 index 000000000..a2810948e Binary files /dev/null and b/assets/images/wyre-icon.png differ diff --git a/assets/images/wyre.png b/assets/images/wyre.png new file mode 100644 index 000000000..a16bbdc8b Binary files /dev/null and b/assets/images/wyre.png differ diff --git a/assets/images/xoswap.svg b/assets/images/xoswap.svg deleted file mode 100644 index ef83c58b1..000000000 --- a/assets/images/xoswap.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/yat_crypto.png b/assets/images/yat_crypto.png new file mode 100644 index 000000000..fbd5d2483 Binary files /dev/null and b/assets/images/yat_crypto.png differ diff --git a/assets/images/zano_icon.png b/assets/images/zano_icon.png deleted file mode 100644 index fd48fd6a5..000000000 Binary files a/assets/images/zano_icon.png and /dev/null differ diff --git a/assets/litecoin_electrum_server_list.yml b/assets/litecoin_electrum_server_list.yml index 550b900e1..e61d0996c 100644 --- a/assets/litecoin_electrum_server_list.yml +++ b/assets/litecoin_electrum_server_list.yml @@ -1,19 +1,2 @@ - - uri: ltc-electrum.cakewallet.com:50002 - useSSL: true - isDefault: true -- - uri: litecoin.stackwallet.com:20063 - useSSL: true -- - uri: electrum-ltc.bysh.me:50002 - useSSL: true -- - uri: lightweight.fiatfaucet.com:50002 - useSSL: true -- - uri: electrum.ltc.xurious.com:50002 - useSSL: true -- - uri: backup.electrum-ltc.org:443 - useSSL: true + uri: ltc-electrum.cakewallet.com:50002 \ No newline at end of file diff --git a/assets/nano_node_list.yml b/assets/nano_node_list.yml index cda931b5e..63b4baec1 100644 --- a/assets/nano_node_list.yml +++ b/assets/nano_node_list.yml @@ -1,31 +1,6 @@ -- - uri: nano.nownodes.io - useSSL: true - uri: rpc.nano.to + useSSL: true is_default: true - useSSL: true - - uri: node.nautilus.io - path: /api - useSSL: true -- - uri: app.natrium.io - path: /api - useSSL: true -- - uri: rainstorm.city - path: /api - useSSL: true -- - uri: node.somenano.com - path: /proxy - useSSL: true -- - uri: nanoslo.0x.no - path: /proxy - useSSL: true -- - uri: www.bitrequest.app - port: 8020 - useSSL: true \ No newline at end of file + uri: node.perish.co:9076 \ No newline at end of file diff --git a/assets/nano_pow_node_list.yml b/assets/nano_pow_node_list.yml index 3bbc7c3fb..b90845034 100644 --- a/assets/nano_pow_node_list.yml +++ b/assets/nano_pow_node_list.yml @@ -6,4 +6,4 @@ uri: workers.perish.co - uri: worker.nanoriver.cc - useSSL: true + useSSL: true \ No newline at end of file diff --git a/assets/node_list.yml b/assets/node_list.yml index 917dadfd9..bc7a9dc4a 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -1,8 +1,6 @@ - uri: xmr-node.cakewallet.com:18081 is_default: true - trusted: true - useSSL: true - uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 is_default: false @@ -13,3 +11,12 @@ - uri: nodes.hashvault.pro:18081 is_default: false +- + uri: node.c3pool.com:18081 + is_default: false +- + uri: node.community.rino.io:18081 + is_default: false +- + uri: node.moneroworld.com:18089 + is_default: false diff --git a/assets/polygon_node_list.yml b/assets/polygon_node_list.yml deleted file mode 100644 index 3b2cdcdc3..000000000 --- a/assets/polygon_node_list.yml +++ /dev/null @@ -1,10 +0,0 @@ -- - uri: polygon-rpc.com -- - uri: polygon-bor-rpc.publicnode.com - useSSL: true - isDefault: true -- - uri: polygon.llamarpc.com -- - uri: matic.nownodes.io \ No newline at end of file diff --git a/assets/solana_node_list.yml b/assets/solana_node_list.yml deleted file mode 100644 index 3ba74d980..000000000 --- a/assets/solana_node_list.yml +++ /dev/null @@ -1,13 +0,0 @@ -- - uri: rpc.ankr.com - useSSL: true -- - uri: api.mainnet-beta.solana.com:443 - useSSL: true -- - uri: solana-rpc.publicnode.com:443 - useSSL: true -- - uri: solana-mainnet.core.chainstack.com - useSSL: true - is_default: true \ No newline at end of file diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index faf57258a..46a62494b 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,4 +1,3 @@ -Add built-in Tor support (experimental) -Ledger improvements -UI/UX improvements -Bug fixes \ No newline at end of file +Support getting Addresses from ENS and Mastodon +Bug fixes +Minor enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index c49b895e3..795ec3427 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,9 +1,3 @@ -Add built-in Tor support (experimental) -Add dEuro investments -Solana fixes/enhancements -Polygon fixes/enhancements -WalletConnect improvements -Ledger improvements -Payjoin improvements -UI/UX improvements -Bug fixes \ No newline at end of file +Add BitcoinCash (BCH) +Bug fixes +Accessibility enhancements \ No newline at end of file diff --git a/assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt b/assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt deleted file mode 100644 index de54473f7..000000000 --- a/assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt +++ /dev/null @@ -1,8717 +0,0 @@ -000039088e7635bd3d088ff0376a2c12b3d14fc2714f3126f1b2f4c8cf919dcd -00010e2247ac2a27a3f266e1f7354b523e368e3790fab7529bb29d29b2264189 -000447f29779b8c3893421a796abd089a2fc9cc50297467be733ca64d5c3cb70 -00044d7e25a645f5da26bcb906df1aeb5ef35e1c86582579561240311bf8ccf5 -00090cb1f4794070340b8e6ba697711ff59545175b1acb69980a7f0c14f3e6a0 -000afe48e10afc15299e54e7ac3dec1cd6957903067eebc8424f68eb666f3790 -000f01e767f12f7e392693854b4e5e5cd79bc847fded720aaaefe885e00279d6 -001a911b00e9f1f201982ff86df75bbf718c46b0e441cda527a634cdf1186bcb -001d490eb469e0e93089485e7dbf477e872b24c680209f762d3501a8067f71be -0021a875205e02f95cf0f5fa2cb435c950f77a3c27e342afabc28c2aae2a7766 -00262b5e3444d083d2772b12b320f28c499f49a218d1e16af1a72f0221718554 -00289954798211fbdc5ae10303cc17ef5649068f12ced8ca73ba229896993fc2 -0028a1567f9a4a6c619db76dc9ff622d9e177848ea5c89413770426d0a705e7e -002cdc7c708744cffa707f5e161c64bcda06afeef254e730f28be35e1d866df0 -00472c6d9f5bcbedaff69723326965247d76dae59d0a8dfa3c888ff894ef96b9 -004ada6c6d9fbf968aaca13f9d3bc53c57dea45cf06b7a805c401375eac232ef -0051652606bbbefa01e6ff5abc69e785b7588263708690b06d85aca365000d70 -0053b9fc7ac7828051bf0d38d4e77f8920e80787e5eb316019ad7a700b0d8077 -0066a62953fe7aa63a0bd28796fffb1b6c888e337403f830747403be4a05792c -0066cb182adf7a9f3bf8ab07d6342c6f701f3e9cbd19fb09b67bd44f07b8fc61 -006f72695184e9f4eb694ea41049c0bcb43031ba396cc651f2e526b340e35e10 -0073dbc1905143eee9355dbb172930ff40bcaa07f4194128df88b415da0c9b63 -00748efa0448e91376bb58fff14eda5163e5d6f601b23a108a50a92261d8d8bc -00750a3efd0bb937a0a4abb99c4dad594c390ebc9cb09582b04be8b9539f1fdc -0077ff20158828edbac74a0c7ea8295201f8cf96a85bd39841e9afd49c884f5e -007a65ea4c15ef9a397fbbfa0bae4a4b5c58e53071102fb38f6b0f9f5f402032 -0084939fd209a3b90a1e09b69896de6cdddca9780cbf57b9379cad6ae3797636 -0090c2bdeedd129e587cc3dea4b744ffec9f5c2f7e7708e9df86ccc66e3adcf5 -0092e1c7c3dab725117d3f61b07be9d50b2f067434016680438dec20a079fdba -00c78b47ab7b699aefa6c78b0b045ffb7d99bbe7025da177b67803ad82259b8b -00cf7c07b20281c171a68dca3d517981ccb5823f5ea273a3e9bb218705e59f63 -00d71b0bb4ad85b33d62bf115b418a07b329dbe33dee1a9a93b016b67e4527b6 -00dcaa01b15b706918dd3a27dc0b1290cbe61d350d294ba8b5fb48b96fcd8a3f -00dda82b6b7ffb87052937555e6e5073ad5d3c492c4576de1a92c63948d7efc2 -00ecb86087cb1cc6ff59b4e6ea9394398ba12b10b48577dddc5e872940b11ab8 -00f16eb82cd88b72c0fe5a06661cd1f00e4f450eaa2a07fcda91d98b51b7de7f -00f57f9002890152916ab46b3d91f60f14d239e0b48b96f13374790fefb5f537 -00fdadc62e1139a21a0e0edf8bbb7d8e2c0c670831a98efd6a4d05ffae8664c2 -0103850d9fd1bd01510b026a795ee970619056ded7aac6df740d7625c5b41764 -0107338c9d85387be3e309dbb84569261d181b41f4aa693c3724981d6b99f494 -010986b900d45a5b2cf85e40ea783a35c90f0bb6cce682f4f2655d0975511077 -010aa6990557df1746703831acfeacb9e63db28109150cfb20d1b891da0839d9 -01198dbdca402406c23dcb946657a0743fd8966cb945bc4c0896931beb377900 -011a9c580fa0e7d69a177dfee0a7676e539ddc75e434617815c32282ac921866 -011ab37f89d19a791833ebf4c9c68fc1b2e02b0fae970d4502bb1c1de58cb816 -0121f8045225d48b8100e2fcc5c88f6b9e595cc8696450b8f24d92ee56b5b788 -012afda7d24835561f52770a3b7857db41f236878912fbeeb2cae712f5dd6a7b -013432372532de946523c91ebf29a358527f76c97d57f7dcd34ca1329993e0c1 -0135089b5f09fe3f68b796e9f3c5b7db1bf26de6f8f97e28824bc635f4d6b005 -0141fe7684497b1b5aa4d95776ba82f11614c357c99ba30c9b6117e54c50c623 -01436e653b2f03faaf845097e59174fca7fda63125fa68f44d2b40451a1ef340 -014391a1e04420975d3bbce0dd18797a7140433645ba892eb028e085d78d6b6f -015eba92a710c4e4b538f2d069c00da555bf57f8c38c4bd9afef9f37460383fd -01638e9d9c7dd0c49642514daa1076c9555b55c888b6dbb7efda2b54c927d36e -01649b21d9a781b778c80df37d59ef31de2bc5d45a2a80eb7bb8f788ef5a921d -016d30c1a01b24e780d28f6c552856936c643dca57d19dd20c00663894a8b2be -017b3056e5e7f90c72cab3d4c555fc3533217eb37b82cbdf4e796826c4653b05 -0184e29e10dc106cb2f5fa52da7d752a0c9bb8a4519576a8f6a7874c31878609 -0187025704e4b57dfeb0fc07948cc076c12c94635cb1d7c02c8d8f603bc2f325 -018a424338c2328f74de393628bd0d9e3306467d4cd6509bbdced15e6e1b05ee -0192a84c09abf17df69d7f07dcefdf658975b9dba98825ff3b64953c79823705 -0194437ef12f044f97106274940b681e816bfcdbd62345af13f8bfc1961add48 -019657da7cebdf8cd531a3874c924ba242b1bc2b7d863694de56a7f3b23240c3 -01ab967755e6f57d20eb7faafe6d28810b71788383b72d4a0ed6ad74c6cab51d -01abb622e29219767d909b038fa8ce7f4ba874ab0c14f71c111dd99aa714a572 -01b4e4ee78093925a73efc586c62db0a23eabdd0378aa2a109d2119a3437e7ab -01b6dbfac01fd182530f6e535e7f2f0da8f0dffa00006f5976b79f10ad6eb6a4 -01cdef7c54a1b8bcc91ea251cb89b0f88ab878c89dd08560485c8bf3438775f1 -01ce7b781636bed03a443549985215cb85f190dfb250872b443c436b61f14141 -01d91a3ac1c3f26e76bdff719b321ffe9fceb41a8fc5e1546a799632a1bae9f8 -01e65b9cf686a20207543a38a941fe6f593e0c7b21bfe4fdbfbd8722781c1284 -01e9b6850ef3b0f450652c7ef6c9a6939b78ac8c55b4b64274f7552af5e85f8a -01f01a1ae0983ae1d7d64858c6cdec0661fbcde54ebba7c05e88c4870a0f92fa -01f16ecd75c67e1b459aabed7c8dc3cbaf1e25d5fd3079b38dea7eba0ebc439b -01f5bb39e1a2efcd688db104f5d05b2b5f9f23a84fdc335545ee04e38e66f894 -02028d8ed82d2db379588bf14e48805adca0ce70104d4664230f333120105ea7 -02114ddefdcaf6d100e499fb3392967ab21da7e5cd1c01271822dd73dbb54e31 -021482b4e8ffc2906b2180d532213d1f197b02f6aaacc74580d2dbf07a4b6179 -0215e9c94001069afa2da537d68e09c5b5aba1bb36ac4fded93375702b45800f -0217ab4904ac9242722212bca9d460e05d99c5cba65ad096ac6dc552cecdd00e -02184ab24fcd25abce582bd6699b09e7ec7a2ce67188286b214596ee06bdb625 -021b58ed0ae7c1adc45f34aff3e338878641869fe241c115b562b8ef54bd33db -021ed0da8e92760cde50963654b23b53bf2496a89e08e93e61c101f040de11c0 -02256240ff566cfe79c676bbc773693c64672febcdd6aa6ea504b9bbd3b09996 -0225e5d9cdcc11c2c0837a44ef6a6bd7751d86503d9d301733ba2647c93af55b -02309f00b3243374a78972da1ff7df51f09e3cbe465ea3bc280e3e8db6d55e9a -02432a4134afd8c6be9844b446b3aaeecb98daef35beb89d0d0a5ab9739d4fb7 -02460db9708ac93ed17260bf81a7bfc12bcc24d52822ec277bae3d50b4c2ab0e -024b1aa80da00a026d776de396406b4e2bb7559ad312bf76fb8c144eaea7aca4 -02509d3516f8939ce59f3b6d29de933db4a21829d400f3c11240a1e60f01e376 -025b2edd014aa4b3ae16fdab8dc8b79a168eaf72af58bc71cbe65a9a6b0c0e13 -025b8747f8afc6e3cd281694025526684d6cf5091a22b89ce8e40ca8d60d3971 -0263fcbbcfc4f256579530fef1709f1a9c5ca9be886064d9d5dd57373631ad44 -027cdc52eff0c7114e82ac62d642300fc3143cdaa7e412f606248021bad81028 -028661ba0d98ebc3b8f3da7ed3da5d08bc58554dc231713f6f0fba366d4de056 -0293513ed8ceefc6529c51eac76fc297187f77abb1ad3b6dbc288e8b43ca7a4b -0298600936f3b6476d5900d3519ec197d16560af6f0108451dc9bd63298e8c70 -02999f15b404ad494ab5273e3d3eaef3dac0092f05849911e2808346c4cde5a7 -029a7b64570c3ceade3aa2e71c4e69059346789ca4953983dbf9d1b294c8ffbd -029b7b3640a35f2db6ce4de9b215a4c9c8f9d0bbbc0a7312fabc5012cf6cb0f0 -02a11a3d3aa640d840c7789761b3b06b7fbb9cc00534d79867f8914e59ef9654 -02a51316deb0764356c7d248084c305f8a10580035d2c56ec35c87213f1ef172 -02a9f55f904675179ababdfd0e72dee783cdc6fd22bb0b7ef7be9d8942155cf2 -02b65a80f14e88037656080865d38d39ca41194f1654adcbe36f41b8ef62bdd3 -02c3f217606a01eca39264f7a2da194737d42dec5b790617ffe8420a87552828 -02c92345c1095abb0d41233b694af05f39bff45782aa47b7879e7cb3cb0ff26c -02cb2261a4b8aa25ce341a0478c4158a0fc690cc09a4c35f7c8ba9b2d3624521 -02cea36dc91239b8748d82e7e2f4bb71b404275e3b7ec137c24461909b0fc95e -02d18db6ef9e055eae20d4882189b31deb664262f059fd21764eb80fb0bad196 -02d21b2e89f938b0ee84033210eff6800ef5da6968902406a5d5ca9f8c8f5162 -02d8c3a94c10358b2fd81936ff2cf890a6dfc6e57c8ae82a213e5258fc805420 -02db6d8c6530708392d3da5f1baf057ffae4ea907719e9e7b45e6fc2aafa7609 -02dbc3df064ba39a14fa74d9a8e435fc50807ef786ed3f5e458f1fce94dd78be -02e9c99342cab22c730b1d15dcad2afbcb5712ba6cf22e884206ed2c5f747735 -02f457d612ad9bc40e5a9294ef2f6779c2f3ca1d10f8e5750b29eea28b7a23a3 -02f9575e3836178517cebb3cccc16d39ceb3d7fe673a26fb6b8fb6a83126b2a2 -02fadb1ee79e91b40c715fa024a72c5dee1c1f872273d93621528d2f22134145 -0305a8d19dccd295173551437afd27cb15da9cdb4a3639307d1e13794435edce -030a73ff96fcb9ab7a2f1b02805992cebd2faf41caf6c5288cc1063b6ea96cd4 -031bd8149f49570af4ac6a9b2609005e5c2f83c1d760630ecd5da4ff1f3841ac -03269d089f5d08fd9c242cbfd173857c100ebbb595f3895da839f02337a6493e -0330a79af5742edd35389ca7280d215f7cb2fa366c1b99cba92d2cf2f9df3c7a -0335bff6c510add55c37cfeba308dbdf77afcc10bb664ed2c9b29117d005a832 -033f4f385331e34bab2d045790d3360d121078281a47fb5d348be79689cca7c5 -03462bbfd4a46e1db66c7ca1f1e78b1e9df52a79d520ff205a564c5a031bf8c3 -034dfa27d6db1c235b8aa4143f4d003000d6f698f4d58f85b91d5ae22fb69c7a -0350695c8149c4110a47f4f0a17e84008f2c27ca1c1ca57ca4d95965e2b89f26 -035249f0fd16678aecaa954abaec659e64235b474c625307073ac2e45a5ca4c6 -03543d282210b5bb24c92196450699b31425167253525dd52ac28f2bc73fab31 -0361ea4f9f70e051fdd7b1d90d1ef6a8e98e7bf7b841bd19bb4186bfcd7e54ef -03633b8089b373aca617e896f81045b73b0e0d6197bbf98656be1d7f19436671 -036ee09acb46e47b8ba441dd00b2c86d3049b7cc95d4145cb1da1fe8b4bd3b60 -0372da5987082416aeb0c194741c585ec7486f76c86443d2ef67dc05f54f7c74 -03746c1f7dc3bb267515846d752f4f02bd40f3f4c1ead4c8e88751c588e1a39b -0374aa439ef497473a09503ad3e82c944bb9cf7a72d75494d1d7be5a85b8d6b4 -0374bfc30a1ed92d6dd1cd8fccb0607bf395d594aa43ba89b5aebd7c6c4e9e3d -037f9c680266ca9d83b16fc700e55c3e50bdf37b1c2b162a825e8cdd67c898d0 -038105c0c9a2dffeb9866f40861c1c0769978f0404e9a223f9fd8edf8bd63731 -0386f1bb4d6598ddd5569123c4a605581083848e407f70c282f310e30c9b2262 -0391933ab24b2a2aa0b088d0b33656ae564f48acf4798e371ecdb88174076a6b -039c9635f12322c07dea5e392814c3c07bd9a505326c7f0a1225798aa66b728e -03b576f6e487153327f869bd4dc2873d6b4aefc272060ad859432e2173244da4 -03bb87bac9adc40de22deb878466b80f3def10b6158c2da4cb40a743dfe8be57 -03bdfbb8e8c0d35afe96db5b322f3ce5af43de023f6198482fde20835bfed396 -03c051f4fcf6fe77fb67ea99d098e80e4a713fe30eb80ef286db72434157dac3 -03c32e01f09fffb2ad0a0de0057d07b593da862e987b850505a0d6f19a42ce29 -03c4271444962ce30c98cfeb5d5878e18088a6d5f7df75af52a5295d8cb9eb0b -03c87428af54284e489e3d5878869b0b85ef3370c572203a77396b463d14b15e -03d6cc320c869e8f63d84a1fe744776f54642e2583c21d4bb8c4590be51a4417 -03de1ec76c94e716aa0588064886180c48f3519f5095e9981966d3acdc05d78c -03e49c2d24ecdd12a0c265965e0924cc694f3527eb678cb02fbc10bce6ea7563 -03f3b9885eb9dd3cbd93fd5f7e3c25887bd403baaf8a6a77791ec5d166b03cbf -03f66a39625e42189af0c0f5fe3f863629d50a33fd94912134ae4bc4a4a2c362 -03f849d69557bd5f60d42bcb4b72883599268ad9d16e94a43d1731f7eced4d16 -03f84d8dc0cf6a415f76c60308edb9584a1a6a3f12a769bc42a0358e27d445cf -040fbb780fe25fd30d58bc1428375a054cc88bb7c8bb66e5c1e1a119bd76ad97 -04100d4001f581ae1dd3528931892d3b38337eeaf9ee677529c33f0ce3406810 -0413ce625288ada0d0d6609285ff97c69689d6ecbafd1eaaca555e1b56cacdd2 -04182686a830e8719f09b4762c444e3329a111251ea160b7c6d7b335dec31d93 -04186834ba72c034b0885d812f239739885926f56e54831e22fc6d0dda345f6d -0426e87f5dece79cdf35750a06455685d06a5ae7aa7d86daa49a0bf726b53cfe -042809c65b6fee594fdb07040701c2c7e81919db9005fd1bf096233299f3aaef -044012f23e92bf935ce2118206851a00cf78f1e80b3062ca2a67a29415c838b0 -04451199b00988d48dfb745fe35ca369bfa1f7947c0c4ab3c9ee22e699344114 -045478ca6cb5485d8377ba2dcaf44706b9b13797f0ea42d23a24c5dabf860f19 -046ad86c5d3c8229c044024baf033d7a75c2bc9ca0b5e75f62f59a07df9da403 -046ba8f91579b9f0c91375308d5fc1474e38d6d23546d09dfa3c71110106c83b -046c9617113365a1e0edabe3eaf32dbebe21931617b589de93a3a17e3049bb35 -0478f06649ddddb07c677c3b99539a4fca0608198d0609a5891c924d2fb26a1c -048228cdcb498a1dafd8bf4293020f905518c902432cd4e778c56e1b6277a457 -0487dbf96b49409456d5769958e768816d5cc0d49bada3881ab6aa1a27ba1496 -04888123a859f8f1c99c0cee494ac186de9a12829b693d17a486ac029e114c9c -04941180d91544b64a41968bd4e44d52b656543b69980aa4342f735927d5af33 -049c2ee4914c5dda2f736ac9acb34dbcd5a29c3580982004bf28cc8fce5f927b -04a2cef1454824dc608489ed51cf4cad59ad2e0eed9e77cd3194bd518755ea9d -04b3a6e30db291bc6695caac4e329242625498b16135f6cdc9cbd6bb9763575d -04b76e701ad0980945d28360317df87dcfbd2eec46bf51acadc34ea15176b815 -04bb3a4a17c127e78aa4fd9ebdd2245c996d782daea248dc7a72533a66d2d83d -04c4b8d41617216af068faf852d956c95c26a9aebd6d7b4b67f9654faffcb417 -04d8a5a38bbe7ba6e619b4c9d27ce02e95e4a41dd1f55915ead929fb3b847c99 -05077947f347493d8c06b4dd9bd42f67c251bd3ecf9b04c696eff5087f08bd2e -05155913acb2e2c0cc6e21c47b2125883c79c2f6e097135da1567cc2f06933f4 -0516d529b7d6c29ca51f5e9e43db2c14192f29f3c6ce55dd14733095e7a3b108 -05173768fb647a471beb316312b0a7e67140de3835cef584c25ddcd196bd8d25 -05242124150e05aff1914d8731c77f6cc0e1080c5585c2b509e092ad8147eeb5 -05367f6d632c458312fb8e5782116372d1dc0f21c5acb2879e224ab86123dfdb -053968d4862c453243f3f163f591e9082450d2c58a96aad61bd95209612a7e5b -053ebd756f4acad5a18a3b5d638f9437c056dcb0be642c748e283d9467d3570f -05484895266fb2f54142d1fc85710a90950442b6fb144478b663c57c3121996d -0549d3cca5c7972a3a78172eb103e4d6a7dd9d11c46efc823fec5ae960afa6bd -054a734c424022c3ee898dd9afbd0d7854d84db91bfb2d60b1d2af16e879bd23 -055990de128d3c2184504e0dec3567f9b5b040d16520013a0de1d7c9a689dfd2 -056390158a4aba6bbc048ac7c3fe2792b025121e7ccf651cbf9a98315d47ada9 -0563cee4924684bfcf9f83ff091f640612e90a7df787ede7ad0cc30579e5ceb4 -0575e1d112baf91229bcf52d1ffa7dbca0c0ad3ac4553b04bae37f769aa0e39f -05863e650fc0deed0803bfd92746fec2b1c914f7ba3ace71165c6c1677e12e91 -058aca3f1bf367c1d3e73342e468f496da812ab28133c27a5b96a4761140a120 -0590cb4b1d49775e1f0c5ea557abfd89cce3c001b5a0a201754bdd501d2d0444 -059236edf2f7d9518b4fc3fa01ab1dd9ff1e25e8459b84495068cbf57d4fc356 -0594db5b902a25da02304afee9e3c7c1a0493785f992d868756fa72f3cba09a9 -0594fc24ec3209133ff90d72e3f969c33783f3fb8b2350898edd170ff9b27718 -059dfb1993a824317d583512f6072cac3fdd275352abc77da1c107c6395dcdcf -059f89c76ec40b05076e47b4b3e14b89d3e3272c60ee122f74e27537efa78c34 -05a39c963db9978b14cea9c85d868a2badd4958498a73a047232e8b6e70a15b8 -05a5f45c0aaa379c811f56d64d56de502f49c0086d946d57bb0c5184d11a86d5 -05a74b7cb3175732f8668541c29cf064f3f520f9b5d99a33cdf58ba3437a6f3b -05ad3f8cc4514ecaa3a9e5bc044657b3ec9c6033a1aecc9446aa3b2fd42f020d -05be62a10e2a4ce727a756b1b60fc09e4f82f63bcc7e115dc19325dfa6b4fde7 -05daed35611cf06308df9ed5383fc54863972c15542fd6fc834a277c8cbe8d8e -05de0102f7f07e40566d7ab346b95aa6a05b87c0aaaae42566350fad2ad116c3 -05e772545d3ac48cac9a4f2f71ac41e102c03df2eecb5ccfe0cd669964ec95c8 -05e7afad054a2e8b13891b35517fadb32a6556f8f48b40cebfe88d3caa844f9b -05e7b44dc734e8f54ce673caff49321c52e2a27a0fb276f3b7d12632394aee7c -05f8c4d4cec9b5e40b17371e4fd31a9578fdc52b8437c7008d23f316b23a57cc -05fdb1caad2e9496c936beee05ca4c8ca61eb47e09ee6d0903e2bb09ba50bc5e -060aa623da5b943ae2a44a68e1e6e1aa7423b02666ed63648027f3b36b457363 -060ddbdbc04867f65fb1728438bff362d1b89b27b89320ca9e6642a199cbbaea -0614fef14c50e542f4610c4b0c915ec0a9de58d0aaef86e2cb73406292c8867b -061fe3918274b66786fae5919ec038c11d8ec952bc12694ce11c4b8ee48c46ab -062b4b2ce5bbbbc861200b30529f98bc6aa13fe583428a1fe35e601e42374101 -063013ff0018d6005cb4485ad1c43c3c1b6e6ff939539ad84fe4287f954a8758 -06428b8d2aa9e4d7850e33085829b4910ea16c220de558927212ebabcf6fa38f -064ce20b230a936b7e083cca164172e61948312e2f73d01afb1c9feab451d9bb -064fa41fe471f009f003111f86f253991e6bd3a111e960f5ff2420f4c21d38aa -06522e5b5783e864a7270aa2cc822c449db73eba1ddeae9f941d0bb153411f79 -065350dcb7e189b5d9dd782d744d1643b52a5a4b591fced72aebc4f6de20c86c -0654954905b89a0ba48a2d0db1f6d6ef40425213ce169c09de86dfb4aeb341fd -065fb84f704f065ce6c497cf5b40c01590596d3052f6593a902187a74e3f6a85 -0664f30f1803cdaaa5c1a48586c7bf5a0ba24354ebd185a948631cbaf5e16b1a -06695db7e36ecfe4298993a0a5083b57ae4473a527f909f5e42200b751983543 -06702d364784a96040fe7faad17ed37d54b30a47153343be8f674228185d03f5 -067048717a9009cb98700b5fa72d2547fb1763ea19228ebf749213e7964676a9 -06732f214d050fd8507870649d077fb67e75d82de1cf2a3512f609ac047b8f33 -067d4db9dd62d21a379d1cc202af686a9914eda3d630b13c917c3c9de5d53b54 -068601df89aa1c212c681cb2524c0243613c6fca2c5927b17abd8c96bc9f28de -068a3d0ec7527621db5782acecc80e6f238a0df45889dea5f6fc4d808a632d4c -0693ab66b44fb6e20d5363e91779fa678fe58f8494a4855a0afe0b9be494231b -06982350d8be8af55458fb224ac141309ea41d2422b7ed105b734ed19d72ddd4 -06a30c9a6882ff0f8416d2ca26c1ee1c6b3bea7597eec44415376e69f1c099eb -06aa561fea0b5e5b0c88fb20f3a39c711ceeb326815b57f4d67240e8845132a1 -06b5c6811097aac10872af086c5ad55e018ff53d724a108e786f721d6bf4fbcb -06be301723e59bc4c24afc02cd1a4ed5457c073b0360af2e020bf31a6e52ad0d -06c9ce2b4f8a795970f30f06a1db140ed36a3e63a0d78769bd3d57fbe01f749b -06cd04027807920542bab041bda329cc273d1661c2773337c42627f88a088e6b -06dc6e02d2b8fd80af8ee0d56f60405420cb0e00d8d1cfdb9cbb86215bce0d75 -06e016aeeeaa94eb42ce1494c42aef820e51a76d1615003e4929d543064b4d9a -06e15fb4eb4bd6c66ad4cafa093f304b596aa680ff7b261dc5f0e3d5b4e8f3cb -06fc8a839a0d50846c34459644caebd9e1e489cd6e7619a25f4f68e44762073e -070025230c82b2aa3da0a02ebca48d20ee2f5c08493106f4cf1c204944be0c09 -0705305967a61138accf5938b5d32369771524c81317e645a6fd9ae6228e0075 -070e2cc58138031dbd5d92fd05aaf9c8a5b431ca4b1fca518d3524b9c20d0048 -070ec346f12a5526e350009daf540145324f536b26438281173af729f8f78f70 -07121c0b70bc4fa75568e15e6417005c6539af5c681ee4ae515404ecac762073 -0713065ae385ce486e38beafed7cb034a0549a9ea9fa020e2409d8a4aeb36ced -071c93bc1b3983d14d2365d8191149da91075416cf8083a4aa85b9426378c093 -0735ee5b86d4907900ac53b3be35e079ebff6f858136dafc71127ee2c9ce2799 -073df1a7231bc1d7bfbe4b9d7e69b544329cf375e710554d932d4e45990e6b51 -073fcddc204278088d02eefe51439f94c0f6edf7d4c61cfc373fbb6c59b93ff1 -0742f9b766d324756437345b49699527800bb32e82e662ca5a912bdef4b9836d -0747dee4936afac30fb17496b71ba5f73c3ff552040d56969e59c0816044e4d0 -074ef6aeb2af0ea564cf94f5d2190688f2fd42b3e95f616cf95ec77d0209f2fc -0751e5222c89f9f5ddc5f7b0b37051a3f2175df1c92a3ba985132db0592b14b0 -07698dacf7752fc46893a53eb523ae24fedf8799f2b047e867b9475783065aa2 -076e4b805d8b78282ec9436b857a580bd74218f335d59ba590c9415abe68b1ea -078059ec52d056617cc53d20f9a3d4ee19d366d776d40808df007740a6ebf5e8 -07809abd973e0f1812df8e229ed50ea85fea486b8416962b869cb2e0167de61d -07860f8fd4ab5817b2a8213e617d75c3ee6b57fee014547a5ecd3e1a071f6931 -078969b2010df6a2b1fa707989e33cafa1c35c5dd8da73ec70c8e586d90d2340 -078c1e10b62ae22d47cb9812e6f0b4201b0d194c3235aa41842919c5785d5d1b -07a1d2e8c3fd2ff8a00e75ae2aa7a18bab3b0a7fc74a63b2665b2736771c4f2e -07b1989d39d8b4e0d8ebbbc44a891ae999515a54207b4e2f50428697142d8179 -07b5cf3692c956fd209d1b439cb2471ca7ef9028bd93f736c04a0eeab98c231d -07b60f0b54daa140784b16ff7476027eeddfd78634445a690cb9a82b15a721d9 -07b654edc5c89aa0b652f126a35e14553a96485263eb2ea3ee204ba41bbd9642 -07c1df33db5cbdcbe27aa583632ee45368b894c69b50cd1b8e082610ce124ae2 -07dc302716b82790e8d84cc89caef581ff8764e9aba508a4e286c049606073cb -07e2a983256e5edc890b07015606de6250fe74bc88c4a07dce46b8ffa7d80582 -07ecc0f6f69e3c58dbb40a66ddda93f73423d1d338cf62a43d947bcad3028ed7 -07f22c12af7f38c4362f5bc11c1a0d6433f3cdb6e14c9bb90055ae3d7ab63138 -0805393a9ddbfd01e79d29697cc6464c9d5e60ef33ccc7d7123013a69c22c527 -080a27bb42b278ccd08b825c611f25463b2c0b7db0f81d69ae578d757ee4098c -0823481560e72eed90d56d069fdf65ebb2d1cd0192f04be3758baab80bc6cd8b -082447e59e1fc6b08338dbd56dc59b1058f2586899d7858982f2f701d57f2006 -0828706373ca8dd0bd88a8f41b08015ffb4bfe1ae725ebfe8e43eb95a9ee525a -0831efca1d4aad1cd08d883e2e70b2f3a7fd1f0d0f0eb85b780fbd3aa21409dd -0833543ef052c7138bc4e9621fa3685eccf5105b7ccbf7e704021d15ad0531a5 -08367b4a6da7ca5e0ac0811a445662c2efed6f1fe807b4ccd7e368fed759829e -083b2e07621b24e22a276fe58eea5548f0901f377241c36f60557182262d2827 -083e44bb9ad7ed4769177ce7d68c838bc679dd9096816ca363d0fd9d3ab75e69 -083e4892aa97360a2b13bb81138549b14d58b6c0c2ddeb008ec8e8b72e8c8ccf -083f09473dda97ee7e0949296d912be2bc681d3d5034e7d33c9386a14b3d3766 -084462e855e751966176bdd25e30ebe3f225410329816346823c30d6c6051a97 -084ae30994def7ec8baaecee5c16b336730bc90278224c266f84f48e3750fddd -08556a63e42d3fa5375a06d925c792cd91d0706a379dcb5138fa8ec761ceab5b -0856d8a51d004f58ce180b287b26214d1e5038d325e4d8c9f12e6edee491bfdf -08652ce9e29131f92b03b9b4937a75f2b1ef71771acde47eefac2af4cbfbe9cd -086c32b68eeda5ecc829f62a1aff368d4fbe5ce9e9665bfb000f9cc9119d7c2f -086d43560df06b20284dec5bfdc7664c79052b178d6eb70dd95c9dbe8f9efef5 -086e59db192ee7d333bc39ca05813dc6caeb55059cec22f21a908d9a06d08d6a -08708433e31ff0a97b94db2d5a954076214ac15d572f65c952eb22c60f564374 -0872c2d3dcea05aadafd76b88bdc9a8c3d5f568f6e6dc5bfa415d6c81a93ff52 -0874bb2548e83549b9fce1fd8af9ebb2c5d87acd7b01d163da8b8093858ea2a9 -08765fbe14dcd5a45a887249228c07fe6eadb356ce0a680acc4c55584e34bd77 -0879692755624cd839e288b473035e49c8f44bdd72e8c4601f42234ac94c3e6c -087b54432df80dbef2b48414bde08c327472a5a9d548e6f129c2d9cd96999447 -08896c1cb25b75a158875772ca2fee19bed77064bceb623c63f1c57d889621f0 -0897487b4fcfb2bd063412dfce99e2469686f2181bc07f12e4d1fb3451d7e50f -08976eec68469cca29621229a31060c547d5fa96f365c48b1b344f03b56a2bba -089fdad244246ab0b2d9dcabc5e419c58b544185e0f8d7a38cc238cd2e3fc132 -08a347414cbcf854e4f7c145e808ee733afdf9476524fbde69359f961887ab1b -08a498ee0f36b7b2edb5cbb27386d66a12b9bea0ca319f33e1eb7649c3ae0eb9 -08a7f132d91165ec5fda1832103f25a9cc9e04abc92a0e6d8833568009f47d5f -08aefadc3ec650da44bd24da0c32cf9c496eed87ea1f96b0b3fd71823f8fa32c -08b33384fd2bf69907ef15b7637475368aff808438ddf7d6fa01661405f45b66 -08b6fabb1622b258f7af8ed3bede949ec6249f3a1e2fdc0a1c601e7a11398424 -08c2587319fe93aaf1627245eb2709c076b20a6a98fc962cde8166c7f76ac011 -08c360060b6c87bd0d383cf42f579119495959d2099f49b8c27a388ad54789d1 -08cbac9612944b891e654d3bd233d5aa67b62e4571dc366182405653b6b49828 -08d139ad48bc0c21cd9f8ab721c09c63603cdad455147dacb17dc7960b354118 -08d4aa18e8a1fef29e741d95e9a66664db94ec1f068d27b3c635e0fbcd988e17 -08e7e84a9cbd313a1e03c66e3ef542e826cf4a5a995de3b964e2eaf26b4d3bac -08fed91262805f0a3fee90a66d66aea5696593d02bf33c8b5abc18d9e06ebb80 -09077a0e4caef161f9194e521f25151fd7b647824bc579e4dae0a652077a2d15 -090b23e5254eeb7928804b07d81989a22ec2742dc52a88714be8b577a85d2e7f -091b570f4868275dba0c65dbe45edce7c1c023ad170c5705a99b4bd3119f6464 -091d389ed5cb0af0fdcb83ee67019f385d79bf4974a721d3e3f1634ef0e9bb8c -0924557bd00814bb92076f8424216a4b27cf701511e8f95af7c538e69060f1c2 -093fad1a4f8ca4d1870a82262934b54626808e022389b9be7b2e2cff3d9f91f2 -09455c8cb0392bd0cb759c12ae0fbeb8ce5abd7898b29660350711e193699aae -095548dcc592c14e6443a0773b44d97886525c155d5b358543c34ccd42eff6aa -09579fe75acadb47714366e0bcac34727df7550930809f7ec362f097e5bf2543 -095d0e9083a68cfbb7ee1cc411d6a712ed682795cf5b5fc9c01bd365a369ac34 -095d49a5de862cf699b146db2172e149a22b36df95399b0c93b0d74c1fb7b6a5 -09623d68d894c17739845e70e1c6923554b461747734b2c4c1e68cff92e9b637 -096734c0ecc98b9af1e8af5a341316b81cc988e04634b694466a66ba36a85967 -096a882bb9250e0313a6887059f98e95dfb612e25c92e3e2b18af7f367bd6298 -0974cad608671463d46b37e85541a7181d8658adaab10addbf1f4a6397f8065c -0978ca20bed9e373d61b6b0f17aafa1b7820ad3868c590cf076d9cdc66963cb4 -0986fdd946ae9d7b9e2d48929bff694d9565cb7ba9fc7762a5c703499d54741d -09883f1dd8bae9a7baec4fa048c3db3e068445cee291781b7cce34b9d3793754 -099f1ce8a896970b159ac9396d413c673f54679c1e29eafedf8c563a40b6397b -09a6dce68fcfc37394e6f02aaefba88e96813ac93a7fc4ee6632acda580a7263 -09a8a4ffd987528c117cf38dfbba17898eba9ebce6574e17bcefebb520cd952e -09b13125db80a15ec9a6d1163e82053216c43215b3b25fb3712f990749a62950 -09b5a0276ff183937e5c3c08925b35774af7865017443f47fced5da95372aef8 -09b5b5e1a6d8447f0a5623c5e8218a07cace0810a0d4caa9509dbec3d0874169 -09bd9fb287a7779e230e11cfe30a90c6e90fe22524283160105feaf5cfe78c70 -09bf3954198dd762cf1c2903ae760c1dcf99b2506b0904725dfab71f998c7652 -09d01de4c403241c7166c21f5088745d23402cefdc2a5111713d288b6d837a72 -09d25c1a3bd507709bc1088206d5fd423851df0753f41b63f6bf3631f011d343 -09d44dac3108974d6b1fd8114705470fdfb95b5895ec821f12673c4a7deec47c -09d9b6f80623de453ca765ae3b6d5507a82feb486f3eb05c8a6418c5f073704a -09e504e676b6899f04743e2cd982e4c51a49152d850afadcdddb260f4e95028d -09f118c3377f1db098f43e12768eaa7ad1ad580431774f34134afec399929111 -09f145a6b0f3c6660159eb62140eac2d9693eeb4c7453d325a10359313bc3eba -09f688e259c8cccde2a5dabdfd3bb46a13c16e0a54e5d560633851db3d72a6b3 -09fac6a60677b29ef48997cc3a26792682cdea6eb177493adc8ced27da9e31ab -09faf60fb56d63cfdcd4f5f3797ef90cbb0ae3ed8cabf8059d4c018ab41f0696 -09fc931206ecbcbd54a209bbc3dd6d45e340187c709da0af893d17238b38083a -0a01847392e70812eac5005b064e3dea3259194ad24263b223e3eb230553ad02 -0a0457db9953078a0539538367dbf7b70d68fd867db8a49fccb71d2fdcd44941 -0a07aabde430c1ed43aed3432ca603fea749afebef03562bb7e04ab0a21fd4c8 -0a0d91be048549f88374e56f854dba1c7e8c7b1674ef6edf78aaafabd0d396de -0a15124036afbc1c37aff14fb3070bcebd2bdd71233db607c9e92c48d33aa112 -0a23f7c5418a96b00211599fad9dda41e5a7d3e1b93e89b6bdc01d3f74bb47c7 -0a280ff1aafa425d314f6a5694b1fef7c253e052ad8a57f57d778d09d898fe4c -0a288bfc64fce7789b64387f1b64e0cba23878ce8540492968e793ebc720472b -0a3d2cf39f0a113d7133f657ed9065e051038b95b8918d91ff8b681e2369152a -0a3d388797a893d3f58d53753fa201729f913d7af089dd4d98129c1044d7110d -0a3d6b6800b8c3e62e81260b26a9fd406bca20a6ac453614da03ed04e9e7ef67 -0a3e715105a2dbb42cd00566dba0a7b2cfb9171d73ed052bd3adf54a847c7f5f -0a57d8cf74a18360cfdc11f48c0c86d32fb4e6c19a35ab7d4f2e0c2eeff6dcd8 -0a5bde248f1b45a2185ca2221d5d0593c03cbcfd67f3e42337dac8e3544a2579 -0a5f386bc7afd1f7e71a5ef08c2def8f79b1c5078c61701e947db8b9c6dcfd98 -0a5fa6feef167a45be4d996431b0fff43d28112616d6b7ecf37e160584f3ee01 -0a654fe4ab795d6ca9c0476c6673da34c397f8cc8f599a04ef9d6960beabbce8 -0a6e5ea817c43b572b20110bce95963d2ee80c37eb6070812eef93e4626ef60a -0a6f31a77ae32557f041aad10944b9c1fb0ecf9cca8e9a8ea1dec556b584295d -0a6ff18a42774eef488f6ceed43ed9578499231b48a540680785beaebbe62fa1 -0a760af0d859cf77247c7ceb344ad983fb1ecc08548f902634c0b4f675cdd540 -0a9820eb81dede6bb238cf1771bac034dee63fc881bd52f338346a632fb816df -0a9f82e8436964cd9bc6f6c9c14d09a90ddb2e719f404545a28f246ce8763ba3 -0aabd8bb0d4798b3270dc04874fab0e0fd889eaa07f2d1c444f0135e3156e22f -0ab2519f453043e132e59df0edc33a77566938c3d47f2a9618436fb0950cb9c8 -0ab6662bbf060a7ff7eda4a33f20973ed758fca8c97dc86008f65d371c147c78 -0ab863f852e3f9b67a10ae7455db6ede2b96d42ad7d150009e03092234cfb6e7 -0aba79ff74b89bbe24b47db992a9285a9890775dd428d15880aa701dfd01a022 -0abc7d340258cb1110816d52533a17fdd395665539ca260e1b76289430510fac -0ac9dea425f14db08aa94345d1223b14cc635e3bddb16687fd39ae0d04e0f7ea -0acedd09af784d3f53851b87ee99a630fa0cee91512beddfe5ecc0c875111a4e -0ad114cfea0e4d55e9809b4338fd46568abc7a600c391ce965dbbdc97f0a1a3a -0ad3cbad64d2aa284d62cf32364468f1b72fc9314d16114287b2ed76882cba61 -0ad652cfe8c6d12f25c0c56ea1090a15b251367f1f77183ab3238d54afcd82f2 -0af1af70f75ddc074a6fc642085238f68d562341a9380db3848404e9e561a0e0 -0af2a1df2cb2aab17799b57c1c8f51f263d1cdd2bf85ba3ef01c87dd5b0b3079 -0af2ef1cf705048e77ebe0ecfbae87dbb7b169ffaeab4b492fa22ad21f87d1e1 -0b06bab63d4a90dfa901722ac17b5754d423d2c92beeb8141a662c34aae8d861 -0b07372c2ae2d9f92d2598e9cf1846a706d798ecc68880b9ed9f6818b4d13d34 -0b0747f9be3d0eed16b423dd10ead14a23cf8525798ce9e6c018a56c12b82d13 -0b10e557fe5a4350b3eeb3472b38ad2bfece1e48b0a9e6c056d1cb3e9fe3faed -0b1b7e8f2e05f6c35733fd71847940790efeaa3ace0d40ca07125d4ca18cd238 -0b1becc1034bcd3a0efca2f9b23a16c0611f3dd213602cde9d1b7eca39520718 -0b1d40ef48cef68faf858386edbedcac6d0b50dd077d147b324646d58873e1e2 -0b282fec163d7ca7b6108eea3fbe9190eb54e451be563112921d2ac9719e8334 -0b375cac56a9c075886e19e406adf9a2c55dda1c57ccdc622b3e942d581c65e0 -0b3f1e4c657dca8c1861814579ec1ce6c651ff43ae88e9589190810ae4833fed -0b4af5662df46c2133efc733b41fdd0a500726c2ac725b235bc451beb2498015 -0b564aaf56a69b42b78ac462eb622a8111ea00365adcb9affb7d05914996ba0d -0b5bf28c8038bbceca2c9e3d9839a6eb267ded39580ab7b099001b4d7b87d651 -0b5cbed090a2130f51bf232aead4578dc2e5b812ea4f395b95f64f4e0181b2bd -0b68893d34b95b7477a43fddddbae4093e57a952b0555de3eda8b4bd2f84f267 -0b6a0e9c269743241c1a595148df1f03d9ba5d7be29bdc59a314643fd6f6255a -0b6e458ed99acf85c6ba43e3acdf080697a2dc389b0caa7170aa8be77b29f12c -0b756c1b3c38cb8cf5583bac808a2df043d132c907f7185544860c9c5eff32b5 -0b825d8b2720184eb7d7221ae77621dde26ad4ca23605183bc5f8f79ca2cc740 -0b88eb2facb360efa418fc3eece855bd6f13638c31080ef48cb3cb6a10cabef7 -0b8bb30c443b849a36eb3dd3b929e7cce70aadcb17c2f121531111b3d5f42b91 -0b8f990e77c82f1c999934f3f93d57f9d55fcb3e423eb34503a1643c1e7bb521 -0b94ebe86979f7011b9e90f01f063129ee89bde3633231a294abb67f40537a42 -0b9519a86edab8ebf69bde672944be50d77f3367f1fb3082f74fa1a7fad30c91 -0b99720a903fb13b07d45a76e38800e33170bdd905c3c6dec49c734c86cbc5c9 -0b9bb8fcfef0128b9ad0f7140f79f7cf60c1c4e7dc039ecf97fbf7fdc03f9ca7 -0b9bcf1232091363ae9af829d88273f19b0880546e36b66d6b09f088d6ce8a17 -0b9d49f02dca925ccc8e7d88afa1fb4a7e065ee4570e4fcf906d6c327753fcc8 -0ba9ad68bb8ffb48fe0e74992d6b442828824bb63bc7a819ce18166f8243f8d2 -0bac19f6b76c3db0c0a82cc753350a027d999570b1fc2c56703efefaa9ff9170 -0bace2fcf574f0e0560106606c23444f07e6fc9e05b9f074258b1c7a36102588 -0bb5a743e6407accd192745855fd7035800b08324897dc0142bdb9a5893e0f34 -0bb73952f2168781a4acf74cdc65d710300ed0165f528b301a71378797570f67 -0bb78e7b46b09b55054dae53593997d2c1357c78ce7d817be045f9e7dacc5894 -0bc759de71afba50e06469dd382273bb99b7bbea0647c2130423613d33041105 -0bd02d1e65c231c904cc2d681a33cedd3b0b420433ad560bb5dbbc82393cfae0 -0bd41539680e07ed31e6f57d5ddccc822c4304f2644126f5ac4973ddab98a39a -0bd4270a4f55d6675dc87a9ad4c97d37593fa977e7027f45e43d0481d0b0096d -0bdd97d554c4bc7dcbc7b57464a0f0757d86b84735795065b707cf4e68e843e2 -0bde4a89b379f59a7c2daf4c0b01200f7a8f3aff85e9674a613b8cc0a8e6128a -0be0a0dbc961c3c6707c2b5248123d8f7f1c8c01b362ac08aac05a19e3480256 -0be14ae27059398300048e18570de0a31c3c7226fcd51e0c01422b206a8eb177 -0be1ff0ce1bdc60a1ac13fe9d40db374475946ad1485309914e9c3e4d774d2e7 -0be26c186254f95ff70984e445fe0c9444e82bd93dd26d3d43def4e0e503ba7b -0beeb08b22f9a01db4ee3f618fef87d5ce30e85384b8307ada340434c1cff6b0 -0bef61a470c418037457580392b17bd73f94d2d49203effce0d4db36fa173461 -0bf8856f5fbe0a709f78b60a53cf18b0bf8c6d2b0e109eebbc9fe5d97127aa94 -0c05afb02298c25ca7d8f70d48c03299bf62e73f886187f69a58495f638f1a91 -0c08669bf6bc38f1fe6a4c46a6f114f00871435390192b7b872bb11db8308170 -0c18bd3231508b98c7a54841fd1c7d74150dd0a4f596579844712efedab484e8 -0c1f2329815e733362ab988b3a6a6caf3f390d7cca520e1627c609a46b8adc30 -0c20a8a527d1dd12261610b7ab6325c7a9a655e160a78f342bba56a5c025f8d0 -0c330728b5f0aee6fcff5a9c68a835b541c51c0da73b22296049e59b9bf6a8be -0c3dacdb9ba6257c3c791a642a63b7733b8eb72346b2c5a24b51d0537648ebe8 -0c519bd01d25c926ee7b9c3ec5186c23da2a28f2a31e6905f5c85797be673345 -0c6cce02d69ae68fff8e781e50c7dfad992780697ebc3cc6f9a35e94999e9cf3 -0c6f736cc87b5291b8313852697136ab3bb02ea06d00ac364a282c0d229058f1 -0c7d33d09584b9590201f62dcffea61213c5399a37ea1a2e29c2e97a47427b82 -0c81a6324a233d2afdbf5f09362749345fe651da92733d72dc8b6c48abfe7ac3 -0c8f423e0d3ff395b068b24bb6f861fdb1cabfe94d3b15f5c7af90fa178b3216 -0c9b162aa86d5637f84aad5a96c8abbf8b05efd9e90af157417d2041d3ed98b9 -0c9d4f9c1c566d7b9fa9b21336f130d735dba340733f143c939d2dd19d4099c2 -0c9e7d2cb87bd076e0c6b8d69dfbddabca8b327206b6c4798753794128e7b589 -0ca4103621144439872755bb33dbcde00aafe42ae6127e17fca1ce3c3f32ee80 -0ca465d30967ba948a131dd776395598cbbbb75f855cd8d57ee5b231a079fb1f -0cb188897d51a641d2a798a275ec7254c539e9b26f5a6560f80ce4dc1f9c4c68 -0cc078afeff3b6a67ef24588f0871e4696ccacf165b28ee558dad85f8813e48f -0ce3fac3dacb4a1b3d04225e5f80b33cd02b6e8b26fe1af0a16f37513a4e26d5 -0cea8fbbd20984be9833861735ee1ab35d61cc3f68cc5e619319af55780317cd -0cec516d225cc4a560f232ad856d6141b5d755c140cae0e7f9d6875b1e238785 -0cf511e04442bda4c550aebafd0d29ad47c0f8062f1d6f73d2b95e07bc6a5a4a -0cf5d3a916489a13e379d3f6f1347d9ec1caabde3b5d30c77725c57efa115267 -0cfb40be305f7b6f935682e3f48956aa6658efabd4ee7db34ecbd6dad5760277 -0cfb4620b6fe64877465a3b6417b288e52f30773854d0cb1f3e9944783e8148a -0d05314c6dc310a2d8e124214c15d061f0922ba24c6a5c3ced86ab93e430a20f -0d079e387f9032fdbc4e82af9bd3ec24db538e34d1c1d36f17af407e306b818d -0d1730a8ae3098e05b98a2cc20fe935220c21288f57fd112d58f4edd29b8a110 -0d1e2b3bfae5d5e4c4611d539d84ae6f9177412336a6099af7344a120d8b6181 -0d2a3a963dd0c2e55ea12c58019c8e596ec7c3aef696ac6d35a25a91d92af46c -0d3d5d06a5f5c464321c873cf4a58076b160c97c68f5ceed3ea2687a8584658a -0d5118c40c97da53d3d3f3bfa9ab56e903b16eb2148622b79815094f23fdd5a8 -0d56259867909bfae7d1a93719dc19e9a460a9f2cee360f7212dd5027dc1f97e -0d56f79d52d4117a7e747f3fb158d3440bf2cde1bbe03a24b5c4409915e44962 -0d5c409f36475271b59abdcc0a545e1d22bfa240af5106cb65ba75516cce57d8 -0d626c04e8c70d719ad0f9ecf141f5bc86e51227fcfd536b59c6e938893dc574 -0d6c6ecfdde6d52ee352a7b00440baad6a3f3ac33690705d6e12130131b2d6bd -0d792a51a63b64696f7549198932dc1f3af5a7db261baf7bc042bf04ae906e46 -0d80c24e8805b2dffcffdfa42d7a8180a11cbf8c6b5256bcaef179a81198805c -0d956531d954da2f98e7b45b31202fea5b2466d3afe9356946884445a49f87e0 -0da5ec42ea152cd63d3704cb83054d636fa3707136b3ded5e5c74b767c9ac749 -0da7319729a692957b2320224aaad138670a41f0e99e7874d714cc9f9e63a50a -0da9e52cbe4cf59e2800cf7d0cff18d5f78a4d431fa579aae04dea1a23810bde -0daf04c44466e97578ff7b448b2006a022a13ecb4d7a0b484a975739f7ef2932 -0daf2e07f84d09ad526f1e12b29472fe66d41349f1b9e94eff83d17e167a3b8a -0dc7bdea69ce535ba61775fea77c2ca203468250f91403fcbc4449ffb06dd106 -0dd19c0df7ab7f5439142ce81c609fa6fd0c185e04b5ab8fd88491301c5071a9 -0de9ad69e765dd246c2f7714a423831049926087234ae52a1f38fbcf95e34060 -0deded4daacd402939394f668631bd9d675942ecd67d512035a2e2cdcb67123e -0df00e5070a4e6fa453b9bf30347834849cd8bf4acc3095844a1df2b67e093ae -0df0b96bb1cbd92244ccb841b040d9f3b2e4f5175ec83d56508ec0459a0b8e6b -0df6316a8173dce1754b026550ad371bb47395a8b6fe9fd8b9aec881ab68835e -0df6b83a39cbbf845805e13b733cfc88e8b65331f2fd1b4c48c328ad44e7bcc8 -0dfde2a1df7cd93d3d425111256546eca51db19c9d41bc6297c0430f39ad1ff4 -0e03e01fc61fe18829d28fb4b6bcc28034d684817ef36d747d477bc0dc51448f -0e11fe59c372fa50f5e97e02cb9dd9bd459a87454cdf1ba29536e59ea27a6be0 -0e136e88740d267a80d1f874ebd0bf5e99eb84fe4106455465e2010617076c41 -0e2c438e3ad54878715e2749f5bd98058ae49931c5e4eb691d3f372ec412ea5c -0e2f1fb4cfa3c8be9d97f309dfeecac77d3cff245fe7d6b36456d00b74b4340f -0e33aff9d2c594edc85d6ac2db6b8c3cbfa9711b1f973c2fa8d2ab5f745fc4e5 -0e3890a77ff679665810c12abda8dda1c4c55815eaa994afd2394cbb1b59c4ff -0e3c1c83c96ed8ebfffb4b560e1bc83bffce219a3a5e7ac98c4c78c90628cdd6 -0e41f0625e48da5c237d80c497253f225eccde02de6531cfb1b85a5d36e51eb0 -0e4cf82e5d6081344186dd64a1825a67c0ce5f149d23f690e9d3978786bb76b1 -0e4eec610f0ed8b9858b4a56fb44cf9ac68e29694eb680338cc5d0d3f1330749 -0e6f22a1e1bf262cb926b226a5633c8dba89fa626db350fe270dc9b379a85832 -0e737c14e22b4c6377d34f5b3b3fe6fb364f1ba7eb262431c9e33580c52303fa -0e88812a4b779eba99788a95c00b39169fd5a384f8b6721ff68fd27f5ec906f9 -0e889ca478fc44fbe43b7c5909f60a96c0a4dc33af89d9379565a541aa48b3f0 -0e93a4e57a6cd703a361db87a7eaea1cc95283f2f7eedeaf5b89abe783db1a8f -0e9f08e0a3e8d8a14ac2b6aa2b2f880d646036fa3589b1257ff9d733888e0f16 -0e9fd8ce7bd33c62078dcc4eba4cc5c8ca689a523fc6284f402b304becdb1c20 -0ea61d2b1b87584729a74b98dac1f60ecdaf6e0364c8c5034e7989d495f83429 -0ea87b3baec04821d5604d4a8e58115d9a6c345d0cdf0501a342306ff43296e4 -0eb7e2b30e7964e4000d0f6f805f4ef395214df6fb63c0186a575063b996ba2d -0ebe42c3ada4f4eed35280b6fae3e29f4ff127d1a4cad3b72714cc1ad714bdb2 -0ec6e82c28dd2119a29f77f8dc7e35a9d72f49de631ec882b3eb30135adb87ff -0edcaafd12570ce3e50d8de2699b478fda42660c61acf5651002678fd0a5fad9 -0ee843871a15b63e9d035103141fa179818666aaefc321409d9cde4d49318553 -0eebb57de9571a5674e35fdd04b67cb3c7dc9b816567815c703aedc5dbb56186 -0efc99c9360cfaf61476755c76875534322909455181d47dce9c84c398d2eac9 -0efdbc90e6631f8ce4cb2ff246fa7d17fc5a7f3864b77d456a50520d3f472396 -0f061ed23777f6e018023a9265b1940d282eee557c9b58f506b8ac26111031b6 -0f0723bbf43986d8285c8348df3f8df40a9340001dfd5a98e5b48b79324f26fe -0f0db27b5daadd9c0c6dd1378915fb01758191f036292ed809744b88e8c1e93e -0f1ad0c8ee31d0c4b5211a89a91fe97ae6cf1fea1f4cb85085df0ce51f230481 -0f1cd5ead4583b6e4a23971308ce5fb4aab8516f47a8787ac4e4f56dbc846eb5 -0f1f41f49178058a648234540522f9c9bd8a5e99592618214422a9c128ddee1f -0f243034f8bf1e57a44d973b074f4d80b1e8ae8bdda1e054396c375684e41b86 -0f26c8c69cd0ab675b57df945c9c0ffb6892d845097dbe895841f77a44375165 -0f423a8e11dae48436d10f742eb4cf5f4febf6a23d4d86d9d322ac1edd369399 -0f43e65dd4d1157ed77e10b6dbcbcb015718abdeebab6eb00ac69f7529b9e317 -0f52f2abb484e215fcc61263347036c143e961bdece6acbbbf0d840717f42bf5 -0f55a2e14b0bce6a8ba2c1ea5b20e25e53816b3cf33c80e6383f4b6f253aa09a -0f67000df73e3972a542a8b07e4d22746bfb747c5c10bde275a2f6fd8b2928f0 -0f6f9e3dac165cdbf753088e1b59942b82f8c6dc9f589631557984b668c571f4 -0f7b6d6d56765c611d9c3836b90fffd2befef3e65fadb41d1fb9e82b5ee0b7b5 -0f80e142aa78ea2601a5e810ef4e38b33a00a27c376e29785a4a7e73878fb29a -0f9486b6aa59639d199848eb0efa0596f017c28739112e6e5d39994fb4b0bd68 -0f957cd5614f5f2a2f4ee3520494b1c9d6fd5803f4f70e1aaa957ea9eebf3154 -0f99f3476bcdec6c983eb9376f92e28120b1b489f4fe6dd884a0b19060a998b6 -0f9c30a1e15cdce7f6bb59a16bdf148e3cb8fa9637460df1891257cc17e5c04d -0fa2b3c4db735115fe85d578d508cef2125ba835503b3393b0145667806887d7 -0fadfc9067b5d8d77caabcc6b4058475cafae02cb0c6648b77aac6a71dd11f93 -0fb3edbe411f25f90d71c6faf60a5e64190e20828bf9c8cb37bad3d4d24337a5 -0fba49b26bff2af41ed4a013ba3de244c10b52b76964b56f19ab03696e608e4a -0fbf1538d61732bc35e1c73d9cb428265e4564346e86b6beae19d9c666fa436c -0fc88e6fd738b150e115b07b11e27be66672e8f91d5a4d1c73406cb01c6ae551 -0fd99a412ea943e33a5fe0c80c0521686adda6b0ce8c107644e6cf2f90dd9b07 -0feca39673bb59523b72e354a6c6a663ea9c753eca83cf4cd8c62794484c8c4e -0ff9326ac4e7d1f254dac9e488f6a84d94df458e0ce88545c598870454c12ee5 -0ffbd3d17ed8f1461f46265c7d1a88374bddc45afafc39e7871a518666ded77e -0ffdfd01d440af1a78824aef783d179f411184888777668fed51a13e4680dcb2 -100329b164eb816b2a87d28845d49ffb0dee79728853397c6001f0dd5f073849 -10057842311aded999383514046cd9efd0ec9d9aa8a2fe36b902260b64e79ee3 -1007550eaaa43fe026aba79232c3ca3f829c50d17f099df07a25211067c1553d -1007b974a30442ec3f127c7dbaa9078937b1e12819596914ec49573e84f84475 -1007f91a19a63835e22a006652df052454fd7d5ed53367a3532340c26130b097 -1008aa1fc782be62141c00ed670e4ea94dc32df84279f59cba1d5a6e18126fa9 -100fbb6e4478014d6627517ffefa846d7cf657a662f17377745e2742237ff061 -10116cb15da829ac002d87f0e61f0065d9849ba10478a908851cec057d32f86d -101333e289b2dd64c611d6db446cd09dbc1516a68b6253ee3a53003c4f6f507c -101d8e63e560ed2cfe574084e7ba712c65e90b44649d06efb4032d026dd0b8a5 -1033e245d4a7545956fe9f723e215fabf2c53654b08207cc7bc7a3b0e1942631 -1034552a0b4863c942f7383786eafd7b2ea95361ff28a39b12cb3ad05adde9f1 -103b8763630fbd9963db2775647e2aef1c825182ea898e37a9b23c49f017b2cc -1042403fd010fc3670d50e6af305fb80249997922929506cf09a053c8013419a -1042e27a6f4d0b90daeb2fe46626097ac12efc83c17644071d3408ea2ef15d89 -1043dbb5648b0d7908cb6f2f20493acbc470725af2b814600733a44683352d93 -1047b8846a59b4514e1b6c9c6078ff864a45b0665d6b3f86e757e855c77dc40c -105c87bd967351afbc9dac3f753a72f20c0275543d42b966c226af78ba924db1 -1069705c5d645773d8b87e93cb07c2e03a3c3a177316d286cba066d6291224dc -107604c94b4259b2d3e09ade96fb94c0059f19bcceaf162bc40ecb962d7f560f -10784b4e3a22c8729d1eecb8e3278914aad4daba9607ae6740be9fe0a6601f58 -1078bf5c7cface7da013acffadb9944e21910ae188b8f7c2d0b8356c445fbde7 -1080b1014ee7c5c5eb9f8af4473e6a14c8e2fb2aab07e8621b5557e8baf9e67a -1084af5f0218e1c263987bd484092000946d9f5e71b306c99285fb7e7c73dfab -1085c6fe5789e09945a5bda156ad75f31d247b40b1b5a1e1e3b69780961bf350 -1094e5edddab567d026c07d82887125dfc72aa994718f94636ed99f92ba4567b -109e968e01d50eaa193e83e5413fe56a78e64671b002a4285c56df0f1db24d2d -10a06987479f4f9829f83ebd554f4dfec2455920957d8dd79c86f693672ade0f -10a175545d7697d0ae01f58ec68b152ac01c9cb81ee32abef9b448b999ad7298 -10b108127686e722c1f488fe9229cd24abe4ce2f9169ea2da3ff5f58a640564e -10b885a1b631798227d2c1add652f2a092f4dd721b5802b84fb5aad1716f7a01 -10bc557efadfa9d93a4a8810563a14aadb3309e039d3a2baf5e3c5163498da59 -10c951cb67b1dda551cd898e15bec5c822c05dd345d45e74e0f95683b8cc032a -10e3ac2ab668592f47e6b60f5d7b3783c02a7b3fa064ed2fec342b4086e10c2d -10fe7f2bfc1b7fa81ea1a3e1089dd42fbb7621b01a61f174bc29d3478c6bc8a3 -1101aa0d176b5e9129ee87c8b3ea2cb4c6beacdf29e22f722619d3d6e59130b8 -1109dd3457f983c38fb3d611eae0f9942f96e3d36908b5535562703ced22e4e3 -112471a583f0618aa6efc4b11b225a9383f03547d4f34ee8af7ef8629d2cdd5e -1128bdaef74d48f33fa3a15f6d6c758ae90cd47e591b05ee13733accc4d025bf -112c9415f3325d5afd49f41bc763a8cacc6e3f892407db2527081ae0cb173cd9 -11472ff513f1b8cf78fb593c0f50300d9c6112a483311017348bd03be1edb624 -114be39cc63af3e81720a077895a86b549cfca8ee4e0e214e17fc502b6246249 -1150c8ac2170035a42f7e8f11edc116ad2403848522a08d729b349681e565c0f -1158b5b7eac6b78c808b9cb3a6076545f3957df285dc278ac535d5466480c4fb -11613243cdc29efebc1bbd760bdec7960f0e3572ecfb4fdd5699908cc0bf531a -11713e1875b4070de327a89a5bb42d759af9e30614e121cd8935a47ba9138f00 -117799a5ddc6bf296f56755e8b8ec8663ccd41cb2f7c78e08bf055296cac8c8b -117bc2101558c0ee0d6a8abcf7217a52ed484bf493a23f1b9f066522a88fcccf -117d84749cccb6fbf81098eb04411f7b097891b065f4c727f181d1b62e142c81 -11885ec0bcf460247f4a1a6cb3562e351afd2746006583f0b1896077c6abdd0a -11892aba622e25e00532b4d6fb33539769f7cb311279ffcccef671b94d62b1cb -119bbd2684d2321fe65b6de36937ff1ad88a85bf28dac133e6e591f72d9164f1 -119ff0d03d40ed265576f72f89c00e8dcc76ccdd6849eaf70606ced2b473a372 -11a09baa49e2d5b68357a25951e333b529ce89e11449937fa3b380ff50446ec9 -11a17faa7543fa3719c4b3c3328d61048bf21ba1823aac512440f55b13c8e745 -11a266e18ea2570bac288cda1b644ab151f63a87ebe9bad2d4b0772668dd439a -11a673a882b0ae54522a6d982297c4f366889384536f22add3364b4b07dbc8ac -11a72d1f3d6aa8035f786cf7ef8807fb6a89540c49d7d5afec3e70b16252016e -11ac27ded8cddf49c9d38598de571c7ba5c29486a6db0fd6cd33f0a32994a302 -11ad81ca6b94dcd9d323479d0fdd355411fd53987df9922df148f609594a3b1d -11b6f4e6effb7474ab5c49b1db2adfac274e07b28d10de91e4bd06bb08864135 -11c26c5c0c7a915e5a6d7fef690a8f4baefddd513ae30867ff6b86e8a68cffda -11c5650a5e55e8d37431c3d5511d3b343ce1e1dbfad1e87ea941efdbf2f4560e -11c6c6a6cc29185e50e5d4aec93f74889e70876c41ad7f9edaf60dc56267c88f -11cd65e7985a6b9932e87e36e04fc8d1df281a690ae30ee851342d3ccb6cf826 -11dddb9470fd4a1daba1a08265b3334cca2b32655ad828b10186d2441a82346c -11de509c14fa04e01e7474744f07056cb0044bb0a0968ded372db91760d7ce5e -11debdb80129b0000530f30598cd8da13baf2c6a86e9effa1dcd71b3ca8ee06f -11e1425aa91b4b1e1d1b1d0805fdc02d5fd9b70f9619a21e9e5686b87ff61525 -11e4b3aab75b86e795d1ab769b1c448202dcde59dc3626f1c404cd926f98dc6a -11e5cc9860cde35223310e3475dbd59b566f556d70e997de25de34f204bc9a22 -11eb8e4e9588c6a15c3a81db07b9ff28935a25b7e880004dccc2e22d6df6b54d -11edd147595c65137a8cbb8ff5525a753c98a1ece95eba3fc88bf3a88cb2e846 -11f9d9d532c48327720bb369c5a4c6e94a171edb6307fecdbd167f3b78637d38 -11fdbb6bdb02a41915c7c6ad6acebe3bcd0d25fb58646cb7e75d96e2a1e44a3c -120959bc20b74c33e78872573162e0e94b06167f71321bd0e8f0c12f8cb5f659 -120da7c5f6224e334144390086f30423229eeedc5296cb8322da67217c80db51 -1211b7c81f1052052e2dc0cc5eebe0edf568fd895a4804c66b3548585a55cddd -121e2d9565ecf561f3e7e6d98e2ae6a2c49d11198d71e7d284a8659aee7d5096 -1221891650374406699eab079583c9747a856f496e9a9ccd0f01c7608624f4fa -122790f5f5827ee8232bd10fbc6477fe0269c90af96f587b16fb9696ead187e6 -122b3a571b98c5988b64554516d7818c6851bce897abda3a34c69abe3c6429aa -122e2d750364275019b0a06738b319c9bef6bcf036f10b02399ae585017d9278 -122ff4567df977b34c5ee817e09b28f45493192a92caea7678eec755db86f28a -123c626b0bc0254050097698e5252a15a824ef15c345a799468f905d985c40a7 -123cc5d86b4e7bb7736327d5a42c445fe66f721df91ca0435d60c73ebbff2715 -123dc12438f90aa2d12b6b7bb7980f32c7c5e025d1a7177a8c7fdb1a6acae40a -12401f1d3810dbc1acdc042c7bbc1783cdc52cafda9df943294c60770ce2fd94 -1241164a1b3e651f0dd03991bdb8400230da8b61f8d033ae3c6e1c0a4b24010c -1248c582c3b2053558d004dd06d6116256cd7b074cd5609cc02971e1b7e87737 -12496d90e17978788551c91993fe6d12a4d2f3e41bd06c395ffd797856f3e412 -1257176bbe6c09c15a1ae98d860b207b6e3d8ac614df776ce3138f685622f785 -1257afe79eb43476a80569de186dd31fdad3d2340f797a2f3ab3b965dd4928bb -126128e7306e48b9301f3a50a740b86f457a41118723186cbbcac0955448b29b -1268dceba3c0576fef38a332d6e7740305a9dc21b7cd577e0b688965ad001e77 -12759501e449878e2b668c48f15b36304333537cd407c7b98720c69373694c3c -127b7edb6a1cba95de69682293eabc3308c5ab040a7ac7f92367037769b38511 -128132e2ca4bf4a231f2e4ef27bbe1be040812fdc43a68389aa702306ae55940 -128b119b8fa294a54debad20f17f7635f088aaea313f28f3a54f1464c3281ac6 -128df13ed3ccc74933d8c00314e140acdf10a92d5bb18459396660fec8a43300 -12952acf91bcd21763ec39b06b45b018398248c2c598a67a7e7d71be696c4303 -12999f9f7cebcda08930c485ced2f0cb2dce58b04b8eebbbbec76c97e2696c43 -129b0faafd4c21f134885d191e1e50d9656ce2b8c6ff035c5fcf3f6ee3686927 -129b671dbcb91e69567af383ab1673eb79a879f86d92309240b84c2feac215c0 -129f87ae7d8de9b5785a420cbc656e94a480258a61d7e541e94d776e5c865929 -12a0792bfba1d9cc4b7ef1b626e3347896c299e092dc21c39ab5a5b80dcf95e3 -12a136d265ff710db4e84e87807d9a39dcfa211fb74050c909c8298ec565f88a -12a6ef639b434265f6ecab0a51c704dbca728ffcfa788b7ef4336969831a8e21 -12b266407642ef082643c30d528dbdeb78b462b3a3fca251b608215a62be0ec4 -12bbfe96ac2478921de24e525065c581f75fb64e225a9b6237b2fe76e574b183 -12c911cd260675df7044b2fb3aa48d68c6215cc2c6a5edf0119be5893b95e679 -12d1bab7f4258daad518cadfa5053e6b89c4224f1c6ac22778d2f244060cd36a -12d1faf5a380b4a99c5fb6ee16eb5c1de4aca66552fd1ed6e5ec77d2f29f1d88 -12e0644329773a327b9bfc3fe6b34fb3b3160ee27e029e68757d80d0d0a6edde -12e6814860a2a03f3b8764261872094824cab76d40f01f0c9f205d7320d9b14a -12eb488b09d99ba4f2398c90166efe09f6abc2e0351da6319acaf5538111bafc -12f41120eab79b7e81c1650a7be4359be107e85c9fb399d52e34780786864740 -12fb2af9ef04eb697551b49de9d9b50c82ec8a2051d3a524b21ee8618a4af3f7 -12fc21ca1cd18423280b41c93efbb58465e91c320a9e6514e8fbe1931e950470 -1301117848b275207601f757972c485ebf95d6d2b89ae7846d7b4c34b46e32b0 -130d36f306603904dd43c606397b369881ba3f1159fa93b4217ea055e24643f4 -130f193731ec0c63e1b9443b378d0b7c6020fe7221800ecc99f29c414af62ec3 -131117f92ccd5d936ec231974cbb1ea1dd059975fc4a5977bd2f44a6dd6a69b5 -13121c8f6876b131dfb68811c0e563f25978d085bb4ad2fa9640eafdd559b237 -131a2e95956ff195c4893e571455935b325e5d57db7bbf4c71ad34573a7a7db0 -1327b3a2d4a787016e5de7d9d9fdf8c4c326b23f4ab9aff1c607fcb16ef09f9e -132cf9aad939026c62838e0963863ded3aca18ba820145b296b77890e4aadc01 -132e31f101949bc0761e73e0a0b6ca61532808f8d984502692fd0908f38e0cd4 -1340d9062593b1ca3860bc51dc01e75f81ce680d2ed109c6a27c74dad3ef0a05 -1342075ebeca1ee22ea20dde347cb5c5544f4659c97fa9e4e12e6911d96b01c2 -1347237905ec82fb4cc980fe5b5ffef7ea81916fff7b466ea492983eb99db5b9 -1347e4cdd6ef61afd8cfbd3f941dc0acc6cb13252f27acb12904f41d83d43562 -1350cf01bbd4edfef75d1ccc3c06dda64f0478a6f2087108e4518b84c01104e6 -1352b4bc521ab556aaf8653c593e10c7123762a790193865785a57def0b328df -1360b657cae9c6eff52e413c99a82553d6be78d9e7e750d38d43668d3a0ad05d -136346cba103b1bafc2498514fb15b18747ea522470a1a7cb877dbe16a67b81b -136393dcf1875652b7ca733e4a80eb530c6a585a02660dbd645d7e662da215dc -13687dc848f33453ad7c939c7ae3f2c78513142dc32f51dd6dc38eed54618b0c -136b31b56a6f04c01a917fc53e2d7c75a310d32a5d07361ff836991fccd93175 -136de7180a62b71d9d959c75456ce5b9ea94d7712225b16cfd476788b27f243e -137e82f15530f53031a77fe083e09a9041958e797d056c20221a4b4577984902 -1387319a24a4a0ba8a21ff9ba53b153d6a6e741e1afeddb5d2f25fae29152f46 -138971e08ae33faa2fc46000354a8c6a3ff14d38a4e9a50192df788cf439c0c7 -138a6cb9edc1ab09615f9e2bd208d04bdbc78a7705bb212d11821b2d9ab72ca2 -1396ae4721a914dc346acb0d521a6cc58cad908bba54032a4981233bd3d578a4 -139ab768bef4debacc4885af7ba9dc1deaca8afe3aa73a72882abccd6df6c25f -13b1aaec8c9ce26e8b8191a0d2345f192104e2beec2c6d86602dd860fecae3f1 -13b3eb12e7124e7ab8f1538bc8a9546ced8d8a9b8dbf07e20fbf80da00799d97 -13b5ae4bc5b6ab32ad030e584afcd566ed0a22535306c7079e6027493906e649 -13b7c31e63ca06b5e39e5104d31d93f97d6915420eb0c844444e09b1d3778bdb -13bcfded3231c1317b9a4e71adc754b512bcb743a104ac11bae1cbeb2f8ece3a -13bd386f3fa37abd02df8699b413eebde8c6871ee48e34947df6813d55a93f08 -13d1ee274bcada854bfab75a61d74eb845eab7f1c4aaf001d2cf69992b8f4a12 -13d638d15d0e49f16d540f4dda92d0c74b34a75a909388d4ecdd3906205e2f58 -13e7904b0b0ce15cf5154cd57c2297671c50539227feebd45466e7555bed245c -13f4ed8d2ca98a87ac8b82e2f12cccba4aa2dff99731281d3e9f32996066498c -13f8bd068bed0c92fe90f857b0b4d8b4414d532c30589f5e691f0e12d4478307 -13f981a37573fadb062d36aaa4f1543ef9c0ae67f981e1be04aa354d1dbc3cc0 -13fb6f03d4a84678f2a46b312072c9dcb2ec43d914c67d789ecfbdf3d94d67fb -140a35b16a253d371350fb8e3a93eb51454bdb0572f2d88bb7c4c6c6df9da5bb -140d76fd63de47a84f4235a3c6a58719b3edec531fdf9f771e6833238958081c -141091296d8fd59449c9023a69c11ebc79fdc62866e3be652405c3dc751ff496 -1427a974068c34a753269221179c38c40df3b3cf3c8f94bc27b9f2550d4154f8 -1432220388c42d034b21ed84132c39fd06280dc49c186017a0ca04f9eb99ff21 -143fe88a62bb964f8e4acac354fb253c595d849866d14e0ba152e5d34403121d -14420a6f7b2bbbd7a90781b1b8d72148b754ba66d2b16e0f6dfcdc8db50922d0 -1446074c7e1398bb44139d41ac8acf2736e3df8feebcb84f8c1b88fdc6f03d69 -14521a15401ce994dd25690314638ed52dd087eea433da16737a94f2fabddd3f -1473e63297a1979475bdab13ee594e6e182b3abeaa1459e40c31ebe651880fe9 -14779418fae720a2c29f6f828c0358888a3d3a8fe3a1a356de6cceab35b9fac5 -1485da6ffde6976cf3fdf2274ae3108fc764c78f12b62a26fea63f9e93daeee2 -14884d32c976fd2b9804fba92726635cbc0f4a4390e44856961b72a1ed05382d -1494200891024b09770ec22a42648f95e687506a17eea24eb2fe38aa5af919cf -14942af7de9569ee1f6f238c6a0d0c4922297ebcd5b71deba188da338b67fb11 -1495b08d24eac30c03c85a9952c67125e43c48aac5fbdc1fcaa4cc773c767a10 -14a7a9f1eea7c685f35e0a0db68d8f05576a2263cb3b8a74a9f8db4daa3dcf49 -14ad813ef6d686db11d5e3da66b48ff85b916dfcba9fabcba6e6db5227d39f35 -14b80de84bfaf3aed715c94732069da7907f3433d79f5226569107f07bd307e6 -14bb1a814c2d5d0574bc85bf0ed46f7aaf255a78fe9917b49b2e3a2db8ab9360 -14c322a44e320d5585830594a6d43a9251b8917de026cdb1991ad0c949cfbaa0 -14c683051074951d70395df4ffffb780ba42adde48f2d28fe71da76abf9fb737 -14dea4cde51dd45bfb4637388548b4385f533288fac465112f8170f5faad1a35 -14e3fdc21410f462a6ca8a0646c1a2db64383eb3dd5833fbca5992a38d3cf99f -14e66944ff7bb446f4146a1b9c3a1d44e0db1d24768b1b6b004917887241aa18 -14f95232d1e97bd5cdf56b33693d8268adeab130516c53b01a6302d01f41ae8f -14fae5ca7b6f38adc35563ce46e8b7d756fb25df8cc4ef907e96c1cf31db8da7 -1507d1a4e8b996c566b76cf61fcec4cc58560a3c7c9a8ad61cb1b8aabf4c33ec -150ca147536d7e808ab7905fb5fefa4481e1b38f91eadedf6ac405d015249b8e -150f83596a817aaecd064133dada9b2fad255bc3715a21fbea26c6dc9fa184c9 -1514e48a4bee1a05597cb2e71d8c8844cad7a08fee1e775041321e3316ec2cb4 -151f481048ffe20b948713fec4de6704f0f7b3eb76523c805d9e83d53901eeb4 -152040ecf3366503e7277c13031fb56af7108d9535c30f295c855c48b32cba28 -152710481a31aa9252cb7d7a28b9e0f2260c9116c8faea1503777c28b8e976a9 -15345e016a0aee1214762a6b356255e0bce540bc16598a3e50710313f71c38d2 -1537137067a71511a41a88f1201e72b92b6b749f15c4e4f276ba43c37e0aba40 -154bbc6cb2ad505c5b6a530920d30d3aaf5576bd7d5a8c2d910841d2e34fe337 -155b0bcc6709069b6fdface89ecd23b0b32178b7c5d3d4066be14a62e5d56c4b -157068cd873fab755b180be602d130cc10fe5d774bd38e8b167a9a78f37a9e90 -157a65d42cc73357ae9a507cf7c72b51aaa8cb5148dfe6b1bd1f58e21f5cf438 -157b08855d2700895255dcba53f9ded5216fcaeed2bcebb15b1002531f992811 -1582e033d06550204365ddfa2202e653ba16f19b644484117fe84c833b5b7e58 -1587e39d2bfbeb5771fa87e303e9e422da2648a0a4828d06c24ac45adc127ee6 -1591d886aab8d9ba93f51a8ebcb73f9d94c46f12cdd0959560249d654878838b -15975241aa2b05a06bc9dcc307d7c450d7ce808ded71d97f07a438502bfa3943 -1598821f681a6ac9ef4bfde658aa99e18c46e70ab67f653b54f2d772297f4ba8 -159f30e6ff86d4e08e45131e53e0b3eef50981461bd0ee035f7e1a38fc436d33 -15a61f936f282ecacf51aabcf429b5894ff5ce2a0e358f301d7df95219d4bf51 -15bb24231f06fb115adecc03a6d0bb2d439ee09ac3d07ca78db8ff360529ee2f -15beaf57e79f83e778373953f68ffc90b664dbd28a2a1fa467bd76e99143d912 -15c0b9a148976ee19a48ac1a56dc4e805fddcb9df5fb2aefbb6d7627c1a34b79 -15c55318bd52ed5dc29edd598b85c71faf8b89c717bcc5718aa304d0bac0e43a -15c846e1980eb32bdf35087939b8c1014c6e11d8ebe975c2323a493b19a94068 -15d0fe2ac7fc4f5555ae167b0db9fcfd79d411a41530c362d273ca7ba0b9b52a -15d4316d06e146db379aa3747cd60d6a464ce95975f8e49cee5073186db26652 -15da69a97243b64768c9af9e5de0f003614b9ef76ca9eb5bbb3f177de3c88e4a -15dd8d059d92191b2ad91028ec79635b698cd54c6a8487bd999777422cfe86d3 -15e1a70c3fe122987bbefc4099d1935fbde1a3a14caee397112d720ea19bb40f -15e3250846be9ea154b1ed1ee8c6b742a388f40b962d073ae2f5b67ab3afa634 -15f1ef8a4500adfdfe0c4a1195ff0dd7bafa3a1acb802fe30e29f633865f7545 -1602e961be32584bb1a316163791e731733dc1f2f2d3aaffb7babead99658ab2 -160f3db92830eb745f8f7e1bc598d40ea6b7ca485c6bb92c817ae411598a3f71 -1626e20962eae650a0593d76ce37d70fa3188d0903da5ac9daa29bda6578f90f -162f5a31fef5b0b4368a8334bb9a136b949dbafc9333d3426dac6969b151c635 -1641590535913ef5d6d0e266f2d70f05f87ec46d7312b1f4148f2db0b1731721 -16446de9eaaf71b428c9b201d91795b60fe2d5830831c1b9e8bc5b3108722367 -1649a1e26cf4af53f9fd70432be6236d2e8e859c0528e65b898830d18b53ee9c -1657870e07bcfb625ac7709d819dbbd3c0b28a4f141694237d451227ec38250d -1660fc6abf80af80526261d2e547a30f343e2e38da36ba277ebde0c7764fbcca -1676473b0fb26985c630e360fa7bf9e82e2a234dd413664787c9e494315ee17e -167acaaf5c2eb3b3df31d1251ba148ef1e289d785d99f263516eb6e516d32284 -167b8e72ba05c652a4481748ca16528b4a4c94786b6ff7c2bf1fca67253a8167 -169238c60e75adbbc052fcdfff6c04c1fbfbe2512975bf76e2958aad5abc0a3a -169f2b87e9b698a6a3b50d88b66c737a0b1e7e0aa16ed5a01197eb3b87bf9836 -16aa373fbbaeed5b73366c4b9f8b40c4a43e69e8e802008b51ba2edb4cf53014 -16abe8ff6dc3e91d768b4788673225a5f633ddba5931284806b05c885759f3bc -16b5f74b9053c9050238f45b6a95de3100a563f32337d56d5922d48fc16df13c -16c0f32bf3bdf34a23cc0580bf9015b5b8e0c9a3c6c0a302294fcb706ce80821 -16d5039dc0241f8df751f120c6a9002dc80baaac197a77cf041f19ebf6febe88 -16db79e6ba1e6964787ee94a9f3ed19d81951b550144e70ae7d1261bb6b8f2cf -16dee607063777547c1ff926b71b1c7f712edfb1243f119f131a7ae109de2e16 -16e99365865f9519873848b753c653bb9d5f7bbef2679d511225d3c2652ff3c0 -16f2495e47fac7c73a75e6459d40a8dfc1156c1b327bc88a492ea581ef411eb4 -16f626eceba30a416c24efe50fa5d1a7f435c613f30d377d7ef50355f32d85e6 -170196686a3bfa30a885e7f6a54287f6503d9f3d01f69acede0791f993b2beca -171104b1b32ba885a6454dfd092609363838462dfaf25076765862106cb704f9 -1717c847bc2eaa6c118cbbe3ef437dcc5a1a0caa2561efa95eac47e0a56956d5 -173bb215758d9ec5839fdf2a3017268775d77f4ca280671b488ef37e9e6981a1 -173def979b6285e530088ac8fcf9fbd000f2c59bcc8eb18277bb57024005e9cc -1747b3dfecfed1f8e38dab1d8d88d889a017e5d7c663286c9460121a2ba3b59f -174f94f3e8bfd55a5813e50c49ce92b2e61f23b64c9a28b64137702d939cdc3a -175c18941876a29631cac3815b4fdbca921487ab9d42115f744b63be391de84e -176f414c6afb21d6e3ab780e32347fdb123fdd1526796c6007b3e71d9b6949ce -178072e20747f952d9ce8aedb33e7ca8fdca87a2ae5f214268e89be4da00c8bf -1785bb2f9c1bd1aa83184b73450bf7a042383f16b29651a9a286409aff4140fb -1794b8ff575cbb9b447cdcd59be0858a9870c76b7332bf69b91f6bda0b1a2f32 -17ad90bfa7499976d7984fb0b33886d9cfe1c34183182998195590fabbd6934d -17af650406218a21553eb402b8ca17518a9314f926e1262f7e916b29346f4890 -17b3b2f35ecf894392314f892b8eb7bfa24f1569d05d39cb88b83787a3250337 -17bbbd7306e0213558f6c6a716594469a057a0913bb516543d83ed27a3293b9e -17c4d4fd7ce97e53d9c5dbd946b8677eebd7b6952fd2751b29c1d70e558f1cf6 -17c62415f20dc81dbcb9df6693a021693c980ab36cb489f4d297755f2ed32d1f -17d0190df0f30d05b8b4a213b8267efeb668eef8ea3737039274fccd97b1e222 -17d67c9b742f0c683e4b831c888bcd68fbef8983549260432c2e3c57ba8d2893 -17e08317d99bb60719921cdcc66513583c6e9148de5ef801c6c8e1da46bdd218 -17eb6ac1fa06e9b15271796f35315541daee922ebff1524640d3f67fe0640e2b -17fe30256c51ddc9802b4090663b8e7156f883d991177165f290fb8f07c69f0c -1804697c632544ed70ffca2bf3b32ddf860dee65f65aabdd4d2eb51a735feaae -1807663c5a20f5d1c85ee4ea98a85760dc3614616fa12a623e7baf57480a7632 -180db89b7df89ad238594368b8fe3cf61494a741ad8bf5a56ec374044171f91b -180eb8818889fa76c2f6de4d2dafa3d03fabc5f79519c256b0670e462944d3e8 -18137729981cd85ff2d5ddfb9d26f71d0234ecf0c15069c36e503962514f8314 -181a01135b2e1f51ef2b196e287ed75249732074d1d01da3c0d77449021e4972 -18201f8ffe93d5a78011be5fe7ea4e56bce3a2c55a27a3b8d5e60c46aa99794a -1823c2aa2f07351268bafbd8bc9549c78cd4f512889f305e1fd6026eba157e01 -18282d09a64088acb4cfb669611a7ab7fd8bd19bae396529637106a06239cd54 -182a665b19c2d9335fe0dcc38af5a04ac8e3915bff3d2e2effcb0b1ac396e321 -184346a9205c0ef26779f06b8d42d242edce247149c69ab9fa20c62763e2fc6c -184655e1d8c264d67fa7b5c8d60d3c9d79dd7bad6d20c4a78fc2efaeb568a493 -1863967f135e54b9cbf83ff4cf6d883b76a518982b77f2ac410668ee5894928d -1867ee6ecf1cf174d6f664b5cf30d45ec6f199638eadacde0739710cc4e3a419 -186f9dea4c7b87755a6ec783729d6abcfaf126336ef98b4001248deb3c42459c -187d4b82fa397494d9aa76a3043a8969e23123eb60e9f027d13acba6c3424268 -187d8fbabeb65da2b2676447d2f3e3429e2554c08be15d1c2de988d12e8d92d6 -189e1ec623f96c6e4b3f5d3e9ca9f08ecb1fd8dfe41bc362f472f482d343c2d1 -18a18868b250f2e1fcd1ca3cc5946df1cd9c42069d8318053968d0a18556e570 -18a244758740e49169d8497e4afdaeb573e9cac0cebd8e27d1b77f428dfbfb91 -18ac914e3722a91ac9bacacd541986d21b981370b64cd1b2320cc76856deb5d0 -18c0400e34389023c74bd7c705a40c445231e7045847e1bd84da8a68b65047c0 -18cf8cccf09d8c3a26a76e26fd9e28cebf411f2269276b18636113048a94a05c -18da7d562a947797b5ed406e16cc1082d8184b63ce771292241b88de39a0cecb -18db51dda4102cc3ece2665c29aa1fcaa905a4e23eac4c32989c4919f8053cdf -18e0f9500eee2361473ab33cd223b575ca41bae28be63e9ee83ea01b22f414cf -18e75a561a9b7e332549c278dde5d268179705cdee040f38ef8eb8f9bb5fabd0 -18f19dc5864d51c6cf23f233c8d75a4015db938154a8ea88f18859d1d906607a -18f38566ca46a546c1eec0e55f27034d3ed9084ba3049a5ad36fb6782afa8a1b -18f42e57864529ec57bcd9e629e09f30a69f8adc2879a3d1db35ecec4ca3861d -18f4d3c827a4ce913d86784a211f7432e195649fe4795a844a5313fe66559282 -18fe249f716cce7a067934685003f2abbcfa821de294d0eb51e44696fd5ddb38 -190556bfde04273ff2b905129027f05a6cf01afa388472d5ff106fb1353ab375 -1912fe04d689dfa0bca10f7edba6d86908e23535da305f952f487a7d537b2013 -192c8cd0bfb81e2e5e919e2885fdd7c523dc7f52fbe764e022d9d1c07199f0be -192f56e4001c45e6657535c820c73ce2bcb682d95d62aa1d1b1545293493a24b -19450f329dcc0727e448cd4ff96224e5106b322d7f188f89d290cc286ab0997b -1947868bbf7bd877ba619e518f95b9c195dcf13fb81b4837e35e9f76360a79ef -1952fe302a711c2958954b9e0a75b8baa97831af5b8332b5e1e60e9824767dd6 -1965ef69231a5e57662bcb465dcf7579029e6ce9feaa897513018d8137398884 -19661ea0deb4f1eb65b147c46445dc7e6f3a28f61bb3822ad01f0044631e924d -1983fea9f10d8fd75da7ed12dcbe59a12cde092120de844e855ce15b67483c89 -199a9b838844113fbf643cf78961cef20918ca4af52eda76d855f06f4cfb878f -19a1710c7f11033bcea96395c06139253d901707924b48b84e37ad277ff4131c -19a612cdbc63bcf90f272787087fc56bdfe6336a5a2821060c2e12986e928c1f -19b4bf216ebaa4f7ccf55f93208d8f22c19f492a29775710fd453ed182ebfa5a -19c77a4ac5605749a41804e8f341a486b37739ec5459d4832f8477ca3b34c0fa -19d83d351a49c07ab307c4915048bb4674800b816fa2ae56e0fd3cf656396eab -19e40c4ca85c2dc6355ed93a049d062ed0f2c290e73b920fe72e6a2d0d7b0980 -19e68f0d75927008fdc14fc5b815963aac716e6e8380d4ce0d1b781ad98c376b -1a03370b7fd5efcf3ac342646379779b77d450cc41fb04c4bdd9f9141f2d9075 -1a0d162874baa6508c7953b3a091b87a6c78393b0c9e49f85f7ed80c8540e73f -1a12da96d247944de936803387ff8115a80363006c5dd992931e90ff2864d263 -1a194998c094db6c58e7a54ef200922f2e05bbf355d559c0109e67c50511bfd3 -1a254885d6559d1ef8f4b2fd7109c927fe58ecebc58ee353f155dfa6dbb68a85 -1a25deb3fd8a87527ca72ef10f605b4e79cbaa9033eca3a04697ee6af5667474 -1a318f30883295e862dab079e0625946076e43a3fe7f886742d4ab8c81c78a55 -1a35a1f2871f639db2fd4265e18cd0e66e50af30f292cd91a863bcfba3185e0a -1a396368dd71c1ab272c1f6e31fb2ed9bf6a5894e6109c40802e37af429095df -1a3f4fe09829ac498b00f6cd43119ae0c7318f5bf7af355b28c57b48564bfc90 -1a470cd78cdadd15bc81607ab15177543055cb3e63990565f6719796304fcdf0 -1a487f8d9f133079f5a05f87d72c8a2613b20925a5aff4ac9e998e34e4ea27da -1a4e9b6664f1f2aaf141fc5e18acc2f32abcf18a694bd9e3f829d0515cd102ad -1a5979b8188cf7d9d9e87726de68296a3168b9adfcc3c4c263a9369e2d7c44ab -1a5d10a1854a9f1d494b855fc12de0bf309c831455ca1b562dcac0450e42115b -1a5dce411ee2ce8609495627782519381330ea70e84c313f39dcbdb2b37e23c5 -1a623f7f7aace62d349c61183af10742c45d37675dd3cd4a9cfa2858e9f892d0 -1a695462323cb2ca296dd88bc8e04751075535794fb4b204f054605a7b6db1ea -1a6eddac71c4255f7a91580d77c7df05518df7296fc6d009e8cdd0a49cb38311 -1a6ffbabc647ff6bdffc4dd29a031861c509d086be26214547131a87772935d9 -1a733af2667d36a0948e9b3427ef0cb8d9a3254f98b46d0d2063dcba59350727 -1a81e70dd778315f04f94e6bd6306f4fb9f88f8a236218f8d86aa3e333838dc9 -1a971e5e1d0cb8b05618585e6986beb31b55d0a5336b7384cadc1c3e087a827b -1aa88d97e7595c98a2fc13134427f5986af2a0107158a23745077d20c2fa37d9 -1abbeac1b2628a7ecbbedb17ade6fb9b85c9535ef19ed5ec99b8e1d60ace9712 -1abd73784fa7951112c7f815bda9fa14ff66d8e3d2ee90a546fd4981cb4f1aa1 -1abef397c1759e88143f39f359f61c0950e5edd80601902af351432efda9a7f0 -1ac20aaea5262e24b84d190513f76c60ef04402b8227bebc44c974324eb203a7 -1ac23c1e1a2d13d5edc95f9bdcad9bed002bfdb02e456be34fc8a851bed21f38 -1ad977d9b8a621c97fd2844e27dd794b49cae1de2c0fe337c12a4b43d277bbc8 -1adba94af065e3037c60573f7541199997e8e88d6c5eee3873741bb43fab82c4 -1ae33ed55148822e525c134e57f6c29930d94297e9e6b5a5109fa4d5ea805f1d -1aefe3e139e9084f1ce4ac1c7f14ec085918a2007e1c214381aa699ef4057a21 -1af26dda38ff5d4fa64eb1705181e858c61b9675d7c73c83d3952578988668e7 -1af84f9143d1f91ef7208bf178b7d1933845086af759519f44558c363fc27567 -1afa38b9d45c30e6c7e82e9bcbf1195022a78bda46dc111d51e6d81319190487 -1b0595c7a62901bb9a40fdabe664d8ef7ac1a83cff5b5ed48a50afeff39e3d6b -1b0ad21d03389e62554a7b05ea0a19b2acae21cf1a3d5b10540a4d20c8e6f125 -1b0bddaeedfa227fc7fcbd726371798a5fbf3454dbb587d81b3498de410045b9 -1b0cc26f48bfc4aedc9360bfda24f6333522944e577436d6bbc3b45eb94ce53d -1b1326166fad2bed8f1d8b789a0a7b8c7e38b7f57b6da2fa11032320df2db2c8 -1b13f49d968973504d121f576c771d64a4249d82a03bd2a2851eaa7e9c4bbf5f -1b1ce4e0c9ae12b94c0a9570f2dcd35ed17ff5d9d1d6483dfab3f63242c41f96 -1b247759a8aad2792c3b2f469cafceaacbfa5e2f2abe846254fd9407067cd231 -1b2f16532fc7c0413d637f089c28deadcdef0f8648e9fca8a07bc9810029c741 -1b52d711dcab127884115c69348d3d09035e79860c841c859a485877dd6bf8d2 -1b578b7ac97188ffbc56d5b26072a3066d651792254d0710b609f39e880d0eff -1b61c23a5c1e452e7ff5545ae9d28b0c9fbd8254eab617b07b018bd7ead401a9 -1b6b1274bdc0c2973ebb1b46529731b3f254123fd811afa63b12c096040f1fd6 -1b7a83e3dd7ffa4a5462e77a4caef5d8b5738dab8ed79f899926a750aa5467f9 -1b869b3a083f3cce7cc60fe3be8f8f44d4c998562cef83bb0e65a9a0fb075d7e -1bb4f54fd7b860a1d539dd62c27611f7f2ee31b9a5d81cc90e447f7ffcb1ea66 -1bcc01ce0800b0ee13481668bbb9ffab55e68e78563815a720abd2ed67b455ab -1bce44baa6eded3627c604b6af68f5045003a01d3581620ff22eab422a3c5714 -1bce4b97361b3905a6dce201c0d90ff52bed6f2d161ceda8111462801ea6566b -1bce4e092462871cbd6cd5a38fc44f12fefa156cbddb4aa1825adfa3a1fd7a95 -1bd91ae94766730a6046d5c0309dfcd85b8d873e8ec3abf0fef0c55eb33162fb -1c08cad49a2b8084b158b1140435825b399fc8df167eb43cbfb9ec826bf4596c -1c0bad659c754ec55c3d1143351c4353279bdfa22e0f2d834ccff1cdfff7fb77 -1c10b1f157598a76eec298da287eef52b119211331087dcba2f3c8cc146b5655 -1c199e077fbe3c2961eec229714a6a48ae0cd8ad50b28b1c0d4cd8cfd790dbba -1c1a08dd6717668daa93f9ee1b90af1b05e4595830c552f733c3345ff7412626 -1c1cf0d32f8006ab7f0b81338a4d1ac272aaf76cb60e4f220afc4803ba694da7 -1c1d789cfec0d026db97a9a2119ba396c7a92d4f2efec7b8cf89701dd8957a51 -1c206f0343ba649b7fe19d86356aba6f28093520dd3b66c49b8b8388840328ae -1c23decee65039ad263d2e82f864f004da353377bd4ea8581d2b402f5dee0d63 -1c26fa3992facd4835ff0e82388b62a8c6b9c9b40bd912aa4174fc17e6da5a01 -1c2c209a284df2736ebb85da2361ec76c22343f8b9f97bb80ffd78c860bbbdf2 -1c2f40ee516ebd3f1e12bcf72573ef8d6cae7fcec6d4a2d1c8d471d0caa1a378 -1c3247cbf3b526215a4da5e95f53b77c85e4fb4a22f895104a8525e8d91867f1 -1c36a367fc9ef038aabf5714d61b861a0a786f23c601d3bdd296ae121ab50c58 -1c3cf009248e502f8a40ba3ea434fa65099f5aac9e5f0a54274d72beafc21def -1c4b5b05e08cdc4903b70423590e5ebbcd44c750216451e43f776288d53c17b3 -1c5238b3da64f427f686e008f7fc3635baae846c10b1a8cf90733436da275c34 -1c5bde5f0e2899a73fe05fbcd6f1ef3e680561773a962209166a7db5df0daaa9 -1c5dbcbd8266861db168b590c7b8590bdb125a7205552d1a91cf019d5f20cde6 -1c63b60c76a1764d481d6460189f0e6dcb34e24b4159f2acf613a6662b71855a -1c66e6cad878094d49c2a190604a18c361926a492b540d43a7cb39d0dfa6ab9d -1c690481dbe6d69d73654354635eb5d31a54312835e4d2b49fad788603f4ec92 -1c7098c368fbf6178edd3a8b2890d1e955fcd7467b000c4c98f7c838809a3a63 -1c70dbd14cbce5b547103952f2b542b734ee0db2cdf2442a6df71e52cbe9175d -1c72d2f93cfc0002d2e5bd6360b2405517d229f6cb499c6cefb691bb3717606b -1c9f5f7b6b8e6233269b126df931bbb9339adface1a5f083730ae869baa403f7 -1ca050a2846022e9c7b4d11437447a0054536075fb09990254a4d6098b0091c1 -1cb0594c50ddeab338449d7cf3d9ab73e033d981b9968ec47ac640002b50af7d -1cbe15acadfa518ab1a09d2e1ac60093501d494f142d021d3723145f76a13774 -1cbf4884809573fc8ba65990a3af1bff96e312b2f4dfd9b8b5b8bb26c3447408 -1cc4038d97c828c4962a5744253b7f3fe591aee819ad07fecf9352d926227c3f -1cc42fe259a96efeed9c7a3ce0aff65f7ef3b5d0875477da17d08fad262f338a -1cc57be625d0b78e5fde6c817898708d08a4221b43d51f8d934af97fd3ca6904 -1cccf88516c18547487d15a5d29d78bab3e9ead1f219ef5c2a951ca08ab414db -1cd7e9ab83e9ce637a8d655b1600b5943762725135b3c1fe4da13d636bfba0bf -1cda848ae864cf815d51a6735a3aa7698182a2afb002ef7a6bd4e1e8f5e46b86 -1cfbecd93bcdc6e36d519aba2d4625bf3a92b5805aa070f0dffea1dd2acdaa1b -1cfe27b80db0db58d3d6ed7651a59c430b89cab8ea1aa2bb6cef2572cf578e10 -1d0a8d65cd9f4f81b057df2b625e666e65642bf52ad3007c4885f1169b71ec41 -1d16cd711b7c74db8c06d54a3c7f8c7d7b5b8972c045c3723930e46966fd981f -1d1ef27eb7f3985a62909861c21375cdf318d91a3b5dd1f92794902fd9d1eb89 -1d29e28b127a53f0bbff4e0b977f9e927d2a3c9a21ae13a514e09cb817323d8e -1d3a66459a4dd800ab6b385512a9fb8971c69da7bc7c7e03ef7433459ad23f14 -1d4ae945094ec8156b539a246a289ce8da2d0690c2021ff9b6f7f41a1679647e -1d55979c44fcbef91ce968297447980a91c8df45a6e5f6d32461f84b3137e12a -1d5a2fd7eb56979b5eaab707ee44963dabb5d23e8aa39947b3ef6f46feec51ba -1d5b6fb1858d53656341e504242ea9eb9a701442e62f82fcb29033966c4645f9 -1d5db178f9bb454a5fa33086d1e993ae9c26c57f961dfd0350d4d573e3682cf0 -1d7145ee1bf52328861318d4f211491cd2fffa9204e3dbc5b4359c491bc361cd -1d73ae8ecea12e4af572d0d60e97a13b7c4dae4c17b619a49c2580ab0130f7d2 -1d74ded1b585659872758ffbcbc3880ec1bec4eb5c81cf716d58308dc4aeca75 -1d801271dc1a12e3550847ca4fae43af9a98f09bd612fd5e15eb5372e858767c -1d8d3ec43ba1551b9a900749609b9b9faf8761bf13efa39e74810b7c0eb0849a -1d8e9a0bf639190114b7010cced50be3b8f024df5417a1b1f2d715c92223f886 -1d8ef9df17ffbb6dd6c76c0c8a980a97ad885cd50632add51af78d35f18dd85f -1d8f19d20588796e738b2bb24ee814ac1dce67e28b8a2294e4ce443d7e01b65b -1d96174ad7007c94be22a7c1b61912688dcdb3ac9044bc32f8655ee8a63fba7a -1d97992d7fec418f739ec0cca3b42148917dc2aa7beb476898b7497832327091 -1dabb2630affb9959de6da18d9ffcdb99fc7f72b6ecc32541767ee588d09351e -1db22a95007eb0d6b6d0098e24d9c72a1c7168f1f642a8c60cdf214d9b24fee9 -1dbecbd87bd2c57ecf7c6ada23deafcd27f279269147a32edfdd5c5b9705d800 -1dd0b8d4a5cfecd3c19a0d448d1a4172d1104832e90f7cb7e84b2615a849281e -1dd127463c99d696529d7b494381b255ba6496876021aaecd3b5a9d431a895b7 -1ddd2eacae2c8d7d0076660a6e6c614b0cb40c2f00a518b953c738543263c7c2 -1de618af825d991ac6cf3db6160e24bf5245bcf0f838ace6db1abd69df8c085c -1deb0b39d8708e18c647b4bc8ae82a119ea176193eba1e02874364075ce603a0 -1def810717e69d6c66269989a1bedeafe814c45eb530c27a7eb1f06bc8ed8924 -1df3254c69abe1aaab64e9627688ded97f0943e5c1471f543627d8981464ed90 -1e0e832c5dee46a572fb263200380b6255cc746ba9a24fd14721e9d9ff00ceec -1e1277931faf2bf14ee6cb2a75588dc19dffb5fb76292a32670eeca94717f314 -1e12d1a794bb3df99a96514c54cc381b5f92bd7832beccceac6e29bd3148749a -1e19771d5138becfb8381c6c4e3017d258e30fab380d353d1b3c5da9af266065 -1e2449f86034813d65b890eba7506b38349dac93cf5ed568a28f120bc00167fe -1e26493e8c240e4da5bd636170702f883835aced1c929e345fe00e92a4d7abc9 -1e335f727d30a8a26f7f81d933dde15da9cf3cd1f9cf1571a3a1866686ba8a7a -1e38b2ebdca0ef879dba9944ae479cbf4fd36e524e41d5298fbfbc8cc0fd9c07 -1e3b481bff6ddda7dd0b0dfc90e80919b179f83eac7cf2a751f143670d8b2163 -1e4426307b701eacb4af8fa5077a8c0a10fb2267c90bf12044b8f44351c8b44c -1e4767c1a0236531b35fed35aa380889d17d67fa1ad797d22fccb5ca32f61f4e -1e4c7e2219178cb5d9065614c8faabdf3d36283b8e95dcd74b65ea16d6a85530 -1e4f5daa4495c99d74976a5b5dc9caa2343ab4f09141b59311aee6997dad9c81 -1e54205498d4cbe847cef89eca377a5aac6bb5b39b029c6306be4645160554cd -1e6287dabf363684a0c109a91b7c0eae2cb6dae826991b05110222d855481ae2 -1e62c41c8e7d11700ad91028ac467b88a73354af82c3920259892dcb1fa33b39 -1e6358c517612594029be7c643ef7ab47666251e1da5f6e71e25711f5d358840 -1e6c26f2a8c937c239d3f153244256196a51b785d842af4a1e3f0f3fa594ffdd -1e6c9ab63941654ff40a1fb3754d081dc9b2fc8135ab1f88792b33049667a5a3 -1e6e22dc23e34a24c15cb4f3739852ec93e416ee877e1c89d0e21a627d02a824 -1e70a10cb887ddf77150e94b8d1176af8a8b367103a369b6a77e3f0323c58657 -1e75eb94fea5a11924ae3f324e29eba5836ee5c9de38689944534abc41ef3533 -1e78428d80e5680290609ec9c7cfd3eab45c6d040a2f02b1fa193190dfab3b9e -1e8c1d50ef4ca034ec86ec3d1c9ee3eb0f21bc86c40752a7974bf1b17479c11c -1e9b22f63c6b6e8e29e5fabc2c32ad627ec5880480e18c47ec909dd63dafdbba -1e9d0e69528f8751a030ea00e75a89b5f9183edab80f3c7efff8281a3e29bde0 -1ebc343baaaf041d57a2226cd4663a7b0f8eda0ceba1c325532aa929c2701f11 -1ebffd6d8e77d25f30cbb7e66bbd860755a0cb5f49c27268673beef8703b2907 -1ec28940704833230db1469fb4c958cd3b890fa7832b3de5abe756f280c7e838 -1ec51a0d90fcabc52746f43ac93b4214b2e4afe24b7f1edbb260b53d3767dd3a -1ecc7dcaec866ee2782841af4f4f9bb6aa166f2c4e775b3929fd0bb4b902f066 -1ee05caadb2028c2b5de9b496d63b078c67013ff533ec175eacb837ac484171c -1ee2522c0d1840b0025d31e30905f1b13a98ec2b0d45399b79f2ee206e5be8e9 -1ee33ff95611a5f16fb094ba73bb92af50ea93d6ec4c35f987e918004de569e9 -1ee67ef9ba07d956e909c52b8fbe81b663ab1310c2fe93fceea1ea5b506e07b4 -1f0b73350113059d0017828120c33c102ad1bb240a7681943f395d349dae557a -1f19d6694cf986e4eade0393d1c65a51c42a11d6e2ade41d03fa84dba177d6f9 -1f1c4cd80dee4e81f9cac0492525e212aa73d83922a393019e69ec7481dd9fc3 -1f23ff05426f4f192b253c09c4b9ab59399cc135b29e52b7400474a2d9114677 -1f2e4a36b8c6c6d5dc676e7a40165c03b69692eafbb91e631a5875df97f40db8 -1f2f58a4601e273fe4b739edfa9dc468d6b2205e4fca085554430dcec735d457 -1f3a225bf18d7ff8f4610957527936aa2bab61068dbbf6488e6a9a3befa2be5c -1f476607d28f2dd96e619e9b297d38252e2e2ddac5e7f76fedef82f3e50ff451 -1f4d7ea16207f065eb5c168de835c5bd81c552d6691618d027c9f362e6b3f2f1 -1f54ace33d71094a698ae22ce5b5d09dc2776e3e075642be2fa2893e474a34c6 -1f57d0098ab8bfdd702d04b4ffec74dca2e7160d403c0021446b282b59d51a9d -1f582a512f479e3c9979a7dd0684a0d4ae0a65c94e16140872fd3f3ad8cee27c -1f5ebc1dc677dc128a8d9d1b94e6e3c5fcd2b3e3d06570211b4c5b320cd3dd85 -1f6948f9a59144a81dbaebff717ad81b1c9859eeafd82d74c01107837aab456b -1f6fa06d3d4a0c35838f2c6132e4df8ec8e05516e173dfa03148aa36fdc669ca -1f71dc3043e4131d878132bda49496d77f4273950ad45d4f53aca2a786095226 -1f75d6dc99c17c4ca2fb8d9e54ffb531a909ee3a87271ed8dd78809e3d84e496 -1f772cc98dc7edabf341d7b22ce1678d337dc5d5ee9030acd4c0103d1aee07b6 -1f78456d1dd292e51accf112b77bccbf070deaa3cfd7fd17d0c0e65c525b4c35 -1f80463c89dd53e08561c081e8a87b5f2437dc940cd5e1d67bca6e7c72315ced -1f8be3d87954adad1e79efea5e83fd9527beb8ca1ca7dfebd01787d84ffb30da -1f8ca800887ec9b7e69ac20ae518c2efcaf85e79c3c9a9bb20edc565e14f1460 -1f8fd2453428a8c20b3f21137d7cda099a91a4a6d35fd74fa3c2ee5cad14f12c -1f93662fb78019b3d606a00057479bd9736352781c3211e17f26aef97020cea4 -1fae8749456eec97bc8d71c3b4bff594b4a73f343839119bc22e66d051d3a59d -1fb2a6e0b878e2260c302c7f3f2859daba4ac440c2d27f00210347bf7cec0f33 -1fb5116f3fd3dd7821bf89e253caf65935ba3132f1a01b9b64b171b536ac2fe4 -1fbbec85016585e3e07bd51ae07c7d6c1c00fa502555385e5a4b78923cb0adff -1fbe7f70bfa7ffcf9e88e485b18e8720058ade7522c35cf01b51f7c8ea922dbd -1fccb8dc1ed96b3f04dd7ad7d7eceb2ea86a2eafa6223c37c6240bead72ae78a -1fd0f5cc4aa73320db5e209723a29dc30511b8cb90e861d8ea629603e5370111 -1fd9a1b1890d8f7f2b87df5b85cd404fbb6475329fd4cbfcaf02ad21df818b77 -1fde07c7e529ad71386c3bf089cebe5fab891e858aa1ac4e4c6280e676d32205 -1fe324a519207d195a57d5d789c246acc886b97e10022b5c3d53ac3c7e417997 -1feac8e438e7549d0a217fa3f1e74bc6660543840aa55834fca996b9fa501be7 -1fec27d0cc01385a88c4e92e1fc06b24a22dd264d4124a33f63c55e6e22eeb13 -1fecb74eef1e103a72ff97454b3d5614732a1ef23ef45e47dfdd926458f88297 -1ff11a6a96df8421cac1aa0c7476061556fc3cf68747a5aa5303772ecf957d94 -1ff3b4863d3ee7fc932461d44109d812ac88f5b114ba037442b57ff4501a937b -2000d83e77f3fa6e132583475a1f010afc9c3e4e2f1c87e67fc8abc73e12ecdf -2009526ca2b76418003a4ee3cb5b6f53fda08d3cac5a0f5495f513e51b65cb5d -200c00b3e0a3f76222b368942443ac6d3b7abab83a24bdac6f774f2bddeb7b33 -20102c0ace89c5786106d49cf81bccd8430268e4db95ac2cf5c6946e275c64df -20135702dc8325d2341f7efd8f27a82cc6c9c04d88a95758af4c64c825368d81 -20256f90466c71f9ab7da21c9ed66b7285eb32a903b2d50a66fd09d3b73627d7 -202f6c687967fc61fba64a82ff5c2a2991d339aaae138370cc1f32c1ed7aabdb -20343386786dd3e4887d6b96478c8521619d47d2bafcc5749be05f690e28c7bb -20386a22264420c8cd86f354cbb69fa48be437322f03a74f2c27629fc98e2035 -203d15a99f7ad4078198230bea61bfbdad963eb739ec1d78c85f534c0d29195d -2051ad97da43c12d0415ac8c3bf8e9aecfb5142688e16757ef4956a04b4069be -205a12242d08f12e71e4865c04f4afa1db9b58d7495d49f4c575d53508d2b09f -205de5d634d797385b2572880f0a73baa4cdae6e181c00b76b731bcb050c2e41 -205f8db71beaf8928d787e9ea7434c6878f5d6c666e1c0046bc28b963ada3f1e -206457b71fa828de047f4b208b81b065645b14518f5c398c10c0b56c11bf3454 -2064bdfaf6bcba7af933b20091e673df54baf6829bb5a46080823cf2dec82183 -206c25c6de92b1a888a983ae02ceb1935217ca37b5b500db4c9a094c4d995289 -20738a0210be45f413fd0639f4fb79edfa3bf63480cbc5d9f0c7e992c296064a -207dbf6668e038fcd08cefe13fec41fe7f89e0591d272c8ca114512b65c0aae0 -207fa19fdb133956967e2718ff599b7c0094cc7f2735b1b1d4d6e7a199ce39a0 -2081fe14eba4fbc5e5a059b184299fbbc6528b51a7427a8b8307beb329708891 -2084ad453b7afbea7f906c9f42614f9aa4e46628743dbcf5e8125870009ae966 -20854cc287f4ce5eb0e592571822512a5f93fae6ac67b3117a51e9cadea47bfd -208cbe8b70998b2470df63cb1c1559776a9ee63372d2fb1f04a771df4e1b0020 -209a4ab5e3eea503af350c7a384d2a1d30079476a976ba0d1e6e6041c46fec10 -209d83872495ab5eeb620b0edfa20a004d5a371c965b8e7efb3dd5a024adadc1 -20a23de07a115656ce39b717f47c4ad2195c608ffa83df82d02b46325f15bf4d -20a4afadfabc566ad89df085141f262a593a5818b0f40880fbaa8c14d28b219c -20a551bf81913e13819d2b30a0fb567ec935ac5a588aeae4ad4bd0ca1ed84a98 -20ac077a6cb72f575045f27773282cfeb172637021d870f047bd687b56b8f124 -20af335c511ce1ccabec4196320dd66b05a4ce5002b9edc6be7e57f4d4872cad -20afad0753edbcbaade5a621c7822331b8aaaadb78a31365e939585a6c8cdd26 -20bcf0838de62fbdaf76c7b4df3bf2807afacd46e43be4484a2dee3b70eaccf1 -20c4c554331e1232bf9c1a2b179a3c44aee1961bb5ae7814e23013155a615312 -20c8fef9c6bbb5390cfa52d28659ebbf2a7454c3477ec5a7c501ca7a53f5a23a -20c982b99798f42bd6bad4488810e96657a862d4800f0420b3a7999174af1dd1 -20cb1f8594b57cc83c086fdd6067381ecc1ad4959e665ff708a4fd8961dbe729 -20d02b6a232e1f280e32116f994f2cf2471cfec372381d0765169c4c6306ce7b -20d955ac4f1a7ef5b381da228671fca266dbe5b2d5eea8a6420f784419f9bb23 -20ddba51466fe6587088a58d8edde429f583af671fdd5c4ceee003861168ab36 -20dfb708f0cdbd062a74d22499f060da2acc80348a9c51db87cb68b797b51a31 -20e05ce25c3abd5f8ceb0cfddaddf2d2173ca5dc0d7d92b8586dd994270a83ad -20e981443a6081b3bf9cde7408860436ee8b8bf45d4476d64553a2aed1fbead3 -20ec34681aabd73a29ca01a5df05a46a6720094c8b8765c6c972d5952d297f17 -20f7a48dbf177efb83389fa074e06ad0756516fd4825a07cf599bea3e63bf8df -21017cd22bac9f9fe514c4fe192f901a7ac12d74b618dbff8495eb9f644ed0f7 -210504545e83256e16504d1d530f2cb1607bff1a47e37a4b64887171a8c2f328 -21068c482f8e99c4f143e3a555067a64a7345b9596c06fccb8e3c5a2f8e0ca6f -210beb283ea55a4944626894c89b4cf6c758ecbd3d7013fb2f0fc371dd2e86b2 -210c9098a6b6f1518bbbe034e63ce7eafd8344cb5fdb62f74fa80683b8ed0964 -211e3e13eae2ce240b9dab119fae72cfc9c354587942ea0985c11f8a04215cf1 -21206956e54fc29477a6578cc820e4388891edc2db865c3388bae77a65bf2cd3 -2141823dadaa7da76e3cbe334573d0e8fe55231fb05d57cd9fc94bd5cecbe089 -2144604a186c0c25c9ae294310bd833344e4b5605211e002616cca18ac65ce40 -2155d962113ab149a2efc3542cbb9f0df1f07924d4f7e45ab2c17075148f5b0a -215b6dcfb05c2fccd5d93bac65dd939f42154845279f0d0f5619498b1b5abfd5 -215bc19beb689b685e747dca91a999d6ffa6912776fd59d66626b666869611b0 -215bd97148502fe93d4256549821720f0f1a377794804403b2d17e266baecebe -215cc30ac6796c01efb12e1d6c57a64a69a56d8e1dc506ca4e4a676b984d1e47 -21658ac2f08d30f585f6522ab85431cbd04bc6d3f748a6c60e6ff447b23b1d79 -217182be44f9f7fe1e0ccfb8dadbfd11fc5b943c1df897d2168c5078b4d05904 -2174117d285f88dd5581196e56e4b9d0a4437ea079d1b9b1752e19cce251cbff -2179d57d8bfe50206480befc054ca387ebab63365a42462edba679721faf7891 -217dbb2db57ce33e20068f48aade28b697a8654fae2e8b822244289a38d26a1d -217dcd8ac138a52d15fdc34815b3a0e3822a4cc2756e81fa8e4babbca1f1277c -21852da3bdbc2e2e3b26c95205447fbfb0b58916403a9d77077b256bef3c2240 -2188a3aac1dc030e8458b2fa8fb288e0614f676c1287a023adb751e97e1ba6ae -2188e5a188b3e80fba033eff0a9f924c8453283c025fbb9718d61e64a1ec7fc2 -219617ccafacbd4e0c0d5e420627d725267fd8ae88fb2b4f357463de7f07a2d8 -219d744027e4475a0c5929c84ebaaef3a0b597e1d344293d12f4d23061ac0d0e -21a20da3c4501acb18d46583d3490565d48cac76ff9ef0354fe78ab01f1bdf7d -21b61581549c6d976520d9e63f7aa6b20679ede281360c52fa83cfd2389d3ef6 -21bc45c3d2d05e8fc5b81f6aa641916ba573d682faf27eb0a52db7b6528e27ad -21bee839c0356c0d82a1d49de3f17dbc5fc10e7912620adea107ea660326a28f -21c747c8baab2eb2d53275ea28d933b2a2cac875334ff6b80e4de9944fc0454a -21cad8adb19c5faf85a30330afd26edabf57e75beb39e0e8a22a4e6bf50a1a34 -21cf95436173c41216a9109f0fc3c86d57c3b739efd9f27f164ac5123a62d63c -21d1b34d2256cc32926f78eefa99451d33f4c0ae4df20c56475b4e4f97a07ebf -21d3b2c50d5341b169d286ad5b5f1e056637793e72bcac0b061c832d76866d08 -21df43ed3998412fc4f9ada7d5147b2f3ca0bfae2fe289f45f77fdf8377ec942 -21e30765ce3170f07e72b4b2f321d36e324fb04a206dfdeb0d9afb9546d3e5f6 -21f50f38c067338e187cd8d60f4787cf3eadd93aba13192d8fd562c26fbdc478 -2204741344c01a97459ea73016e308083b1d60ee5fecf9901d5c1ded14f88fa0 -220b98e4877545013f378ef822d8d75fdee2d979b82e22ebe0571afc539dd1d6 -221b042e2d3e8b5341e77f8ab8b83ca2b20691cd765a5aefb1c27a20bb1f88ec -221f802a981342a5ead76253070c58fb5cc46c6d5acec347856d5f9b92a91fc0 -222193a3a525ed239ebe4b72e7fec2cdb4e3acaf69fdecd15ea635ee3d8ab12e -222584ff4991475f505cba2b25a42a20f5ffcaf271faf1a5e2956f539a08b5ee -222f09faab8e5f34564fc8862e6d1ff05b2f388d2e0738effdf72657853a2367 -22313eb89a16e4d6e2d620835a1010d9b5da8e586036ba03ca783c4e295277e2 -2233353825ec6ec92c17c7ee58ba8da98b8f94838040cd373b7aca179734328e -224fcc371b2de1d47850671f3f3dfddb26d1c4c2972d9f3fa245f19220b091d9 -2264ba41e656db1374d19612f2fec52a9420624060af90973e5028da69a2f655 -2267feb5b0ada13eaa9ad742b257336d876f842ca4b3778b13896ae70fec21c4 -226a04d1edaa6408626e6257e02c16e7404cc6e451bdd55ea2aca6d65f4b97d8 -2271858a48c87f9299f38140f2ffccfbab027651560460ea9f3e38e96f2d1459 -2274f70e1671685eb0c2840a4bfd81cb98766b73e7baa620ec1fe950260e14b1 -227ca0bb0e2a4ee49a9fd530a472fd76eba76a8248fa43ac4d87ba3b28b31613 -227fdcc40260a610ff8ac1231989370d9180784f3233d17a650bb544e0dbc088 -2288990e3f5920c5485d5cbe764b814c51d86c0dc8747c3eff2a14c3dd2b305e -22892a85410b1166b9df969416bdbbd29e55bef722880871e42c341268e4539b -228efdfa4f00f339cd841b19e3c8a49a411a62f3efffd5c2719515dd7d7e852a -228fb306d9ee885a83669b65ac112885612e76bff85fbfb7a83fec96a443c349 -22904466ae1e501e4ff5a7b560b83ce598281cfacece54dd6ddde18850d60d46 -22921c9da32cca16b34ad23e017898c26923998f4defdc24b4a13a4ec76af5b6 -22a6bfafd292aa5c0ba63623c78816c7d9c7c9048b2dfbf1c3b8d3dafa286888 -22ab662128742f34fce508c3db0526cbd6ba4b18c6bdb5f52656ca871fda207d -22b6630b08e498f3c74200557254eea8653f5bdd9b78097d80e2ec76373189ba -22c4357d1f28de4dc6ffdc25a331fbb3975a9f51bbb93f7fb7080f9c0f35afb9 -22cc25786a7701092e55d39dd0a0148a8fb4f35db9170437bbe0e6a64be5ad99 -22dec70af1e04d4d3450a0d0125b90657a51c501196d9fa48a474ee4701f1e76 -22df2918a997f1c244327eca48f7176d324b277187c14c5db2ea1b53c22bcca6 -22dfe966670382dfd888aa918912dd358a6c0d6b401f894a61d1a8a27a19c771 -22e05722e6e1771cd1113fa79fc301e49c1a14a7f4d454bf95a30bcc20d3cc22 -22f1ce3581c30d7bf86706456692fdcb986271773b345f53e6ea0afbab95b15b -22fb1a4a941dab32374353cb4568c2d675bafcb1dfa476bf7afccf8f193b74d0 -23028d5736460e2fc7bb3acbec2b4c01b2f4546b45e0788ac81d34cd605d5839 -23078a26be7d0ad7dda4e7426f543bc3d5cef7799ee223d47da2c67d1a836011 -23164297d2bb83f7509911f785270e60a502a069618898073c17d80f5fac0af8 -2331e78e5c3fd387109d090df3dbcff1b3a5e3a950576a6db1281445180b5340 -2338adc87cc4928942615b848808e45d0bf1a146c6df822ed1af7ec4c14ea252 -2339d434617beee3de2b5ad5c86d633b94a06a67fcddb3690dcfb9f8043bfb2f -234e92461de3b933001000629139568fbdf841db7ef14a5522582191accfaabd -235dc178652480dc405f1ecea911ff5992a322eec6a23de0759593f23f32c711 -23602dc2055d26af923544eab5b400dd302187dd06f056857dbc461b232a2130 -2360618b92ec4e2718d841ece069cb1834916a69c612760d6cb9f8e21a054032 -2369430cdb64b6a3349860cb58b94d13bc505416a053eb11f2832440675c0e58 -236bdc080c486f467fc0a2c080be03e2ea0f063c5d20722b76db5a31bb21ce93 -237a0650351583281e52e2e473a896de12ea302294d97a8a056149328e8a3613 -237d4d78153ff456f9a89ea9189b655dc515471c40b59e47dab7dbbcd2d62cd2 -237df41b4bbff5da03de815035637052008ed7c8ce95049ee5c87ecc2466b0a4 -237fac8657d8911236dc79128d4fcba08c96417a6b7cfb095dddfab51ed2bddc -23963113da8e0fafaffe3945c5a26ee7736f6f690b9a4d4b826a9b05618d6fbe -23973df3ff21a5c29fbf965def9a00943ded118c0406073511ddc30da1cd91c3 -2398412c7cd1e7179e203abd866cd0fe01d4305db145ea529f3f09a010355477 -239f3d4d84f9600de30f22227e07306e21f3f23b677a41c36a32e58c467cc493 -23a102b52ff30edb0e7a495c4a68828b09c9882597d4bdfec985678ee9823f81 -23a5126a40fc8af0654c59c99c945841d6ea3dec6c306e22f1cde2e35fecac41 -23a7bd337ab26db9e1d88969883f4e11989010429a2637bb0f42f843534b8a9c -23b26c56e6c66c14582bc6513387c825f3e5331f34e726a7cab56825874f2e0f -23b6e978ee3b1f2ad71e9081892197bf34abefeaba24c182e855b3a8609d547d -23bb48687fb4b570f622d016297ca2d80bd404d5fac743971dd01d26619a43e3 -23c448fc946587727c7b1895b2fea7cec7616ad55dc7416b51cbf3799a4f2c89 -23c83678aa001f61bd699a09eba48fd16e906a2687559b875c9bc4f6c6a4d62a -23c95199086fda067da37d2fb4a9aa02cfad5803d51ab113bf5d80006b38859f -23cd2d2e7aaf33596f2c06ce8cbb5e45699aeaed9467c466243e0dba6f4d8400 -23d074e634393083e7f06929a1ebf87a50fd28a59ccecaa4c3d05a14195af5d9 -23d0a15352b26fafa37b055f972b9cc57b028ae85161169e234faec28c7e1749 -23d23c6eefd1a580eb8d3814b4a15c9724d3ad54a10c5308cb6801d127189a84 -23e508e5c5a29dd1dd44776b4102f9679e6ecf320e9b984592094e861ddc94c7 -23e9b49d19c8aba36c96c380201a5ec15092516ebe8ffbb1d1f258c760e20a31 -23fa55a9d945d6e3dc86c6bc610b349c0504f764cebfcf42ef71e9a7921a7019 -240705bea6d89ba5f655de8ed9dc224b1cdd78f32c1b2f05d3f8ac4e0221cd97 -240d00fba11a71c00e17b8cc8441a54aeb530932af61a7343d58b039b54ee8ab -240d7f22de6ff59c5b6a3fad0f86ecc8698af32b7cb21d37b4337769aa9675c2 -24188b49532383206662cc3284c10d8d1869b8cc074493262185eddb426201b3 -241d154dcf1c1d3768a94785f14e0eb38273cdb6b93c949c3f40008a4fd7f591 -24220ff3f2c79606ef14133ff5f72d3b82759feb59dc0eddc4c588308595feeb -24247f4cfe13c5915729277b0e55fad6995779a6ddb1c3bf5869d745ceec934c -243417abe5eb0b67a260238ee2e4642bd4fc82537f3e39df58de72cdfc07ed50 -24377f37c08109470e214f4b8250fcd5a2ba904f9e37654d44dee3bbf89f4ce8 -244425e821abb231a5f420320cbbf79e6be8bace69c77ede1b62aa0e7bf5088d -244cd1e5c9fa7360faa9c3792885f25a18ad02ca77311aca57567073654f9a04 -2456a96550c5d41a94381be6362a3bf68203ac3ce29cd2531f2333bcf293d1fd -2459354d2b3691e1acabab4c1fc8f28d7787003c2b24e93e66ef9171dff1191d -245a503e45152ad98896addbc6d3a4cf3eabc986a3d4361203557a85092d4ce9 -2460b82a74c0babe183a8ccd26bace38649c84baed9d0b55c742554f0fcf79fa -2462f64808bf900c967bf70b7597a1cf8803cbd2a755fd89ae95865491b7539d -24639ba041c13bf9ed40d1f5ed72f097ffda54ce3793f689e03fef8b32ed13c7 -246771d34da6912afebd64632742c7383d3f21fa63ae966217a16ed17bb806d4 -247baf6e032e5dbbd502ac168fb7b83b36d05d350fc0f4b7eeca2ce9c506eb54 -247eb6555611f9fc75ccab82fbc6ddfee955b1725d22469e6be8f7b8b5e91eb0 -248eb4d747141ed9fd4eda4a38fd19bdb3d01cf301166b1abb896f5250ba4b1a -249969c4a48a6190c83e04906175bf4a814391d76be6778440c0ad5f867113df -249b2d34a278ec42de3bdaab8c68f31ada74d82812c50f167ff968dec3ec5d9d -24a62c9ea0d95da6047bbbd8f0995564ecfe6b004f180b2d8ab6875abe4125ec -24accd4df72a95f4a422b66fbaeb46f22fa48da25fefdca3324062aae492f2e3 -24b111bfceba405690fcd9c57ba7125f27122991d2b4d1a25cd69dd0329020bb -24b378acd250f5fe17508f099e4aeed7d3c0d1eb2370e344c4ffbe4760dd888e -24c08bc465e8050c0acab05ea538097d9ff552d921879c831b62c39655d30852 -24d5c36338dfe4a7fc5929316a15a2ce41e1be2d73e068370184a22a17cea0e2 -24d71c7f5043bd31aa7de8e94c98d57049de86338d4c1b61f687ceffbdb8a9f9 -24d8d5478916842c99fca2ac0cbd666490fb5157135c36971ec3d1a6ab549289 -24dc9bbea77ddd9ce4b451605a91db25bd5430c4d535007d673bae1084aa1f6d -24ecc7c7b1112c4f138a9750f7197fe9e3c423cc27c684df3973f5edd5068596 -24f2f02433ccb3bd960ae4074eaffa2cfc670a28daf80e7b7bd592526f5ce330 -24fe4a7c9f63d74024102313784ac0b140f074ecf3a3dc29541731a5fdf999ed -25021dc44537795f1debfc9492add32e599de7df18c50d536850c81e43578837 -250c274092a863e35d4dca62af7c3001b7b2dea540dbc023751f6ed0833bbb5b -251ca2004d27240d404b6fcf373a72796e3c0c3e94cfb8066173490476481847 -2523e8feba869c621c69f3053080a4c5e33ef8777f4377af2345d587e97e7758 -2526af1cce3821b07e704772e649172d82b398325f9c24c5403400a024535e50 -25282efb31c32cff18d63f82b2b5ac86ca8acb38f67d103a8d62155f20073286 -252baa2355e59e046fe97b9ee9d0527debfce2471db6ec9d380f0d3ee426adce -25354ee377a1098a4f2c39d9a8f893e274189d4511596e544f78c87c0ccdace3 -2537d4b03c82265f007aa24abb997e576f4b92c05ffa3bb2068490629843b0b9 -254346eda1c5702cb9f44de407f0baea9a7ab00c7bf2a55e0fc3fcae208862c0 -254b9f64020cff1ec8d21b17de084ddc3964a6f21193d92581f2b25a1e07c2dd -254f902789ac2fcea0f03a1157e8800f09d607d7cf53a9c283887ffe4ae7e60c -25523b272efb5ffdbb9650ae0bb2278548c257d277d5d786a22a6e29427b6727 -255bc15e5b5cfffa12f8f28a146c94b33a69a24cd5abf1e36d0a7a052c7c852d -25705f1fe3afa030d4d80aa84c59c0826c7675e7626a848f7578679f1cbb5087 -2585629be7ef9c8a0028ee6eabb709f5dc585b5c4d2eadf5fca85c58e300cd2d -2587374b11fa1414b4f15f081712a3b167aa9c3ac7819c7e0f149e72c56de440 -2596da43e61df93ae45cc07ba135fcfa3c8f7dbdadfdfb858c4939f76ce82da0 -2596e283f1ebc78d1ad0ea3e32b48d9204dc54b848eeaddc6de2237bdd57c3da -259a8df1c416a8ae125a3c0615a32611798fa5407c3308e04ba710430a2e78f6 -259ef38770544a486782a3415eb14f9c961cf23ffb5a04e6f1db88b90bad86da -25a42b6777942ebaa59690850034a58c6b9b83e160dbc8750827926332a54c58 -25a9a062b7753e041fae8831d7e5cdf7f61499fdd3c4c113052bc966d60c1995 -25af1a6629c1b97e01ab5e3f8a562af38c24e985d5de5fe8c200cb36a5b5ff03 -25b0979ac11c91229b382be9587ee7440630f358846951bbd4770d6fa0ed64e1 -25b5429ce1cb6420c272b9e712c88b11ec942d15bdf9e33f2074f3e10eef50d8 -25b89cdc1b13b2aec52dfb2e805cea7fde4bd4241196e557246d6d507bc78817 -25b96e50d295b301251a08165eb214031286c91c496b93da6e57a0d38f3eda05 -25bd46bca72d9b9da13c2061f2e7c075fa682d68ea5843d22f9ae4bda7390342 -25c59d436526c4a0cc9c81793eaddfbd2b4f47e8dd6e8b5cfedf7be5da652fa6 -25c695ffa1d89b6ee69d86dd3ada201e8ce92bc66a1a79060aeeadfacc9070da -25d60733b20e1037136862478603b2315f00afabd1642650e72a94197dd981d9 -25db63bfab2e4d995487b8f4fe46bd2152d9d87074f364224cdc5943648a5f2f -25dee46057590a4994bd06a0a620a0d0360547bde5d6cfacae10ca120f4da32a -25e8709c324e126e864306074ba5ce730a688b279f54d7d0d258bc3ec03224ce -25eb1274a702d8e0575de8dcdac9bad18297d1ab5773e25f5c72f94b6665e48f -25fb948ca17e0cf4a9905906ef19142b5b67cc38f8b5484e92b0265a2da3bd0a -25fcf2a1204d24b384f5fe85c8d0792dad6c7037117066593476c0dcb6af0d5b -25fffed50712b4142641ab3876b1b9fb433b256045e946965231088cf44e5e55 -26045e6faa9036631adc56dc5265f53331569b57254dab9dd516eeff60ca7815 -260fc823ac76651544449aeb93b5ce582ec2925f0f3f80d8b4d3dd967d7c767b -2614d6650c0d28890dffb86bf4da37113e9b6d0334330a60b1cabab52ab367e0 -261bc05705af8d658ca928bf1d9afb8cd545b8be3e8ed279f1b82dbc8c24d0d5 -26244be784b9db9e98f61b2da8b1e8610582662044ae2274014c359075a075db -2628d1591c901b3741ca235058be30b93e753f5e2f319edbd70806413d527924 -262f0fbd4e81a834ef2223a5c7336c27efbbefa8858d72cbd6f0fc6e2f2f2c90 -263ccec136efd813da78d2899426e73d639cde467a5d77a84e56343b84d4e174 -263e71dcd7f57a19efa1752dd577191290ddd4c27193aa5b1820d76158f3a7cc -263f42b6522462854e38249854db6b80e69f9be8591ddf40c32cd233383af015 -264ce70a0dd42690625e2092daf9a5aa9627251c5e386e403b8a6b258679e92a -2652cda417bb4b874085543ad2b5f302c3fcf71a227d414ccabaabca86adcb20 -26546f1349e73c8a2b7c9cbb2f5f66e497c155f60104ee69f78047c4d233c03e -265a382c6c283f3dec057bb3c834239dd8c714ee8211b243a9c54b31563ba600 -265aa160e8b778f8b69206af405e69894bd5e961c74f9e80a3f5f1758015d179 -2682eb4a2dc4180ae52e94f4c4f60fe2aac15962889780c86284e4f6bf6e682f -26838c3aea405228cb24a18fa234f3c4ebb1876081671eb9db7a82f7ed0b6fcb -2689f9d8ca86a4cbdeca593071a2bad120d6dea4b247d52c167cb7cd4f34b922 -268c6c8916657bdbae96e2c7de4c49e0b256d028a13a3375b843b12533c853a2 -2691fa400ae4a1c4400b1e8ea354f693e261d2443bae07c477c91d68ba3b8e83 -269841dd8e1a9dfc3608eb7b316a759f16b55a02d091b94653b8dd83cf987d37 -26b049b15795401ca09bcbf50ffee0b98b52e6a235b584c1629bc39b12799fe3 -26b89bab4cbef9a8fe2d822bcc322dc051654ba27338c3ffa4de95c8cc5bfd45 -26d74ecbb77dd0ce4102e629997c3ea53a5c658e5376333ce311ccf68d889a06 -26db9581876cbf962c9d0136457cbca619e02780637e78fe73647c2a7bb3b9c7 -26ecfe2556c16f49a25cf1ad20372232e723dc7bd61950c7dbbe9ef24fb5a5a3 -26ee67a053a428d8472df61a0a3b945b5115a75f68fbc8f4b9a258b1f11e22b9 -26f1c6018b78166a7e7b4950b2b669b871f640a94e09aed48a33c58758b2e3ba -26f7446a57e0bb6f652974f76af87bb5a4f2098f0bd8d48ace5edc60696672a9 -2700d7f97415603971f21f63b1fae66e45936feff7c6b26d4df36984a22e96de -27029d24bcbfeb8c3f306cde90e8292feee9452097d4a65247cfb5b68539b2cc -270358841e350839e7be7f2485f5995463642dd20ff455db53d0c198b398fe8e -271cd503f547bba2b87c1216e1257adc5958e6232e74e103e6d0d612af3c4289 -271d0670934e92dc404d9587a2658a9626af162e3fc4ac3459d9f803dbdb1d25 -27225fb2d50ee8c4cae5452cbf38cc4ca956ab845eedc0a61901d7540ed17a6a -2723770b9c452e68ed1cd6c48f58bd8455a15dbce5ab8f77cf38a765e984f80d -272798432e8e3236148bb1ea06612555b86297b0594f8b2492699c20eb1997b1 -272d528e10511299bf7de108f274f217c74853b42f5343c6223af4a27c9e0cfd -272d843395f15c5225e49927b93bc273143a2b3b073c71c1253c547fb84fb9a0 -27347f1d54469b6c99bc871f1ba7ac4203135115b0ba1ec34355b690b8eab1ca -27364ac09e5b658460513b72a4f45cc1a277665e621376ba0e2071e8610cd89c -273d234a301924f1ec3287582694894949b94e5abac3bc2e6b82d53ff5d86f7f -274674ff0bdec8d30cdf34912b554ab736549c36299bee3261e1cfb676322398 -274a938469f99a090b62a3d59ab13cc2f21e0a9c141df033ac9f2a5c99ebadd6 -2755c30bf936f5735682766911accc60932355b87052fe8694cfc96358e5ea61 -27562a5c0a37090a16f648cfb9eee293ada1daec8382881999bc4ab9da9445d7 -2768605d526c0efb8f294471721edd9339bd830309f6214b35e56023fc61bc82 -27694d037176349812c51be4aa2f295faa9f9978f4fbd34aae1ed096933f3b16 -276edc16f25deed2c418892448333f070e1301c1031342c74ea152403da62bbc -27792dc5e65eccfb697f865abae9bd3ef09cf3fe5a3e46123767dbd3e7952365 -277961e62efd340c050d35b87ed56233b5679b74131eb06573836c3ae8ff764e -2780c140f9f5ac9d13e0b45fea6350fb1b36068c77fd52053056c463d8377350 -27868b2122d0ef90be78a2183a593aad0fb24518c455edd71b76c7105436e869 -27877ed76f58c2e8469bf88171f45591485158de1ae45723c2b65811e80c69d2 -27a01b7933fb41de0ddf29e4c4af203390b6962fccdd1320fb8a417c451893f3 -27a01da67dd60b619b4f93536c05364d13bfdf9450a1cd44dd52ae875a127d2a -27a5401049437e40d46ce57678bce28f8fb58c1dc112211227bd2faeec701bec -27b70c4c9554ac96fb0029824a1f5b9f22b8ae02dd46cd1815efe8c0ceb0d6b1 -27b79e200992a4d6bac2da2960ffa6029fe14405ececb559567eaf8f33bebeba -27bfb74105f5f6e579d09a8b2ebc4245081a1e3820182a9e2659afcee7ea471e -27c2578bd842b3dfcc13630417bb97574d0b9115f0107cf47c81942b16fd2445 -27cc10c2cd71c8cb6371a47adba8fa33b1cb49349757d90519edf7f01bbc40f6 -27d29cbf04b4ce3b7fa5233f507879dc9e22fb964d5e00ebe6172ba9fd43ff99 -27dc67503a67d508bff47f22b68e3c69840c988fe70ac042036371d96452b2fd -27e1d03600a0e3639abc43b881c9eb2040c29c3091603f23ca411ff1b02be5a7 -27e89b182fb31619c03d87409bb1341684b889b6e43010fa7ac69174c57fe57a -27f35b052ec0e0414d630a67cb6f126659802da1e5b3b8d18801fc5b17e9ce24 -28124e7c03c69e24c9833d2d5ce965658b054073503b224437a7d1563ce9b173 -281ca4df1e7d584fdeae4eba27add54f110a10fb73f16359a7412de03e02bddd -281fb0ed1906ee3983d084ff6dcc508baedcb23c406973da6450bcf2cb9d553d -28211d830da22358a44220b869fb73f36640c4ef6574bb4fb73905b09af41a3d -2821bde6806dc43bdb318bb3ed4c60fdecc9db9ef95f648fa2d014baba0f5364 -28274b04629ea1a8cde7973404cadf54356d1b7c919320f50eced06b17f742f3 -283f69f0e3a32ed52e0434972504157360ec8372172a0aa6b00c117dd7f53645 -2849c53845f2ca94179c2c06773c16032ba75741568613e7d194299568adec79 -2854b0e5de82f4d325bc3058e7dce18cfc6a6fd2a8136544356483357223e89e -2859f9e94d9b4e9b90b4c5b03afaa63ca41eea8680b591c13de6a7442f807c88 -28672ff60f2a35eacc5bf44ae6e01067c8e36ae5db59e813e6077fe2efb15a66 -286978865bd6b4db83e8240f0e8acb49e88495e7589249eae67ab91b69419301 -286e2f80ef88f254d7dbcf6744871247e32a37b708e1fed9bd166d8d6c8f3bc2 -287edeb8f5a5cb756b69692053dd7301d00afe00fa98d2242c8647ac026fdf85 -28924a7a4195e12294e5fae301d04f488c830d878f717cdfe2c52fa76bb4d388 -289515c98abefc291c356c333a0267eed8c4624e517d7c71c92b096eb353ba4f -28a2085e51c1fed289ec67fa14509e5270fbf017089e551ad8648d35336085f1 -28a4b5a8fa2d2634ca5eefce16cb1500038075c46500ef02f3f1987f11ca10b3 -28c0902c5fff77a83cef0481f4b16fc4be6e0c4027398840b9d89ed238f88c79 -28c33afc65867e5b33cb6889a49fa894a9b49576db52165e3925b8cdff1f44a6 -28c825f3802ccc27e56fec2fb74e07c26a3adb5c3b2a560a66d376c46e50a3a5 -28d1ada238ed177f5e7d7d9b36b4ec61b6855b86638dce42c349f5f621c1fdc5 -28d65a53ff00b6d304968545f51e7ca873e7e827e0323aa8843e543ca83306a5 -28e0859761374ce27241f99227353335130d7cb7af8f1c1d738b5e778e3bff98 -28e5577be6a216f52ae5241f7db02a8fd00211c2db83f4f29705bb71119c1b19 -28e626bb7d1a2639b33bd38e0d45f4e4ccf2653e9017537e7fb5f421132167e9 -28e725e18e889fa22d7c9bb6be215cff7ddec4902db7b771b75db912ad04b37f -28f6a8df5fd187ef40fad2e14640806cf60cfa7010659a52b7de27a96db2ca73 -28fe2588bd59f31917a7fd7d08d0422dd57579fd14ffceae82f83f7a7d506c4e -29013ec8802ac969e0deea93ab0a96bff2d3f425cf6080610c53ce57f4bdcf5e -290d4fa53860ec564fbf2f770406fb6ccb481fc2a48f6fbe21bb97e6bb73fa29 -29124fced18c09f1ff164ceff17d33b45a1d6d52bf80fb2166b78a19b8ba790f -291464a678f19f249571a7290add021a03488af3091f6285820cb71fb3a1bc33 -291a6ec2d182bf6edd47b12c371d1ba8a1aebc31242cc06890d56e93d8b9f221 -291d9e4e610cb9bc8dd0debbbb23cce11ff8a1cbdc998b32a1a63baedeac25f1 -2930cf8a003509aeaaac7ad763476cf58f6e6cf550cfbb34aa80de63ea62e280 -293c921236d9448e2c54fc3b0b42245e11b32a539e315df9914753a340b6632b -293db398c0909b31ba691dd153d2280740f2fd54d440a6f4bd32523ba0341537 -2946830a6aee78269979eb9e19a2705ec16fbebf7f692009bb9710759fa9e315 -29474be384b3b67e4831a2171c2d85e4101ffd7565e173abb38a8380ac843f97 -2950dc472a807a4d277131f46ff7f21d24415db42e27672316fc581c2fbc4a81 -2952a1e0a04776a5edbfcac38fbe5e4617e09340c6d00ca02bd6722e060e6609 -295c1349c18a2b439d4c44baf0c3eab68345a9593068d0ac1eb6f9ccee44b231 -2965f04d3279f465a6e6fc2329783aa2536a01bef0f44229ccb5b1d37ca97a15 -2969b0045a4330737f5a8ea1a0fb80320185e0ec35451b1464795b153c1d2011 -2976591677f24f0a351e442d9008e99d0e25b360c2cc50ef7da007b0ef9897e3 -297663e31e51c42eea7d8b423f19e270008de78748a64ceb72471fe1ab4947e2 -297a49d481c7fb7fa80f507a6f3f6685bc60a3367e258324c77829ddd0c7e3db -2984122c35bff472a286ddf3073985e8825289264bb0c331c0f4bf0315dc0742 -2987f15e5e68a7dc010839cd4b9a4740216795c7b0ff498037245cc83c5b5ff1 -298a4a3eb4dcf1f5fc78ab1d0557623c3861c252307956a6db7702f408780682 -29907dfa9aa21515b578540ca18eefcf3e83694d4f409865d36c5486725d01c9 -2993fde69e81dd210bd8a11293fb14fcb8eafedca636fb75da094e0a4ed44c41 -29987acfc03650897ed1f540eeccab493d927966e9595f2e539878866c5411d4 -29988a87448ce11ecad5f77f3f6166f71518007b27fc2d3cc95578369769669a -299b1c7160d37265009f82b87ca157de854e225f573f6aaad7deb7ec086d0bb5 -29a74ce1d9e28f29028f209d63b7eb3445a42df61ef490708521e20dd550cf8f -29bd519c7ad03d1171328546b38781d0145c8c04d3bc74adf3360d596e4de647 -29c13d30fc0406572a8d1f84b0ef00df659904fac5fea4eb5806c556204afaa3 -29caf0faf1047f2510c7bcad978a00079535017f4cb0397576f57c986858cbff -29cc6e5fa84d3d865ce98698adb3828c1a3d3a29534f93d005d20e720707ff34 -29cea3f0fe87b6f91cf1764723027332bf9ac6f4e4b6e38038f1c91ecb3edcea -29cfd3008ffae1c8fb94ddd40f9a83baa917db010092d3b0e674c5134d4f16c3 -29dba0231031a9531c5f57e6ba670a42f8a77960885d40005d4cbafe8c3a9df0 -29e07edf1233cf910cacff7d7766dea5ade602cf6d57581a7c519eff99eb5d1d -29ecb9af5cd5cf0bcfbb5904355e563d6aa538b8bcc3aaf6615fc356e3b15d2a -29f0acba45133898a82101a051a1f13ee00dd1af8b6316c9c465eb5dc7211ebf -29f3c689cf20ad6e04ccedacb2be0cf40768c9110fcfdec0c02e173ee7badecd -29f52fe0250d9dc78760584e3a1c0dfc35c85e7a875a4552da72aa1dc04d3ccc -29f5fec451ab2e3e89c6df826f5b29342e6dfa2293b66fe23a19ca8da50e6b1e -29f9771ec005bafe105dd2d6ef4c1e71046e0c5fbe622267c20f58bbccb499b1 -2a01e2291b6e5bc5127b44a51faef1974f044cbbb5f29ed0c1838e7bd9b93194 -2a05d3dd50713a4950d2b021304bea100af37f1c23d2f587fc6dd3bf80ad37d0 -2a08859dc977430e5c7f37a3384c254b9ef4330351522e6cb925f962b0f9fbb1 -2a164cb2e118b254b0e3e895002a0ffc2a1aa53127583ed62a817b954c120de5 -2a2742f602b167ecdb7474063926e4b7ecd7271bb4e7b33acbbbdb8a35416fd0 -2a2818076007b49771cab9ddf03d8b5eb2757bbb0fce6698c98a4e5139ee44d8 -2a2e9f6dcf276861ec0881749ab3c47335b878f5dad09b7e093b2c49c15bf41f -2a396a1475da44e6df9d6c249c169a98dc990b5479bcffea6f7f55f74de4c5d6 -2a3ff15ea937c5624f3b5cb520f34d9d23b2aabfd6f668c76f5c1c9d4fa08476 -2a40dcba4b7fdbdb9300e7f578e7acff60dd62d3d67ee54d5c289fc94d06c108 -2a45bb8484179fbcca449243f0857f7825a2cf1fd9654053de77a6245e76ca93 -2a48d797289aa92061656ba82c49a710c687fa7ae7c1052035c5993b605f5fa7 -2a53aafd87ad1565b05522498f373f26b87fcd41c53dbd1b5b93e46c8c1e3dfa -2a53c6b0f75ec25d7f97c4e5cf1bd905ff52e405b624ff749dafff6af70294fb -2a546dd80e54d8acd4af309357865dcd66a68458a22d52476a993a2c033a90e5 -2a683e7dcdb46bd1a1fa157851338e4562c5b3356fdb7c55654036149e89c9d1 -2a6affaa82db16edf2757961ff12034b43362db761f3df17ae1f4c9af6c4509d -2a711e81b8af9e33d4a03af1ada1d3696622a233e6369aea330ce3a26dc04a64 -2a724b83376c1259c2c3fc9d5488a9967d59a0834208590b5e05c5112459a4f0 -2a77b4d988b39945af6958089746640c3900f63566c74d9055bf21cfda5befa2 -2a7db7c3630a67e8d670be5943779b38404b8aeb293f3ec9688278cd77749e0a -2a8455443881c923450719531aa4da3d83b3592fcf91e09c03da7f0b2c6610b6 -2a85d43ebab017fb2b80d75790ab06683579601b9489b507c5bba58b02c79979 -2a8a8d5a61f3ca88ba47e7d4e308edcb97693924ea59733c494a9a2093c879bf -2a91706f75f95e423e4b8f69366b4fe7540093c7aaa2f01e592765d31112ad2a -2a98d8f123501049f69e154c114dcda31eee50f520466f103f0d1183da840c44 -2a993aaf5e0729f840e4a5f8bdc39e525d4b93862e70bde47f702eee4b9c859c -2a9dacc2b0e8f0664e29a77488d905d59a2066df390ff1a3cdabf99c7b72a234 -2a9dbcfdad69ed0bf36f834d48cd6c9f122aa4ec7185dd35ba6f7a2333fbc5c0 -2aa033b0d52f286fc23943c4bb29479918f2b0689f6f6eee2285908d486be600 -2aa250185068495950645e0f0b2506109bd532928e377999043589d19b640ad1 -2aad9d5a0f4149416831717e138e18a7dffd357bba63ea4a53c5d206aabed721 -2ab83a48d7aef9a364b45b44deefa6b75c8d7732e33677fa2bc40cc0f2b08e21 -2ac473c1fd218ee17cbf8c84a8f31e1ff74f6df8eea1888d1278809f327262e9 -2ac4c114d4f22b7ea151c692634e34cdf8a9e46ca5700ce7722b70d1a0a779d7 -2ac7f2af00c7d57c5308d8a35049673c067d3c7c0175b0f116a2edf1594b7490 -2aca0dd2fd2706aeaebcd18d4d655f4e36f4ee0b5229e624b5dc186d1be887fa -2accbff38146839c6e72c8207f46947e4a80c74137e55e3ad83c78e39ce08adf -2ad08244595b3f8d1a09c6bc26b8ab903a86abaf406c62d5f108f7fd3acb88b9 -2ad3a77fe37a00f06fbbb7eb53612497695213cef5c784c1347f559a57414d78 -2aeb71ca2099458ca69eb26a81b13f58b681ec6bd3a198dbe4a87f972cff4dd7 -2aee9c59405e0bba974d0ddc44a32da0ff7c21f3470210351d982c9ed4d327a3 -2aef3df7bb1b5191ccc321897f2683270f1d70e492a22a77341496c4dab656e7 -2af305961fee941f3c8a335d9560349952ba98da4aa608b45d604e5b0ca90a35 -2afdf8cef57b27193601d2e565a79e4ca2e3220b03b14c751f3f7acea3e84a35 -2b0212d4d1d2c494c64d8ecd4d874369ccdac45bcd6528396bcd53fbe25de3f7 -2b07a500c3209762d2ddda6c4ad600bba6660783bdf783b470db59d3ccf06591 -2b1221f17a3e1487df5378ecdae5d7067e6c8f772cd305994b6da9587358f0b1 -2b17101aa3c3c1d03999e229afca72aefc3d3db7934c90559a593a7aad5b3054 -2b17b2fdc91a8214ace6b226b160a72d15c963bee7e554787d2f67e37f5819da -2b194cb011579fcb10e757a4c85319e757a779371eb859aba981ee06d70f064e -2b1e02d4ba5eadb9527044a6494fe85bbfe7efa8704010b0561c0cf4155e8e09 -2b2414d1a45984e05c202bbb0a402c87a972142bc53889019bf1c448061bbb5d -2b2a10c8965da80f43231be2268d804054ed73d297b05fb969a0d4b303a59cbe -2b2c894d3b07d2310682fde06c2736dcd23790837d558e619ed7691836b2b5c7 -2b35b2f4f1dcdaae0151a090706d1a880d74490519fa801694fb7d0b54b723cd -2b3aac6c2f8f257fdaac11d7f333d86b40e65d254c524e2e9b9f0edd7be3dfe1 -2b4486309f22d833eb3622cced3976762f852ec9ce3007cd479c3fa6559a5ff2 -2b5ae202938b9642010caa22362eb37f7180f4a244f7395a000c720ab8d76f1e -2b5d792d7ef7fb07e2ffb8265e643d80197074848336909cbb2eb98c1b2bbac0 -2b6668404eefd7a1e0ad9553b699338eb677f6b875828f6c114070826c5c9a0e -2b66b02306936d4264239524cd0ed4abdececa8b84902332f8946e3fd9d95dcd -2b6c1575f1cd5bf7e5de6cedbabbd530361f69093e9d342e264ab59131b012ed -2b754d8e3303e5fd09774d37a2c4d55f837056ef9a9711224d54485fed99579d -2b822eb4568ec9b2164d9e031b6270e50997833fd07b4dc74880be3e6e2c6581 -2b8e35cc78df188365aefdf823738d866805a546930c242cfd315e89f7bd1fbc -2ba0b29c4423a4baff72cbd14055e8036969fa48e4d47019e8a8e543e0ec74e8 -2bb48a5a4b40446800eaf73f68afc5d6f0d3528e55ec63605e24c554ddea4ac6 -2bbb8ce667a8084fe386954af7e5571f4f6b0a12632b2472508f6b4f543127e9 -2bbe24092eb8a613d2949e9c0b6e9e9c5cc751893da23413b43ed46488cd7c6c -2bbf466f4ebf2dcf2b1e188ed050e29a771141c91e5c33d5864d6c4671906d6c -2bc0cd274d468bbab30d0e87d3e30631e7d97159a5873e64beb0ea4fac6132bd -2bc68ad477b3c3eb22868e5edb31977c3339aa323436169944eab5bcf50fda90 -2bccf9cd0087d29bb5d8559f75a343107514684e2415bb8a0761c8e5718f8f29 -2be0cc716e8b778035cbe0186768536bc9a246e397c205af4b2a8363c77f778a -2c01aac091fdbab045dec76ff3f0839f7dd4c3995418cf9503b2ac54379a3d7f -2c02a18c1be6a8f39da68258e4651192ca555fb03dedff2d2d170f64d4f38941 -2c03fc4be16f25dd1328fc40de47359673365c3ca47d6ae4d146c3fff569ac5c -2c068ae74adc7276ab32b552c9d6c9426c4b15feb69348ba40c04a47344aae92 -2c0899fffa7551d33ed37b2c6366c83707299c95e67d1823cf5338fb973c7969 -2c09c361bd9e49157696058973b93738726aa81c8a591d6269918883eabac69f -2c0ee28d2851cfc19016b00e3f0f2c19a3a43bad18545abfbf2a6fd290738c4f -2c1273aec6e02c642dfa543d5d105c2344b60888ef4d85ad5326db0c501cf51e -2c1ee34395ca87c1fb2264a14be0a134012719fc2e7ae941d3e9c8cd9f3513e1 -2c32a4bbd882ad7b6cab57c29524963ac03981c8e56a37d35d83163f1567866d -2c3d0e24d8be7b18947ee7be9cd5c428005a9c7d2ad30d788ab562b73f7d4dc7 -2c4f108a519d1c8cb103f7ab10aa1abd023d4fde7f3847b4c322758885a8988f -2c506aca96e0e83f6ec5a14a9acb6ce106dba38ae0a009605377e1499c632d79 -2c507418a82f5ea03aa000bc3d2efd0a0419b461bf564c8164781ba3034c87db -2c51916bec15fa5f6fdf40e0f05a7432e22aab6954994bbb389f8a8c75736ea8 -2c5ff476c1d8b08610578a8cab5511587258cec4ccc2cb2a7839ae55cfae6fef -2c61a0dbdbb43b365b2c9239689700dcf6518232d79b70848763aa4408ebf595 -2c6977f88667cd502e32ee7240c864ac9351e09fa9ba54bb83d8d2798189c809 -2c69c225b0453d4aba30f00e53a95e24bccec1a39cdfa39d0596ecfdeeb2a308 -2c7e56565d6e7581e9bb04703324c923b54c96724b65d9ae0b108627cacfa292 -2c7e589412a35acba5a2b9d1dc805f563b12e0d650d4f089cc4119cbd90d9ae9 -2c8181fec32d9af0e1a4d7386b069ffec9fb5ed8db222255ea36940c7d67f852 -2c819157e4e069471344835bc8098b6c4f8f294e014225ac6b48c38875893266 -2c819438a9035d0acf911c3834c3678c446e17d73394945fd8baed4f073bfef0 -2c8946dcf8c27e6cdf01369d2ef1277daac89f227847794ee49f1bde87671571 -2c91ab7650a4b579b12bf313f6ef6f5948ea1c23b6a7df5e641e965116fc9378 -2c99d5ae41be59d1f22f44640b883cedad8094cc405fd050176ee22591ceb45b -2cac6c262c0957f8d6d3e097b831b6a5a8bd4346d5bef5e284a226e9efe17d41 -2cb093d13ea7dda26ac913c37716a0e82f8bb2b36d48f2b9bdaf3327b0e45278 -2cb4d3c0c18c9f28f4fd558ab863e08c38231f925ba83f7afded185acd252529 -2cbba49825c2e37d5e7258d624f97c6dd292cd6092b10c297a59a5e574c410e9 -2cbf11c7c9c10ddaba64ac83a98fb423ac8f91680a5c0daee77bccdc0d8d05a1 -2cc833219dc324f40f61bc3538d26fef2e48cd5acf0ed7aa6c34db9e0191bb2f -2ccec602cb720eec4878955bdc9bb35c43d5c4fb299734675e723db1fde872ec -2cd38134ea798833bc7f61c28b62a3995a40d349e72d4486faa4e798dfed7a44 -2cd4648dcea46e3d1ca5dc12725195253996a65bd8e10206480c5ea27196dabf -2cd6f85d820c2c17d6281a944bfd229987d88c0a9e90b97b47d88aefa5ae4ddd -2cd9a8ab3fd1a793f6b997bd8e5e4d9272012b10d01a45df0cd471a233b65fcc -2cdced5cf0296704ff7d619a585044b1d2aa8faecb1dd3c80105a40d0165b05c -2cf158b1a5caecdefbdc1d11b9d9588cdac2e554c4ecf2e94c2e00de263978da -2cf20839f05da852691e3bf1ded2f180b96c89cf2e3514a0c3b41695dcece86a -2cf2998c55e9a13639b0c2f3557e7c29a3eff5c73a2b1ccd83ddc54493394d5f -2d09b4479b20d2f85c3d50262742b6ed1cee370cc9974bf7c063711dac36d853 -2d19997d2cf358d1748fe9b7c6719f9ce4cc833b3c8dce75e138eefa057af681 -2d1a8e8aa4797e5b64c5f29038d716b65099226cfd8c46cc9d90cff3a142c41f -2d1df705cb6f83fb569b94ec095a483593a8925a7a6c49ea4ad8df0820cd496c -2d2012362bf5143805fc2593d0521e00bf94e700513f3a7b707a636dbe467110 -2d380961feb30598b39380ba5d48decf1b4c7f89f971c7c6ecab54482628539d -2d405a5a7d207e672d45af0b2d43aefebc47afb6909c406a146642d35709409d -2d4251ed2d6d4c298c5b335664ff963603aac0bf2d0d9a44444a9f4bf953b326 -2d43759a522a42a4a0ed66e267bb4c08876d32881991a4d2311b8c60f6362820 -2d56a55300e3613deda0d47dbb67b2b3a1e1bd5a44485f86777b988a9fce4a0f -2d5936ae545197afb7da3a4dcc27717b97f028921c5d43679cb041a78624fa34 -2d5a27bf1c033ffe5e8933e66bf9be2ed45621c417e0b5017c5246ff134d9066 -2d850bd1bfbcf58fcf26bbbe97b3bab6e1327749e1b35e37b86e9d6af1f3512b -2d89439e8d0412d03885fefcdaa18bd7a4a429cfef01be0c183276827d288e17 -2d8c2bde4acf99ed7b420f92756188cadb79e1da4d00317f1111ca1402ef6da8 -2d9319ff1ef4287019c2153bf5936561809e82a6726d4e51d3248a10e506160b -2d9590a99f7949bd771f5c57a13844f4418fca47ddcec8959aba6b4ff8ec589b -2d9822472b73558ff2f1051b5d4171320803b4387c96b0eaa35bb15e5e90220a -2d98f5f5812162db4bfc9477a024c308816f00cd01d062dbde2297925eb17d40 -2daf155bfe3aeed5af62ef73bcfe729656ba6875bcf961b3bda1d6f813b7cb9d -2db1e456d7658cb4ca569ed4e15c7ec59448129cd77d3a04e9888a6c0f302852 -2db43f4c1e8fdbcf9d9f5563fe754e0769f31f35ca87021b214bb463ece10b32 -2db6fc10c1abd5781ee89efb27daf0e519714497cfbdf1f0cf4383bef3134311 -2dbddf6a1a685013bdd32f4dcfe6eb27269c7d39afb5922c2c8eb2026e2283a1 -2dc0e42591d35ed6ab7712858aa8be1ad3de3cb26e1e6fb8ad81fe0ea741bcb7 -2ddc251371cc78d9b224e5e4aca280f7860d0c6481c09bcdd0e2503fdba5c324 -2dde7676b5992d444ff69b0100345a008f6b7cbe806aa05758ed94a5265d457b -2de005b57e8a143889f7fd7662952577597b2e6581cb596f622aa160b880b825 -2de0cdebee6960004799b865a08357bc43c94eb0c3d89cb197f5046b7c82c348 -2de3aa3cc6ca26468f1b336da16b742b6554ed49e7b75cd4d901b39bad0ba6c0 -2de6b1c2c9d5611ff9cc878cf7c0cfb0b90c2a81536430c82af96f9ae98cee73 -2de7c6beb82e52717c01643a1eb4380bb4b51320b05f8946beb63513c1d5b5ad -2df98eacd0ee799d384cc0d7c849bbdbf61b26ed41048cee7179af5c6a34fbdb -2e0a93749fcac642200bc36b01366f138a1ee927ef30fd7f1a977a97a9e69a26 -2e0eb993291e4a856fbd63fece8d069f2c5e3e34e06d522eda4c07139281d32c -2e377762bd22a6f5935cc9a4f8732b3456070e8f63e6d7d33ec0f42990691d11 -2e428c1b4bc1f0b4774bb132b266a91dba6fe6db3fc9faaa56a31ec850409e24 -2e42c91250109732f6c5a83f5d52bde6a3c63cacfd973641d87ccccf6da11841 -2e48cfb10343776d11886003a921334c04c1ca620517bc743c91f9cf3c54cb14 -2e4de878ba8ee7e9173b8780febaa9f28338599fb6989dc42965a8adb00e04ce -2e53606c7febba004fa39d2d8f51183c3a8cc0c3018507009fac3f09dc710ca0 -2e59a8545be7e71f6d8f43459cb1a5ba341109dfb28143b144de6ae9bd03e6f3 -2e5f1b5f132eccbc065586b9764366383762ae64003fa211d787fb09223c4eae -2e5f8708e5796929428b75cf42606504766b8471c95f4b898637ca88474bbbbf -2e60f8a8aa583b0e89ca93950e4a52eea63dd216a929c872effb83d1898ac23e -2e61e1d3fc8838f4e34af6df2b85d7dcd47f6390599fb114953d4653eb263060 -2e643b88b3231825aa6f78fb6b8ed9f806aa20025ee15de8616f1fdfe2be815e -2e6a2c90a32293268c1e0e62c6c9cb71d028b57e0c319a41d2b5b78e38d76459 -2e744b0d7fa7a46da542040125b9acb33817370c21398ec44a4a7f44aad034c7 -2e7f7bb7e9daf62d4f22acc971fac8771553a5cd25be01b3f2b0a867aa2a4f32 -2e861d71928522c6e4adde861afee185192e7e929750546b969a81fd48315816 -2e970124abe1b8bcf9978d2e480edfaf85d8d6e54e6ece2d6c7ad3edf573ac72 -2eb4af6488edf1c3bc6de38b1fa13c783d6cd464b969697c75558f2770791098 -2ebb823ab4aad2fb63de965fe46904edbcb3476b6e0049531b63114235af29c8 -2ec7d0b4a42e009c5cc45fb763a2125437ebbb6f87e1a2efeb43db4883ba8656 -2ed89b4fac514c1f25da5ac2528b21ab483f2ac9fe79e8c3153c439ee1045130 -2ee5fa91a2dd54d95939b33ec2933f8d9d98f8245a2d7d937c06c1be3260ffad -2ee7311d9217d3c8d9c029a837a0e0681a362816afd262b9c113393a9fce795c -2ee7e751f577d1f3e60404e247747c97a9b14949bd427a791d85563001af62d6 -2eec01ee24786ede6e0bc13bc7011714dd68d7475a37746971c6f01014ef9408 -2ef690d5d4c35ea2c7e2c13153faffc4f03ca1c15f1b99f4b028c09de29119bf -2ef944eb124307efa55bf0d8e748524ee0110859faa4a8a1fff285e6493d0fb3 -2efaf8a48472d37c69b8f244890d56ec81d252a6091ba86afbd9ed3b23f19538 -2f1488035699ddfdb6a90fd654946a74c475c5bc3e813ab98330f2684f1e9226 -2f1569c703e68030f702771586f203d4c7181a30be604b1c9b7f2d4b78f72d91 -2f1957224b9a44d6b32fc3290326f968741497e0eb97f2a807f23d6adb74f41d -2f234c7acb6de76f21d001c7e447f862cf845fa280e7412748159dfcd572f8c9 -2f2651f6d733f435f1087c46a4b0c547facc09b40fb0de47e65613136a31e903 -2f3534e41515e5d71c0b52767efccc4e84463afc79f231f59acfe2e84f7c0c4d -2f416b950a9e57845fd1c51752c16c1917565b805cdcce6afa2588d9a712e1b9 -2f437b6c63ed6f8fbdf9986aa435802f28303f87b72cbeb458b19dd83cf66550 -2f556d353b1154a790ee9d36867c5ff3c4f52f6d2f3baba0a82c41ac79456d49 -2f5a6ba03ea394c6aa0991c1826ac76e38e0053a15328e949a2b4bd2170c16e1 -2f5b29dfdffdf24a64bcb539111a54b44b0c1052338e730ad002bc8d3a3a5a77 -2f639723a2dd995d0b0f9f5ca49602a60852de877930b0651da829e0e67e7629 -2f723d2ceb8e74543ad705413ef89bfe1a6459440cda429d96627709bb31a92d -2f78feb669f186cc5a3b21b96e2394f5e1e2d6a32c50cc88c63ebe00c36c929e -2f843e3df6b1bf344504a3a67b07b05068ce2ca947ed4d2d2885cede6b60b8ff -2f84725fc84bb96b75368624621be3e4cce25f41fc72c249d61998f10fd873d8 -2f8f5f5fde5467236a46484a92e43fc808a58e81f0327126669ec23e45be8dd0 -2f9046fe50600cbda7db91b6541c6a91288a9e49388abed8acd565a224bb0b89 -2f9772c11a79ed99a57381dc7949f18c08026c041572a17c777b7085cdd6a1a9 -2fa731941c92391f1dc795e1994b5db983d43bbcd48baf615e71a1089fa64884 -2faa623f3fbe3bcf2f4f12f4889065e496ca8d4715809c13f4adeb5c0108806b -2fae9fa23091b0a5ea6da2a5f930f9f822f3ff80458cbf1e2c2cfbb7d65e1632 -2fc571c0a7cdd170e28b3d3bf1ef8f0b5b6e1a45b09d01af2ed0872b59310064 -2fc6cb14ac25d9468c258a9777b2bbbf09b03c29e8920f295942f41e8d6dd413 -2fd86ff122d1982451eed76ecd2d9f68ae1a79ebef2bb2cf36851ee767ce3cc8 -2fe1c2bf93f5ec1fea63908cb6442bedf483a1c76eff02f15db2833cba45b01b -2fe97524a32f73c04260929b6347a28de5a21bcf3e07349ecca0f1c9a35e358f -2fef0639ddb96b1afb8cec72f823436d30ba697f527191de2d965b26c0c8ee92 -2fef728ff392c1648b9fc4593be23a29a5e4f8a4dd911664e4ed86abf394774f -2ffaad633a80380797d9cdb802e175254d175253b2ee1057a81c1082c32af524 -2ffc3137dfa42182b9be46609a7816f797d521be83d5d0d5a06ae68a42dd1dfa -2ffd81f8442e33a1849bc3f8d15c010c488f2cd8a405999fe79519232083276c -2ffddd69011ab7db595eb01b589384914434d24cb637907274b976036b58d804 -30095bcba4718bf6dcd91c4bc18c429b1f2ae16e12008032971c4df53568b7b7 -300b892ca6594e801638f113615cc62adffb5c9299d693bb46df8f66fc17a975 -3013ac265b82164374e7f8b5fde3ecefb13f2494fa6b923265714869f305eef8 -301eee33e6680e3900cb4d9b560f7a6b6652659f0d7ac79038e07b673c39667c -3027baeb5f7f7fb94f094a028951a3bd220f8fdb4e12504fd9580ad37bfdb0a6 -303a8e4776b7e9b9f17d47e393496b44741d4bc8ab7dd905924e8a3327035167 -303d251ced436ee1c493320c3b7fa6e4cc60efebe7770165f4fc82bee4e40ea4 -304494fceb42ac01dde908c256d333f7bcb95696c7cdd0826562c09e9b676ac5 -3049fbd41c8cf972ff4a5dbf4f9839f5bfa1c7de0c2092bad05f5bbddad91476 -305b0c31a585193aef56d8e5d3e028cf4cfcff2a3aad71a44c934d3c2677b92d -3061a3bdfb2d03052d86d4dff0db47c34d9708d28dbf46f355211ef4979bafab -307690d6e99a67cdd3712a16fe3231d571d56e20bf74de1add43e1241f949e22 -308115ba64e57c9f0a67bffb7b360a7669be7836dbefd2ebbb2c9a577a0c2e4a -3086d5d1cbbfd71e99f02ed238f3ec7955151a39ece89f513d29a3d3eee96529 -308f1fbb21cce241471cf66828fde7cf00467eb56e44f708ff06a7153f041cb5 -3090fc958c17d889ebb76a581b37d3370e2d6b0f272444d00f86e176e9f81a24 -309c0c2c8f3aea34417d251cfaa73581b6df3a6c20b79dc420b9e47ee762c3fd -309e16ec743844c59d36b1cc9c4e54e57f26521d8c4b8ba0e64d93bb47c361ee -309ecfa438fd307d2c348b5311467bd581134322f30f79332d1df10e57af9b55 -30a910ea02137cf888028f174aeee9188dd825439dcd9ada76e81c01f011c403 -30afdcca7789b285b0acb0248876edcf9d74ef7bb86c34752801e2e1fba7862f -30b129cb525fb2284d4e68ecb801570cda0b332ef7c810afc39e0a59608dbabb -30b153e549f745a4d3cfe2c23eadcf3f436256735637d7817d588589dfdd71d4 -30b99a90e7f200beee2ad37a9cd71dd12f1efddb57ba57e4c4245adbacc1cc12 -30bddfc80a6e25ad9df26a3e92e4b0f8777fe4beb6ee7ddfc2f5f89f7f5eb4b9 -30c6c2afb4b3226bbf8bb7660b9e6bf77a71cfb0a1dfd2eff3b5658c799224be -30d6c521f9fe72ba99424ae7de76f129f016ded180749b208f2fec732929f6aa -30d6db396d917530a9f51e8d5072758afca90bbec4dc46b9aa444f565b19886c -30dce52379390d134126cf0c6240f2a3f561787123cb8b02928c8c7413a81ea2 -30e573e93592661fdb7cf8175915ee887cb2eee24ef47aa94a6e53cf370fe96d -3101c2769f441e7afffe2221c19b1a5de6dbdc752b17e3ee8c6dd02cc2704e7e -3102d95a8b3309587c6d197946b03e6c6f3595434ac360fee040d5bbc6310996 -31048aecf6b49b4c353d2c5d6bbc69b831ae7980333adfb55a3116e8977b4610 -31078847b1a38cb04f71d8892c41e18c7ec18b75c9ada2e04d3eb68b4451808d -31082cc23ef0de72fbddaf7067f6a734b7a98ecbf01980188b599cb81d5a2dae -31094585cd38c8064f490580bebba23cb3e821bb6fbd2a59c57a53a8349af0ce -310a5534722c8ae86ad1e5887a4c871ea092a6ee7e836b83824aa2cbbffd400d -311005439e6225f38945b3a99214c5f445327966dfbaee597e591dfa4164f05f -3111417b397aef4e3a520c45785bb7f71d46d32b7d4e42d5b52bfee27035f264 -3115665350e896b8723d76a6d8565b72f957659515dd6221c30d760263b51fa0 -311aa4f37b858dfba2035432b4f82049b207dd535d8c31458c7aa72abcc76992 -3123a07c18e3dddf865deff0605f860183a4155eaccf94cadf574d50f04deb6f -3123e54ce315aed022777e0011b9d5363725a096adb492d83de2b138e72b0f8a -3125bd961e4f665ef62964a74fcf0c5d33705ff06cd41dd6bc2408b28debf084 -3125c8d26824c6668fe3b2ee755fb35bfaf0190cd935e377712b5408707791da -3130c48efb594a6e139d79cc8fc1438c0e8803c817213dc4da1a23c4f96bec0f -31321707eced2bd34c168a02b8102531eecc847630f58e06f98adf769cbec8d9 -3132735445d4f0f6864c8a3beb03ba5e26dc56dddf5d9ce9db94f2c7862f95a4 -313c10fe22c2eb5cf20e4a4418975e9212137fdbfdb0fec7d2599107cb11e124 -31421b48e4cdc496464ac1e0b61014d7dec31b6c6fc595a1e1619169a0b9a68c -31596f3f05e373b004f504ed07a834120bc6e1ac12cf110503860cc82d6e33b5 -315d60276b56ab5a04a7d1babb0c779784ca70bdbcb6a2b62753e003e7257b3e -3166927d2189d02a55b7e30b20536a0dd3980cd48ac808a0550b8000b5e488d2 -316740db2d3b9bff35340ddcc107a0e0d6bcd3bddfb93f0ab80aa20b8c9b3eed -31694c9d6b864d50341439c98c3701017024d3567f3e21e1cb987b26e66d9960 -316a7fbd7a2793238878fef6618d414f62fb86848236f4ae2d6848915200e820 -316a8a5faa19f86e011578412e66f93cef6cf60111dc9a273c5983f251487ddf -316bde79387ddfb467fd82ed775da01ff6201fdef689a7ca50410aa34c3a6fb1 -316c4359fc20005d72519763b7898318a6329d1505a44fbd49e362b3cf19e702 -317736101dbb9d4e39ccbd902e466288e0fbe6603a9234aa55ecbe6c55ba020f -317d9a33c50c63bf53053a20751ff633d07af3d8baf2640ea7fc6622b1a12fa0 -317f8d02d574aebdcc0dfb9e0801875aa8e84ce4b6bc0a8849cbc3a49a522f8f -31840f035afc6b0bc923fa8c322d78fc723486da58f94b47318cb67af7c1bfc6 -3187ad6c7831eb3d15b75619de9067943a8403a60391f7f4f112b78b2a123f45 -319440f70e90b285894f106def5388bfc4b38e634dda3ed518f572bb87b845ae -3197fffd404f3b9209f3a9e011c134e04424726a01d8125865470619305a03dc -319e591e2761be8d24da19eb0cb306b6517f36fa084084d0962b64ba117daf9f -31aa4cacaa723d5f62244e274684e590531b395b11fe5dbb6f988a834d85a23f -31ab4ed39b4c02850d4b14093e54abb591ff4fcc9d57bc6d973ca0fd4555af28 -31ad06b0833f2da9b9153b4eaff23e55609b851dbf476dcc9cdc3a2d330a9fd1 -31c6ca952bebdc492d77220e9b5f55f5c32ccda327311e13ed6ae109ceb383b8 -31c8601471d03ebe0f5bddf93f3601eb652bbd7554ac9f28bbfdafbc8b71e2d2 -31dfd2784ee818065232a48971d6bc3c8987140e708f2ee59b7b2bb5fd49f7cd -31dffbf273617547b98ac9d562fef877204451a3cda2e7d841fdbfbfb330f2a5 -31e3270b9d665f35b0f27b05814dba42f2b015c4249ddc239d85f03642e8b46b -31e608e6e0f3d513af537dcadaa65c6012f9c679d4926f0b3775043729d59b89 -31f3996879bd0224a4b3f794cf4d3aca609eb64ff0d6cb77da56a7a99aa49472 -3204415c7a09ee8e8d77171b25f92d4811e0e0a92c5b3af65b68788c39cffdb2 -320817dbed73d4d5c1a8dbf44ab33fd20ffedd242a66f68b6390357f68e16d5b -3208afaf5e0e9059ddf9ac8dff965d0b33bce587bdca455ce0abca5ed88468e8 -32094efad37386377fe800a65fdb85e227de838a71fe474fec47eee7e33fe249 -32279739c39a5465e32635036a2c2a9273f80037ea225e34964b83c7870b13e2 -322c6d081f740c8e638c01447fc819ac69a1871724fd965149aa51fd4a44786d -3232202d7000077d948526af659b1f43ea3f540b8ae35309789981ac8451ad92 -323462692b7e8d966da52afd95f862c5ed423bb8f3a59b4051b5fc3301cfa479 -323bd8cd4657c983aa3ba1d4061b0413045c99a9a2db8749c0909d8f6375fc44 -32594e707c84948cd9ed7355c182586d01c45a3e2553489e3ace42608a81e97c -32659fe3349460f114ec60595a73c3bb6b030a27a26a44f8111bb79375add41d -32738bd4ba0861c80cb6b005e3f9289386099e66feda8358229084238c02061a -32750f0b691889737be71b461ea1e065291f22ac6c615f66305dbddba808dc8d -327a3280799b65b5964c3199df296e24ac687215992167b8b5e26e1ba50c5cec -327b934913ce54cdc1b79814122dcfb59382bcbc02afc8540642d5a128eff8a3 -3281c98c1cc4d4bd2bb73de6e118a38d37363bfc315a3bdefa1c8491d25ef032 -329829e1809b86b835dd283c228cddba750e3d4588972dca6bd19530670b450b -329c592dba05766cede05ba9bdc4ee78774db5c67a6738fca141bec48037a8a9 -329e2a0bf57a11a238cc9d0bcd4351e4c46d6de9964fb821736f0b7b4264c8c2 -32a1d9efd89e5d2cee12e99f3b3ab50441c23a2c8d66f10cd1eb9d5c55be1a44 -32a1eeda05d8e74df58cb617a5cafa41905d5551cc5b9a459a0ed8290a79856c -32a6020d3a65258c356c9ca6edf34cc642100f09b96bfd43634cb5c124b953d4 -32a785d3453b0b86cb7e7d1ee72d43f1bdb77264320a1fbff21203ff4d35ca71 -32c1248471f427e6258d5bfcff66f83b0292a549a3dce11000e9281d92e00dba -32c1d6685bf2463e9fa589c6e997d2db25664b87bcdf79acb733999a919b707c -32c8e21c093886eb0c5f652aac14c0688adb25d8e85cd5819937d132474182cc -32cf87548099d8940f08576572a64fdec3e34103d27b14d3b6efc37a3090e724 -32d0651ef6e259f1f15783a5301ef39989973c4d8dd23271978d894d3fd12883 -32de1d360aaf7cd1ff58e3092fc1cd570f0c8e7c09f0f898812dc6732f1c9a58 -32e02ea13b6bb6241b864b28a1c17bbdb879d468210a10b0859166babd29ac8e -32e9e0d6f02df8159addbcdadc12736d2f4a8a08d88f9714c68f91677d619d10 -32f023ada5991920f19ba2cf0fd8b3674eaf6941b75b9a9a54072f840192c4f4 -32fac7cedb355da1d276fadbf67ceb05410aa42b74bfe253966cf3762d0784da -32fce7bf42671706b3e3e87ed2cf4bd5f11cec1599a5744ed10d8777b040e832 -33083a4f7b6601754fe2971a1c081510b356b8a2dddd729f44a825fa8753d544 -330e9415e753526941bdb75e9e66f4630dc3e287864633e7eb0b82aee3eea20c -33174831aa08688771b2ed1d17079856650515a6734b5764a6f8229f0bb135ca -331efb4822581bf072bc36b9893ccecf26dac869736a3b75aee22f18469cbb92 -3320156fbd6ad051b9ea2f18904ac4d6637f7edd75963cb9bfb75bc83e07ed4a -3339e95890b23168eaf77885edeb1e1db1e2c712a1c39345193d3667d8069951 -33433ddac23ff581b91880bf29135cfe642369dd0ed5c2c0ab1cf80068a5a8a8 -3345d6ce68f2ec67bfebfe6227b5ad46ddadf8ba4bad2785967398788db7acf7 -3348cfaf8b1d60533bc0a3467ecfc3e96f8e8d6af6e8ff0a60277cc9b1b8cf7a -334dbb3de54624bee2c425c8512e7602aa93baaf3859e8561eb34d4eea4972a6 -33557ffbf6040ca473e49fe30794a32b6e3081949bc956daf4e46577fbc73cfa -3359480bf63e6b630d2832d4f00d8ade2938c8b0393c5e506d0a1517bf56b5a8 -33599d9df52788c4e1eea1bd3a689d067351d76531ef71e0fecb3da11601cbf0 -33614e6f37c7f95ce48bfda0698c4f6d63eccdbf66b491de5f49fb67a1df1d78 -336c39c3b330bf1252d4e65e3284ad3171252ab5eab605275663d74d258a7372 -336ced33716595945deeaaec39ddd886b39789ec053c370f0f0ddecb192d1f7a -33a1305bbea03dec47f4cdf0170c9d31d175facb8c17a6f8ed6cc3ab96e0223b -33b38c18d28f31388ea1ef519674b035b2197fa029e4bf5b8a408047022dbf8a -33b50bda5d1ce32beeead2eb035b6d012d7b454e227746fe2e6f862ff8cdab87 -33c924e15d7520f5add776386a527f9f52ad02c5fcf6a352751e676516c15ec4 -33cf16a3b3b9dc7478cabf6257c7a9ff20717817ed1aeba45dd76d533fbd468d -33d14a54cfade9fdb661b594d9ec95ce4a93b056abccef79f7b90be77d0a7ac1 -33d2512e87dcce51686e473a26e81d8fa9b79c85c6e8ca667cfee152fb6e96bd -33da35f283ea69b00418cdd91b9f1dfa578fc46393a43ed29cdf66a080822828 -33e0413d42de13eb9029f62529eaf985a23f9146054ab6466d99125702071227 -33e0469779057455f14a0791ba981b0f33866ae9b3cc35398af4470c2b58193d -33ebdb6d7e3915266d11fef5097df642338f39f85e87d18ffa18d12417b85f26 -33f9264e229be2cd506faebe732be4d4275b97a3240a3ed343554142c2cf351b -33fd9f908e2606798a765994788c9defc981b2275f0335ea5a871257e0e87e01 -340bb5aa2994ab8eae435cb0d70be2110047fd18f928e1b80f7ad82de22f38c8 -3414751c22365ad1495e53b2cdbb4d17f00058a04f2d19d1bdb66dd829b4d3ad -341db76ab6c04b32f6779a6f2739ff4444ac2d47aae7540f4a86edea433f4738 -341dc6bbf7f96a19d637d650a2885ab474607c1aaa267a557b8d790eaeb637a3 -34236d70578ec08b5a4bf93a9188633fbf36fc808907f2d2abd90b8d9be410e7 -3424cff9c172314bf62df5fa8e3cc2cf91431081f46b04bf22114bfca2b9c2a5 -343250d178df628eb26ab957f9aa89970cafd918ddf75056ae2799d9a0556d93 -34460ca650af3355b6f5f918b3156f322eef85ba1cd597b0d8869181fdeb51f8 -3447464642d0c25bfab3962552b79d68a5649b2774c1f88005d027cea57f8216 -344a74b1182a2f503ad16762fb4f2d65182e06f473790a10d7a52ee37c2a31f4 -34542b78d7e5edcca1a9f014237997f8835219392f50c7a1f472b012cc60df56 -345afd2a6304f26c6600a10c8cd4b261f78ad6d613a7bce5b3c8e8bc0174ade6 -34615282e53b43f87660ff965c9492177738a93c281e7b5a3ea2ec91abe3a066 -34632b75292917c4211a76c43cf3388b956fa5265d45cb6fe19df4b5fcd8e27c -346352a65ffc84751b84caa5c1993708e6ebdf57057a3476757d53cad356c0cf -346d4fed9b1bbcf475ed62ee4afe320afcac61dccffe0bd3ba941dea600ff221 -3475b9ee6cca0b240217dd328f4f47023ab329e54260301922904dee40d32f44 -347dc8cfa686db00dc18af11958d89be4b12bb6d75dd0109388fed03ae5aaa5b -3495f2c9a90c6e291487bb81f1860b2fc21d1f156f49ef9781ffeb7c87fae82b -34b1b2348a403eee9ab4ffd00391f4e830c564acbcab0dd33a1708403c97519a -34b474304233418e64816af72cb3511f2cb74d3b3977eb321585412a41584cba -34b6c9af985d3210538cc474d0ac9563f342c98883bcd2dfd3e4b85c29d737ea -34b815779bb7be27a2c4f11f3eded9dd02a530d627bc6cb65cc393650b108d64 -34c3cca9ceac0e3aff1f778d5e6d8f3370f2ae0c77fe01341c1dda2308d6be80 -34caf9199d462010fbce5776ef831621e40b658975cfc4cc9d5e86d6c0c42ee4 -34cfd17a285681728360729db5177db6bdb3bc933a2655fcc8e66381977a8628 -34d187eee0fb3fb8f14926777752c7bf41661cc4820c82f3477df58b12c9e1c8 -34d3176146d8cddfb0e75ce5ee6bf5cf8def213a5af39ce8c5db9d7f28ff06f9 -34d3306b0f1cb65a19076b5604219e7b0c44bd57a0a0536abda8c4321e6eaab3 -34daa4ee43905ea0c84e12ff1906b97b6f5a0a6e487265c7e91cac6a0938f147 -34f4a51b187b1810e18b92580da908b8d055f24e28791d0cad6a753582bcccb5 -34fa91c3886597d94d884f1d06269e1f8918578701388e3cd67ed084a11a267f -35086616ece1cfe99d1eb750697cc1694fb1641949fd075d797ff5ea923728c4 -350f8ee8e0f5f652a71d4ed1af2ca8f7385ded2b9939de758723d9cb960f2d86 -35146e6fd7f2fd9779d41e23e0381ba7e3b83430160de565239cba0e54b2de33 -351b4de22af262d2c2392071c5e45d8008c1a14a02de2ee3b4c949a0bbd2423d -351fb6c48629dae76170e3e5621a7675c982574d6a6f93afde21074172f2ce1b -3523bbc7f9d16492f9020d75c52d33b9804bd4b9e0a7d3ea32179971b915def9 -352ef648d3c29a833c90f5770809a7b603a3d05b699822640ae9a844098f6e92 -354a0cf546cda85c1ec60e0bc5d4b39a1196128f0e61e11a3557c9d82c3c6c31 -354ac5083f6b4871faa16eaf6f04dccaab5ee83d839d9a3af87b50c5a7401ef0 -354dbba5ea4e5aae08e146f26c38a3bbbe8b6b838b3c3e57b6af06821b98e471 -3567f52323f8b5a357e7cdb049fd56580ee69d4467a932f6c2d0c63ba8a8d6b8 -35717c0d2ff83bfad00041e7b3bfab580539bf68d934a9ca7c96849f461219dc -3572e7c01763bc8d5b76978552a4dd426da24c2f74bd484e98971d18fa63ceef -3572fa364b4bf2f3e14404f30c85bfc065d275b878990778b929d80bef776416 -3574c34b8d3fddc098537f3786de337636891ebc1a63d85208d79fc5e481a87d -358375b8a070a8eed103d7b08213d919af4dbb3b9127edc02b5e614a8f00a9a1 -359d8c6af852c02637c1210c7eb1223bae841ea43f25cb4e8356cfdd19e04341 -35a05c5d1c3cc47bf5536d1880ccaea16e3d8d568ba4d1c2497bdd8baf5bb902 -35a116648def36024a61e34e0af6eaaba0b46688307697810c2b8d668ee41791 -35b31ec8b8f3fcbb3f6561337faae4ba681ee4c0f7570e29928021997987f095 -35c736488decfeaf9835935c903e61c0094014ff6a6a1e0dd1db37431977b1a3 -35e0ecd03515a9a2d47ab9f2d9e348a7667d3e25d436ae16ccc943e518ed760e -35e1d272741f38ede9baa0a33603125c694f924bdbf601bd6740aaf89547867d -35e742d5fdf7be040a589f0e344780677913870c820d667225e134b3b17e2fd7 -35f4ad5549d0cc5e388ca76d591844b2ad552ebfded4dc6f25804979e8886243 -360441846dc16cadc44e484c0a146b989e69e76c7e8db673df42e9a58f3304d9 -360647cba9e560cb9eae05493953b44db79121e870eb87f5df3f41af81b248f2 -360946a68947e2fd3b682a388b7b606b9f4190b386229ea8d929af6aefb1f8ff -360d1195c100a38546d83b8e2c410c5d31edd9a56ea1d633181a6f4a0ea0597a -360f53f9b67f8f2af932d16c5c99e6e0b2706287352cb81b70cad697113643f5 -36110ab2cdaa66ee12625af8c40a3fba99eb564b148485a52787906fa53b11f8 -3620f6202f9446baf2d0713859483ec4d8d6d3bfee4aa7281c44ce0ecbc126df -3623dcc88fe18ab45560f3edb5e0a9db058fab47336b3cefd606bebce497bc81 -3637bd5f7ee61fbc1d1e886effb381df066240731b3ed83d3a569974bef123ef -363bd49914ec1a80917a0199c76390ebb0560e13c38e17308157cd9d13629986 -3643a8c4325babdb7c19b99e0939661acc78c54f02aa39af9ece268897a8c3c5 -364686cf47e1b891d9f9fe99d9a35712788f11d15627f3b4a0d78a1e240e0a47 -3646e9a55e190a792b0578930d2f1a78488726a00f9ee385d41846bb4f3630ee -364892023bac3d3c206e173add5d7fff7cdda043cf7988b3990a98d7cc708618 -364ceee5166d1657c27e4b1970846dc19539eac53d7a16b542f1cf53171b92da -36615bb76619a27ff860cad4d489ba8321e56cf4c44e7e7797026d9d353f02a4 -36736255e4a255228539867bd2b6f4b451f8ed1c74feb6299b33a348468f6eeb -36736e3334572f6dbb4e005465ee77478ad1cd247562df8500da7e9113692054 -3674cfbb098cc650d07f2ba8668c324c097fb385af3890f11912cdb665c82fd0 -36773d2bcd489ee6c8ce34dbc0f225353cb6336ea0d585d76e75a45706906cd8 -368cfcc69e500ab4ba062635d487917e6e5f9ca8f183ff3edb54a41059b5fc11 -36933f3448fc9d867027f3ca08763d6762f49157ae287b76fa9dbe9aca6ebf7c -3693eb41fb7f9fae2cf7cf92a173ccd441dd9e27ec8a7c453697ceba7d76bb7c -3694f344e9c3a1e35950c25ccddd89b56913fcdbbece88deb162997ae0b4f55f -3696528c6946fb46537670570383289cffb3c34833c54ab7de37f358b71ae22e -3697f20ae0473d9b8ea22301f437aa82775c91692687ebd6678c8317207c0958 -369ad8fb68fa1bdb87916b27a7b5d940d03787607d604ed61ecf8be1fcd3a213 -369ca4621ca72d124a826a3165c8baaea6998f47d1c02e1bfc5a7a1bb1d24555 -36a3d15cfd010da0cb8632bcac0ff513448211d248c18729a5e7088631ec25c0 -36a85c99f2d02c2226819887b22c010d5eebf2b4875ebd94b9f68725845f6700 -36ab45745a19cb6f710a10cb4ad16edf5d32a1968a20a2ecb05fe66f2e883ec8 -36be42a7e7d0e9f59be2fe1ab5323bb066c9c28644213d907a9270ea880ce304 -36ceb801b8f44bb64878857296480436ffa3d67b76fb297b0fb8be34ee5fcce6 -36d6045b3828473e0dfa4dd20c9a3a275d7ddb275e3897bf7f7191c172cb7c69 -36db33e3822c436ddf2eae4cca48150ae363ada0b7e09850564ccc36e0ba558f -36dc236226c95759fb03ecf4322b7bda4e7baa842df58272830643ffe1ce553a -36de02c9c13d8730eae31ff292576fb3afea7f007dd3d00353ca8142896a159c -36eb9fcc2356d4802f41981e92af62861c2674c8fac47fdc2d01398bf706d4a5 -36ef7e15ac7488cf1b33963a56d6d6019b021123f6f04b1e2ff2f8e9bde3d1cf -37015c6fd717fac30e7faf027ecac401b2bf0afd212c4426d583c325d246f8e6 -3707eb1176b7180765833bcd491e489bb597439933d685325dfbb0f495a39a7e -37225967bc853da0d5d495bdde4f31da40a3678e354a6ad67fd64da4cfa811e9 -372cdf71505209a37ace33e30de99bddcb3fda25a5450a0baf25e9c401e84a5c -372cfaf3a545cb70afc4caff10ca80dbbcab3ecc280dc9d551de0996605b49a2 -373f2dbc946ae22ae5f8bd15d6b98731f5960249d3d44bc8e19d9d9f46eef9cb -374713152bbfe2a766c5462e712c0f00e72ef96b0f9573a12421543cf9cb002a -375db66fb7918f9bab7ecf18bc9093dabd4cc536d0a941efca78dd42eda0c851 -376aad0bd0309a7c7699fe0fc16c30e20ef70a98bc8ee6690f707675cdb76a43 -3774ce22b99a9648b5abd49229dacf13993ddac8056a38915ec463aadd23a812 -3777affaa188307d7c6aa8b81fa7088918c0552c0ae98237b586e6c1670da365 -378147dccf2f4f314184d103a5a3e56c047c0d52c5bd32ab72ef90dd472fc527 -378206145705eb7c2eefdf7d321614a8fa9ae9c909701f28f5cbb62890c4eaea -3790e8b316b26ea5e41e8cc8f904c753c766905c1c534f3e870bf3f2ec6b5b92 -3799601c7dfbcc4d5c013612f756218988a5a25e73167b04c4784352f47382aa -37a1b77a46c71b1e52b5f42852e2f1132eb124ab31d455709802dc52f6fe4d50 -37a3b1db4149dda12d7a2e8f65280aecdfd49c3ed368212b7fc15ad08050ccce -37ab83cee62fe6ef3602380cefb3d3d9ae65fa7e67c35cecbb1a61c05766c821 -37ba8d8158b34d6c759147eaae87667c65412dd3380195a811ea357a3aa56e39 -37c18e8b1c929974b07892f7714943380389264faa5279ddee4e912bc7cf2401 -37e36aad54b2b4b1dc7fd3166d006752341af693e7063ae4b6ea05170de4c7a9 -37e4ed2ce2842a5c6d6905fdd0f7a6c8f38f90907767bd97727bbe7cb3ef3cc8 -37eba7dc9ab410e1f9dc63c980df349f0400c15142ac3415ff836d3d30518bbe -37f4294701e5a13e8ac0623a6a647a4c092923644bad94a82d43bf42b4237128 -37f78733c4924a24b55ed9f57e597bc625cb51130088d5d78e3ecab0e828d7ae -37f88305f030403b40f7702054ed623587c4369d86a8c64e73681922f1d57660 -37fbf4021489f95371bb744445c7ba952c10e5806bd1512eef4d5887e7dce1b8 -38047caa5f5998f15ef17bf068407eb3c8447ebdde45c0d0baff9ca180b42882 -3804f5c58b05246d3ea0de9d442b8d3af10f37443096e6281c083a7668c6fdab -3806622605140e0d41d455f75d3acdb8d6f595a14af5749253df368aed6ca3aa -380b7dcdbda293ccf150e1ca27cf7f1eeca8b4b5bdcf972be66e7c7f03500c5e -380d4857a84b1bf787dadf5af108be2210921d90b5248b2659808544815a644d -380d8ac7a006053bc5d6bdf3663b901b378ec2553d5476c81435eb8b10c1d948 -3833ce8b8ce341952a2bcaab606b825d88d7c5213e176781f580c02cace557ac -383aef97207818a8cd8c2464c4dd6fc59d0bed398b449ebf60cbbeb2e1037cd3 -383d8981a0fa2013c49f3e48b160158499dea6cef69a572a97e4c51034b8c266 -3849821944ab2c111b6e0cc7fa057faaf56615cc5963db4695dd3bc8d7e7721b -384d0f225d54aa47eec8a0582d03c6126f76589f04ff88758e3a99bcdac44555 -385479e5a904d5e23a26ded8f422847b6c6c2b58cc05e4bc73625b1a7576bb4d -38555c9bfff744d7967bcd59f43a91aac78793a1771e2f42eb5be91a4a99f92e -38634d2fcd7d095933a0637bc3c403147685ac934dbf647f453281455ce9044e -38676e31042f9becf7b919960bbe7e0a10ea1d706b046f98efe7c6244b68d39b -386c00ebb54cd4d9f76af713b7799b6c93506cd919aa128aa7e035a55f7f1c73 -386f712df04604cb772af3c10209e0f7b141d645d16353c959edde1fd57c6b28 -3871aecae86fee829d4febc91ef397d7202e90d814a5dead13bc38293251363e -38726101f5c88fedce19895ff75abbf1f4c83dd6ee75e4934fb5d7412f3b96aa -3877c5fc887d52b571f95b4ed1da4fbbf80efa0a682037ee026d588e483a3c0e -389abcd746343586910ae84cbff203503a9ba8ebf47162415f24d0c76b776189 -389bc915a5b700a98f117976f3b0d4af9c8c54fdfd5027cda807d74775f4f11e -38a22a44569d444556c0aa44642229f9d65ffe0b579f7ca1331200d07c8dda3f -38a619b88f98ed5024d733bf52868f2cb374b3219f72599f6a18ff1279200a91 -38b61924cdee68f5d4529eda3cca5c0a63dc9ce850891caad95973b2107a878e -38ba270bac9881ade299e9108f41f7d105b20210f6de017e8a6f838c9546dc15 -38bf2e9b344b592826f207c5b704ace68fab41f00b7dc4f9d02b647aa3de2146 -38c1f68e908e418da4ab402afd17c66264128898d075853dd6503808ad858c96 -38e985a037be7681d7cf0cbc104ea8b7b68ff858784f1f8f7f765af1921ec771 -38f258bca3721b57a6ac3a9ab9a0b5ba0c0c47604bff2c13b2ec16956bba6819 -390ed798f7590be327a86aa2ae7c4eb65ffba462881fdb14e7761bc7e885fc82 -3914478d16d30a8e2acca31ea842a4f61d66fddf1417603f288bfe9df92e0dff -3922f4582b8813294846e7be79a09db3b24e04ac5400adfe230005ea5b5e1579 -392b4f73dd574ea1d898f1f9588ffc95d46f0da7e74658725136304f4a603190 -393d8dc6310dd2f69fa24d085c3f10da5d14758d4539daf51a64bd22141aff64 -394cc68f6beb96bf01e741f13adbe333e789fb340721eb31a962d45622a0f0aa -3951639861a049381a848c0532e1ce4300b86c74a11f5b133b661e6aba6f5879 -395342c7fe763a3a1d3b3a0a6933fd5c178f2765db0a45eb3a16be0fbf96c23d -3969c66a3526a9d50041be853b5b0b3038ccf0af9385bb478d701ff0c250d0ea -3970a0f7e53dab979ee18ab838cb060967d2757e2f10f79aa0fe1cf376ef23f2 -3977f0e4343fcab8814dd1eea9f45595cc6ce6a9da4862929b33ecda9628a6b9 -3979f860e24c39cfa7f59249a19b54716d5ca8ca966d4b0fb284c8f2f1ef14b8 -3980455af258dc95985b94ee5ba3ea1e44fe08db32d62fdb550ce88e95544af9 -3990226fe88c89ab1b0103e815e27207f9592105a40d04505459846fe9d50b71 -399a31f806433228e719c0824301e4e1f376178c54977b03d62d0e374c22104a -399a3c6b8a5f679bd5da1388a71bd086a315f64d2234011fa57bd7bd399fe780 -399abf9bb1684663b8bdede66c56fd1b7d035d88f9630aea3604624a3af03085 -39a3241a401d1c06edb831ad46d471fef75ed9cd49f19b49c74bc817202ed5e1 -39a3c6871d117555099e55024ac641913591bdc677b60589309b574cf33248a6 -39a7abddadc82f2b836ad8d4ea6a045204c0f84782c8bb470aa62b521f599128 -39aba87d328640a48cecbd13066cd3d2fc4b881a69ce937e156101a0b3c649c4 -39bbbb2b546615f0362e18fb68ab87d30001df548f9115bae5cc66a7ae1d1746 -39d1c60cd74de4e5f4fd55eedfddc04ebcc8043ba2f44fa793615b10efcd2069 -39d38f305ceb856097e6ac3b440f66bf157b23a4e0e3cbccff137a11ec983c1e -39d4361f3a1e949e02a1663d9bc1062627650edae325ec6887814262e59ac7ec -39d892542fecedac3e3944f087192e3dab12ba9dc7353bc401b2cf2e58338b0e -39d9ff3760a071602b8ad7d68a6ca21c753c55b4769c29e18b03add1a4bc9884 -39dc9689ef731d5224eed7e2299399b49a54dd47e7a023dc105e19b083f22c02 -39de0dea45b89c3fa9bf77b05c2a4a03a99d496d15b09671f124e87563450839 -39e255338b1ce0331f77145704cd0d32f0497773df81f0d2037557fae34027eb -39e909fc2b77fc1760b80f9592466869783f07649f47093ec10ed97b42e11f38 -3a0d3b559f89eb238075778de74e64bd5ced100283684124932e5250e47eeee1 -3a0dd2ac5ddb39d767a3c21e6d8e30f63f43bb7a52692a8d88c0646a1d022d51 -3a15e7a0e9b1b9e1c3858314af0c7dcbfeec3d75f36b7b321c5c0e27f8ec42eb -3a1bebd82092609133d6eebb5ba8f8710b5425077171dfc0f5c4eb3d6f7f2294 -3a2851b7e812cde96c7d3089829e42f095cde7055df35686bb0ad9295daac6c5 -3a2e17bc884c4738c01c111686e0c28f61a9b00cdb2a25dd5fc1315a40f57bbd -3a341af241d1022166f65b0f54f20e2d7d074e848409c596e791bab7ae96b8ec -3a3a46940f49baefc63a19da5b6365c81ab7c2cc8346e72a34d7c5de5c220a89 -3a453da021d15e3e200e5ab079283d52ab485c63d53b6ec86aa9a0d3ddd33353 -3a51bdf19bc3a40c631a3af15aca22377d88bd722079c179bd8123cd801d6178 -3a52df1ef71547edddaedc59c0990fa4d2a09817d97e5068d03d6cfc26f232ca -3a5be8d73cdbd96d6f0226d134cc33efa0f6d489d24970a1fc4c08c0c3fcf04c -3a5cca518448728bd878eb6fb38b86cc0563549a7707ef87d85f769bd23a8ad9 -3a5ccb2a4cb5d3f2e5bbb1f787457651a3b78744ed282495c673ac9c2f8aca27 -3a6b7d19dfa3e2169c0ff914c4ffc256125c341329adbab7a8fdfddec8eff652 -3a7b1403458ad65d2f786233bfa5fb11a632ad3321419eab57c3d28c2b91449b -3a7d762159a7d974a06e97d4e354edf75a2fc16d1fc6d286e636578cc9f2d936 -3a8c0580ff5caee0126094b6bae5503d5df0507ae17f61b776d01207fa6436a4 -3a8f2da8715cb66f83d38ca9b654eed02324ad0b024d721488bda4c56e82d1c2 -3a91a769c8a59dcb098b245127541d92fb19e786ac446c84f0af00dc06ec06e0 -3aa7a507d060e6aa04ec1717e29782b1d2288747af7d53c37b27e9d453762378 -3ab6e8ef84858a4d69890843c4b3f99c70bd8dbafcf216da718972d8c9dbfe14 -3ab888062c5e7009c86d693923a74b0b4a89a93fded14e6845fd84a16732f16c -3abb9c360bf4f926d184646b82cd4405944501ff3c4917c762bbd8440d66e0d5 -3aca4395334abebb408112d95adfbc51e7c0bd47e53570140f2089cbab0350c6 -3aca84a0ebda2b7a3f5000614c4e6bbbc0f9c53d7c586f951ba70b2be87544c2 -3adb951a441fdf5edeb69eb3b79cf1d840368ed190b123e214cb7e1a46ff7fc8 -3ade63a5b9cdb64a740dcc6a53773f1d562b19fd6a3678e3ee94fe022aae1e52 -3ae147967ab4b1d5f72dfb2268e783ace373f4d3f1854c9f4b306255e383d6e5 -3ae751ea3f6c344d461665f3e1aab6803b887810dc432870bf2d02861685b469 -3ae7bb4369f33b979c3d09afb7b5b59e829aa13c1446ac6585a57351befc40c1 -3ae864b06a78680fa577adcfabadeee9794ae830dbd14705377eec3c0f059160 -3aedef5d03aa547cea7644460db6fe3661a5a652e2e9658e3303f687cfe44a7c -3b005ce19f3130963c36c4a3e9b1a5a2a26f6f73d0d95cd10c77092a240dce59 -3b159d3ac09f37bdc31866a56100bd090d2a54894428e34a80b3cc4345261865 -3b1e16c08bf38e8b7e61864efab3b47804cb095c718a1bb0e767c73b84ef6dcb -3b249c2830778bf6db21d4f038e79f34d49a30373defdf2e150050d77f1c4621 -3b2708585e7f188059cedab0900277c09199c28881256a7bb053cec89ac4c41c -3b3a0f6b820b1070f54da319c9e2dfe36489b7659d462b5eb0be3388d2ca0589 -3b3fb1a6f55d36ad206521ef17150412eb28a170377b31490733ccc52f9ac35d -3b45bf1c827b9c953a47485485072dde6d60f80842c7cd4111fed94a76af6f36 -3b47a3d845a1fe513a3316b8fedc521dcaf0c0b244ee8c3084203e98a0ebdad1 -3b4de2f9c2686437a6c91725bc2db442bdb0f827e53ad871fa14aee2c3185824 -3b56cf57e6addb046f99cd1e61b9100a91df9c3bc127dcf2881a077ce8037c8c -3b61fb93179f5669c056a3194e847482d21502d761f4a818c742168158ad7095 -3b64e5df9e924512acf78b5bcdc8e36bddcd735cc939be15fa8b256c7acf0728 -3b661396aa47d8b4911a98e9f319c0447dbc8226e0878da6ab74724ba0bb23bd -3b6ef35a61cc52ab4e50f71c4320e0e5a592da4ecf77e7d0b58642fed948b7ef -3b77ba6a9a3494a5161ca7aca9b2801859acaf40076457f944d5eda963b0cdca -3b7c2de4ab07bb32a3da3c63666ef8bb0a075bf8c856f6a94c1aa521772e1123 -3b8094be9ad943e2b4fa6e4b098f16ede7a89c3d36101025dd07267581571679 -3b84e2e6ab8ab0809a40ecfbb86c819e42960d36b2fb7454d2d9481abcde008f -3b863a811f95268e7678de56b24c56f119a3f9c03ef52fb3b433a1bb6e83c074 -3b88b28efac4c125a50c1383b8c49c208698e67b4f89bc2479dedbc21cb1dc2c -3b8c3ce9fb7af3000243f8d3e25cadd3244b2f0e287b089d3774ce765b5aa249 -3b8e665139e8b58cfca248aa82485c35f46d6a443bca88efe205ab5b108587bf -3b91560c68b0d79692f82b292d8af1e3cbfddb15441ecb3e28a2246a3c554389 -3b9a68d10319ea7b8ed18df006fd2d55d522e71815aa399b34c59d73fa65409e -3ba59f37f928026651c95b47973f1e202a42676b7b779ecf31bfce04198b0cf4 -3bad902c846311447d6503d0d9385f8178f892dddbdc2844935ce0407e4b3f56 -3bb2dc89a9e6f1e334b7d0e4784014ef32f4a7161d55d980151c6f9aea1f8643 -3bbaa1488599b30ebcdc7ac4acd6292817249b6a3b96be696dd42cc4aed944c9 -3bc82ee917163f4e87145c4dd443d0f33e0c966eedabed3027392de2102ae06e -3bd7cba1bd488525541f0e604490375ddd3d8b1bd47222c2f5070769fd6da0e9 -3bda4a575b8b306a80fa0d4a7b9a3e1c91baa35405901f50e5da8fe8020538f1 -3bdde1104a064647da729fdecb24ad416b4971b865ad2a48f741d3427e5cf9c4 -3be6bf02d2418c1f1c626d6cd55c9d52b4a400f72136a662aefb3ac6ce2ae5ac -3bee09bf77d281662ac3b7175b9860c116c7a0709954d91c3e2760aca7c00e74 -3bef4da48f23560d9fef08e33d85bee280751b78ecc79f298fd88a5319478c43 -3bfd31e2cf246dbbc60621879b25dcb77c071df1498b0903827b6dccf4d50dac -3c00dbe225056d1028fa2119390f7b6c9cab1f4136f9ba11512a9a7c3ccdc7da -3c089cd05f8e60a4336446f90c1449496a1a70720dc0efde7f3085acdc2a796b -3c08d5b0af6a4f4bc42d0f3370cdc3279364f926730a4190b5e3e452ea1aa342 -3c19eb42ac4c491db219712e64caf90ec7c2aab003991ed1c018c6f9986ab1dc -3c1f68baba708d92b08b921b7008abf09deb59c4bf0884ac2a1a5c667b3077a1 -3c2bbb379a4b1ef283a6f11c45ff6cf78715296a36e848eb27f3564ee77fe383 -3c2f93317860f6bb0590d5fc4b229c0b4ab889fea5c03c93b0c3978fe0afc1e9 -3c34f192596177a45d1a7893fec798fe73823c268a3a4bbb56cf838831abb352 -3c39b6c05a3a2fedc0b010a4f4f76ef3866d57a599a078f0c4e32bf516b0283f -3c3b632bf15edab4e0d25689c48a2e57f464661b0b3565786836b44d3d7fb067 -3c3dd03254f9b801837c54b2197c571f52ceac90b78d90da3d861dace7260ed3 -3c468b89e6f401953c9e0ead1ed4e6c5891d3f99a3ee763839f8ed550cb83678 -3c572fb98ebee4800a0777d5a4a2cfc60fbf9200cc50a55717918ecbcecd13a5 -3c58b7e5b8cea79a0e8f5f6232da0627339272c3ba95272dce67d36799746afd -3c5982840de49e96d80eea559c15a71072b6ff96528e7be81fe78aa3f07a0770 -3c5f5ca2c7fbd50ee42c3a906bc87e8382c4f068c9c189beb39d230b17e10790 -3c6bb18799e9fe19668a0c63fb73ecda98a8eb82cb800fefd665d7655a4b7561 -3c6c9dc7a35eab9a00b847aa28e5f37122daf9c69400e881f14979bb1c9b7c3d -3c6ee8e06229bf5c9ffcb97ae7c412aadf86a98e62864e069238bc4079d7e4c4 -3c79325a4541a445691da6e93cbd654d2eba7106f75d3de15ad1a1924b35b7cc -3c7b7faae3fd69179b4650eab239d40435ddf86887fde416c8943690a4f67b7c -3c7cf9ec7bd3efa422a1d138656922224835560fca1a63bda78cc94b0ba58ee6 -3c8af236539abc018dab41b2466ee9e419fa204c2cda587f23450e681ff35947 -3c96a25b2fac520b5b14dc6813da497c63b5c528a8ff91f5d74b93c592f138b3 -3caa6c654e3fe0e2d80955ec6c7278d44b55af64725d4706108a882392a6f026 -3cb8e4c1216faf2e811295aae6084f8c94e9bf8a9d28b3cb5d56e64bdf482de0 -3cba52c43e33e862bf8f49aeb61f992bda90eb156767c91cbdcec97faeb22d85 -3cc431ac28b8a8184390f759dfb6622c1a55edf309adedb4c7f4ef9b3c157a90 -3ccac64abd8eeae582f42754ca2a63e58e234bde47f5779232cbccc8b499a40a -3cccd11c3c238acc3b60081c88bde53ecf93584d3a638951ee1f41586de0f2d3 -3cd4767348fb47855cad8fddbb19cf922afa2bea24ee2c85a503a748bdb329a8 -3cdf7013ddf16a17dffaf1023523c2a454c6b997099dddac26b900f63e59f6d3 -3cefc637af58a4e2970e84b072b3e1517f0e75166039fffb700f3658ea781503 -3cf5b949113eb9f8063d49393f095fcd80691dfa93a7196e5593e5ec59acbc7d -3cf8ab0c0666013ee5716dd3a53236df0f73416058b5d5accabbd81baed04ed8 -3cfbcd061e09fa7c8a9c93b0cf780edf87e338a6dcc8faa23378a152f99e6ffe -3cfe98898a17fe6de12b1468d9b10637e96857d7ed08cbb78fc2415f3c08068f -3d03ec89b8fade28b4ceb64b0593afef3a0afc6f6aa2a38cc8749e53e5070efe -3d0a1e576e9c9214b0cbfac6a779d499f26764adc08345028119ee4f8f5732a2 -3d1e3fe01e7abd559159999c494274cf7b0ad7872d94fa644b72b4329a8a7914 -3d220c796f07b1b74a1e95e4e0b8ba3b8d9955230ca9fdfe3521f2ff0d23d0bb -3d22c4e5e969687f2c5b61cff956a2e38fe2b9994e1818f557479591fdc79a65 -3d2a67dfdb0cdb594c2a52d14af6900ba459a4237522448691ad4e30d6ee1ac9 -3d2f000c87f4cae6bc9956dcf2c445dcb136bdba94dac60969634abddf62df24 -3d312415b0dea4db36dd78c06afc2eb1913d4403dfd5fc720329e36ca41dfacb -3d32fee70ffcb0268202f788a2a563a20919e871f3f13d460ecd3046b8280e54 -3d365db476e9dbcc111c09358a80b89a32e0b1498b1b0bd2586c549bf8fecc96 -3d372235181122033ea7a06016709b8547151e58415e640fee618df03063b4f0 -3d3b2ad36f21b8d3491192c8668691a458c5d7e4dfdf315efadd83c041df4273 -3d3b690ade2e51f114cfde14a87daec014e2ef1b12b20353ece7b6e253362c13 -3d46ca7e37f6544f4ebec2afa278536b641f9e2962bc92bd4b9987bc44f62027 -3d4ec8a351f4f12a81dbdf9bfa78a2b2635519ce4dd81c783dbf467883bf0c88 -3d511edffecaace24e6d18f96d9305aea0d36abba870a13825e12e8a88abf619 -3d608dec990f21634840ade12d89a1a3f716114aae80dbe6ef8dbdc5b9db0ee6 -3d627a285bec4ac1b4bf27d057a2db829e8d8b6bd0762485430b88c73fffe173 -3d691fa2f1493f2e2e39a81f5e10aee0abc4a011c0e042bd656e4007ee428f51 -3d6d03b438def602aaea4f8064b91c90335c15111c1eedfb548e9f504934ee5b -3d7682ea1cdb4b12065914115310c5932932cba0d7978e31ae2b5209710d4736 -3d7b9aafc61914af9d2d732c27d73a8dd2131ae6337ff6fb1e19734b03cd5c7c -3d8e997337437fa9c797f667130d91beadf5ce4168dc6cb4540ed403f512a303 -3d8f094d798175137252670e31b7c6a666af2805d0d96bcb06176a15b5f397d4 -3d9064c9bc54f07b043531fae3f65274419ce6232210880ed466090496fb2919 -3d9088a62c398c2633d284107e073f909393c837de19fe92729bf3b681bfb5bf -3d9c180960d1c900b8e946fcfde52b41659bffd0ae543633738b76e732b8af61 -3d9fa9ae6f4d9bb9d78504d03cac9588e83847c384b2fcb35d81fa46668e679c -3da5583623af81eed88c951019faa6cb482fbc170bc53bf311e088af43819d4d -3da5c0307220332236ae57dc6a8aac6316b306e4798057db81b4545525213842 -3da6d18ecd349dedb2552146cb68746af2397cfa26166d56bdb11ae37cde7928 -3da761dde3ecb27009fab650839fb3a7607e867519f7922f31ead7ec75f5cc8a -3dbbe3d8d5ec151272c66bcd7570b61347ee1ce31ce700ca5a4b32edaf3dfc70 -3dbff21096413d1b0e6ccc19cc37911a5073bde7f0db0c6583a160f875793577 -3dc3fb7cc8ef54b95d94348044467ba667cc323469e307411b54505beb20bed8 -3dc94bb7af6905e8562360a53b8106e5deca5a82b64c4b35e630f7fab428077a -3dcc982852e663741146e1988e09728280c447b526574424a55790ef7b2a060e -3dd967fc0f1b7b8c89ab13f86e118405cb3ca3528d0d3156f31c1cdcfe8f7d88 -3dd973aa6e7ad841d2a139bb702781534cd84d190caba368aa011ca4c1ea180d -3ddebc189d8aafa476295948c9b289e8fd94c94556ffe9ca4647c0b45f13e0c4 -3de118c1ea7b473c785acbde07db5e714982dbb397800d24dc01e499e3390a62 -3de2d6468c6fdc189240dc267bffef80307daa7863e967e569027ae2508ea4cb -3de810e32a93c3ddb343fb2ea43785d078a88cbe23ed6539f7d225f9ac985980 -3deafbd56fdfa6b73643847d125ec5fb325314c703b584aba6d98c2cfdcd94dd -3df89e3e38fd9818c1d6e048c5c9a0305954f582f54199f18e6b9f2feabdc9c0 -3df9c95d6f1eb84f0540f4246ffc3ab46ff0d6026891850982b2ee9dab616a3a -3dfaa9c78ccaf054fae84322dcc3ee11c42bd3e598f38845bc6e4ea880e9e676 -3e06171006f6cd25e50d4c648a5ba67c200aaea41cc23dbbc9566b776e379955 -3e06ecf7e6cd0846011d919b33212ef333d62cc61118e29330696b01fee9cb95 -3e15e282d71e30c8bc7c7f80a9a255e0d0727c72776758bd6b797ea125e629f5 -3e295e78fb6f8b68ed8590c20ed53d6e03ed0e6538aad51cb3c879a657989721 -3e31357f30861af661b38924301eb94066dd869aca7086edf8735ca41d81e588 -3e4825c120c46252e61fe750b6398650461c4780752d8af943571726d487c30a -3e5268038cc95964e0b0c2a26d7b7c17be4c79e4124c42f4682c37b6d192df7e -3e5353fdd7e01a2d0f972cce0ce8dac16bb10bddb30a479b1dd0dea3411dbf93 -3e676f534064519ec37f3f706a50cb8f530dbd0325b01e69330de6588948cd64 -3e79d0a463d34083769d0a11c8168c3f45fd97daf55ac15beb85f453bd7965a7 -3eb5dc749223a42a39dff803fbce823c3b7f385e468b10cc484dc363a2c4586b -3eb814e7b3bd0d3c2142a41290a1c76c372c72ebf49ba00b118306e1ada7b9d0 -3eba7c38c4e4e614bf481ea6e96ea0fd66517ce0d40e203fcc329bc416c83f0e -3ec17a72a05fc002b69532c3a37661570eb87df6611e521b7c74e47b8e25727b -3ec655157f07b630ded2f1399dfe3da69a5ca2f3ecdd3c7573782a64ca16e2d6 -3ec9644f00b972ee81d885982a4a6998967c404c448037c709cd95c022227c33 -3ed218bf4789fe3bb7b0b21237ddaf1ba10a093a2a822fa7923620aeeb369495 -3ed6405bfe5f02a6d65ef2446159ca296793cb958b9c5920cbdf8afd02a0d446 -3ed6b74943a97552a7ab6e5edd0f33727c04dbf6de276efceb0fb0c5444371e2 -3ed7ff0272139be091787dfe504e8971d454fd0ceb024bb772286f3f55db3f1e -3ed9d004b4a238af0ac25768c288c5c0dbe2629017e30cc969cbfe6f4704f58d -3edea2d2964e2b8ccc28951224a33eb61fcb0a18622fc9df5a30651d33fe3b1e -3ee027559ccc6581f475f399d6fa339c699a6d5eb8b99ec1c18a0ba2f42a2b89 -3ef2c014fcacdcc88b68dd6a8cfd3a1a58440129afa52cbeaf98192499d3b13f -3f05156a6b865575aa2975c38dc3d880c2fa3d557df48bd32a9f2508b93607c4 -3f0e8f7e16eea7478d8e122d9e29b98f093f2d2c288df06db83d22774cbb2a9a -3f1448f091a26db5e3c24d2ca2848792e6d23990e9a6a58d0acb67eec480e1fb -3f2ef5f4c546ed5031837cef72b11a312469bb9578db14b88a54c287ac3932ff -3f2f1d877d6f8a4a72d802ed9b57160d1f4d5075b5a9821c16d74f5958b69da5 -3f35f0e6ea1955cd2f5cae8dfe132f233b75f5ca85a76b6b0c31249d51ecc664 -3f39c5b76b6a0afb400fde34a6f25bccbea91ae0ce9f5e4fd251aaf5894509dc -3f3fd19320889b801411004912c7674c45178b8d95b23151284e66aa5149dd6c -3f4fe4eb1c887f409a0a3841b6d548d56feb8b64fe4891ba9213223ca9837841 -3f5e9add3e26d3ff0c52bde1a8a0937772b3190ffa1ee8b07d271b7470f6d157 -3f68bde0a326d5f5f28a2347722b5669abf818f1831acbddb420d838ad289e53 -3f6aee119abf40b59f9de6deb3740efe84316b137f4ad073cf9d9df22950df46 -3f73efd194648a47ebf8436654a3b0d7b6911c9ba347687026c6a11859960961 -3f74b08ea74668a1305afab8c3c6e6f0db682ea97f9097e7342e2072ecb6b74d -3f77558be78ad11289bcb90f87fe6d376f608218f51215ebf1f34c607d88613e -3f8cc385c5e63d20829d03ec6d5c1340661a6045e92b7b3937c62a292d31170a -3f8f4f78e8cc1b451bf3a59a91a3481b583893c33cee1fe816a30f9b6716e9a0 -3f909faeea1faa47316f68db698b713c56c387dafc2f0f716d3fd7c043aee79d -3f90bd329da9146f9126c7c8e28470e94254c7063959ac10a6d826d31e846151 -3f933b7724a11806e6838b7cfbb0063154a9592d4f6954805904802bcfdf6e7b -3f968f9dada3427192c558feac579ebe8ce0b9c07975b7b976b4a272d77234dc -3fa66d0effe721e12b1be2f023d5d2ab34fece19126cfea2dc5ac1ee9a7a6019 -3fb9ef40d059926c6ca03f9a237dd35f6b7b3922b0a5db169269115008cb4197 -3fcf63219a7ccbbcf7d987a193f6c84459ee6f82aea737a7832917c882d9103c -3fec87cd91b515ce88df7a40025b1122f5bc51d40ca4c9f8e971513cbeb451d0 -3ff098cc98f4eca49893798bc11bf9032b04df35586334e68222088e4311cffa -3ff1d6f7af86afa7a049dc843dbcfbc8ad7968f64e76e5a30fb1dafc0c138bc5 -3ff346f9017582170ede4003fe5a5e75d8ec4f22b44722c76eb3e1de3ff2e5e1 -3ff6c4b9afa67e88705f774e86c786ef126f874a032703e4f5cac8af4c1052c2 -4006ba85d2abd477b25f677a99daceb29c8046652fb50a2d6327e03d7cd4e9e6 -4007222ec8547246a1166e0b32d313877d83b1d7cdf301fa0b7e00da3a6c6ac3 -400acb4f0462d5040a34c399908cdb1411bb2d3e5d2db9da0e638004a5663c25 -4010c24ce668f558b69df38dcb7e31ae5cb9c6091b1333d17d6c3c56103417bf -4017effa6029694195835eec414e5d585e98aa2aa0fb63222d7de124bbec3c34 -4018c1beda54d5ddc64ba276299aa1511c47e6a60482e5cf8582b7c578ec441b -4019fca1b330ac0db5a608b25c301d56373dae35d63fc1c09eb924c1a6b3100e -401de293454f181cd606c645df346091b0eec4aca42ab2b5579a1f0e320db1bb -401ec85f0671084d3613bd84d2b0a9070aa41a2baa53a9a2d7ed5d43dc572bc2 -402d4e168ec957ac8e7c73315e5a2f83cff53baf3d2603c0b3fa360731ca1634 -40307ff1007410d8d3de26d38e199d5b72dadfa6b8f5ae2a522c90ac1268acf5 -4030f68ee324f29c0a5ce730c346f0d22d726ec5ff724dbe601429f105a03a36 -403538678bbd5e4a1ab1a7580d7594cfbe9e3be93c20eec85f1325f5275e6baa -40434773a641fb0991144db5318722443c416b17a6ffacba497fbc734a8c69ba -4047ba59f3ebd7838e50eeef989e1784562532b996ec934711e3d62a29f921a4 -404805218cec6cea8926d116f129bb4a5f30368689d19f1ee9ff1982552a00ea -404c3024913f5e99808d0053227e8e83823d4a1cb2bfa1c1d22451398ac8a3ec -4067d213a77b2c1b9ec67bfef24bb5ff92b47bb553b3893e3e1c6ede436231ba -406d43769024e5551aeb243f8bef8edbcf9b6dba030d452bfc2663c631d75fe6 -406de82bee677edaa2f0939a26ac357858f9208b2a9453d919e477451e3f111e -407372bb641468c7b85cddb6185d7fcd2650680e05a6e5a6471fd62e5ca70c4c -4080a12a93f2a69e88d19518365635cfb0c0ff81e54624d6376ce286ff830670 -4083b31599bb95497e57710c4883f09665c92312c545f45dd1826af570c4e0fd -4088bcc3c1790350eded8b52c4c729a8a6ea392af40244d3308f6d23ceaaab81 -40928ab178f8468f40c245696dd074a3db4fad13ccfecfd4bc252ff1e2b1b382 -4098e7710b4d18480963c8cfe33505754e5da1a25a3088f964305cb6ea04c007 -4098ed89ac9fd989651bfd715f7e9370a2c4caab9b5d35bfe24330832f3faee4 -409e3d7a2fb1f3c5c16286e1f4b1d1d8853827200fc6a46cd3d4fb587859fcde -40ab00a467dfb55614e8062781286c654aa905b011123c123bce649ae1dc881c -40b0ff3fe608f17954849effa0bac6ceb797f39d13b218e5f07f770b512c819f -40bdb859f0a9e71a98be3155b01293bb711b4d40d044b2867388c5211c77930f -40c3def1a241786663ea4d147c7db89e34f489d7966a6a07aa8669dc6977ece8 -40c72e2f6c8884993b8342c79bb6e037f601854e94caac3e22ab7f8034b47886 -40cb9855c1b516b62026b421f06ec1631c70d52ad4cfab2e030787e554615396 -40d403c23ca5f5875dd442f7ca45f66d1571fc6b70d949b90a2a9264c6c0f62d -40d5dd4b8977b133b6df59cbcdfb6534c7122249495b403bc10bca60c01717e6 -40d66ddb04223d2316e7efebc24e5b86cc7ed9f3833d6beb40c408d6941ae471 -40d876c11dd8472b534c34275e6313e9a0b5ebaa78ff19caa21f562047947f61 -40efd1d04afbbd9961ee53a7afdea571a2a11568549ed464a81b3350853b01c2 -40f4dc0128bdfefd8a85f8fc0965d4f81fb303312fdda8702ccebf58b921af57 -40fe7b270ba59e01c3e204f9b73e5d9e7932ae668eee8343a44e93cfa6407205 -4106184111b24c35fccdbc8d5bb729ea021711fef3d556997f0a55bd5c16131d -410678a7fc231a70eadeb756bc301434a04002f4173378bcd7e10a27a83a6104 -4117cbb29edbc97d96380b2519e4f693f77b3cdcee837d89a706672d7df7c82f -411896549c3b5b741dcbef2a22c17f27969c4fe4f3f7bd8b8c49d140fbe2f728 -41315261b963ee2b4a706bb5b7fbfbf5996e8ed4f1c62d309bb9a1f2a652d019 -4137824217f279831c08735ba23316ddd2d77a02faadc604d9fb44295ec5d696 -413dd0e02de3edb7755193078428708e5b3590a4950b2e02785b8d6af5d0f246 -4144468c64d7e4ae056dfa366430374039af1ac3fac9bbe1eaa5656cd7cb8e15 -41496631975555b0f20163b9700a80830de88b460e7e56ec025c61c97634d775 -414d5f2999f3eead375625fb00b0dc10f857a4ad24a9d32be07ab28b251a98ff -415635d6f9b1b71cde5d04c551a7a03ff551f56308dc7fe0253c3d02c48887d5 -415e0c948be5e554f3a7e3e73c364821d3d3acc7c8fc5993cba031815e4d9b23 -4168ed870f753287b6c87f1d7b3f4f85d56904cfd262e06304c4f0a6f411c4db -4172fd0d73d8b648cb1782de7c2581e67d00e6f76f67dbec29e1279c902ba131 -41794773c13cfc7433544fb011ac84f3b8ebd63f323f9ad10c3eb7d17352769d -41896bfbaadd43447c7d61f63c639c6e2343f4d03882ed221cb463b83f2d0ef3 -418f2a362d499d0e8352819c0d1d4bbe78717ec312e8d859c9324c4cc7244693 -4193c4580bd20d7aea9ccab09cc2686bba94c05a9efb932125bfec8ba81787c5 -4197fca7d1aa01cd75738df39f31ec4710e84208465fdb5a7544c8aa1f72715b -41a33a4c88c92a479e4e56b23fc011493e378daa230e8f9da4869d147a342972 -41a8ef820e87643a02d46c51d1b5f065d7558fe5026e5572e82cc940b040ad99 -41b23dd0e0699eda3dcfd6cd333704bf36976bbc20b90f709fad9d9e8cfd83c5 -41b39ae1ec2276708f7b50560d16f7b9ce64fa93ec848ecf4cd611bb9abedeaf -41bbd6afc7d6e3e6db1b51a5db737599125d030fabf05c7b29a82bc2f2567482 -41cabe0788ce798d7d4225c4563343c2eaf8529dbcdc0f2a28eff6206daa30da -41cefea372cfb5f795757bb1106f0eaf445fb42e36435dc7e3fc41f8c217c7a0 -41d4a0316cd0b11c6dd04c9dfb6e7fafccd134f4688a007e788ffeee9244786c -41ea13ef660e00ead0aaf43b2246da69f8864a113ed39868b0eb0231c40a0228 -41f04be0cafdc133683e9270764affcb77218e70d5f22af5ad49978fd29937fb -41fbeda89641921ab04a0ed211fbc7d5593c0ec20228c13d6e1e67a7e603977d -420ab7d61ce15d22ded14147f918f076b3fca3e187e03964302843d414d661d3 -421115631da4185d809a2c1074c0176dcce56bee853b77402d09d9f953be1156 -421896d86e89fd5e406fd7b1ca8c12bc591658436453bc4ad53eca74fd4f5599 -421f2131c2910aeb31af919b810011ed21b3b0b0ed312d3640707188cab9deaa -4226d33153a176978c7a54203f65f0dce06f20f8139391bbffdf50385c1828b0 -422b5f6eba96249d7e11f8e13dbc71a73b4a1345727016e1cd18f515e31b658e -422cecee7e1abc31041333fd3b37ab488745073355b5cc81b391ce7b71ef11a1 -422fb7d9dddd4ae5eab56fc3dd26b60553158ea740c50cab984d5f48d67222f3 -423f53f675e765a38535c1231b410bf83bd54986b68f9235ce97350b6d44c974 -42435e1738563619d3d5a3042e0cbf55e00953e92e086e377b5f9b03c09c1008 -4246f9bc83ff7263f800721957f34a8e34768813f41b269d23d569640faf1f46 -425cf5573ed2026043477c860edd00b3066496db2505144d122c233dae99c616 -425f764a53a614d269a608ae4d85f66596709f6b9f9e34806cc47027a8b3bc42 -426c9edb16bebeb8dce7bce2f18db8d19a2e566f064b0d17fb184210b4b163cc -42742ade2f99e48c82eb3cf4196908a6790eb23d3644bfd40b87a932dd1ab7da -427aa4caac035b7ed1f4eb975ee3d0d3748e0a66c658ac34a57b93309c08e040 -42811c9d00a1c41bd9ffc0d414d08abe1e3d36e06db282e7b0a39e6a9330cefc -4289c373ffae06c0a0d3993706deee29062275fe37a28537a406829d055fcc0e -42939ff3c92ab1b1f14a82d0c91255d070e490732ebaef9c85806aa489fa8dcb -429879cc721b1ec56b03952966f8c7a26428f1faa17e7c9f369a67073d53233d -42a52759754cb162471991f1f99a7d37fe90811230df9caf41e4d622c7533fbd -42aa8928f52abc63f40df682ca95cc961fc7d0d6ef09d9a18107cd4e25fbc2bf -42c8592ae6876393873ea927c1df065388b4b6a524c0cea6694f4f8f07022fd4 -42c9bbd1e6e0932d0e5f8f65c1e817ef34ed37c5845954b01d08193c53b0f3e7 -42d7b948365945999b91ecde5f90dcec7269142f01d211518c165ed9fa88a2f8 -42dd71da0c9a58188613b657267e3507206f5952176f9cc60840c0992763b074 -42e01125f35f234d1353a5dcb5e9efb7237881ad48409a8566b1685861b33cc3 -42e1181983e3a71352eb7fac05fe1d939a650a6df72ff288e2896eaa44d4d651 -42e78727138914797e6e86d217ebb183e3d7e3f594100af9fb40fd65852865e0 -42fe9af69c90a8e658124133284dbca09e8feab768dec8d77e371b2368019f9d -4308de77939f766deb666080f95cd95ba628c874da6806c078ab12b80111db71 -430af0ae6fa3d482ce2fa62329ccad408df40303d083251f176df943cba68c63 -4316c3d6f5596547edb638d8f3aba03a10eb508610b901d72d42896b2596b6bb -431717d40fa64293a085d5a0180b295edf42bba5ed134f22e59d93667cdc9d23 -431ba5d71dfc6e93a8f0e9c1ebf3faef9cd797e61753d07e96577a63210dfaf2 -431d83499ae4689c637e5c154b20d8f1df6021b5eae02eba45c78650d433cf57 -432ce92ff114320f9a1dc4644d9a156e9f1b48340a1aacd46f1161fb99064f40 -4333b5d4e8f1c7e9ab057e184620cf5d5f2f15fcd01d740b9d5764b6ac39bef7 -433ffbfea65ed432719dbcec05bc6d39e34280d7fc6f3ff591c33b23d48b020d -4340a5e8b37e0a687bb2e45944e79aafbbbc6ec6dde104234b95af9e9ec7f0f0 -4341b8eda2cea8b1b43d1f8d5128384a98eb18a98fed5c57a7adc3631c0e2c72 -4349ac5e6f6f9ff59636a84d20dc49788549f76a5b7dce67409c94d5bfa01db4 -434debc4ebfbc0e53c774347142dfd1b764d9e9c295e4be8791dc75ef0875ead -435228fbccf5d605a52dbdf1090364f25df12a4e069d7e70cfdcaf2ebd15b1db -435a56d60cb472d5154165a43a089ff7800e7f2958de56ce13a61a01b518e064 -435ff2f9dd8df45f46af786d388f71acf9526592cf11f03ac38649c75b4c02c7 -43626d0d6be19cf27512192884402661f47640cf90d43f4619453c375b235240 -4367fe2b561319658b412537922c30e897288b8fcae0494eb8c760e781e51a91 -437c87373b58cd15865e113a0b647a5f87f418342b6b21f84242a20542cca812 -438cadaabc7914e353e76fc14f5623b7aafb873fad4be7e50818045ec230af53 -4394e0b4c3cb9641b13c328806b3067b1d900da947f814508714129a4bcaac2c -43a9083ea650a8e10866bb0fb11e0b2553cfe385612ca1becf7ceabed70bafe5 -43b24e9c45666956174c2f8bf667b7f307c29f8796522672ff70e90712c61d24 -43b3fa1ff2e61da9f05ec08818c2983ca4f95d7b4c2c27b83bfc41e080c53e5f -43c28946fbb6a03c2e2c6c8e918e6beb408caea4975459322c8349514d71edd9 -43c369a90c4e7d941f27c568fc3fa35ce5531508a724d4bebfd562f9970025eb -43d5f277d4075910102d3930f3b37b1a968fd1521219b0ba62ebbdc6cf30300e -43d8812cbbc299451f6d2bc37161cdad4d3e4c6e700ea6f27079517463223516 -43e381863013ff289d3d77ea0b18eba874941d4f40338e0d05ce255b0600ed6b -43e3e0601dc4e3b060f5949faabe381be657f7d117c9cde8c5db02c5d1f855d8 -43f007f30ebdb67645f24458f3f1971be6b81397e9a9dea20f5feb26f2cdbc0e -43f7b70448c7f74a33c61f530111f121465879bb6f1493c34469b9e8f03a984f -44079501ac825d2e6497ec18eab50eb9247db394d32b608423c21320eaf5472e -44088cd4c049e44a8aaf5941a9100734a1065874feea3a6a1cd4b6a14a0e0728 -44091b4e4d2a8fda6313b66970976f6ec6503ea04c4999cca086221e21a8d7c8 -440b29077ce05bf4cc7608c993db115807d23d2694189915bcb09e4bd093cf8b -440fe7fdf9474de1cee5862adc3e46896435f887df9bf92af4abe4d032829eb1 -44129e263896e1efefd50df66cae0468fb2f59dfe3876022878d3b6781c20278 -44258de4b2bf1a80630c6f90833a3ee00727ea6c4d6aec59f4319e47b69faf70 -44340929feb0437352f8281f809de71159b1c242708714d3455f1ed65d53da4f -444f046674d549dd3d7b949ec4fa28fd40278c9ea4cabe0a06065bdb6b7a62ea -4459fd4b009c03ca62fdd01ed79f441889820937a269ff8c3228aa4cfffbaef5 -445e255e4e0e4d944636bef5864c5f0d433b97672dbf94c13a73b14bf05ea8b9 -4468f65393e92a5af04cb2f4d942e218aa14ac49eaabadf5f7d50c70218306a1 -446a81c90e6063ae5b18a67ada4057f94a3dfee026afcd04440f57c111fd86ae -446ce2664b6a495912d1daa5047562dcb03dec1ef9b1a6c65ba636adea1a52cd -446ec76126744d541b1efd270c71c076313f077872ae0275695d8b7d3647bf18 -446f07d7a4d815953b5c385235922e1ce4617a71f4d583bd380d0b4cecf0d5b6 -4475471f54bd9ba27264d3ae5dff6460567a20bafca5f495984f07012ad3c581 -4477d4b92769ab2cacd70ef1c3dd42eeed01210e199b9689f0956475871cf9d2 -4478a755f2c9e05a0a7e121957ccec7b3ebebf4e8c367f46194fe503a20fc36f -44859b3cdafa864a0a44418611e0f4ae44207badb48691bd73e0ea2ec09009dc -44878e0d438feb7f68ca41f16100dcb6438d113db0239f584a6d622b5f2439d2 -448857e75e302bb4124fe05f3e1b1df19bd944b68c1abad817a862a5b200049b -44940b9355c44bfa03ddd2da4e4b44e066cb7c1d062f75ebdb66c4fd4ff237b6 -4496c6e58edc742d2b8b0e7f6db9751a3beefdddaf3e10d68015ce43b87ee29e -449b20627a7a987ec5b2b4993b4f8256f1a693e8b9d04ecea2ca0a7bddfad4ac -44a0cdbf21785ad9c814a46376ba32bd851876e6c8fc7cadb8e0e9c3769ec3db -44ad57a8f5cf75c5a6b03b4fb0f54cb49d970a44d72cd4fbace940f8f4d63997 -44bc45aa67a1f1b6417f26db76d3161c7573b69f1f46f6a39a9289ac6f7114f1 -44d73575707579adea9845f3c9cdc2bbc34aa562341f0b2b30aea12b97a18504 -44d91ff33a75267a9f7972bda696709b573ee2c8aefa7a66193485a3ccdf914f -44db14af1411eb8d70cfbc12a444861a52f4c35a67a90c704ca48558be3bf48f -44dec7f1287991e2fb8dcd67691cf0d3246c949a941a5ec9ad9131833a6a9b61 -44e1031a5e735e3b9f0cb69b35bcb96f2d776747711b5a743f1065d609ecce0b -44e46e5a4494b43bd839da3615d42d1b9e416e0ac076963647babc7ebddf6bfb -44f924fc63c3200a451721bd19bbb83aa486f04ab265518c1f6313168510792e -44fb5e17e94388b4535b2fa276a2e7593e9b4491bab59d78690583cf214456ab -4502325fb49130ba4121b9c7d1bfe13abfdb631ebcc2c1019a0a80dc438e6db7 -451890f5a467be7e16aef8a39a6cf7a66abc21ce85483a14ffc9ecc32c0d787e -4521c673f08c6cff33f26c4158d7084d31e2321a3faae6b81cc43129ae6872a1 -4548b153d2ce6e738fa910883543598d294d9bd9e1869009d557b6ae66511e59 -454b3e9ed23289d50b25be75606de4eee164833989b044bfac999048b7add97a -454c8c503a1fdbeb0aee166923642858fe0f3d300af982afac36e3e107fe7ea3 -456e346805608952031f699b6ad0a3557bbeaa3a4329c9f0c10cc87bba5886c9 -4576abc865a0ef8a5c047c1014b32c270f006c3421a16d6ced9dea63d211f550 -457a0dc916d518d3031e2dd904136ea529db8fa4f344211d734082071dd2a676 -457d52ebe0a785c7733a65c7434de2c78d6411fb1f7d6c77e2235d85876dd10f -4582f0c2044ce40c8ea399f8ede3c938ad5cec3dbc209fc3e9b31c0edc2445fc -45868317058048fc0d26007168501f3c48723ad781b06a3c9e4ad29ed72f7d7e -4587fd348aba345d545d31a08238cb21a42faf48d79bae8fdf1258b9c0457f2a -45900822972c3147d1cdcc536d486f3d6c5e7f1166b185283f9f21ed53f92612 -4590483a40abf7fe7e3509b4e1a7ce48a0be18d2f97c0f2ea9ed9bf75f5e6973 -459509da8dae9a5d11feb7ee630db63b43c742da8eec83c73a5d4759d1507ff4 -459cd7aed2d2683bb2810564ede0b1a7031af4fa3ae10419bcbabadd1b0e82fe -459e8481b151a42354835491e984459619b0e3292f32d197bb3d349e4c171204 -45b36e9f69cc3e551d7d10e38d744a9ef45f97d2265b46928e823db951c0c9f9 -45b9b18ca58100f98ca0b66a6f7c82ae3c3203d7fd56c9d933361c2b1f628d66 -45bded0b5549bc1767961d85f12df6eafb253f4907b256fdb21861d999d55680 -45c3fdeb0e63a3f389566c22ea9f8711278e2f4412700755a18fa260923acaaf -45c5ad5c536d81a78f9c6c2ecbd2919f0a5c408853f7751502ee05ae2f35064c -45cf78f49b0c88bb21bab012d4a5e5bf7476d909534e32c159f30257970a7674 -45dd46bd5fc5830637d516519256d6681ac8cc44534a280062385c1088d6d5a5 -45dd4cae7a0f525a9f9b3cbd07820601d1f71fc783f60eb7239002b80d81b816 -45ddeed33568f19a3a65a40b24b050a7c6e415c72b09fd2a0322a742bda30937 -45e0367911850e9f59c73258a706ef3480430b47c9f80bd9f4444cc2eb687a1b -45e1b935cd22e726caac5e00b67edf423471b7e35a4dfa39d8d74d7a9eda9676 -45e3381db21103072301969dd661d7caff374aaa14258b0e8fac72aef20a6196 -45e9e722005494c314dbf1658535828cb2f0365786a1b0a0b15d854c37ce3cf3 -45f3d0fbe2b427e50365835002cbff21658eabd0b46b8084dff4e061a3589fc9 -45f99bf9e73346e2bfc3361f849b244d2728a1b3a943ab0762d1251858701070 -4600b3de699082b727f8bbe120342ba6cd3586a9a39bb5714d8da2fd46f09877 -4601a25f02ba0c1f2f341c814cbdfbca74205827f7371e574d4a4c3ded8350e2 -460b8825cf8266739ba011239a43f2842e65443eeddf56d83f3f479e0ba09430 -460bc40db740bfb5d8eb9e6d438d9e8ee347ede0672ab2ef69a5d03df0514cc2 -4618599c5c3ca548148bff086bd9f0e9998b47bed10306fd67a17b5ad2df6e5f -4619738d1b99c8c281222ada6970fff92b940b4496f18533585013a558c73c82 -461b68e13081cf16e7b5829ffe39c7698611620590f3f0c0af45394dd728834e -461e027cb14bf32b96e9c5d4f486a2fec4a5a2865b4ce8476a7df64c26ff9e57 -462b628aed5e762b54866a3a83e90597ca35d0142c08cc7af166b0c6cf7f2c0c -463244caea086a9dc4b8123917ce4f3ac2ec0713d188964f01352aaca38e7ab8 -463bac339824081532c28800a75f5c9c31be046af6fd4a434310a2288cb5ccec -463c29e8337c541ca878bbcd7b49ffb8b12b5b5545ab35d1886e01968feb19dc -4645a8182018113af190bc1c0be7d8f702697e31bc7a314b572f20256efbd2ed -464b72984e6844e2673bc8f769966c118086745236d9f68777d3147c4932c905 -465293d833696280da06e006577cd2e1848d8909e9e4782c1109096106e019b9 -46639d21d06e79334aab9854c66babb19d60b70396f9628948e48026e7586469 -4669b885e63ce577a3e7ef35176075e73213db48f4f244356f70cce1f644d4f5 -466b1d95d3850f4dc3e54d684a45b78682f093842f98e2bf4432ee48eea6ce77 -466ce1f044abf27180c5bdec980b64ffb757694cf6137e24a5e3336c2401d0e1 -466dd3c263d345c46b0e25d8e677314d5011534e489c036dffcffb6d24dadc3c -46752c2352810feae91fee9099875564ebbc6beeda0150cde3b3cc170fd35f9f -468639c908c2af96720fb79aea226d219a425ec0febbd928e0ba54396152ec69 -46884c91312e7231205a9dda5a2e8f2fbb8ed7e54334b9c41dda467bc22de568 -468eeeb59584c99fb9386d37ca46922dce4e9ce387de334d6857d98d861429c9 -469cc0bd943319862cbd7084a6b36deb5bc2e6d7637fb1e7e0ed69ef6d5d5ca1 -46a83fcbfd18dfee0a1e6fe9548f05e75578c5dd4caeda74125eaceb7cf9ec31 -46aadfb4291c9e88bd742eb9e0844b130856ede7fb7f73c64cc282a7406a0c02 -46ac5a1f7eae7b8a362a15ce0e45e99e4fbeb4afd4a270a3aea42be18af1e42b -46b7ffb1e1b073ee8f99d21ea074ab1a4ac82e683348ac3f27c96b15ceffd235 -46be36cc96edc16e8555853ad97d308ed84fd380a0da685cd7825f2d36afe9c6 -46c452b9634c79e923f40abb27bb7430ba9461d545133c6ec74cbcf2313e0932 -46c7d445bfbe7b69a09483f2dc2383e90dc79f85509114d77dd6d8c24905bb8b -46e44cc796bfe018ba8b80f68cfd2473d765fc46b473e9869c40be6392f0a369 -46fc3447217adeffe3c5983c496c060894a973d627328ba86c74c806864242fb -46fd20ff8ff60f5bb2661e9ee66565f213c34357fb278ff52636c83f979d549b -4700fab4b56391e604e8997859b2338f3cc9f1ae63f8a7aa8b65c52133be1686 -470dac805db24a2b4ddb7b0d86771270261dbcb4b6e7779b6133032ac78d09cb -470f494a4928f066f7ed030433d8b63fa03da6b7e870ed587811be0e6c2c2797 -470fcd8d5dd52392e2437253cf06c48238313346c4483c84b6f07d68686ddd37 -4714d9e55403f04bbb9736a38855fcfb6b3c8a0fd8f976c334a390efd26d2f7e -4717d585a84c338e6b4b2103361de1ef54648318db826058a0f9d9592b06515a -471af6b00acd237c97203a30f60a3873e7b78276c71835656df48bb2c76330eb -471eaef403502c26b81f69feae8eafb9ca708f723f56b40f52bef61df64c42d8 -47293201dcc933c02b984e73f951d4a71ca24d8c5769bd90dbfb2c0148ab601c -472d9236a1b383a03b3a1b2446f101eed4f43582afc1867fc265d53dca381171 -472e32e4f046a98acf48e7b89697a6b8d6a9c9b4a2e2d2a70cda480452882aa7 -472fca5910db544f665b479ba55d1663ae413664fc6fd3b54507e4eb26c981f7 -473c98551d88cf848765baa4a4f6ac8643bc5eefef4d33a97143726a1a9b585f -473ded38448b05677dcd9a142b39aea8732069ff69aaeab92f5df50b6a8f1df2 -47434d676d08e78bfd9c2701c3f8f9eae18016c4793ad18964c8dbd46ab1f1ee -4748bf8f350add1fb8c33d493f140d1616b0346d67eda2a1a14c9209c1167380 -4760414ca1f0c52f16fae16b820d8023f9c3c5016b82a520da578bb60b1bc1b5 -47698fede91053bda054742edb88c9a17cc1c9dbbd61da2e8154d5fd3cd3bc87 -47761e4d40520d5187342bfade93b4bd123dcc5e45357a808bc388f670767fb1 -47854e524dc16617f18821822eae8a44d46e5b4954651e0c50c64eebff299e7f -478930547a0614909af93716f30cd06f69153e25814fcf1fd9d80c5022b9552e -479116d88429b1086e1f90ecb65f5c4ad36b04cccf710d9d925347a7894b24d0 -479c21b9690ccca3ee92939e8545fd621b6a71d61a142d56bbd2e2edf528f971 -47ac0df367efaa8384c3de2e2b113970625c0a0b1fc62659763212cf098f515f -47b2d227fc43f6ae1850d9ef820805ee41639a51fbdb7182861172fa0f2e2fe6 -47c80107ea650493edcd2abb889674cca0bb21913300210bf706e2cb7c1df3b6 -47c825734377a018eb1452f266be6cfe7932716de689fb36886c3002ece889f7 -47d5850743fdfe67b87cbd35b278436a01ff8e77ecaa6d342ce3ffc901018435 -47e21cfb5e5a7e372e51ef4a4b6b8b43c6ab4e6253c410a50d8184c0212de3c1 -47f4b03d2b40c0419ad097f1fc9f66b88ed37b5bc80c164481735fd45c37aad6 -47f883e8686b368ef68cda1751c6a6d9daf4032bdb9d731eb062c4b288400388 -47f919b6d5b6ca03f8d8388cb65957b30ed0afa5aad04cd397e56470524dee78 -47fcb002583daf73087828c656349779196f3672ed8eb35a8e16b9c683397944 -48162890a2d95b5e7073a0f906a46f401421b33fc2f79b1747e9a4d23ce30f2a -4816429a69131f859a294d5c6f1810974b886c34d3cdd0104c1230fb9ea010b6 -48197dc3af1b0790596350371c42c3d2eb466a2aa0600395de33cfbac05a17f8 -4828c719f371ef552f91dc2985f8662bc94c2041df3f7427cd750b7805440ffd -4830bcb78712d10c3203ade630d826eedd9e8023cc16a7eedeb8af5730df0b47 -4832885c197dc6d2b5cbb5bb3a925caa40aa7d86aed93e5a6c2c7ee8222b0c2d -484362b3fef040e3ddbced8d77879e2b54c49d18ee8fdb9f80f1055b987556df -4858c35808e7230b740c1c2dfefeb1e38df57847731ab73c55a361eacbe7a268 -485903cbd59d20b3bf7dd74725f2655c0461e3ab2366e17e8d9036f83519097b -485d68aa49d9361ce459d76ac1cd2f0858f3f1493a0cff60502f0585b39292e0 -486058c664931b99b3e1b19fdb1b2eacca57ea877ced386ee29a321f463a8490 -48640d959d4044249e416cd7d1a58754199c55a4226374a7c362bc8b7cf120f6 -4868988daccd6e118d9f5d60dd80bfc1cddbad18b25811be5e284fd47684a6ef -4875b0b8203324705fb54882736dc4cc7074ab82f1f15bb468ebea458b693722 -4877485eccf0dc1e532ca1d44a5196d75c5f6f4e7bc9fa236680510909388042 -4877c02b407a389eaaa7ce02536196a05844ab76bf04928f90e63cb4ac99fcd1 -4877ed2d9733dd52874c988084bcfa62314541e3a7b2af049e4f7eaf89277fe1 -4879928b665133f33b9e7a87d39a317ad478de973d26ead531778a0125817235 -487b2c23e80d53acc3513645ed2f8590359aa9d966c928668e6b462499dc9383 -4882a4c226cd0f7d5e61ed6e6e4f85d62f81a2914180f020185ebe7991f2d020 -4883140d3103f7b24c8444737cbcfa890d35adf574e3cc4973a9ce6294c3b6ee -48838bd275895d92610632b4b90d7fc363ab00c29848a2efad97568d48bae125 -488805c16bfa04668c527fc1d72e3564f3b4bababb3ac31ab40cd8b72e076aa4 -4889f66c093e4052edfa9126ed98cc360a0d0f0867af4e0c29fa0b5e7e5cc0da -4892cfe6e1b7243955a4ad1702f1402f2aed3a50f6644acbd8d59afc65f2e836 -48957f3a451b67df14bd3b3362f11f731d39d740d6ea4503b8095e0bf84885f6 -489b32b3c2efa0710ef000d7ccdcacd35e79e2fc9f4d3079f0b2a4998f160190 -48a1e8026eef94490db40d988d3684c32a979ef8503275d8350e167374de7422 -48a4ca62495bb29f9bf4edb8444eac6f9644ad8b1864a3672536144bb5ecfd1e -48b07f79c7f3f49435b7f527de1fb581b76079fbe26c6f313ac8b4db804faab4 -48b2b480853676bfbe68813fad333439466ffebb03b85ff47d9a3325f36dd7db -48b2ef9c2913f71cd1dadbae9f0212b039cb808f540eb2b40de4d505a2c32341 -48c5194f93244807df553b733a5162087e0bacf89509919ac746941cff75d1b4 -48d3648f72b946ebe7b856c6eb9449239a5445ecdf01951529e678450b755129 -48d55fd33b073c428df3cf5039b7b4318896522098a30ab3a47d9c4a9b0d5a12 -48d67257f8bc6c584b9aaefd1fe33f13fb4ec0c179a6f389e75ac45454db17e9 -48eaec9df8ef440953cc0e14c4214195157cf5f8677aa79c525a2b1e86e387f6 -48f2a692457a9bfb81906288c87004a4f86b1082086351dc725e9bbac6b50fe3 -48f35eb4050024c99ab49467dac5a8718561648a4faf11abcb080825a9e73e6e -48fce1c2c22b03eec33a1e1dd619dd9040c26bb44b5fca597a29f64ce812436c -48fcf2ad0e63824b4f2f79ae7d75b081fd6cea89eb4022dcb69c750e2ba9f436 -490136ae73aaaa45f42f174533433f441e755f2da59c85f3d9c3bc7439fd5f71 -4901804b7de96449affef5ddad4125fb726801e6557fed6a7264399420b48b53 -49029546820946f907aa9f6d5a49469869e0bff30ce33b3a2d42cd9bbeb34a94 -4909a160aded877b723cede972d447e0b4c1cbdd691e6e78671480f7831bcb4c -490a030c28c513bbd13773a3fd4f3b148ddcc0cf9aea92ce6d355214495ed7a9 -490dfce57625a8f81c071f16f0172cd4c6e548201621afc782db76e42e59ab49 -490fda7e7f5aa43bf61e74d3524cabc93cac6eecb302b445966d4ec43589d634 -491caf394c9dd1e6692f0f3edf19d06782644c17ce837013e2b1c6a73a0d7e1c -491cd7b59d8d34b696b82f5653eb23f88e520060f7fc4b36a9b7cad4a2422735 -49249e1b5a9dfaa8c4d78f55b53a1788f5c8677ec03c23496a2a85ef509e08b4 -4928ebb9cdc882a31c2104b1c6bb478e31bc0787ce86f68f069fb3ab223b271e -492d1a8083d6e962059dfd6602ef29df03419de0fa902ff40e017b5a4cc213dd -493335fbbcd4c0cdd6767e42117adbddc05599001b0d281aa38a259d33f18dc2 -4935ca7b53ab417e8a2974b2bce6c0e66a8ff64a3a2fc29b5797fe9ec2c513d7 -4938938c1074502a896d0f635742272e4107c16c3d786e25094edc863b85e386 -494532b829a0074084cc58ecb04ad5625fe26eabd5df8f74e844a28be791b44c -494fa687fba340fd4a9bc663c0bc913d2b4c385ce3b81c9d887038287568f00b -495465b0c1ddfc7f5cb301c5319c598032fde5862d8287577618e8423973e318 -4959e6b6ec0a5f37895cf673f1126d65f78e7f2661b1f19c5c25bd8703dac79a -495e4d70b38018ac8ffdda4cfe1308931f257308672c58c392d341467d8e187c -496a7b86cc3b90a15489544ab94ad8399a731eb95bc4ee1691a70fbc68e7f254 -4970606230b5993ee5d38b841ac5fb15c9b79f5ff032eb00e49530b6b6b65fb9 -4979dc890ac7d635d93a289fd9ec8c060e2d133cbd061f2fd65c61c596f61c65 -49845547924097c80040c95996d0c04a5a6c0f9625ce4c008fc29b3d416660aa -498a8fb68f2dad29ff589913a8e590629513c46ba356201b3d1acc38e5ed3e91 -499355ba1619bfd75ea2bffeb56a6870b87a8dee419cc85e65b6d016eac925b7 -4998e965f74f87c975bbe98a0e81921d75b2fd2186876fa6c2cb120e12cfc657 -499e358f4b4763d1e804f49d5068995fe643f5d72a330152d30a90fe732ab635 -49b448185228021eb025d2778c1b19131c8306f955afe8f53afb5f1575607806 -49b4ef15a0e5a4e92f6a88b9c98a90329339a8c32caefda5175ba8731e6649b8 -49bcc2b7f5dfac0e10e5266b9d28922f4860e6474dfe3ca0295ebc363b6ad8f5 -49c022f5f3250ced0f29b08306d84ff894edca8e6060a63d4e3205ffc4190bd2 -49c07ce75639eeb3ae43c16f75f143bc526bb4a4269a52aaff3a20af90426f02 -49c5265b56422a0861d5b441500dcc1f41bd50591d8a05ee3e36e5ecda7a11d5 -49d279281eced61c4cba30b5c29ab48a68d7ba188f535d320016617413cf5b23 -49d46a79767a822d35420c012aa5e8ef2b0c0e35c5e2fe5dbcc7de32a89fb59e -49d5924a76d13a0ec6de6771da2346ccfbc3386ac65445db4b334e9befa17489 -49e293313d7f9d70f02b9ce1d269b60596bdc9414280c5eafccf0fd15c7c3bad -49eb96eec947916fe81dc5581dbd4ef93aee7a5c2ddef2d6de5b07d4dcb159e0 -49fa2d2ab6d1f2b9b72fff6094b9a5557c2277b6e7a8d10a5a33f02179a0d9e5 -49fbc2d2cc21b4489e132a4db6c3b8f70f6ef82b38962a49d3872da54b5f9ed9 -4a00ce4c091e004706d4088132f51c31a3ad9399757b8a59483812ca08904f60 -4a014be87d1399877ada3f300bd4e4684da3d54971853d64a9fa841ec6ec5203 -4a01ec75fadae73489f217c3177e549e99ba9f604bbc0483ed4ca8efa97031f3 -4a04ad76479afa26833ddea6b0d08935ed54cd6163225102f4f5ce3a88a76515 -4a0f237be11fb725cfff174b2f74bb0a95ddc92f3f32fa63aa9e56e9912c99cc -4a15152946ef31502b0d4740666e3c22dcc367f11865f07146e570d04495827f -4a17bede49066dd1c0374d6563b4914979d44d9940200bb1c0fecfc71a2e2263 -4a236b979007306f8a38e1bccc0f229d0b5697f77f5315ae3de248cfaf1674c4 -4a278b295230a108d9e787dbda06bde09ef89efb80ec4bd9606e1c95af09f1cd -4a28a8bb02cc641bee4a41a2e6c4cf9dbd17825d7e9c31bc6bcb55c1c47af963 -4a2f015e780918462ad3adaaf5dab69c516a98ff3978964d6da8d9a606d5b47a -4a37991514985fbb2242bcabdd85d81976ab945e0982df706ee9a19ef4a4e47c -4a3cdb022efe52fe9900f2d34d96aef536cbd3d700c4b536be84cecbd45e961f -4a43e1fd889378494e89e36fe1aaed4aeb3c0ffc077198bccf6639ed0cb2f4ab -4a537cd636a5ac235390fa2c23ae1bfaa8913092f8fa76f9dd24364135ed945c -4a546bb8d4f06ce27a2d156e035b776a01149854023332c698dea4b09ddce582 -4a5a64499ef8c004a527b0557cc8457fe5a8d16317e3d735ab6a2998cb5009c5 -4a67d5ddd0d916e02f99e759a79fc527c293c8c98eb1d01a60ed4f193d869788 -4a6a26647101346bd4a67efd273653ca6639286ddcd942dff08a4b09c8a57e9a -4a6e4f943d577eef0dbfd351551a5993e7c3c42560054a18f06b065d8a72e152 -4a7597bf34e57c375097b086c40b59e8e615334b8255b882e157d53e8c4efd09 -4a7dbbca75689c33f5ce643b743a1eb9d4c7c6741978ae1167fdd75bc9139ca9 -4a8410ac0153f94af52e382808b79844bbf63f6d613e0ae7b2c12594b8307e8a -4a84f9f1fa7c6949fdd88375a2129c0811b4f1cdabbef178d352e37cb8c12ab5 -4a9ac4f99cac0767edc268285e371c53d4874aab2a99fac2cdb85f7161d92e1e -4aa73278027554e61798aefc361ce10116d8e8bab6fdb7e987424dfa04fa8280 -4aabfce0733b78672d1fef1252392a17e036abe946ff2238612e55142aee5e4d -4ab35ecccac97bd877eda1d33b31a818144e9cd95cfaac622fe4b4d0e734b190 -4ab8e77c810dff753ac04875d8d26837fd1335eaf756962b403629a0dcb3ba8f -4aba47478cd67b29430eb01162ab71054e24766855fbc85a1cccef878259dd88 -4ada01e9ea879f02ab54f3f0529066c8bf779d97bd5a3bd3c255b1ea297db152 -4adf11e3deec3c75a826962c7c2bf83c86b42f5a18bfaf574cb7b96ec35b3fba -4aee1253a238ba42632f2e4b5d86adf42c2a2395551aa45036b902a54de5aa66 -4aef8e3fb3cd9750628add6d4f693875075bfe3d5585470f521487c8a42f9686 -4af796bcdc2bee8f3f45d19cadf069f4874d10864788ffead2ed1c8ad6185596 -4afff8313270a4f9e0a7759582b78ae193484c735a5dec262e55a3e5b83578b8 -4b05c98eec1b6a64401318cb60b8e4cf4967beab61bcda503246d1a989e9aa36 -4b08dc3f2e83b462247ae5abc25161b03131df551376369692e01c4c7834aa9d -4b0b104bc3783b6bc064b7ad034468c531fcbf44a486435eb03cc2425f4f8eb4 -4b0cf312346abc0f97eb8e68765d413dafcf35b9788a7899c6d89fc742208ca5 -4b10cc85a42d1a5574cea4edba73c352bf0925f3ce6dcf98030819664a3ee597 -4b12269ca81c54e45ba172cde16a5f98300a2ec03136cdd9cc87a7b64ab3d403 -4b12506cf01380c9a988be4ce1fa10ace829ef988a11ad0aacffc517e31fe65d -4b12fa031366116610ccadda864eb4806cb369ceb99eb964e809ad08c9db657c -4b1b2e98294f6bed9c4607da58dcde4d1fb3716bc50e9ffa59e4f2cd57c1b51e -4b1fc4e1160ec91b6fb0879501018185a01426a2fc897729a54c6c79f812e115 -4b277c5d03126ef3542056edbeb9625c9b71abf70d51ff1417565b3361b2e2e6 -4b28bc7cede1fe5f2a82806a88846f3036f7c9202b11df5f17b44b312299169c -4b2ca9d579aa0f19b07d2af2f5ae4f11baaec168b145af21227834f473fc403b -4b409e6e875ad21c0cfe900dbb92657c716a78192155ff589c6ab995b80b5531 -4b46b7931ab289943933fedb78edccce947e6a315a47fb7222999a1c1a49809f -4b4ce64cbc7bbcfb4256806a11a35462622b2b78aebc88ba09d406f214514aa5 -4b53ec1fb0fdfd7112a68e1f8c2e335f483fd97c432c61045977dfbfc94d6e68 -4b67b476b8f277c9a4f5f42a329463d8a089bc72b30a767944f6782517e73874 -4b791ca53c2a2beed2c2593fd32169151854f3619b70f57c9d3e1a9bded9223a -4b87ee9a2d42d6ed5e033f14031622010d8f090a82a47f9b42ca9a579fdb132f -4b8bad16c23ee95008fe66c3277779ac023bf174a90b048430f8bf0239aa10dd -4b8d38d95db7fe0659db8c5e9b8a304af0c59510b08c004b9058ad31c891142c -4b9afbd696c76fc0ca79ac7c91f202428a63373ab698ba8f3dbac606c1aa15f4 -4b9cba0854124098292cdc89fcbb235449502a3bbcacd281a3f6775735a84db1 -4ba66992d6cd030c9592a006b288a0f47f295d10dd7cd4f0d4c1851eef3b2d64 -4bbb5262acdb19ac8ffec198365dd52e5ff91374f4da14aeb663163da03fb6ea -4bc3bc23768d36825046218e3ce4638f2410e6f4a850e838aece84deb385c7b4 -4bc5baf7bb609869713aa38ac204128482fa3b9549db10129c80aafa85ea431b -4bca62cb5e5d91ff86c7ac7336b879b6eca31ae25daec951fcfbd55b2dd22693 -4bcefd48bae6b64c36e6c4970ff914215bed86fb4593fb767509f6980aecc10c -4bd7ae2a1f91fbaad93177617376e03c6e73ac0feed0b9312001ec565c336753 -4bdac644e5ffb0a3bd7e9874d406ee60717a9b7e6d799fd14f7fd1dd5ff5250f -4be66b1fa0d2f2eb3ace6a5842d11169c72ad1dc3704ca13033adb8f76fd28ee -4bf4157732a7848bfd7a646dc658fe3d86169d3a64052bb554c4e9f4b0f632c0 -4bf81a039d17bb192b0ee9ca2ebf2ecbcdce3bcbe4d0d34e4ed7959fce87d3a1 -4bfceb22ef5f40744316327e8cce314c76a1a0a49701625a5ad2f4e279a1349f -4bfd4e7884474bf6b775d42de6d78ab9a21ef5b09ccdd6485c44573c9fed5a8a -4c0147a24d040ce905f73bf3c198e4fdecd4eaba062e4b7bbf2ef1686bb90ad1 -4c046ad72de1cb398220f2352afdf8f3343b48c22762c711d509f13a9eb65f03 -4c17636072cb8a849cde8907af254894dab88cf49a1429911c8aec420c22ecb1 -4c200190145976363b19d82a4bbfbebe6a3928d469065c5e1502787409c23604 -4c213180d2848413f5a57ceb8339d5703a888d372ff489e004b1d62689900a6a -4c2bac35fb3a8aea681a879af141e44d3cfc22254ffe62d5ede0862ae327efa4 -4c2d0e1fd940f27e7e26eb027fe1f5a162dcfe5bb423b04251482f91ed2b20d9 -4c39d4407e3ead526fac40ea8eaf2c1881b40f44be2cf21b365057d7023c635a -4c3cea49bedc10d49952fed8e00f1fc0933515bf3ce841bae6703be1a14e03d5 -4c3eb182606e6460542c8ab6d34557e967aca67294283eaa6e0a0fefd1356927 -4c41289141bdfa1c38fa93e16c801b542192613e61a28a96059734118e215a0b -4c46cc36811a8bcaa3627dca544f9f19a390d4eb0227a7812a07957f4f52e02c -4c4be838dc60a8a97a753d9e9160554ff5550f6ab8d7759537dfb69c0c391779 -4c52ec0ae805a749b3615b1aa9b054fcfc47830a027cde104ea76df35528c612 -4c60b6f973805dbd5a66e8d78593c6751580b88f426f75815eea4ea83b5deaff -4c620a880fff5d6a223b0209a43164990b944bf2d09a865153390bce318baad1 -4c6d6c9c8e83d29816df4d50aa97dfe8224cd39095850591d20c84e879253b81 -4c709e19469cd5367755ba4dd5f074899c8d7bd9743355ee7c839dffe399e2dd -4c71b1a8f3d3ca6f4afefa729c4d862a456438e3c0ebaf94186284c56a718ebc -4c79a3adad16320bca755ba0da85df70c422dc2a13b11e2e2c3c54582a9cb924 -4c82ca9ba44a391f7ab5d8f689541357ec07d8acc7367843118a648f1cf9c9e5 -4c88a46939708f6b53cbc2ad07e98a3db2861ae04de9a984bf915174ebd02e42 -4c88acd0dc0d49a7323732b88906045812948e3691e42adb628ed37b76811879 -4c91a38fc60ad5ea32eae1ec9139eb05c8f8109c0bd1beb5b9dbd24e55be7fb2 -4c93fae636d0f13c612aa668db79c747ce36f020cfd5fea8b7abcb50ad767385 -4c9561bcd3884b3cdb52cf70e4d907df4399ef69db0631eed6c1b0e69ef75d08 -4c9ec5abffc458f60d697cf48faa440f4153acf29e679feae957665264091d29 -4ca4c0dbaa2f7832de12d42ca358e332904142c4e725f4962e3732432c7b2276 -4cac2ed879eea9896d4b3fbd9234186d5e15ba54aa064e80b138153f28d107e9 -4cadcb9d4638e6aae3e5f59bb4bfaef1e433a2f56edbf90dce1a05bb2575c317 -4cb25053f1e079a406aee1fa0e5fa41a037be305e529fc7c10aa76d35249c44c -4cb59bb67251ca88cb6aa554fc88748ece22ad200b7cd57b437d340e034e5811 -4cb66fe2d205a727b13da445a17e9134c35e77f3a6776b719e9f1d89f318e142 -4cbaae3e203c0fb0d820bd36a8a92f2c2becfa49909affebe32b04a07a755598 -4cbc05d39236fe07c9ef3cab562361d07b9f22177257fd242f544b386c860c0d -4cbce6c87c151b0cc4c1a4a04689e208260cf5e031e118ef74b2194529df0b76 -4ccc2cd2dc4c27aeb6b3d2a870180796ec85dbf14ddca949be09de62b7396d0e -4cd5ee40efa6bbaf7a70bcb7f897ae8887b791e171ab8712c74c1cbbf46eafd7 -4cd9341541367b042728b0b31c1027042f9d9280c349c65e672e8a7751b68cd7 -4cda1e13048c5b252223cb53ec05032fee89cbad59fda0529c89b98168650bdd -4cedff17fa5c0c3dfacc5946f841774ecba01420bf233a19d6bb8add4bdd7402 -4d10a4bef54175a13eaa2cb9c22d4a895417a5fc040ae0a52dc5783307bfc092 -4d1a1a326349070b9cebcf4be13a609aa94cbc69c403200ffe1e473005bb432e -4d1b853684678e963475f7f7cf04e36e9e201bb1514a6960a837b3d9a933f046 -4d1d7162e1daab06b519d6afd55908dc2bb5c0ff6cb4e02fac3ed33064cf219d -4d1fa9ecd2b2b171fa25f0af68f1703bd6f8307e0e9fcac8935f8076485df2a9 -4d21d0048c3cdfd2ae05bf3ee22c6b64d6aef3fdc046d14a9d99b20809387a76 -4d24490aae47efbd713c1df36cfb5ec2ea566be6ad164e77952bd4ba71a21f07 -4d259bde235f58609548fd985e50402f61dce535f371a44bdcb9f7a39189c45b -4d259d4db27211cd0ef730fdaaae39b50734b38702290af2e42bd38185ee2cd1 -4d2613e7bf593827281aa684f6ce4f7533487db17d5e31f045a10611b372940c -4d27f4a59049138dcb4f1ade0fa89887679f5ae8c3c295e492694e5079508e63 -4d2ab3944c02654cb796e2e7ccfbaf7b76cb5869306d493a81c8a6abd9b42446 -4d2ce683aa18410c56cb5dc052a8613eb7dbefa3e6fcfb6f32d72453d16064e4 -4d3ef3a9e8928b9b150897b58ee5ba7255738e9543ce8f2ae9056985b870b309 -4d4237e293d7e383df7418a7137391c8315f66ded6f83954f1e273cef7dfe6b6 -4d44d8af36ff213bfaacac5f0f9a33c7b4c027455f9ef503c473ab83acb38b2d -4d505d8169b3bd98dd99bd18a1375b91826561442c6c95f9a0d561d7b03c628a -4d52c8b7a92197ae08c02db5192a8a3afb6e9829a799dc4b9e607db0eabdb4d1 -4d54f8b8e6cbcd0346b5b5cfe02dd73cf4055764b40a49661e75a109737fc5ae -4d598c440d0e03508e6a3c97b33f36ae93dff9a1093e6c04daefefc49d07d80e -4d6653af781e057e2ff67d31637f9ae7cce18d0e42282e24bdfcdeb93c667226 -4d66807fc619a9191bf73339614636f3163915e2fc9bc5dbc65a52c4a74c0927 -4d670fae86e2f19c8ca79ca8dcd86f3511cc57521a442c1dd87f6d7522eb26c5 -4d71c2487b3e4f78d3c4b6cc22bbb84be2665aacd1d8190e861f265e10b02638 -4d7873cfb97c1f4042ad3f3ca87aecbe64d5426fae692c78d183cb2161ecb7d7 -4d84b278efabbfb20b17db491d80473560200bde3e6b546d87e3f6684b4c5aa0 -4d85ac1061d6552862e9740cee072368d1134ad43f8c1f4ee068ba569ece5387 -4d87b79b0d31035eed8cf4e95fd1b6625471de3a23b1b39d8b6b3b8f02cfb9bf -4d924b7b96ef28243d0dbe3f486375898016c9dea078af18e833927a7eaa00d3 -4d927227a2ecfa1c845cd25a4c13632d9d4e40f46a8958aa1ba480a239681d02 -4dae8f358dae36bb6614e20fd13558502b22be9fd56e530058792238bdf8c59c -4daece7352560bfc27ceeaea7d08b0183e9c7caf2f63afe41db530cf408e73e9 -4daf4e14553b627121c578ca5015e79b00f8796d158c55f1ce1e0ab5bfb3f6b6 -4dba046a6eeaaf3763d0a3f9d05683b3477cf03ecbdef0a1c4c0a66cbaf54935 -4dbca8c64061cc9bcdae9b6d91272bdfc25f61d206566b4969330a3afa6e38c7 -4dbec363ecaaff9d3683695e889f324233c45bd044a3469943dcd0a1c3cd46d0 -4dc46ca9eee893128212568d4349dfe7d17ec7e4b17ccd6dc4758956d326757d -4dc4bc6deb064afab0c9ae06890456b0d664e973e8ce4546acd3e32b12a0cfeb -4dcae2d692cbd30ba38df0a8057015765255c27b012309c6856a473c7d640387 -4dd30c16bd3203bc53945b9153d0259174a63ea2b740ff045a2db387e9c9496f -4dda357f120fbc9dadd85468c0ee122b24086616f3b81d945d0c0834bb3dad9a -4dea2e21c59409f8d500a295f4d57dfbbe222364f036533a312ef4b266a99325 -4df3257239a067b67d8fa3ef05ec60df757544dd457e4e88b3932c268e759f4b -4df7b592f81368b2427d0842221f18ec4e76d6557872f229ceb8d398ba6d4774 -4df8d5d16503ed1fae58c3e0ff46c85523a08184e93b191317ee66d62b43d688 -4e10a31c6790a254ba44627728af778053984bf40b340de64238d3f45163a662 -4e1957a1b830940900439586c14437ee5b56df068392f6a60ac4b59cfc38dffd -4e25573d06c8dcc615977699159dd2109bb80b87ff56a9852b5963eb56cc17bf -4e28f0b1b31daa00166deb7c7ef33f32d5dae1bc08cc2115d7bdcac9d6faf4de -4e346ffc03ce3e47109409d5c61ee6fbf98e560fe5753186beacf6d4d91fc350 -4e37e043ed0c15fe88c1285fd4404c71e32c198dfeffbc914806087566bdbda4 -4e3c7cc9a9333749dfd02a312915955b71d767da98592676de6bef907e79b766 -4e5a5a99b58cde7bd651b983f3357f107ccb5334e7ec0ebf67250c80a0878c5a -4e5cec1fea6ae2253feaa28532250ab47459dded6d710eb2deb43246bbd710c9 -4e60f8e6111c480f9a13ea8f7bee0a59de7539cffaeaae40e4614d5031f8de11 -4e6419939c76ecd6248704662929510429b011293ecd9cec6b2dd083e02773ad -4e6cdbf0b9798c05b9bfe09e343e5c67124c5f3f6fc3f043b89ef5dc51cd8749 -4e70bbb598d63848f189df54dc21d61e1960e5be1400dd441ec56bbcfd39d0d9 -4e79b10956a7e28a18e6592072ef1ed73934bacaf62616d24de06691c21212e6 -4e8368eabc578d2ad150f46c19c89b5866c31809a02511281a11f554bafc4344 -4e8886bcbb25b7687243ce07cf7e5c856c0c7335127b2db646ac409b7c40b843 -4e8c9a0bfdccc347da87318a6a91d05c6b16e45eb66754e71cf849d8a316e842 -4e9b057adb8a8b5344ecf26daa5724fc9d2cabdd08288aaa433fb8ea3804daf1 -4ea2fff28d8d132eb037291677ffed652eb729e197a9147b61e578031d4909de -4ea5a3b180343259f9279cc81dd371e65c734d0d4b8b98ec8c352e39c5b7fa40 -4ea8280e461d032f7b726dda8f3b7896702fb35f71dfb5011c5f2c99c8164a25 -4eae6412c7b3196940b6f9333572b10b567be1604cbe6f474f15a485dd548387 -4eb546424d04712cc72da26628b9bb6bc230518815bdf75424f41133e1da8aa5 -4eb7e3c802a2fccc8683b4f25d81fe18db04ca7ca964b64d05f1fa8962adaeea -4eb8b2cbe89f46e9f828603e59d18e7a17a355ebae2a4edb0cef6cd5f7a05afb -4eb948fddfbc38e87168caa168aad3274e3cf91aaea5b4ae708ffa1716058721 -4ecf8f6ccb7d8f8d45f795494cbce8d70ad54104a47a6a2aad7322c7bf021268 -4ed2ddf4c5b00013da07c8c757055a179b709bd2e841e3235fcc7fa4f3997763 -4ee407186e96c482ff2154cf4a91cff1dbdae741ba17b6de19def8cc089f3b93 -4ee894264e9e093b3cc22bad958e8c92f0df1c5378b469d23d042112247aad7d -4f0385dbd35f49e5862c02727591ddd179d0f706759ee8f0da20c6fab85d0ab1 -4f09be0e430fe5f4ca99d9dab6cd4d59aa0181b6894800dd1d417778b6995ab5 -4f0d1d878c5cd060db1dbfe72eef406ee41212339e1b0516a4792460f64a7c3d -4f360da5522f8a20f6baa955fd8a867f204bfa40844fedf301776db30935a092 -4f37e1f3891b5687a44956591c74c24357e51d0d5dda9aa2c496f43f825f18f3 -4f38814011f58be79f318e67149b5e878a6c1f48080a164503d3c8d5263e9f69 -4f39d34836f6cf682db9de1d2faacd47a5a8d707098077cc18b88abdd710ef92 -4f439845cb0bb5ac333f4d36deb516cc149727bf2e737af077d0c1df0aa1712b -4f4bd9e1560dc047eb6bda318008a94422b1f39656b1eeafee56742a0d8ca18b -4f5355c6237f14aa4912f083ed99d4b2d3019f1241f94d23bcdd4fe01479945b -4f5d2b9ed6f7c18869299d12bf8e4c9a084fc9630fadee44d2d95caa688bf47a -4f62c6e7b485606aa7aebae11e4d5ebb3120a7713418a6544cc76e80ba112fb0 -4f68292c9f904f674595f835f58e15feabe19f241942a3d7a4076c5e21e19f96 -4f6a809428033ad3570b679b41e93d3ca25238b47c663d1761ad6003a51c3a0e -4f7a6ed551a0884af3a262eb21179a096f2051710dda4165da4d85b16edf6dda -4f7e8563e8314ba6bcf365ad4eea34ee48d036fb7530a33ba36430ce2de871c8 -4f8cd89716487a37d78c8203714b2b3e3519f3e75c9c6fb84b066300835aaeff -4f8e1b483857d6121981775c72c55a1f9f34ba7408dd1c57603f879f0d891a75 -4fa4a689a05e80baa54184d807d65c77f6a4481e71604c9ef6b2629ffe5a8648 -4fa5def82fc5f39de878e0150452c37727bb0629bd1741f804d5471f5d6558fd -4fa67b7d0b3c5160e6ad61e2a4fedaff349dfe65288a83071636e2d35790f454 -4fa9f1e697a13b19d2c3f373fe22336c5a504fa16d84a8dc26e52dd8ff646fdf -4fbf9bb841755bec933a3ac1a93af5da31f38c76b7d372dc139f5345fd8ffc5f -4fc4ea85a29c715d2d580a1e791f375b144893435d2025415f6ab95bc8afb8b1 -4fd258eb072c51da78a80fb5c41b3d25ce5c35e25a32ad64a8503688bb12397a -4fd89794b17ff32250b1917dd9a48cf3767dce250fddc0d1f7e166de4197a77d -4fda3ce292f4edcc8ec3198f839c28ca024ed5d05e14113091caa1eaefdeab8f -4fdfb562ad816975c0f9b7c2da4b6fa5223281b0f9c71ef164221f83a4b01fcb -4ff8394cbfefb8522496e1ef0b5e4dc3bdae5e4694a16fe52eb8f85f173919e0 -4ff91216466616754d19b69a049563b3cebd747bc6b6d9ad0a1bc8df8227d5fe -4ffb894a1e9ea1b63ee51695474520bccd2ebfbb957e404f26283e4da29d846a -4ffc9c29cc26a3cd3e9cc8050a0242165468acd9d91fd3165d3132a9a979f59f -4ffd479f629ac648818411ef62a0f09c7954c644bf7ebcbcf817a6bd00112153 -4ffd73c264706f17f6d37e9a5c6910c24a90c75a783044e6a9ce19597f7999e8 -50108df3ed32b14166f6d48e1ea8a7780f372c33a13ca609a8187eb01543df6c -501a49e2ca50412bc1950e40387c5d3204cc635505aa3a235df40ba623e911a8 -5028b3ebd25da6897fddc48039c035131ca43aad9dee1221c4159506d7a37a96 -50339a1027fc5a2ed68fcb75b99aefb374293b7a872ea2847034a5851691a8f2 -503600e8f553e5c863154c1b7f76e65a2482c3f7deddec4edd9b4a4111e53937 -50362d3ac50954a051a00d628440f8d5f7f2c5acf4c9bbcd5264d6324afc8442 -503b61708bf22ae8504f0a9defb4b5a47c4961097a385c1876a7ef5187204be8 -503f61537e7fc2cc3079e0ec8b79340b1579786d226e7c5a9c8023d82f2fb9e4 -504444ee59b42f6ca5cdd3a3077c2ae48e4ecfccc4240044ddd8aae8fb81433d -5047c628be3cdcb2681fb18c56a781156a3271bf22c86fc9063d6ac7fd665cb5 -5055a3b77f5c21de450523bf745c7e812a817b52fc747cbca29fbc1f1c218d41 -505ab3e98a0c37c33a1cb65249276e68daa0066a823d3fdfa03355a0517fbf8e -505ddbaad42a77f5444ab0fd67813e5dbfa8f2e8c97c57db3268892705641523 -5068b3ddbe1900f62ba172b162de0b8e062ef030bab762bf36bfcb720c07dfb2 -506c5e37c81d21904b72528f810085e3f07e41692183abcd80ba064139feb225 -506f7cc54b0050b75e85945fd0b1a9e10937a5245a0c90e80f7f5f761ca0dc18 -507a4fbe9595b4dc235cee4defaca7d4f9a8da72a6b5ddca3e50e7254ad1208c -508c2596e6dffd9d5c364f7cfcae01a277620f0a3ff219a7e049bc8d36d01f41 -50ad8f622d73f13cc175811d6555f3d8d464bf0f64bb7796b20b695d3a01c0f0 -50b5e8d3c3e7e7d1da1b3aefc1d38567cb79b2c16440e80a5b95b35178a7b9ef -50c2ce22d16c9593f2d2981c20d82d46610d11c9a37dcd73cef65b3a5ab056a9 -50cae0b5b828aca9a3aa2f4bcff3cd2fc17fb09a24a4087ccd00d2433ce9a8ed -50cd0cbbccf09935a22232d0f145f689fc08faaf81d73c4daffe122aaa2609fb -50d002b05287dd63a0dc1e40d8b07dc51c6aeacf4d9088df23340863b0edc255 -50da1bc9a57218a664594c395cf7d75b7307f3e218bececb92320c2a8f32fbad -50e2bbce7c47534f28c0bd5bbad371b404518084f872af61de2019d6eedccfa0 -50e64d6bccbdbe6c732ad35ce6bfddce5f68d763d96361a97e91f3ab96f1919f -50eed73f3d17f6a55ff70758d7d06b981d988f97dc6ba539bd8fc35fb67a48f0 -50f119b45e2d3ef82a055e0695952a16dc63e56d0c11ce1b024c2d12d4cade1d -50f22bb4c9be992bf8855f5a8ec38b2c2b1caa6d15f22981bfe41bcc2ca364fb -50fc9ca3470b56f0250e5292d23570da178bee49e91c75ac2398d6cbc23e1a23 -50fdbd644eb241f29ec23f8596ac6e498939767ef4396441f9632b1e56397364 -510aaff733845d09d8e906ebcce3bebe416c0dc4e05ab59a1f5fffe5dfd93d37 -510e10f595bc131deb52b05ffa87350bafc8a87e25c80dfd60fcfb44830403c8 -511a45a865cbd4ab674f1ed46aba2be85177ae9a88ca9418466c14081a5b2ce9 -51375ebd641ed0390652772e436be7d0cd2ebe2d7b9933248685a4fb7485ee35 -51401010b73e63590bb2d683dd2d010569a4b884f532f6e0ad8afda4d069f11c -51427d69ebc4a0973d31546ec84518f0dd1109ab689869b9abf7a137ffd26879 -51465d8c9310069efb36ee596a97f05dc86a272e7fb97636340fb99ea0f65e30 -514baa6c0f522de2998c2f0ccef4da2293a76a5193d73c5c84cb89b488f8a347 -5150f67683f99b020732eb983b79a6b2e91cc5c98b04d64a66b22272d4ef7331 -5151e63adf2e08bd6903f9a6e67fd4297f64f687efb9c98ca467b11d9221ff80 -5152e716b48511d23e1ff7bdd054f0cfea34aff49d90be175c1a42a1e844114e -515a839423dbbed01b79889d904849182aa7103e7155d33a0916237d55cb7a38 -515b27c3e2fd6f19f807fa68f4b818b89a3a27a3ab439e100d2e68a0ef9a1ccd -515ce320ba99f1b868f3a0ff5279169c5bef7ce7ca11313d081a2d7ff43aec2d -5163e242cc5eaf06e79eb3d607d17d3f514a6d5f9a25ecaf547f1d24826f957c -5168df1442c1f6441e0c0010df6449c051124d62a8119cd4511e7f1b7985894b -5171c0438c87a9f944207005055b078e6d3783961545f28bbd30499cf4e9f7ae -518a7877b4c1f772c968b9279aaacebd4985a46da80699465bbb06f738713e90 -518c83ec88293011548f25aa47b30f8fb665476733eae9740fe17a8049473b70 -51950f518aa40143660d2870df1e40f12719789f92621fbf1fe4276ca82f3dd6 -51a0b8830472a47b67d9331af7e2140b637dc9422aa5d2eb77040c032ae3859a -51a8f5fb9c21cccef48fad422d3ac84fbf9855acbbc23eb6690857f16d85c34d -51ae6c24dd81b5afec8f1583ce5bfaf17456762c1ab1bae4871f6af0e20b3d9a -51af2ce31e1b5df7265b9601b76be28b1e647088bba686782ac58c0a4376a9f0 -51b5d2db1006349cef18e7f9c9b67ada5c7b9d95e13622b708e2a90b8c503db2 -51b6ab22264b5598edf8178db30715902b8f7f73fa3075485d78d8b11822dd42 -51b86e04be91a1334f2a0a94775b30e379dc1393eb4a1e96b5b5ac8c29566675 -51c0a302bacc869068fbf69e594e15247cf28f09abfc27a89997678b2d348294 -51cb19f6534304e8c0ac82c600b95954146bd40067b96d66067c07a07dc54a45 -51d783a301233cf0fc1bdcf74b276b9f7ca5fa64339af96db555c16a66c9ae9e -51db822476c333f6575d765a34dd0d6aebab1127f38b2842d5e2b17166537cb0 -51dd171bbd38f03261e669db6cf6c54b2c9fb5879ab1ec8bac345ca8460218dc -51ebbd53c2422db150104e1a2cfb1433e4105f8da9ca09084725f34da145b84b -51f646081861cebd1ee6c86ccf106d4c980ba0123f70795ab94ecc58327c99c8 -51fbd570e6ded203609d39c406419316f866e28662e0deb94f278ad89ef0d35e -52049553ec3a027726a83f650c50f904dc60e3f1d13e03d1627010c9e13ca51b -52050036f1748d7861b29a45e5bf06c227ddc55c03d3789c256ef9b491b4aa00 -521291ef1c11e2fd5bc9ae5a3cf90b122aa4015c19bbe1757201e682a3bc4384 -5214b68e8916c594af3be673084c4d656f8d45a73d7e598b9f7db55746c200bb -5217bd83dcaad57e16598f7467413b872db10b9c022db318fb18a20ebb093867 -521eefc5d4422e973ddcc870b72721b687f4f8786e9c1a6673b4ae5fb4df1794 -5221fb37603a47febc416976dd7efe88f247c4c87584dbf0cc5ab28ea0a5666f -522286323953b0ef12ce853d37b3bead114d61246e85b09e3e57f616cff8b502 -5225e1dedbd7d54bacfcf7959ba5ba09755e5774946eb4ad202af53aea2e18f3 -5225fabc3a6487ab3a1d49a768b59b041e4bf957581c13f2beaf52d30bae988e -5227f9da500290518b5e17fef8873447769c33ab111a581702ba5bfbd890084c -522a652e1e16b933c74395ff282166c703e7b061acdb6e3584631b3bb696ecd7 -522d2816b53fcb2c1594da70f9d053e4d330765097d5aa780443c57dfa4a7e9a -5242fac048f195052bbe63ea837ff0b5d524958190c7bf0801670861e628a549 -52435f96284473c5de798a0267498f5d1a995a51759bc967a3f8e1bb3fde6ae6 -5246738da1bc23e7dfda91539321cee8050c647165ab01a9277d2c5904abde41 -5248e74d0f9326449019c9d66e7f331558a996a564462d58339052f1902a5dbd -524b45bee2a8bcbfca2e5f94061fca9b305be5114ac98b6bf1a15f756545b315 -524d8a37e08f6e67881445f37ca404ef73f28644307518559c550b9680b1e670 -5260dd34ec28b5b50ca0f3399028e358acee385abaf63b4d275df102c9ca4ee6 -5262328ebffb0e50af52a2b7d2e1dcaf6d7417d8c8da31e82a0ec8076dd06b0c -5265785e5e7a573c7f5c2705cab49ca94feff144d50519224e4f9812f6cd44f9 -5279b28d2e6584d446837992f027b5653574f5617aae16d7e503b312eb3ca544 -527b65ff207fc182b03103e6c8365192aa3d3723b217e42cb5aded9a931f6ecf -528104d687f45d33a6e06fbeac4bb89ea4e9f5d73088bf6d4f5c1e17e60f54c1 -52968819482f60d3ecf74a2fc002ccf4cc898bfd91e5a3275c3155a0fe0f98f6 -529b54f41a65c2ad4660b39576b8d282ce08897d271f06a67fc85596584aa354 -52afc9cce9d3e99828f49053a8b2831e518f6f83aa162d881a8f31a9d714bff2 -52b2bf9da1951e085f945fe5858e4d8f612919fd3bec6560c3553b4ee18a6943 -52ba24bcb35fddaa18766ff046ee950dc364d26998f4042b31910fe58b100bd5 -52bbac7f86a748a9b4efa891a494d9d0b970f97e060056f252695293d353909f -52d42e569087d77559a855f1460c01d62b3e22a523a76fa233f05c8963c72f16 -52d8a4b2be67478f63370680bd828e49c675152cf70fa89d053084585d88c0be -52f463a3ad4dc9478e7298ad19130398f01cfa109dc4b11eb6243a5ddfe3ee35 -52fbef73b4f3c9abc0052d47f28a6c634461ef184e7ca6548ce319fa7dacfccf -52fc2be2d9affd3d245d47f64cea3034fe7a54306c092f4db6b3aecfa2ec1c12 -5300747b3147f90a60cc4d7cff7f01311de737fbd0f8c722b8957c0b120d0a32 -530d4183dc0e43bb102c467abaca564883f1de0b546d11ccc9bf955d1c7cc0d9 -530e15fa1718530b00bb17a63eb0fc3dd82d491bfcb90e952ed8767b5f69ba32 -531a8e890933de8a4091672a09e1d8877fbc0cc76b5d0e4737e2ca3a2bc1de7a -531e78796d4a6095e08d1acaa7d543f8f743364d7a688da18a632b17f6c0d8a3 -5321265dd2537208bb8747cef88326bfb7fc3ece0677b262d05a34fea9ec53fb -532c3f18f72a059dc45684520ac6defbc04aab26f48fe9d956d7b2dcdedb8115 -53377577c4dc081f5a9fd1ca1393042ae66639eeeda25bc880d49ff242b0493c -533b9a0523423326cb537b225964c07f31a30a6fc845eff54d4f17b63f33f19f -533d1ed5a2060501f3787c52c0be54b6e47d7e7b7753aabacc0e713443e30f8a -535a0e8c563cd2d65cfb63b7c372265a96326548c68f3bb5991dc28b63bc2b85 -537291bd94c04e9a0b31b086f7bcdfe81d1f15d88fc53e1354444954cbe6aa92 -5383d11e3615a211aee0aeef3b0555a2f7587c74395b6e496536236b63dd31dd -538d0d050a1230db6707e70a54b3f0a7f26d937779c8660580510f8ccf7f3ad5 -5394b5ee0c8b2fc1689da58a61db1d246de46ffa9ec642749091248c2acacec0 -53a4d5058fccaae514275845bb108569efe0cf0c52f7d3bdc10e6af6eb67d852 -53aa1029e7f41a66edf7328bc7f8a3a98c167a616bbfab71dd8fc291908908cb -53b4c1536c14a2807f1abb809d836ead60fd629aa0f3f3028e5e9b2d9628b6b5 -53ba97b06b29e5af8dbf01779ab20df0ce3c7d7f9defc9d1469d0244f7b86559 -53c363a2a4638a86ae3abf5938acf5bdea60e345568cfe88bb75f5c0a87e58ac -53cba20466e9e3d719e3abab13fd28d0a1fd1b6d91b1c14a9aebd7abf1eafbe3 -53cd227a92b1e0fefa2e5eeac548fc58c80c34e844b83f60531dc356c7984747 -53d12277d477b69ff1724da158c77e6e3658677904eca2e7b89928bbf30c11a8 -53fc00d14e8c3a0fa45d1b137ad16aeafc6d045ca4f4679be4adb28f4bec0584 -54017d99fdb8e1041e35bc71070501a7173f930bccba4f545994b3906ba59c70 -540b162184b75f720db02ae3eedd4736dbfa1bb6ec7db4a8555e2b7b772f69b5 -540ea0a3c72308995cc79031998e680887abae08ae823d6c687d58be2da7f8a8 -540f101ac5bd9fa93e3c897c5f3ed627de018e43e4145443a830e2e653a643fd -54144604de5814635b8c39902fd24bfc6a03d0373c7bcdcc6fa5a5bca0e51a6d -5416f82686d3036b113d759397731436ee8e2140bee26b6ab725ce8228cbcc7d -541793648e798f1b4d9f43734c5d090ead6447006f868173bee7eee34207aede -542093044e2e8ed2255b1aaa5b3fc9e8489113673117299604ea273d00455f7f -54242edb7c036449938d43629947b4a752e565318bccebf0f8ec4ee790bc389e -543667adc4b76135aa67b6fcb48ece0f34671ae6ce185f91109cb5916541be96 -543d5dd8e65d998166ae357377fdc3f8a7b0922a09b7d3d34b67147965494180 -54429006a8aa91ab4ae12a49298f629366c557798a4a9743540fc451718cb6be -545020c7ccbf1a3438c874a705a1e86374779f845c2ed640726f1e5985c83801 -54505bca67a3b34d279df560f2a15e840fbdb9239cb161952fb7e2e2d2c8ab1f -54507f0b29f0d616772eda42b05b0e2783f83828a84eef14f6c1e0c2f2c2a8e6 -54543c9c2afbba0c3ad4b30f78b365abbe5a641b79f840e0d010540d798aabca -545641231136def75e79e9a7688faaed31c2f01d7adb81a162c71d079560d830 -545729b88c1e19c88caf482b92024385e3877ef7aaae622869687122d6db6b21 -546e7f434e7967ec14c5635c7b5606b5da3bdf4c4976b98f4851006844cf895f -546ff64463585d171bf9e2dbda6121193c3f79ae9d9fba1dfcc43cc829ae7afa -54728a8b4ca8981cc9f36d854b32d6110f4efc209f3401631801e97e29a8e8c1 -5475ca79f8a06f0ac74654d2adee0943ca58e792c122e8f1d72016a6e2d6ff6a -548457c5f7720a0195496039eea67cc85bb250e31d9e131e1b3625726e9fb666 -548dd53fded681422f5bde9b6e126fea72ed47790ee962034287cbf2c05775af -548dfd48247eadccc6cb473f84c7d03ce700f08478c30504a083c96184682b4c -54a3b0db9cc64470b9a8eb30a32b6c8c96c86669de67ca3bc00bacc87b348f55 -54aae8032ca447ea81ed961764c67c63312bd00da834c40af39eaa8b0046b38b -54ae0f3694f9ee7a19f2ffce0c281d8b4ec0a189b71c4b2f08956bb74866f079 -54afdb77f9524b2b459ee2e7665cf85cbe218fc44c6af805222c19905ac53373 -54b74aab26ca14f89cfc96518a240f7be93a238858f710068f6c4b613bd4a0a2 -54bc65cf8ca59eca1f998fc0b0dba8a8c6fdecc510f185c5152bd0a3a602dafc -54beb5b3a0163f68dd0e5c735f008ff43c7c8c3ff44fa6a4d190c9a0aba77dcb -54c0e6fd9ae37974acc13af0f8933590383af87aadf9bf97279eed7795f49558 -54c14aa3c50b3f8870753e937b4ffc00d2baacf0a964be089383f2b64eaa4d98 -54c441e37dba43a7bc8c051c3128fd82310539cefd5f45d19abd9db2d3091e6d -54c7c3eb632cff16851b6ede0173bf4d006bb4d637ec732b25250a4d80a7c589 -54d2a980f54c77407fc220b3ae421eaaf2fc8e55a7407b15916529c8ed0d55e7 -54da3f0b844b0ce69c9f4f804b475ce61b862dc76cf257db413f5f2006178c2b -54de965ec9877f09717619cff6e887f85a76cc648a50e7adf364643e8742bb39 -54e33d30f759fd6518a0fd42af9cdf8961e2a0609920089fb2af8a02ee8c690f -54ebee04a913e7d9d1e731a3dad4903517bdec5e586501c4ab7cbb2374a06d71 -54ed72aa382f1e0859c33c31a96e656959a8ebc444badb0815fb2dbc7b2a9f77 -54ff8cf08f4cd26e6de71bea728419dfbc92bf3ba81bfc17f190163c20cc238f -551444030d364e1d2a64d1c6854caa5f62f683f63bde8f6279e40307eac7f010 -551e83421151db4672138239c2b0930224f64981f77de7081ad90e679bd612f1 -5520be3e5ce39a1b3bc8a29403563edcb707e049c7a92f7ab9865bc94fe51ddb -552eb7b0e1c740e38678bc5e90221debc669cbf6e39c0e0b35e78611eb0b8362 -553264c6b7a4393a96a1eb768ea70bc0e23efb4dec87fc768ed703941494a763 -55547e8ea4bc0a2424a661a7ac9aa6048eacb189fcd31af74485ba9db1e9b402 -5554dec89566d478fa4c7a4f4fdc852337a71088540791b35499c8b32228d92c -5566c26ff9b45f276e5003fcc4b2f448be35993f73c0377d2df07da294660c6f -556dc1bd99dddd552fbe828d719e128e839bf55823aa1d52b0bc5ba7c028d845 -55702a56b53670d10d248baab3518211bce633ff4ab1c48cfe54b08b23792053 -55778eb72c51f1b226e31b9cf485b512fe01164d609e1447d0d053588ec009ef -5580a676f7ceb6042398d030754025910bc4308ce46026f3fec11013d08fcd70 -55851a0906d31bd77c30180434c8a942c6e8ee56a16b76a66f5137d8e233f77e -55916854e256b14c5389fb25abc2c15dc33c40ffe5826334cb78e06f2e21daf5 -559629e03d980925803d35a2f409c3afb45d2f05c75820ee2d3767ded8102037 -559c732f5b6b6a151dfc6500069f83f18f1484df05cbc7f82914dfa0d2180991 -55a037b1d440fee919bc893ca58d76ad19f2cd4d2e57e34604dbbf88612d871c -55a1e35ba91908d2868aeac1e3d80c257fce3e513ea4e65bbedea8cc60d1e8b1 -55a4057b181f54c2f83ae0f2e9f2d5ecb9bb5fc967f58d368bf55475de3c417c -55a66bc4b672bb9ec4e09ef14c3b9035ca7a2f8ef306de249dcf897bd06f5624 -55a983a2eecb711cdd48c8ef41c5e88bace4366fcf064301a586d01d4aa61701 -55b9055156a04865f4e426a7abcc3c597c8c712223da65c625e5be1390874472 -55c4be13eb72aa52e72abed19adf06c01c4ebe6cd0ce0a3142833f7d5ed23f57 -55c9ab1661a6f58a5e0e973cba1b3307466ba418266e01738976f70bac1cf283 -55d452778061ea689ae0f2a73415d587b6c0c482475ad0064eada3f9483eab92 -55faff6eaf5001d994ba44829961c596e556cb7620e1dcbe7ef89a63ef6d4788 -560a173b866f7f62af2b2e8667be869714438d7045aefe80a8da5feb5301f838 -560bdc87251de5f9586a6a85bc170149a7065ab8e6a77e24bfa958bf1d3b97fb -560d3346427582414c8c7eab7904d6fde3d365f740ab964f5d674ee1839d762c -56175b002fbda20c3ebb3f4dd09e567dc6eed0367c4a3809f537696304dbbcf1 -56188380dff48c54388d0759bc928591ff8aeeb23133019933337a4371dfcc96 -562036e80f853626c20e1f1e68bb3710742c9d2ba5d3d5e1641e321cceaaaffc -562ffc5d906487ff180da9d3ba99a112b1b4cbc57268bff6c7df1ca083517471 -563b8e35ae307f55a4d4cbf146dde522f59acefcfcd1de91ef0069347ec2e382 -564ad3412f05e114ecdd4fd1809aa285e1077abeb21d87d2c4ce51af18c20724 -5653af5996003b1cbbba24e68da5b9ae7366400824f9d115bec4ad2ddf21db26 -565a8d396d284218b646ec89754ead145ad98600b920daa6c75acd983a502a63 -565dde9f4e4c286a8770e6f3b0efcaf6351c45fdb7b0cd02068060e834fc300a -567ce94894479b7b4e603dd7a73c6cfb9a72fbd74b7336b2bbc429bf2d3417df -5686c68d5ada8694a455c65707cd050e9a77f558e226c3546e0c5ebf3a381743 -568bc8f1a3e559d1ea66cb6bd88a3cadb56b233fc034a289432d7fc058a1712c -56957bfc3168520b2eb810889df518e57070bd6c00e45785913fecd223fffb6f -56977bd9a60f3b813baca7d4ad6c9b1bc72a6417926147807e51ca1503306fb8 -569bb870adffdd5e2bf9d2b39bbf679cc28bb8cc501f9b12ccb55ee1daeda9bf -56a637579ecce26805d52342f474606b6c1d56070a835079e5cd85d5376a72dd -56a6fa0b4230610e142d583ee514feae141422a7ede6313e940b26a1b2b3aac7 -56b26dd9d660e4d578a076dba14aa159fe314ac8bc3ba40037bf3a9a9eecd809 -56b90feda3936617bace9252ef4b04d31adb299921ec76775d1b6f96deb40d0e -56cb3713a9ac7d1c4cb43701f412410dfdc14029695f9559cdea4c01d484137f -56cbbcf6eda77e9e6acad8e96a6821449e8832de28b535550fa40d00aa3d6ba3 -56d337a3f300b509651196285fca0a38bc98963e7dd5790af3d322fbf4346663 -56d46cedd080845cbfb160a53fe74e92468906f99877a2019d619469ce1f104a -56d75ac1d992f110dbddf6e7ea5871eae2a4b3038e38cff5687edcb929ff7ac8 -56e01707851cebf8f3a287d5377c7864a85c1a5c5dab315919f6411d5ff9f2f2 -56eadfcc368f9b8c70408aa9d7c89afcac8caa810b2a11647c8a3f9a8e436ad8 -56efb3e00a58165e81328dfd2c0c2c50c67c03da22fac3cb06864aef96335d59 -56f94c0377ffb30a1783feac6fa7be5cca609cb113349fd27b45c9496ed861cd -56f9dfe8b325e73e9a069271a3039ac0a5868c66f3a95934d59fe934bb146e8b -570269b7d7ca7384a922093e20f8cb445a4f12419121f109f0775cbeba17ffa1 -5704e2033bd9df946c0299b0acdf2f79b711443139925cda334442f63c44e176 -570be1ba50639a51c2808ba9fb76bc7dd471267d377f80fd67ff7c9a2b691481 -570edbb005268265796df8aa73feba453104a0bfc04d5eb1fdf7ac2d4ee16f21 -571106555ddbafb1d18d42daf79f5fba6a6898eaf41789b195c32933ed8c95a7 -571a5e1487ca778dbcb4be0ecb439cbada432ed34e4cfe34e2664c56d3bd1175 -571acb93556b179ee98d2aea72b52775e947b8285b846a3f6add3906cd6c579d -571cbc6a1fdb82278ca9343e02f0ab1edbcf52e1b085d8513cf26e39f701941a -571d52a8f9996ed1a7a119d9acc6579d42d1b2919bff3237bd800dc03c8411d0 -5726d15aa1108540aadb58cfc1e125d50710db773f6e6f080b41c6f417d81fdc -57335d4024c9171eae830268eef474b0838aecc7922ff67a87549f3aa5278584 -573ed32c0150d96153c44bb7223a8dc9092793bb16b23cf75f82343e2802ff25 -573f8d04d3b6f50b88ad8bbe58e6973d8b35a3bfd5e7525b5d78a8feb86a91ed -5741940a8666c34e07d4b62e903ec7a15c2cbe8690c9bcc6ba06389622873be1 -57558ed38b191d075ed20d1295a941560aaf65e5e693a0186d69e42d77fb7027 -575d7046ee44537aca217d03ee37afa641991959d340e55ddeabbd54e23b320c -576aa78c6c0c35c6a446a05dd1855f8c8068e47db04ff22511da47d98686c7ef -576eb833973c3673ed7395965bf1c6dc5eee0df4fd6d47abc31601de41e2d1fd -5775d18f59fce6a6285eace4fbf09d1dab8e75a4dc51daa844fc36f0835cac0e -577ddaaa14578fd761584e04fcb047589f9af769587b8272988080d53e5db3a7 -577e0d64701b79c53386887a8d09883a64f1fba00ab8e641473ef45df37f540a -578baafd958259c85c4961eb12e11fa40aefccd26fd2094cdc1b0ef54873aa34 -5799c790fbd287cb5b2198780455007dec2582a35b11c52ee6d2a83039ab39ab -579df21ed6df8426b248506c0f605c7572f9361a4a73e5583642fab813767243 -57b6d3b961d5764be9b63ad9990c33029e0863697e7d1ac0235411f25d6cd3e6 -57b95faa8f298793317296960ec5421d37db94a756428a87cf79dd49ff1a142f -57bbd14dba31aceccdfcb017676d3fd564645743e43e6f871539dd12b8d14c76 -57e0b3cfa2c0ca610eea35e6682f3a6758fd336f687e3f29cd44feb11d3174f4 -57e151a1b3e22da615c392f7c0a2921472150988729a210ca0906bdc68ce212b -57e33109971f27914cff60097070c1b1da72beb555265b76764dcca2a408ca03 -57e6a0b1a4ffda2376cc2dc07d3fa49d2f3bee82c8374a6bfd0a19104172958c -57ec48cbc95e7d085e4fbee06189e7e8aebbb468de54993123d0f09d3f623ec3 -57fbadb1199efc6551b921d7a820147fbc97721a8d0fc0ae9617bfb2d2b8d3f2 -57fc1a35e2581a51cac637a57d82742499170a43a33fd24e3731f2bc1b7369ae -5801f6d2df47b1424ca9e0ef7c392c51db1c534dc6231189f4d95a8a74c00ce6 -5808a5268da7f77db97581a255e603839b354f0c78437dbf6c696522f39d69eb -580aa9be1f61de7b0e220f629a84397ed041997ef428086ebf148899764df580 -580b69a0a5bc226448abb1002b8c1b1529b1af677796dbedf4f637926278cc40 -580bd53bc208ff26f56a7343f904546a203a4b0bedfbf14e311809325278b239 -580bfbfd776546ce0977d8f205b83ce045d4f20c80155c408e25ce217a4e0c3c -580f77f3ddd1f786ccad17ca109bcd251e7ae52fdca370ba4d033cd24eccb2be -5847d8285e48ddfb854fb800f1a7be8d8df2bda9b7a3653219505fe430e2d230 -5847f53416e92694d92ac1301d1b0ffece9be522f675b8542cb512b71b522f42 -5856ea5a9effe2389d71afb29199b0c68dcff2e7ddc43e53cbd076250aba2cb5 -58583808fcda42aea8d85284ee5480b64503d9c91c45478615044befbee88711 -5858d66666fbdb51aee00628de6d3b4fd3482c17ad27032b843a31c8ef749537 -5859b21c57647424c0dfbf39c585302a42e57f09dc93c284209455711d529ce2 -5861c3daab9b17289dba96ae360728c22699b44f8ebcebe8076dbe8057a0debb -58620c7418cad3e2807696557fa3b89a5c1e402d554b28617f4bc7d0fa638c1b -5868a4f4c06af9a284478f2524a953ffa3db9d8c100b2a769b87b8b7a25deed4 -586cc702d87fa765d00673eee2b616196809167d8d3616449ae1a7abf30843e2 -586ef42b002eef460ff5ef3f24bdcb81b6af66be2a846f96b82b73b1151b29d0 -586f100db372d26c3dbf621f2cfc739903de1b466b684ede859a73f8d98e22e9 -587d295c7b1ebca94e0d736c309e4faed96cf582718116c8e5d7848120eb6f30 -588e84bd5825c603147ba1dbc60e7095585b861bf6b85e86172b5b0953cc8318 -5891e19d887891066b1e955d96c40abd661a9e4ee91aef841428990dbe38cad3 -5896cc54a1abe10abc961446f5c144b6025d34e704295a8173495855d4897a43 -5899466eab1ffba672d04654f062bfa385ed497a078fb104e1c1ed5a2e76f303 -589f95bc36c322b1e5588ad19d053b02f841dc80885fde126a9ee95e783f1670 -58aa5368aee8fa5f162f5999926ee2601b117f2d49626f163a18a51a74ad1888 -58aaee5dec65017894d0136afedf40cd43eab6731a830642981b77f54dca2c6f -58b17ec8fb1e851689fd91f0280ebb4d7f77a6a84b19c92e1bd81b3d58cc1b08 -58b2f5d2f05ee5628e9dfafb937602686c557d88249a6010c61f90bbdc4bf638 -58c5878877c9b9e4a9520eb8678841370b013412ecbd97692b6dd0c0955a568e -58c718a5aa97ce2b63108b8ea12c2e20f073489ad699e4cb0b912c84f249a8d0 -58cd6f2b6a9934144378688449e508c29c87641cf7d4a108bf3b02b8681f2a5c -58d2988725a1f5ebeeef40841609f9c56baa0782d57517848c21fb2fd8d15d2d -58dc45f337b74e346ead5ae70f529af066051869502a1f4da917e8768a348a2d -58e231b3ba9e22876aa56fcabc23e5f252779f679a38c4c47f0fb62f2b2adad8 -58e5104df14d0cd4466ab7a8823e1920d5fa191c016e0e27a92353313c7aeec7 -58f79391b5b5722c62b876841206263217436690618a61870736c55f69910105 -58faec27e4065b1f6b1e56ff82a966377f047190318be92af82d660de1780ebb -58fc09fa7c96e80ec3060e22d6d24a8f42572e18c64c58ddea28fd16db7492c4 -590796d6d4144b51cc0ac3ce2b7ddc88c16fefc7177cf2145a793cacb26b8801 -590c250bb5e4e587a67bc5bdc06fd5b07b3f72e1948ac689a771e2c8c4b10ecf -590ece10d493b34db52aa82a69d8e8606faaa7607ce3a5e1bb5b463a88758f51 -591220c3ffc2815759e1913950420ddc6bf3b2360d6b9878beb1add270c734ca -591585422201016cbe04d337128f07fc931ec0724e04f04c9de5372dc8b4e541 -59264be040a699f109c0a535ee3427d0acbde43c986bd62401287a680e079fae -59297625273b637be01253b3739a3b192497d007415837f49b18c549c7c509cf -593635d5baf25c8a493ee4fac6a8e2a6d6761c894f3e7d256abb433111faadac -5944f1c881bbef1a38beb8737b37cbf0c4aaea8db46c87fb019253b2df953690 -594893a64229fd71b8233c1e8d8b8accadc2461ee9abf929ad7a1fac37fa5160 -594a14d11ef6c5df8a4f0930b351654841f7a2503d211ce5e77dc4665ba8faf1 -594ef4751274b077ad995e10f2ffa2aeeef75dd3af83a86b2fa2362933464586 -59502893665c22f4adfd14b3c375ec024f02acf5d75fb6101d976f87b8ba3001 -5957ee0e9be1fa10ac6241d64371e0d960f59e0b76994ddf804c5727d3b2dc29 -595ae1c0a7922a301291abe9824aafdb934d35baec2200415e02f0d6270b0e9b -595c025aec56209ca2fb34520bbef56827074afc0f7090f3071fd65a5327259a -595ef29c342710e9f05a97388ec7835e0ced389404d7765121dcc8dbd9675c4a -5966ebc3b9348b953e0300c719cded656ca7cc9d4588bb8a225165ba11062f49 -59693670329d5a6329638c4d798a731370ae2c796eea19603dfb78c2cbdb0b86 -597969392988204ae1567c328d30806e7260d1e5e418b81e6eb0df262ba029da -5982cf5e9aa1c1fffafd1197b070570c04090030df98bf8ab97a919d42cbd82c -5989c8a729c7f18377790390b7d2fe0e5bd4745bd479323405eec2a7c48b9502 -5992530b3ebba6b7dad96ebb8c590a557188c4109c8c09072c1454156daaab27 -59926e9cffdb35ccced10eb7d17d7049a6ce57db5d8c9e597d019b59060c597d -59936f311ac08bc6850c6b3a384cb363827d6db862a0602d57cad42718a836bc -599fcf99d3f17795313cb1947fa1de18372af7c49d16a6270c97d759b49cca14 -59a14b7d85e0fd412fe09bc1b21e7cc59cdbf7063ded1556a2365857f280fda9 -59b49acb2297021fde1855a60aacfb09ba3b883cac662ddaed1602b8edad385b -59c2a043b396f189eed4388f40e70c4f48fa53eb0b8d978f44e54a2a0f5255ef -59caa825d96dcbb3c04109a433fa57bbad325c2ef1376ac5ed444bb0eccd20e0 -59d32e343b6670fc001e1c36002c0536257c2f296ec1f4b7ee359ae5125cd5c6 -59dd38577efbf8a1130e58e81d57245935e0bbfbf755517f3055fbb0e706a69b -59e32abfc0b470f9334bd1f4fae92e503251f42dcfcaf70a6272426b88e9dcbb -59e5ea8275b470609a18feb49decc136de01a321ec5592ea65fc8c094a328f24 -59e692da28646aa1b1cdbaeb11b6bc714f892ffcf2181760814dfbb1b61a7f3b -59f4ed0b7d505c65f889dee8d3bfc575168f4dc1a1fc8a1412f95835228c38fd -59fa823c998916ad02595bb3526128f94130e7b14357c1c8ec8889008c46b1cb -59ffd1332664631f852966ff8e7369d32cb31aabb5e7e8fdf0149226c1f8f1ad -5a1ab6d7473e5ff2d70045ec3d1a5488dc7fd19150dd2ff56f0b489318553c8f -5a1ddc4ea90773fd8daad8af3db501b2709ea1342f902f25c9a0a7c2ebd53028 -5a2b94fc649de3a7d41ccf69096f3bf0435de34f4a1c571c3f1ba60ecf35f71b -5a3031635a41fc488c2ca90530acb51f79aa95c73d5c47c78f4a1af80800729f -5a3416ff9e987fce76248306a1c7a05f916386756ced824b1be0c82c122a8a1d -5a345a5a4017bd473e9f7b2539374bf7fb7f039f00228730d4f62c8fc7843f57 -5a42a14443dc476bbf7f5be0eb9b96dd6fe8579424405b71d19987b42ec37972 -5a5073dfd7ac34f27077c3a06f1d7fa85a8aed80867dee2c9cadf341d3dcfe74 -5a5b9eec02526ec0b77bd3e5ae8a18c127f3637c40e8bc6ded26a7e3fdda553b -5a69b3457efa6401c8f1ace74d34003bdd0fef53a494d019bf9a7a2ffe936f20 -5a73da5fe9b7195dd9bfef48291e89ac22fd73c2d5883d672613af48cd88621b -5a8879954826bcf30daac4b8a0bfbc602475b9b9017153acec45a5ff4de796ba -5a957463bf4f69cff7de1b96a1c34de90f0a0397c58e64e69cac84c95ff6208f -5a9a6c94e531df76a9a4123548a2b0affe3a294a916a02070fa18d401052a1b5 -5aa455d6fb5a8cca5e58522088310ecb67c654fbdd663cd6305c6e877079d0ec -5aa4c46daaccbc44d2f9e11a2d42478e79f6dc47044da9ed88bd222a561f60b4 -5aab081d9c5f9ae996ab61e2d9c6db5d9b57658979534b6c031f4aa5ac72d349 -5ab71a938edb5f4ef7f978528f9ba088bba835125759053f555bc1164e78579b -5ac49ac4abfdf88dc18fafc6623e123658a5c65222172f4f60b3899e48c2fa4e -5ac6301f2bffcf50627f25851a7d8d7ae01d030afd391f68e9f65d3a0d5c8500 -5acbb601743cc930bf493f4131f3f54926f12befffdd11353f9a0303d78e6d85 -5ad39790084ff40aed9259d2f5973da7a7d7caef3f361f67ade54e527120641d -5ad802307e5ddf2c531a129edb6f5f32d74d9e90776ca4ce27314277a735c746 -5adb35af00c86fb3b1b0c42a62f4c55a031f1f223a1bf99233ec121338e02a85 -5ae080452c57218fb33d2f06b4e4aa47c4dd9bb53d04c0d2c0a73ab533f18e10 -5af801991724fa2705347104bafe59176a898df965afcd56602b6edb328593aa -5b01ff507bedec977c474ed31953e90fd82a88f77fbceef188f989c100271d69 -5b0b29083e083d5f92adde9bdf6297b7910434fccf71bc784522fb33f5ebccae -5b0d57d45d19ceedcecb5c55bc42b30001ce804dafc17a75aa37e8daec58609f -5b0d6d6262fa0478095f58cd8fc598a5fa4d99a6f6835e482e96db933efc2b16 -5b11aef17b9aebee07a9367d1e02c37ae04da2c861716b37a794648a6a237c38 -5b14f065103c3f3653d7404b00ba521048acefe1b5acaadcb9dc3052c57b556b -5b167654fb1679d82db6296f8502deedc79595cf5cfe5d17adb6f018b6a9d9ea -5b27a899c5ed10ca4a27ee6104c16068c281b41acf2775b2fc4df31bc044961f -5b2a949a1df4eaf826a6d21e61701e8a90a3ec345b954536335fe5d51ff05b46 -5b33ed29d9a4b7b354c365bfa59ad20682f29709e075e1a4483f43e9473bc7d0 -5b3abccbee47a33d453ce2beec7e8c1ca5b9bc950336cb75ef42951443e99b46 -5b3b31fe6db5e2d0d8778ae4d76b1a858a5a1c1ee9c3a81ebf7597ab8d3631a0 -5b3e694e26a46df94f79ef07c639dcec0be82ca770bc1c7f65a0b91a71819ba1 -5b4238e306c8d95a829cabe9375004605b788891017ccf437a042c3e93ff7bd0 -5b574b247ddfb7a2d83409315d0a4bd9520c0e35b96a700a98ec020b2e15f290 -5b593f4634f1de9fb00e2752d589a3d6497c90544fe564094b8a1a3a81635a59 -5b5a62cb05dbac1a2a127289a9930fc394cfcdd65627a03e70bf9a6fd2999bb0 -5b62a9f91fdaac8ba3ec76b9f3c21ff3d50d62f09a23b62ff333059932e5acf3 -5b77dfcdc1e26b7b64cbf8bd9e7667de9d724930fff3d8f247e9f3814cccd062 -5b7e7ab191f5362d76dba6a275780f033fe7dbbb9e4ca08cf240ebdcf3283f45 -5b7f1ca6da259b57572c9b9040b5952b72c858bda22f3bd0bbc5042edcf41392 -5b94f2be354b1b39bc68c91ea72a3dc910c1b5ab53138bb77e432df62fd7e782 -5bb757b9c26de06b27be480f26f7913a1ba88bdc0676abafe2f6d76c6d0fd0e2 -5bc6c2516e0733a7e6240a6f4b6c3a20c119dda5a64726d4a3d206bf86c68a37 -5bc9ef2b3d6411113c6065e74e4506c7ae7a9c2a89df2cc5e186c5d5fce0d0d6 -5bcdbac1120f964c2559501f5977fc9d927af6f0420a55f170ff99a75c7d217c -5bd015efcb0222547eb4a24e3d508bff5f733a451c6ec25a2325b4a3354939e4 -5bdaa6afc10eb40cf99e7fa379bef435f231fcecac7145565e21b4d0f1d81f2b -5bdc6443410eff135bff97c920e34d05bf2f32b9a9cc9316d34bf03e68da07fd -5be3dd3ab60b378125a9370331b63e9b8b2069f5a3661ad4f9d4230a2689a038 -5be7e93bf321cb66d2a8ff6cec2fecf0e5a041808b55d22b2730b76fa6fc15ce -5bf20315e9e710b3bf2e179f34f00e5ca49e2a44186faa3a529826902de17736 -5bf84456113000e2c398e84fac7b402f009c8a70cf7d517d819abcce4892b7ae -5c020caa5ca3e59b1424b687fbc912aab6ac2755545f8b314748e7cbee8b0863 -5c026fd11c32d31385fa82858035f492538a40aed7da70ce41d08130b35f98fb -5c0f7fc3aa3f1f1298b73dc7f251f9af80c1692e07047cc6cc8c7076b8d41086 -5c1f7a2b58ac3e18f50d252dbd35688bd32d33ac0cc6a3549a5e97a4751e6eb4 -5c24cc60bb209bd963778bc09bb6af6ce8505b4fa994163283b4046e4d7becb6 -5c2da7fa9b38ee464bd102499bfe48f7bf8fed0d77678269d4829705117818d9 -5c2e237077d63c3098cc0e2e0e8355a0a3699b0d3287e7a5a774021da452ebd7 -5c347158497075a3e0821860c9e4919109a9c0936bd350c5fdc696e449337e82 -5c441fb942ded260bdf07dce5c4d231c0c5444d2218c5d6e3b044127116cdd2b -5c4744b21a7fb59142f89a479771498aef1991536c3a50714f882e903883f4c5 -5c543e96efab2054f91286a1d467be18d35b2fa52228293441e5e1fbc68d1b6f -5c5f8f65f46475fdbe5d6fb3c28ba610a4976d2d6828b6ea57f0806640d8c276 -5c7d0b19d109a6411657af62253b781b8be7bedf211aa2b527de1f8c7eff48df -5c823618bdce0425a634db726fe161fa00bc56c7ba5141142a13985d187fd4ed -5c8f4edeb2e29bda483dad1a48bbb1e86d1e8076d3907d8a04a41b3c5d9ad10f -5c900257cd069cbeb56c25ed125844074b9f8053a250608880cc06df7ff21d64 -5c9cc44e65a976b8bcc43f1b65905d7b274a1bb13818de43832133523d5ad545 -5ca4ebeac8395fecf2111d37c8a2ee9397b803d590cf2742afd337ac856e1caf -5cb3648aba7f5513eb9e62b744920c17a3f1bf63261075c150e0ea41dbeef5cc -5cb81d676c2e45d5d075b0fc1b93756ef285531821f73ed8227910ac78d7be4a -5cca541162557c363387e9a59eaea04bc3ecc7d5eef0d56c5d7b0bd59a2c6ea3 -5ccb665d84fe49c98d994162b5e015d0047a722b34b3437d214f3112ee494962 -5cd6a376fdf51b20cf30df8ce9bc7d1bf93f0764a3f7bc2f7f5e9052d0fde99b -5cd79cc72e91cb15745658f99cb013d0ede8ef9744cb9b9e55c22615bb6b90fb -5cfd2f0f0ca24f996cf1928ecabe1f22d088dfe42784970e372093c75075aaa1 -5cfecf9d1163ba03106f5079407b675fdd0dfe6419451c32132ce586fa820988 -5d02181ad127acd57ef7daca5a2921801b90f331fc29c93d700cd4e3370a757d -5d07dbba4cdff6144d5eaae44214f3babda8b9779393c39a24cb329a51e6b37e -5d0824d7ea9c183891c6f6293ecc0d16ff49d2ade210b009050161e11a935fad -5d0a395b4c39d17126acb8477455f59e09eec5e5b5cc2a2f65a9c08eab9476f1 -5d1d4ba074339496e8f531535bc8894266bf2e17da5f8c3dc5d43897bbecb0bb -5d223df5a6d6f8f0517e20af78619d52d7c7ca427801c5d658db34ffa16bc0de -5d23cc4dccba3367869f812bae363de81d95dc716dc3dec51a404e024c956656 -5d2a3080a9b02e5341473041e69d53e87da29168572176a5b375c74619f48693 -5d2cba6abbd01b9fff6aa013462c7f429919b87e331f0136e0a2020da8a7ab1f -5d340ada40da150de9d1eecbf5c373dadb14212bc2550c5559a717025adf55ae -5d39de44d44a79cd2374a21f46d21f117500085ce464d321a391c9567c742807 -5d40d455a217a1a80a79bae6473ceeea02e7bd84c9c28701323f40339c6884e5 -5d41e451c374985675af83bee885b8757b1d4f83a834d2b12e3552a18516ab2b -5d59be2645a2ca3d35145fd64420067dfcb693d7349232ea68d4c95fd25c8586 -5d5da776f173ca7e626b915467224eafdf9dd1a962a5049979ca8bf66ac1f3aa -5d60d89904ce7a27602f86e701b080652396bc7cb2f6aa12fcbdfd7a0bd7eba2 -5d66acf71c77654450866af0a14bcb90bdd365df5664e200c3cea84c6ed0ebe6 -5d6715377d239eb459f0b1f30ececa93e368ec85c17c7279927b225af6d8749e -5d69d8054b4932095211fe50f6a966b990892bd228fe005897718356f67e35d2 -5d6e63d27991c6554198ae97187ad04efe51f941428a244edf821487101258f2 -5d72817a2ec58aee51f4cc34c95fb3687b4acd7cd8f218d89f6ad3bb4ffcffe3 -5d7c4509708534ea4c15ec0c7b13a1d679ca7893bc6f26bb8256d3b13efdc682 -5d7d8b5556061781eea13d7b9b92ffe53af2a35675e1276a46b1d8382cabca5d -5d80cec9d96e2a5f84008f21c2e8181a3b89f38f978ec6faae64c861ff1fa3d6 -5d8e4e701475f7c6983c55393a205746f9b6b2bce51d2713a427113c2ee2a031 -5d8f9337e792ef4682ec31ed7ffdad0246a9bc972bdc09557684cbb9dab313bc -5dab7e4184626bbd1bb72165a3b6f10b5c3d9dd67c3b083abdc29c478c5cbecc -5dc935c77f581e7817fbf25a14a8a314733e10ad322ccb427b12d33513ce7840 -5dcedf2b700c33d38b091ac7e1b2b5de9cf26742aabe5dbf9bae923ddfa24e1b -5dd385f594080846fd67c903009f2d978f332e1dd3be3053faed9ad6277d223a -5dd62104f054f83572c96644b44d5f4096ebb68c3c89550e39ce509ca30fcb29 -5dd7ce8f663eaa3f7927074230228ac553d077f71c62ea359dbb44c416656583 -5de42bcaaacf7d548bc38833ecb4d9996e8d8e3e1abf5ed4c93fd3f914a46552 -5de89c2b4830bd5513935d8fb9ba7354f10697aa8ecefdf5eaf81a66b3b27036 -5dec56940995b64f5eee9da9f9e18c659b55ae3baa88dcc055286e81fcedc20c -5df3e2a04fa1be7747418e4841bb06d890840f1b111ce2c7c56ac2db7f9e5e71 -5df62a236129a79ea75624d85f06929b1d4d59ffb62587d4ae1aeece147e81ba -5df9bafb9a2f02a84d37acbb5344d77d156bc5a25de8f90faeae1061f1a82b16 -5e0369ce160ce15e88db473df7f4cd7a1cd2804491b72036f429e25a65356385 -5e03c41e165f61366cb3a252f8aba8c694ec9651d7b39c75e04711ff1fe44d0d -5e09a1aaae3a08686a5009f349c656904ed476cf3e91e28b563f1b27d5a80039 -5e25a836af74db188f6ba6026629ce4c9667fe304398cce514fca30443f259c1 -5e3b54eb564b5e579c48835cced9d7f28eba161da73283757895931dee5e0d04 -5e3bc826bcbe50bb22f9b30d29795bb35502e30cd4a9e85555cf6f35240fb8d6 -5e3e7370a91b059b14308790c436814d3f2ac7ba26448ba8b096cdf6588a840d -5e453115224eac9fcb90782d8cee0dfeff89361eb614b725af90efb7f93d55f3 -5e482724ca8fa809e5033a8103641b28a28de9ec72bcc7e2dccb54895928a93f -5e5c1bdac5e2d74737fee7f2f59ae374c73c4bba7e799bd8d25dfab9e8f973dc -5e659ff260d039ca4c5d077362b9f7a886b5b506dd810bc2914199fee56057b1 -5e66b1cc9ea47a4988968e37706eeeb1dbfd132a6c102593337cacffafde102a -5e6e39caa9bca6e708414e9b59131adb8bb35099910097783961ffc84b39fc13 -5e74ce990c1599562c64902e82824e330f2489ba196025fc0a722845c99fda57 -5e7e8ab6c1305c5abc0ecd794c37b4d94ac33675dc4afe258a83d32dab6eb2ee -5e96e8bd2d9a1116bd31051400071126e03a67341a72b3133abe181e3b4f7d4f -5e9703663ec9191513e747eef2803b7ea54c281f107444db8687546cc5aecdf1 -5ea141cc5968343e55753b12e28f86d7e42e5218d0478117e684161f575bc6ba -5ea328db18e40759bbc2d36a6c36c8c0cc14642283ece5a14c6f7102f9b35ee7 -5ea72a9c56defa1b22c33568ac30105cb899d749a6d82fb579e7ff73f4a5cae1 -5ec05671cec9a478b8857e76d380ae20e23ae7d74bb32f347181494be15cfa5b -5ec310c20fe68e9f66726c0c1a73c56293c570f12cd086585f0f2acdd00aa2ff -5ec7330561524926a0fa41bdebe1684280afe5fdb4246b8f4c1944e5a2688ded -5ecc7b2416c79d4ad6b6b73e52d58c35ea922ae3d6e1b4ecbfb32b49e109676b -5ecdd692362cdd16e7fe94008b762ddd530ea9c1d9e96cf5e9f9ce030ae99860 -5ed2c0fce131b91b58071db46b9c3890de29f3ac824cc2c5f1bdf9775c804ea6 -5ed80be36d052d1518f637679738c695988fde1c8011e535bbca700cf491c1c0 -5ee779c837a947056434b72bb1baec747dea91ed37cf5cb8f39340154c597640 -5ee7c14ecba412069dd59101b0421a3abba988f2c1c98f1a20742c0853d1930a -5eeb79603e570a0cead8a7c9753a8b749b9f9a7aca9a13d15fd1dc6d0fcec5db -5eede6f8482fda3931a7db9e714956aecf23955cd9853251408efde57baf017e -5eedec8253f7eb6a2a77ff8dc102ed0a71e0d47cee4353d77964f9626381c183 -5ef965a1733b475e5a56f48e0b99db5d75a7dea8df0421629f8a803e52c344b8 -5efb2458046efa7e8c3dcc5ad0546efb785c75698824527076b31e8c1a384b09 -5efb40639a2c24669f06ef6622d94b9952dc92b14eb062749a95f11d2f8c1270 -5f061a9a22775c43ae13a0a9e3aa8fbb2004b4a97de7ac1ce7dd1526566a9feb -5f0db6f65c709713aae00817b601c17519e5edd348f4791eba7530bac76e1198 -5f1e02d051a862c65b87149484f34fb727d124a3d5885395df5f9ec9b75999ad -5f28490aa64922eb4180169269e7a1c3bfd1919b97d476c6fbe7dd49a049d218 -5f28904acd286b75af77f9936aba1bd1c7c8b3051f076d743a866fc30b6175c2 -5f28c4a99b78f2f2f71c6c96711d8271228d33bd9206eb7dad7261b5340bbbcc -5f2b20ee269e7121cc479c6447a7a78b7dc2690bdf20555111a4bd333e382824 -5f332834f176430709361564381cb79bec2d7047432188b88f9d66ef33187481 -5f3ef043a87834cfc1b23d1be3a5ca4c3ee01200c95540667bb7ebbd5de5b4f2 -5f4c1c18fdfef56dae9a22f2681006cfcd96c97522a83af2631147881dfb3966 -5f4d2612a9fc0976123eb88ea1625740064655f6d06b583ce6c9dd6dd33ef0f4 -5f5eea23d96385fe381310fda48c2387c80a348b9d8ff79799930ce9fe41a2cf -5f603c6d8bd0eb6b2fdba94945055de814a93924981ecad38ca0998c45a3f9e6 -5f6f5bf0221b7567186d7777bc20e57aa2f5529039bfb8d6d8d0b91362073115 -5f735f4de2f083a8e68847646033045ee800f86ef4ec7e72ed48bb46c95759cf -5f806c8e46919c8a05bf8077e44b0a0e92c21f58c916824e53abd4f121125a57 -5f83ccd5c94dbc4c12ff454d783c5261550d5e4ffa3a04476f3f9a74a8e682fd -5f9583320c348a2950d5b6aa70f328147ec9766dff25486f4167ba7acefdd7cd -5f99bfd9ad0878f55f568cf956611d1b395b5ffd3fc0f11c4085fa31126aa66f -5f9c382a9d8eea9fbee5749d676c55a20ed5a359df9c8c379095f4a5678224b8 -5facec55753ca90714878023e5779da5cda4527b43830251c7a2ee33d3e3197e -5fb07759f7d26deca30cbe2d16480a77850726511d109315267e78a057d6ffb6 -5fb7cba6c3b61aae1b6384784fe2d34dd72b501c2cc10dd5741c53c895720e03 -5fbeb84a4c2afd177f2277a83d09846755585eaff7c667057ef370a28b0b8790 -5fc03ebc9ee336c6ab5da55eeae6ce716cfd885fc1f80bf4e086bee96fc6a166 -5fc481b157a8ea087045f568e50e1466270631ffd957030bc3b0471af19afe5c -5fc9b2cd23b2a4fb4e87d0765885b651fda26fa46cc64a607f421091b33548db -5fcaef2b8ef61ca96fbe325ac5bb37f4f76eda7537c11b0da12afc04fd5aff22 -5fd81bd2180792fcb933d6ba93acd6c3ec2cce2f7d3df0d47e9b7a332a751885 -5fdefc118f1854d8b242953a17a3333eded138530f712fe8b29c218e3e90aa57 -5fe0e234b1c636fb216b8c8974e8de285c9f55d58ab133eebb15a4146490bacd -5fe57ea052c0704f586713194a8a8962eabc5b018abfcb3910bfbf79a7b814a8 -5feb5ba9fcd2331a2d0d0e75c8f4ffd985a234bfa57c9a6a2b93759c30b52ba3 -5feb8b274218a6d3373a134e5e72de6b42b98e7c28d9efd0fae088a48af0b682 -5fedecd269ed2df51bdae12a6ffe94270a5210b8763508990e6d60c6a93a50be -5feef68c095c8be8fccca6c2e1b5c2502053fa208396957b826f7053d2837b88 -5ff033f7ff7b50c44a97f5448124c718b10e3e457c2ba1a0c5df641c1cc913ff -600fb27af272550623c3b247ded004aeeed1b301a09c69799dbf9930aa186ac7 -6015a9a35d9044fe890f802f1290a1c435f240cb6c0d7fd0d9b81be6466b397a -6017ceead1f298d9e75c2d4ff5c40c5c7d8286cc5616648472c08ce5d5fb64bb -601ae4c899d09647348386641afa8dab380daefdfc0bc44075c825a6f9d1ca23 -60206b992028873003876e99487da8793ff570d38195a366d750fbe9a9bf4203 -60346656b2f47f8adda169b77ab8ba9ec5a22a0a1150c783f16849dea33f649e -60369a1b6a04ed313da804de98e0c26ddcfa74387b808734b58f4f95e95b27d4 -603a6a742f0ab25593451728cc41caacf81125f1a2a864add2cbcb0578b99287 -604d960f4bde6e179818bc8c0af6a9da8a49d61b660c650cc158b47de2733699 -60564a42803fc1032915ef848dc5455a6ed22159fce5485ceebeb23edb0aba38 -6057e91feed68d19c01db4a5cf0dd43b1646f7e61de3b5877c032aefdc2ae0d4 -60659ed54e0a028e42284a178c51207a286bd03d1a26214589f91856f689c95f -6068a8030879b8391b320d63a5d09cae5f75dad101f3f4200a84394675a5a2ca -606f398125cd67ff283a061f105edf0515b88c6989803f61763d1a28644af1b7 -607b2eab6e93f31ddb5e9794b1a573dd17ce590c14cfb024c990960bb6fc7884 -608242f96410f42ac7b33cd5bf7cee0d21c641da6f8d09988f538dffd736871e -608552540b3f0b41b4142b38a90b71f4a6902e03a7b098a8f055bf8c71441b69 -608bfd13ccc427aea005195cadafe5011a8a6c68b305690be2c4c10a11a4767d -609166e5e5d79cfde4f5cc07d1daac7405f48daa7952563c74f2fc387cf863bb -6096fdc5b8ee9d5c18a9c2653ba7a198a29c4bb22365e187e417eed3e7724ee4 -60b40ac800f1aa1cafa904526a9073892dea9f939b3c62ce6fd91f357acf1f80 -60b625642f4c632862025532b991ed9ca385b0151cd079ba202885328bf1541e -60be7f0ae72e1a6c7c5ee4d2b961e9dab0844c8d575b75aafc6d05ed4eb2f6cb -60c263ed5d632aa07541aca981cbcb5e3e7da85aa7626b1631170ac5154fa898 -60ced47299ec7f140aaca183458063f846259e03edeb8cbbe4916aa2c8f2e1fc -60dc688beaa0c9ae422c1275d07bd1e3b2bc4d1fbe9504758ecdd4b029e232b7 -60e0adbcc90d93a1d5c4eb6e4e34d4e012c1380e6903046549488e890de95424 -60e3400d35299e6fc5faeafcb01fb6e0ecbfa2aafbac6b68bbe0063728bab9ec -60e452073057281808cfe4a582d0975f2e8bac317ab623cbc3c8e53c899b43d8 -60ea4436452ba00c95717a342a4a98510802d8ee35b6c37ee0c2f3fc272f832d -60f2feb3a62705961fe206e6d84e7357ff1c646a2a88f615c99078ed86d59c16 -60fa2dcfba703f4148ac60577bb581eecf7620b37073d69cc367ef6820943faf -610e1ca14cffd23c38fd8ebe9c1ff9cc1f106ca6d1658ec21bd3da81dcfc2642 -610e80a81a3a17a8e873189dc677d9e1522ab1ad110e3daec08fdfdee03cd550 -61199dae99942223d65538015c8638a57b98e22abf3304c84959984dc8b88796 -61214312b2155ea524f7034d55cd678417d780b616b80ac657432d3a958ace35 -612babfccfc233318148ff3471b9aa27587f003ad9bc7ab171598829a5f84068 -612eea18da3b22743186b400534cde98296f387ea210d33f22c1853cbb5b25b5 -613078c0ff3cdd370f9b4c65f8a2476e4db1f33f4b9c8d40c40a58e2946cfe27 -613192c323a9626b8c8d75e310a6fc9089a580dcc34dd64642deb9b064a59ec6 -6151caa4def92f24f553f9f5187eb3a3c71f4e110343a29e7a7181886570792b -615477c07cb3d983d46a2d99f0ef25a1f5360ed3c71d07e667fbb4a264701eee -615f31ac2e13f9679e63e0c600447ce712fbc01d2ce5e2ca4ef8b386de54eb72 -61607040e22fa68fe243ebbfda14d6dbcc85b22a8b8094c0a708b0b23a05e14e -61661a668d2fa7fe087f95be8fa0618442a29a373adef09166e8e1fcce6b7868 -617211dffa0285db3f73c5d44234f408e0a094b7ecf2793942b20644c0f23054 -6172163f6f7294f978ee15c6409da65908a35a3331442679acae3cfbea7a8b4d -61889b163781b61952ade2804ad9db1f48d6a256a380619a6d88d7fcd4ce66ac -618bf95d749e28ec8e45f7e4c711f67844adef7e2cd4318477742b284d23069d -618e4255ee993f7048d70c66a1ab2f7e72dd465335ab48909caede8d1317e662 -61a0662121a87c1d7db831d035553831f7b8a57445e97b9414f065a4d7b259f2 -61a451fa0cce89831eacd119db3ee9ac79b2d73c4df10065965f0abfe469ce1e -61a862d8d4c5793628cbb05ea49d0606364a639ded7085f8588dbe1d1f1aab96 -61a9b4f701ac981ed76e2855a20f9af9715da9d0cd65f09fa1c23a813d8b857b -61a9bad1fe9e937d9b31ee3b9881b8365d4768ada54ab0585e716262dd8ee955 -61aa6a0720a1a624a399e0a8746a3fcf2075765f8b96b3eab622ee85c5763dc4 -61bbf3126ad54683deda8536a6c7053a90d50e6bac35cbc577054f411160168d -61c7f8635b5d52b85f8d707591843742dcef55c0ec014a4116b8424419f34daa -61cb70505d5658ab662819828ad07df48731e336bcf8ef7c19336b104997fa59 -61cc2926a25aeea855a5b0a99b66a2a429d12d68260b8c782bc9865a2f2e5696 -61cd0556fdf4264b436431f3a588632db1315d5108a7730063451589f36c7832 -61cf57bb39fe88ef9443a325bd265646aa3b2b4d82541a007b394f8e8f6ced1c -61e03f97708b3376109e95f58208e015a71eeaffb4b39efb0e1e9a4215394006 -61e0c6d1753c2c50865542761630b4a1792cc27c67cf2f069faa8596931ef9ae -61e1fc48776dc4045b3395217e3e8d4658fb446485fea5bbe30750824470eaf3 -61e40398bcf1a767df5771227bcea24547bbfb7187f5b31b6dac58358dc01764 -61f8beadb8f837d12265b64736e466eecebcf674fbbf701679c3f9bbd75ffda5 -61ff4f535bd14b2dffbde4e061c2c664f9a0aff2ae44cf51db793585d8425e3d -62050007761924ae320c8be9fc4f926ec7ee04b8dce730087128fb7d82b740b3 -620f135ec735e4e829d02faf902b3b3a4a4102c296698cf59daaa774b41f430f -620f54a957c5dc011488f1a78981ba170b9f5b2663a6fe48ff473ff0134b1ecf -6227456f1919e77ce75c1ee35b86287cdbbb8acd6aa12ac87ebdc6dce7544295 -622df28b1fe6e78bae215c2b52b7718b0e96abfeeacc677a9dfa2dadb76f5b4e -622f7004f5dc3f5a2e9b3c16e8733fe58aa323d580dacee615dd2f142905535a -6237885a861e2ab59d348c44412b2320c9095bc72d1f1661a6ed045f3cda6648 -623cdad7841068267a83caea1be5e992c54965ae0177bd00c908a6c19bf91999 -625288b395dcc8c7b01f5da9d0b1fc09fa00efaa8db4ffcf19785c93211ad010 -6252da087864a2413582d4a18919a7ed1d8fb59e4e900b32837725a2de951f46 -62535e3c2b78fe6c4811c3c1588cfeb4561b8550dc332dbaeb33ecbe6f7bd115 -6260cb8f248a19e7f4cf6d99e3c8a8a64f9c73c120a6ee1d2530ddedfdb39295 -626a5e896d4f87dddb223b66062ef229758cbb7655aba3af0e3a4b3e7bbf5350 -626f961771c000242dee537b9034df54c48acedeb83e0affdb344cf48ca62e2b -6280a90d500503c96480bef8a03d928d56561da2a990b3fac5825c60142aae14 -62850eda586840d3d7f9a0295dc3031d8aec9c5d14b320fdc4ef79ee51256220 -6287e3e8e99b11834615e11903aef1f8a753b6aa0081a2fd02580c0643c9ae21 -6288fd2e27be95dafd0f28ed9e3c9031bea68a15ee12068d910e2fe8489c155e -628b6321fe27774ee9f45f5cb6228f954453efaec5140c05f56ec63ac281f45c -628fe8181605a0324b4cfbabb266a7345e8efe8cffbf5f5ae1475c2a6a8fb29f -6294d8949bdc19ae94307a1ec9f41628a0e964501945dc542f7ae35e452565e5 -6294fef74e5ab547500aa537dd5d0af19ff0904a462dfaa2962423ee7bcef28e -6295c512edefe4d1534e4472af97b4ea02bd3024d0871fd6f48bf66e73fe96a8 -6298eabf7c24347df416bc43b0fa14a743053e0ee701743944ca8a2c21845743 -629b506fbe5ac3732e704b662781602029014b84d551a8011a6779e63842a869 -62a1c4eb34148627eee6ee5a6ccf2cc17be755816ef305516b352eeae72e5ae6 -62ae000d1084e1be097ca1754de5a233305588f2fd8419c95af9c342c90addc6 -62b433ce304cb4d7ebe838c64d636fe488b3c0037eaf9e0e663625967e067703 -62b5b5cccae4faee1e9466cf49862126c9f77721b42a384afdb291153e819dfb -62be39ce8db67bb058082f12a81ffce6ce7d00359e66ab8ff0d127e4ac72fa08 -62c2b692c72ed7d84a4273dfb6545d0ac1182080f46ef77778d0225cf01736eb -62c5250e3eb6a4ff29d1a2ce994ef030a51f150085bc3e7765814dc47159a1bc -62ccaf3196df33fba3bf3f64dc9da88af2771589e62e26c2e44d9e8ee391da42 -62f7eebfce4378e59abe3c361e35437f26c6ce2bc6aa50205d9dc6f363933961 -63006e92e2c599945d80bad153f5acf3f066b95f58afface9ba535cbc7222865 -6301a14e98c8fe385c8ac5d29033508270ac42e84c4d0ab468594738d73579fe -6310532a2d1380733975e88bc52fcc20b2ffb21c448845d67cc379e25b5733c9 -631695ac3c6b41404a09ddadd605c0d3e3bc464f86a8eb8be78f92a7112385a1 -631b8ed8a2db616799d45bc3b28401cdb7829ca699a11c48060aba193061378f -6327e282a3372e63c029883f4cd561af3336f9f8e0b236123eac2b1a6822bc42 -634fd2fd7d26414ba5abf67cd02f6cf121abf3bfa81b34ec9a5350e5f0af2db5 -635b7e5db8aefa1628f7524877876e7de10c127064a70218660d614c82bb3bf3 -63656c3fa12b9a814b1d92ade52a7df4039b441759bb3a5f222c8079a0daf69d -636a3bacd2387c6a3453ab3562d8b85bdd2abfc359541c8aca33765b63bb175a -63709af29a73a92cbe63f3b6ccad1cbbf7dbf456245e8b92e45a3a8977132c9a -637178a71c0403ac13f280da0207e2ce8ef6edd0543e43a1d33bbcb6b99c97ac -63788e9855b6afc2c82634f567a86680aa18ea6ed5d353d52328866a3cebf535 -6379579dde36a9e9e83072dc2a8124725ad9c7cf7c2e7d8bf2f055227b2fc377 -638810e320645a4b122a38529d21b17d5dfee0d848f74364e385b77ba5082041 -63933de0b958970a41686349bd373c2214c77985cc163a4675149a96d6a72cf4 -63a33610279114945eed8812829ca6cab6a7cbb3206ff406c108acf1eb4f62d7 -63a4637201ef1537cea43329334d1ec99ceac7f50499d0d7b4a5a46b326ddd11 -63a53f1ae4f8df6cf37c5f818d7c489ffa6f96bbd0a8b10d72b4d1e63f4ae95b -63b003bc3d4bd9e04bdd0f9976a968fb85bb94fbbd9d56849318fd80f6e82446 -63b1593157b285729de7a993a92a718ceaac40a074a2e1dc6f78fd139f7c4454 -63b491dcd9e2d53dd4f705fc8c8306bb6acb7d3a7d2a415c7ccb26bd7fd1e9bf -63b7a1cd3b2fbe368079ba7c59b07b472480d63674125a99566990108081fd83 -63b99dd03cd12474d25e00691f86a4f0b8b248016708b275efe3b51b6f8614c4 -63bdc99a95bd245ad234319586d2e37d1e53ec0a44a1c1815b04edd0302a1e71 -63c3a1a5b21aab7f375a31f399cb48e0195f8bc448388bdefed080bb452e4719 -63cb8dae41419d66f2ff49b6f270ad54d9f744cecf64a8ab54c092e7f751894a -63cc696b42e349089c801e1637b5428e2dee4fd72b4a4472d6f034daca8646b2 -63cc8c1da80b260cac3e458ff697598c528274407f57be3488dccc4adb7915da -63ccb12f426dea0ab9ce956a33e4607ba5070ffcbb25dac78154a11cb2b4274d -63ccf35d4727875e8531da4ee70ffb61f553ac7baf9ef7e66783b43f954c9fd4 -63cf4939ec482298f28d9184534b088b5ff2ad0e92b129044198a7818f8a5da3 -63d2a97bbcae4142e00a74b597b35e6c4f97c567627aaabae92603096290df76 -63e1f141caee64aba1f44b67bc8f2163da987a42d3d3154fdd23cf2b2e381031 -63e37576198bc57346f99f08c18beb6f81cedaaa6ff586b80e73388919e9e56f -63e7aeac07992a5511ba486fc076791f82bec3fafed8964943cbfaa54427f8c6 -63ea1c3ea2d91a77ed50414a277f2b142eefee3f9ccc88e3b6a5a0929039084d -63f1c63e19743b1aa934b742e00051ea0db3dc8e8b14a55cb4fc96f01c7d2342 -63f467fa1c47b0e5d64cd4a7eea7ec7880ee71e14addbe653167000c7fe6dac6 -63fcd7953d9765cc0be4e9b396ad6656a5824e9113d0f78247d2badbcb1fc26f -6404229eff7038876da3297e086f4536af09fa765abccbbc9e410b3f2ae38601 -64099770ee978e8c7144a1c66720e7cc00b5ab8dcd0877f74032380ab3a3f579 -6412876077f8e116e4ef8dc3b1beb00a86b8a190903bc36eb06a8b020956026a -64168d8e01c860b0d3364baaef94531f70bee1604ca584a492204f67e4f4e899 -64168f371fe25ef41eb78e6918e359868d7820e534f8dfb758068266920a1bbe -641760448a164c0e08cca388909b0286235b8d43ad4936928e62b443287e0fcb -641baab59d653a2ae7e92b7540d5d4340fa155cb39b70de210f00613e849ad8e -641dc4719e9d780e789c7310c8393d126785eec153635477fbe5c086b2fdfe54 -641f9ef2f766eda896c34cdecb3099e1e8da556032b1e934d1394c85fd9adbe3 -642f62c4519fe912e8bdd25cdcd3fec2b69b6c7d9f587dd35d18fa3b9abef1d3 -6436d4cbfa38722dcd529b4dafba45031681e5ceff718b1c744d9ba04127e48f -643bb66d5afa803ad22f9c62bd56a0c8a9c4ec66957972d1e148be687fda1899 -643e83a477180db5d234f457ad9fe39bb69a57aa9f0461a299507edc4ba8b690 -645e376b86854a68ac436b4bf1273dfec2366088f0ebf5a5348a39fce081a7e1 -6467ff64baf6506f0dad2e1047ae0dc0b87bc9eaeb284e165a417c5795fc45dc -647159bec5a04e358bbda77ba669d4dc9ed0e5ee908e1d8f6f111bea31de9360 -6474e5300f5c2adec0da6ef005a3512c6911773691ee99a6c00a50c2af796ee8 -64760ca5e092f1610e745e8a74a2902fb8669d24d49b4efa72ded358c6f9d0c6 -64801364f85b9c722f91d3b0b36ecf6322bbfea28908cd61cca4fc47299efef0 -64809cf8b782529bac4855853fb252de53887ec6c60d3ab4b75b087f9d359a50 -6481ef0dd259bc19fff92d128d1ed265b082e2d0bc15a8b09a50a3cc3b00176f -6490019755e03220857d952145903028ee16b3549893261a1ee8bda19fc72700 -649838b3cc9a2a30522ea0fad5e85bf6cca26193fc01a98208adf61c888927f7 -64a78c2613052836571480cc5bae8630ab153a799f8764964152f4bb5582701b -64a83114e221743fada9f2b2a114555410c01a018a2ca1ae036e225e3667a104 -64b510f92a7272fab363ee33467dcc8207dfa30f37437c4c46eebc8f4347c6ab -64c2d42a1b2b64407dbeef952e1c38c474316a9d51ebc69d3341d94ee63fd383 -64c3250a7073b34eb39e7661096dd266bab69718caec0ec71efd0a9e221a69b5 -64c83802f593501b1f546c0cc571d66d227c57837b80beb6e0eee7a9fdf2e036 -64dff90f0725111a0d89df1e66684e8b4c85f2925db5f17a613e5b5feb3c91f2 -64e667165ea9d802f3af3b4d1f29ae246a04e2e10e1179d434040bc961259b98 -64f0fbe05bc1a632807e12239fa9fe0ff18557511d214f973d4849f24f3ef66c -64f59959ad8e00ff52145ed374c7122719d89ea24335a439be0000e086d055a6 -64f7556be5c3ca9fa3a240e232141775e20cacd8d851ab48f30dff988d596796 -64f7b134ffe44b0923e9326cba83bdd7d018bde570a5118d1b8b3e29416e3088 -650189a88a226369c79c5b6d4d7432ff2694cbb6e3f960b534102e25744e33bc -65103c50d06091ddd114df913e5f74665d27df5404c07d4ae61db69097b79e16 -65490dc07994d27ce860401a1078a82a022d54229516a62d27ce0638fd15049a -6551f57a8fe2858db85d7d8aab1649af9f8c16661264716b7c223c423185592d -655659a8d484eb5514b0ad8033fe4814a1a2f0f8ff4a18c09cef8ce4aa9f4a65 -656507b85bc044dd26fb05fffa758ebcfc7ce298147fd5717b6b149ed6df9f6e -6576c09dbb9ab58291cf9bbce71c1ac8777e80507a5920aabfafe7defdd7ce40 -6576ffbc65ee0050214e7d3f78320ba9f57deaa903ed74ab0e542e85f06c867a -657983d2cf53d0ea72da20b0fce1952094b1ac499b1ad602b7144eb5384680ae -657af8228d3fd85fc11d3ad4e8de97101667feeab3b20a715ed4010a0d6c391c -65806142101ebc815697a0151424f0abfdbb86c518092c6f4bd0b96773dec843 -658d71c536bb5d29b7a2d4d25cc460a0c8513ad3776e4bd838d77e5b93e46e97 -65a042962edb8fbb4b45e3118dd190050d0f9096efed6d527db5c64b6a560141 -65a088295cefb58a4d356930b569f14ac6619ae21d5862ca6afe6c9b0e58111b -65a2ca5ee3bfc23d51ff8cafc6fc826d0ce666bac12499d193e74eb914a479c6 -65a71505d15fc03a8fbcc5fd0eeb185bbc5785c190683d2263faa9ccd59a35cd -65ae19211f09bd8e149f6f5abb5a0384707dd7b9f4bf3e66cee5afafc06253bc -65ae9a175db0affbe91d7212879bf9eca185c4dd971ba5273224b60af383e21c -65b63836dde98ad89ae08708f159cda54c31971f57ec0393e03e5e406683a9e6 -65b7324e1d4f28b13fddec2415826de701f2416b601eae94fcef95a9374351a5 -65bf5a80291bea917e08de59e336d3555421fa9730b8b10aefc52a518d22436d -65c025e45012a39cbd0e00fb7cd0700825122e9bdb167dd6596108e87cde583b -65c514dd8bed5ba8c5d0b70ecde2ba60f8a990ce6a603cf145058cddef58745b -65cba998ed1aa74162c3468e5026b65354ee62cc08071b13e2cc38806d2e87d8 -65d6e654b58ded84cefe7becb8e8f97e400f8bfad912935da74275937233b8e9 -65e0dc6b3529f625cb3ea56a36ee3c13fd8e298ea8634246bf4bba6c95e66507 -65e93457412bc4c835ed7f0e22156f84e8848bb78c4e53c3b3fb626d53010ce7 -65fb3fdf52ba136d42f14cb8596a6f9ce8db8490a9f28553b982a8717a9871aa -6606256a57e480d72645e8f66058252b9c4389e6d80a1780f376e3fef789467c -660933d3779b1724cd1bb57b57647433048046b09b7072d36f84b1d98c63172e -661b6a2b10ae0ee10eff56bbbd99f1e34c5168affe74b00789e6b07fc2286288 -663055e6f7db4f3d82b038849b63a1efb3920526db9391808246aacd85c94138 -663f2c6a97fa4715751441d59ad0198fd148c71370ea5d444da08cad0a76d623 -664323fd6f19f794ae8b5df8a9935ab1aae0e5025115b7ddabb68b74be9ac428 -6645c5fbcb88c3a475ff66909a999af650fb1cdee5284b4de6818a4a23d753ee -665014eab948f82f781bbdbd5725d3331e146244056dcd61fb3ae8c8cbfd270b -666670f7c8df4c3e28dd6aff0776b7df8e056f24b286e55b40c89556a6827b10 -666790d1682e32418b047abbc9cd27a4028ce697947b9d1ba865847e7712f477 -667eae83d8c101074419e7f198a9ee0c1629909d3ace4d71a1e00130444d7199 -667fa39c52d919e54fe3f74b6cf53d9a927976ee346636fe64d46a2a18d846b2 -668ff4924c7ec1934d95b2bf8216cfb5a4de274929866d2b22d965b74699512d -6690803681cad739a22e0b08eddd70c67367e145b7707f0b83955293e66889e9 -6698c2dff130a4bf316e24475e84ab03933ca7cfed95fc45fc90ee3641ccd11d -66a50d6f4b03dd197d5918125e8b08da11d9c34611a750255a06cc2350e1b1b4 -66aab03bcd8013f327e64d4fcc9e63b9143ce0e00c6c93b439a69272cd2232a3 -66b815f602c4f6705efe6b0a31acc95fb7f9fdd5ca8b9c6204d625a33f874d41 -66c3928dc11dcdac958ce5bd2533d44fd172e2ab25de55abe93dbbb0500ac298 -66c7dc83c1130215d62a960b3fe533a251e16addae63422793da944e33d90c58 -66cae1d4a583754bc1e4d9f257dcb2a2c8ed0044540f2b6c263d8dcbc33f2208 -66ce386b2d7f885e4a47278b74a64c874241f4cca9cf659aef85d180ab85aa8c -66cf71225f9af5d2d02883b5145b186ddab65c3e524e79f897e5efe5e592e89d -66d2e4f1c355379b01c12243d459999a59c05f6a8e4fa7b73b4efb4766fb0665 -66e9394c1c192ce5375b70fe37c628b4195ae933cea1252408b0339a77c51eb8 -66ea473fc38a87d503aefd9166a497395164a66cfca75549eb97bd4c2568d878 -66fdec063945c3cde3c20a0522e5ecc794a80641d8b788de63ba917bfc1936f0 -67016d76edfd9de94807cd91449db1d6ba295b7e0927370d3cfcdf4064fd4dd4 -67044defb061874ceeca24c6147e168f359fab873e48322876d9b65a4a3b31e3 -6714f157c69e0249ad777f4f811aaf7a4e77367f80a236f081c65af36ebff70e -6718969d8372d90394ee2f69d816f9cfc0e6acaa07d00f5683c73fea9f2321ce -671a208481fb7aa0bcd3ed64ef9b83a11002ee78ebea2be04b31dc241aaf9db9 -671a7dda336035bcca4fc9a64bcc1b738da3c605ae3034bd3424bdafea2fc6fd -671ad1d59ca86dda49b80adb02bfb5f199b23b2880845a0676d3fa16100e2a93 -671ad9b7306c4e563be231182dab3ef33e615dcda0461bd467b345fcb5c1293f -672924bdad8ad188dcc6c5bf45e347ee06416f5a684fcf057a5bd69bb2283fdb -672992b067ba38f2e353e67382104ce8822ff38f6093087c8a17666a33899d94 -67359ee3e0d044a109e3795fb1951b1dbaf33dc1f7be14ccb37772d482f890e1 -67378c81eb74f8f0d3f26b04e739e836286249171e7cd0ee40bdc4cac02e2ef8 -6753ce679e6f3becdbf040f3563b91c5c9e6d1817e914a8b5a0bc11d497319b7 -675acc8fe7cb03f17623c400bca1d9611b7b4dfaddd19430b04631569b02c25c -676df5b5f705dbc555add00b0b994bb8fc5526e247f481d5e18c93f911267781 -6778b4a61835b589f84931b41969ab862972d255485e7071be5f7ba5bf6ed178 -677942a0f7484d97a03a69d2bc7f342e6537c5352b0577d6e90ed5b03876ceb9 -677c55305584698b8cd87e039096b3ec3a12b355cf7346c93592316ae1ddb5e7 -67856d2c476840a3d20cdf76da0c6a1a8f505a57ee5b363848cdddfa3b4aec36 -678a47c916f731c0ccd3fbf39bb7f15572bd7877ca40a794bf1c62626e4228e7 -67a7610a385e5f81aad376c2ea4f0d8d648e5ea5457df93d7568dba719e97cb0 -67bda5c528c0815889ccecf534b436ea2e807083eb0353a47d7bf428ba620dc9 -67c624d5952d551775bf96f2b7e901f104f0171213622ee32dee266d480003fa -67c9a0d985a732e27fb900743f4eed1d33e947e47ad662c8a1d286e7f23df451 -67cbfd923aa4b270547b5f22079f83d9aa1de6243af62ec393f8c1b4bfcff4db -67cdbefb06f967e79a52a54ef71f21cebd86c545934bea0f60a265bab3d712f2 -67cfd8b12b1759a3e0f6991ed77ee7d9e90553ba93677e8074d91ab8c6330b0f -67d8ae12cca3096c3550846b65d0e2dd0fae9b25a3556793473fff5d88bca1fa -67de7d73fdc7e6c70196e781662657368c215861f8cd9c61eb6e67f5ba72066a -67e2fecfac3b9d6ff16189f6f5c8208844113c8db687270b27c2f608d48d8215 -67eb40c3fa680ff77078e0c15f78c64722d3202f82be03e86a6420962bd9161f -67f90a72e92b40cde583b8da66bdbad3f4b7c9ae086a98af179fdfe51879fab6 -67fb467051b9e4ae75635a64755a1247991877db2033231cb8f239d3d946ba1c -67fc4cf8845e1d7a1c1a47bd20389a1c89e17508298025eb200b40a179937ffd -680273f23ab8e2ec4a80765161750bc9fefd11c33205ace9b9790fc2aad36003 -681c06c74f1ff57e893c98bda0c0bde0722cf7d053d799273da5c58c632aca7f -681e5744a8544b3e7773aa856720c5d71e1d56b70997a6147d253056cd988a32 -6827a447d85df0c17e9b300ac8bc1f638770e840dad76a28fc7154351dfd5e30 -6838c4a41ea2d720ac53ec8b1e671bb8436e6058d503c7d08dc1441efcae1f32 -683ad98ee5754339f2a74ea8c334e5f98a0ca0c42d67c3c23699a8ceb427d060 -6842e0f715e4e9371c85b5391e3083f2aceab84c59b58050e940de64b887b44f -68551c3f040f2ad1616ae3303fb71e189658d1e3b91759537c015e066e40b647 -6855a94657409ef3f3b4b4e0f04d4ac352ca0407bb1bcc91242fdcf4ab65cfbe -6859df0ceff12c394a8e813e3426c8d21ab19d477964ac23e58119c0272b7026 -685b52d6f0651851588d293b07b0d85c8d160e8f0bbbc4a834b645556871a452 -686309405a0d7a28f37fbe9cb7c6a69bdf034c0284bd222ed283c49a9902380e -68648e3baf5b72d50cd27a61ff929688c046b9077eac0c3a3ef6da14d7df3f7e -686a54852b3a61c1fdb98d01a6f1d8e6a3d7d9ed25e9ddb11a70d83baa9de5d9 -68701ddc30a693b2851167288afa219e976452e963605feda5dc9a5323a575cb -687c57645a001c5727db4a23d62e97e2f934be7edfe40d42ef18d12554e899eb -687e3b0b9ee9bf527c4fa0c10f2cd7716c008cd0a2df2ef338592660ef0b06e6 -68842e1347340ca0ac5bb31f68130f18cc6b98f22e8d914cc2a65cc6d2d4910a -68865659da1218916ea016a0da622e562993045952cc89fc2461909b93c3b54c -6888b8d2f0d3e736b5fefe03401eccaa0a5bbd7bbee1e70f95064e68b104aaad -688908ea48e0f375abda62175b58c2e4eb12ad304df4fb690f6d03daa83ab2d6 -6895e44ec80f0e75b550fc0f38006579beff6a73fad00746a6181b0ac856e434 -689b4a18ea75ce12b0b24b2d237bfd2f17f8fc3162c1706fe5e0c24dd706e60e -68a8f97f5768bfe9da244306857a3e76df6e3342d2815de58a4658cc95de7846 -68b574da1729da3d57315bd45e8002617f3fde2ee0e0b158dfd4d85e3e9d0bab -68ba360152ece847b9a880a3d3f837c8a075b8588de2b9faf58f2eb64307e5f7 -68bd996b833292645361f9c3459700d7111bb3fa762ed7a2709a3aa7f1923e00 -68c32dd952dbdc9be940df63411409e2d7be2413939f111713749307238db2aa -68c6a84fe05b013841b99f9a565ae389473c8a784e1e2c1a98bf3de6e74baf5b -68ccae3307f1ff17e5feaece74b24ca2c55bff3116eabe09cde111148739f3e3 -68ceb211b8ab7f0eb24067819f8d0579f44e77ba5db8a8c4c55366b7d2949b5d -68d843c105ef65da95377a992def9cdb2a200370ac8ab6655faccd8e02302144 -68d994d4301e1d1d0ff851a40d6abdd51f91faff26c167b780e22f8eecf0e805 -68db58ea0798f61329c98594433f7e150ea28e04fa9cab90072f905cc76f1274 -68e0e25d5d77c7e8b74175533651648eb886789ce3ebdab10d2ece58f4e2612c -68eb67827d441d13be5811676224b8f92ab6461b16e9511d4924754f91ba8366 -68f39a5e2d19cace699b78f5155ee54e6a848c5340deb06943a32c8c3ae28702 -68f6886d1f172bd68333e2862652483ef3c555bdb5e2ec9fc0885145f38f83f0 -68fa2c23683c3db750552e7fa7384d812b957b3817031cdd616546a0917c2525 -68ff05947974193ea80fb3b08085ee9e59b22603b4e36eff64f7958e4d1441ad -6900f21cfa685153546737ce9ff65a6dea704c839a34fb5bd03200180ea50e6a -69064af91baad4eeaa5299a7bdb246a0269442800d9e5a874caff9b0bcd5b71a -690f4ce570b483534899aa092a6c7a13967db69540cab93e443089e3735f69a4 -691ec2cdf2a079c35059112b674d33307ee4261f1471268e709162e7cd5797a5 -6920b1c0fd0c1f2235ff56a57feaad3c89449838fabbc1b2ad8288a10cd000fc -6929707b6bc749b5fb0c8cd439c2822aca8ba68b0b179203ac2dcac5e48758f2 -693028a8f5caf65b12d5756e05501fe834b04c7270a9ed9b519cfebf4f0b8e20 -6939737ce97e4a9da9d23f0d674b1941d2fff940d6b5b73530cd6d082b395842 -69427c9b239caddd6bd2ccbf74a666f029fdcbb120adffcdc6fd5b6f82043e30 -694ecd5e438e37b7d565df1bf48f3790a13fd97fa6f1449a63df00fc13fb2149 -6958161bd18e2abe5113cbf707ab77b8e7be5b42bc69d91a38498e4853de00c5 -695a24ad34fbddbc4b51cd02cff6a67dfbbdf2069b6623d65a98429b16510b02 -6962e65b6f0f012d3925eb3da6338c56ef83c1c765379717470baaad5f994d58 -696bf32f0caeb5dfe4aa834083648bfbf5e64505b32c7f453e2bb9a2b8ad3d26 -6976e39de1f4ae91a60d48b70e5632f4c7105900d62e0f3fd0de2a97175e9a41 -697b82e618cc88b927c1aa277d872d5b29643da1294b8cd4a665c255380de21c -697cb515c2dda0ef4c08db4788d9c41198c24e10b3e997bc1abf4245f6f08887 -698983d5e3c02f79f26736975932707fa4e39f7172d5a4b4357f95317b0d6b03 -698dfa853f26a7fc20ad3235a660e6ac6d623d51d09814e04881563a743e71bf -698e3dc510ba7d9350b39898a33cca2f84265600e524ebbb627314ddf4691ced -698e65f8b7642f3d8d2d5aee968ef778cb2a4ea15526a07b6a4699ee33995f6e -6990dc2e259e597ceda47bd5dace00ce7764d475873bb56fc280c78159a9f87c -6995a324b129556f467a7f5b807bdf3bb4f4474d0682ba85e170375d9b7e3d36 -699cd5dead6c911ee72f230dd780b6a9de6ef7f2beeea05617cbe2b0adcc097d -69a8dab85426f2b9655203280b943e229fe2afc467f54288643c8e29ca5b7c15 -69b34b030c139259eeb8fbf87d33e1e856aba87ca0891b83385f3d535426c454 -69b39824874d5165a634011f31b600e719c6fe1ec9860c76f476682d60226494 -69d1ed2ea39f7b91e8d91626b63f934e7d283b699512c603180df33e4da62ac0 -69d3a9de44a4a017d9263c287d6f4129efbfe2766aa2e89e5db1e18e70057186 -69dd335cb3faae89a5b92a0dcde5ff4b1788b956e41e3a35525475a4bfe878e4 -69e0819bfdc35523215a9bcc4618fd19c23e24dfe11fd245134a43fd1836eff7 -69e3a95d4aaef5668285d820501ccff978cc972317e727cbe0478bfb987c34f2 -69e46dfeab1957c91db823e2cf939fd8985a95c1542ede099a1dfc2e0ef58fe7 -69e54c4b0b36656385e9047c05cf2cfa9e7625b6ffed6b451f5383a401ad6fa3 -69e8c1ed92522a3a5d2e22c593601583dff53b100079a68ec1bfcb91ee14d291 -69ec2165aa653c2d0e333a7b535abaf1c82a395562141ea0e3c360069c53ed28 -69f20ccd65c28abb6894a3c9fb4937104cf9c2f4df561e0f98b5c6e67ee303b6 -69f344721d527f213183e97c3828461c6b6c748b8f02ecd6c5e49b7db25180ef -69f92da7148037ec2a81e001c27242f3b3bd5a7f92a2f06c7cb7addfae3af1fb -69faad1449307c380add928aa0faddd86d8e4b49ebbc0eac2868b14fcdc8bdd8 -69fb875aaec7d2a3187e7e7ad41698e5853dcbf185bed85bc9b0878e2dae60e4 -6a03de07583363421541c72f26982d51003a77833120b9e7cad1fe188617ad8c -6a05f79655dd506747c460742de4274733d5fd1beaab503262e1e0a178dab5cb -6a0fa6d73b799df26e0bd2d1b871b60d73b1ec5e04d95904239498ad08afbf1a -6a15194b0396d92e8e2ae0791f8c4c783f01dc5c320a75e87007be34b5362d9d -6a23b9711a5563d971bcadbcba49c1a34087653f6114daf80e2309bdd058a3ce -6a361c487cab49cee4446d5fcca5fd0fb792241f25492b4ed1c53a1158daaa64 -6a37b90dcfb4aa347de061e7ad879d4911c540f989a77b0ed6cd05927c12bde1 -6a3b4e80ff50107312a0d6870790e1fba29b3f538bdb0714c37f376b4cd9ac2d -6a404fa96940245ff0345c853e8361568b2fcf8451537083caded7627a64175f -6a46e6d76386faa5d5c77e4ff6eb5468e4df7e4116298ab711010ccd37a1d261 -6a56cbeff1347aaa7e277c1f72739aab2f60f531da129304f78db5885dc56f25 -6a5aac75853522c81bfab8346a86aa43769002152256360b9b4e2c1172375124 -6a5e0aa012e338e595a05b686779aa4b113d36f1368f7c131a55d2a7fddde1bd -6a5efe5fd852dfe80fcab0d69153e8270c12c00cf93d6cbf2ffbe173a12835ee -6a651078a9d4e5773fd4a89b0e0110cd1b81e1aef539ef1a03229ad0d7464575 -6a6ce58276d00c34feb3db6bb384d14b9d2135501d1bd004e2bc0a7408e3f269 -6a6e1a3223973ed4300136f566395eda1782ffa7121264e6cc768821f9c00c6a -6a71b9e61f94227cd35117b51ab25a2ce3dde45ae131735b0e65779819e455e3 -6a8f18eb35455d35659f50f33e92df51288e71ccfdc32a8592e08d6d03bc5a2f -6a92487fd86b9eabd4cded9afe4b8343c23e62ab1a7dbeccc4a5bbe03ab26439 -6a98cabeff53565e34f88528454d4947ab75ef63ae3f62f65a2d20e49af8d0b0 -6a9ec4184d558120a80a05b96a1886f1bc992eb4775be263cfb1e9d77c71a0a5 -6aa696c970e2b7518f0e7f95cadcbd58bdb6c7de2db42e39e3650f50b073c189 -6aa9b747ccf024b0dd7a7a75cf16b639961d066952160b1f2afa571b615d0753 -6ac025d36495f29a0476f74dc449bd84619a01f1ab843d4746d8df45687e7a48 -6ac5abd74897e3f6a54afde53b319ab548046f0241305dff0e33f031ab36e350 -6acce706704a0b417da0b7190d6d0e8ae94c008c152710180ee3ea5af7527ee1 -6ace3b7a39912e63a3eda1d82fe3e6aad380c3e7949f45ef9416db43f8f87030 -6ae130505b0ae00f5a084fde89420ffe54fc63d1973559cabc49cb9cdf08f5d4 -6ae560e9b1a7f2daf8fea6ba22444df397934e7232057ab2338a404c0ead0704 -6aeedd07c760e8a2f2cc46751d57bd473137dc9d287ab3848f98cd160775aa09 -6af0205947aa91be146b639023fcddce33d176915753285adf81cacb0f83eddd -6af5d1d8c74bd5e2f2929c083135d95aa98eb18df5e32e9e334c4db95522151f -6b02fac902adceb3db594a4b8bba8687498d7da1d506e265c36bc6fe3348d4e2 -6b2a6bbb9a2adbc48df09312793ecdd9b888f65abd949963abb989d2f7a48316 -6b2a852953bce89207449ac2687132aba27d9e99bd1a7db8d8c43d15bd3893b4 -6b316ca55a665de4dcd497306c8a3a33d2f210f898f3d4efc3cd7f75d7e5f5d6 -6b3a5f230c31ecd47b3ada227f2d82c2faf19ccc906188d8ae7256d445ecb6f4 -6b499442e2bda2f344993d96780eba25cec4fffa1da0d3d75ecd29a0d37b35bb -6b6116b8d308f88d7d59424ad3267ea51bf4a811261b46496e868a7121460eec -6b61fd07a9edec4bce38a70c9d7543ba304f434aeb2bd9509af84b62f08bd6fb -6b662320b5b41f7e024a3dfae6a9aa4c3244b728879ef225268727ada1b42c39 -6b6d101e6e3c63429edf76bc9a0dbb428c436182eb32082f30806e0ad98b87c4 -6b6e5b66e0c151be4799c823f48bcd4adb397119235a4c2cab3fae2cbe5195ff -6b76aff49934fa5612f0257ff7b38b21d6582eed56a430e334fab17b96009474 -6b788b1696e70716863e8993d823039ce2d8cbd51a1eecc00b0dbdc5e202ae3d -6b80bef5175aec05865b51a73dad45b0a29ef7a3f2f8f0efbeec415e36b5c355 -6b88b52c9056c60c3113c26e511ead6551112e32b29d4d74844c4906cc0b0194 -6b8ad534948b842c4da8a358119cdf62bbdb04bfbef8e08f3b0047a8bbb05dd9 -6b8bd2138154a2a31b9cb9714e8f229a0e733464ddef92e1b651019bfee215d4 -6b981a1f988ae1a465edd01bef651952a84c7ed80ace8026524fb9bec29d853a -6ba4bbaa3d373c808ed06b387462b96d9e2365a9213dcf63aaaabaebc0ad1635 -6bb2782405a47c39e78857622e0a8830d9ab603ff356dc1d70eed231caae4dea -6bb3384e3447a62eec787e202fe6a34273a210b4593704ce5ecc638325390a9c -6bba100f21226964f7d3d69aaa8f61cf4cfc2b6ee1d218826656687d0f583e51 -6bc27efcfee892c6c20d0172760d8a550eb29d708f84b09d2f5ad2e3f87292f6 -6bc2d613bf8819c5fc154b048a0a613fd0f5f5986f2acc7a0adff0e591ff43e4 -6bc78c71a31ed6b344f155324abb2c304c06b41687e0f3e5fa1c97d5b0a772f7 -6bc8b1e36dbd4c184d435af19aacb2850d6242482ea0a29a2ed42021d19d6a05 -6bcae83dca1e521a0ffdaf7f0eafe04f54f964fa6c8452cc7e6278aec2201a70 -6bd933b3fffe4d51bc9dd08ad10a3f31b3583b01257b3bbb7f95f86a37f21bb8 -6be95b5ae404cc430cff337fc143eb3a6aaba9b26d3933e0334a5525752a3c7a -6beae6f227b0025e977b4f09f122a13e51ea0c456c7402743753d5f2dd1520e0 -6bf71d222d08ab9057bc4487d4477aa465e85ddc86ca54f43415ac670fd58a07 -6bf7a5cfa13f895f75f45d9467bde92e7a8f12e83990d730af2afa5df7fbefd6 -6c01f5bb8c40256f3f53c207e3877edec8f0238415b1863b7ed704b46c797c4a -6c031d712ce4ab8fd994226f26bb7ffa52ce34ef32035151422461883ac0529b -6c05b10396460fe7d94751ddf463077899230e5106ce131e9bf1dfef6a707188 -6c0a5e3071a34609fa58ce6ee1f06cde84873822df6ef76366592d8b0076fb35 -6c22f1c053314ac47120ee3217d637402cf0149d79863f914eb930a8c05302f4 -6c32905239920fc1bd9dd70c01462ed976faada718169ef28854346b00e9e6fb -6c38a67dc99914be9827558e03be25a4cea308db038621a26bcf113443b31559 -6c39431c5381d577172a7aa6de06d60375d3bf10f1ba9aec7bc0cc7bc1b19a46 -6c39c5e1b1b021a5e8a9378d628183f0f66128ec38fc740f01f2b03b95ac07fd -6c3a64cabd5a3f0cfc1002d2e615ca1412792a79c2313cbf484a81f85efff24f -6c3ec0ae81285b6f344c1f67eb9585d1252f349d64068e82ed482f5bb16f92eb -6c3f37b99d7d3d0b0ebbba6fd5b82aa895124658a62a698f545a26b5d7c6f222 -6c46892024f6b73ec335749f83835a63a5b708275ab9d32f3b6c9087919da56b -6c5cd60e37f18e4499b6346351dfd4f0c72316d5b8160cbdbe6b0c2eee96ff30 -6c5fac9d91d9c8036ce04e07a74042dedb52bed534facfad3247185b314340eb -6c76bf8de298411cfc63c0bc936cd4df38d5ecbb39eb91c9d8b38b6fb448c52a -6c8ff32c42cf5a4f96cfd7257222d6c5f0093d30b1eeff0dfa072a2cdec261b8 -6ca2a2172fa9263668c58f80a4ebcbb6ad3e9af9e70895162d92feee6352d98f -6ca9c928a8cdca347833e29667981c99b2ba87a46fbaa92e555f89a7407409ad -6caf01c70f96e0f295c754bbb87011de732a42721eda1d95f61ee26c16ac4807 -6cb383f7877b15c66b3a41468e1cd22810f66b08f967b36d04029af987577b05 -6cbddc8a93844ef341ca3f5cd70f6999c0ab5f64ea4c2254fdd5cc6947e66f46 -6cd0f0b8c421b5c0318efcb5e68194fed35f5b17f71b443b530eae444d196e68 -6ce40d768a9c1bc8094697274a25ee109b54589de2f96f659eae6d6b1ca3290d -6ce52196786150bfd428a3409c0c2589263d3d21e38b8ba5c11c3af452fb8308 -6ce5282b086acbdf1429c2e888a1e9f9e218fdcf9c96f8066add5fef37d665e0 -6cf06d6405edd049b83fd4f2047b6123466bac304add6c1a2ac1d70f3aa375ae -6cfd0b231ef966cc4139ccd420df8c78f64d19451f2ffa11b7d699ca52d6795d -6d025eabc9a499b085cb433bb89e3978548b0d3e2af1d84bb95950e8d68198f3 -6d05238e22289c9cca433130f6a4e37c4a835ca18c90ce74f47718ac11e02046 -6d0e4f3578a836faa0004f22543059bb96bf98640de5b60b682c4bbe088edeff -6d1059aaa574025baff8b9674702cbb0436a657671a1141b57427899bc45edd7 -6d36dfab38e13285e23d9df40723cb3a3e6a7225b8817eed84cbdb8f05cb367f -6d40cc589bd942aa4f29e61cdf7bb44e47328d62f289a009cd1915e1fe213ab4 -6d456b6f4b73b5e19964bc29287bedea2af1d91af646fe0f34fdc7d5b692c1c1 -6d482f902dc2a906d3327db35537e6ae3c8f0f9b080224c85a566d42a1e7bfc3 -6d52d4805e0cd6dcfc443cb91d1df4368a8ec0a593232729498c643cbc2d08d8 -6d5be59b35d629bf4562febf1c9425fc0855708ba4f4fb7fb96269d58ae67643 -6d5c69f358a95d2855dc722ec0c54042e10130ad2de498cf355610e6345db805 -6d5cfa35db2339724b894df53c83a93c9cb30a15b1a0f1cd1b14bc15d5ddbbce -6d5e47cddfc408af56ba1f4dcefe3088ff03dc750fd570956f995442741c05fc -6d6470236a3c7d20285129f5d5bfc88bdae8fbcbda7a9f7552767065077fbf15 -6d6ac9a05c42e31b007914441ec18aa638e6e8d5a2c3bf74d46ed4ebd8047092 -6d7a36a476c1d8a125dda7efe018b487108ce18901f0c3cfa2c3c98e8697df54 -6d7a6401e84de777bf2c099ea404858cca655b5643822ad455988b923941c26f -6d7da165c2792967f3fd60c101ff6c26b3f06c11cf1f02349afabea9703bd7b5 -6d8588af16983b078f9f17ac7f056452bd68d218545203668ca96ef1ddc7fe56 -6d974c0e20cd2f8bf361508a02f94f21db32c6263620b68e1325d4d99a064f4c -6d9849798cf05f2e13f2434baa6864cba95908fa8c586b303cf69bcf52ec996c -6da03409c657d5e0063b64849e616e69c11bc46974d255ad8ce6a1af546b6892 -6da5c3f6a1edf1076acbda13660e6ba97f1ad42197a7b12e4c519bbdf391f2a9 -6daf5c0b15634ce8756c8d66f4162556deee6ccc9cf3d5ad7f54eabef2f315c8 -6db21be3bb680d67413c8c2d6f0cd7fbe9444b7eb64f19c4d0d5ef5a6be1ca08 -6dcb005df37e7b0f450fe656aba45a92f99de5d77ce3d96850f25264bc8dbe01 -6dcd2cb8fb89118a804454f1b1b48a35bb2995af8605d3685fd1c9258e8f1e89 -6dcdf201b6f895a0ada9da5577127e726c64714eb3135cde083c93d201303c29 -6ddcd6f83a1fdcae6f66cdd616933b7d7e0a26b3d883a09db4ef600e7ce0d952 -6de35e65ce616cfbfb84bd6b91f43dca127701c91a7a4cab09e6bd9e53cb6646 -6ded36d301996d9accb2fc1b864148a428bc48c1ffd53f8477dde5c36903d8c2 -6dfc7f9c23f25d183243beeb0690fc2e1e6cb400d14fb11e6d785ca2ee354e79 -6dfec56e7aaefd80b1fd2dc621334ab9944ba38b7ae16b926f7244a579d5c96b -6e058d102bb752b018e15cb64b604436e84f176518f13f3be77302a10f80cb78 -6e08983983d992d8e89e435defcf1d17fec042594c62674f83a16102874c3f69 -6e0949cf67e40e3fdc7e9585f46c5a7cafb2fcfa14546abfab279df0230dc5e4 -6e1e4e8d01f2fcb896fef6bdb8aa40bd4f2b6de9c2aac845630fe5d5030cdc40 -6e23ecccf332f88bddbb4e83750bf8c28c90d2a4f73b978dc30cfe8f3af1a71f -6e2e112af09a198d82ca404478e185904dd9f90274ff4321edb0d894278935ba -6e47a56bd79d2fc63a6a02cc2f0e6a1835aa02fa1af97b959a4c73ad46bbb137 -6e496600e31c6eda3ef0415349169917cf596b748be2fba1403a8e6f13f4b3cd -6e4f2b4df712bb918e3753ec03a87716317eceb6b8f5603693d53c29de584f95 -6e57c8f620b9c2366353340db95015f613d151cf0b0e27ade9251ffc8279c928 -6e5c68fe951ad17da8bca9868ba86509ab159cf3fc7b36cb2a425dd2de81b1f6 -6e66b28a4abb279c5859cdd2dbd2e9b27d803f36690dc394e647beeba01131df -6e6a01dc3719da6bed956cabc99131c2702657bac7ddddec1d18acfc07059bec -6e73d68710a661aec9f8df3915e4c51b3313e5ee21478a4433e5909369f6d2bd -6e7c0f3649c9fb52580b39daaf318c3e6d747d8162e1591af08c0b267eb03486 -6e7cbabaadf558ee0f6bd14b596aff059e1a362f1f0e76cebfea4dab23e932ed -6e7e904af3ead70dc3fe2592e1ed387f06fc733a50b9825b800f7e748ef01872 -6e88d53a91f84db9af862dac2ccf473a3fde40173048fcbe1707e1d964395ec3 -6e8ce58b8599f409a121ac09d2a2bd0a4fafff8341fa7173ed9fed5216d0a296 -6e8ee5e047ad79edc2fe5864dd628687ab1790e5367e0f926138b1b30ade2445 -6e9967bd11a8e3fe7a2667394207a10c9689b9c3c3bfd92f2d0aa7ee5ab70a73 -6ea6b7084a73c2a63bd47f080c4a2970cfaf4a1740b666f25470cc3ca4785ca6 -6ea760278d11397f67113f775764a09b51be52e46e2ebf7844475b292bca7d56 -6eabe4b592a27ec599e06fa2bb7236573920fc607882733872bcfe3d6d30076f -6ead1a90da1d26560bf028665988b3772693c5b542ccb5ba85c9f0e0bb82369f -6eaf202b6388d2e4eb8efc913f798a780ffe38279aafff93ce58864c3fbf6e3b -6eb08b1da7258310b6e630c4432fbea422b863f0ee2b4dd88c02b29a7694652c -6eb23c2b56abe5493af35c0a7107a80f183ce7bf6789d751314e1f8d045f9ab3 -6eb4fa854f00108477c60d44c036a309aca5258b1d94f2548288eba0cdaf9594 -6eb5311ca42e97627da3079e0e01de23453b94920bed4055c8538a848c184910 -6ec4eb57986e44f71c8512e349b5d408078ea28bf059a2fb956566dce9376f8c -6ecc3091d198fd7c16cdd9cfc7f18921a50552c6749bb9311f08ea4ab563067d -6ed29f24cdd6113e07f1ed64c49558f2b1a9d6b934324f7b522c85f53fa40656 -6ee080d942299f59c4d20127fa7a5d08d28c1dee7ac57a6b5418f2d8f185c258 -6eeb63a333b86c375018a0873a4301ac6422e860887a96990f67d182686e6e02 -6eef11330d97c08d4e4c2969fd7cd86535139090684434f29411778b9b685f2e -6ef15a22a5d1b9a6a807d829cd8d04439ad3eb1cfc8bf742e22bb637ed7c2665 -6efaa1215360fda3e6b13232b469310d9b41a06581b3d3eb0e57f897411f6681 -6efb67032aa7fbb50f7ce3b54a5fbf930ac546f2195f01530f01fc40d70902c6 -6efb74bc76f74afd16971103a194c1867385be5b3b92f294e72d208d55a2113d -6f05733b6aa815f0fc73afcad6c34d6c9add7a81ed4656123aef03984e9fcdd2 -6f07778a8b087d6ca69dc47b3bce6cbf70affb974faafd7c44066c290172219b -6f09cafb873f7299231ba9494c32ae9b5ffc8616039ce2de8c9dadf52e5038c3 -6f0d65ce516c55176a6c7af46480d7b4ecb5e32fb2a2e7011829a15f75852ef3 -6f201745713a581502fb14b8757cd1230065713d65da0a052f868abf5dc307e3 -6f2eb1820aea77e81a06233f3ae5aa9ab4994881732b6720f67144777b4f71d5 -6f3617b21df86b5f4eb3b01ce72bda8a09b005d7615c4dae5635b4cd7378db24 -6f3fea44a04cc75620006e222361dc36360ad3ecb11c5f7c6c39447c0af2c9b9 -6f43d147145267e381eff3110353eaa955569476d4cef32b309d43890e7209e5 -6f4501105f8818e4d11288aead9614dec2861d68603463c3196fdd0a49679d02 -6f50f2f7cf42fb5b34bca004b80c19d85cbccf5db7e643e1e804f030bc7db4d3 -6f6b4bd8ca9c7e7a665d9e465596e7763db9f9261f5ca6ed8a38cf982c244edd -6f831bc07cca3b4926f15efc4addb64296df4097ab29c2a6b0382fa563dd9e21 -6f846674e8d602e524708a4121b2db0573a0e2f1237c33eaf778085a1e27bdaf -6f89bc9cc5c9360e1a83bda3430e4aa4ec30634ce9da0a2e4399bb68cceff20a -6f9e56d17acf84ba434b9860620ad29cece63669947771dc11bf912af95b48a1 -6faa2249290fedf5fc466be71717f665ca9241bb0cce72caad940e7aa3cca13c -6fadad6d3aef25981406769b6ca4da67ab85f991d0bb864e0be26aec5085feb3 -6fb29916a1232a9d30d53bdd0e5c145032208e080677c9895794cb2f26152c28 -6fc63ee38ec803aeb9e8a9287f4ccd6b2fc5cee6fd9ae64f7778653c028e23ac -6fe8dff952607fcd119291eb0b21fde436265f761b729da5c20fe0de238ff5c9 -6feb9c1c6b01263b22d3e7ff691bd21aa13e802beeea97dee3c28647b73ba4b4 -6ff0c24e665a6623a74ea854c8976d9001899ae258781d39ed7bb5c08c0e10e8 -6ff8660378b7a6012fbe6b62dc8f27ef38fca83d91512a7353eb4d7d76e6d099 -700178bdffc12fe9ee83f2094c58d6be40f1c353748a4661b0fe8f2a62e0f8c0 -70356d2a5f5e5e5f11e1c7d1fe820b6de04167918750df90e3a8d07d15ed9298 -703768dc8524b7c1b13017dc46a73c7f128b34c1ee6337985f4ba65960713cce -7043e8b26fe257d26f5409fe566a4fbf4470b36904e2877a2e72b91dd40cc4aa -7047d274601743db36626bed6bbdd703e66269667f1aa7eb78cc5a25e9924859 -7050f641d46740a3416b447e0e702c272f32a1eb24891bed55a78c75180ad0ee -70551e05b4acf2ce4f752dce75c496e3e4af264e28fd65d3c07737e8d930f476 -70584a68c07be86d0cecab6132d6a59c380d2ce117da13ed12c75321aab58cfe -705d9414f5f635ffe146d855f05807c35bc77422c3f2771369df670af3fc0a92 -70603d9a3cb3d7e222020580d6c75af19be0be65f4219c85491b8249cbf44adc -7065e81091ea86fbbd596a32c737cc6872c1b17c574420c58cf081b9e3979279 -706d260542e7682fc87f64561c92e43677418f7f7a303923f15b9e6e69623c13 -707bcf081484016f38c4c9282bbc35e6eed60912b1158c574185b575179799f9 -7080e415770879142efd8ca8456a04fd7c43b2f91444e556d9e3d9fa692496ce -70844b71201744750eab6894c38cdbfaa033c9a2ab6baf818a4f29e3db5a0569 -709568dfbe2ab93b47c16ccd43289e514954a11acb579b9c7219a5206b9e32ea -709e62141a4abf384b03045b4c636cebabf13bf4ff2b28fde3e06c62418043b9 -70aa7a960c496f7acf5d7285aa845bb0963f663465f76d0f42c1da85c5704111 -70abc377372f5e0346ee451d95d69b71db9f62afde8f647ce7dad658cc563126 -70b361283302d1c50f6cfbf3cf5f3fd0755ff1df11a3e812fd28bf728f293f6a -70b8d816bcf87edeb52dae41d1b871bad546f05a680f5489b1c78952fa8a7e0a -70c221ff62c7852566e7c9f4671a7c586cc7658e88de7e97e500ddefb6590207 -70c5665f6efbaf1d528b5bbc3a859a6d02648629cd56f27efae66919d9f31d36 -70ca90a694f55515b087667d16298bdb14536fc4a08236c902b0ad6858643812 -70ccec4edeb1003401f8ea034014678c1872b50524a8c15a36fb5e4259ab0f96 -70d10336907760230d34e147a00a66f77ef4d7cdab97c16e86e6daac9f9a76dc -70d1f0e45f6f2117fa0fae33769d4a0b1c0b580c170c3a764135b1e3af002786 -70da315d6d3dd1be85d534f72676805a9258603d05f12562226b0f4ab3a79284 -70db091f3ca1b2cd0c2d20c7c036c5e54f6cccfb20aeb681c42169af3999ba81 -7102e02fb92fc5955ae6067a613b4fd2b2a1b9c6176c257e49575bad45f0c7d4 -7105fe4644110cc9e0d59295673aaa7225348138307cd814209c3906f0a342dd -711e0666e99b19a4c96294415a840b971f9ea7a14ba990a03645537fa1d37252 -712c44661718dc597a4b32c2bde5de499874a7202d6ef82ba995da24dfd91881 -712e1fdf3a26ce0a381bf147eb918b22020d2cf6d22069714bdcc7e6571c64ba -7130e0b71594642ff221547702a5a9052ed5d8969ff657e4f107b0903f39f318 -71341bf98bfddf152ca6296929fcebd2df3009df7b3776d259091a8fd862bee9 -71365bfc9a65a581b9707540b3617ab7dd939546c878440b4c6d913f47d52a50 -713d0dc01c2c822d01142215e70b3b8a2a6ced72bcb96f2916a6059381354217 -714914352ea3909f6bff77c1c66170f5c39cd1ed8a56c85866eb03be3ec1a07a -7157724c1f9c3a42f41f85fd44842a4f8bc7d49097c99c42f13e8e4ee6e8219d -7158af073d512b102a0c230f64fb72f11d8dae127763c37539b0e1f8d08d95b6 -716e59aae1f58875312128f70f35c1fdeb9ad64043adef70d4531bd83d956273 -717aaaf275c2f00e54b6d701274ee63cfe38a0e93e908511f5053b76d02ec1e3 -7189b0c027f8febda24041185e288a3826fcfbc9ae9d0d6d9775fe00c2d20b55 -7193192ffd786d13f4b1af98b7b4f7d46a6b97fadb9ef56493377317ce57b6e8 -719c5f416aad5aa9f996f70ae01643d68bfb4b26e9253f80ec77da47a639dc66 -719cda0b5838f5b459e782981697cb4348e27840085a6e8f48f4ebbb8fdbb166 -719e766642d03d4b0b4146e8ea779394ff029fe34fb86e6b1a4374503c0e977e -71a31a0195e8fc1b54c103effc4bd3bb87e4bc36825fd707e63c7921a6ccd4fa -71a8be4c4f29cc5172cde3cdc32faee4d40afb265dfc6f03f882c7dc42d756b3 -71b4c857876bad0e308630fac800c31f11eefe922ed2460f0a1ed6b31f29074e -71b60f2e47e25172886b4a48a6b277baa215b5127920384e170aeca84bfa3499 -71b6ba2dd6ec028be5e24314feafd3215cbab5ed95f55f327b153181f216bfad -71badc4d61558b3b4a0cfd0fb5a7ca8bdeadef7c257212ea7598099c2d41292d -71bc66d87311f8fd18f75fa327c536263d42c8593b679b049f5afcfb5f57f380 -71be3e278b47755faff90a871e0e7980f012130608e0201c54668c75b1e3ac63 -71d00b26f98d7e3aa980ab577f8791545a34642e409ba36ace81569e8ae704b8 -71d8a3f48d73662d1a45f4d1b25265abc4614e424acb395c06424ca9fbd66a92 -71e30e8fdee6d7bca203d3761b84bbdac7f419cf2a664befe70782c152d7f033 -71e786afaaae83d3490e38bc1b0130c31ff3e21be9b6d352abb184c87327a310 -71e9e51f05185931cc8835478654376981b57307e0bbc101065d5a48ea7f3e8b -71f5c79d29f126a5bf4c9bbcc72ed81084abacd436dedfb12ffe747ad251909e -71f63ee06d1fb03f44d1a0baa9ceb59b3a2d7de5b449e1efaf8e323a7c15a295 -71f7766bbc5b55abd84100c314cba1513c66a07ac0499b05f4808df516fd4993 -71fcb2f5b6082de95fe250ad8d4ee7db9f5c24bbf19fa3031c21664803aa1bc5 -71fda2f63f1560267dec9a7fb856e407d68aec91138a1bbc9f026c4bc121ef50 -71fdb59b54c51ff6c6a80133fc6c50c4a3ba6675eeb95e3a8fac487488a53189 -7208809b357db5baef7ed8e8f60643b6c9a75f09977332192472a884545fefbd -7208dc776d43aed542fda6bfc718ef1fa46b2c4bd5107d3bbb12078b9c7c1f0a -720a41fee54b4fbc30036928a23307c3fcab8db5280782c9b46e1695da7754aa -720a6af7c3f111d4993ffdb1e8a188bf80bf4545070c6c0ee69d42749b011ed0 -72127b66c29d845cbd6ed8f9f099f42a7a8e5fc77d3b738f7e82e0802e4508db -72162a791bfb963133d4510f60d8d03d9eb93c9873a0def6bcc0a46eb1c00c56 -7219d0501b104b313528f05beac11baf24206f321c1ef7b42417421ac7d9adfa -721bf550cb69d22241351a03fa33690bbc04b91d8556bc3f30c741df1a2652e0 -721e3c630839803017bcb8375a1c79b36f5874c4e72d1fc262cff18cf174f70e -72354b88652e4f6a5b838cc3b75b9a4613239363efdc061abbcef67def686682 -72366d98ee392d507cfc2ab3c3cfeb6142599ed4bef7bd0e0791ddd6b487661b -7236e4ec5b81dad4a1e8494458cfdd00dcbefb9496ef0b4be90194032ce03582 -72486bf7b343dc8ce5bcbd1aff76db1c743023a829e832fcd75ef225a64b461b -7258ee6a213180cf912a34204d3371d0dbd164224ccb6195de1a3ca1f536ea22 -725b386c6429fbfba5d19037f583dd8f0d573a4dbd0c62dab52147b358069f10 -725e73093a82de4c580d6cee07e93af705a58fa66e195b429d758a58d46f68c2 -7261f80ede84be8a716b7dfe735cd9a0a8d5375d84f1ba3a35fed742fd168237 -7272d2e145dead2ffc755fc4679fb014f25b3c46c2a2b8f653a508fef806e1a0 -727461e76a1aa6274367c340fd8ebbd2ea08e9eecaa8ee7fad15da661b95a0bd -728015eb171689d8cab6a91e30c8577383960498a8738d62a117d9afab1cc4df -728138f2e719edc39daad0c75a56a0d3ad6508c8d5670efd87a704cf8b11b214 -7284b7f39dccb039181fb6d17415eaf00a627c219a5c952027329b4da12b29cc -7287892b76e48ac74d8965cb4aeb88831d802d9dd23d41c6dbeab70955672b34 -7289b5c62f50a4fd14715d7c290e62cc0355fff385f7fbc78e9b806815ac10aa -728d2b5f99ad1b649f102ef8b5089438a09ec261ded3340d968a06323a8c4f6d -72965e5d3cb228c9264cb69b2ef17575ac76c7b23df5a753198363e56a86a604 -729a5f2eed46cc929abfc25f7835422ea9169eadb3694c0510ad8fd8d0fc9304 -729b30d0033d00895e749977a7b1b76aa15c4ccdeef83b62f3792f14705377dc -72a1f93b78a47c81913e0d77b5497b7b7b6e06a40f231d18ed67e0a5ffcfcf3b -72af2cd73ff31ffa59b623322d7414969e7e5ca3bc4d8dd9b60a9e621214febb -72b492b110424e63fa71696121ad5593efa0eb3631c7766c7625395fb684a533 -72b7da18f2242c31a6110fd36c9194eb8a5df001eea53108d5721224e54f4afc -72b90086f95a200cfe83450e7223041bcff11d76ccc35ead582c3ab67d4e0834 -72ba52372b344c8bce29d25966b54f4e9576ae0ca084128d3090422aa6b76eb4 -72bedad8c1c89eebcdbc995ffaa2eab422d14e94722e318589e460e1a5351700 -72c6ae9b7a0e57a899875a328b0216fd24a0ab383697f07fd132439cbe53ed26 -72c6b70285a74e45b58e1f9926a5023985190e2c0a8abeef56ecafa440f78706 -72ceb3b1a161f5b3ebaf6692933cd6072b1e5bffbe686f7b854ebdc222d92314 -72cff9780fc45b0565080db857c9f70f717f071e1bd4e8f850e20960b81bcf90 -72daa3df7aec152467bc219a556503860e99c5207d3ed3a8973a48e6a43909f7 -72dbc1bc6a1af1ce97497862a714e1c1ad3723547bcd532902e6b258bee821e9 -72e1089e20763501ab01a9af24b2950ab2ce2424566366d94b967a5441bf5dd0 -72ee25f8e956e6e2f327704f30cda5ce0c6c4c16c361dcfa4b409e59f7787896 -72f267aaa294a358a72860f6c18f64d296daeb1ad6155dc8795ba8e6c71e8e51 -72f7ba094e74fd750448d9f4503509e04ee464069814df97f35a5d69b51e1545 -72fa228c6c5a336dc1343daf666736124f4f375d3dc7dfe7868cb74b178e42b3 -7306e14c516141da9c41dca895fd03a5cf0fc6c35797cbea2dcd209d36c812dc -730ad41074989844ced76260c9f6228b186783101fa3215af57daa0c9555f6e7 -73185d610bef8f2f9e909ffd4342fac7eb98f8fec31f4e5c7f53893a441dc069 -73208c0c4c261a0611089b629fcc2dc71694887fa1c74cc3233ebbe9a06e53d9 -7322a94d8dd10353837d2f202b69283b86dd49a351492a5df773d508b09f5968 -7324a1840685b707a8f2b27dbc2f13e05588a87079fc1d744935c8cc04c2b3bc -7326b847e09d78ae4b2f93a637f3107cfbab843579fd3740cf5f88364511e6ed -7327252c80bb5f73b297ba6fa4bcc420733d0600e098e7e3aac058d0558a6e98 -732e6ab3f6ef25fc8f2f06a4540a98e12d67e4b74c03eec3f3e39bed70c80714 -733332fa18002eff3ad91d66dccda61c6bed698d8a46435a354081e0dccae1b1 -73351ff0f36a25faea7ee68f96d938070d9407124b935010a2d8990802496521 -733c83d7fab13a1e0ab6241fecc2f29a44067e87a39ca4ffb3f2ccd3b5a767bf -7343df444949151c44c7cbaea8d884664b5e13f228c99d73f2adf70a37c32c20 -734a68f79a49f3399fd5c6efbc65b46eaee77c27047bed880a16bcdce0bed205 -734bcbf26debfc54078a1a3aaf781f10f41b702348dfb1584362cd5227998d03 -734e6416085636275476e491acb60354f59a27065396fbac80d764b5c08a43df -73603a7342531d7f577ddcae810bd036d2c6c94399bbc21cc3b1a41f9d698315 -7366695ee304fff4514a7a545a97470dacacff742d3ae5d963e8697d1fe77842 -736a36d09852296608f71378ccd87fa1631c25e1879248296554bfb11307f719 -736db43f385713fd112fe890e73c6acf4b627a739ca0524aed5406a20e2b85e4 -73881e0144969a66fcbdc65fc9344a6bd0a947ad287cec70bb7f7e9260d3ad5f -7389cc8c195dbba6bdfed64c45c3f743c6aee84a7dfb2dbf83bffa6c0b33621c -739af3ee2d8b5b2194e92b519bf46efdac452dd66e81f435a3b4b30d8aa9ee3f -739dffbf0b559afd51c4a64c4a29df7a571f3ca17486ec68c698d284d7d52026 -73a5ed1c294ec74dd1db8e57b801b22e8100e36c7c0be5ecb3082569327dc588 -73b350aaff04d309d4038bb6ac466c07b005cb90c587d8c0b7c8f1ab5b5e2683 -73b8abff9c13fc04519c78322296f31603e32af550b9f1ea443f77853343d9d4 -73c9c34e0238fd179ddcce388500f5e4cbb640ec3da1930f0f051029548f00d5 -73d04484d0b707a5fc853f0d5fa0d62ba060d6a09878073cbccf44f4ae19824d -73d4aea1544a80f89702195473467515490e2dc3da55b5c2e021a1b009f163d5 -73d8b4b32e0357583d373ed32b8ff105caf17182f55eb70b8693beccc486722c -73e581b278c345c0db21b71cd85993a70b9ac3ad329d81ac45c62dd2b50963df -73ede197bb8d74a19199fd12fc6a0ee7b163864e314f1334ac88170be10a88d6 -73f102683d14b8eb4594e3885ae495cef45d365b69dbd94d40ba792a01e3807b -73f577c43fb7181ec996210702a65c95d33799c3bbab9301dc50b81b60287d26 -73f83d0ad7a593ffcc870cea197414f2dae4b688ff74c59a204693e0b2132ebb -740a5ca588930dbd1c152c11b08d477986fcaeb74238e1dffb9c52a83874e7a1 -740bf5184d28f0b47bed4d45abd6e90f2a398e50024381188cd6f34d0cf82308 -741032d6bf99883c8acd90c1aeb04389f87897dc2e0e56e8d71b8e9a31207ef3 -74226bda84a267dfbd85e158d9772191e03f8da85b835315d73073cb2f599665 -742880a3809163ec1325edc364743687a5a838b564e5c7e362a8cba8dd327218 -742c44cf4e6a74cc10e8964ca1937bd0dfd2d12d9b29f21a8a4958e76b9efccc -743234cbf285bf1f719cff2d72e09835f49e9375144144469cabfb4a5c362dbf -74324eda97ce85d5318ecdbae72b98a3ade690a4057567f41ee1089f059557d0 -74336895dbc2e6611d9f7c20b88c4968aee3df7f6bac0f04d6d4895df45bba5b -74419d3cdfc909aacd0faab34fb98b6c54a402e5ed234b171b9fa6c6de7599ae -7445011b4c415fed9eb5f2d3fe771c31cdf4b2ceb28eae603d2cdf9db5e47d16 -7451992601e51f6cc4f7640c1ed513dca8147ac9559b8b6917a301c4839c8f3b -74551b22371ebc7a09b1d968fbe1cedef7fa8ef1c20dc7b9a743b044ff98a4a0 -745a8f695d6fb65679f61069b1ca30dee91a588ceb65dec06a9cc583877b5aec -745cfffdae66b415aa049a9ff5f8ec3ad173f2805a328d84bad1995647f4d750 -746ae252bf0b78092f6a9ad3fd59389b16709d70fd42814471d579c1accd7a17 -746d503332b46bd09bac28698688a88882e8387f0b27cbb731a9805e6b7c118f -749469924ec91633ae851f821210e92f5bb3facf39863a6a756dbb95142285d8 -749c80ff3826267667d3fb28fc97f100c1d8a1ef3c1397cd6333d4786e7f6e9e -74a0e9d9da7082a234129f4a04a4bdd8eda8f0b22dcc7cda10feb0bfbbbd0d3c -74a1012d0c227c8c0cdda4f4e8b12568d4644b76f6fa822bc1ce167110a845aa -74b4fb1aade46de01b879cac65ac3ad5d385cc1f624b9d426abfc0c48e355017 -74bd769fa3f961fe87143f1ea994da4ac0a1bb5806f2516568f2691d2f36c541 -74bf84842f10da466038a01912ad6186922faf576b646fb3eca8877e8fb64e44 -74c242292961f2cd5435e90ad728d097e686df530b83a5f13053ebde372e4572 -74c41ba047fadfb54b30741bb8ce4a4102bd06c9e6ac4731637f39d871843749 -74c56030bde4ef3bf94ed8c1aef481b72982c5d9e03a8b00df0ea495bd6d5c85 -74c7512336acf6430e1501a437f0a67f7d8579bc9ab5abac4e9893c59d831b06 -74d734d3d12243b3957368134ba15b1a63bccdd5b60b6fac13e617a4904bceca -74dcc6163120deb24bc7d231d8c3fe440fa86b0d6d6dbc77e4cd5fc789fb7ca1 -74e6fa70b385f11cd613ff2f48576c7355753d95a600508ca7e3a42fec745efa -74e9317bab0ba376127427589c943f33a59e4a4b44738ef82d481df94cf76493 -74eddb6c644900a35123af5c6ad5c0a065ada87b30045bf53ca7bc0ad2b2e395 -74ee9de7d33090a0e4976ea60dd72d0eb12604a1e96a99769ab51a3319cd4ee2 -74f4755060a5028ef297f3454ba1dde4e15c70eff389108e57fdade96f502126 -7501abe7f93f54630b087dc0d221ebf76e9e51cf80a07ecc7c214f5b760d25a6 -750dce75d5d74c56895123ac59baf38d9e81969dc6ae113ab5bd154d5ad1e681 -75128e208dec6e592850430e0c519abd3688de14e728a9afd15679e5eaa27044 -75135a28de4c6e022b29f4e0fd29f4303303396b33f076f57655feffc140560e -751c60dae3ac7aaf08c28750aeb4411ab8a489c3e967dcd08d346f510c43634c -751dcf72cb2c29e1d821d359df482e021c941eaae076ca2a2bcae05243126580 -75216bf53d16061a2338b9e3e69324ae741db48e6d51b2fba4620e299efe9433 -7525ec73fc824a173e02c700a9f54bc29310979d5f299371b6022648ee345a16 -7529ed145cb76c4bdde68d8c9028129f084e8a930725eb8a0a742b4d9b9e4fc9 -752bc250e67c77485a325d8e80f9355875e766dbf908ca6bd4484c127ae06202 -752d37b99721fd7096fe6e471ef13058110018bb1781d0c7f965a1a89b355586 -7548481ab9118273951599b35423c58230c38a93d2b4f76d0c3a7221f74221a0 -75550de7fe6950dc90660eda16be97c51dcfa65814965cf7ab5c074fb3e51dde -7559fcb3c9229e9524090d20379c5fa97f0932da8c8ae064549bbac55e06354c -755a04ba4238a1498f9a3a2846716e848514a4af7388b1ea32d2248dea71e14c -755a4df12a42f2ac316770b0fe9ae241e984cb30aa70d15ca813bb678253dfdb -7565f6c4f70fa1fe99c9c161e05a0fa795705ab5709d819b0093cf5115531cfc -75717c7d000a525bacb932aa35f9c214ddfab4db910477ee2b465d6612a5e0b4 -7574ba005b332231441421f1aef1f40bf5b370da63713454fb7c6afd8c883306 -7586686d7f4e224b2276a55e687113d8c856cf9fb6d2558651a7afa0385349a1 -7591e6c080b97cf9109387a2cef0b27f052269b2258a1604da25a12e9f2220a7 -7593e3b4a672ba77b7f49b79637ede2d6ed85424c7381fbd8d12f0b7db0e7996 -759540022bc85ea8fa16714cd5b4e9e59633268f46f69af2494904d63260730b -75a3db73d237fcee6d24f4d8f5e24778a97af54bae685d3208a8a110ac13a1fe -75a5096a379a751b4e36513fc3dc1a786444836f10ddf1208965868d10777c6a -75a61500f45081b688c9d8fc932417d3cc73657fcecd88a9308abc6f9113def5 -75b79856b1206ce2a07e9126e3c9992b814c5ca2b399f063867d47883eb7a7b0 -75bd9f8d5260e620d4ef8fc81a1cff0f3fc27fffa1bef32a8ee4ce169195b414 -75c2babca32f3317b6e56ef24dca37407f5d4114185a06c16d03ca9e0814fa19 -75c9e25149bc0c1033078b08b661822b2de4e2ea9375e7271436122714e6dc9b -75cc897b1d23f7179f17e9834afad2f5c95bef0be19aa7cb0eb3e6aef42af697 -75d286a7930d8539c3d406f0425a4193851b4982caf613aa719f0ac9fad959b0 -75d32b2c67b9006ec7e649f2a2ad059e86dc4d65da4a93eb7360e74b05010e89 -75d6ee917f302256dcc2744fb6a26dc798439b4b377cabbee225a35ad3375b86 -75d7507bbe012ee7113033b86c8d5b55bb0a5eeddfa3e1bead1518c5b20a5222 -75e7cf87d48cfff5d715c765e05139c872f9a04e18c35b7a945fb232e66507d3 -75fcc15775779241ff00b494970cf9809359a4b0510b7ac1fd9799a6d7d4b62c -7607f223203f978f2e2a54c8d4b3c084f0a33e9b5a27c1fcb2eb2124c5b2e70c -760cbc56b6494ba5127e3a3ae768e45b8327d06703d9362b95c4795b2dedac59 -761bb00f79842b3d45cb6554c1295e31dea9ab0e0774c648535f02382a5ed986 -7624e1710c3a12a936479df88ff0ea37fc77040116ef395e0054e809f946fe93 -7626562b03e7eff751425ca2e945871f158ab4442924b9b59903ea7f0c589a0b -762f79c8b5d49db05bba300cac24b34abdebd2cb71d2cfdb6a440175f79411dc -76322dd04d4ce9b158d6e99d42e8a37ddebefa7aee2e59ea61a80217f8267401 -7633631f3a349d443788a64779694993ed90ad33c070e3ff43ee32c54fd74ff9 -76392155ed084feacda86e90d6aa595e392e577d2f9c2a4acac9c1e6278583ca -763b1cad85fcc29cafd79512be37063294eac6e62ef466b87cb92495da62692a -763b53d95cc2c1365ce8228350407d4d8df8d1bd8d2c7cc8e42b2b5ba5b7f7e0 -76456c625c15b931111123bb47deaeb94130ba891c70210651c0b59a52215c2e -76530ceb1bb03e8d1c6b8180788da20a0fa7f8fdc2fdb443daa91a4eea626799 -76664abcc538931f041002bc31c96cf9d17d16c8bab218f7f2ea3d4b91e3313e -766a06ee2b919eb6fe5fbb29659605e3d5de786df549baca44bd0952d9362c07 -767376379c372a818a2ce625fac0d325f949935958f158f460b8a48522e71444 -76907578f67f33a1a4bc190e12b70194bc3f1ecd0eacc0ec72e3a29f73cde964 -7694f37bdf8fcad17f28be162c5b646689f79681022de86f079580b84e34db93 -7696351807b196c1b8c49cfe466a5ba16e69d027e79926ee02d45c04918b4480 -76997b03ffa2ffd0d0af5d3f5705721a24cf94e9be59f595984e89a0f54cdb00 -769aeb7aace3a9ff8c71010b0aac6e7667306cb95eea69d758ad651b4684f158 -76a1c19725cd1ff4382ae7244afba9154b44fa374f9efb32143b46fbac32cc24 -76a3df426c078efcacb5009a035a391cbd7511a0218de39ac5483ff02eaf57ca -76a54858e2745093fde89bc6c921089442c93cfaf1cadae3e8e548b77b469ca0 -76a92143c4761d3494d2f5a1be65a72e433f9d9d524ec2f75b6f5ae786b940ba -76b035609cbea3cf30156de7d6dfd8804e75c32d0dace1b39e623a3f9528aeea -76b6367f388701ee0378076f29e67fd0c26fa5664a65465ee930ca54df5c91ab -76b6b9ae57625fde5f28ac78b282ea46c56ccc5a474d77c20ab94c84d1bed63a -76b783695cb8b7ec37d3561bf55b5dbe4db1f8c6c1b7875cdb790f398e068654 -76c241c06dabdff6f41fe4c614149603562342f7503b8279cded559812622048 -76dd0a7f5188d5ce9042e633e936ab6b2c8a2e54e01d988dabeef4aeb8fb49bf -76e0fdb5bacb66c0b2b25fd1d0dcfd8b1b32968a3efe903408025f1dd723c518 -76e10860126d6992603482e523078cd8c6f2f98700b17b9d44793c2e24022414 -76e6936a6ea79188f00c33ce57e3596be64a6e7cc642f6c59893948be9ea0024 -76ef68e4fcddbb2ff7aacb41bc303efe527261dfc79545b392803f36a05df3cb -771d2d17e88129bc84f563e063fcf74ddbc9f079ffbd1ed062a4062bcbc4bebe -7738474b01b167f62d2b79a3e0325cc49056b13a174d740b3e28606d739dc373 -77485d79920af39d6f66d1b2b8665a0bfb76b3141fe8f0e892b6ff2c948f0d78 -7750f60cff073e07dd86ba2079f8f0ceda74ed213c79d930039ffe7de86d7811 -775306b235b8bb6ab9b9fccd72d53092dea923d1722dd87ff246d1ad8820a82e -77550ccee25e398e81583dbbfc3b8686483f62a07f2ee77fea588397f4b8a7b0 -7757dda644874d2dc118e704319833bb7bed0617542af13eb6dd2c711c1c8ee9 -775a8b8274b12830866d12f8aef3303c586970500b82a58917efa0a36e97c77e -775b4614685d28035cf6c6b13c188b8c261be8119ae6f1c1f59826fdd2c19d58 -775fbe305b56a0ec7485c037a4857b4c75f2d6f16c3823b33bc14c5dfc306170 -776a2e5d59b350634141d1703727a0b5cbcc34a503bef67e94c9c17ad006e5d9 -77827cffbeda4a0e964a7060255043b03cc6d98b57686e42bd83a7415cea2920 -7785af6e5a1d423e2ee75884be135ca37ba492bb799e049d71256d7364f5a726 -778f9dd398f07a18c4ea70b2483302fa9c8e71c022fe1bcf22b72d8b02525f9d -7794c6d1d257ce364ec7f717dc9fbde3402913e11b07993995fd20ca667f7e0d -77a0eca3b262327e33d1f229892ff07b2f20d41868b452e48cf6d014536d876a -77ac42f34921dc60a1c884f3bcd755a961958f4c5ef2f8f320f353ba1d188cee -77b1ba6f726e06de15fd9248fe0d7dda7a3b2199e9bcec9b6cc9b1de552fa433 -77b4de0e14c9cbfe953144684e77c113f7bb7cb779f79828b0c3c02dbb36c884 -77c46e45eadb3fd4e9dce0b469b548c18c833b8a6fd0eae802ee97f2bf42c181 -77d51b2c905298763c3f812cae659c368db268b7b04379e9afd33ed3c4193903 -77dffcb8a051bac52c6f65daa36251ae954eabc11d0277950f6df37b236a8d09 -77e73fefdca20b4ddfdc7f843eccc02224a6528a4450aa6edf7f369b5b7e9165 -77f4fe0092e81cf2b38a6585d0b482d3e60b6abed3ef068ff74fcb1f2ad48237 -77f6b9a7dc3c9303caf10db46850b22a5e1b13cc30f05b2933b6c6524dcf97c9 -7801e18e92eccfbca6f087277f006839a8d9b4995b710d1e94e617cc8d9d7866 -78065f75e9682979f01e70c2e99a89d3c709f81384557f8c92c3e4c1fec07079 -780aa55667a07d8ff28c657fd8f99142e71960a846e53ce7fb78d44e0fa2b6ea -7818f83ed9eb377600d4337074e34f8ef15d146b40defc5dff1a1269d71c47ab -781d8240f9db9a1cd229040dc43b805a830129f5fd6902900b98efadfcc60572 -781e84537d32a1aafa57aed7dc60ce0f837ef42eb8fe7eb31d0d89c645e3fd68 -7824c9440c2d39ed5812bbaadfdbfe8c491a9bdd1e58160fe95e57446eead195 -782db8350cc9eb5fd9b21c05f127c68b47d781548d9a4edd706e7fec11fedcd6 -782efc81282f92f31169caad53d7f1bebd47310b86be574845d636ae6a69c0e0 -782ff65828c5bc6e4fd50bb70828ef0ff8dc1912235abb134038322128d9e97b -7848cbcf9f084562b0249e54fd367e673e57755421783132e5c0db37089b79e4 -78503689a77ae9c290d02de820db7f108d67f84218c348f7004d57725f3e6c96 -7852c00550e2c09054a66b116e5c3906b2c3ba34de58de2c073f06abd9f3e83b -78654c1b73423ba84dc2b209cc1ec5a3d849f04633315acc836431aecf10d95f -786f350e4cc86935481fee3eb9e7e5483b0a1de7d0f819840416e1f3d058f47f -786ffb58c245297d4192e12341cbe02923bcaede3b4c30b9a872263323b1aa27 -78786fc42e8cb3ac162ccb40788aadfd99d1d914f698f7499cf6edd6ad9e5c82 -788457e99366b391cf241fe8ecebdc23fdebcec76bb80744b0c0579e195baf22 -788ea884a4d6966b3c21428c6ef3fec89d258db6d7f48eeac35aa8622cfddfaf -78a12a844800dc13363ba2b116cc57a1e04336bb1488e1364e4477bfa1c84718 -78aa1e6e9ec3d6af7d987f854dfb3f0735d2ae438e995311f1b2a9fcec035550 -78b51d7657310cc0e326edc7e43a64d916b5a3a38d3c9ebbddd42206db5e3e0c -78c0a70df20a356e8a67ca8bc1724e63557b6479773081f1823e61f5dbf01a89 -78c15550850d24ad16724cbc573869c940c701de7867a605a8eb429690aebb69 -78ccb10433d3741c728d78ae859c55cdb8090adba9f1a5547abb714f6fd46c87 -78d359beda2c2f703a07a7c7af6ffd4e052d336eb140e8d1ff88ca5e7338a7a1 -78d3c41a319f84565c8423023558678d52ad628570ebce28758c7047d320edc4 -78d43684db7aa8f0c899d8886bdfcbc5f9c604269d40eaa2b6290dc93b4c911f -78d472205f1b988812bc7d297d4bcfc0bf795e9e9a4aeac7af3e767d1ee85634 -78d83f03ef888baccbff2af28eff91a6fccd16c35989c1f8cca9762f0e001dd5 -78e501b28b5eca4d1ec50b3130d65e1850a76570b230b992ed93b4b2cc518bab -78f41c7d68713b7fc84307ea32f0251778fb309409a8a64cca1f4a48bfbc9248 -78f65051f5f0902d8ef66cb921c874ed0596282cc6086a6dc572066441325be8 -78fd99e5cb7c8b5a12cab7a854789b5a1427568a4750f08f01f211ad1647655c -7906458ac8e27a25b24cd7a1cafe7fa2e4585801741ab368c33212e50d83ec03 -79185162e055a04dbd00ba8c6604523164364831bc06779487ace21be0cf4a0b -7919fb8acfddde49d406187b9ed2f466dfc62ecd5d81623c1e159806adc5d17e -791ca6e1b6b021d96d66ed133366bdcc4db972306d698eb80092a25637dec999 -791f9d7acf60a6256a9fcfd79c271740c005d7f91d753ab2a2e55ef529ce0a37 -7921b75f55b1d8b4bbb60684b4b226ec3216b3b1a58f9f0811f4e1a8b1e3e951 -793311b8e13f864b471588c5e4e8ff1cb31ef2d7515cac879d8fa576df0f1f8e -793cf2513cdbcecc09639beecf78a4c580bddeabe392082d84e1c563198da512 -7944475889b919bf984121f0cb5842c5e6456c84b2b84fc51601a794f422abf8 -7946eb6dd4a45fe455b00e8209d0aedfa9a8658e78325fcd57cfeaa1455fc0e9 -794738dcf74f305d76649a8cc25cc7dc8597aa3cf0c5532b9f40bac63dae7584 -794de10effc8f6687f19b9d7ce088d350f992b9cce7e7fbc88715a1338278fbe -794f7f552c71747605ed2a85264a762871d7ac74e68bf2550de34093d145d245 -795448142babb02766ed68dd1b748f211f6bfdd0fbe03ffd88fb76df75a79330 -795a9447bb82530375b36b3f740ca7db8dff1b52c5b2857293c0657ca9a2ab32 -795e76cfd0477282dbdb22f389a2a80574c0b7af1f9f95a4f9e284afe24e66b2 -7962e5a30e75d41f72a0b88a2c91f67f47ddf003ed10200925bc6eedf611754f -796567635a55ce19c1374113e9575e4e46e6e8bdfa64bdbe7803be49abe23c30 -798e46492331cdb3331e1c645cfb0d0073c53f41a2dba09028aac5a6023cacc0 -798fad17531a347b087e5e7829fe2c351272460ac1b0c3c72bdb9975b017c938 -799b502a19379bb2120f0945c005b9bc7a12a4324aac0abad961a8a185ae49d9 -79a286b8fbbb750157e4bd785e75d802f51df378a1ebf1f4627c322adcdefa11 -79b002124e016bb884b2b4acee5ddc4080a32f3e107ba92e1b66a976597cae5f -79cb081c423b083abe165fd321d6427bdab670049c386da1b47db28462ad34a3 -79d30b55d74e404357f61caf8b12eb68ff888f1a35351c56bb26882bf3b29b8d -79df779acc146ac7f75db4207351507167c286725a9ff9bdc663114f1befaf0d -79e8a9aa04a211dc36bf1354f343592c06e5f1d0ecad00e393bb30f2d013a4d7 -79f5091e3fede40eef34427c8e7eda53cb91972e61d394036e2e76014dd9a024 -79fdf823ae23e498618685f3d4372b1200f85714ac6312ddb15ac5624ed8b802 -7a068e374ae976916222b1ef071f0151fcc97cde8378c12f40365e8065e0b4b8 -7a08831e7904b5f98f9a994fc7a2a93fe2ed8bb3226066c465e5b9dd54d4bc24 -7a0b7994b34a28374fa735752fa84fce7bf1281b91d99338d67514d4410a7675 -7a0d504329f76fac1df729fbe85b9194b125ffa98ffe90bd594ea602ed34f09b -7a13e3838de75bc597f760293f65afb7471572a6550e3635eee11349a757d2b4 -7a2185c1203a9b1b65b087925d20474201b71af13ab8ad70f4443f15b2854004 -7a2bfc4ab60f5be014fcae73db19dd3ba4315dd07450cdebef03cd5f2d6bec92 -7a2d400c67ac8d1eb01ea794f2fe1403ffa575fb32976987b722d63c32628ed5 -7a2f867e913bd2e726699ee77b0c61ed8ef6c948fcc2e693b5efe5a67d0d2e0d -7a3c0bef68b743cc1311013929dcd9fb098d898ae092e1ba1f00ec52115e2c1d -7a3e78d6ee5447a2345e807b402f5b50201cbcdbc5ac31b9c570bd832d93e926 -7a4083514ae9a2075832e2416523e7ee804861f8ebbf4bfb84ef67bfc6b5dfec -7a488f9aab6f276dfd1000dc7cdcfd2d414cc00ae1d156b713f82ac6792da57b -7a4fd4f06d20dab6ea1d9c79f5833d38edd65a545425d2c113555391bbb544c0 -7a584b54e4f29590ae75d30a809de4e9bbb23ad6a5b20377cbb4f0ebf2a06dd8 -7a59f6889f21daa39646acf772444d299fa8cd7553d40277f992c1d1d1fb1824 -7a7a0f3da3a9f3021b93e45a28a011b3f559cfda78cd17983a36feed63773e02 -7a7ccac5476621fa4b25bf277e8e9f3758643feb43b18cbc48f3cf694be70b74 -7a888d37c7893d03fc39da69cdb08736fd97e5c2f26fdf41951810369fff192d -7a991140d2579e66e9be48252ba369c48dbbfbcf68b9a8baf09e895932aa5d1c -7a9a4a81d4e6ffacae196b3490b72b4311d72e98c9891f4741758cedb20b5098 -7a9d1811d097dae80dcf4a18de0350b7460a04c8c51d01dda652878c2c2a10a8 -7aa05b0356cf7ec8e284e46f3f5caa060bb37ec18e39bbdd129f1a824362af03 -7aa9d3c1410220b047e15faa9c7d12a622d80e1fc6ce428dfd1a59cc4c1c49fd -7aad8014b05688606f0373a469559b093760da9fbfe2f2c233456df66415d574 -7ab159354469d3e43130e77b6e3672b05e379d2f38e665d691ab153bb672dc39 -7ab8203583159b35810cc08810a4b216b603923cd4c6185d2ad1b4f2c4be51fd -7ab9d03146f64957ed3f93fe7810ac8914e081b0bed0a9a40a1c782c82bf5076 -7ad4c1e481c301b89038c08382afa6881d2072d9695b0889805b307988b0c2a5 -7ad4e5c3b96725075e73d07e3668ebdac19da74401070dcb4863d0ddbced3bdb -7ad62d0b02b7e2ce658407731ee583cfdd9476bda39475f7cc38fa7bf80a4cf3 -7ada745728d3adc302210d2e0a383a5078c5b2a9ff224d6417ea1a82169824b8 -7ae193b494f42df4f0802499a99ff8923a40eb8609c13611dfa0d64a0a3e1a9d -7ae2a4742be7ea0190d28b5abcc69a46e8050478bfcb05def424324875784de6 -7af61d866fc90d9898bf7abbff6e349ed4b31545b5c3b9bc5c25402becd2aa67 -7afc8b0332be8725c59d22df3bf8d0220818d44266c8d814823991d24e560d3d -7b0771f78575c3780a481d58735d96749bcaa1d191ba6e67331b2905dca8e075 -7b104a8a691cb62c0b09767cdd70e9f8a818a2ca42ba526e4a5d07964bafef02 -7b12cd93dd2c5aa2b10f0fd21cf634efc3952f98e93ffb22b2c6ac86fdc0c299 -7b15a66036efbd09de2973a9e23828eeae94162c18b1dff57a09dd588a2dc42d -7b23636cee2c0cc654650d45a9a1de39d35f97507079bc45c910d322020b56d5 -7b3916244c6b12621756530c9047b2992c7734dd1e294b44c1ac298fb6107951 -7b4c95ccc04f82ee95a0275916a9683072399fdaea52f668a1bd6682b191ce35 -7b4f585c3379620045afb521c99296592dab858c6271914adb81750a130b17bd -7b5a055baae02099795d0e14c3ff0a6a1862c52b3ca2d8647def2a1f5704e181 -7b65ae179d014b7313affdc284a16ada85f2410301227a9decd9be7b0deddac2 -7b68beecf2a446863bfe336e71f6fdcd3aaef7ec5d2ae2ebce99ebea2c16cc79 -7b738179b255f64628b24c19e59e0d145f434d3c0dfb019b7572cdbbc93edf1f -7b7ef2468fddf757a15f2cf87f6b99d5f49d28edda4f2d751533cc9c58bce39b -7b9139623dc1c6828447a9da807abad81653e0a94d54627f768a2c62d4a3ba38 -7b94a63d2d9f3c6462bab7ce465da8dd3d76eee0e8167d9a68e748647fb63ba7 -7b9558fe45c41f1ff54fffd7f76611fbbb13094dac32c303314cb232a1b59aa8 -7ba09f0a9735de0ca1cb3d73da31c30cc61f9ef9c35419a8adbeb97ecfb05587 -7bb2bb3991372dc272d1742b0b7435c2f764ef603e107cb5cc7c41411dd6291f -7bc8f10a210ea617c26aee80bc84ad86a608821af4d1e039617e775acfbca8c3 -7bc9424a18a6bb1943b66a327647381281152037aa64081de5945e314038ed4f -7bce4d4ed5bc5c86495cc2a25b6b7b2f8b8539e8acc9e45e1f265fc5258bc319 -7be42a010698e525201f3da4d6be7b1787a50153737cc0358912b158463ae972 -7be530d62b2c8d768000f468d2174409d826fd7c6d666edfc2baaac26dff2cff -7be5498a7526bf2102ee286ca1d33566cac3208d56d63687dcc449d7ff2b4b16 -7bea56c1ba8319e03e5e0a1ecca7de0a90211e6b0c221ba27826c7907d98c9f9 -7bec6f20ce87c99d33071973220f4287b28598235899fce20638d1f2fb4ced2f -7bf7b5606b299f85efa2922c3635392b41553b9b0e7e70a9c3cdf4e645359520 -7c0b86848bf6292ff7239b936ed23d1de8666a4c8fdc1274ce42efce892c4ca2 -7c17011ee3547606760dfd7d0199d4837dd75fd4a7259b2a5ccb3574564df3f5 -7c27210fda86ba1470c1eed1ec37f157612a3eaef5e3be12ea91ebe5236108f9 -7c36eaf04bdd47bf051dad9d0981b39f0c14ecd88cc638e98df8d226758b910f -7c392b377e6b5f78f75c583c2cfd61659526f3ab5242cc276eda24e70a808eff -7c3e99c6155df2ea0599e72c700a15540e9a00267063f2a45329d0744e2db4cf -7c47aa6d4365807a10152a1c7221ef01e924518f78e5977475370517fe2ef8bf -7c4d1fe8d03b437db3a5e9aa9d0c0e3ced11e525433ea6b203feb41126f360d6 -7c4de95a4953517f3ef6d115e078ddaefe1666fdf29539ef18b26bfe02c43e19 -7c4e72d1a54ac687e32a34a50e8f1161d4220818fa081f54bf1569f0f781f7af -7c4ed3b1fab598d8da6a287be2c5974892f08e32f41d7c274ea6ac0c02a2fe17 -7c53bd4a1ce1ef7a72cad4d0a3e91c30f8b04625d2166ac21d7eabb986767340 -7c54a124067fb9a81bb071edeb60ffcf284a53698414c57e431c06eb0b3325c3 -7c56950f06bb00651f5e56eedaf59cd11682fe99a175148aafd858f037ac72bc -7c5b777f7e7dc09a91baf491e9a3427a9ca754f426ee2919c4cef52cec574a17 -7c5cb2903711e165995b11a6bd6e5fc5e47b454b36d0ed4b8b9ad8ff45127a6f -7c5e43554b5196aedd448177c79c80cd3f9cc16c86adb3544ac5fdd87a5d93ab -7c6804cf478cc191c7d0f092b73a218f6987ed9172033640121e2dc3a1723794 -7c6e487edd42deac7f5d4728cc9da7d8ec14e3de272a4644f6ed1eb597182a9c -7c703c59fe5eb580a47bbf8eef99e9bb59fb16fb0de7562ea1010f0fb0c793e6 -7c72f8dd135ea8352eed18d9c062ac4b0a6e0f0cb37ab11c8c8ef438559b5563 -7c7c05cab9c63223344c34746847080a34e12a73448e8e0b7ef0db43ed57c5f1 -7c8c1e845617023610ab5eb8b1e3bf9512d0b607143cc21ea055a2db7e5d42e9 -7cac30a06958aa0c4fa65a8c517ceb56439485ec54002a4c597a2955dc2aa0c1 -7cb8481aed6cb01404889b7322a681c5ccd3b3ab606182411def6d1b433c23ee -7cba48474900a9675a2b6bba57873cbd7916f5fd4afe2591df7f59a1b73c0ef1 -7cc8331f8f1104272d488b6b06f9aeaad82ca61fd6651dfab79ba0dea308a97e -7cce27dde79eaef3cf2db001730ca3a36b65465c36602ff548107c9a4bdd5773 -7cd8c862469178587b4d07b2b4e3d5aad003f7e147d9cbaa82d981c2f8bd839a -7cd8fcdcce0d1f104ea0215336d220d316130d868194ea4d694e13e9c74b85a7 -7cdbbfd7f57df3b798a3724c0f8ba1f058b422eac0be14b05cfd37b76e715eb2 -7cded0af8f2325fe6c9e0b775eaa69776d939d1d6e38df6f759cc5810424d9d4 -7cf68b59699ebeb2d9700ce205bc91ea2715e6a16b4a5014d148c601fdde8d83 -7cfa33904bb136b473743c6632d6f788e1725d589fc9d6ffd252cf6efea92236 -7d0ee206097584350a0240b7bb602ab080fc7fcb51f240ec78efff9a0f81ed91 -7d0f5def1b5dacc2edf43b5842e361ae55d56c4849657f0e573c7e561dfc3b32 -7d100eef87d040e0b1d5e95c88760189c23d31dff943a4edaf1b789d46c20383 -7d2ad99e6e7afc558b58ddd592e912f43bdb36f85abce484d6be096da543b2e5 -7d2b3f96b1251e45687258593ef5553cd484d129330522c26b66fded55eb3ad4 -7d2deba91a3f29114c2b2f1689b1ea37fa37f093b114fb8412a6e00f398c28af -7d2ea24fdc50612b61d67aaac4207f38e2792fbb4344e561b6b50924bb4d0162 -7d2ea6a6b3c4b0c21b589cb3ef8cedc46a839244c888f5eb14baf3796540c20c -7d3629a9ae8766fe317620c9eeade901010ac3b812317fa5579929194227cda5 -7d38ab1c4a94723f07f7152914f98e712789526d1fbd9cfa30fa79c7485b8f40 -7d3df64c162da15f9402d140d4e366079d1c891315e6301c144c377745239336 -7d404c1eda2d7c7abede6597c425c0268ca5dd2d61e7bf87736f301236590a57 -7d414ea21ff5fbb5e71397d9849c8e9c87092a0ed65ac8a9eb87c4d4ed42b896 -7d422f326768cbaa6adb4815560762efb60ee6d6ba05368dd7e92058f040b677 -7d450df2a6ca07ad73cee558e35453afb2a90fd2f43fdabdf718128949c59f3c -7d4c4b44c7ca20c70dfd53503493e00d11a43830f6df730ab892c2ac4e9954c5 -7d5233fce05cd9b71b752a98c07297849ee55cf7cb44049528dbc877d3998c20 -7d57290f7448e87a9f10eebb28817c551468176f2a06341c15be6c196369c3d8 -7d61562cd01b05c89608c0f5456252bda051ca84454bc43505822915cf134a50 -7d7d052c7295ea13f0e1f9ef48fc8b0eeff17950b1b881728f50de9b6d7bca70 -7d7fc9523bd06788d5f80cf4a33e9bf9c5da0cd3a9d8e44123dfb9e82d50cae6 -7d847e23a78ed67119b24e8d24a191ac156a4fb0ab0fbee18933edf56c1c35ad -7d85c415df8925b4a2834069bd473b13a96375e03753892bf25ccd7ff3ad8015 -7d8f4e7af272d76e9ef6ab90f7a7a46881befd79b97a3267564512780dae1f80 -7d90e578496c8040e2adc461d47f160bbb09d02babe50cc385e8d10e07883079 -7d91cecc9429bc47d7b5495e8ad367c021b4d2a8756f5337cc32009a4fbfc645 -7d93afe9fda9072be4bf9ed36b4a0e44cfbbe3bb91162a0242db965dd66298cc -7d9d17063a955a6f9a766a08478f7b4603a4cd287bba056f777db0ae0cb8b0fa -7d9eb7c67d3cca33ade3f74080981c49496152e3cf8c6ff1dc66ecf581ee5e51 -7d9f4b3491dededb7620be677645078bd49c19c6e0c4dddc3ed3f615f7cac00f -7da31f2b0f820272e236b187dc7f71112ce3f85518a37a0aae7915e2f0e5fa68 -7da61f5b5c7f5baa6c7a94f971b465f3a123cd10f477a6c18837f5bdbc225ed6 -7dab4072440fc200299b683a022b2510937ff90622f3535a940ba97eb5b7612a -7db6ce4dfabf251bd5bf04627ec048210d79b1e5c357589c2f75ce1bbc2ed9c1 -7dc5d473825448b9da75a92da6792a6bf19a90956b9acb405c918c6b3ba09658 -7dc5f407ee4f5125f2d56ce55e735ef96a03d959ea328323053a32ab2af53678 -7de8146a47bc379dc024c1b426ba643790480c7fe47e9838e4ad6ddd6ab569a2 -7ded7f9885884b31a16d49acf9be0d9d6cbb4984a13875a8ce0c09e53a3415ab -7e0ae66dd94e3fb780f7b361998bfda45f100f2c094d02e73dd25abf6cd117da -7e0c6397555de905018a91763916558ad4f47d4d27bff26a6363767ac20fb100 -7e14218fb42824dddfefe15400b168906d3ec8a222d6642d597f66cf6c48520d -7e152fd76130211cdd3b21e063ca7b21231d6e1112f7e58ee692c36eb3e2a7f2 -7e1930c8c940235b6583a5d8a1ee062ef1cebc09ed3066e3fb56a9e979e7cd49 -7e22413ebbef4626a5dc88b0a89961f51d6af3cd99a76b6396871907b4e26015 -7e240492cae7dbe09d118d5d5f6bc40292a6463d069222225d5e95ff68a4588b -7e27e1fa79b261dae6f33ed173ae1601f840276e32e7ff730381229ec5b1ae76 -7e3c10724d3f074d25e7fc67e04f98510322db532f848d29f7c57dbbb81d3d4f -7e452a290d6a8cd5f5d3e63dd8eb59f43d6bb1f47d1c233009c396a5f7995d64 -7e4655e5cb4204e302f695b7527fdc0d339564b169b39a766aa2b5e23fa168b5 -7e577c02708569695b0367f227f4517d54618c97b21c00f277108e7281fe8e2e -7e5e530f6e095c6aa889e48346854262cb4a86115c412510048a7a71ec9ed70c -7e625ea2f27ef9b359d77ebc1ff2d2545bc91ad5588835aa372017bbde201a81 -7e6adc4d583464cffb8b907a3d6780283ec353a11f8d54b9c90acdae11198be5 -7e6c4619b6de0aed514ee343c4993187ff5e477179dad9fe03e9a857161b5b0c -7e735c9740e3f1f4c9f59aa5336c4ac6a483a2accb6c3984783780eaa3804f6d -7e81fbc7cc147c12667da624b4563f9e88803c261c6cbb86fdae6ca555a8d0c2 -7e921ffa28b337527d8bcc3dadb90912ef440cdd60f6930221537d0dd0db40c9 -7e959d77ba0f16b353d46dd1ee6e5323431a0e3e97f926c37aabbe8928fac643 -7e9a805c91bc4548d1b148bc0c87a88ef136d10a0fc4017b470f9bfc3539a36e -7e9f359fecdada4058763abc936e29939e15a815bb936696a0bfcea3293bbd71 -7ea3c43de64d2da64bff3aa588518379f8820d86f1cd24dfa68f75945f0f340f -7eb5ca425f0cfbed00c8132ddbd145c142761b50cf53ac3521a3e23a61e1991a -7eb8c0362bec636cda7cb7521228063bc2e753810c168abc4d39a608b6b7134e -7eba08c449266b93ed87833169abe0206858c3be5580082af5ea3f7e2475515b -7ec04c92e102982770b745def804bfb41ce3c6c4ccf302aa381349127e84feae -7ec6e76284afbf509e52005bb6af22782c7e66dd87b44600e24f58ec166f460f -7ed4d7cfd3fd8a874324d101d852e92ee5049cd80bf251947171b04587e885e5 -7ed59431ec0a8fe762a9a1ed9c1f03979c59c722b4a7f4ca1a5fbcef20b2d013 -7ed618f42230406455f9e4595382fd904409e552982065e279a68cbdaa046ca5 -7ed8803ce8d425a40a8202e491fd5a3906cc74287ac9876661784c15318e7f21 -7ee6a7f6ccec49e3491b51baea965e599fe7d2c613c82141824e330ddce1ec69 -7ee77fbfc2a263530afc4c647482ef7ac1eabae2f2f76177c3e02657bf2080bb -7eef57cdbaeda2ce1f72e206dd3b8122c6117c8209dbdd6d5a0bc35c15dca5b0 -7ef6e6246fe8d26b4db15e8ce3a39f7b0c1e7b98a0d520379c3a221fdfd21ce2 -7f017278d14521e8097746f1a689a2a1fd4d5837b14211e8c06bf1bd3ecec754 -7f10770dbdfcfdbfa214455934eaa91909d59f66b1534fbfcea7a3beaf66a2fa -7f141fc62e434a75ad557ff67e38973fa298a0948cb17f1f400b3ad2c4a5aff0 -7f1510e6749bf0be72b344736ef0c325204e39b1f7cb14aa06a175e807e2e992 -7f2b4b3580cc7c2ad75dd954487bf8ba27a79cb70dfcaacdbab7e6b96bd74550 -7f2bc30aa23d5c527e27fab69bded2e503ac9c38caa3fbb8821f9822f27e54a0 -7f3a1bacac8b838b8b83e91a130a9251c8b3af1ab7167cfc06d787ccdf501938 -7f439caac6b1301bc2a6a7b4a383af5443b33094a691f8e6fe0df9816e21dad2 -7f4d0d71324678b0b52bdb45fb627fc75dd189a97322e776e2fb1afb22b2e19e -7f504bec4a3e97b19dfd006ac6e14d7d51dba9ac2feed4b0b41a710665d1dc1f -7f5cbff0a6d64d1f76d58b69c49504d3ce0f94017d9d7410c4d70042bcef5786 -7f65d4ee60b4e9df2b010380d0fda3cdc9e68e13aab5aeb9f11ee23e236cdc63 -7f730eaf87b3dbf7dcd8375a7eeca2217acd3d422ffd513c17334ae92d5af9aa -7f8503d3e41303697d25051663fa33d2b28264fbeb135d0969e1a658c30725de -7f8ea2535b61a527dee808ab114f6b3ee2f3814ba9878349834f7a15d7572568 -7f950201de2baa43e96d066264cd5a8a6faacedbea1f06ce2a3cc95649540886 -7f9690d624be939ac51f193319aff64d1534791a2e46ba7582caf5ba6922111d -7fa257db4b6933c1d863ffab09b96c225b2f14361d53a1842b2612fcb408f612 -7fba3898f4a1aa39b86583e5f4894dc6d070083c0ec183319f25c57a4037079f -7fbb7cac96be68f220e1810875494438703e50ea888239ed0e3c5378ab5f29e7 -7fc721f330ef4a9c70a38d809f860d861cff69ba124ecd5976e35003533e0023 -7fd1821678a009799b7d2d313169268602b16e86c2455ae79e4fde7fbe83dd6d -7fd4be779b63c2973c78c1f625a72fc3d8e1124a84da35754e254ca1e596b5ba -7fd5d18e32b3b454f6587ac7a8fe413da6a2987f9e9c6b9cc7765c9768fb8ac0 -7fda4074a39f4dafa8ffa5c8d8d5f8b4df7b825eea87ffc35b4b2bfe95f7eaea -7fdb5119312c5d85c037876d635e140240216b2cec3b8c2f2b407cee0aaaa1ac -7fde1677a059e9372a4d0e6d6558799ddd7697fd840837c5806c39c09db27ffc -7fdfcff149a32140db01044ed1e07c07c8c4394498621a27fb8be7d7f7b5642b -7fe05ba049d56c532a246bc560be64d3464d267f8010672dbd34718b4714d623 -7fe41c9837f8519701680de26a0207e78a296f01e3574869f6e3b94e8a0cb9eb -7fe5a31d5480390b104a345a05c7d907699a0ba7dc2a0bd8726fbeaae0644975 -7fe5b504be9e013570a8685482c9769ff5ee0e16dd31f5b43cdb12ea87ed42a5 -7ff60e3a86610905c6bf4aa7299b8fb4d6abd6d3368792a124b65eecf81a02c0 -7ff6878fe24db6a61a1e863a09d0a2505f329f4493c83d50d6e8e68366aa533a -7ff79b05386f1f23711e1750ce6cd5203d361ddc71c0bc06708ecde07e25f4a8 -800032e582d5ea857c292508b2acc9bc2a466635b12b15311e601f14be0ab28b -8031c5552a25d80280eca45ec2e84b6d99ddd2670e3ff61a8aa009adff2438d4 -803a12b89ebe2b9ee04ceabe39b20dd8ce8828e99ebf6d2f4d74724f819d332d -803cd5fd2f70306bc09fb815968c81d035013ff405bc7061e1e951dcdadb55f2 -80476b4be81e859fa1b11aadcad84101c131586b87eb0fd12c0cb7f446d4f6d5 -805021df7103e554d485dbd30ea1028d0d7e8efe4289cd4e7f19f138afe72e55 -8053a866aeba1b9aa056700b6769d0a2e848ba0de56c4071a6e33ac9ebbc4684 -805b320f0dbc6c0d3eb56a0992c23ab0577686de83c9fd40589c3482e6c0cbf4 -805cb62034d320650541f60d2a4382a4263098165371d69cba9dd0c0850d0240 -8063671ba8dc304ff7eb8596ed47daff8cd0df541e85c22aaef56655c9e5f139 -806ab17037265785ccaec5db5037a88dc24c6b79f304c561c433c5cf0e8958a5 -808058a2d680c41c92ce204fd1f58d6574aea4a7153d937348a3c31912b1183c -8099bce323ad271ab6e0aa3f666dbb4dad55a2c308cfaaf51974025d022f9ccc -80a2f9ffc964f4b40fcfb6e34b04312aab4f3d086b84300f374530353684b3bb -80a489e54b22093d6843f41ea5f23229fd50afa6895317802ffaef0c5abc5d75 -80af1ab68c88eb12d38f9ef80bad47ac08b6baabaf2f126816b2d8122cb3b2bb -80b0950b4251030247d08109628395b456ca1a55559952b55e73ad57bb07da40 -80b19b88ff1199b85bd5e66218f8934172523333f38637c9000f87bcdaa1a299 -80b1d8df12c08c98eecde5280500a90667e5131263a23549c1276d7b7a13a2c4 -80bc8b71000d7908816b39eeb0735a6b1bd52cda7216e5600134a46e4a51d438 -80c0f564f4de731159e978e80f909234500239488858aabc43f17e9df5bc6fc1 -80c842e5aab8e884b9b10699ce916ec53ae33c87851c9d1491bec4f2bd495fb5 -80ca6cb98903dd4c3bb1d27f0c7db5314f004e49fb8013aae0796418040eb03c -80cefcd8424f7873217fee519cd4b39aba1db2e5b3a70c5c5074005b267c8140 -80d4d64420954c8ac912baa1bae1504aa6cb3ffb169d705fa41ec29faac09977 -80d967ecce3d54562aa58b92e46d461579c7609071e1778d0e3dca879746a30d -80db1a13e3ccf9cb422b9d9117a98d65d8f89fcf55b5d8305c50e268b8cacb06 -80df81af12fe12c48910023eafc8d3efb07bd5e2c19f510aae3f4a5a5cd25af3 -80f41416e1e02d20468f32f8290aae23eabbe665773126e94ab6301c1325a388 -81039826610019d24323153d8070a35b9cbb70176c7547a5a2d1fb89f4928ce3 -8109c54e5c601a3ef686b2c902244535e194d765e0925311ef7b966bddb136bd -811dd95cf96d416bc2ae85bb757438cc6195f66a4844be85929fe0b26b462aae -813290c175f78fe5d7f3946744398636c8de53a6c633873d8a26a7932c421b4b -8136cba520d9c0d25caa9573fb3c8553ee7c37f023ebb58470b860cc4d9ec741 -813a0f00452244fdb41231f584f3c19283f3d2f19dc2138dd31b848830b2f77b -813b1b0872d2476e7f42d199499df65ebcf985beaa18b45d7e8b57b81156d1f1 -813c89bf251f436098608c5d5d638979fb264ad2c5ce64199c3658326f4d7877 -814dcd9874913f3962744720beb3879d3bcb34a15293699db0dbb9004edb35f0 -814e31e7610a162a83a8c730aef2a8281b14f85b12032889dec52cbb9c6386e3 -815c64424dc00118cd2775cc171325c32e14a861506bd1c9113c4522b345f1ac -815dabcc0bc21211ba892718e0f23fc6f6cc23d2f207209d1fe013c1f1428494 -816354ca9949fe17e7d8e3ca2b0d09913e92773518e6204065c8d8ed92e0dc24 -8163c42102165f1aea78e2c3ac18e36d0b385465770fd856eb97ab9ede216039 -816f59840d0e455729507d7163c788e66b140a0c26817db2bc022eb55038216b -817d37cbff0c8459ac21f991afd5c21a4831348306d20cae15fd6bde2e33f5e1 -8182e9e47fed431d340ae4ce865b94a8b40c23b8de01b01ca1aa16f39da0e198 -818df3dec357c19b384e29e1df6af8b1543cdd4743887f82ded9e67887d05097 -8194c9b78cb23dc6fffb214ead70ba246bed5093c7b15a339835fae7c02ff769 -819623730623a8b6007689b7748570293f312f8915b276005e42f7ea43fb02db -81a63a6c83374812b614b9d380079baa317748242fff3c6f20fb577d7c024b46 -81af07ded78346266ae14c1be99efa0a68af78c2551f193ce5491044ef746395 -81b0789153592aea8575df1672034e936a633fab96a4f050f3002359954a27a0 -81b0d7459c845c07dca8e81b7258b3c0739e36bab6e6d2173326d2019b3bb1b7 -81b6333b09f97caad7f6ccf12e010d3077995668582ee5dd0385e88c9af1d301 -81cf5e758c36cc06968975b84534cb2ceff6bbf10ac28caf455010e0182e6b5e -81dad76b75e9ab1a88164b988c76dfe358548e63903bdc28bc1d401c13e0e949 -81df2afab9903c7c0c5ae956d3647d54b766bc954cc0c905ae211adea574837c -81e56e333a7dc094ded6204fca7bf50f48135609f91d1f5a8c4c443c19d5b19e -81e6a52433429a1263cf40a44ca5eef94fb62fb8244b595e5c7d456c5a6ea52c -81e7c1d5b74435fa3237ba588a3a99bfcda1b2c5d2e84db3e4d472acd4884bad -81f31c02dc0e75443b7ea38c57de0bac3387bf0ab79cacbe8ad01bd75ec1323b -81fc1b263220ec775e3a731667b42bbb82d1d60d6cb50f60cb7c49abf014db5b -82009fdd1bd7b5e620828701288a39c8fcf438aace6e534408c20a8950ab632c -8207c4d3b3d4958a7c7ca0bb44dbd0e317985e76a65d64b38588dc17d49a1d1b -82105ad87b25a99fa012e5d74587257b2ab5c2d64f79304351e334479c8f61f9 -82179c27faaa854a9230c2f40fad536a58f17e2382020c5edd2857c8c51104a2 -82179d7b613043a216fd13871e761919fcb634260233289a4a24f94ba475be0f -82290ea0a8d5926ac113ee9b2be724059cb6cdbb0aa181d8516d19ba8894f4ee -8229e9751df88bce3b07256df10ee790b234552fa4257a3c5c680231576a2ff7 -823579ad79c0d79cef4f2bc410f43587a01314550c493ea4697fa2f140542c6e -824571045ce55f023ddcec2425f96bbdabb0e9318f7bf15e6f6dabf5c1157e4f -8258d5a18d36f5e8c13491378d560adb434562668d0d895a6138926d1c37e428 -825b5dec36dfe556b8454f93faf411ace2a38916f9b72dda81f9220a08930c15 -825d33389c390bd1575cbef4f56b0ec31ec5384f43ad8e9f50ca02307b2e3f2a -826aae1a76e7d56dcf7faae62b4362291366226ff533052033cd6c59ffc320c5 -826bf2ba6ab4978c4a51104edaab90663ccc645188c74ed95c2b0f96f4cbc18c -826cad06930938966b484b483c5bb5695ddc92bf9599dac57736d8ec5a5f05c3 -827365f30c13e68b31ceeb1665b1acf6d951e701d9a8d58edb9fdeb34e799501 -827db4e165f05e550a150842204f323c9971d82946538f47d06d1396af5c9c9c -82823ab845dc025cdb28b15b724e000220027c197c82781ddd38f6358060bb73 -828743953f0ecc96ec0c5f37660635fd11c5b3f765bbb9fb598bf30c7e94cfa6 -82997afdb246737538a54f5fa50b042136745c29da3154fc3b37d4c15ae23f75 -829aa5fbc4f14984a0b006e3f91ebeb9acf74d663ecf1fca8092b36d3c92e732 -82aa7068d802a3a67431be0459bb9ba93670db64fd84232a894c0b93be81ca02 -82ad9da18ce96d4c5d325bdfcb9bda311b8726d5f6333b3866f9eeb43d1e0158 -82b514f06e3558d5b7cd5843a7692bcf552a3ec592e7f5706b4dcaa81dd64768 -82b55ed76275137b1680657e2d545c4d9c5eeaf9d76099ddb8fb6dc5e4420ff4 -82c872c44575c5e6ad9b39fd9b1fea4f18f1a3b0b0dd576bb8ce4fe049c34b6c -82d221b6278732eff71117cf8f865c2296e67db87858ad7960a19b127fce7c7c -82d2c2cfeb76fbcc1d753227b59b3ddeadfc1aa90d84ec70d381459ab901b044 -82d6342f19fb1ff0eca9dc9b382750f89c581abe503587ce41e8974a7aa0763f -82d9e324508be3342bbe646cdcfadabcf8fad01d76993fb1b6bbc473b00dd14d -82da587627224f0c51749702e07350f5ee546584c914f5ef8126cf3ef996b4d7 -82e67d0c7502191ec8acfaba32d671c38c3adca5c124a772b536f36fce2f1cba -82fa5e641d581d7ffa624fd55e3eb7cd018ad1aca42b52220defa4d53dd21f30 -82fb60b15786533897a682365c292320057ecb685cefeb0ba69ed7453eff1cbb -830c0912b28dde4fb3b0d006ba412e0970d4c35f60adebdaed9bfc7208d96861 -8314bf261274ea77c50500cff02dd0171ab8305206225bdfbd8299e48d2d5b00 -831ad77f6ec3f2b03e3460ac1fa265bb6a5e906a27f0c78b6d75c670f483917d -83213c3783f6425da86a8c79c36e2e51dc7eeb82569aa4fad567a9fcaca1fea8 -832537270695dfb4f4428623e7311c1a9968e6207fadf6b487f95d3d82d40242 -832d8166938a361dcd454fe41b8213981ddb74b53ebf2d82dd3cdbbf9472920d -8330a01d29bb9ec5bdfd9f6491fc5c97df80a5246ce5cb61861f3a2e3dbbe59c -833e059b152c45cd2d843e42781f590c91a76e4466aa64ee6b4a4f58c00b25c3 -833e4b1fae26f8f8950dcbbe9137be08f24b07a988dea07bd9f737684eb5a6d9 -8340e5fe713ef462809ddc35bc7c4f48c4661051b38dd1f13c6b7f8863775510 -83468a45d168be82a1d03a7be475a4f46f83c2636a237301938e8b3f6ca3bd2f -834efe10771777305fe12fc5cc993fcd3010110cd5019275440e02e3b0323445 -83537400736423e572454e9e480098966050f5d3c914f747ce8a7f5974b7500f -8354fe9e9ade0d789a56662685f6efd2aa60db9611bedfae3af6d3d6d28e5deb -8366021977d8ca7712119af42a3a6976069c324bb317c8b63521321e03577d5f -837b4db352ddbdf3cab31d230706a9f948ce04d307b9b7107aa2b4839a5fba6c -837e86e3397262a5926f67e3f68e42ccd2a8d4194eaade6f8c507398876e3497 -838a2247f22e9ffd171b625bbfa79dccbd178b52538ab54e3584c66eebecc1c4 -83965703ccb1ec54a30fb419a22c21780ecf8f4a7fc162f665515dac7aa5c857 -83a35f55d7e9da4695f8ce27242f442964105edd9b6bcca8883a89c54b07fa8a -83a52101ebd8156e9cfb573e9814262555a12821a74c992c4aae9bb1b463e419 -83a6ae6d513e67519e9733aab543d115aad15879b4fbfc1f881755e2e71f6dd6 -83a6c6f5c7b1824782b14d42a295411dba8fc06c3e6a7da2a88e2e9a3a81df28 -83b26b686379c05fd9078ad00f5d8e16f2feac3ae23334541243f6bb21584f4e -83be45ed14854b0a87ca44554add07ea2f67e5bd614a2701b245f1bcb117281c -83c594d6ee1e33cb8c7fc0853f35d6faf57bbb6e6725c05bb8baf5dde50969da -83d1baf81a119344d175eaae5d17646bf4ffc97fcaf93760507afb64db7f5dfd -83d3a76c61b384beca179fbf8768639455ac9153b6c6d341e6e9e27537121513 -83e3ae34b75adcd6ffada0779d88f36d9e45ab6ac5e8f1ed7e6175d2837f9936 -83e3d01980a4a6d3b7eeb86c0908b477c4adf17c9656421972e065f84ff9fd43 -83e5ff5d34ec5a6eb527ba3a13aab6c5120554e6cdb9d9d8702b074d7fe4cf08 -83e7b0da48c30afaff4e70adb23a4f4b90a32eb0223dca750928665e467fade4 -83ec275caf75cd95ff4b6f45289ba0017279df6767f73ceb59d19ee0bbfbca06 -83f1621260371fe85227e7150c2ea1d3a2f08b5a1cb4501d204e3907833ab41c -83f389ed7b547cec4c4ff7a771ba0794c878a717423f7bd494fc2ba34aaebe1f -84006bd8dc24c1108c87b6ec8e0edb2a006b04e3bef8394f7b1f82eef7dd2591 -8408064f8097e4a445ae862e26eade9f4e16c4cf0d48a42957b97f3dd84abd98 -8417d603a5bb301a60d0e968e875096013fb777079f2a3a4fb5a4111dea57c27 -841e97842a9b235932ec2cf1200eb269063f1027907ae6b5b21de469bb122229 -842997f43616aaf65f702b1c1b865d8fba665fe579ade57a9d84b8622de0ebb7 -8433a2cd2c28d92741e569366f160e54d415f1f319b7605373fa9e7d78c80ee7 -843659180c5c7fd3e94cabc88f5be6c64483390f28c55a3b4540216e20b141f7 -843f27e9906376e56591a558c6cea777f932fb3f639a98b9c50bfddc219d5f7a -8443a7e471aafa87732b384dec23d492af63c4e149172702f63d8251cd690159 -8446fd0b19231d95581323261a5150ded9a00ccebe0fe13b0a4ef55fc9c85937 -845272b9bed64b013f0aa1722035af238e66785c6c8cf448470e100f59bf80e2 -845cf3aad0ed9c310f4ddec149f58ae286db1da55dbf652110db01bdf8c7bb1a -845f5f34c3e4a6c951ad7d1505478f05342d554de502116008d32e00f5059712 -8468e4079c63a535f7aac11183680c97e9ae4304519b488cf951c83b3affd0fe -8481320c4f8f4770b8dde4388475c2b9da8b79d357e26313a1fae9454c777c51 -8482381a5029864664180045b1eeef77e6dbd4f252fc198e602b9357a660fb66 -84856841cfb1ec12173f2f64db74a3024b5820491e622b4e3a5a4a160099998a -84895649e73fc3df8097729a5beafe0c2b8cb75c35af7cfbf3fad898c89c3e1c -848d12de5ba0e9d93d9c53b60aaad7422881d56094c2d621edddac482ca580f9 -8491ee81cbd5bb51f76977896457829c6e302498f6cd66be0f54f88dce04192e -849f658111db356a859424a255b03f2cc48334b2699823a18ebdce671f7f437a -84bef94bdea5fbef2351f6671fe888249d8826eba152f19e3dd0b082e70ab962 -84c57c98d23a308dbec9a7d1b657128bd77bb20d0890e2dd8737f5b44ff4872a -84d8bbf5503ee1ac97adeb366599bd1212cd569f0e95477c7f60c80530a6627a -84da61a27e777a0cd20dc721a47946d0ebc025ee524bb694d43a12c816c6d24f -84e22f84536c36f15ecca2f29802673ded2eea95fe850ebd2451c2ea95f77c8b -84e45bc1f32b9905237191f58b4a05b189fb779b87319a1ba4cc0a538ee9ebc0 -84f286b2a0051243cbbd5c0aaf323158948cf03b8b1be12bf44abacbe1fb5643 -84f475b70e0ee99b0fa6eee1fc8243701f346c447a1446466ed12ee0eee09d9a -8504152e2a3c3cbb7d63f3baa75d0ed7e7f272bfdf2666eb0e18fbfbe01dbea6 -8506c3fa4628603e190906ef29a3bb418987cbc925f1515605ce01cd96a822e6 -85134abd875d966fde282ea5a2617c98b1b1a6422eb6664b4372752d52d45cf2 -8517f9066cd2e6e489b53312c5e22ccc32664628b32cf4a2ca3cc24761222960 -851e83062c20c6b3f83bbe080f7ae2562b14e96f5ddc96985bbc9c331004aa76 -8520c802dd9e57cf57867db14855022abed8bdcce32f40e18f202496f7464def -852b219e3bd9fee50c0c31dcb5fbe5f25007de97292c20194aa2a8a4b2ce0e16 -853157759efbf3dc1fc56af5adf9338ec8163051ab61ac27e7ae5305276373f6 -853402091782f1cbd25a78792f1c731580651e8b3639311be8429137ee6d2c2f -8537b808744ee68b8aa546562c571392d6611f0cee26ed2b33a68e2697823ced -85471d228fe2af1103fea7076603c2505ab20a4290d2757a3c9528e10d8b9c29 -8548ccc1bf58bc6fa38299da784446b3aa3b46795e517591ce75349508c82e3a -854978fff1942577fd7a83dabbeee7125a819a9150b78a5acb3f2602ed38273f -8549ad888280e7ba5f4f6984f175508ded4b9ce852d61730f712a30fd417af63 -854b57ee720a69a252bfd614c80a5316707e69d411e4c6da8d58bd4a92f23b7a -854b96bd3731e0c0d68ac472283d32cb6836bde639d5b56d4aa9864c88ad2895 -854ddbb1225b306bd2686a8ff75a38eb19e4e667276367dcaf804e1e0439f17c -854e09cb0fa81067f8180ffd9b0a22004861b1ee7129cd94696ba4d8b614fbe0 -85558b757d45d0e55846db1b1cc80e6a6974e618bad8950c1ec8a3d3acdb8e73 -85589625e63dd226c872407e9634108b88f8454cdca00bc2e2b3a68851ef4c9a -855fae35c6173a85f0bfefeacbceb8b10e8adfcd5363dce915b0a1b75c69e3a9 -8561ec1d79b71ec536c11a7af019c6cf1148ffab0b5a3c42f6eb689ef5f5338f -856378e53d502fedc3f7d9ab0885c1f3e7cb6e80f61f3c150ff0fcacffa43a1e -8568a6d88f6f202ec0b5436629cdcc4385e1c3f58d69bc4dc6323c5a59009fe9 -856c52206c5bccdd2cc470e5f294903d266901318e0d29537043493e71adc75d -8570f9176e828df97983db2801a4c316331a5b883e6d1094581bf8f9b4fc537d -8574b57dce3f43e721d43bed62c31915bf1b50b12c5f23e0ff3a0b41f66a609a -857a2b8c51fa7ae85202348f1445354f47da1d54520069bbe02dae1c89dd29bc -8582370d15e9e6120fc7b4bbcd47d58ed5a70632457edf6f3a8ce8265085d344 -8587b7ead18b83ff2f80412e3df3475b3a924ef16a9aa8bef73cf6426f8ee6ac -858aef9c3a6d42d6ad15147a9dfd751118feeac436ea381db165c82948de6106 -85a04457af092ed67ecf9333e7fdd0a7c58491498f86f977f4b81de13bba7974 -85a19bfa5e852ea211c8ca69a9e3410a0314d62b2d8bf07314c8e5108bfa3e94 -85bd1aee967d749e3b88c1130be6b1f524e555db0fd8c906d12e678734e441cd -85bf4ef425fba7bd1f727d929d611c88375c7c0aab1d617a5b7033f46fb95740 -85bfe748a2850cc7bcdc5f8ecd249c5d84bef52a30872fdffc14cf0a41ae1fa2 -85c0acaf6ea94d58af83caac91fedd0aaf0f9fb8b9a94477abac79ded83dd86f -85c63d1d06a3b23d4fa9428832e988815d4ebce84589a9fc8e1c1b27c088f061 -85c6a634d2a74c4fdebd92efefa4402f69ed8fb4744c59423e032f3efe8efe32 -85c96d83074cbbc7044c1058af1997d5aec2da3abb0fcf6e4119be6fd88b3a46 -85d341c2b3478a8fbb18f8818c94373ab68eeb0bd3a7f9e84cbd115df7dfb2c5 -85d694613b2efe443bc1360d6f036031ec5882b1920f412f8c58549999c23ed4 -85da1b4df6443da615edd392296480b80420b44b782b6041d8262bdb7f4a3fef -85df8fda0c77d349c89eaeb86c3362ba9068b94bf5850c97249969d694ba2fbb -85e4ff6630aebe8ab821650062add6a2d5afc5b9dd464125fe640e178738f955 -85e543e70e3d04ba4f234b7292961c4a0871d7f89ec5cdab120d0d85212c41ec -85e73328bee95a43499fd15e381b067ca35004aae357079b4d99d8ea3258268e -85ee2b7f4c33d5228cea5034f214bfead6fe50396836b45e819ae93e5d5ed5a0 -85f2a3d30eed516d5815a4407dea2172ea401d06096952c18a31dce95c2b6a60 -85f510e563edf2b6cfaa9e69e8e71c4cb20e6ccb1a9a0f2a21e56b2dacbef133 -85f8bea05f53193b2d9dacf663fedbf55f1d546007bf3ed3910e60dc49a40b48 -85fc165d2e6b04d5c69e8a85cf7fc8394d6c1de59aeefb65938599dd1a103b85 -86046d77486c23a87d1a4b49759cb7afb1f5132767400a2a8e5c9504efcb238b -860548ad9bf887f1a3ede8bdacbd393d3eaed3af073bb0d2068ebf9117f72c10 -8607f3fa017b10c721c8c7117bb72e7b0e1c0aded022280fd1959e9e3ffd7b82 -8607f42b8f5b3b546c144f072573f0a5398d1095f3a3dfe80aaab563b1766406 -860af8b5c62f822da80bed19e6711f1964e6d83911b5e6378a708c9aa43c7b59 -8613f4ab751a08ce780f2460add0e323d59a8bfb69dd395c2cdb473925846831 -8618e989b0fcf23823772378391b06ef8c7a7931b35a9ce3f9a59c2cfb20cd6c -8625dbd83ceecd67aa8236918de1458674e9cfcdbdb0c66f13d22320b5618db9 -862a67dc229834b0d368629a3bf2a490f0fd03a1350ebc7785d7ac77457fb7f0 -862b5cdf24d16dd959e2585c9beee2496cc302bbe2f0844aea3595b383f95642 -862c37566fd856b3915471ebb0d3671bc644a472ce9dd5286c9c8fc985a473a1 -862cd522d01d55bf05f0ef40a9dbbf4f722867cfb5e82646e59388ba2c3e0bb7 -86306896edabbc3fa8b1faf65347991347912a4767202e9d4275ecce7a668583 -8647ad3beee126878b632688622e5a1e01df6115d147574fff56946f5928b614 -86551659adea0b4be0c9231c0308b191f480d9c875b7c2d93fca223170b59cca -8662f8eba856a393d856170204888d600821aa28a590524393b052328a3efac1 -86668d5ab1eb299129859efa774d517e45db8fa6476104e10908c22832e98756 -86701bd0e59e492f049becef8406509d1f4ae77db688aeda6b5258d0f5399523 -8676c5b7fc0318e528cc84201af9cae7844aebee4a84a478b8f5df193d1d204a -867cadfc919e5572e96ee3a3c667d20a201894adee9f2a713515d399e65ddead -86906b73f17339600415303a59f796f6d841afe00ee9333424b29eba3b198dff -86a0ca09c4d06d5f61bc1a52f0f304f8d9635a36297f724bd6d1fa3d87dea646 -86a356884da5989e123f663bb0f764c61b2af867f35a7ca0e841b97d3c7a760a -86ad9732b0e0e4b68fd7ca72978071dca6731756aeee4908885b2dbae52cb65a -86ae7928275ad239a048775adfa3271b41e08546704e1020d0406e240e6ba6b8 -86b02db07cf18176d139d828a916a8dfc259f769d42348c5acd4888e44f81a08 -86c0d37ac167f67ba45bfa33c7159020e4486cd09bcb3a50aa5f6f0916c65ef1 -86d56e43d4cb7fcebee82e0afc18ee93aab9a5f50d6a20d4fb8039581113ea52 -86d71e5ec2b9d83a3c14ae352590316873ff774cd4d1782c89f9400d9eee25c4 -86db19e6033fc7cf7e2e6f1f25070adace58e492df1f263b1e4af2740730cb02 -86e34791c0ddf8a9f085601698c2bcdf76a34c37aae14587a0ef35a6049b8f3d -86ed23779fbe7443d6dc181671baef433651af4e4765fec5fa2c054eaa58484f -86ed8657993fdf82c8d7f9445ddef9ebea33c3b101e7ddbf7ff1715fb4fe60c5 -86edfa2b5de6217f0f3bef0f46b74f9bd859650e838edb0efd7752529370be5f -86edfbe5d26b8e10bf56a94631920f4e40967ec1e407d795b7467b17ff2cadad -86f06466e77138928dc320f912722ead6fca4c26ea7a7b7490a124742993d1ce -87025376cc455ccc4162c2fd778eb3938a59c8394289cc8701ac40503f5d4c5b -8704feccbc7ea49cb4654950f5f2d8a2f8a991795b04d3328ec03634e9312fee -8707ae49633b064d033c02eb32f9077881a62cbd01f0f4a9c7c771e8f21d4e2e -870a32dd3397392af84b9b823b89db4937c96515b5369540709ebfbb3d76934b -871567ed77fa88ff53cf301841e4cc59b92db7c16a44d0b6f6553234e722549b -87232d9171cd2d073b779563f3eebdcb32fa0eaeb11aaf95e7ebff8243e2034b -872780e3fc218aae35d7540f073c92b040829b1571bc24bd207585d6e22c1e7d -87331a7a696760a69fc01a7d1da18c609e01ebac4024b91c7a21da99d0738055 -873bb75c0ed1d25fd1ed85777fa020dbe6ba10482653cad5d58a232329d50500 -875243de245a2081ecc3f40e8598be1cc5664d158a554f4ab33b3ab1086561e2 -87688b7721cf9984e9d486f92066b0dddad6b5261379583d270f7cfd4484d72e -877098387ee8cb2416023dd7572a7483e625c73a97d8d1776649737e694f094c -8770cee7ae7ca7501dc0d7b19c0fb2f84f531cf9416850c2974402c29501b2c7 -877e948c8663a6587d9ec6c2a3ba18da649c4f161ba1725b9c662a2ac0f7cb25 -87812ceca39991322821729694b04c40a6e55abd6e516eded1c9c898c8624059 -8784f68f0e0d9329b6c354aa9325a89dda70b7f933018be5d543d0f96e08e8fb -878cc754465d176e2a1426172cbdfc882e7fcddc3a402074f2c7251df45cea26 -8796079c4a18a5a60c8b497da5505f67e0dfd53657cbd9d1a939f757ce4202b5 -879934bd14635a85eb74049b3fa19fe24106624b4b77455ca716b556f6a1ec8a -87a6f450fcc01e5728ebd9fad3086cccb3577228a85520ba3a761987ab148310 -87ae46104885893bc99dbac4edc1e7db7e05e936609f59b778cb2ff41a758244 -87c8896cb4bf3bcf1d627a27f250e8a7f40c75f9c97a710e51e4dacba99981bf -87c93df5a9b69e2b201fdbe8b2d3593002e49edc0f109be78cb16c635e3e6e21 -87cd7194660b38acc82fee512937bb79be3bbf17b472652b7e8edff6c086dfc1 -87d4251a1cfd071781c6b30231aaa1b2f70c1aa5b9c9521a052a7e74bcd8b7d7 -87dbbf1028c082620f4558efec771b5ca1d7d55df0627a78c05175afb1d81390 -87dcdf0eab8b4b2d23264b6ade92a8315850a770e72c890f12c7a8e5b44aed5a -87e10e5c27f90794e4fe53395f886af01ecae6fe7430a7674fac22aa3531516b -87e4a5d944a7ae25fc9ee6b1f1c9cdb5adda30e68ca1fa956adaed3239b3c4c8 -87e84c0b3dcabf89a01b0a79ac71429b8dd0888984090f5b52973a97601fe5b4 -87eb9bb9c9c152f985fbb91146266b8c3521f8ccfea093f901085ce20f3408db -8804ed6ca3c6fd9c06bc513c7a449bce2f8c5d35fc46539bd7f2ca6cde57296c -88071955564b5ce1ab170ca07bdfaacd5499d20ddbc3f47420b6af7a5b99f14a -881073e9d0070f8057711d6c37210100284c30144c8adac76438c1dddbf5ebbc -882492cf9af08de5f2823bd32006fee99264890bb0cbe7ab120b079352908375 -8828b37967c74d7af40a5ffa950589aa23edb16ada3143be2b5f155a5cbc2eee -882d1e15924b59a3c3f666f952a6df3b3c90dae071187a794ac9400828e5b3fa -883332ef82ba41a7c0e5b15b25c6ae081ce8fd4690e90f9e4f5ae004fbc2c8c2 -883791b4e3f89a482a7957abf4aafaeb38a11198d7be9800c7c0aa61a4733afd -88443302039608c9f59c0f80335156022ec028b917cada88d64b28c607d0ed88 -884a26f679cfabf85310122c3690d432fcbd091b8e091df284ad73d72e43939e -8853c4ac1e8745950854d87da5daf1acdf895d8e4d21648c225686fe7889acad -885a32acd95bff685d479059daa882b9f278cbed0cae0fe774943d27dfb0444e -885d35be2913a402c9feaf255d75310b204edf5d1de2a793bf51b3cedd0033d0 -885d6e99d3b0de658f63c786410825b7f7474aacf61dda28a39f4bd19daf8925 -885e43f0f7bc7916b44fc54c48201752445783c0224e11eccc76a7a880c3855b -886257b0ae5b377537c8e8e394b7a31f9904b5cd1971c8216e353b2f93f97d60 -8869efc9cff4eccda4d730bf8cf27e1f87a1f3ff998d0b9348b53e38be8e5f76 -88813d751cd3c03e678de3ecda34cbe2f1a9d4020d3db9ae77ccf2e522ae3148 -88822cc4933d70f19f3cd004801efe2d0d5f75592936a2426d25c051129a652a -889192731990ecf68b3bcd4b98a71690a54dd8d5b129339b06719f6fdb04cfde -88b23e40dcace54067cbf851eb3fa2bf8a67d133ca0ec12152259617683a7ce3 -88c45f5110016afda2545ba5871ebbd7d06145c4a80dea23094b2ae60933f272 -88d6182b6ca0aa317baf4b87906a8567c3873e62c6d5d458bf830fdd49348cfc -88dfc516e159be1650d49337af5c8a2829cd89d72eadef430786cfad952656f2 -88e5cdeaa7450486db005e493b204c11350c30364450da4aa6b67baf7b3ab609 -88e86b3e74f3cf3dbdb340b9c468d17cd112c5e7066fea69c5b9afcd1ded796f -88ebe5c724f21b540fd547faeb7312ab05ec5ab928f056463682c3b43adb51eb -88ef3fd185b7a50dcaf841710ad87fa8109336658541a627e81c74e1c3f84cb1 -88f88c168675bc094a476a5376bc99a8d9ac5bdc11bb992ff8633e592a1f9406 -88fe7748bae8017aadc6a4ce977c4b49f97711dbc459da0dfbdd5e33fcbf427a -8906ffd831de533ea40dc4499b0eac91e9041b6994a0b480c8465e675d3ea2df -891053caee73359fe2e4fdeaf2c6731dda7916a6b267ac3944d66edfb53eeac6 -8910ed2873e3835a7d5f8e3b635395bf8705213dae239b36fbc3f6bdf85fef7d -891715a2bc1693952c61f4e51433a47301cbee7ac764038c25c57d8327179c97 -8918760f2f61ec15e40d24d270a15a26d83c054e9b2dd42e4172b56acb6ddf6e -892066e1b9124ec157ea11392e07ba3a1d8408f4891bb3efbbc7d44993f9eea4 -892392cca3506f5d393a0f97dd27a762d339a0884dbdb0e2e62c86937b632672 -8925b201f3fedf6838ad09024950744611f802c18a1924f71d7173bd4fe7f6c9 -8937937ed7e713ea138887375f5317e1437600a6c7c6d39e36b4aa88e40de2a2 -89390a82cc1ec2e8076a34dca2d539879bf5447aec7088eddcb47e3012498a75 -894f3038517037ddc32dcb895edd197c14b07f84238d27078b1fe7b4e646bb60 -8955065af4df1b777f9c5063bf3946e28c302e3ac37ec2057255c144eb9f614e -89563336d462dcb80d03a7f9ad05c5b5534cc4fb0e7ea0500c1c5e3cc046922b -8956be43a0932061bd256ea52534847e15cee84b3aba3139bb24e2ce166811c4 -89579b0cc3a0b67c519e3dbc0a880c1701f53046d7b94182a9c76ff215c8895b -8958ad0ace694dec7734d0146c645ff7d214da3ef31c4481d4f35a82931db49b -89596d86fb0e93695160bb1be677daa25f66098fe89485c4b3034b286dcaf2fc -8963e4cdbbd150e9109358c36a475fea24f97ca76fdc2ef887c488cd08dc9bdb -8967899ff027aa69bed2ec0fad77a9e2911b3cb866b769ee9a0255aa1a376fed -896e59b4350fce734343493c6da6b73c30981fe8f634525607689068c3ec4a5f -8973beba18071ca2f3e2212e85a2a2225b377bc3098d2143367c2ac109ec68d3 -897a58045d6b338cef4da1dce18ecdcdc92406b953826bd454fc558153f8db14 -897e6a683964d402277c64e0c7a3d2eb41743e3b692d7b29d5abb9ab89b52160 -8983c466be8aca62e5fcad8be6920a9609ba043f7ca78e2e30d940eae74df8ad -8986c0e2c58584a7b901fdf54b633cfcb380686b81af28d2527355fb9b9fbc55 -8986c438d35ebbad9a81fa42bcea6f2f2989728951ee5f855fc96ee47297040c -8988ec652b2084bfbff3d3d813308ab84969b6b47664abb4ede56a9b19d80827 -8994ff035af396162cf0b01c734d7f0b1d7ad585e0e875100fefce12a8cd5016 -899fbe3cfa2c3bd915b38d0abe30a29adc54013a2530d5754e8febf5fcbbb64b -89a28b9df52f64c16abb4fb118d4263b7ca6391866849fa4780ee59371f33607 -89aab9c9e712c9a6494f825a95626ab2f5820c410390c213b0362e5a6bce2be9 -89ae8d48888226111461f0180766d9e831771a331371c52bcd4ef55eebe7b696 -89bbfd22e9fa9b0ce8d5f25c27f86f76316248bf9f67003e01e5f001b1ed32b9 -89bdcf7a95747895a68d6960d8cb19938834908f6f3feecdd424851d8127519e -89beba49ac83440e74a42dc5240cb6bf1779c6cb8edb97ba0e2952ce25a70a83 -89c8ca72a441659cf5e20b9198e9c6f9d93c94fab31da663463b6a2f14036df5 -89e6884d6853204e8c4090756561b11964e51e3c7a1ec1c35d1852f639eb3596 -89e77eff3283d7b1fb5425fb7c53d14877f18b0f847923a2f673b7eb24d32d4c -89ea84c2efbc5a569bb246a6f7f5ae00f52ad225d8c55c4b0490faab289cc6cb -89f150d6538f3916d0759bd768f25e7b6e8b3456d11dc815621480109ee80a13 -89f2afe1051e1072b5cbbf1098f378469fe4666555de185c1a7dc13fd21c7350 -8a12255bb00bd77100a30ea72ce5ac1fac93ce84eadf21518e29073d5163be84 -8a1b0238d6ee8b8ee1af5220ba21f60ee3e0ee33f4b95b23f79e89c0300166cf -8a2d1eefaa60109cac4b64a61474bee1e730f1edef4c781151060de975eacc31 -8a2ff51d527b0314ed1f0992f89dcdab7337be52b09f561fc7cfe7148746d412 -8a3534d64c7c7816ed5e536e96235db4668d454842d3ae1ef2d45c951e51bf1c -8a3bcf9059d1fa5a6c04110751e45823ea3881a097d93360d4b00fbcceb6c302 -8a42d4df26be91beeb7dd8f9b1d0c2d5efa2bc2dc44da1041fa6a01e250961d4 -8a4d203a2df71db449bc6b99dd83e064365a5d3bfb8afe8b23f167d5f082e84e -8a4f16d441f35665efd3d5c9bc8886b148e72adb060f574f10a260a0fd79efc1 -8a6351f0d1f8b13a90fbbb25d38aebcf81809736b16ddb638fc497edb954e32a -8a76046294038bdbade8b930fa6f7e67e74a6729b3ee81196c7d32280475aa5b -8a7d833ccc381864f0cdb3b4d38bc2ecca9528b9f7936796d0b06efd8b0a5fc6 -8a98808d675daec74685fc3d6ff174e0010a82fe7513f7f0484cbed35d8a598f -8a9e50920d6adb98abcdb3c1c00e89175acbf2788115bec65d7a9c9caf71ee2a -8aa1bfd55517dfa4b8cd0749d9021eb1e8726f99c89ebe61b5e52fd9c74abe6a -8aa702700f42049d8d9bc18a54d0ff5ec269c13b383245a9286d7f70e0747084 -8aa95887b9a6871ce9f45f98b4f615e36b2b9f3b1a97e75ee0df0a2650de2c6a -8aa98430178e7da2b14b42ebad9fe68b33e21381a26d7b61e3fd23534217fb1d -8ab37cb4f6db794cf99e8d3b6973db2d264d9ebc7a8fb8dbbd8241ad84ab12b6 -8ab577c41b167d68bce78c4906da2b865f6dff7c5b02308207724c9b0092d3f9 -8ac360c0ed4ec3216bb58013b68a83ba956a4eaea45fd987b388cbe1b646759a -8ace04732d2444b763f985010bf63d072f134c5d13741d9d1c1a56fd74fb0cad -8add23f42bca4a7ed58e01429e4bbba7874df3a33f2320c773fb38ffed0d290b -8aeb6a7e35c5cd0752581d92f23ebba309cdb2f028d4dfcae07af0348cac3894 -8aeedce465fc1a0c2414e67e62cbcff47fbe2bfee39cbb7c33a1b6bddf87f5b0 -8af59583ba534da6da15a10b8b8847781fc2650d3ff98317cf67c69fddfb1d7b -8afa44cad69212bc43d534b0863229c55ec7bc05f9a84c5c57879b3089297072 -8b02d0f143f092924a38506985c7dd671693129fabb0eb8cd5158eb5226ea3a8 -8b11d7026cf3eaf4e5680b307d206b5419421db24938b16c6878f2a093b904a0 -8b13876d21e10ada406055aea75d0808e5efa902813987c27dc01497fef1d81b -8b2367191017105b2c14c88be0e68b0e26d36182e80daf9bdad4f2b4fd73e3ca -8b247efd445c6113db32c033c4967daf642f10a5ed10eb6fc76fffa36c8e1340 -8b266f86fa3d1488c2e779b50e716377e6d2871c985938a03d4ea9a2028620da -8b2de9f9d44ba375bf2e510390f4d7d60877ecf4c2e3188b3a045c520ada33b7 -8b36399af3b8da270db640587913092789fe84469de61f14144bfb18f462e689 -8b397a52015311114cac2ac189cd6764b820d6ef671328a225498c004f092be8 -8b4647a5a3a663ba8470c0efeb906208fb5c6a21dbbfaed1ccce1e675f01f6e3 -8b488e679bb216da5679e5db05b4616a5149cb5d2626fbbd29809f5352649dc8 -8b5881a5f1c759a1a1ae80fd661da1d252f24aaea13fa82087ff5272b63bcf4f -8b58edf4137036e3da30e94f3c92ceeab41a2ab241ca3b994d7bb3bf35abbfab -8b6203484bd189d8464296015659778eeab5c6da907bc5b083772043365efb4a -8b69af47b4184042aded239d4438155a454889f06ba9b86fde7c295f09917b65 -8b6e1e6b2d9b13f7cc7d3c927d515eb094fa324d66e267f73676d854de526dd1 -8b7026480afd91f98007bf673e70cec836cc453f77a3ac96cd31f52aec4e889e -8b76c3f6125321ef2d09d84a5ccd893792f8e20d5023665c7c109db28969c4b6 -8b82bb7a74813bfbc989d9a355008c717d42ff6af35f50c1a2a9d77fdac766dd -8b83751fa011fda23ea30e13e29dcc055d4ae77d3902a31137f80804e6dee5af -8b90e59aad2e8fcec69a839b0bd9593971351f22a27b43431bc6768f981d2597 -8b94ffd8147ab3bd5b41c554a7dbcca69ee9c053a44fce0cbc834901ae2c0bb4 -8b98064c448fa7522e92873b114647267c9fa7a20a86551c6f8d440927ca51f0 -8ba7a7d75e15644ed74fb8f842cd185aa1c03513196e76db800867d0c0aaa3c0 -8bc33f7d97163c8a3103885d707361183409a4d8920ebf9157ea5c57e38a05d3 -8bcf6e6cd16ecaf12c7e43aab0ef2b9a1c846d7dd45db3980ba2cdd31ead111b -8bd8506a521834c6b7ba8498347a39cf37bcaec7987734c4d8b5ffd363637da2 -8be20064b10dce4f6f81537c46a3519af7db1c13fb04f7a320cddeaf2e89fdab -8be3e402d1ba28437bba5f948d6637e0fae628e4db0f2123108a2db7628774e9 -8be63feb8d68ce26d902bcafc9eab1351ac577c100cba0fed6e097faf788afd4 -8bf4093ee644f9fff719fe9d3550ce5b31c6c21580d78306468a00acf4cd1979 -8bfc028b868ce3f4099f864d27bbc35459b754938c6e70938ff6e670cfc4a320 -8bfd96e92d3b6f1a0a17714328260a0c2eef9ebb2a3d8d7dcacbba30585349da -8c0112048461ce853ce823cc84d20a6d9c08effb74757a046bcf1b3e8ade224b -8c09df859d9f4d10b08426f20b6b4f7068eea07a1458687d08a3613d04f0afde -8c0bc2e8a19da1d8fb65ec30a6cafb134554a40041738c77ba29e97242231d4b -8c110194096d213ee7163def258310620aba47dbef8bbef96064b587d310d0e5 -8c1ddc8a39b0e5e42e92ee4fb81e2808083381f612c1ae507516ed790694a780 -8c2d6156ad49e0aa3c06b4d25b1d9cf90e6c4afbb70eb8e568f1a2b2dcb50ef5 -8c300152186a77e1042bb718b3cdc28de627acdb08731d5d5b1363f6fef88cfd -8c414b2644d9a809b78fa8cf4d4c388103c95118262959a3c473b1d48dba0689 -8c4167bcbd2ba55e79f06cb1f4f758b19248a8ff952805a8212604a3843c89c9 -8c4f86f14e3d4829db83fd50118fdfc7aa484eebe95f95086e614965ce4c2825 -8c4ff44e566ce0c55deb2b9f5005f6a53035e21c4a6b941b330624843618d1bc -8c5891772cde426cae8ab220956a61e73e151d186a0a544b3a79ebc56d6956f2 -8c64a11d47dc4a89d04475f8db75d1cbe9a640c41c97ea308dad7431728afa38 -8c6fe8387ff68120211876b8e39d392cb35390d675def8d8a4a09b373d131885 -8c70ba302e8dfdf413e72d09d6d53c2bdf9f44a33877840e8ae0c1f8aa3a86c2 -8c75fab0aec71d2f45601e0b53156452bdbcddd2d4b18e57baf4950ce4ff43dc -8c85f6fabe846b14b61e9dc52c2d25f881aafc675ddd9ca3551e238935157811 -8c8698ed9d24d31fbd1dd681a751442352178c5e6005629de9a4d6bf8bda073b -8c86c26010f99726d7b778b166f90be450a34a62620ccde86423b0a86fe59daf -8c88b614360d38f429cfa0d6450ca659a844446bc209abcde04f5b7a5f984ea0 -8c8afe70589eddf522af787fb9ad24d2bdb1ee81d37fec1256d5377be8e2637a -8c92df08bd15eb4837c56a4002fa54468a4a0be4914dceead4f8f94c06b610e8 -8ca34e3175e2298e0118986abf0343545d918eee9a0d644ae33be1df1e0b4ecd -8ca6a14d6ef0781a14c32d2d376715707450a23e56c7660acc91d5e6e3d21700 -8cc4e60d16a0be18db7af9b68c63ec31863803e62ba9f783cda07eb601f9556b -8cc866c5f949d01ea9bbccf6b3e67122d82dcccf8f5f8bac33f743aa1ccd0a7b -8ccf09c2e71d466690e42ebd8f7fa38c0ace73bb68acfe3c5dc91d10d4b77cf6 -8cd541e30833d3a8d4e2c132c362a6f893bcb8c43096620909cb9cd1ec3c292f -8cd65061b373b933b7248b8c4ded203872d16eb399e2bf168c3026fd776ff482 -8ce850585e50097ab54ed06ac90c07a6d676edd78090323e48cdc8871ca4b718 -8cea6a56fb33fde544d31ea352083dd57ceee735ca756c3b1269c61f9eb32399 -8cf02949ea03d3c50fd90c5d4bc16c7263ccd28a84fc86cca9629022a8b3e2e2 -8d028039df903855a6e450720c9472bbb1730f81edba243e8eb1dcb4f693d16d -8d05233bdad2a0bd6380ed1f2e0c885295627fe794f549400deb0fe92eae0060 -8d057ee7183b7c1764dd462883575b1ffa9087c26acfa429a516810b70e6ef3d -8d0babe83ba07126421df23085ec8e6ec48f955846ea0a6d8ea539cec173d679 -8d0e3a1a6e7b3bb763f6c852074a2d450debeb2a7fcd61b70c922ffc021e40a5 -8d1375f36cb6631eabe8de4fc0ef22f0cd949d0af4bbd9303756afe4e4758694 -8d14cecd96e0b387b5b6094c9262b866d02e6c43ae05b42331c1ceec4834893e -8d15ce86b92066ee66f0402146ad99af1d6125ac94899ddfce7ab6e9c0bb8347 -8d167a0e89469c6a7c415a992cacec369af68c0f81fb76685566f79e34c575f0 -8d17c91680a2e62ed6c9c3bca4a6488ba8aad16127d7ccc002d1c39bdebece11 -8d2dc2b103a2c696b6e3fe7686501bf8aba02ddb623fe1a96486109601cc6e23 -8d2e371d2c4c842acbba5170178c0fc369993eb0d327e5b97aefc451252400d6 -8d3c00da2ef5a0e8837e46e3148e99200143ad2638994816fab102f7453b28d7 -8d4c4722eefd8e1e5f8bfbedc65c354234e80455835f34890de19b277ef1143b -8d5101440673055a2112928a9d840b2f6035118f47d5e46b42361a3af1866b11 -8d57f63e55fa46edce3a4872c57fb7e882a1640738dcb1e08adf31b48ae0cc0a -8d69bb9dc108b85d62de16b4e0052b751e5a6991a2b21ff15c59f773bd5833af -8d6cf8b402705f1d2d05fa53bc2167dc587b5a63cd454de8186aea176eca9994 -8d72dea6e7274bc3f0a651e0c3feeaa2aac27290c66c926d790d8001139fe8ff -8d7f9fefd644404079b83dd098103431ec551d8e66378408319a77d8c99beda6 -8d83b273112990986fa90702aaf1844522d4512341e90b8eaea251e3f2e0db4b -8d92819324d80ec2b96be244096fb5197c2fb45f238f1bffd5be8a6692477614 -8d959dfa3310dd089c46c66f4a63c0e6a1138836f6330cce05201fb8a54237ac -8d9b77393663ae0124e7b4adb10171197a138cf5ba9d14f37ad0428b3e109727 -8d9da206f76c54727d956dc3da02dca44bb95f2503f16156e75395032ebc8200 -8db59f4024b3b52aef43ef0b79e261bc10611e3c5f61a4826e6cda91118042e6 -8dc81af526820507ddfcc0899808452146f1731cc547724a0d6fd18607a249c9 -8dc8cab0938e32f4c207eb05a732999a21a8a49c761d0d7f64c96246dd8d23eb -8dd8d737b2b090b120cc72e5cca243885b828dc5ab2e738ca04bfebcb8a3510f -8df216b2ed5fec55dfb8f36c316da7906e030c57b9fa315999c1538e5b0cc9c9 -8df4bac374081dacac8196fd8a19c4251acc47f59f1c805b65b35552512501b3 -8df9a8eb7192ca1441d9132bd620c7512114719a3c48a053f2e1cc54b2758a97 -8e01704982dfc199fcb52d426ce00582bf9f5a443c3c7b1d1c3bb9f668f9a322 -8e02cf1df6b3c6d45176abc45808e9674a88893f629a37ec5680ce770bafd1dd -8e145faed1bbbc4a62e079a084ca23417977862efaa516befa2af3d39c515305 -8e14cc56bc6324ea88a4cf2e6724820585be030a3ba13a147319310307afc680 -8e19dc9148dcb362541d88ffa0477b3864b18b791dad01bbaeefbac4ba56049b -8e1c4e12dd37992d49a783999f24c44b4daefce2d5b76537fd40e3030aaf8ca6 -8e2cb30929752fa3535c18d65d90cd11b9e0e4e1fee77b219c86a3df86fff597 -8e318a48e7080b26ea47f09dc60829afa0801a191950211857d227fdc80ee2c2 -8e33e2477ebe9ea601d62a915af5842583e34bf9312e5eef9329579ef56f29a5 -8e38f76553d0099343703a7ea2444fb449696d4f7e9d766d0705aba37f701731 -8e3913cbd1f305f7172197db1cc13d18dc5b23c5a281dcbf31296e5d4b655e09 -8e406d5ea8d58c91948cd19005ba18bdafa0e85117684a62c133a2808d750873 -8e50b1d05684be7f29a52cab5bc5d5e9650b3c747faf5c153c25a9657b7233c5 -8e552bf0bac38702fb3b1c4a9baf3adde6306e361d389cb3781b6f9677742936 -8e6f2ee2d675a0d65a0badad70f7415b7d1f76f4a52ad06e11f9c6b19186dee6 -8e76a854a923129f810264b9a9e22a2959a67d76c61b0293f200a47597a920f6 -8e7a8e0a9827f4f4599d81d24936decb0f05eb9c0dc8d4d923ef597820dfb431 -8e7ba429d748d92fce4aad01f0ddc2a56ca4a004ea0a4aa330e05f2bbac3bf42 -8e7d4884f4162747b177922732aa4f02e9dcd49a024cf18d888a07efbeb0663f -8e9ce4ad836cda3480c57a80d6d93a617ace04f31fc80603e2f56da2fbdf4fdb -8ea12a37c15a049e72f912dde5ba598703f6b85627faab62e189aaf66d53e4c7 -8ea29ce286d18eaf245fb44ff4c14e4c417f396707696a71bdbd015b00854652 -8ea57ee8aa6e4d7a5568def02656b06e9708111d31f7dde48dd50f704f24394b -8ea688ba33052e6b79e6bd88013d4297aef04a0b2f13e59545aa1267d2ca09bb -8eb4f27668a8b16054b816e45b50b245e03859eef136edf854128c90d0a4f13c -8eb7bcc479e09210b06878feeb122e81755881711caccfe08784820f01be26d4 -8ebb45992109631adb8625474350999fdd5745e43819e803570f7005c5975ab8 -8ec224812550fd4a251d230aadf90164304f5411fae4105d521914f7c34fbfbf -8ec27d43adc5e0f6f1e9585a3ba81d124621979a322eb4ff9eb3e3c5e3d1773d -8ec7a645b226dd825056cd9bac7fe7189895a5306b49110aaef8effb3432b855 -8edba483219d2807454401c124d19b2612742b96b3d412d40a0230daf8032ea2 -8eded787b93ffef641fce0552d795db81c3b8188d4ebfecac35798b33956e632 -8edf436f83b9127da385eda60d2a13fbf4109a240c3fa4801b0e9c5b36cf5b2e -8edf7f3270dcb73d1e238679d6ff054123ba5c1c540fe31287f93e1434ada0fa -8ee3c029918d5f201731e782da5541fe7b40dc86c441e3650672c844d97bea75 -8ef23470459d278fbafb0d53b1eb322ce02c11cc2816da86c363c7d1c8e69807 -8ef5eb6e17b78ab37bf70e5a9b14ffc65e714b69a90992584bc3c6f220095486 -8f0ee4ef4cb56d38086bf7eff4554405438457e68dc5ca69cd8d516fd5e931c7 -8f29765016fefc804c8c3bba30c2d45ee5c6bea090362f15864f896e5b529fa6 -8f2ac7efeb04c07f16751b9aa12a691a770f54e1536bb9cc935728c93c58dfbc -8f425fe30f7732b7d5222cabaa2d995c58b6250b949850b8930db607a2f94209 -8f4c24caff4ea6a40c23f123970821396646b5c19f45b82c425f50cbe4a44e66 -8f67bf1bfadcf48eb03e42eee2d1848774a561be0a70e12167e5d5df954cbfd1 -8f72e219818fb2256fd86dc6781a64afcc53eed983a6a06f7cfa30c4031fd76f -8f74bf29a0dad00ce12dea66e0f246bfc7fae3f757606e4cfa024dcb975ffff6 -8f7b2a73ac08cb51095a312909764240efef54af32eb007ebdf3bfdca1aa55d4 -8f81e2c6b95d8c32c38cb3411ccaec6dc9b75c896c3d09e1448f8b3956758287 -8f8543e0269d8acd01327c981b4faf950948c85a7029994460392307554c3477 -8f93de3fb49d4a22c2ab23dd36589ba43370b17e5298035fe0c55c751a35584f -8fb206c63d4263f8334b10a6ba5699c40aea95d52b8d10dc916e5ce7ebac5f94 -8fc16b1342ca1fdfaecebfc9bf71597cff9a5dc00cc5634c00b656e952884ebe -8fc3aa74ff84fac62b33bdcc6806d5c5c2adbfe71b1e3166a06c9ad2dd433cf9 -8fc9ba184229e85ebcf27d0c622111da892b4c6805603f48b9a7980892791181 -8fccc070d1335d6c2220a3331632c3c930659618ee756ee75a1cb8efe2410310 -8fcfe419d8450cbd1850dc08c4a7b7084356de2f74cd3671a42cc85a3ce4c370 -8fdde227a449432c0fe4a09ae894d2b8d5bff6d76f203869a4df992929a4bcb2 -8fe0b758585a8e861a53d45c43445342ac31b9a32e42f63bea5ce6ee2447a7e5 -8fe933b869eff0bfae92dde4497547462c9658d23b039dfae25f318f303baa23 -8fea93a27c0821be309f989a859df10d468c6e61d1910885c289e9e7850dbe82 -8fecad8ee2bcafa1c0dcf3fc2af22664ab8eea89bc2dabf40162ecc355e8c32b -8ffba7a6a1b2ec50b0878b884fab06966a3d2599d0443955b335b20014ed41e5 -8ffdfee23d8703e9ac820e88643181e3fdeb42633674776340520563739a59c0 -9005b7c56ec6161a93e35771d3a9ecae12ef4fdbf75bacff67a3223b277d4248 -90143d3d083237736ec3bf31e409ad2253f0f7da418c77e3ae8a9b15e23d5a42 -9018066c6d7182b501674ce4677f9370b8073fa581cf085564d00bc4f7b60b67 -902d4105c5122eac6a9cf7f9e37bc3cb8f9531cb3d580ca4bb3b2b0e736cb970 -9038ddfa38da760eb8628c447467e955d5e3738b94c576f139ea0a9bb9d86abb -903c5c701d85ebbbf88283fd091ae673a65b0f7d5c75b1496cd613f82c006239 -904946aa56ff52c86e2e7dcc891cef9fa197203e1a4df4ceaa3a3d7912cc9735 -904d812291211711db747226b6d65567aed2355060b4135a5625b47cc62b1b28 -904f0bf1141f5b85605bd69a231d3bd5f5b98a8e814e01f3cdbd2e8df0be19d1 -904f248629b50c08983f8bc322ba87fcb1d069fae1fa5ca8b1377455f0c5143a -9052ee07e6779bde40eea5cb07286e29bd2ac44638dba8fef9a88322105e17de -905d25a6e41778cbc01224b5872e1140a588e4a264e52493eec40f7f758eefb4 -9073e527d520a7820fb0d62464fc59e98e4b9f81bfb3c9f7104f5acc3b5f68d4 -9076a2ea346004159a0a30f83a066b463929db71f4dffe11fdb039f27100433f -9079bc6258ea704b6da722750004a445c2f9bad832375d9482bbe44b3269aeea -907d879cc49852af168c74d3f4e8dee849e0117409361031ed48c2cf0eea5898 -908141635012dfa09de724687428894e03aff476bd254361e141ce77fd35d933 -9083ad6d11aa80b450ae51e1062463b8ba03cffd7ba50e36584646fd1876ddf0 -908d727ec15ea41071d24bbc0f3ed1af66da261c7b7617bfa57687fae35ab38c -9091f3ac59e7a35babe626d5cba1a8060970271d8354e220668b0290bf4c8064 -909294ea66d6fa09fea0dbd189be92d452ad0b03f7bab893ffd1485180af5a67 -90a9f77f2b7c922b85f409aadc163fde14be3acd98663b1a23c638657067bd61 -90b6b07ebc02d6ed55859d0b80c712b902fdf3a80291d7cdce9a71a9a75f068e -90b7d6365b22dac621b9163766f629731b31d8a17f0f898e82580cdf47a8c8cc -90bb1775b7d90d9b395d38173415a0d7db0eabb7d4f7cdf7d95390a754966ca3 -90bd8212e142f79a76c2b5a0a79e638650d59e30d6b8857778695d0b14cd0228 -90c1e975293148ad14456bfcd431567364eef50a1dc5a46f193da370a1eba9b1 -90c3a2939ecfd598b61810f2a14bc1fb7780fe5da31f3acaaab478ce04e2f6f7 -90c45610806e38fbd17d0b4840fc21ab63385eae3c8df083be8f9a261c0e8674 -90c6c04363232b8c9750e9e8d677f2c7ef14e707cf24432d90e982d0a16d3ed1 -90c77f29087f2d51c548c00d01c2020845ff6651ec2b1fa78c546c9c878e0cb8 -90cf548d52b79ec48446c2ffd993cfa115d45e509a724734195caaebfbe5342a -90d01f259ab15e02f41197ba40cad65ff4abc5ccce2d02836a23d13c1fe634f0 -90d47dbae0a5de03b5edcd38a184cdaf80985aa3e1201e7fd5800371e3fb787c -90d4b19a9304a137ce97714afad820089dfc8c16e8bb8084ed9e03bad2e258ce -90d882e3c4fbbd67a464f6b2b459692e349a7aab0584df01442febacf513f096 -90e1a771d2629ad91f8959cd4e7590c971d7b1b6cc2f5cad4b4a3628cbf57114 -90e9b150aa4f3861dcb7e23037103804ab338807db1a72d661dc2152fd372a3f -90f54665bcbce0473c26eddeadb952b99577515a29d5008d50d815a0f983d508 -910286347c801d08e26fc711aa924612a1fe19f279edf5957a3584c90fcb155e -910892b55d083c7314cab65f53e5af2fd184e45257d3f080a7e529b3cd7c64d6 -910ce79dab8b92549da077fd2eb191aaa553494c0a6b9b3ef90223e63669740d -91123a4a14e06f9373d716c561a3d759e39d1b9277a8e4ca736db87b04ef7d41 -9118f64ee9dde3b118619232412649bc768a3358f19b067ca5e9cdeb3736d670 -9123798d4ab70ff062e904d578f7f600640c44cac30727bc37c4e6fbea8798eb -91287658aed90dec5be7f631a6f1a0cfc24622b1294420983c9c8e3d54604db0 -912c2310d5bf97c08b400a1d8ead4eb3803ab463e63c675985644dbab8294bca -912fe38d0465fbff6d74aff51d09182408f93f33ec1cdb0ef70b0a7561e3256d -91366c2e82731835a1717c1c63b3bb0e03b1ec90709adc0e4c2388e046be5313 -914977354cc1c4ddc881974559361f36429a9e28b230a8e982bb4878b9f659d7 -914a75d2ea9f9208bef2f47c296ab431f3e8ff30d835cd0f2a786b45a34d133e -915976a2d6a30717e51833fdf0bf5f18f4f6b714dc89b29efa089d12b5130e9d -915d3d2805af7b223bfaa4846944c3270cccdd8468ffda784b37591ff597c4aa -9160b26f5fa14e78eed89f9f3ccbb7e96ee2fcee99949364b4d7cf0885b171dd -9168e5997dedf5045b3239008b6160a8d118cda24b5449c0fc8bbc0b63276f67 -916dff862d20940646d0772f7756a46f9abdc4095a7487ce4373093849f4677c -9176f0e0ea0ab45ef37b462c9deac240a6b21564a812af62b031d5686b32a849 -917c4eab6bb396223cde628f21319e1699abae1897f169639f4fa34967907d3b -919524b54da24444a67677ce62a8325f8bd4b5cf4e4ff1fbf19fa989a8f8af99 -9197bd990ad08f021b6327f5bf74ce053d827b299d3c6a77b4fda9ae8d2da218 -919d9aacf1307de9478604195ed7ba7679fef9d9c3f46f6da9661f9161302a00 -91a8db396706f900c7e1dce1344143f03fc3d5cfabf0d00c545bb3bdfb15c62a -91a9c9a7fa89ab12b47ebdd5d2d36f636de4445afe3de12e417908cce92631e5 -91b6b7dc164359e8c0348d39fe5ee587ab3cabc2b5daf7850933ba15b06218a8 -91bb52e0120c0f6f0898f8d8160610d6caa1f83388af2c6cb9b98a9a0341f456 -91bb9d90ed05f519c00853c2f2d4353e419de012ddf989b109a0e15317ef0b5d -91bfffc008173c4fa273eb0536dd5f5243710ba19d9d3388a05e40699ba141b8 -91c063c59d13ad1b5f7d29e652e3f8f9f97ac1d6470570739fcd432f6cb46e4e -91ca7f46fd31cfc356cc13d52d1002d87c63bdf211be5a8d182f3c0a6ad7729e -91caf2c64d0300aec9ff9533803cc92a14fdd773772d0d97e1937580c40e144e -91d790208c96097b951b212b645db6ddb3f031e68f958bc274d3232429c64dd7 -91d9bd72e919283e6f64116416cac7fc159f81d6ec8b5b75e9ce0a4988c9f0bb -91dc4a3a849c094b32e6c3b9da5f5da57e5ad4c17033081e2ddc332afe16dfb4 -91de5bebefd8bcb5da331b5410f706104317bf087bdc9aecedc12b0a701960c5 -91e430bac374efb7d943f6019ba62122bab1097237ce4b01fd3a5e32a98a9fc0 -91e4c7d0558758bf1b048b19244cd422a20d912aa7b811c78999b9870df76426 -91e52e2229879a42714240bb58899e57ae9967b6b135fd9a0262874060c44ef7 -91e57b369502e522c899d9b99570085ad37b5195a583f55c8bba7bc04b212e34 -91ed61607ad7a6c40c829b590b78c2b83474fc218184821f8a449f903e553e36 -91edfbef4039de35e851102ec22894420932772e5c4e5482c2d914a07fa87e60 -9200b33a382ca158317a31ab06e5b90b6613cc74d207361a3889d54f488fbb4c -92102b5e17b86b06928ef26ea7799ab94c7e499677558214da7470683b6f81ef -921610a15b38d3d771df505482f5c9b40f98bfbadd70bddec39e0a974795e851 -922608ef3e569f55dcde69d2845e41292bce504225515acfc5b9be7508e5f63d -92267051aaaf439c9478cf47c2183a7fed9b9ae318afe4a1687e67925a5c96c1 -922afeaed2b7fe794b6532bab2692b5cbb0418f2370d2a6c2721d9013909a006 -9230c7fc62bb7e3599e14c30e773e7e8eabf472791bc9bfff4c447bedb676a17 -9233dde4f348c4e80352a951faada231e249c4b8d3fb4ef14ff54468915a9c54 -9234f2b9361b98e535ed38637bc12882b6771192b56ffb5f4a511ecd11a7efa5 -923b807154d0f895fd6d20d2db2121426dba739b8e68b421a08802bdeda8a09d -9241a5093fb3bbf3d51ff54ef7d289fa2a153ac861b0fb292a3655a9c97ec532 -92435a9972b00e130028968c199e39951ce32ece7700549f142cb764ee45e5a9 -92586fd604bd08b8f2c8e4af722f6b1d61e13ca236cc9326ad88fdbf85b3836b -9258f9b20db505812ae7dbd0d4acd75e08475592a4bbf84ad22b77267abf1952 -925f694e1ab7fa1a9337adeab960a6d294a246b0388d57374143d15da51c9bcb -92632485803799880195cf1b3e8de79e2a47dd9d9d7952cee4932205af5b809e -9270e12f9e99046968c7e9da4c1fac76a6ae8806f11844efb524007aa6d7b0d5 -9272ca788da480bcf5ed2e544e6a2a9b77ff4be64d2c3e1fac4c2a1e44d21b01 -9277ffe2641f04433c85d6074808e54c27bc95584edcfeb69fc68484649bd9c7 -9286dae8993a82a87f3d0328622fb61f07f7b1a27f425bf536596c05ce940e61 -9289b7bd0ce198b5f9db368abb2351f92151e8cedc219dde9969d95d27fbba29 -928a77616eef3f05e8b9e0f8410acee897f0b507bde9608b1e3f8bf62d5ac862 -92a77d0f84c52f2d6ea3c611e75ba46d8f1a03fa61d8a93594ff2d15ac460c2b -92c0b44993e59f3d09e7f7eb5539bae8cdbe6cba292d22cc48fd5a474c9d454f -92c97792f7214f4aa5b3b0f572a5250a1d32c85f240b7e65dbf804c817b7573d -92d24c6baa21a153df933aade4a8379d50a5b742c6eae4089dc8323a5877c971 -92d2ef8e380444eb8dcc54b30bdcd155f07d8890ad340a7e942d97fa087e28d3 -92da3a0bd45add320b3ee3648afb6128d835b53786fe489f6280a3c44892415b -92dd6882f3faeecad4877620f809f5620105875513f930466d6a36e27ff83db1 -92e24082d38841f951cf288e36c81217b702456c84f1e39b48494d02250bb75c -92ecc3b5aa8ee0b37e96f5a77ace94b6ec99a7c48f944e069123eebf3daabcdd -92f5cff06ef7c0cf1bac1d8e8781e4e5d76a7a98806e4d871b82654c453b9bfa -930953c7d3a91cad1745331e775c3461c2994b42b2183b9a933bb6d0e83f8cc6 -9309f90c24de34a8027d11438819f413b1e1efe8f7297a38ffd4a565b71851fc -9311ed577d4664cd72ba43fd1853b75bfb5849be00910a333d70a537f4c62588 -931a99410304372eb69c00c1b786383d3b58933ed2425ea6eae1770ef78b506c -931c98dfd1ef5fb28175867e7fef2a9d8ea3f2e5e87bbf0015ca55312ad3e9ee -9322dd9141262b8598e07ee5bfbd6ed0087f67f5cb07e04ecd1ad07e53e91926 -9323bfe67208af28b0a15762be64ae5b820e2aa9ffb0b7c581bd8a635b25aed3 -932b33c19a39baf69c0dac2c20162cd165a3150af4d4573bd4d92e3a76293504 -93346596eccc6f9932e65a95fa0feec4bb8c98369178ab4e11347eec5a1b3333 -93533f15cda6a6dff2a1f582b4e4c4a55738f2c07e99fad55835b1237ce6e7ce -936eacbaf31ab3b35e23a8f4a1a713564e2191d855275259c1941feb9737feaa -93747b750b272b9eed27c0e3fe48a96011af6955066777b0a80e68658c167f48 -9377f8820d94b1815e44e520d5a026030acab61e2c375b24f52568d400bd7c88 -937cc6c974855ceb8ad23530d737893f38c1d037350b2df920da37f164dfe2da -938d83fc49f8d8d7827237d44820ac864911d9203de05f3653d4d6143d4f704f -93a75b23ffa8c6da004c1921b2b12b15fd083fd86b2b2eac2830d0e27d12f4f3 -93aad0b6591d428808a81d7697a3d86bf4806661778beb6894f8a547d032aa1a -93b19d84e9aca692c75cb160a050fb53d1b211686b3f4c867a4e09a9b0380027 -93c2c439f48db81651d6e6c6241cf0865f08a52b5925309bc314348745f71cee -93c63ab5e0863e5ba86be4b1f9fa0463eb4b8d10f6a2ff3f6bf91d6e43ca5dd5 -93c86597394e3410565d1a310bf97099bb7c1883d4d5b5dcedfb95ecc33d70b4 -93d03ef70f78b59edc0cb0dd76c05a0e812ef4dc02a918aa755eec09f40ac26c -93d128126403257c96c8966a7bda097bb9188ef35c420696fbb82b5c82d01f7b -94102b4565cd4af3587aed1f4d3c83d8e63778b70bd4d1af6583204e15eeafa3 -94123ce5546af7e18dd6a7bef1391e5a9049d9c05d9d2f77eb283365f9f3015c -941e2af55e334cb43d53ead2254503679fd63426ae3ee4f7f3b800b3e586a0dd -942059bb56ef161541caebec257f0f02159e53c3539d9884c3d84fd2ab9470c9 -9427b78dfd6cfa14ea0770d31336e7215e1d6050bc8070d85519843049862332 -9427e8b5f2160ca54d7454658529bdfe182d04d7aa755d29426cace45447781e -94297b82934468d7b1f05773c5a978f25f1c0fe22fc8f528bfc03aeaf2ca60a1 -94313b8dee74f27cfc183e9dea118ca4073290dc98ff6e4190306dabd91ba661 -9432dc71bb273f82fe3c032b302d39d5f327968e5b4641287d7b7ff13598a1cb -94382144ef98f4aea8f3f3fd641beee9bc54c8d53c184512c9ffe1018634b6a8 -9446d788319fbdda6508ba91d2efcf0a06f051f5cd28fa3a3339836a1cb7a380 -944fcb5bb940e5bc701bdf4496008fe6844676287fd87e3478cdb4d67c8b5b78 -9452e4ae8e09a61dff1ff724c1732a16d3652bb4a3370365ae1fe4ebc65ba364 -94556743c4052ad8c972474182efeb90e659b6b1d1ad07e94d32d09ff4807d7a -945630484aec2124f7d216ca72372a68cdb556620eb435049e9452a4ec9b27be -94676bcf9e272874eda032e29344011d188615b4abac356fe646192d833e08d7 -94707406abbc7b6ec1b8974e70c7a70cfbec55b2ec2848ae321eb3b569b6bf5f -947330c773409cdb0988c8c06b7f466dc4ac2233c12e14a17276eefa17ae661b -947a2d589e5f1e57c1d8bc18879800704afc34fc15ef7b471c4d33c53f0167d1 -947aa58a33dbd9766881c00ccde4167fe1129e62d70f942d1859f8374acd970a -947ec5046a70816176d171c5d38af4a6d986b1d9634fb05aa3d77576fa441476 -9488e7d55e48447fb3a88b9e75fec9b3bb7692005315599080c91a8f6d2d52d5 -949a2b5d8d85b67fc936e00c80cb9c643565a1ebb25c27fb6f8d472ba23a2444 -949a74e0ce4d6ae0f2c73a3c510c73f4a9f466c652536e29c066b2eeb6259d3b -949f4be6f6f146f56ed3236bbfbe0cdb4369f2051ba5b9d4fce3f767bc33c884 -94a2f93130cabe4676817587e66ab026b0dd77601d16532b4922f60505c4faa2 -94a54709bdd59f1b204d797b058c77de702555e835eb29e5d93987c0261da860 -94b5cd8e9dfa77daffc009983f5b7b426c0158987532e80ecc018e4650d976f0 -94b633f40407f2854a8be1ee1571545eed87bd68ae055590ef6ed55d741ebb9f -94c8c9059143ddef3d628251896e81a7766f68363c02ac45131a092c125d9776 -94ccd8c9ffabc804e994c1ace40f00006e305af88138d49490020d1728d673c2 -94cd1cc90d6a27f2d3fd338e23577b18edbe9cb2be6f1f4f8e892ee73aa8cdae -94d66cbbfeb551ce717a1f1e58c0a87beef381cab451a3ec39673447e1ca864a -94e11228c54c36e819f758862fc4acd75b4b3d033d27717cd842aa3a3ed607ab -94e82c1f92d33e1d93575d28da618c7a8a4808487d9ad5e68e780fa453a51822 -94ed9fa21d06bb27c0a88a96dca84cb0a5ed94e1f3d11b819278bf804fe3d915 -94f197e92be351e981dbb3bafc8c9b5468f84022928e55995a73c32b333f74a3 -94f34d995c12435a9ea1671e3c4992f4d96af7f255c8f6d9236f01e19ebbadf4 -94f59b224825a6e489f057a385e5cd24fcf8d294f57a26c7ac2b2cc82b7a7d75 -94f5eefa4e58b497940134de85c5f3d33a0faf9a0c20ecbcf03218f607bf51d3 -950e5ac8c572ff506ff56ac57b42951aa149332103d1f7285edc84c55d297079 -951279a9e6a5e1aa00cded0870d1f88edde080ea3072886dae6c181cdda0089d -9513a80c715c4ab938ca90d31d2e20f06eb98549dafac082447fe86b23bc5c81 -9514dcaf92b5e5ca6e5e941eb44de031fa4e2f52223ee6230c4c5851e756f800 -951783855e759701d46ee10b5c82f7df5e8264cc21ce37ff56253d12e1c2174a -951e2c6fae90e3516cf27754fcb06a2b93242c5b6de7b781b9ce9438da363b81 -9520517233935fb57b90d11b801717a0076b77cbb8b6aadc29b4383047e46dce -9526edc8442043c655d2fb5ac939d0e5d203811fae0b437935a34a535616da6f -952af151eb660f7eeb77d42e3a86d455abab6c3384bb7dfb0db4925417eb1e70 -952efd4b6f05431f13dca7bb582bd771ebf27bdedde60b4353140c4aac10192e -9541a21c5de1b8b8ad7e6413f2735366a15d5b025a8575e69c14c3e212c669ee -9548219a8235997f7e60060f415ff1e8dc19d3ecaf3c00302f1730a793e67ea5 -954bd0d9a114ee1d2c96dcaecf06c3fe20697733f2d35eebec346c35370bb281 -954bf2711a14f7f6f6b6fc036baf757d05d8ed9fff8ec1590caf1c6112a7c462 -954ca5fd903a5e7daa24e75b295018a9bf5ba63bbf6f59bbe093134f9d09efa1 -957095eb388ac9f26a222cdf66759ce464343203f49e34a57306e2aa694372ac -957d098b0c2f6cda1f6806b96f72ff18ecb7e1a8e31c6cf20b0cfec1a467f451 -957d4c61c3322e4512350b2172eade8de2d7753a7c1d203c6289a1e6457b715e -957f448319660f30f53554bccc248172ef9ad23e168d5b64e4065a3cb66c859d -95866e8a99578410be5bb6accb3b2070413abacaf4fc257b74321ab76042a0bd -958785d307c5a2ca53deb5c3fd5d702c498152f04b649a59543f4674465d0219 -9598560f72878acdbd5ddc766a4075b93981d3898d4d56735a633f504e43dff7 -959ac17e68f21e18b4e5e78356be8607c25721b2fb1d3e601b099b4ad16f16f3 -95aa76a10ee2071d078adc7081b5dacd8e5aa7c07384e6938521ed6d1e88b55e -95b0cc24b563738634f69865ac84606fdf209d46f15d80e5e045676ef9ff5421 -95b464255e699c121f7162c080b30ca8792d52db9f9a12fa34f054c90ad1c38b -95b8acb0aeb747d0002cd31054abbab1341272d247667aa881d5c4861fe0c3e4 -95bb6dcc4a608e8db2b44c7a7fc52530980199c893d1285cd8e3dd35fae57cbf -95c6a9dda11fc029e041a590c7761b73ed843c99504bc3a10aa4e0b4cc15d4e8 -95cc4f1a32bfa7c7baffdc1982cace1505203bbda841105ce2c3fd2f5429c8f7 -95cd83186afa909c68b24bb212eca34cbcec24025316a3eb87a39e0e52dd506f -95d0f25476918e43924be137c17abf1839598010cfdebfd5a3436141b6a2a6d0 -95ead86e17cd26e9b7c8558e3d7e7902b25b5015b3f55c727c1f685ad6fe5d15 -95ee621d59d679324bf57926242ee1119f4fc83fbf47d8d529a71ef85f737321 -95ff63a1b05e7435f26a1ea1df089d07ee013f7af2bc10e79e986081f538425a -960da285833f3cae0a7327b46fcf33009c92be9fc455b5468cdd994da176c3a1 -9612b587f63e523e5c3908b450132342cec88dc06075b5ae1a71a40bf0f97273 -9620b848a9706b218b0625681949897c230387a278ed0f83002bc2e1da096add -9622b3054606d4d68bd0952bb5a1949f8481755ecf82bc83c270cd37eabdcb92 -962f8de1f73aafd650d469a4e011e84d965f5e7f814ff06f3ec921a761e910e9 -963266626fb122b429490aea0f79bbc7f6feb400a2fe74973c1bf631402a36a3 -9636c9eadfdf0f30c7169f6bb2a8d5656618f708d9d7cd9858ecddf9b449a405 -9644dd53030f22ffa3ba730610cd44a68150bf49df01be149bac53250352f2f6 -9644de0e6ab7e9cf07ac34dfa10ea802d85f9fbdbb2e7e0042e69bff3184f875 -9645ec53aab386da03056884d02932afd7510a8fd55706d35382043d2d47480e -9648fa2733e50d0c7d78d28e3b355ea7c0eb817c6580ddc1b7b19f7281762bc2 -964f980f7aaa138e70cbe1860943363b053489d95039d418c46908298130f502 -9658236a94b584f56c88d89ac6efe35d78e31ebfe81cc3cf91847cf80eeeda04 -965e4190bf473498fcb80d59821ba1a631a7384358da1a1616ec41fed7bb75e0 -96731e5a3869d90aea79b3b3e10186b3142a6d3ace02234fc4d4b082402dbc15 -967373357f8718de4b428fe70076b699a5c22570448a237b285bdf20c79c4509 -967a3266694e01d63e23058a3e25abf5d0d8079db662545f48b45fc4dcbd3018 -96836f80083438292c4938470c773dcb5f8ddbae5876e6cef09bc2289b01f9fa -968514d4e7abab05f288fee095237a874207475f70565a4a337f8b943341ba52 -96907ec095f1cbe8f8570577b7f028deb8c4425bc9d2c4864a849889920d0349 -969c43454345078215f9b056b6e373c34d29747d3c960d6bb31f2832444f4108 -96a1544b8f0d373aa682f0f368867666f1d9c062b2dc2d9f3cc458c1fde44ec0 -96a22e7c12b4f826c060ffab7b068cd8fbd2077797f512adec788f6a3464d2f5 -96a2d4b994e3daa9dd590a7a038441686ce337e181371649a075aeb00b677483 -96aa692860361bd4eb8aa7338fdf859aa78124c2c653ed2fadea4790fda53769 -96ab05eb57e3a8b383a695eaf74169605a300420c902ec7b6b5f2abf0d13aada -96ae76394785f68677e9bf1de4a3df75c851c16b4560176d47992cbd89e40f45 -96b1fcb8b96baba1c7431ccb0afefbf027fb49eaac3511cf3cbf4eb9e2181d35 -96b5bc336b60895d4bc7702d5d5d243a3403ace7af72271f86a0a7d7d6aac72d -96b60a1461c4ed533825320365546cd58c96fd1c364bcfe8a85179597ee1ae04 -96ca94acb8df57bd3c2d4210b125cf351ed2cbafe51a1a999eedb6da221fe877 -96d3d53d1753764187d1d22559d0c2229d1fc0971912b3dcf32bfdabf101cef1 -96dfbdb09f43649ce1616bd5cb26f23c0b82c1e53b1f08a61ed5d86890314dc5 -96e4689ecf197e113d3b99174923bfaa2a4723a1b243c79fe5e0389192a58d98 -97026bbf2ce7de17dab6e845f184cddf97810337984c9f6977b13cca02021a37 -970b972fe561f9738f0a87911f4873f19db5051f4cef5512624fc4e6dd140ec7 -972486fa949fb236cfdbd825f1ecb62cf9715e461dc7182776df47bab5618b5e -973b5a5ca6184f1c300bb894a0b8fdc5de0ade214ba2c7a199596996abdf1d9f -973cf8ef8e43230590355e3b54f68e18820571f86a3e1e7f5c106559f6e083aa -9742be6dac034ef552847c1422c9664d330a7d44dfc73d5984d97eba812c3d2f -9744eda1502d4576dc6f8f4af379464517c84c306dbcca750adc8c995b658d3f -9760b88a120c143805a1e0ac2b3708f079d41385d86e99ff265ae076f5f6ddb4 -9763d013c802c4f18cfda359493a9b40d4e6add8712f504fecfdd3c2ffb0d123 -976745390641b5b44370308e1098565065644e275f733eb019ae17edf18830cc -976ad0347e95f5d8e0b8b4e3f4b89521f63d15d0945960c435123b67d4421f7d -9772b3adb04f0b4c21d8a6096ea0fd1b8230e124cf000220838d88163d36a1e1 -9773b4856de6e213e79390614dbbb788ba70d825e0041bef40975edff9328c1f -977420dd3857610aefba5aff916fa87de8c6b556fd774c7bff0161120432ccbf -977daf61cf430466798f8fb18904f0231c579f8c6a53dd834c6aba069d0049ce -978a4b5e504a2118cb38daba92aa8c9660c3b35d17a2eb0077b51b6338457458 -978c5e9c27d60eb8ba2bf9afeede3f93e58f01779d2ab658e686baaf582d073c -97928935321e986972ca963ca633cb33c3733e36d1fd46fbca425a447dda5956 -9793b1dce616db5760aa2734238d08291e89c8dfa6e86d54648f2d5c0c15aea8 -9797df1d9ec1aaac62b30487189124744da4b4a736225dd7122d12f37a841431 -97980f311899c139c32ab8c6fa3a3fc3cc4a5abd5175c31d2e5f5390e7d243bd -97a245cbae50fdda2fb30092787e3b3298c009f006fc1df428cdeb9758bed743 -97a2c2cff16db628b6536249a42997b98d91dddc3fc19339b812cc787c61ccf2 -97af5cfe5a1fd01640e2cd6ebdb739f34afdabad0b6e839ae37ab67fc8502cf1 -97afa88c1acc47cb42f0e646dba38608a37f4c66181219d93ef1c9a660f16879 -97b7c3d9a7e1bf5cf82e00a77da5422212a2db2d80990008fab17875364320ff -97bbaa98b35a3188da63059f835ea280dd8d55002905728ae9e3f39804c4bf2f -97beb832ce301bcc93897f881235782cd363253f98c9271a9487c0e5b87bae6d -97c6c2e32f03678c70446379a3936eab2c656951a3455199e53f2d349269e812 -97ca5d20688b199f531ae8759488bbdc0c1ea4b574f7f1a4dd43e9b25fdc2eb2 -97d7bd891a6e96f35ab1dbb3ac2f335351796c820d0bb2254a4d9affe0880d2a -97d7c4442b12d9c2e307a2d597a8ebe011b1a856fdd52e52dd2a4cc23b7017ca -97dfbab59ca920c6229e5820515471fa7b23fc515f03bce512c857d4c6a3889f -97e6e1e29b1baf50bf71efb78f15f364ad660ab7ccaaad158a916ddfc10e7352 -9800dff8ac581c370764e0bf67e8ee7e2690dec616401bd2a4eecd944be9aab5 -98028a435afd31477db77eb44c8b7141cfa3ceaac10b6de3ca8e1551e85edf05 -98057578f14016906acc6e052f71887f9e446373046b0d37fe354ffa856f3818 -98164a6507099edd2e007f4677055045263f2384f8b1f070770365199b5377d3 -9818aea8be5faa64be197d264ef652b16e3bbed6d8b865308da9a1f04263de0d -98196f7a47f237ebae33a3aed9e3462fc787cc2c129c014d049ee8afad3d176a -981badc6ecf4547605c7b88a25e2839095c695f914b86bc98a369aba36bd3f8e -9831aa4c519fd0f8a855d0209afd1597b42564f0926a55c2c5e3ff2cc4b7d731 -9832c762842d7b4b7306d301f07f39b4a554a29abfe5b4895e9834c68f3dbcbf -9841f21652dcc1df339c6c89db9a788648f3cd2a77b5148bf61b57e959c27ace -9845a75b8831c3a53104b43c9bc898c986fcbdfa2119868304bb706beaa71376 -9859932cecd81281a790b528d1f627a41eaa5bd742f2a64e5006d35cee27045a -9862b15c6195d0dc9fbc1a6a44d549185155ea84bf37acdbc977b8c93c414d13 -9864014a690353fff549cc1c8a0f005beee6cbd91d150dd5b3f3e257f64b33a4 -9870b5c3484e4971c1458bafe9afcce02f9a68d4231ea3ace9d9fa2986d2d8da -988073da44602f9259bf13779af85b71d98838e1419944aa52e4c05b02198902 -988cd2c286dda7a518929ec0a19dea647fef38d9426a33cb34c4670abd027708 -988e2160ece7bc11172a8a7c9652b31d6b8c7a22789a8ea7e48eddabc0f86ea7 -9893c29f245610e57d7939025ed9eafcfee57af35566517711f580627366321f -989a100141ea4d58a6925d292becb8083ee979ca1ec3de671d2abdabfaf3c454 -989af2bbbdaa466bf985632c6b104e8f2e054363f126f77c31d1047a8881b3e4 -98a30b78fac80753414e0b0d8500f483483adb96b60a2702e35b73c7183fad3e -98a39cfa4192cc60d5536997539d8d584802d4e4436368c1ec24089b7b8d00d3 -98a7c68627357ff02082968ec3ad630e62de31aa1782cf0be4ac34f3bbcebd70 -98acb56d955563c241669b75c21fe4b9d2ea81a75c48f53e090bef5b5b86cc46 -98ad2b0d1ffdb2bf319d4b47dc2522c16fdf683b89b5c348327bb0be60c9e4e3 -98ad3697271df673f81d8068f5aa85fc4431231e5e3042f7d0459dc6a213d014 -98aebce4bc39d8986b1bc1fdc0a4beb63cfaa9c77ec47d226efd4a468ed39d3f -98b4ced7a8dfb9c23b42b934ca289d9a47802ad8396ee9d819a13b4140fbe295 -98b9501afb821ead6ff56512e29b5517a6f0f95de2ed5b0c93cbbd587fb6ab04 -98ca3eb5cb4de99474c0e8a6ad136ca74e285218bd9b54f02672fdecbec1db01 -98ca9b59c43cecbec8e04db91bb8bc1a8066e197815125aa08097b7db0637f8d -98d3458d9367213ce25507f3cf94741cd29fdb5c718d2b8261dfa4423dd222f3 -98d45bee0c2933f3922b047d0a1acca4f8ed40cf2f4839527002ff32f492631b -98d6a6c891cf9dfe4e1d06c88b92ba14c220e7728aaaea837a52693db44e8e91 -98dbda5f2501489e8ed33d9594513924da3b4568a0b294e2e72be7ba8ea3811a -98e62da79b3633b1243c5ee79ccbeabf6491dc13466a10faf2711cfa5e6d0979 -98eecf24ed681239a73e72f2ed7a401af9a07aaeafe3c7c7855a0445d22218ae -98f3bb9c8b56fc628b2c8d3aaf42dc0123143aeafda581b160ad35d49de73c7f -98f461285ad8fc54ee6468ac0d1d339418acf8048f8ea6bce185abc69120541c -98ffc61ccd8e212484b430a0d5386e2b924fda2be2e16fb34e72f92c567718d2 -99056578029a58d1fa1d9dc756b106e6148afd6415ce13f5985f1e5ee204d4d8 -99134543f1ed66dc91c9cbcc70a6d4f9f8ea1af2045f307b0ba03d08305018a8 -9915e3736ea8544f8d437fde32908b7655e3ac89d3f2fa8487cd06671030c962 -991bf7bdb2561cb1784ddf6e34b09a8cc157235737b1db45550d0cdf541fb90c -993726ddc3a041bd9e87688cb74bd53c94fbee90e050922b59841685d363385e -993782b18e1098833f9d1bf748fd8c6b4dd0cea376cc6750da6490ab39deb64d -993d1c3eff3aa9e4ffe5ca142bef6dbf1606f7ebce2e790b58970ee77fccc822 -99631396ccb5e1e66b1b6a462ba4f1fe6dd1977c79f42b0a4c638be3c0e05a69 -9968746ad1fe6db2ff4621b3d67b84022d9dcbe400ad922a1db3a0583c375db0 -9969669dc661e991ffaf6960c1d61148a63ad9ccb0c64568a066bf9f7864571a -9976922d4644dbf8a1a8e03e152409a9dbd5aaa992a6d2b7a9889f83ea3c9df7 -9979a528f5830d5b68f435bd5cf010a4e6b8a7c8eff874369095bd647a2c48c4 -9981d93d44b3c818371228ecf7dbacd9dc831f6fd383aae189cfdeee5fa0ff13 -9982a7556b650eb6c9e7e19c6681f881f126a74df922a7a0478bca87eafde5e1 -9987628506cc69a3db11eefb9f5d1ca6da499e418b6105804b3538b19a32d749 -99907c9a8e547db9d4cab9c3cf82f5660b60e3d4004e499a53b4d0a7bd8e34ee -99a143ff874ef6aa08fc3c5a864a8dacf2c5aa00ea8f07216c753b69bf5613e7 -99a8ee70d5a096062870e3a1e1a27ac8c1e6af9a19a73eadcbe37264d7d371bf -99ac0f61243006eaa9af81a43a09fba17b6e4b1448d38c9f1237cfc3d6744f41 -99b4b252b6e9645909d4bd0f258099dd02f799aec2b459d45e06bcb9bd079850 -99ba1120d1e730296941e02d47c71a244716d73d0a7f41ff83d62b236670840d -99cc4c5f36d0cbb22a1046bcd573bd481c62cc49e7ea041e2f511aecfbc42a7e -99d260b993865aeb1f58453ecce72f8592fa6d05434b839a6b8fdc07958a09f6 -99d44fa5e8701cb89fe974a6c2157e3a716b73fc8e79828b4674a556f9542077 -99dafa0429d4f6db6c0f573502cbfe1a8c7fe804c321be1afc32fa6a5a71743d -99db4b2a2832652c6ed3a81042bf730215d394c82f22369422dd278fa59ab9ca -99ec88d5038e97c56ab70bae41e2f9615668c55a83ecaae24e81ae760306119c -99fec4955dbcafe82051ac478af8010c2b17983d9e323e4922e8718b4b46dd89 -9a0564f64f4fa5cc4c6d79755bc1dc2c6a6f9e3f8cce8f25599e0639cf02663f -9a07bef61ab469b9bf1fc487a22db1e6efb87c186a0fa34b520c22cf5b414285 -9a17f88d0a22003c2cc67e8015f573fefc7dc00d432e8c6ac943c89d4dd37a11 -9a19193e8688668ea794b05460d4975fd931fc5de93e0ee33e6e1d2b0f399c21 -9a1cf27df5b0ee04bd21098bfec7dda525cdb2d766ec78265f3e7a57f9778870 -9a2945d59b1869ec12849f45b3a5edea11c09d9788b2358e2df7dd4e91cb0724 -9a31d444922c7f452160a2f64d140508bd8cc62accfc948d1db310ea72b45df3 -9a3997ec59bfdf981a5cec0cba87e2e4da1b3151a9822e6390ef9ed256430034 -9a3a6746eea5e18b56107efaed1156d125aea587fb62b03800498f04f3aa1fed -9a4a2ab501d6d9e3386b7828d8e718de8f2bf5350aa09b76255ecd50bc5d1401 -9a51f328b67d30404597bae74d981d25fa595e0345ab19fa32db8d1b942ff049 -9a55d0b480c02bccf63c408e041ffe2e4106a38864d5326805d883db6d0db236 -9a5cb3864fb25a94eab2005eb3aed44330df0963f9c157dfaa39e64a0e6e7fe1 -9a60252ed00eb47a4064a57141468711c33de4baa419ce6f5f0a899cfdec8009 -9a651db4d117c6f6359ce0bce16c7c1d27baac6bc3b9a194021c96e57dceba2a -9a6ddf7dd9170c459ac48a569a8f4ba617a6855f3e93a6668b5d3a60de955133 -9a6e6a18382fe7b4ece58a4880c18740f275193a5c31d9285cc5de9e2a9dd5e0 -9a73e26ab611b67e2441cd9baa419be547cc9b1aab94d76716e18ae038174695 -9a8832813e268a82a9f2a65782de0d40ed90aa8bf0ce5a1a3f068f28b9a9e972 -9a8aa1df12340078022ac5adf0c9579a792d8f5382571cd0e885d51c82bada4a -9a8e99e76be72fe4d11353bf29bd202ca5745c7f9b21859d4a382edec71a41f4 -9a8f0b4ab4018e98f1e7dd5a878c7583fe8b64b8dab43e93b632203491767fe2 -9a987fabd712f9e3b5cd425aa4a2fc0e16a283f6f9f72cce6096967928eed4c8 -9ab43aabdc8f1c836891994b5505e332bec2845c3a14e379c6561c5ffa59cf1f -9abd576d30d6f177a85e141aa3ed1eb75003377d13d9f13c782edf631ae78347 -9ac02e372bcec8a45d7853e24dc7cf9affd2f6625d7d18925d9b5e1d38592da3 -9ac54750446807c3debc8084d2aff1b783213860a8f00f711f3df41a666b3db9 -9ad885cf85be9a130dc8435a643b5933017ca828e0fc7dba28b0c38e5f659c18 -9adb6ead27a4055270cc5718cae5841db47ae92cc445af4b302e6291f43f7388 -9ae8169596b7742af8e30e809117edf699cb15fd32976b7c51e9a06fcec49557 -9aecd95591b3239ce9bf1d592d93b3fa61be23476981b6f31c383a61e2670a35 -9b19e2f33ef04c6722e6e3e5bdd84196912bbc974c2d2b0b1caca19e64b0d118 -9b2a740581a0c3d3413f6ef81422ef708ce2473cf97569c642e83bfbfdefadef -9b40f3fd3f1c0d79b2e2a5f7b0ed13cc92d8c80e387c4303aceb9298aeec946b -9b496210b907c2eb1eed6d12c1552654bbce4485417825a7eaa633e2d66db774 -9b511bc411fdf71b94a160c1e79b03fc5f622f82d9da530414216b052ca1c80f -9b60597fd0898b7e47b1e416551bf7340d8bbce4e4a5ca8e64d3b202514d5347 -9b61d9f01d5cfda0f4ad96972c77838814c613385778b8295667d981bf5b8ab1 -9b72a0bc3bdb4f8a95219ca95645823a24b13824bf9d671ba9eb794848bad1d3 -9b73fe3006c771634026be2e8f92935878b1d51a3bb6c2caf0bb22f70e4ab9b5 -9b79ecac8ddaa01f313cba7f8b3c0a42f1732e701e1b75a28ea0333692b20d34 -9b7c519295ee8c428454c627a813b32cfb71eb34b74e723a10c570f138edfa53 -9b7ff09861c931fef89745eb245846ccc0750ee4f939feaf8e510764402a7f1f -9b83017a1838daadbb085b1c393fea2e1ee6ccf4b8d66c3ef8c17069ec21fa01 -9b8303a71480dd60f4d9b64b8dbe6d810ccc7ab07e33d9b22db5905bef850a16 -9b85560f481e6cefe66210a40df8af78aa46767e7ec50a2e8103cebfba793946 -9b95a20a617046d62aa3a45c91786c74edb82c6825a3c71b07b985f4e13c7620 -9ba532490d81068b382c0f8e573024ec71a1f264e10ac8320888590fc1c1a3eb -9bae917ab83ba11f8b6102891b4dd8566598c3ac49ef4663d105074491427acd -9bb2f60df1309a6ccc599f4420e9d8f80f655238ea0fde3c054c076b538e3485 -9bb467761522cc8d5035cf93df88b28955bd7d3f91b33589596c605e4363a2e0 -9bba5ab796c916e428729c94a1efcdec132b442d0d74cf1660a6a96d4e95242e -9bc64e4b38daa31b182268a3ea9b64cc3a021dc53593111c0abfd9d088ad6abf -9bcf91e03b7dce8fff9732d699dde2d703ecf816cf2dd314424dcaa7359ccefc -9bd65d7491459391d0e946543e6cdfde011bffd08fdadd810149d17802bdf8c1 -9be440d0b4ce1ab3fcca5b8a15c648413a6c2c8562dab543bc2a7be672557690 -9be740064784156036c93c65bb737a72bafc244ec5f1e5379b94a1c8776ec9cc -9be7bf87edbbf32d10fe7c04283b751db954d8b6e88ac284bc2f122b3c674aa5 -9be7ddfbe6a25e519424b290b533e9b77c2586926672c271fd3ceaa4900326c9 -9be9bce50f32c1b97ecfa29945aea6f81f8005c9b7bca4210a2cd4e3d9dc5ea3 -9becd8be59c38c8add5774bd3dccac8c0e86e9e7b0b71cd891fcd9cd5e6c83ca -9bf00b9a6004b62bd676c3755954a6e3fac477fe988967dadec74fe65659559e -9bf1c739a16ca93980e7490e9b818092ab4c47f5f92fc29103f624184fd3e535 -9bf59fc5bbd27ec348e07e58719229bddf8ecbb1ae780cceb1c9a99ba2f16996 -9bf6929aa03c60dcf663dd2dabf69870f7be5f8f66b3725ed6ed021d427cd735 -9bf84653ed76f74fcfdc51d6301725593297ca2d27494757d52890f1ecd02925 -9bf9ca7b2b8fb88186574ec5108898a7f99e906a80c81b31b6e19c69b075e9d5 -9c00c94ca9a14e772b44eea8d0096aeac0dc3e1d7db1e4b2000e3762355f2ed7 -9c0bc9dc7e49dad20ca6d7eff9a540df56926bd6775b6b53bd9d271f1e4f8d85 -9c0caddde6cc3721310f24969960b19a96f9e117359446b1429504bab17749ab -9c1bb801d0f5cf05b4699ff7d84d991c5036af789d16e094503f8879ce58a1a7 -9c1bcfcd77bab3e5594c048c2dcbb09775cda61e831d4ed4cd31f909aefd248b -9c1f37abc657f480815ed27cff1b8e5607ee88cc3fa9ed4f769f34121a64b873 -9c1f5f5c6bac71ce97c17e9f79d49f8eecb8fe6eebf9798ab07bcd0a32f5f456 -9c2ef788b07d7b8ccb84a41dd6104a97800207a7d0003c5df7ff411c69a80402 -9c378a25c822978a0703e9290a494d1359aaa7afc94921fb9de6af1e8cd70928 -9c3a80d52ce9d58456a8b920850606e4ddeab0f31b352df4a3202f9508affd29 -9c4e08280130c68b5083aae9f5b8c60a3d16c1aeb1d0e00d5c3609c3974e877a -9c567b6f515e5cb5a54b4f1c3bb832a529884d2543c3c13da799898b3b7d078e -9c574f3051f6a73878621f2225d1f802263264e1a2ce8f67b57d36767f24e029 -9c740b196cc2f5dce487bc50b204a538b160399c30694e444b399f3ded530ecc -9c753c9b5d8d21dbfccd9d7bd095dbdf812f5de0b4979e6711ec440034812b08 -9c775e49e4ee0731e6bc35fcc2a734dbaa4dbd335f0a2c3b67d83012ce7b726e -9c7e4a044f391b06a21da2ab1176985d808f5810c88cb37daf919d6967dfef43 -9c8a5db9c0a754217524cc12a9e483d9daea2518f360e26d000eb63c12298e64 -9c95771541aed288682aa324a1bd687b1db07b11df7705ee31f9ccf21f2453fe -9c95efa694bc43492eb7a739a303e68173d302388b4a97a0703d0283ca65ae00 -9ca5251fc72982f702e7fcdec87680ccdb1f77e36e49cd05f41e9e51dbf77642 -9ca850d9a917c3237a67adf2c928dfce6fd514f68ce0b36c1400435db58e7d8d -9cb362b1bc3eaff402b59b74bb98c4846053aad3d43114c33ad018e779968aef -9cbb6451cf3d812f266b36e176f33b10d60cc521943aa6a8a29bf4680699b3b4 -9cd117f35fc6978471b9483137645f31a832f30711cd87bb937c31775ca4c057 -9cd75e4dc0989f481a5f02cf9fcf5c7646357f9ccc3fa76e0f99e376c393faa6 -9cd7c78b2c83b3996287d96eef0951a548d33f67fbc7e1aabd624b50898d5825 -9cd8ace79e3cb4c286c3ccc49543f904549ab64b2b3354ec0d6765c6132277a4 -9cd94dc4d9c8e95537afee1ef842e6b20e2ee0833931e0929cbcb4613aea1ae5 -9ce26f14c35e66f58774cd3fdd739d453a78e1a677498c9d84eb7e1b8fbef6e1 -9ced8aa942bcdc6c3ce514ce10c5f17e58ec29aec9bc36ae103869769fa3c7ee -9cf1ce8b87c4ade49c58214594c4432cb039850dfe658c4c96c1c62493c7850d -9cfb63a6749a2d1f1a25fef0bf8789fce5c4f7d1710376e4490c9f8323e61054 -9cfc3fec5867aee38cf85fc39e63c7c5ed5722728f7f6c1ff252dde6eefebdf1 -9cfdb557b5e6da25467af91f189c9b466fc9bc4c20ec32f6032b1796c2ef3917 -9cfeadbc3c160506a1db0b4029f2383b5ecb290df21b40a75b5875a1f08de717 -9d01b1d36748785039358f7f87b8fdaf84e8fdc576e5d5f9361a877677f5a42f -9d069f72a28c16b1e5860f54198186fe1a10677d39cc7ca7eeecf838d0813222 -9d0e16d9de15755557b7a173396221a25f2defbe8d131d3010392eb9be0859b8 -9d1d866e7dfa58341d98e1d271af22cb0c8198f51bae8d2b8c51b38efc30b01a -9d2b5137333f14c668998c20eb90346caa47fe59f3fa79bab437b6ec56f4ab03 -9d2b686a0f838c816a00e8390541bb35d37ded3e88b37e25643bac11be06ae7b -9d38d03501776d222d5286e0d7022e5b444a359dbb491f4c4dfbb90c8f60bf3e -9d3beb75def3464833a4b79e201085489ce91df42c2444e54d8db1c7cbcb3068 -9d4508150982e9c1ff0b1847e98292d2176e72c37903f7f6be2119d3427238a4 -9d4d01431ad08e982a4d1534dbb206f2e570e19b9e4f410f2186b8f2cddc89a0 -9d6b988065628058c6282ee14e05c0d464e8a4991d0bd2af3f8eeeb2ff259992 -9d6e13302d850082114d38576f0794cb931668ca7e2c4a66dc51ccc3e50891e9 -9d6f1fb41d24f76c3d9d18d0a3d0ac331476febf4ab4d2b806d9cfe6f4b2d36b -9d6fd9510b1af7086d072c82f1cf621b02aaa6d98fb28b97cac3883e24465b10 -9d7dad75bfe51d0e038d17fb931781867cc8662a5f325001dbef525f77bf0e8b -9d7f817fbd30d26eec069f25471b22e7a8f57e089aa09bcd5d1a18e958ad26a2 -9d875fe79eceb572f667868ba271ccd17acb809cca864ef420b90fd393f2d3ef -9d87b8f3cab4de3566cd1ec9fa2134617a8b41be9a9c5b5dbf0e45aa4467074c -9d89cf0a1f7d7b089503641f48c1427b04e732230736e3c5eea606fcae5761c6 -9d8b545ecc9914093519e29391efe3528c68179177019e4157eebbedce4588bc -9d9007efa2f9cc0b0b768d4ce837ddfea3fa5de25375a74d7880c1f608ee9357 -9d97cad6ba0ec3c5aad0df20ddd6a199a4246386c2f34b2c2ff535d489bea56f -9da2264046f0b7a4684778f6986c7ee5f26fe933619a4066160c841fad4675a6 -9dac8930cd02fdb26f4108a6fc532575f3c28224b9e9f15c19eaebeaf3d40cfe -9db11ae6a90c51331acae4716e660995dfac1c2c77d9df75a09a0bd4f6eb33d8 -9dba5c3bdf6e112db785e6cfbe8424a975e9471fa17d766e52d9675684febd3c -9dc32c5e6f1e4a2364b5683ead3d30a336b3fbb649917f49faed622466b77aab -9dcb513b0caf96efa6878030dcf3f66395142630af8cf16126c422b69372b87d -9de5b0f651dff3493d49116dd4b45cf7f85e3b8ac12b61418bc13f1ad7969ad6 -9de8dcb8294ce398711be99ab80dbddd4b2f11a14f029572b0e4354514df3e38 -9deab98efb0514fc03a4ae64d9feec5afb4f3ce5da31ea3d047f0dd048251894 -9dee01603ba9fdbd2ad736ef37c7aed3e7613fd2fa9d3c5c8d4c0cf43ae83472 -9df252950333cbf124d51510b72fdad4e4aa59a9a3e3450632803532b6eb26e4 -9df5b7f5e769acae1327adb4dccaeffe2baf37d85918a31925926ccdbc09697a -9dfa17cfb42169fe997c2e2729524b1f02428666405d688978f8f3c493a39e5c -9e0bf354827c0bfcef9079a10320f80e89c965556e0a48499d43077df4b640f8 -9e0d751d1e58924681ebfca99a03035bd00ed41696150261875f93091c57226f -9e0dd562aee58a81c9460b54fa8fb2ee9c9a1ae4ead1dd7a0ef952263c7e3e7d -9e1a4858c9f8b366179d490ba553fab867c4be7d6d837ad0846931dc56e05a36 -9e1cd2f6ddf40ef67254904d7358adadc8d791ec4bce2af78e90e9c9b9d9691c -9e1eb7a0423b297c7bdc28ceb58b70ddd88c01d63149131e62a4c11804750d3a -9e229d0cf8284d54ff7c62cf1f90b1a827cdfd25b2c1023ceec8f28bb6a72372 -9e2a6024a1a9e8a6e1a675b80fcb934cdf6700175b93198a5dd5c10673477a0f -9e2c1b170d12cf375c636009268078131743367cfc9b01028db3e7a16e3bdee3 -9e30e34f773524fedd7f856ae2bef12522dbcfd319bf31983f45d1a1a3e61011 -9e330360046b92bbc2e2e7d73f4eef8e8ee489159ec79c5ab79041611acafaa6 -9e368596caefd167d50271251f989858eae0489ab650fca4db4b488347c0b4ca -9e37fa0a01802a07875f879870ba892652c2f8f496afd10ce8282f279a4090aa -9e3b2571f5894ceef4ee0b1a706d4337f1eac9cab5c32f3273097fa81a02ab41 -9e474b65d29281728d1d5c901b3aad1f62920be812e0d48a4b5706f9a970e4b7 -9e49433ff12aafb55aa03ccb67a56bc701bccc92ed03e63ffb53a49b5dc1200f -9e5523b364150fe3059ce2594c8a2c3b2518df5c5721ebee7024534d69a46d7a -9e57b1fbc524f06d973d682a491cfa04774b48cca8eac40a6044101bf3ce7461 -9e5c197f0637ed960379bc2c289b7a04647f1540797b0143f428f7ea4e40cb81 -9e5ef356bfecc4aba521b1da87920689f16bd12d9eca6f91c583fb89a32f56ad -9e68eb7796771156acaefd42246e752be681980fb8db5bcdf3f92ee64cd01450 -9e6c706086e9016b61be5cac5e1e3d73a7b4e39da8c6c494314a66f9615739d5 -9e6e355e9b49d4d20f09fa0f13c55b83b2d3628bcb39731049b538dbb62cfd90 -9e872feecc516ad1785d472f74d96734fd651537f9bba37c7076325d95f84b62 -9e87f4218dbc8a2725aaae5f700d35e62f8ba285452224669910ce58ac0dcacf -9e911ea8a9737744d80e0b0995a632e5f538d7bda7bc23181809e37d5dc7609b -9e97a86cef9a829b947e049feec66360477621bd5148c9f5068d565eaea480c9 -9e98c94a126b99647c3e1c5ebc40a2c2d5e0458a2b4bfa4690adce18f1a1305f -9e9bf7cce4c052a736b050cc102865f2a51353cb3412add65d1ff45c45cd95be -9ea3497d0f66a39289fd7be112d0a685b2ee9676115f3eb2498720f22794135a -9ea56fa011c62ff4a63c234bd53c428056ff7f8e34d3dff3d8f1d10fea9782ad -9eac49a86a2dd3a68f75afb533f3401de53c7d73ed562d6961d0c036c4651986 -9eb4cbffdf04203f2bfbb0f8da51d39680acfa1407aca60c574c774baf0155cb -9eb66e09c9c476fec6de50e00742aebe738908f6605df9dcef55b0967f79f1bd -9eb9473e058aeb7c694d219c4f4dd2b012d6605ab6c654778f1c47ce23109bcf -9eb969cb22c79b7040004596c9ddf11fc2bfdf94942583ac2b37cc37ecb0d9e4 -9eba1920eaac83e01079681cc9e86ee16b54d360a791536d2c1c82e0b1b1f5fb -9ebdf1cbe42ed4f6196eb2bb3216c251e2830cc3ea2dc8a3f4c218cd5ea22e59 -9ebf018c89404da9ef9227752b6c1685e30f72297c9c58e1e34884ec9297c458 -9ec3d5a1ad10df074e3b9e6d648a02c77314c047e2f49a92a70b5c8cb6839757 -9ec714dfe1187d4c2e0952c239a4f44dfabd9506e17fbd6e9bb6e2da653f1e8b -9ecb6491863f486298a126f8ec61f35aa2046b5f0d212695b0fe7859b1e35662 -9ecec8bfe0a9660235372bcf925163f5c189ab91b4918ccfea03adf55fdd5be7 -9ede3acc6d2b983b1556d8d725a958c61ec5b56dc2cc91e631558ec72a521531 -9eec6d0e060428dcae68ca77404c97115d29bb1eef6f0c2dbcda82a1441be355 -9f0523f4c2d9942d58f2ea81f8a298a3319c749c6e5e0bc36e4ea90b6e5f2093 -9f0f5093675e40514d03678442656c3d150a1f0d7429e16a1f2bcc70c9c0fed4 -9f10c72271e7a4c026c873ef9cc7abc40687739734d930fbd190e01b18cb5b0a -9f1207a508deb0c08ab9df4482aa1c04cb918873933ef3e63d499052fb7a2b8b -9f247228d473a669276abcc1b2070fcf5cd3755dece32fb180ef650eed76ba2f -9f251d570ffeedee8b01f17688d0c0cbda3b16896378f5731fdc132a87a8e876 -9f260b40db319a909ac7557f874fffa9d65fb7ea4a7d657d720ac26d7f718dee -9f271b045da6faffe61924dd238a6c2157c347560be8d676abc10a7c2e566300 -9f28a87787f08017b7879aaa40d5badfa64757ad395cd8372878d62202015dd2 -9f336183e706632b3fc7d7f36cc7ba0145611c1e5614f33f684a435aed6415cd -9f3f47860f3e1a674573ea483ae4af0590dae65f9d694b3632c39ed0faeffe72 -9f4030f144e5b63743e9b633b89848a14970448b551ad8b3ccf364e552ce2213 -9f4dc37c535fac702e05d4bb01194838d2244eb59f5c096c126821f343298513 -9f51350e03b0e2673edefcab96275be43a8e7863ad0234d26affc90b18d6ed5d -9f65874da39e9ab2925fb0218cc674a50cc6ceacf48ebf9fb3e73b7c3513e537 -9f76a9bd8107de5c3f9ed43f732c7c8182a06623261fc8fc51643de6d2d78dc0 -9f8fb9010fc059b4e88b4646dc9e106dc31140468b8fb2dee877faee15f13fc6 -9fa885219c97f15ac29ed97f21255d39a12dabf56cbd1df001da53e3e4b0e0cb -9fb644ebb630d573f6860a2d1dcddfc981e3ac637e04af6913d537e9f686ca2c -9fb79586b7d6e7b115e2ca768534f53c3d183638284fef6be79fce2866ab2c2a -9fc40c88af5dcf4f40a7d85fffbfac4609a02288431eeeb970e23757a8089ecb -9fc4554cb462f3ace8abf1c67d43bfc6d541fc17e81463e8d3f0810df91e754c -9fc473fd402e3f9acee29b02f492294b382006f5d70eb1d78f2bfeb4f4779a07 -9fcb788a6ebd8de73e6570d73ab78daf5d6062b62e601bc7bb747efe6a2948e7 -9fd03f2c9504e0bd5421cb6eb0b50f55b9d635aa784a28375160b5c1b3b40c05 -9fd3595dadd18ec19bad19c45973d771d7712e209bdac36ca9735f03793a6d73 -9fd559a49517cc6e98ef88c11c0de522e6e5c8b359bb0b8b01e5a95f7fa5db35 -9fd85b5eb0414b6e13e433d77855a4bf02fa42282e99b9433a11b9befe940d7a -9fe4fbe58bc66f44e5c9a646d4f30c7834ee83f0c5cf6e740d82e6ceec28b2c1 -9fe55780e76f2c8cf075d6c7f8a52cdab28a0dc117f0693022dc34640b485cba -9fec2dc24e42aa40d49e02729a60b97049f301df1c70388796ba08c0d6747571 -9feda246a890933d4ff7fff913fa979e8b81d088972b3191e43b93ae0d4e912b -9ffe2aa2cf181d247b59c652c82dbb5b910e2dc5301cfe4b84580e117bfeccdf -a006bcbe1f55ca2635c9588f860432fd7d76ebff38bc7d7586782153f151a5c2 -a00c8b1074fe2fcb8631324d9e0370616b039e84cffe90141ceec977afc3033b -a010652084398eef7e419636061b8a8f0c86da50f20fa42b914c71c63c6d8617 -a012dcf6df1105e78293cb7588fc4a854f930649d44d132f1ac1fb8e3680bb4e -a02032e46018c95aa43c4f8eb119d916e1a4aa126b806842f150392df441c8ef -a0228c070c871d01e866e8fb689438d9da6cd5b74a6ca2556ce1781eaf9a5ec2 -a0242571670449b8f1fb5f9146d20b45ab4e45290cfeb4bf4c58cf48091dbc21 -a02c8196259f8718df4d0cbc662fa5bf359c2a42cafa062e1bb8a9300c391256 -a042b60b601b322eb10f244b8f7a863c548fe1eb73da3ccc0071051eeafca88a -a047b4d5c8c9f89c6a2bb11ae83829620cf36c7073d00904335b21f9b23eb828 -a055e918f3b77cfa9ac58351fbbb00af4fa08a0332c4cc9cad57fd68331f39ae -a05b2d4846ca580cf7fc9bd903ea363148ab02c34308c246853953009eedc571 -a06a69621df2b2fae4b529abb7a27946c1c3358e32f4e6293a38af56c0c4c6b2 -a06c95492703615ace7b8712e772f595d1fcfa9320ef6a81c269338d75c5dc62 -a08a53d697e1126694a3f490dfb07ea685d0d6045a0b21d07cc05ace0dca1f95 -a09d52421b93c09588543ecec88522fa254db7eab70d8196984564a6c92badbc -a0a156f037c192d5b2e4708a33585ef6bcf86b237e96b84d80e59c15f9f929a3 -a0abf3e221f807874e019fd69dafcc08a0ba8a81da9ae359392636bf945527c9 -a0ae4eaba59741bdad6325513fe29f521656ce2c76e31d0ad9505f07a1874b56 -a0ae6bd8e272fe4115f04b35f1f4ba2b9ad102a7392e17e7b0ebd12c6f11e1c1 -a0b24daeef6b034f5c01cb228201754ee35bd29b4be4f777beebb3b532be50f1 -a0b8e16381a58d454b44ec1079d9f499f16cb148b60d5697984fcea37befa84a -a0bb8b957da0a059f0c68f4e2099c8f2c1e71c4f6fce41bd2a88d8bafb102d36 -a0dd8afd5fb043eb27c51ce1e039dcb25464ef0e18a94f9cd66519d919645650 -a0dec67bac2e869256ed7962ba367c9ebec3d7ab7a292aa89bc953c338c3595a -a0e2997de41e7d6a57016c6c4b4638bc2053210ef62af1726d665bcbbcae0fe5 -a0e2c24b4553fe5b6e475a30cb5f71a5123615c3dd62fac4f53dc7cb040c6d22 -a0eeb74f415c015d453c324b38ee6f4d41ba11311c915157ec5a7836bb6cf4c9 -a0f91c95e8b7f2457a9ab3c732b48c48237b5def359d48dde079b5c57a218a4c -a0fd66ce23cf2d354ce8dd307c2bb84475705f818a19649d6cf844d95d321ac5 -a111602b3c2f1c92b9b353fc088ec01ac3c5fe988cb246686e76dbf847755b8b -a1180c620bea7bf21f9b803e81c18bfc488d44e29c8d027f41734480885613f0 -a12c5a55aebf377d189c1455d4a3f8d6094644ae85e18547ab619fabce3b6730 -a130550a7cf9eb8fa95cff15701e7a16ea2b06902c610b6d600e8f836ada83df -a13097c13aeaf31c1661f1dcc6fa7bc8ebe57b4b2bc6eef67b86c52da823705e -a1361cde3cc5c82459dd97e7d278a99a215bb31438d336addb3f4c030533cf28 -a143ac67620ba248dca85120c4268f1873040e74ab9528b4deed7486bd2e066d -a147891b3eeafbf54535bfbf120db3f92b1b9f9947ead1bc0d4517be926242d6 -a14bf841477e0854b7133769e5eba5f8944df4b01efd3650d0a225e69f5b95ad -a14c8b7a457699c02d8140df988c11a5f3e505289940555e863a09a19e6576d4 -a15b4f8a214028ad9c1e99372025208552bde9e5a86a126c8c164b5f504df1cd -a16a35a85f21d44da5a361415084b47b273705f71a0199fd9eff5d5c6d3a7b10 -a19770fed87e15bb183255ca12fb1ba2d67d5e9900ec5c3ab8ecbaa1ce2564ab -a1b46c8729a539efa2b329a695c97e4a8c325e1873d44e10f62a97681ebf54cb -a1bf41d7e0d8dc25486539d98f8b4a41217fd7b0de1a249d52f2093668efab3c -a1c75e90642b0ae0d8df315061073dccc7b29b9cc89118572865d234dc03be51 -a1c7c82a209ea60a54662d0c33f72b2e09ae9351c2f814e435c84de698d15903 -a1cc8c1577bebcc8e477995c67ab6dcbc48fb7a2c819919ac0dc9635455505ec -a1d23bc2ddea2a8f7a5146385ba3bc0ae62c4e1ec99e32a53b8adfa3b3ef8193 -a1d33865e7c4ba8f0364328fe602b088b03b4d914a9c96d0152a0736d2f4acb1 -a1d84bbf07a4c39ceada5e80dddd0b10841a8a09baed68409914b83305ddbdac -a1ecbe244e9dba97096392640c42183f6c15cce07d148d6c6e449e35a301d823 -a1f3ceee5c4cf303a54d23b900438455ddd2109f30b2d6bdbb712e8ce31675e9 -a1f72442c7bb978649ed34d46d9acc13731560633595fa7f92caf5e637ede52e -a1f94e83df8ec375a1c62b9ef389846489b1f3f2c22d3d32071f621ebe2a1673 -a1fdc0894cf3f30f6bb7b7939fd3cfd1d6b2ddefc368370fba84eadea62c52b4 -a1fec135551cfe990460e8a8fe9f282f6ded02b20987a69764e28f9c4933550a -a20468644dcebbdd1e7d175d34d1c0fc8b95d499f3ce27599794544c31aa3a23 -a20704be0fd2eb110554531746e5bc3a988e9d45472099bdac4635d8975d6ff7 -a20e58ddc0bd2a0ea7b6a8f991b64f509303700b3594f27a666b4c792aaab982 -a22503d864f3a04dcef580a645ee7f57c017289e7521c47314a04c55293d11b9 -a24d1781ec519ccdbd82051a6690c025b8f02ebcbf2545a09f105d6e33dccf86 -a24fea534154e96d0aff3001ff59abc0b95bf7a582a2f716e3a158aa24865ad7 -a25d02ef8c52e9b00a56cfd74eed6bf230b4541b0ddfac6a3e167aa8c4c58d88 -a261a8dcd7538514a387eb63d59f949c50044249da3b760c481f278603d28a71 -a2666b8a60a77112dc8a674b9d1d041ed8f915f5430260425ab01d7ed83ecc9d -a267417fb2fed516e019490582e04dcfe3af68f0b589c7ff0db01979d947401c -a2968ff575e6948f1c8c95ad28104301dadee206a52cb4ed3d6c14e629dfbbbc -a29847abcfa6d9e85200cb27ba5f7b2d02385f12ba22ac92bf5c30fdf37c693d -a2b0f2fcc088658fb57dcb63a267f6fccb6058f5a67a9f28e26789d3283552a6 -a2bc9094f43605a1db30e9950bb9f7e1cc6f2c35da94f691de8599ea49361fe3 -a2bf01dd13d825aea1b1f774d983f7589a3045d6ae51164a99ad201dcea9e916 -a2c02ee21be13af4f0ee49a79fdfc4e81c75c8c112be77e109a56d793540b692 -a2c2c0c62e775c6a8da6f906df3ef194165f4fa1b635fc1bfcaa92272f397360 -a2c96c0539768f926e9d3a68af52f8b782a2d5bb0c4ef2b609d856ff75584235 -a2cb870e2bde367b921c2fb54268ca6dbc886c4b882c5a31abeeb9f8c9ddf851 -a2d756fed32d8ab6a9fc0c5f98cd0a7eb86ccb01faa0c35f871fde51d8f418c3 -a2e024402bc690efe064713567410a074abc9a012a98f54cc20365f87fc47d52 -a2e5dd37e7bd697205848eea18f0c5c7d1c433a97044329d3c6e10d37cdc26a0 -a2e8bddcc45b6d7f56f7a9791a5f9b09132ecaa7288a25dc48bc4803e2d8aa84 -a2eb75a0e96f3ca0b52554edd37b20baa50c1912c820b12f427ef7b79a12795c -a2f8c50ea09e3d0820ea54290123d4097a6a4cd725255f1dc6a46cdeb4e6c097 -a2fa01eea4248e1d7cb28f145d55796f36d22041e745e608e322d99b45efc421 -a2fd7431d0822b78b6a6d662d1e043ee0b40cf6440e4f43c7e3a5757d60b46a6 -a2feb0f78a336564d2be3724866ca03227d372c106d1658ab9974daff64a0bf5 -a302824d8f6602586532d7099a18c5da97c4899f07599c80cca0c11a475e5a33 -a3030af3d3efd54a11d3e613a2d2896e446914b74bf57ccc276838ad66b216ee -a31070e3a684ad932d3cdfe8800b04800e6ae1491d677f2576904ff2536ca105 -a3127516ac2b88751aa24237baf87cc30bf30f98801308a0d852e0a2727b30bd -a3134f069a7d127fec546b06ea44ab02f4a184d13837dea1a764df1eca98c6e1 -a3203922eb4312f4969348ba8a2f64bda82dd87ff1d429ed6123d19b8f996671 -a32fa6ff793d369bca82a7e8000592e7145b15db7c8192b5b6a7131c23c60b6e -a3321bff7df67ed7a032d7e7a1c52afce5ac228213215482d76bba59cb350d35 -a33262a7f779d50b951670c166df32000074ee0b12dae39f98171229d260ea5e -a3334dd23c064c9ac8b405c28b0d2cae5931dc080955ec5f52d59102f2b62d9d -a34b218051fa18daceb62c96e5bde4f476de3f757971ac961f1d6127ea570ed3 -a34cbc49ce0e84d6c48fd0660b1f5385da4ace847ea79457be26242effb55e75 -a34d96f6fd537c18f08f7fdd6c43d3b3ee13bb48c0e9ff810fee3802dde078f4 -a353c8199b54750aafd9bcd2762749c7724e3b5b00c48ed4b0fb9d8821b8be73 -a356e46c65595946fec82713076fb32aec8c0caa88e81ccd602a09cd969b131a -a35bc469bb20afda2922a46429ffdec68675a24c30806b849beb439bb1ea7cdf -a360edcb925707a7260b88ed8f19f6c2c376f68d0ccf95a06f60f6a184fa90ea -a36d32dcd55e2f0cceed9c305c51babdffeacce0dca35778ecf3302993d937a2 -a3716c74ceb9184749aa6c1ad60a5223b5c14a497ef11fc97ac5ae1c6d79eb5a -a378e3affd20eba61f1bfba94f9170bceb4d6b0bf44fb5e41cfd798b61e7ea0e -a37f33c1894f2f98e7428c69cd562cccbbc3bb1d91c9324a31348710ee720fa0 -a3814f0a4e21129cb6566c3f4e705115a79c96be8655155573951ffb469a37e1 -a382685476d60e3daeed856f4c8269f43d33c34ffdba3699ca21c3b5903f95fd -a3864de4f53149dce8e1029fdef1c19c6f0033773bbd74459b51415c777719aa -a399bd0d87303ee9539643d048316beadb71749c9c42f7ce0b08c045b451c1f9 -a3a2352c3905bf8600825683dbf274af2b9bf793ce889adef8256b63c75a4038 -a3ac14a58135fe37c6df8ba2b8f41599864ae4771c166a23fff4296b038ef9b4 -a3adb47dd1e3884b664e2ad3e74e0d37d38d4206351dc9ba02e18ba3cc138d0e -a3b7862d68777a44f155ca769be17697c9bba25cd07e73992aadbaf2228ef28d -a3b792eca244df82a5066735310bdc2971fe6ebc4f216f03052332b4a3120a23 -a3b7d79c28322c758be0b26f4593ef09f9ea72aaee545babcbc4f5ef13fab222 -a3dde0e4f136b2cd72442ade8d6274b9461f648b44393901fe426ff6cbb6da25 -a3e771e0664f482e9017aa385ddd0e77950d33d278da2b80165bfa0d18136b4b -a3eb4d06520e2a3479d8b2f4277276c10f66a235994eb93142e72f1d7f3911d8 -a3ecc4a5bd2451e65e5a477f90f3a9a330c1a293a64427270318c88ee26d1043 -a3f0edfcd2e832abf28f46c1c2133e2a4e8d5758ed40f6495b8b7f2a14a2481a -a3f7f4e8bcdf69c2dde1cf86fc0f56033acd934a5d5cc0476a980e0c2203b1be -a3fae7f0370dcb4e7f0a47d2b1fc70175daeb583e328d918af1d1b8a894dc6a9 -a3fc26cc4247b74829f29eaca9ef8c7f2a577ef746efde4430446bf3afc1085b -a40028debe32421b1bf81bb3777e807f20e1e509c2a7d08295b2ba82a3f1c3e8 -a40d348e61ba9d08b76af34c9e075e87a5ef9f672d2bc1b28b52555b114347fc -a414e8488f32f32765a719f7a4b011fae0721d4a169032ee450d531d331f0673 -a415e714372a9a7caf979ea3833692f76d90397b93ea2496e1ab7ccad0ba25e3 -a419c95258790ddf9aa45b97109bd3d2e9a2556cc0c8e0b761c84d3f1eaefbb7 -a41dd6c8b1c5f0ae73a607ae16686920f40301bd10dc7a47541de977316ac44e -a44052c9de382ad5deeef2a17345b2ab5ec090d1679fe88b04418e5e2d8a062d -a448cee2cdd21b702c30f112b2a318e93f09d07be31987d50138f67fff0cda35 -a44937a3a99b237f45b32ae0455aff56150dec42d9cb4ce3325a39b415303803 -a44a1defe17230e75cada30d7305791a6e51ee5bd16e0ed66ac5f99a5ad0c01e -a454beb85ac97b209a9ec0b018f57c697de3cd65cbf69dee7180fe894cc06616 -a4595978d4ab4247e3aeea4f6c8f83272c3993355f8693c459dd928a6d3bcea8 -a45bca1288d5525b77b01f0804d829b42889a155cedf9420bbe291e7b8f9e549 -a45ccd612cdff8952720adfce4a12afbf3ba9048d6951080242812180ffc15df -a45f8042feb92c734226378c719acb94eb1c10e650aa54631a0e6575b2d23569 -a468a3cb6a26838634e3c0283aeb9b1c197659e284b4899b9beb896176905528 -a46cdd607f900cd9679356ea65e5f14bc0399940ecd3b7ea06131d533ae20602 -a475e00dd1a095c8c1533055bedabb9fce8dd3fe80ad9f44e6ae655c6a4605fd -a4763a745e974c12389c3bde33068661183be25806f62b88abc9becc8f080acf -a48ace20fd289f423d13d2bee3213d6dbdbeaa9e6efe50dca841846000daf1ed -a48b5e65ce31a0828c20d23bb730af8a966cff5e43e8217eb760799ea125ae4a -a498b9ea4e72299c9be3420d03cb8d656940e9637add3d82724743adee6e873d -a49c9e3f409ceddb41e41b7e03456a3995334d704c1b7b418f5ff0274e4a2e25 -a4a2bd8a702e897fdacab60504df9cdc44c2d61b2697c2e91105fa457d88e044 -a4b78dc74da652159eb72f61374f3570b101ea6adf5f9b473fe95f00a63017e6 -a4cb048346b6cf0f615a333912bf744c3ef6f9eb4f3b5575b2ae1e16d8e8cfdc -a4d2ce00dc4c94ea1b10b99340237d8d924a6ff413b1dfee44b0f74335e89976 -a4d45fdf196b1d2f0b27eed996a785dfeaba9359d9e37a1202e72353ea57eabb -a4e67955700c96da550fd917bdb44a04c588a463db01a69d30ac28d181ee0672 -a4e88ba1dbe71b57a73f589b3bf6c826bf0b7c8f1a403ad1e9fd64484aba149a -a4f079956f4dbd92671c877d90c0440a11620074980c91ab3e5c7337dd872f0c -a4fb8b6382cad907ed9fc734feeeca3f5221ab9d414e8ed4f728eee82fa2ed32 -a50073389291f03a62e0aa59727c175120c54a753b4ba276f861aedeca231281 -a500dbaf2eae318bee8882020c2e3e8dad504d179cc177ecbc782630e1664491 -a5047a4ad116104b2e718141c2f879abe6075d33a684018acb07880f1dd68225 -a5079c6b105e9182cf90bde90b38d3acdba2925c1c7eeff83d124b5a96b04ce8 -a5223f9df1caacbbc28ffcadb3ba39b0c78747c516c645a6b7dee5ecf0f4f203 -a530883a1e82c261d224c03fe6776ce4f3be56a04e8f76482adb3accf96bee35 -a535e586562ed345a5741f56bdba0fff4644b83e34c1c0662012793656ca8b27 -a53b747ccdb757f7bce9c0f12ea7530d44acb2002ea4c90d546395fc9bf884b9 -a53c38212e076b32adf9da14eb18c531f8b1ea9167a6b8c63e6b46604791900e -a5472a111e3a3f56aca3d0e8ecaf760fcd71a798e08164affd1e48cfd060ec71 -a5569201dfc9d4e52a00e947c88c1d1f086c84ec34cb0b5b0b06650d0052f946 -a55987958ed066695c302e9dce64be8b9eb7579232eb8e88e64fc452c162838d -a560e952f38637bd074af8a38a26077115fe9d0cdb93a1766840a57d2994d4e1 -a5790bdc36606bb055245713580fd91a65b669d4ccb45f01b3b4ee49f947bd04 -a583454f89c1821696ae1901d4e7c04e243fd95274c62c8547ec79674fe80148 -a584f724fb7cc4e2ef90a7f2baa197ccdfe418d7a11bee897c54ecad2d1dabcd -a58e57dc9ee19461816d9fdb729d0a9bbe038a76f9d50a8540c5aedf58ec6fba -a5945a9488fe7cbb8a3a07da70f9aa1ee49b9277301bee44c44b5efd4b067012 -a595a7ce1b1d6261fedebb015ca9e83d05c9b1be792caa6e1f3cda7f169f9714 -a5ab98b3a541bf112fde7f44f5167617f191997937052572523f596e973e0fc0 -a5aec28766e594ed0468b1803b9e8a496d909e7003f26f472d9bf25f24fd5f16 -a5b0ebead0880909230e3b06be64347c29b919dda0964a3ca32f3322b51b9013 -a5b4c807928bcca4c3f819b32439b1ed7e85e9ed7b80a7c230357f859d094c1a -a5bc351073c11ed7b429bf494375e103d97168636b5a711ca2dc40b64c4d4df2 -a5bd155590c4a39b7b484fb51bf7bb28ef25b5e520549fdc3dc61e411e458b30 -a5c17e4cc77d852f35f135a42e4958dd8c915e4ac5bb3c2c0c204099f69757c1 -a5c4a8488180cab0bc61b67b19a8600bb1a78b612ab2b9326387fae67efdba20 -a5c61f775600ad7db232b5e687279d9333b2b4074f056121420d877ed02a2ab4 -a5d8b4620d18f23d15fc081c7ef2e56b1156d6fe6a86c0f8ca48304b77505eb6 -a5e03129556e99e401f534608da8a50d89b6566b5f3aa5caa5d10ef4c2d92539 -a5e141aaa0afb0889893e76dc6b6fbeaad4156fa136df8b973751640b149bbf7 -a5ef9c9da3d7e04a55f7d25c1c04c687567be5dee75a90c744a42466e2204ba5 -a5f0e2cd4dede72d1bcc23f9419091517546434e6b747b3c2474673d2cc2ff90 -a5ff5a5e5f461587f3de7b816df2f5d92c1c32e8ad3a9223938b0efef12de6ec -a60208c131a814fbf1dcabbbd141441772ef5a81dd9a84590e4ab4254ef33520 -a6031580f7c3c70b69ef7239c658a14f573580e4c20ec0a0c5a86da720f34f84 -a6048a2246f0f633f7afa262d7c08a2b98f9ff0173d7a9a2536009e2b3ae2b5e -a60e9f1457fcad53938337537fe383a784535a316afd5077c0952e3c13a0cc17 -a612a3801adc05785638d1f80b783a5376697da9bf954bb578b141e3ec2ecdec -a6245bbbd798f5b5e87eb22e7a6036ea3ff855ce7846c44dd92a3bd4940ae037 -a624e6d3526b5e0784b69c58316158411a42a7e656bb06ea8efb0cd4e80a1a0d -a63109bdc131c09c0e00971964506cbdc42065425ecae7384191de37d421a3a4 -a631677b1956bd66de78dc773391bb190bd74851c0d73aa7bf298cfb34114307 -a63642883d59033e7556824418a5ba2d649bec7ff3e2ef75886c0b441ab50fba -a638965f6b2fd64afa90ae25d6b535e147cae164d69532c894dbd78e456fde24 -a6502b9ea7fbcb5da1073ed8e163b1643f46036a4c0ef1ee04ff690c4c799b50 -a65132987028dc70bde3907b06197613bb3a3d1c8e2bfe487275ea1b379a1846 -a659b1fad5b36f2f46bd3fd25c9da886b7fa8e7bede6af616d7d5f31ab588b73 -a65b6925486943ee872b054dd78dfb75798e33c2f85a2fa3a2fe76177cc07b75 -a66912028ba873a31061ceffb84e2abd51601266b6b47d540c714fc93d8e5c87 -a67a1ef9c038399cb558f8e765ba8d2b3ea6d8b818e4577fb0740a487949cad5 -a67b23bb95619a17037914e41da44f66d1f3b9dd7336ba066d2bb9228e022641 -a6804a6cb29f1d16af409662a31e309d69b24c4d505bfdcc6c24370598df9e69 -a6871d2bc34ea6e1e56c0d6adabe4e8ab53356cfd47b56d07456552b937dfccf -a69847bca6a71d34a858291dd49e0677db0c1d7fd0ff2da3ebd2503704173613 -a6a3b4ffef76ddd901e64de445986fd7c55e752c9d1a1f9d4367b2d65cef328a -a6a4074501a19c06c1a9418f74b7c7c902628b177d6c65a78ef29eea13f15d72 -a6a4cc4acdac66fc3d3655f98cd6f12cc6e19f1e097d8d13075d285d516bb190 -a6aaaccae60db6bddaa171031477f783f1bfa3d749e5ef1f046323841f6f4a81 -a6be8c3df5e8679ade7b264d16b19e3dcd3b473352285d36556684d90c61b564 -a6ceac5d56bc3d9b76210b86a9f8ecd0ebbeaa63881af770ae5471568f1a4afd -a6cfeaaafba2f72419d1582552f6f5652b0a894dcba09fc6ff963b73541c0352 -a6d048adabfe90f9e11de298ae836fc6bf06c7a43cec8251c7dfb0375d5c707a -a6d2c10fc81009befd4b14d32a26632e0e071664843525878adad4e34fa2f773 -a6d60dd2d0f3b9efc5be01b395459f4f09e230ce57e77e94c6872a87d36d673b -a6dcb1ea4fea9925009ebb2093d82b12867449db1a43342c9422de791e8045f7 -a6e7851c51e279f2321ab29d90a2d94d607cbc94fcf777c32e58b51c5ad5ac27 -a6f1415c28d45aa2dc4f9d72aa7da4800b99eec6a2df38029691fc4e16b13c6f -a6f243ecda83152b779fdf6ef02482025c68c755a20d3f45f45635bb47046251 -a6f6aed42e8b96f173b4702a6ff43e6d0912b4703fc9c78642d01f8bfe92a795 -a6feb2425a0e44310fce444b562a4f3eed32c50f37d94f210f2842bf467f2c25 -a700024323e2ba87d1ed295b7da469df518ccd5e15311368a9773d9a13278103 -a70af95c43fc4a8fcd58074b58b80639d5e04868a0e0b42f0e8c126a3effbca6 -a71095dbf1cc241c7959df24c8a9caace2ff42d8a5a73a7c464794bb290170db -a71730508c4f4bedb695762c1117f91c1a7ace543d2977dc89e28cae7fc7aa42 -a71cc1736568ecc0cebdf9270b748b00494c5904d267dbf92f51ea264377839f -a727f3b5b05f4aaee75db195ca179a6dcb3674b40ec68dfcf0dcbfb80a2e23eb -a72db3414386cfb839c4705d78251a8724ca2a32171515725b156558e59387b8 -a736502bf1b79e78302af307b6bdb4b26d38302ebde529d6451c63d2f58ec97e -a7366bfc736d00e52ebef886994713b619c38502851ed637c11f5c750c504c9a -a745bc907c9b4a3a02f5a52a2ba3133dc6b197544c7491266d7163e7f8c6613a -a74739ae341a7420a1e55234f309d12c469cf5b4f1bf56a8fc3fe35d49ec9cf0 -a747a4f230444c317aa29d217d27fef277e941f6bccf7340f3c0380703ee8f3e -a74b0c59d283f897c9f121bfdd80b79be736bb9c0062bd757ff9c71d1808372d -a74d5257cc9322c948d93504386c412b2aed29cc0b92fd2eeb9f764806dc0ff4 -a74d686ce274c2f13254395bcb09c644986c1fadd7ed7e1a43d84c3f552d9308 -a7571b88feb391e5e4636940e589b8a11daa5f8d61d535b37cdeeff6e73e10fd -a75913b0055b2d1e43f391610f285483c78f614d5d8349641fcbe92b3d2010c7 -a761e089ba8b69869fcf4bfe46b16ee8cad0fd901058a8248d93c199b99e3df4 -a761f878390de1fea236087377a2374808bf8262118131be8484b34555aa203b -a76e6880011ebfb312835afe0a14b7f8bb81ab6185ee62c7fe8b0698431ae857 -a771415ae51f3826d4d0bd0af8de7f7b38ffa18b9f35f529da1ede0105d0b566 -a77a3614dc3537013049b3a9ec683a254e6c38a5ff65e0f92f3094aa1b5a731b -a78a587b9b2788e34b11738e449ee239aa60d7a28b201b78c1277d30dfb4c7d6 -a78ff276078b34227e10ec133d8a571e1d61edf2e1b6e6233abedbc2b9b31fe5 -a79911a2512a2c3d3f5f85f2d022c467a4ff43cefd6cb6cb7531b0b713daa47d -a7b83891bbf331dfe7cbe5cbc9ead7ce233b64780ed2075e810457d860241189 -a7bd19ea4c36414d9a4cbc543a6d9b5d60a1c674720d99d7555121600869eece -a7c0fa9f3a0d764f31c8f5ec0aef88ebe945a8ae08c4bdcc5808b28e5025a7f0 -a7c8ab4bcf8890c93c29b6f9d337d83acc1754580db0c18155b40b59704a54fe -a7c9e12efa6283cabf6b97f5169441a17506ce57477f3ff350d5b31b52176f31 -a7d50f003862943a16207d59afffe265c82d849033269393b85869fa2650fb8e -a7dddb65900096ba36fa166e384809f997af2a630aac977b3bcd294d2396dab4 -a7f4f288d0ef37d91961c7fb5527c07a21947ede78559fb812771c9e2fe23990 -a7fd50811caa25eda3cecc61b0c63097ac646cef0a6c8ae8a574db6d7adb4a77 -a803698797efd783d058d4730030e6b08dbd5947bdd7dc52649f60ffd90d7264 -a80a13a469ffe0d8f20c4331a94ad57af391ce13c70dfb0094b3699b14b22a1b -a80ae099145bcd79bbb53d33e5581fb7bafb8fc7492c9b03afc55eb19a9a9cd5 -a816cff22ab324ed9d3c7793e8ae0c5dee7180e9094de1f35d4db1ec217b41e8 -a822a37c541d2b023131e758ae3c306bffa8eb8fb4b28cd70bbceb1106ed10b1 -a82656dd4560521bf7200c58d4a24cf5addf2d54ff1cafa2dcedffebbf234be1 -a82a3612c4e1d97adefd27ac61009914e0ed1fad1c73ea6d5d07d2ba4dbde279 -a83a90258bee19257d97bbd003e08b76258aa3931648b3392d454f9384c63ecc -a8473c2ab8337d4458499ae33be746e6d6ad5fb1897356d8808145a8f40321f5 -a847b15c8f7328011a27aef98dcf2496a5a31385d812c9b29a208a9fd6536121 -a847fb3ce38fb2ba26b053b55dfad3bff19bf4219a6021b098e8e40ea1d136be -a854fdaaf4f3b5f7ecf00e7635dcf6e330a402f03c8edeef7fa239aa682b9a46 -a8593a3ce39b864921fc2b49216a29f217e183c7fdda11681bade3fce04f0add -a85fafd53106b76fc40f9234c76ca4340b4855330d6afa41bf22ae685c5d4754 -a87be7d8c7da6c38833944139bcd9f08c692f285343cb74439a7489d7abdc9bb -a87cf80ebc83d8f3377da194b57ffa0e823e094d48785fdc24bf1fb62378c013 -a889f8cc8c5ee668061abd996f8caa78e515fd17e2072050b5bf629a15f0fa54 -a88ea904efe6077f2b07b37081a7a79a1b3ac15bdecddc74a23a9969bf7982c9 -a8946fb4dfe4cf465acd55f95757bb1264ac4c74499a1536814d4421324202ef -a8954ed45286c6709421135621334d0657ba7036930c64a9985b82eec48fe898 -a8959a94ef21707990a1b6ba32acdfbe3f8ed9cccb07d335932e7e0ab792be36 -a899d4e0cbacdcf9180979f4f0b3c1b5dac0b022a53fad775080e7d54cad2a91 -a8a1d3dfcc98099157ffda2e6c0449820dc00a20f9f6eabbe546893cda2ce2eb -a8b06554a97bb3cea3c5684e16f76d7d9104128f89119531079f52ca59636984 -a8b205d6cb8cc6f32a10f7fd776ffc06e4811014af35612d8c6296715376370f -a8b52378bf07d215690a0dcc2646c7c79116224bf17108c7a0b6af1e15e8b4e2 -a8b791a884a2ebf2eb9f9f0e9ccc67edcb50998a357d3dc602ac5b38bb4203b9 -a8b7acfb9d932c25086960825d603edc730a705b87672f82930f39027eeb310f -a8bfb3b697ba8b56e86543652638b8b74d83cf3138bcf9525f61dc5f6d91743b -a8c478fa541f6cb69c623bfb232de98fc23ee36139be4f9b6457e46c4bd6aed9 -a8d05cc17d3b0c5cbb3ef1cb2cba184dd37235c3e6669cde35fff23b373ff4fd -a8daa6ba603d5dd1a2c2222f8289697d0211ce4bc7e0915e3af45c93200b9504 -a8fe7b5aa73bca6fd7c5f17dea5c097298de3d0214886a9dfbae9d87dab6d534 -a90e565779ef0f3dca859e500eb9a8389421ea256a595585d0c87952e80ec91a -a914b80feb46dd8ac4c66611dfc2ec549965ae58b06251b56c70d5fd0a1af83d -a915ccd9bb38294de96aa72d483b677f634245c7ce295c80bc906688a0ffd8af -a91c819466da7b3ea8264340d9aa6bd5c4a5d298330152e385f91321a4959017 -a9319eb1baab749683bfbb9d5f8e16231fabc0e680d3d03a34c6dbc17218c9e1 -a9328fa26055be5dec49a28fb9f4418d37aab9ad3e1b2a9647318da6caf4480b -a9370fc545c16d859322bc7cf75f97408b91c4a4beddefb57895335898bb4ca1 -a937902311065cf0fc9770be0b46eea8d07ce9eed2472eedc9e59c11ecacc68c -a93f50c52fe593c1e7e07b71b0049aef82257d5f956fae0e563767002adabbe1 -a941606468e1b1ad03445cf4d5a5ecc223a629fe688022cf29b6e117ff8a035b -a942beae11eba4bf1adf17c9ea5df84a259ac98aa810c29142cc2978696473da -a942d229859e80b6a6f3d638f22802990e4597ecfcd1c5f7bdc50205ae83adbe -a94681bc03a7cb56185ccd490ec52ee3a3a1a9606815354d6ec64102b992c960 -a947d7696c1f3220e14db1dd6fb9ec8f196784a21a4434e5d07dc65b8abc29d2 -a94812163fdc2ffd3430d00340a91b8cb88de428c40bcc98b2b874a770cfb238 -a95d05665fc0de8f98c6e1c574e4fa8997a8958b78ae12e33fb1ff9f7f431c29 -a95da1d8d41e2e12bbf0fb4b7e4f1e0175c339367bc896420b46fe42ba1c9e54 -a964d8aa98e5fe91f8758382fe315fe86abeb6a82df9b1a2c47b6d63b33fee33 -a9664e44c077ae19d89f25250d970a4ed5491aca68ea44464f49e86e16ed92a2 -a968b23c78eaa52b10d70c6cba82ccce8966451c042134d80c57742d24b6a194 -a970988697f12e544fae4dd1d3aa183cab2619bf07744e716a6cb2ec7362bb4c -a98ced21d5c2b56d5512e577eef109c410ed6071aa10610349864f308ca29bb8 -a98efa5f4b8ece3dd49b321055e5b995af5c884df98ff6ea71cf25574ea4c1c3 -a99233d570011ebd1e8f769bb5455e34e2192908c3bb22616f3520e61fafd07a -a993556f47044cd77689d7ec00a4b4485ef52601929e34d3849b4c4318d4fc87 -a9ab4424b2e8279133de0ccdaf350cd280e16727528065c12578fe7e3d5f79de -a9abfffda7a5b262927712d2b69d0dad527384d358419f182eca71022cc201e8 -a9ac17b58521a6482792303fa44c919868ec6158cc0db91d4369a089ae0b8f07 -a9aefc6375880f31d5ea77071f9451d4fb50d30e96e9186d5796b0ae526f00e7 -a9b28c6a2e5c2efbc09d2656fd6729a9c007a1fcd293dd89a5678116a834cd35 -a9bd077d0701199cd102ad7c2a357abf7436352d663f5aa87139cdc95e8d4633 -a9cfefd3f653828603a24d5e7dfc741edfedcddf25b805b878330772de7a0f25 -a9dac0c6c8836e52ed6a6324d8be68ea7138f6071e20d3ef771cf55e99979bd9 -a9dd8731652eadec135651c3b882b4e4acc374eebf6ddca6b1ae99d05bc4a0a8 -a9ef163ddfaff097a24c4de33f050cd3f8875970e4ad5efecfeb8a1d48bf462d -a9f1b914606ef51464778f8b72d83ef85795af520d4fc70e87b46de4cdb96c5c -a9f215a07f322d2ec13fe5016b5b657390698f2a629f325b40dfa2df1072bb1e -a9f666dd6b1ea62ca0eff32d77f45c365f4e20d67f5002a70a379165752feaae -aa0d91671840c2c44c1103e4ffdaad2c45635bb796cae3417fc385bc23fcb3db -aa1eab199c68e0169541884c3e609a2cccc53c02eec0184d630e78eaafe1610e -aa34619c52f6feff55ebc6b4a430796e712de5213da609b0b077309b5745340f -aa3741994e3a75856a52a847b72b333caacc6c8ebd172d2534349e95afcf3d52 -aa38e8274f00174ee405a4c66ab4d4ed575856b1cbda8f131aa4b002c7da7f19 -aa3d73cb4510ea57f628d902f90a6c4d33640f7d628f02317f883d9bd8d1fdf6 -aa4315dcacd9d93eecf692dbd30875ff8880e2ef308c82933301c909cfb017a7 -aa46a02dc7574de38d0d5864dc1917e5b5d22916e166528dc7e75f8c6395c73e -aa4a40e1bc790238ac2733c5c194070c20f7ab3cb8e53c3136fb5aad32dc0bff -aa559de5583bcf69c6af34a4ed4dfd90046369b28539b74ac79a1efbd750135f -aa5ca0ebbe5c347fbeec549c3a729a3f5ec3bc672ab66622922b924420ef94af -aa682721ff13986e9a4e2d91fe0d8262d47cbbc3214c39670424010307a35a25 -aa7089049bf19f4abeefc1147abb90d686ae097543f0cb62fd4f07ed75a13430 -aa986b90f76b6d0ad8214e05b96af205d942e1c56fa058b5689cdba61c99ffb5 -aa9a6e82e610b5db5283a1af943d869b2eaf4f9ae458b579f2a4a65bab03d0c7 -aaa70051bded33c42c252907b8c36185ed0253c4e458756df02ac40ffc64e92c -aab81867c40d116109678db664ef41c99d68c30bcf96a4b84836a94879649e26 -aabb1df788e3e573907e45c36632f0c5c96714a7a03e5723fe9bc3ef3c848ad6 -aabeedbaff731a005d01152367d78d30525bfea50e682440b2ee14eaac4f9989 -aabfadef5444886ade7b33853c72ff2383593f5f204f0b63c7258ec908c31a02 -aac79b24271d761b92087cfeef0d14ac91d3dfaede0345069f2dd8631b2b5115 -aacee4170bb07985bf29645ca8406d3f04eab50a1b692d306378300ad87c6e8e -aadfea4a8fb0d9834b8e83af6eedeb13f7105e2758637269144e1992445cb0cb -aae0617d9470fd6e4531fbe7d2a02978ee18491f1eaefa8b2beb7a2f1a74c9e1 -aaec645667a113473f0546fea088abeae1ff7ed548a37caf1ac916fe0ee6c5a9 -aaeddd76248c12bb6ae73d1989e86cecc82aef1e39f16b7d9f0c618d3b8a42fd -aaeebbe62c656d0619a752c2a7f3c26c9e189bd9dfec871e98dbb7603e67526c -aaef03a96e6e65d5f39bdc09befc5f0bd45eecc397c16b0641f422d0b79eeef8 -aaf7583741735fb6006ba1e40a1d9f0d1d07fa8d1b04e49c97bb85634aa3e66e -aafb322e99a987fb44ea7d5a11acb4f9effc282b9bff2b2c03ec522d9597cf9f -aafd26e4420a309a40d1993b75dc8721783737f3d36724b09a298b8ee611dd7c -ab001952f1b0e9175d97bb1daf9ea0e0a6af231595bc592daa4f50044b38b7d1 -ab101fcaa36f685f6e46e84e87d31989e6e272a83d3149820660412fc9d6d52b -ab1ca6ec35c99a1981756f677d2a785077cab97e43cbc53f2de8acdaf99b96dd -ab39bb28cee3cb9ff9445fcb074d114d7ab4e21b6dcea069884b6af92de03b73 -ab3c142a084ca6f0620ac8bf80c7265fd1eb6d333876100364498414cf829623 -ab435f4eb2e50b13e4c0fa832aafaaf0c92c890fe9d0ab35074ec2c5188adf3c -ab4743ef30f09012b77810ad9ef4dabbcf812ee53776a83a6c6943b3eb76341c -ab483c0d4a126fceacee37299322fbb3ed265165c7ebfcd8f36edff75f0875b0 -ab538c54a0b94543edc4cfd40fb195254be70450649e0e6d413efa3a00fc3f0d -ab5df6c61747b98eadb2ccb80408b51fd194499538a8b2b92d7f53f8757ba477 -ab683c92b8aa2866522331c2f40099c4de78e486c8bdff536cde958a6a1c2871 -ab6df1b70c04819b51129aba0f16caa1d419ff34e9afceb78c8174a31cb3841c -ab6f0a028cac3844874c3dea3b46e3b474cbe8dfa02d843ae1d4c8ea82365a32 -ab8e4a5171a5291b0c8a5119efdd6e1033283542fa63a2407128b15a94cc6334 -ab95acd1332a4e2a4e1e697f9ff93a01410a1a633a4f864d9fb745ef1a3e3fdc -ab95e048a6637b0715ec9207d9625245f13cc76683d9c2db42d09c6f7cd5e548 -ab997ee043c7182e62677f1336462ce1934565953df2fc0d4f2d2a0756aaf07c -ab9bf9156219600f399eb989d429d5e0971e0a4a8bdde33fef4bc352beee6f79 -ab9e52a1b973c4d7ebc124f986ce8cabfae1c21508c895ab792cbc5141d07260 -ab9e52b08575dcde1efcaa7295e219b8f45bbdf9a0aa171b19b322da4f42990b -aba1bbea59675b7c42da774efff9306a765da80e11e0ff30fd7aab5134877f9f -aba5250d79d678c289f10495293f48820ae2cfbd06a3909e4ab40e9b26f94553 -aba67f3e2cfb3767a821991f36ab95bdca31edea97585870337ae37e84c02525 -aba90909ef60b9a09d8965e727fde1714a7f299d4252261beaf3a8b4a1df3cff -abb2d79f1ab6af2eb107593a6888f4727898d09fcbeab1e86c3ff254397d2cab -abb872cb69e9fcee5bd37eb034da01eb4236cfaa9fe89970ec03c235002e4241 -abbb390c99b027b5d9ba23860d8a8f6ac9ae725167410ef5c6baeadf8e9b6327 -abc23bd8ff2852c4bbe6edf64ad3fb6da6465b7153396e0ce9bd15c6566c747e -abd732ca9062d1d650b3f1ae49be5e0da9de608f514f4dcc8e64f7e5b9515bb7 -abe73c77c8c610ae48610df3717408c08b24a70594d69c4e940ba4316e243b05 -abe7c5ea9546b1f4e5daa2e074101237e7e830089115e2270756cafa6d6e4d96 -abf7f0f225be4582de2f4cfb3ef206098edbb5b1a130283160c70381d6e7637a -abfd7c4d83a72db00ef4b3a9c827fb5485917039b2b70cb826f41afe1b1e9d01 -ac0cc147f28cd7f7d0fffc005040908cddcdc6bd572ec2c0ef4c91d783bbb73f -ac0ecf1600dde95f337092156e12c64c9e50c903dad4f2f5bcba22b1f7e0fb42 -ac103c53b2ada73c865dd35654833f70720578c9a5ea84aa7a60100333847eac -ac12c67ec4a8c519e118483ca1d00b7317efef70dd79541fa945d6bd1d3e8d19 -ac1ae65c2d0b2fcfe45ee139f11e1299e53b77ab92f4ea7912d914241d6e4869 -ac1ef44b0b266fdd93bbf0250cd667f2572f2ad8da35af83389bb8a2e6e9f92e -ac22eada172880696b42f0eef008ce5d190e622ff022db4e8637d5fdd29696f4 -ac3aaba56f22ef62c63d0ede3c67d0f8379d7e2b94397875f726bdff3b69a616 -ac4c257ca87a2cf48c6d18e00e98a7c689bdfb9ba5292cb375a7e032e73c1281 -ac6679f5db92343cb449ab0818aa26468c5fe4ede6ffe9a5770a78b2ab8fec08 -ac6c5f1d88762a3f60a80453fd547b7fe32311398e603c141cb8409c1366066b -ac6df7380bc06cccd428abf2958bee8c9392c34ecba6600ebed6c6b677d88292 -ac75bffe93202b42db54f794aacf3c180366bfbc7d16c7a85ed5d1ccdefa5000 -ac778f5858aeff52d23dfe15d691e679979b499bb724240305751eee236fca6f -ac7bb367f6b13932fd78f38d9da1e2cda482a4a367ec4897ebf31846ddcd141c -ac8d26caa953f17d5deb081f28e63d3802a7c3a2aab51415f248eae00e64a6c7 -ac8ece7f8d76aa6f2ae5c65a4cad34ceb3cc361a705f7f8a47c9e57920e8c3d6 -ac9241b351158671fdd4b7e6aeef604c07b8d381f96f308ebea51bffc2c94fae -aca2a93298dd11b7ca96954b916b5da0da9d76a567ef1caea93b350626f09f10 -aca462b7544a24ca694a7f31f7dd5b6ee8886025258aa076342809d002852cd6 -acb2bdfe98f1e71332a6f65019fdf13c3c0b259d3ea6cf53fd69001a2ce765c4 -acc2d6c6e279e4faa6edb9ce7cfab9df406c6b02076810c17ac2f072faed08de -acc965e51faee0ed40592901cfc7cbfb20eead108a5661e949ee1a4383e1265e -acd1e29c89247ce271da4e19d534c8c7ea9b81cc321aa3cd6cb3291896976673 -acd7910fac925d12d6571aea4b093834f5f183d5dc3294f5807b3442c5d40e1a -acdb77fb004844727818e5207927e0330cc1f86dc9a5b0ca28150734e2c89a32 -ace1ee428018bb8c3ec5f6c651d5f803f411694c39409690e05aac4dab83444c -ace2731785dc592088eca07c3acefe5799ea453f2de33fe9a51cf314e123d386 -ace3ca9a4b181561faebf1b56073b9abba7fa68fd5d3443089e7b68ce12bafb2 -acf2ec416474b9c441eb5b31912d5369b3ccc7aafbc919495e97cc37a2601d95 -acf7944ad58aacb66a9315b1112894d6b6fbad005198c03ac44b55e727eb3c7f -acf94cdb48e7602af9a10b8d3055dec97da5b1fd1f3577ad61aceca15096db1b -ad00515327d09a6e56bff69c5221b2cdf2a8ada5de35cd86e8f535d9ca885b7f -ad0569b5464c6e28d1588c9b9c52ce039219e9be6564c46db82d5775a8bdc4c2 -ad1863de45cc622d9fa54ee3189313c5c14a53cbf13bc18f5bfd35344bc57471 -ad1e5845c0c7736fa0ad824349ba7ee156966c7ef0d0f42558a45157873f14d1 -ad1e8a111379c159209360bfc252ee24adcf8a794e2b687526bf491f6adea98b -ad424eb0694213290d24cabb620283a2ae854b75caced6436a866f3bb02bdee2 -ad4b06c69277a4279d91e209a2c0a5005843bb4e421366588a9fcdf6993d93cf -ad4f61bc139c3bd508fdedc3e3618bacd448258570393076166ccdad0bed8520 -ad673225d87ba1d3d0c7e81c3d6e72050cab6207ed1ce47464f7b56652fcc48d -ad6bdd9f17071cf9239939622e7e040dd90f3f2643847536fbb40b5d0d7e05d4 -ad7de7a5af27043029c53e6aec8a10a8416f6d26835c8718409eadfacaff6956 -ad801716be1116388db09cb07ee619ee1647db9e8b64e671f2e81497893cd198 -ad84379b37cafe250663a87a2c9c4de3853ba3a7187d8c5edc4161ea81d5b7c7 -ad92f469b9026caaefa6f9e493da64f4ecd7f8157ad2f117422f278816640b9c -ada17e734217374c128a85e46e09c06a5febf8da5fbef73565279b08cbce8510 -ada189becf583ab50fde37b564876aca67f13748e1427c0c2405754d108b070d -ada73edc7f522aaa2881eb19d7239cb57420e646262742cc897b4f8cb35c6336 -adb02281f4b91569a6aa0ea8990159648176048690842c69434b8ccfac107b7d -adb44fd81dc3bc405b8265c9791f50ae021f877da0a0ef26eefd31c035d986a4 -adb7f008b2cbe873a6cf056acb528ecebdb4eebd2ca1498443423c05bfe84f68 -add338d9d7f2a813f57de5b5d961cce1f08c5122a438d30edf90bd78f6ea5667 -ade31909049defa387d626a89edeea66f0cfdb82f5881caaa2a5ffad7cf56f81 -ade445206f3aa4d454672cadef19274506f56aff5bc5675b688bd0f344254e40 -ade53102c94a737bb5f37d8147cae9e89407a4a19e26f6184c7dbd593bc7373d -adedb6df641f9cfc6c52972aeb9147e4e8648c971a41ccd33cb3ef34a3277235 -adefb34a0aab124345ede9e8e4bd9b4bcac91b86ded810a6fdd67297c3db3be3 -ae0c48548e25e45ac07f9809d5f9d89cd0aa9c389379616043fb69f4320943d1 -ae11a5803e4866a1d8d12fe263d0d045f1b064b3150eae3594f8da5db780fb93 -ae1c1bd53f10813b22edfa5624422ecb519b15ff20c3cb64cbac02f20a79062b -ae205ef21807c6a1c5fb196dad3c7a81c0733f31598bbcfd6c2c965f5a42f48f -ae322fe184b852b2c8ccca9a55d4ea87190ecb6a572608d1066d94577bed5f1b -ae43785b3d68b7eae5ca45ef52e266d50e9aa4a08b08863bffd8841b29913eb0 -ae46914829ba12822886e04bf5716bdb602c5c6e8cc19a58cd6f3bb9ea510110 -ae5fe7d4332e98ff958c3f3e257bc1c9e345587e6ca6993d11cb12d6a2626d1f -ae689cf5011223a7ac12a88a2c46908c765639607e2d3341f3fca4fd2f502ea3 -ae6ae79196b2d68bd64a36be497837c2bbb8140fb68b5a386a706bfe146e8bfb -ae6d2347c4c22b4948d05c9b90c400640fc9a3b743496a73bf7ac30112f65bb3 -ae735d54896714d4d9101fa5597dabdab96fc7822eb5869d1fc9925a40c01946 -ae73b76d8239aa24bce6b801a90943597d10e4bb4b5c792264b91c395426f2e5 -ae784383310a9c73f55a49ec47738ede7d37fca800b473a8ae84fff3179c7dd8 -ae86a68719fde548e9187588e816c12c92feaf18fc2d8afc7f369271752e8177 -ae873040bce5f05563fab379f0198a93ed21566825b1984d380fbb745cc036a6 -ae8d8537624c2604e2dc4b26736c03f76f1c4e7655d489b694aaed3e0cfaf66b -ae8da980be4b7177bc9e7fda069d2023f016d7fdde3d301bcd1080dff1c7b054 -ae97c6bc4c3d5c72ec42ed7e9259ff1d629d3c7abef964a03b9539c760c3e5ce -aea5f4ae4ecf71ab6f6b72dcd68093c393cc2834b2f32e58be5dd5741269fee9 -aea771f88a170a422d136d34646aeea69be0f37244baf83969c1a676ac099970 -aeaa49ad685c7a55f08f5845ca9a6a7deb1855b3220eaf911a968e045dd05de5 -aecef1476b2c139b6635850f69fdbbb231e8cd1bb27476f66fe1dbba28a94d1a -aed4572e8210625479677e535ee71d50bf0cc745973f1eaecaee1d7aef1a418e -aed8f7040441086bb22b4972d590a7ff3726d666bc84992831f1a81a3f15d487 -aee3a86d4b7da89d86c0ffa8b5251d33200f0fd3899d5d826ecda5f60782e505 -aee8e80518abe5b191f2e5d13b89f28bfe58313609593a6585f78526a16122a2 -aeee3dafa6485f3337ef97ed82a3d77a65e70de35fdfd9ddfcad1ed3c9368c71 -aef747f21a9efe7dec0d6f11f0a19adafbb6ba37217b8d500fea34b1b15e9d67 -af06fa195df777dd27018f7831082911b2ffefc3a922b6d5d8ca2106d48c902c -af06fb0a4b5221b7eec2e19bdac65e8ff8fedfcd2acac7d0f1d7ebe4a45e3f89 -af0aee9b653a8c99bbd5841f7ef1d5bf85760649712211046af22a5c1e0085c9 -af0bc86d9918ca40265637ec04c7faf74ab3215929218b7816e12f82ded651e0 -af0de3088c45e5450df1ab2abae32e076f1bf836aada1619fba2d13b53cd6a9f -af15472e360e7c1e0affb3b02b05b29001126b3acd3819b9f1a5df65caaffc2f -af1d4e1ef16f7426d995f5dc76b55fdd0279f8434372b55581ad386d0cc818cc -af1d9e70dee3e24f8efd7fd64f60ced708028fbb4d75823a4409426de30b8199 -af204cd0bd29cc84d8097f82cbb87ac179a964feba4db8263fcbfce9b4f0bbc2 -af2e9335d97c58b4442a790315eb4288d1344a2530ea423edb4822fa9016835f -af3051d001bcf98cd2d024bff2b0f0e2b8dd30801e8bcd463239fdc8062eaa2e -af32c040f82e006832adddb0a9ef391cbddf30066fac80d7fcb0f6dfaae54352 -af34209956c3935ea1303ffae06ab62c40ab3f8ee59da649dcefa9151ba96c7b -af3840dac84b78aa749278e1f3f13e8947d8632f1809afadf17a1a45f5e7675f -af44609c18738cb922f5e533ded315fb0193c32564e4fc4aada48b1d0d88762a -af4d4c792bdeb8307f29b58e85cc80cfa7ef40ca8ff768b9cb5404a533941759 -af5256b323b8f77de1e72e496c05e1cecb828c808c2ae4b291eb8e0c6465052e -af5488d8dda5064bd84f4c16381eb500c3275bec1db4ff2352ad12d42c0152f7 -af715af24202e70e0613eb2cdc7ff5bb948d68375f2a874dbacafa32b6de78a8 -af740e7f04178851c0eb11b0abc0cab00438a2f620d08d9291cccade1b968cd5 -af7d18d517f929e7281e25da961cf9fda2321bbedc55c5fc1754e2bdfa383636 -af8006064cb570ac0c4ee45875c03d15baabba7733b9841d30012ee58d53a0e9 -af8356e1831025ff2b805a135b7684cf66a6c4f8b7d3e16228f7e162cf94e6d3 -afa565f464fdefc3a4bc6f81c17a7b664ac77c8f0ca49db65ec59b8623fc7e43 -afb0629d91641a2984e8cac1272ed11b1769fc475d66c3315d6ae586469d3ae5 -afcad3a96733dd9bece6f64f4d7fcab2c1d96790e2dae5031abbc870d240e28a -afdb7ba982c9430986542ddda1c61fc61e993c24cf770315de9cf1965285632c -aff0fbbbfc79d947417f293d2b8841e5706edbb7475a8e22c89f723a6f639d1b -aff2e712a93b1397063a6e883397293a2e42c7b870eced007cc071f34608b02f -aff549a4530dbc46a1c042452d5dc16cadfc5fa7a5b113f4ff3117d7bbb45102 -b0036e385ada5464b0f1b8ab577397ce9cb6df5dcfb971f3219c16657435d485 -b01469062737d762abb0044015307bf0ed87380c92e3eb94b0d323c715c7743d -b017b6b1fc158b35a6486d6e5492704cc02eb926dd41f26f6878f5a734048fbf -b01a3d828d98476b925fea26fb7cc06f7fe52dba081c5778fc99d0487b0ffaa7 -b01a4e51f7a8f96110535c5473f8d5c5b362c55e3750655d95f9560172837668 -b0201f76e8c968c7244fc34f29dd7e61a8e42c2396202853a9f36fe29ee6e880 -b0218fd1ecbc321d55b7527b3fa41ffa6862259964fa1ff4fa464501512ba24a -b027d1750fcc960f91f2cbca4aa9030dc3bf798e8cd18b857cc891811465fda3 -b0384c7d206c35f4716422aa2f900a056a05ef6454556e21c0156c7b98ee9df9 -b03bc8c9214de06d2961aed65d86346684abbf1c9bedb040b67609817a4d90b6 -b0489c657c72bd82607ca4d0e605f44f53ce1212d41d0d78f7ead210239aeb04 -b052c207e46638fd8873a3eb0f159b546db25577adbf424c8fae601a31039f51 -b057538f8e5730f6a3671d167614aaf60dc22541ca500f7eddbf3c2f7d58c6a7 -b05d0b0e2a8cf123b94cc00ec51861b0e47f3603021551566395b11dfbe8d708 -b061e6e77ec3a68b7df0c34b2fc1a12eaf05070352069b26288cf84a10e5b2bb -b067f9c1c8ff5b21953a6d1a43bd7c9acd30d095a4fe7f89ce25eaaed8404727 -b068dbcd053a30277e85e4d8ec60d15d497f3bcc8d4a5e3b7e080391bbf8688d -b06cb8a276a8fc30d6a4f5cd8548647966af30882b5bce59c89e5e9b82b6b360 -b06dd2c09e3e477f3e1a3067a242f09b7cf5a87c151d62469cddbd2b9adc1edd -b073a45be2c612cc2c6d00f5008031b858d69cb2f1c3c949a2a77a3c46fa1c09 -b07ac6b37df7e997d90d5bd72dff0bbc03363c044e7ce2c179e12c2683edcc65 -b07d0b7c75dc7fea118b227d2de1a9e99d4a0a6ccc989ef2dcdd238a15532f8b -b07d915295b1bfbf590a268b1414838ecfc44163460cd1e1382c07338cd7c85e -b08000b1dd2f21a0dff343fa74e392b9e781582085789007ab966a080abcee9c -b080d2c5b87e55f47e4fa795caab8bb73cb4d24b827c2ceb69589a531a48eb9c -b0819bb8b729e91a3faac1d9ad7e89b3b6f6a24bfe6b51fff9ee5e4ff3a342f8 -b095d5f6716c8f760dbb4c9c522a45db99e51afbdcebb19850eb00b058211248 -b0a3805506ace0335c0b47e6ea9013e6f8d5f487436acae01046469c4a0174d6 -b0b44723bbafe90c20a1b59adf33bd7224143fec063f0bb7df2945644db2d09b -b0c0945c7593034840d0337df74105e77062085b1f400c14c47b3cf030269027 -b0c20ab98167349c6e26c5f613393c05bc547a3869fd4fd0cc3e7f819593ec84 -b0cb3051459c2c3911bb3ec97756d7b56c22eb499c3e0933645988da10cfcb35 -b0d3772d907ffcf03100aed2d59620fddc02027609f4aba76e760f3480441af1 -b0d5a981181e9a354b3fd2fcdc4dc39d4de9996a386c63430c38fa7d1c58081e -b0e0288c2d519ef59eb16d2c910f90ad6f8d61bd9b3ccdf37f6a380f945005f6 -b0f59923380b0b23e0cfbc45da7aff49520de054a21e8fd356a1bcf907f0641c -b0fa7783efb69f757cd628e7187d224c7363b80064ddcfb3b371585ca3feb5e9 -b0fb0552b8e842073c4b97dffa61405c883a3e925cb43a41d7c379b2e86a5013 -b104775f677748ae310813608060406ee4111e80f1d1f157374c98bfe911eb36 -b1078795ef31f6ed26b7728f546ee36e048808defc16e1fd0ebda7c5634a8a40 -b10b81937dcbd3484e6f610b13775dc513e5fced64c95c8c4140971c3ad6f53e -b121cf78f9a957294d707b624ffde496d54e5deba9ee71810a10ea46bb4c3a77 -b13bb67609242c0ea87a5b581acca6c9ccdfceb52a68ee61eb9e79f0a2b4a0a0 -b1478911561708384180a91b58779a5d7ffbb20729f78ebb48090ca72621dc5d -b14c91683551741c5896d0d9e69734d2907e85c85fa94a5ebdb98cefa904ccf3 -b16213c86d10994c6d78c4ba2d53624ad36f1586a3dfa4016dc8941573dd7e28 -b164fe917c871534c3abcbe40242ad4d497eb6022be55bd91b51777f145d87b3 -b1705a53d8de97613a40e7ea45caf7f86c4578e781fae6409121e782a6bdd5eb -b1773e6310fe885409df468529d53c31cc539a8eb287f65261838af8b37f1358 -b189ce6f11664e2262c2fc2bbbe9fb60a6ce0790cef5d5836567feac0cff8256 -b1904adcdbb8fd454c8f8a94c2ce94dacabd61190f860c0b057ca37e9569d7de -b195dff26294f938033c26661c8c915e902fe2258be69a2aa03f9c3471daff42 -b196fd86ad22f7ca79e0f93dbd4ccc0a9f54493e07eca161337ec0bead96a65b -b19d9a6de4605686e8afe8efa9c5eae96087346ece98cbc8ffd7529a3a089ce8 -b1a012205d327ecafc2b2c941a3bbfac03bf2c66c2e658f9b7bff279f943b29a -b1a4212eaaf4fd2d0df25fa2cdd6a1a1980122d0ad3e2367ccf353d811dc34c6 -b1a9452e8be897d3ddbf626c33b0f2ca214c4326e5df5fc3d13a8cf114008ec3 -b1ab12c5d8c9524239a7728271402bc19d4a468238c851885751cae6cdaaa070 -b1b2300ee359a8ca271cb89397b5432272b18ed1f10794a094c66d368efb0a31 -b1b25178b1b027191e2951ffb8981f1cef31158491c6ed35a6d71583a474a8ca -b1b43bbcb629aaa02c1f12eb6a6f8f3d82457e2a9586df229e0c452d6cd22fef -b1bd77dbda0824fd7b8379f2e48adff0547f8608de4d0076b9d6dc08a5d1c93b -b1bf759bd303cddaa9e7d75faaf55c9dcaa70ac5d361837ef53daa242543051c -b1c2bcf1ab8c19c9ecb545447e5b8fb179a563e2f42572cbb9935978d6287e17 -b1c2eb120fb16704ad2cb1084b0d4371fd390a0fae5c2cb8dd362f3f68617e49 -b1c2f382d30800a8bf7dbed8616c44e4f46578e9cfca2c57d56fb6a89e68b897 -b1c4997d7e9025a404dec32147e73f516a36198b929b45178ea970c20e188e71 -b1cd22eba5f453f31f676f212f1c2268c57257ac41ef9d57e042faa1b4c382fd -b1e1dc6af906c29088aa7c91232ab71698310e5d83b7863ceb802acab934d5c3 -b1ec39189d90804a0e8b4a16b871b3dc09ceb2448702507116754086993464e8 -b1ee0fe29e4c49416464f7c956c16403a2340bfa51748c7e045697c7edb40551 -b1f60f58ba7bf0a82b9fb3adcd7817cea3b0d8fac2f35c76ff85ec93feb6a59d -b1f65a834f2e165a6791183a2ca05d018307560021e7c3a95d8386c06f0746cb -b1fc7f4a2a9de552d097645a30c4ae207eaa8c9ad7137f4fee59abebcdbf5acd -b1fe38663e6c7fce073fb329245f6115f58e9efc0e82942d191a9b20241eee04 -b20ba293fa4c9db89a48ee1a9c5d4be3f82bcd46688ca17b5a95a198d2e5830e -b20f53bb7317f104ef7e1b40ea65e824569f4551e63566f5ce6083a0c3b8f04c -b210691172b1deaaa071f812ff2460592ea3f9db2f4ed4c2a28aab312807360d -b2115f297cfadcd3a5a80503a8acc9c7d2b73e6400ab49e3a0ef77096765c5cf -b216e1139b38349d4f0d47dacceddbb9d471d50a36fcdde5952d7227104ab707 -b21cc78c4aef212bddd794134fedaa0e21fd6e7d4cab02f3063fd0e1c0931a2a -b228cdcae208506c45e0006ee658cc58a9b402894b38f25e042f6b1e887a12b6 -b22952def51efc7293084b09f0f25a42c7d04980e52ffd8f70ae2f11abc98b26 -b22fa00c290e98492a80b6994afd1daf3b8564871c2c1e6469d1bedbc6717178 -b230dd1d22a729065ee49bf301d82c50b103b91979e008f1af52835bbdda7ece -b244335ee6b3dda78c6cd670988a8967b8a6bb29b41e5543c353ca06d4871904 -b24ee3e04e313c2803399336f5fad820887cdf15b7f99713444fc00577726745 -b2535c2b94ab2540fa935777cb61092a26259e12b76ca1069d9d3e2b0121e4ce -b255c21d1bd8a3f264c84dbc91aa8be24cde5d8879069bc7d076209c7aa78f9c -b26eb776f00e7674924e8d58dc852f07c45898bc732b6a67b9b63c87c96e95a5 -b273d412b4d53c242f2f2f77332f8bf1eff4650305a67f3764643f25db535494 -b2778801042a04ea06ae7a1726f98ba833eefed74d88b5330983f367018ef03a -b2780014a0fe3abd4bf64dca70eb1bcc8d5fc9e9c8a6add5452309789eaf9ca1 -b27a1935ceb563da425cee1c47af0e657ca7e5d6fe96873fd3808557e35502b2 -b27b0366b4c5e9317871fd15bc5fe311df1f238d3ca14cddb683f5fee9923396 -b2836b79ee0b83f9840787e034787138ab241cccb8f349b3d81a2ee835de123a -b2871bbea950acc9fd6dbf46b13f9bfe94726a4d017eec4121fc4405f63c82cc -b28f30a0c487a1e8c2eceb819cd966f21af5c5118b7f03ac59fa8cbf0915b3ff -b297d136cd857263919dab2252d78413aaae4a449dc45f1c4ffea76302946e49 -b29ae16682afee0742675a418855f662ebfce70aeef68ff3c2468bd08e5c415b -b2a2f7a7a4bf71d296e896141998bd169e53fba403f984cbf8e34e5a959b9055 -b2ad0dccd0a7de8f6edc8a1f05535381783b7aa9036e470d74530b5795d948f0 -b2b06be3cad6e504629fb16e749406322aacefa4c2f46b89bdb57f3031fa4400 -b2b1926876f62094be68c35ae28899e1d1d488cf269e9f4f4957d7318bb1f7a1 -b2bb8c9f3ea41a662fbc930a2afc69b60f048e07d7df59ba0954dc6efba522be -b2bc2036305051747e4212df0ad5afbc31c4887c1d7ec1a19a4912146db31ca0 -b2d12fa949ab15218cc7540ed700c61ba0d1a8da473f7e722e6daa9e55e279ed -b2d4a7a3b8f81a4cf0a76e37234c1ae7fd8c5970c99296818cd0e956802904f7 -b2d6d4314272934de992011566394a13ef551c26bffaa988ab4a09d33e923b82 -b2d7ec6501f813a21f749f1d18793f692ba840198590658c39d4a73dabf01c87 -b2dd3b1129d89ea6aa72090a2830effbfc055bede0fc21b6e47db0b1701340b0 -b2e5719164c4211500e1bd286c26123939c98e06f6cd6f86ebc8de9ccfa02d35 -b2eb08ef27504c16d2d989fe7dc4204b4083a769528621ff2fddbf6addfcf733 -b2fcd12be8adf8c33b67fb97f9f948f770a7b0056ce5f0598cda7003c0919188 -b2fcdbf5f7bf6ebf2e31f8a52b2015b1a970835ef50f78bfebb5b686f1297f46 -b2ff81653cb54c09a1853fa75dfc4511bf202fbd3be04c2cbbc9a078cb2115cf -b30969285a0e20f94697c8f9e86c2807b0c0c1073d429cf8d4e8512827a0839b -b30f72ea5919ee37469a3e33eb80dea0dc3b976c549bc24340e1c2d265afa53a -b317d0bc1f36836b6c83724d688a1e95bdc20f9be0ba964a169d7ccea5862029 -b3185beb91dc5adbd48a4831a03a2f1557354b158eca6a07a681ef26cecb43a6 -b31c83b24b4aca9333c047add759f7e0a90040d4104990534956d490940d19f3 -b31cc7eecda3d4959b4de0e685b0a515d74fa87951584bf58dba49af680b1863 -b327ed167925eff9ab19aac76b70d6c966ac8be59ebd184deaccd6d768628c20 -b328dd194128ebaa36200af88cc623fd387d1b68f3d5001a5f45e7926630b9ad -b32c48ec9779810d05b3f3c796c76c25056cd71eeb213abb6c6fba43d8506758 -b32da176c7f1a46594d0566ec878dd12845a29f582e252a9f4be92c38f473f4f -b339fce54e17c775924ee7e228f569494bc917261dc02c36a49a6c332f358e37 -b349e837d48041a1af38c69b3f8721a8c1cfc9b9064a1908ef7533ea0e1c3a29 -b35cabe36684dd86b97c5fc3158915c3f3ef4e38bf8a4fa8a04934fdf591fa69 -b361c682e029490aa0acf199991d977ac6548bd8be9a45ef625384491ee310cb -b362e7788ecce392f934f3c0d10530f8dcd1f3cf4a0791ffb853faa8aa926a89 -b3681a59aeeab7281f34fa1911acf50aa31239e0ee67d0617c9e7fbf7a6e62f9 -b37068e5a654d5e6457e3b3d897a192bdf6a0283eb317483f40d9d43c0fee14d -b373dbebc8ecaf520afdaeef1f80e707dd2a8c688c52384777daec1d89b078ee -b39592c70d3efe54a7116d86b5138406147472036ab32d0d644b9ff14b4740f7 -b39c19666e5bf8863b8887786199abed724805316607554539995fed2a02e61b -b3a331c89db0a56050b2c20062bef6091351f98aa1d15962eeb91125b0c6b6a7 -b3a9e817219afef81a465ddecb496e2b4d2bc8f1b3ecca7cf55ad6ba0568c1c3 -b3af69b85bf9020f4e8e241724fb2226ea6d58429504297485e174c6fe19fe06 -b3b2585fbcda4de7c526a96cc77e42bc98c9827acc8777474c60d32c7e361a26 -b3c13b5718f6d8a89ed041b16fbed0dbfe721801c5db5c14e1705d82617a9d5e -b3cbffdec037fa4fc372c6651d4faca8d4b5523e5f50922c21755065ca68cbe5 -b3cdf59014838ab2320bf942fc886a472c1a893f6b691eeded7e6d4c19304e7c -b3dc31ee0805ac8e04e9aab80cdb615e84db496373856f9f56f2559e57d14604 -b3eafe55637efa550ef2686c3c924689f5a451a6e4350effd220fb7457e306d3 -b3f123e78a951479ddcd643ea3634b9776d278860cdc7a2a4f062aeac9b0fcfc -b40243a762d4f4a5b5aa0bd2395016e7664bcdf17a440506b024d36af0d1e82c -b409e2ae6b89eafa3a58d16fd3371aba3f84a9041165b95ba619f4934ea8e2c7 -b41bb9012cbbc9991d2e48fec6d934627fbfe3f3969cdb25027bd1f0f3672451 -b41bdf063adddc3e08192deb39dda994a612c06bff7c46d15a9b60cccb30dcd9 -b421f94416b8a3a2ef676d3dfb2b11f529b5fd74c5572f0d62c5c83674513a74 -b4282bec4d16d498ab56b9c7885e9b59df8a9f9800b5b5ffac22f706bdfefda5 -b429afa603ff6ca0fffb3f8f007d9f39a3570cc77b12ec88a9ed798f4009f1cd -b43375f888225d0bcd9d327e3abd0cb7790751b1f275df8066b12ae61f6b05fe -b433ea4e5631334de011549fc91e76c1b7d028a0e5b142eee637942b4e29a75e -b4395994babf1bae53176c9b70c26671408b9dc30e344113b203d3801b16a3ff -b43d5a26132b80f9aa3af9f88aac6109c4c1290ea7025b85cf2cda96263a39bd -b43e5fcb320850567f88b34caaa9bd635c17cc30455637332a562f6932f508ed -b441814a98f297c36b8221caef6abc33a7c728a4ce66189f97c714407c858e21 -b4521507de96cefec049f7ffc87724e72fb35bceff015e94b6141b12a2898b5f -b4630a53a8e03a57d9500a3c6e62761b17ac1fa86a22bc5e1a0ca985e6ef365c -b47aa42f7bad16982b0cb062d9551cbea90083a34fccf39a2f976e4be8659684 -b47cb82d3ac5a01100f42a5998a831ced3697558203b62d11fdcf374bb565d7a -b48967c2d2ce0760b24fb5e2c8cf1c04f73ea23b9fb8c5cba4b250c0ce89ad9a -b48c5aea51dc232e28727a112c3131c31fc87a3a523f0653ae178750c90bedb3 -b48fbc17c88c28fd46a8600c4dfa9419c89a089e8e3d3ade8cdae3f25141b0e4 -b4914149b7d67c756689ee019713d252d4636da56113564b57361570238b5589 -b49ce309bbed7e63ce1e253c09705303fd8772f36fbe865a1dff5141e01e3bde -b49fd774ddba9580bf920f182a2558fba0a34ef488c632ebe99be4a88009aa07 -b4a18d1bb7a88a4ea141ef61627bc580ec27b25754479321e6ebe2ff5cb22e07 -b4ad023ae5cffc087a2fd961373ade153570f5ade548ed9bd73e8df397633e58 -b4b3e928c5daf228f7705314f3d2dd5cb9efac2b34c7b3256c108c4617a20af5 -b4b3ffd465cd0d0a8c012a67a7bc042b6cc3582cc77c0e2df7b113fb43a0d910 -b4c705686e7fe424fdfe78b2d7af64c8f21ee4bff321c3d82c75266865ed6bbd -b4d65b50b3b8841d0c61f5613f7def344c67c272c8674a0cb90bd79738316778 -b4d84947d00e98ca1869303ab0aecbd28eeffb0bc132a7f9672377fd16c2e172 -b4d8978f9d84e32229b9dbf37240586cdb5c3b428f82813b609c271209ef348d -b4de0ee6889014860a25212cee96b1c2442d191f00a1d63a096253dd2c1d7590 -b4e1858267071c9b0c2f4366f16f724e8d43bb5381c7f3b1f1dce703548d842c -b4e4bd1a473d806abe4443728a916ef96c3435bc96e64b4462fa94fb64ec6b9f -b4e574e65fa528ea7fd7945e7f214630a499798d8b0f3b2b139c4752c3b486f8 -b4e605fdd48207d0b5b3b37954d421a4ffc4a9eb6ed30012679865678e19549f -b4f74bff5ece77c8eb513384708e41de3e3311918763bf70256d1b67c25df957 -b50a0b5d2b243eb4daabefcbcf58a97d4992a43f6ede4774eae2375d7d7af318 -b50b9deec044dc16eff2b0bee69073230d014c5fcab7d6d257ea134de94a3297 -b50ec5a738103634ffbd990beb0df493c6d93167651b5652340236455e09e35a -b51496adb4e5f07e8bc308fb63f60e6c54e79148d0f693a79140d908c5121dd0 -b51640c35fd32ae19f40738555e9fcfaa3c6a920404cca9a12304f925fe51da3 -b522cef5c7329e7677933d9aa24ba31ec14ffd2b977666192a66e3c688f63550 -b52366e9dc3325043d5e9fd51b8e5d144cb31e2570796fb50c937a6eb6996615 -b524c5b3091d6e55d61a6e98eaf64ab1906357c76ecce48864fa9607b0b511a3 -b52b673fe2eba96f7ca3ca6c0111a4f79c3f62fc6d8e2a0be862a4db096cd19f -b52e8016b699cf15396ca22a45887ef8b442c47af368b21fa8a2e93a6e97bf67 -b52eb28cb5cfc2657a85b9149f5fc28cac7af87a25784294cb69045a831c6687 -b532feaf49cc28dcda6f4bbfb78984812b371604e24657e1914b12625a992105 -b5351f77d8b6ae2b7df2b17e70a314e3ee4d3cd01edc1b3aab65fd99471226d3 -b536fe4025e366d9995d96be7ba5304149747cf4db92c848975bbd6eb4473370 -b53d45a09bed11116e31b07d85cc1b3d8062c36ffd63d87b8a20c4964a1ecf9e -b5418e5da436afe7a5b3efe53d70a4c35e74b0a471646c6c905e2c0137e0e1ba -b542b27b82ed3424992fb3c0bf5ac8699e84651a693bdea1a3300b084d51a9de -b55556d2c5b18e0265eba0b168536a84b76ee3694e6ed8ffa1b1e4e3affc312d -b5564d96de50d02e057c7d11ee02edf8d1f5a7db1f50fddf74a7730b94757dbb -b557afa24661a3b85324b2687844f69a3c781364a59e02f7e84da9f27a5d7842 -b5596d7142d80d49961ba9ee7a269eb89633cf815252f4be62040a637769d42f -b55a473586bde7ca668a6050236f5e35f290ce3f59f766f22ca264f9c3c7fd1d -b55d5bb5a3d84f4edaae7aa3e33a1f03dcbffef58c1338ff73f292861d77bdf4 -b5626ff0c956e850dbf0297d15864588d52930264880bedd66e3522fb15c7223 -b56581760e7055bdf5f7cd8a5d0e9629de47005718d5185182a8fa8542bf73e6 -b56ca5a9918ea7907e2d981e7ab45865eb867fdf67382dd8ced5735692b0bc03 -b58694ec7463696cb3caeb610f3d21f99bf303fca5c04da1fc48e8a33fc6ee1a -b588224c1a294af6469c1f88bb2d232240c30c27e7dbce9a05a14eee1259c663 -b58dab604072689933055ded08b5779cf47e891902657e2afc310308865dc46c -b58f8e33c35e59224a6674026c8f3931f16e292433b8b2489ad2acd71730e362 -b5919d497144af7dc91313fef93e058d6c8c150591fb471af6ee9f8451469c07 -b592e4dd201c360d8f3b6d9fa10c2b0c4873d82158ce3905fd474f45b0ee4fe0 -b59410bc39bdf56e0dbda48125b4c8f6413b63f9d018f8044fd771528e4ecf3e -b5965c2dd9d7e067bb185bf2c60c68a4c6e6845e4999cabefc0a578202229d04 -b5983b39220e9a826419d5f91f3c49ec620cb5dbfb48cbc345c585657b992f8f -b59dc88f18dd5d6ebf0d7235b7f7499a482aec6f9a1500d6b390f6f71064ae3b -b5a2481875cf58fc1ffc06a76c797ef505cc26ed44d233d1d1dcbe748b851580 -b5a4ba0ce0215d3f21f1c994cfeb0a6e06a559126b45bb4b16d4afeb7d65c736 -b5a862e44da3f495d5564b4c419d80b8fe06c81da8ec02dec4bbea7ce6ba0c06 -b5adef97b0cf965c100ad8c31678d8fbbe17151d80d40ec45635fcd1ad79891d -b5bba56b56e73762bb4f2e87acf071f75222ac7fd7325b7491239f4f1d32fd8c -b5d31316e6476d296d66a3a0577b6614bdce883e1b3a7a416b049f3ec67ec1a0 -b5d8749489704d7a021065f11fc1bfd63d3ea92211ec9f63f6b68ce25ab217f9 -b5d9516054f7f93174fc95473b242e084c609b63c81aa7ca516b68c5ded16b30 -b5dd88b4a868445ac5711e7ede11e44d97daf5a5aad7aff717840721c7f11932 -b5dfda921ecaa970343107018cc5a117ef108512a86a8e6b83d3d62a1b816d07 -b5e55ab8466bb83eb959381ab4272157c8b901d63c277bf7285d4088f500b040 -b5ebd64d669787d2387dbfb82857fcbda1845d9ba2fd468561ba716ac37828c7 -b5efaba4a474a3cc82fa025225d8ccfce2bdf82122bcb51f8f02ee5e2a6dd2e3 -b6024abe7f1601c67de29cb7696dd3ae74ab03ceccb42d0034cf08a6c5f4ea92 -b612c5b08a510ab3d485e6ab060ef46a4d1ac107f2812a6ec5d23b8ef9412bb1 -b6161a27a9acdab3cb7f11f99d76ee312d58f202f2644612d68ad6ef88e850ca -b623b24c155489091cda9504deb8eac1ed80d73fc3358f5b55f233fc6f36a0f7 -b623c4b1d4771f0ca0f7566663dc38efef0b5b22ca70a7b6925dfc423b3c72a9 -b62a94ab1da34dbf15fe97902ca6350afc34de964102a47e53e09c5ae92f7f65 -b62c06cc82fe41d142194c4d851127d028849d58637fa7b6084219d0573586ba -b6325770b6492bac7399199509a603d3fd34365a64f532b57de47f697d1317ff -b6333167365ef794112be238edaf3aa31f9e401b93e57d5afb7ab9b5bba5a9e2 -b636ea49ae04aebe384bce877590e616fe98ca4da42cd14fdd7d8cb1d8e48db8 -b63ad6b2c8b7e4eba9fbc7da32c7aa8dcb564108389d391c191f0d0f18b7fb4d -b642c0e422c4824bfa4be715fbc54f1935181c43b85f955b44107832fde79713 -b643d0beca0c5fe6481d0f30c7accb537885fdb9351a3c5cf3b6046031d6db2a -b64f4bb80397055e34e834368c934b640a9f2422863bff9f30e98eb0c36a473e -b655bcadcd032b1429259dab9e548189fd1adac54c8519ce749b2f041bb50004 -b65694ba4e1cc1d2a3925cdcca5fe466ba81d589246c4de56c3677bb0c220b9d -b656dc330e0589ed94627906e2e6625c29bd7cfb008199752c2dc9abb2af72f9 -b65c85b483a11499c13ac17cda2f88a5afa79c2d37ebe445d81a1165cff43ce2 -b65e191c6b129da4366ed806819ca4818d8d07c5b3410dc1db13d6ad91a932cc -b65fb815b4053ca560095f30df96df42998e5c1aeed3077689834f50597ee3bf -b663411c4a234e45903598130f9d38fb1999b8cc4116cea781e316914ab20ff8 -b68c3277564af75f3ef165f8961b7e0c568c92f55c07c29fd9177285c1130e65 -b68fdfd77c3ed85b67a4756c5a668c56b10cb0141aa50a206b1181ff87c2f447 -b699c6549e283567364b4f79d29e464d9ac3ec099ab6b63303d0ef6fa9a31841 -b69f7d5c5cc692f9304998ec4cb9811dd83ef0140d3490c6f55d0be31b58fdc4 -b6a397f023d7f53d1be23a5d10c3a153415334be60fbe0896b9ff1eac8055d22 -b6a3e3992ac0fac9a87424ccbad82d8f903ff1dc030a3bc21e9977bc2d8cc81d -b6a8d129c9a43dba453d4fae041f404139edec0ad4339ceec5c1a039c0d0b748 -b6abcd6d2321f81be3a3c60bd415cbbad80b49b0831721d8959c311d1e57fd5e -b6bbec15dc86b3522c80d1fbd189dbf9e533fb11f6ca959b9e40c58bdd2b46c8 -b6c98240f4ce7b95ba3fad096e3e1d96933f0de522b196a8e9186340858f68ea -b6cbfb62480c513d855ef2f200ad73f9a4e19cb0da1dfa4321af146aa3d253d7 -b6cc41655d299d899485ecf11ac8d02d04faf22663abe4e728ff4015aca13b00 -b6d1916e10c90de55cda2b870a3144cc16f8c7d9bd59e52538ccf716b53902d8 -b6d4c1531cd30b5cd1f2c382f4a4c4db7ca9033eb76f795b61fbe36b1c53fa7f -b6df802151e78569127b992a8a404807723193f7767d48fe5cdda6c1444136d4 -b6e06ae75b2a5f155d393a95854d7a173f2e2cccdfe24d4ef216f7df56b01bd1 -b6e840c8fe904be8b108dcb18148d38ef26618fc2464d2119237e6bf586b89e8 -b706ced1a8330f4f595bb6da20eb2fd4a39ec8c9f61bca706bffd5975542ed39 -b70956ab126c9918bcfde37b0e2548f9bb404300c67d9f2e67cf297d3c5f2d18 -b70a09aec60be3b328d453f286faf1a30bfd22161bf252e5fc1e87f3a0e36422 -b70ab83480335ad935b82e73c612be8d0d9bedeb0fe76511fcfa0de687d303c5 -b710150831c7553bec47e62fa3dbbb3979903d9679198087a46479e24e73b0f8 -b710930e9fecea8073509ac645c29c783011a02c8041c6dbb1bf0262a58b2da5 -b717f5b6778874da3028267c944bd0f5e41cdbf52a8821ccb1b39ddea031b7d6 -b72202dee5fb48f9f9070e736370112ff43587af7e69c8e26e9e4368e17f2dab -b728330d9f1929262a1bb3b36e15360b90e0d1fdf95f924d8a1fe03bbca28d12 -b72d7f1ae8bf7dff31c8002dc411491f4bc344593dbdff2e889155d0d02f7237 -b734b5b1a63a485b934fca64a993143c2c0a9a9812e973d1ae36db8736ed89d3 -b735c0960172adfd89307f209a16f3a702ab4fca5e58d6030e56d88fe8eab943 -b73b34878e1346a99dd35123f585df9ca6b5bd317826ff4ebd05fb850bbf8cc3 -b73f9349ba692b893f2e9971571f7288252bead44af1f4dc61c51fe517e6c723 -b74296deb8fdfb5c07ff2185815a0d214ae19a756c09e378db8e7222826899ba -b7583dafad0b04928f73d2a737c6f128d3412069662516a5eb4a78085845e400 -b75e156289444f0946fee7901bd4f201f7bfdc8bb01233b2081ca25cc2e4c569 -b772b72f9d30cb3d960942fb24d84ea8102c9204eebc95787c8826205d5d0a25 -b77b8441d39d0c0033641877b0d180bf3f293c1e079a40ba92b9b2ae03cee310 -b77c76b90a3e04793d79f493c8791c35ec39ea7d7ce3ecd60875ec80e0eec9f8 -b789222aa6f4d6454585b0d4c990a25a281c777af410a5d36a10e6a35f50a0a6 -b78b435e98f9ad2b05436616527c7e51b9a72872174472667b3471768454882d -b7940b49c9433afbe92c1ca0a555c0ca75650d9f40b010859d7731a59487dd89 -b795fc2938514972c4c0e7c3140fd25b4955da89d71ecd89134e155ca0355148 -b79ce3889c835da38059f64a07f8f4d42a0f9db40980fb56b0acd2fd204a689d -b79f73a41b322838f0d72901f37d4f08521246eb9d0a1a734e7a27ebfb41a90b -b7b72b71e0c0cce09bdb95aee9b705e861215b3735f361064b06e97f8981871e -b7b9d286e03081fbd3eebd7b9cc81424cbd2f3b3f86dca96f1687d1bfa14e55c -b7bfb961b12ba745fd1135f7300b4a18c020b8e8e35a1dedaaf3fd15f45ca736 -b7c13dc3c51c0522a5c84c1b784576d0fd63fc7debd9e14e8a7e5d0ba4aefe0c -b7cd71effdef3f3fc42115379a59148bbb5ab2c3e5c31b42c0d9d77a1e8bd9f3 -b7cfb80399dc09e25ee1b3905253deda8d98455b60d5ea3162a21766d2180226 -b7d391dd456303b1a2323c4ed5c9be4d74b60557e4d9cd192aecf7647132bee9 -b7d5e58c02f3a3f9cda7815aa3c38a4cf0eb222cb3746997147a8ed14fe62356 -b7de5a09c2bd3db80534615dd92aeb7d3fe5781bb3b40074c03fdcd6062589fe -b7e734e67a66b8a61893ab9717064e3a778355a49f84943f3f2b0f68ca0d7a4c -b7ec678e2bec400df3f2264b3b56482d789bbf879815d85f7034584078d9c335 -b7f47981af597e216e35380f242ebd0d2f575c34a8546e13b8949ced3bf5c264 -b7fcc0156199721e8f27fd7e2eed19db455adee226e42cf38c85fd0a57289367 -b7fead881d11f43f79f7f6609452c1a4d3be5316fd56422f6660e1ff57e496ea -b8018d30c4b165897fbf0e756bb6d8570207daaba314a02ec3bb42f9596211da -b8070263556177dade15ead3ce4bb4166af01342fb16cb1c6759c30121d36dcf -b80a3db83273be1ff40ae078130b6b7026f682537d2325446e8855a2879167f2 -b81f0ad33c2e11b832127d05c0650889b220fa3e11390ae4ce9b3d42227676a5 -b825c923cd9eb6e932a095551bd1cb8b32a4525acc6e90c21aa975ee413c9ff4 -b8278d0494a06e689851bb0c3b5c86755c94c4879a0b3563d79b515102db1d87 -b82838134bb5cb4efbaebb53bbd0f928ab810d5cda984e4c057a26a75fe0010f -b82a98b73fb121d206164a1b2a6a271f7bc5220cf683ae9bfd7ef3edeec965a1 -b82aaee436925d6f64e543e0039111b17b51806b0357d2e8e6cb039753877bd9 -b830bdb8182b9ef8bb088e51823080fdd24380e974fc2248f68cbcb6f4aed8b7 -b8470e0a77696987acbe8f21920c5a7baf2d0e1aecafe911dad00ae119be7109 -b84e875c082b730440541faabdf0fd7af120939c648e4db04cfbd78729f2b3a2 -b8605b396195bee8d3b8664d06dbaa7efb37cdaaec4df48adea60b35edc9b3fe -b860cd964429f220b006044a58cedcff7b892313f6f79d3d9a6e7fc4cc5f17dd -b8615d7661464ff324c41f336e3e6ab56fd1aa7aa44ff9adf3cdc00f85771f6b -b865bff32f149308b5bb75000a703851b70d6d4c92a1ce352c7b50c78c5c2ba5 -b87283c499cd5ff7dcf6bbac23f0cb32b1522d2cc8e6db4665ee424a2798c271 -b887a1844648455b718ad6a5f929d01de9c226e46e55c45dc3f82dacf58a18e5 -b88ea143aaf475ab0dd9318b2ff914bec7515641f9ddfcbf81456719cf3043fb -b895575311f3f9b09717ccf79395c51799924f1d60a6e53634e1a980619d1da9 -b89bceb3b9dee74719b3cc67949c4cc7dface6dc26bb2133358f3bd93635feff -b8a2e80c3fa4ae47fcdad28c46123f33276e44232d4256d2aed7509fa51bee17 -b8af75168706075dea9c8acdf2dedf2de45e46dbb375a467a247d0528fb73599 -b8b443e7230e652918012eaac8ff9add289fa4b185ef28f0476790aac284e1cb -b8c3ed246a80591c94e2d079f3f2204f5893f9fdf458cb2d7237463bd6b8a623 -b8c5f16c9fd21d1fd73ed40036c98c5e83d356e2d8e4d33a75b5672823bccad3 -b8d08da38c8e753b3f315bdab298888e4ec2e7bbb4471933ec1d1fb0ae49d399 -b8d4d880ca7e0e47795b5f23a887ddbb0d7ccd1e5dc0fc91a1dcb5379ac27586 -b8d624c80f57ba44b4d8224c6180cff8bbd2222a1655d3ec96b47feff5a5eaeb -b8df4a9456956f3ba0111cce24a37a6224dc06aea97adba9a074213b48b5bb1b -b8dff0477d895cd088ad2be3fdcdbec5b3fa433eb5a94030b99e50f1a5cc6ad6 -b8e14e076e1d9f51d8b127f36196f8af7034aaa476d13319ef7aa8a3511ace23 -b8e7ae9c43286f50ab10b32fd3d3cfde46b5c1e7be8958be325b9b6cbd28d08c -b8ef30114c6a8b050af3a9a670d8d12365d25946a2316203d6fabd86a85b5b0e -b8f37be6b019de1f903a28fb7799bdfe6ca9b392a9f738bd1d649ab95f655506 -b8f54e5b7d00f1afea9ccbc5b28ff3707a5a18f3a6d09398fccac1eb66ea582c -b8f96e6843a1867ef56a62389364c88dd11fd7a09f44170cc72458841ee8cfb4 -b8fb965a9e77c860232d3255f1e59ca845f9b5bd55351c343647fe95fa9314d4 -b9011f6cfdeb19e0e352136fdb1234c5f94ad6f3e2ab889f17eb6dbce8c5730b -b9022efdec95d143cdd39870f00ea1a318f0ae3a5cc0c8a47914cef968511ecd -b904dd521b70ee3a2d8bafa741106fcd309b9f6bc47c89c4eb5244169db01a8e -b909cb959eca886beef2c94d193004bf85486480a56637a2897db1c02cb1db1e -b90a2e8c188da72506224b404a8ffff0125bc5ce33166432591eaf556953748a -b90cafd21f73126c305f624dca599827c26b5df70f2a9c8b849110607175b18e -b90f0fb47dcb21cf105dda2e7295f47ae5f6acbbb2ab6751736edd04bed7df93 -b90f8a982fc1d2503daf63dacd35bfc714b22106590a11549fe4d74e85989cfe -b92a327d365d610f72b20bb27845e9390ad53e1066edaaf2bfa673ae274b12eb -b93092c25fc95895189b1ecc8f13fc99ebcb54911b361a093205edf3ec83cf9b -b930c6ceddb6ab9946cc60ccab66842b984468004fd68e404b9f8bd870cb1b84 -b93c720aa5467e92aa9edabe41b46a611dc96127343f8c13c876e6b4057da52f -b93f3ca6d04baa233fb9227923ff93beabbf9831214e8a4e24a6cdc996e719e2 -b946b4f853a943566fd25da6d965723eaa393b997f116a6d10d2df9c6cfea66e -b94e6265464d9be80a0fcb31446bf4442573b66ed41a0a420b1fa705d089d21e -b952b6a49e85ead3a34b331ef07b25eab62c5c4e1985b1b1e2e79e49a188d900 -b95a4966832ad096c0c9c02ff1812ebe191b9f8dfbae995e60de728b873737fa -b9618569184fcfc37fa5726a86d17591be79d7be356338e0207592bfc9a647a5 -b963cca41748f1805f52ba3a904e735f1b780529c4124c340a424c3df5e1346b -b967b949349f220d26451869ebbe4116ce64a419d62885baed877a979d22b6cd -b979115179c8d7742b4a61c6a66576ae2be2e6a63799d42352f1ae5844ab77dc -b97a2803770f4bbf508b13ecfd56455d32c8444f534a5e8befecb160cd916d7a -b98319ed4e8733d8da2868d70945e10aa0f0a8a29968a7a68f57ce53561c682e -b98d66d03de8dcc568e26060681fd18808db448f13cf91d3e9327a7485036d6b -b99189abab0f10c82bc2ffc0650ca085b7210a03506dc56f824dc5c2b20d4bc5 -b9983117d1992d0635f35fe81a650f7db5484b4d07d706abdd01e14fce32730a -b9a6db2c00fa08bcc921cf8561130022d02b3c61e343a1625a81d65c92cc86d8 -b9abfcacaa782b71fe24eb945df308eaf6cc0cb755a77856bef7a59ace16702d -b9c2bfe122ad75efa4e7f14bb983e6f7a6e489d5facc72721ca9c2de758713d2 -b9dce12865e44b2cc19fa6236593cbb48d70149fc2fbe7c01d7846367d30cd7e -b9dec7a938281c1dc3e0d25838bfdf65856369917ef16d5a17b447c609e1517e -b9dfae18b445c7e16c5e1db0fae4bacbef7c5adb69bbbf95b4d0ec2f4bb31be5 -b9f7fdb5b1a53ce2a949a495fe5b91a0b413f96aa6126552ccbcd53958f962cd -ba0604917bf3b9f0169d0b4a2edee9681932e9ef343af71e078711555a57a53f -ba172989eb7c823f72ff8e640e519945aad03f094dc5eef40b2e8d3c1d02dd6b -ba19acabb81652bdc5c38dc65e67534af7ffdf579bce77b9f16a21ab18ed7029 -ba1d0008f3298c9a529fa72190283b04e1cacd7f2aafeeb02d11c58efcfd67d8 -ba1da615185ea49c60937b7e64da9152fb5141c47b6a5969cd9d80679c294409 -ba205b242cda799f3eae71910331bc9521ae4572b63728e7a1e85111eaa6b560 -ba295f8bf687c9ee9e34d1be546e118cc61d41f3829beaf3350a7352374f2b53 -ba3dda8847fa1c5b2580365af27d42e5c2f425b9d57972aeeda9883b8420c9e1 -ba482207dbe2b217dc23d28197611fca0d83d166855f7259f8eb7dd48a1e4e57 -ba4bd9c9bfa6ca72b86b212ae78f3da0b4714f82da3db83849dbde77c67c07af -ba537ac477a66fd8a11f116a9cdf3eb8032d6cc3831a2277352798ae3a188fcb -ba595bbce71f08dcd7ac0b7beebb59ab3b4091aa50a1b00f10fdf8b70c569b39 -ba5c1a25f161412d7bd5d5c09c7111ec690a1c159093e83e9e052025ccc24afd -ba61a6caed94d9b539f086433c17a5fb3ee74932b657df1eedbe930c856d4255 -ba6e4e20669c82641ad0bb685e7cb657f690cb6c4e3f4647836542a76013a1d9 -ba876258f30b98ec3111bba6446a0b84967a1e68635f251b082cce4807c1784f -ba8c2e2f0eb621053311dbf1e09fa1a8be1ec64a600d8f6c883618ac0d744e87 -ba99b5bc4f77d41722772bec485db21e9966114531004c20d67791c60d9c75c1 -ba9aabe13028d3850b0c42f8e4f7949733e96942b283a72d53140ab189f2da85 -ba9c1d4307722d16c4b4c6e01ac8c2bcdfef4cee8002db0753d0e8db6d46f7d5 -baa87bfbca84d5e72cc479819f56f27a8960ae9ea0deccdaec23180b73542d5e -baaa6604fb4705739eaee1e962b176ebb3c96995ee245747db8ae7aaa15a52be -bab0f399111ca2186e1a326a41e1e5e79013acca6eff8a5d4bbfd8798ec3bd5d -bac559ef891b39bdebfad4a62681010aecbe5efc424ecb27d8e52070f84c5cb2 -baca686da5f7ad71044b235ab5f93aec8367ae3980874e9b3d4d6d27d6c622c2 -bacb25d0a7b08ac5e9cd17f238dacecfd5f371235cea64526dc46b63d47e2aec -bad6173a95c9288cd84601266ce420ddc4df5dd9a26a7d37444cc3703fb5ccc6 -bada558bde83bc4da875072e519c8544611df586296ad0b793d702872d2ec66d -badc155162d0cb4058d10f5ac6c5be70f08b6ea01a8db4af8b3a891cee1f4f31 -badeae0dbae8662147a007f9995d3b2fb5fff79e7a46804bf927480fede05b70 -badf1848c90123c9cfbab349a3f482bbb2223e73a292b2aa74d007da11f65b9a -bae379ff00c6b17aa91d5aa1d2604949a1bff87e8a94663a71f08729eb6f0e09 -bae93ae43cd6a20b037a4df22f89fa5e9b9fd129126af4dc8fe695b4fb0c206d -baed7622ce42cc9b16adfa6d24801f05180f7c747f1b3ae1de8b1f4dfd974e72 -baf543f87857a5d3d5f1c9c07df1eaf4f19fb70522a3b83ade108dceb838c94f -baf880aaa994aed203c50fbc4f76410e034478933116c87808199003db4653d9 -bb01523222666bd0fb737bc69336b12d1bfa08732c000d46376a85c243872988 -bb05985ac47e9d3f5beb30c3d387328f242264159109651550c1434a22fb0f0b -bb05c3321d121ad39df1d132b1366893ddb2db5274748baffb72a5d36b029356 -bb0d66c2c7fa456108f8b29ca3cd39360a0d04f9b9cf610aab04646a3e671874 -bb16899bc07b7bd9cc2048ad606c79c123e44508a555e9feb71a0ec71f966894 -bb2b7d1d38d405e25b783fd5c99b7bc185e28c507891d4cfb9cf9af4f532ecae -bb44f54b7bf09c38160d1f647750cbed3d72e0f5e13a21ea1baeec04db13e40b -bb56ccbb563e9d67205d47f5fedfd17784daee1031f2d5af5d61072f1b734881 -bb61344157a2c9f88896220e8893ab97ea2df598db58929249ba031ee346af57 -bb6bc2074ee51b086a6ff02b30d02fe3395b522aac03608cf8d4ac19abd2729d -bb76e4a8d4420f42563ead57e16d830b8c4a8c99a967883e39542d997b21e5c6 -bb7c009dbb5c234caa8f83170fe6184764d41400cb92c23c65235dd951de987f -bb83bc1e33280cddc3c5b8cfdc368f67bedd2b6edb4aa53323714bc70a4407b7 -bb83e476d2f1a6199a3f8b610caecaddc4fdbf99a1091d900ab9a0848493889f -bb91c6ad311d26bbed7aa8aad0aa90df11889cb5593c87bb6a2285ba88feaa8f -bb93e635173c40d8105d5bf3c710f69074c7b0731a90d9cf97d9eaf5fe072bbc -bb94e29b6e578bd46b02e66bf3b49743287f205c51982efaf192ff2a25ee0937 -bb95d85c831e6f14e07da384a7d22a1ba55a3a1643d1621faca2659a64528079 -bba42d1aded7dc8a226ae11b5061d3f1611f2233e995a5557615a3cc472f790a -bba43f6a41ab21bbdc61e4577d3dc8972c28aa89527832e45bfb26f0fb5440b5 -bbc1500ee416a90c4b8ce254a977d0ea0cfe585ec853c362a9485c8b1d2e4e90 -bbc2ff2c1c67e6bea96463f44b395cc6ddb4181ad6d81097fb85373dcfbb2904 -bbdaaf303391dc46957566e2a683eab6285241fe71a25aafe4bceb0a0020cbc3 -bbdb2c2e837ca7188c214f4476d01a6101806e1d53ab1fdf7dbc7bb870226d45 -bbddbe3bb077ec707f9a45e564c1c50b6b1c15b48f63b7e1f3c535312672c308 -bbe02e1200e2a871f16080d6de53dbc0aca4a73132e38079bade82c2d615172b -bbe5d0fd11205787d98ed8a1fc0579403acf1c19d228d602d6b58208a6025a97 -bbed75ab2b988407308bf8171e996100befc0295a780fe9f64da8d845934fc6b -bbf74fa3e504cd9eb2fe416d58943de16430f046df505b840913f9464a821dc5 -bbfcbc1e79c97ff661389e660e7ea00cf516ad7b9817ed40dda44f18d5236d83 -bc04cf51e2263925b5c6b6562379d77975a9cfda33ce01fdb4459b5fa36ba5b0 -bc0acb0993e95f4aef0fd95aa73a561fc8ff8041051ab9be2e1f90367d9ae166 -bc10ea43381942b6642755d2c4611703f2f738b97915027d051be3238a661b83 -bc15b34ea36e124614fc37abd37cbcf0cef2f6ef66a55a73e56c22c95ca7b29e -bc1ba99982ee64d19edaeb92d153b94fa3ea64f8fe6674e52760574d711c3bed -bc2abfc668e7c4a2989eb672a162ba97657e0560ba960fe3c01bce13bc990e8c -bc3292e5ae03554854a68f2afe80c2205a3875bcd766b20764d1d29ce6c9d79e -bc33ae8457e6105f67a465b835192b94df7bc136564ad646d0ae46447b28583e -bc504cb276daf4abef1269e4ddf003bbae4fcb73ac5413708a62f0bff23c698d -bc575873a2649a4f47c5b6bd7f280271ac59748c24e2bef0a8345c59a6500e69 -bc5b19871152dd2bbafca4fb505b4649bf93b65276124925326a6faacbd18fc1 -bc5f4d59baabfef6b9ee25956d043669365653505a23d67e8a057bc4e34e819d -bc7aad34bd6b1d5bd6ea64cf46cf79394e15b7952441fdbd17d5e12c975bd661 -bc7e6e6b157d938df4ff019e2eded0d91beee87bd77391fcfe43f554926db8ee -bc80d7df7599923ade141b9fb4d16a35fa19ea5f29008c8681c6d8458cbf05d2 -bc8db92eb6a2065f5a7709c72bce63fa0dd91e060216ab8ea9cd5e59f8060ce0 -bca3e64142c73ee0addbd2cdf18843520cafc5871e98405cb1ef28b8866aeb80 -bcb0e5019bfe65f958e22c9268d5732fc65933fd3d9ca1daee578c929ad5919a -bcc530cc03af35385b3b828400348fe8d681926681831da3a16312be388a4f10 -bcc56f48e2dba0c6acd65d7ca58a9cc0cf74f8eb5f9591f652b2428e3a1bf9ec -bcd5513c0fe26e384bc3c56ca005e29effb654e65195e1aae9be14586c7fbef1 -bcec5806a0105838819daabbac1d98b8381a5fa2a54849442fae02e0bc59b53c -bcf8ddc59c3395e03f29db19b8a9b009cf93efe5b35b09a597a12adb767e211c -bcfe77d466ac31a629321bb2b4ce83bb44c2bd3f292e33905a36adb09daceec7 -bd02a3c972dcc193cb3853ae36a1e177c49c91ba1a5f2f6851e4383918d2b1f8 -bd0894c3dc516640d0ab34821dc6e09a67b0850027920c2dfaaa4c88bd106f05 -bd09927e5524105d04aeacf5788dddfb75aeb4ec8eb15d89ed5feb2032d85c04 -bd0dc609e8fa6a4b45d82e42d6d8095409688573bc341dfff7c6acd7384a84bd -bd1043a072a1494f12902ffabfe00a8f6c18a040eacc7ba93258c43d67c88faf -bd2cc857e96a84d1601917fef2c72215aa3ee5d57c1052de168979f7c44856fe -bd3449d2a4953bf95ac0d1498de2a2fca5a09eda2bb064c4013455fbc1ab04c3 -bd3c35257c0cd601c515b6cca04f4fd7b6b7be476a6b0d244f3aef27531d1ea8 -bd3fb2c0a37da551866ed9af0ec6e1b83a8522631b00a11eae091fa9b79508c8 -bd41d0762c11445da824aaf0bc47b75936eca85e90d31557e07a578c9f6ef794 -bd4fcfde4b9684b675e9de6a3426b4db9502249183e89bae03a5df3223c4ceae -bd51c94e68f392e1a0cf40a3f98335d4c1a581614db1aeef203f7c9123ffb436 -bd5a5aa8146fb1570f4d50a887b07046f6955b21ad8f5a5b945044c98d99cd8a -bd60cfe1252cfe32b71fd51fdd8bb4e6238709580e2e22ee49a9daa4d9bb10c4 -bd6498934a37d930d2e93d8579f459752d41c92cdb024e35a7e4f13ee4cce006 -bd70ac3875b8309563d009a713397b8f0adc8160c38127dddfb905bd11418cb0 -bd785a65bb3a56ce45d7d94ad8ca2b2ceda88557772ba6df5a354826745c9ae8 -bd7d1e7c1304ac80967e5c856edc9f1e31b96b8d7034594360e6f3edd02ff5ae -bd85e6e0076caf5b69cd10c9632db87caa5ca609a94919ab6b30af248b7e876c -bd8841df4c0595a9e4e64ac5995fb50e2c6202a0ad1639d2fb796a3efeb10c6d -bd8ee5c2521d824fbcef1106169014c120b8694591e3bac0988236fe0075ba5a -bd93a02f219ef2569c7541ee5baefafc983a7b5b293c5177a21434e5a2168ac8 -bd98635999e6f2cd3f57916367510a077d69eaa4137fce7949ba17f8e4f89d8f -bd9e0f4fd94548ebee588ac4c5b62c8e0e29acb7c3779d4baec7f8b866c4f36c -bda2c2ee18dca25e1a5883b619585653c83f6335cb88421cde26be19298bd0eb -bdae1495721c53f4c6c4bed5c3179f63bfcad4374c8cc73d327ff0e4aea6f635 -bdb256b43f965a875d88ed33b61f3e657ef6fa253c99d58d9693ae6cfb00a564 -bdb897232474ffe0e682d39f25aec0b65082101ebc796f6a1315ba6275fa63c1 -bdb97cb9cc416b6808b68e322201a3bd8cf87864ffe33b043a2b6dc5b900d62b -bdbc7e01ca9486d2e1576862c3d8762a45f9bd33932b209937af0ff27c56cd5c -bdd187018cb62d3a90941db0b2e155af0f4add42dc220818390e15c413e7de4b -bdd5af9e6e47466dee8641805eecfc3b6da28a3d7798d4b98a628cff24c15bd0 -bde43f762113e77ecb522c21a4e54682fb4127cc0bbd53a21c2116f9a0388814 -bde9c0f4e2610141852952a42c25cd07dd5153ba74b44ebe323cfe3ad4b79589 -bdef4426fd357fa88e2842736711970f9b85d59e1c79faecd6d675b9ba0f0820 -bdfa2d18479215fc40876b4adfcc178123ba0dc7d33c7fbe78fe51b25aac49bb -bdff0d46472b7e21f71fc2f879a687db13984ca12176d6aa9fd2a84b52255f19 -be005ed68e141a2b69ce81d9632267c450e4a2ac8266be8f424fccba4bd6994a -be02175bcefaa92168fb0502aca8b116bd982d42e63160e383f8b2a240214ba2 -be272529727ad5facbf5ee39212aff865fe6347e14c3e651980a2368959fc0af -be2d93c2095258900306703ffcc6dcd460e8f774a10b1319d229d6b3c56e64f9 -be32d521051d06645a3ff563f7c1a28df7d3373be3706ce0553788cef0be64c2 -be5feab18cc219d6c3a7fe9e33e86ac4c6d8924f900cef675de5b9165e5396dd -be6d9e56592348560d20c94207f0acfef3b88095a661963bd97da5f17e235cde -be847daf0c03ff776ba3b523de0e1dd62be330d7c35a6584f764564424e1c0d5 -be88ece8d892857291f32c3b836fbd87a3183a829d20c8ef307fc5b7db42189b -be8cc60082f3d07a902b59db9ba6aab6cdd279f0725da387647a62642cd23829 -be8ffd8c48ea29e4df537cf7068695a54db2c2c89e3f49722851f7290059bfc8 -be9e1ef9cbeb1e5b69545c50ed011dc5237b92de26f84ce6786846cb53ec2ca6 -beb0308cd1e2be0191607373ae3794864b44126ae5ba4466dd7c6b0c071502f0 -beb6b0d1314af5c03b8ef7528e353a2e0171b0112959472c6a2e093b68f2c250 -bec46317c0cfe01dd007e8a27bf64439c5ca2ec8064b80b8194e0532511490c7 -bec74405e36ff63fbad4830ad89c47e710ab664ac21037acef4d08b1a40eb11c -bed0152995df4a62878e7bb3c59c8f564d0866436bcdc0a87a2abf9d73517926 -bed46ee65124c8fa3e53bf1df0b8b03d857947c7b59dfbfc0858feb8d26a20b4 -bee17fac582bc724bc0762837be9a2d56f7a6e21d5890c225ca19ef954d05dc8 -beea46c30a14b3ca82bbf7a9317a5e8f444272c8118ac8c1de7b794b91240053 -beea68b789439db718c9adbac5af942848fa0a8a1c0774fee354cc14c5d26d28 -bef6fa2915b27094125018eea5cdcd043e618102babe3a8367d898ab4846c8f6 -bf04aa5ec26e4b8fb015cf5abec1f7c6b77f4ef9a7f0f0e8dd6eb6e249dde31a -bf04cd2a71e1d740d4adedc5c5bb59f7fbe57f2148a963d1dc4cfff9a723122f -bf1630622a668fc8684b04a431649475dcce2920db5fa9e24219be8b74f5a23a -bf20ddf618553d14a76356589f8cf1787c7ca090b2e69d3765d172decd9112f0 -bf22a7414734a617bda1c4e48219337aecda7449d8731ad447c70b30d815798b -bf337adfafbe1cabe72972048882f0da56c6e02139fadac7f0e1f52b2432fdb7 -bf339dd0638634b8a4afc848b45997b258b9277d17061939c8b6676c376e48ad -bf38191e283d03d9de86018c76f31cecf4526a1afc8d0af15b5d892d3c46e4e5 -bf39d9e7491f874dc28e5c3c7aae37937062d888df3ca577203f563769888fe5 -bf471da5333f281ac6a8a9e8281b293411711e6113d42975e2b5cb40e8a481ed -bf4b92694008e5e954e703715e52861efd91d32f8b2964336649eb466ad9bf36 -bf519e6d973d8bc2c6cfb59195a63ca6a83adb9ef6cdb1b5e34d580db796d645 -bf6456591839ef4b1f65448554cdcd89709098617624ca71b257c45a6d09872f -bf6ab618c12f9e1525f6e3b702ad460bbf28babd98060ae1ac91f9690c064115 -bf70899865bfb8b3630850c9fe392f585966fba4f2b2639c6d416d7163a36a82 -bf79bffb6ddafb05babf4af8d11232e86f608d251538964cd96df71c1ad3ccba -bf7bb5039b9aa14335b053d255773c830e481cac46bd1e3b1776db8627c11653 -bf7e001309077704756b51209f5f78fd55b8aca6bc8838bc946bcd0c7b5cd6c9 -bf84dc18af096a13fbb3db728e8ae5af47e69318b907086d9a194a2f507f4d1f -bf8f4a0f89beaf545f64a04b90074c7e4a3dba482df3d8c8d9c3bfe1f859ff75 -bf90c7000f360a4d6844ccdb46f55cbba258d0fe44e4029980c6b559562150fd -bfb032b4b3b686899d00c4f1607a507d032b7ae9f6432ac56a459fa51af67ea6 -bfb0c97770e6fbce055baad77e050cf9ebb5345720213afc1963d84a590600c8 -bfb6e1f22ed632ab9de0b523d14abd93d3c221a3f6796c1e02418c33d6accd4c -bfb763221102e182e130368796778fc0be5eb7ca01322b24c2631dc34c90ad13 -bfbb4abd77b78d9e225ca2cc9e78edd1c5af7685d27d2fb1b0370b3e42e4262e -bfc379b212f88c83f22c38cbdc70b8fef9d91c26844423a92d537901044e4464 -bfc461bdbd869d48958885c1f2219998f9311bf52b805d49200ef50e9ca0dcc3 -bfcadf1bbea913e96d18b765e4233329c47e02be97e17dbd87718e1581609d5a -bfd3297a0658baee8083b4bea39aa9c54f685be60e169c4809f80b1fb8f6360d -bfd3bed2f62902612f378a0b80247939d09a6faf3e4d1f674ed93f697461736c -bfda193755905997348506a9503d49df9bc2da7b9386dd027e493380c8ab87ba -bfdedd3a03a682149fb5d97d4b519403631706e83a5e7403cd4efc5a80dcfbd2 -bfe3e39bc0efe8ee75e8116c3d6663d239ea6263235696194f5979942498723a -bfe930021f84c5ac8bb0fb27fca0d78eadb09fccb17aefd707d9dc87a31a2477 -bfea26e2f1f17b6137a7f85e0cbbdabb14834b3f4b01df50f327c3fef36e9b24 -bff1b32326a212c7b894e6c1b431604229ef0e26ccd2c17f64b30b06fd851aa9 -c0023113a5a79ae49779aedd311d7902c0d79ff60628e7e73de9476832b85700 -c005ae0b7cd14e6dc398a35b295a1a1624a7785f76e1af440a7b4d006969a0e9 -c006ef08932a813cd7beb4904971ed702234e3f7598529915393a8bf620c2048 -c0191bb33c1dccca63d1f75d70e9b7808a42b670e15b23b0de917bb45df53866 -c01d2baa56fe1e2311bcaec4f980a360c8526b104b06b14bc7614592c5af4132 -c022fc2974ff6341bda61f0333f82837685c692183cfd13b690945a9614ad841 -c02e91fad2d5b385a689a63b76b43b3ee224534c58678852d52cc87872bddca9 -c0379725872670014187198d083a342aaab1e6c253eb2a903d3116b0b93789a0 -c037ca8f3fff4b12188abda0a5ec12035e7a15306cc58cf2dab94c39bde904cf -c042fed7ad2fa74bccffb9444176d28ddb9f50008edc3e648176620a3d57895d -c057e3412c20931e008269457f488c013fc3eba32ef9ea7189674c77bdd80d91 -c05b80b99c92b2a6665092602410791aeacb1109f7f4ba9833ff87f36bbf8724 -c062aa813b7c2669c670de208fe212070f343c2e6fa285d358c4b5363526fb16 -c06334e37418113e50fb7994e7f571540dfece758ed620e2e6fc4afc28b28157 -c067e5a33981925333c6711eda40e3fa0ce8be6b11b48da8d359eadca46115e5 -c0692723895b4a1eda0b5c82fa8c53dc1f78abcb31cea6eb3874ecdf1f66299f -c07aaa6d04f5e55da7aae9205ad12651a6d4a812bb33251003249ee3ce28b49d -c09216e9b583a2ae677f18152af39063bbd9b1a32d44a244bc3e6e3a3efbc445 -c0984724549da8ff450a345f615e1114a08674298753e364b60e8ba95cd9d9df -c0a0c39300a268efcdeef375616c26a09fbb3bc132d338d20c59c27dd492aa65 -c0b37eb283dad3b49aa8b76fc700ff08de87296f5d5dee223e1490ae5888e693 -c0b7c6d3c75eee3d65472729316ab0a155af883a84d909c5fe3af3f798cb1b9d -c0cbf6fd5b6b6b08e6c3dea0f7211a7c644a59c0860cb4643a24e6d34719e48a -c0cfc210fa245a58bafd58b9453553cc87feaf5cca5a819a72802acce411d6fc -c0dabbd504fd3ebcd41508992a7e78ae1d0556b389eeca8a0c667fcf4f221df7 -c0f0921c67a87320f58aa2ec19fdce362c846b11f560a9fd2fb68c6a0fe13e84 -c0f2aad84dc178513ea7c139f84a7533176a65b225e8b4e63dadd6db46b0da7e -c0f7ee6c3974e1a805ceeccff2f550368b8894891534e7fae80a7a5c1610d61e -c0feed282be7b138abeb68b9b9c8c9388dbed3b5df1b55710661248a805c2e1b -c0ff501a6106ae3f0b90f2907b3f12398691a044d84280e61bbf875b9629f6b7 -c1080e633cdb83f33561a9487def92a5bb942d8aa19690770a3a35ecd865919c -c110e140e61098152fddbd6e808570a3e821aeba2258c15cc9d2ef41ab9d01bc -c1117cdd75f263b9605f988568c17ee9634a737ba133512be459f9c412266d17 -c1123ae7be4eea177d6e11ae7b7cc1b2aa7e67e9e9bbd2837eaf0a53326f9d52 -c124f5f9cd72b966b2f7a88508e5bbb5bfd8d8c24ddf6073695c5a5d638c8646 -c129d2cf6bf4eb55dc6afe3645eb50a0ba0a0c3a8407fc214492cd4e82db082c -c12b139d37e10beee15be82cf8c9c4362c492071b79a877e4b0c62acf00dcc5c -c13d59caee6c1b790f12ba3007d9dbf0fa4551f413b7555961237906431af0ff -c143bd4b4335bbac1b90fc88f6b3acdf9a54c4e27999c631ad76a62d1e907749 -c15af218494e9258ff5e5a3f6e766cdcabc058e5112355b3e0c17b52075ccd83 -c1635a41dc1c6a1f82bbe64142481ed5eef08542fdb32eb19328e4f540f8dd27 -c16c6f4bf728693f5f92c6f54619e9363591a9b2cc10e47a73f18c63e3d166a3 -c180cc825b6241f11d7786c3035b6eba1362ab34f1cd2ac65f1f862604ff15ad -c185285c4849b301e095fe63ca25185fb9742a355f08c474b3189fdd71991586 -c198ef2fadfc135599fc2ff85860e8376761436bf7d7295e00f70a1144d6bee6 -c1a585abcca9ca03ffe2a99695e4c975384ded45dda2783678476ad3d3e36a85 -c1b03e01d96b36566a1583d3571b18d973495a7bd1231512827c884ba2c23b0c -c1b2e348b9045f2f2b6fe70cdc55d94b25d246abe0ad2ba34bb88c23995494f4 -c1ba2d9a637fc6d09930698352f06e36f7d57322299151da435e68c623e57a3f -c1c2435b5b0598ae0add505145dd82e506f0cd2a4dc9cb44057a03ee5f3f981c -c1c57fd0d0522359fb3b9f622a8307b23c7369a4a7d0a5d83c2a71feb7ebdcb5 -c1c7aec8f65c3c55af11e400508a6add9b070c974f605f31dd80f56ccda3dad2 -c1c9d2d21acae1a6bcd5baf935f5452995087c3e5e6d05a7d7fb80e853fa6668 -c1c9fa8c865df3d8e3891603483cc46fc2d82f35efbc57bf78529c1a9e97da31 -c1d5d4292acc3780fd313609209a9c07a5dcc30c90d060c61deeb213db425c38 -c1e151dedecc9b3bc277d6b41143cdeeb58bc3ab067f94987c95f91549c27623 -c1e8d9204c66df0a2d1e4e8c614c811b747f4dc336fb65b866b4c42282b22b3c -c1f48416f8aeb9b060d42a50fd1808c59ec927b0f76890d606aaa59bdaaf7662 -c1f6ff65c137c66ec7fe4848ec83dea3a4af0de8be8de1227ddbed9440977e38 -c1f92d0276b6a3b379bc4a5f10c1c01be486c22994521d1c8d9a1838f72a6714 -c217ce4df0c70f9e74022e1f4dbcf5311648741ffefaf6ed49b42513a282245d -c217eaf424e82259332cc9f735297205d216580e7269fc75a1a96a6e243438d5 -c21bd98713fe2ade6a2e7b7dbb273b02e99f4c04b3f10823f0ed04ab7410f8bb -c22cf3960c084440b22c49809177144d4c6811d99ffe4d92ae63d2f4bb917737 -c232150b2c0faa38708a63010e0bd713fd80a7e16881b2a72b4a9a91ad654d8e -c25709b17eda01d25e2f5e1bb426f2bac4a35be40f4963f1549a9c67189bffbe -c2573a8c66ef0683ad986a2f704cac1308b811c513dd723a06f673a893065771 -c25bac5f3f7e98f0ab7abf1bfd59f5e66501ac9b1631cc18547b25a4af1dd656 -c25ed4010d851fde795135f2e659e59e973190147f01c3bab5e8758ea870e953 -c25ee1b04c968f82ba8da833be820a69bd03714eabf96cb3ee685375192608b1 -c266caa1298354042555c56410afd4e1e5c61eb4eff1510a02095a1bdb5f3b9d -c267cd5ed60482793513bb8314f6edde5ab1e3d99132cc69827550beeb3c5cd9 -c26c88c109f65a98c573bfae283ff68b001e1d9df9dc0473d441fa3d22739d89 -c27c5a65bde76dac5abfac8eaaf7837729a872d4c66f910fe65a20f2ee78bc16 -c27e97d9f04ba88aa86d6b78c21bf80123d98b6ebfa7f3dc8e9f9c04b882a7c9 -c283b30b5919f220009f2b6909c06011ec966642c52a045ea03d32e9bf09e9d4 -c28bfb0e757c42b5f5b52a0fa9e74547d0bf8395e050a7f90c027c56b465ce26 -c28cf133e257ef67c7d7be7012ac5ad659dd019e30427c4070465c3b1ea1a02d -c28e761ec954fcfd6a57706795487730a15b35a44ce67e21b2a41935f6c4bf94 -c28ea2d08b57fc5ae4acdd2920b34defb249b4e0719902a36752279422b28f74 -c292b3ff3c042c748e913f90f82bd452539ab22399d0651dd074a7d1c5e3364f -c2933c259ca8e0398ce96213abe78ad71027ff40f283880d43d0345fddc28b62 -c29386bc3494a459ad6e21ae3d3ba3d079f9c14725007a60fa9814b91fcad628 -c2aa0085ffdb0ae4ead485bfad97f80aff00ee1c01272dec5288c15db54fb786 -c2be405b8f7e4ad115e37b9b3a840eeb3588ae887cbf4d1e65bdfc8462bf397c -c2c0b766fa03804c1ea30a775174c789b6f7a55a74b801c6a82c655afe609dd1 -c2c4bce3d17b88de0cfaa1f13e30c099cce009f5de1f3fc4f175809b47461161 -c2d8a7f2db2600db67d4afcd08079b65a6b523db810e370a239369f8b075c792 -c2deb4bea61fb976d0a739b22bfbc01796cf00d8c27b8bffb45abcba44289a86 -c2e097972625d86713d10ae93c3d02d24cb142477d8bb3c62a2ae2d8d948a313 -c2e3c333204993c214bf62ace80c9ae304df850b108f2240956e2b05319be206 -c2e3de2705571a4752a8aed72b6aa59fa01dcee5b83dfa82e0101dfea3a05a5d -c2ee31026d9d4ff851f7f5e93b36db3dc57203b5564b4b4dbbbe4bb7253f2935 -c2ee455bcde94419d28d5744b80536df5061b6ca384334a168ce3dcf7e3051b9 -c2f05e77783c5a7be337424f587e63accac797210783ea4bafdb1a5340594611 -c2fd0def08a9c4e2be0ffce17072839dcb73c62c5d60fdfc0045531ee726ad60 -c2ff3e69bea93f5feebc12bdf643998f6fa8a2b54833a47ef88e97d19da6ccad -c2ffb9beabf63b3496cc642cb87b210a601131e1733f0158eced597979a70fb9 -c304319ea6957a85280a21b8bdba77903c5ba164e7c1bbee31c976a304ded63d -c304d6ec9e0c93e37abd898853544eac82cb87bbc743c71fabc9b7c4d94293b3 -c30798faf7cd2023a73a6a5f5a008e7390c99a7fdf5eed4efcda27d2103c89cd -c30da80c1db9c601023e208522e76b26aa36646ef4d19aa42a84e9e07a1c5870 -c31589714549076de8d879530039c97cd5bda35dfbef4dcfe3e7fb84c01af167 -c3290056f77dca461d2128744424b6cd216b3576eb86e9cce221266773ca8173 -c32cc4c433857e8ae2fccbfb0fbf05c4cc287b2c23675ed2d70d190326535955 -c32d325fd4196f11f78fe03ef9f533308a18b98a1364d4279cb48161a2ef4047 -c33b6acab0f27035fd1fa1d79678fe26958c6fd2aad27405df3825f5d5ddd5af -c33d40a49f7b17d29224da0759b6db66ab749c6fb4ee87b60ec7afb791b9008e -c33dcb8d3bcc146ffdf0c03ea10835cb67b454ba38191d10a00b0c7843650c79 -c341f0248a744c3d14de167234eb2b08719f7f009c0f231b2e4c4c805f40bd79 -c34564a1c4a8fd67043ae6d03d626b1f21b50eefc279fcce706ba392a2b083d5 -c34fe8e78ea339a2883d24b51b8bbe3c95943321b60194c8fec013bb5813aa8b -c365a44bd459cabf7055770f9ebae5c0a693a7614ecc92944c3a4fae3ce9ba7a -c367bf38a2870b3a51bf0f927b0ffdc04b2bc9c9bbd8ab1c37179c29ad622452 -c36da11603e572d7021300894110840e8712a068398c57d59f3bae1287552a63 -c3711bd48acc875c9efc2779df1349151fbfc39d322d06114e1bdea3ff387a7f -c374b24fae716658bb1df052bef144231846850a7765a1d8c80676d2323c1327 -c37ca4b35a0a86700525ee360f0332f115495bc2133f9f6d69953ea5c058f48b -c3978d348ddfe6a5c3082582c36f37ed1ab4e80294e9beaf2d08acd2d1acc6c3 -c3a6c340779ee11cd9ae1c278c6095b70cb25d6b43bba3bb0b405802190f98a1 -c3a9035d9a0039af790d9c66998c5cc14bfdec64bc5ed959f7b07b3aaac89c24 -c3b0f2f68c7fa7bed8a2583dd2676288f69ba41a348739ff9a08869362d14ed1 -c3b32c52fc802de0cdfcfb906ffe5ab10ec6e92d42aa9a213166730e070067ee -c3b984b36b56567ae98bd4c3f5e54c5a8b66f85f18e8d3b36a39aad791d77f65 -c3c05bf7c12a550a036b116b18c5a71bc846f7d602d3611f9225615bf46c7781 -c3d11542008cf261d609df1ab4b1a706893b79e5dc23462c7a75eb2c28d7ae7a -c3d72708446cd478bb067f459fa8a3f740b1b1c6d92101e5886e3566b9c000b2 -c3d9c13ec7a9732656a0d39616b2ba043daf1fa4dfbbb73ed154e142163697a2 -c3e6e466e14def4e731c1b51f9eeefc645f4b1e44871981e85677ed573afb413 -c3ea980ba8eacbfba3d5707ee476cab6fd925f8bbe4c288923be35f6eeebf26c -c3f74825c6c0b764c4ee75cebe30f2370c039be90cd87d2243d7327a7cec957e -c3fa496ab94954be418434a86188cc80eebb60ecbb8ae2898b56cd228d495582 -c3fbc3ce474573fed2ed2dcd4e2ed37ccb9eda475763aabf3cf809dcd8efd70e -c3ff22fcdd9307e1b087e4318cfd2be263d6c4d6df821a06174f4034dba7e305 -c402d36f0f3a2dd2965569a5f8cb99e82020d8be2b00e70e16e08042e4138c8b -c40d19e0366d54b8a9824ef7f3777085373f4e3dc5b5ae52350f41c66c6e49f7 -c40f740b719e468deba8c6406c418a13eecdfb1fafda0e4e011540ffadf9ab2c -c41a4df42bb26ccd3c9a663d8decb567bd90e5786ed782f15018a824acf8fc4a -c429eb77ace20f1f565479d1e08ca404680ba9e4db9f665d87b40e4bd3cfeaeb -c433713b70b9dc30c012147b1077614e4c3a3d8ece0e00915479b67ed2d8cf85 -c43623d586e809ccd5d4d12e00b5d08a4123fab69dac5533861e9f549f5c18fb -c437bfc38145d0a366c823d4fe77c07fb91d7c6d11dd06f903da665175236875 -c438d51edb4de50a119b2f37fc15831e7fd2d007efe025779fbbf8ae0c28ab8e -c4562edd4681ba099d8b73676634e2c491364b3ed2d8dd2c5fe2ece805df0fc6 -c4586d1b375eaf4a9cacc65f95c30dfff51dea770a4cf7bafa34636d8b6c0c84 -c45aee7cfe2324fe1a1271bce8b5c2eda77d8fdfbc7e61a6e701963e171fe0e8 -c465d814b032fc7d40dd129e19b528463c0697f95b1c5c3fd8616a96160cb7ed -c47365586c8234698a60ca0cdf83ca29b63d89877bd97e9e8f4ff4b3c812e8d0 -c47f3f671fc7889d02f8f49d3ad941606dc3746ae3a19084762288c15522d7e3 -c48a9e67442a69453a997e4571dc2d211f7092b0661d5abad588fd57324bb530 -c497af0d0a42e08217086c209f2e979792cff48be5c80c640ffac321b785e787 -c49aae504f9dd5f55ee8c5e2fa4b9ad43144754bb6f3308f1e445d840a4cf3e4 -c49b7b56b14060370705440445cfcd2f13c1c158d12980d952940ac3f6818b93 -c4a22f56aeb8ab50d70dd2941fea6823e9244e983c75f6fbd9c46b44261453b6 -c4a26d8303595c82181a569f5087b7e4beb10cdfa0030e84639e2fc0a59e39f4 -c4a691bba1b984578190216746bfa628afe9dee26f2de5b8cbf54d5d67d66e98 -c4b16a1f3b3faddedaa70b5e17a95789e9b0eb06f46571fd2a4cbc4914fa9939 -c4b6487114dac6e504d82d0cd600c0668545b9daacf7215a287eac5c46101a5b -c4bf915f6644bd5321226c299855ad1a422cdea8df11bbe629e5abfbbe4441da -c4c2ad991c146712b52584e11da011f4913f6d3c807ed1ca326b782be91d3249 -c4c3a25397ccf86d1ea4fbcbfaa763268c15f47a5eb77695ebd4d098ad978a27 -c4d6bf55353da3e12f9c15a78d7c7aa5571501c0f3466199fd26fb1c7f6be8d4 -c4dfb01dd24fdd0e3df355a333a0ed7da85f58752dce424ed408ee37f611a42a -c4e502cb637ff36010ae5f517bb4309c8c6abdcf7e717ee821086de00aeffa74 -c5106422bc4e2a7fd1ca029986a97774c447a86b422879115deb12f7abdcb61a -c5148d5ab99556c2b90156f66b957e8a140e89fe8684b99bb3c0a46f6c13edc4 -c51777d6fb11cc7461b52460a931a71716af3e5708bab9d23e6e5dd1d2dba232 -c517e2379955ed7d1887278f04ef13873c59f936025668d92bed6dccd3467201 -c51f7db1b479a7f0181495fd189f04d15fd4ecce5d1c8a3bb1d74f823f8e03f4 -c52b22509a5e2c83962a9d0de0b401ddf3a8c72df99bedf1e27d0083b8b0e4c9 -c52b65ff30c32ac1dcf4a3ee41549e2b91d3044f0ea13429c7e6759884083875 -c530bc503d51e5e9d1b2f40612f80fa629a9903aaba860e93844a0dc4458aa82 -c531a8bda5a3fd14b51f19a931b47178d4ea5f0d2c5eb71c5761761a488fd7d2 -c535d8dc45639c542ba01c455674522149611d8690c04cf0cd6e12a0ef11d5e5 -c535df030feb4c981a82f8710ed915da2f7bc0c41e732ac87e242f3c2a270e2a -c53c2d4a6054d2aca49197e15779a3064e448a35a9dc0908a6558198371c370d -c56402b0b41e50397ac6f3b757006dada64b79017d1da27ff45b374eae88cd7a -c5670f269c0bd6589169a54d0101202973799628f2c27c2328cb476881906a69 -c56b3777b3173d2bf166cf9864ca0e2692beeaa8b3f61d07277198b1d2d96310 -c56f022623c6010232758cbd4124e226e1717d734cc7e152f741e133fc1acd3d -c57f3a07365df8dbc448310b0c074e37e2d8c679ff1ab610a7b9df91d9a8ecfb -c580833c78712ad1c18ef12072d0af77c2b87935d238df49ae1d34be4dcb004a -c58338237caabb11691fafbdf23b2f078b4081f1e8591caeae0e2d688cadf6ff -c589cfbc023e7f7a56f6531146405d2c127564c28b3c18b933cdd8b509e9110b -c5a39aa254678fb9c9da125f1977ef5607310e983917d1bad3f266617980203b -c5a5ce51da9bb849c6148ba2eef280db83c96a31c2cf29ecfca79bf2af83a6d6 -c5a60b02de2b6578184aab58b70434b6cf66d84ef4fecbc98efbd8e6d7add71f -c5a7e507d2b9a50f2f755c57ad321bf8dce38cc993d7d7ca74f9717d29d363c7 -c5ad064c78475d141ee68f1dc4b276fb85daa63d3c3e9d2c3f92f533392ccd78 -c5af22231d9256216091214cd8a5f55cd3441fb86f9f7b044f0d8e06dcae7340 -c5b1423b7e189e94b28267abb9b5fa8ce6fac0949733e26d978d1c09a5b8b759 -c5c5e3bdd978d76f041bbc2f4a45cfca830e514b79aef190aaede0d4af85cd99 -c5cccc6dccf73e102426e3afc73e823667d42e5192e139f2b5fcf0375f9ea57f -c5d0b8eb717682293a4b00281fa5ea46813b59225278546c9b6ad2367d87c99a -c5d48f46c987b05242d9ca62412fe1aed86ffe763ef174a156be19f3ed35262b -c5d78ecadf3a154927c9f1186c21c2fe97974c23075a3a7f8b42b1f79bad725e -c5db6bb53fde1a0efc684316cc1470182972676874b4b954c591b3bee662481d -c5e18daaa71a8e49771b25f324973192dce0250876dba2914ddaf595bd7ac3c3 -c5e942ab47bf7af4469d1600304eb11d3633175323ed45338fc8761c11907c8c -c5ece4f585d337089ec5e579d979a968afd09f957e6b16285120045d785ba342 -c5f671960b105218514796ce45224fc6ce2da5a280faa54dd7ddf15a5beb07b9 -c601dcee185347f39ef66d338e08c702fc5ba6007c25368a6b54f66890790da3 -c608c1f531773a5acfcd44d728d352648c6f4743c0c1d2318f07ef22605de6a3 -c614ab0711ec4984d2ef6a7f945e066c2efe27678e37f555231737a7a40abaf7 -c61cd8f3369bd089749f1aa9a2598e661ee40e574f46032821da432c3a3409fb -c6213c0dbd9daad5a3d138b4f3c3b0c5947c557e649ae5b8c9fe08d7483b430a -c628c7bace1e4f2d1e68324f44709224c953b8b05f30d50fa28991a0eaf441a9 -c62943cd1e13c48e11341c161a248b62e2bca3988808e9c739ebb8115e553938 -c63d29eb6c05dd42ac58672d777f1269b3b842996b2eba604c075f2a15f0a288 -c6517722b3d2c7526a6508155475e4ef48dde0cd2e10e412e201e7f1022cdeaf -c6554ab0fafcc9933510c4688ace29ddc0dd5ba3ae3f51f36f9f058353d47ef9 -c663e6f197f940bc54719563b7b3aaf57cdb115a17a1e9de30e883a937cf7af1 -c66e247a286c0cd926381e02ab0dad2824944bc988fd165d27f669aaea4e1b87 -c672400a44aee864bb58064774c20b4bd962ca4d7b6d4d8e76dcffe573717db4 -c673d13c80285cc981fab0a9e3f753ceb14cbb9206d3621e10bfc92acd641ca3 -c6744a13ba88110b3212f57d162de2f6542b52ecc9aed13a5571fc35eb92fa10 -c677927782963fc060cf010d4f520fd0792ce603afd6a4199ebbe5ac575925cc -c687026d341a11501e2427870bbff4bbdd05c4d69ea468f7705ef6195dbfa639 -c689c3a53a05303d848d21e14576b9f34af79400fac1d36f9dc228fc113f0c0d -c693a29f356c221b709f9934f43d6da96b30e5260856f929d5f2e4630904b2d6 -c693ec3bd752b1b30fb75065eb458a3473720f2f4a21b2ec8a5acf116b8ec5b6 -c694ac4948600365b1f7f3fd6840568670c92b223b291880f457adddec5a4764 -c697ccb68686526c43779cf3d2063fcc76ffd76cd6831108d4d1a3251c69522b -c69857689f7cd9dfd12e66acf99243061d57e85f75d5d6c5656cd1adb2f92b1c -c6988d7342d397be313cb38518266ce1905bb57b0b5b27ff9b5ced896c9d3bd0 -c6a02d28c91ed71cc363f387ba6d95e8b8f9d5311e66bbd2d52090786d93a539 -c6a38ce24a9d1a801495a9449572431038d23a43c8de7320c6d2b280e5153d0e -c6aff63ad969b5d30075989e8edac515d2003af204948363bc63046c2115e24c -c6befdb85429747dc73ab0012a41799a0c0d7ec90206878b28d6c980e1597681 -c6c6657d79e549e55055cbb6ff3b0d18f65754526f7ff0409ba867db5f0bd0f1 -c6c8adb52f67d6bd3dbfa24891dbbb3d529decf93da2b0530573f9cbc9456cf3 -c6d0346aeb444e89e6ad0471ffded541ac5d6ce74d6d61ec945c79b99d31cc3c -c6db826613f1a643274cbc3a616e6bba78b3d2ccb93d7206b3fa135d95dc4a51 -c6e0e23d1e5a28aa78fc42b9f5818ef0e3f955641f321b6000d007532f7d4366 -c6eb9083b04e8ba058d2c51c9345592d364f47bf6640413d0e698e976c0acb3a -c6f32bb8811ad9530a1f8019482361e3ed399f49f77107c5a76420284cf7b734 -c6f480854def9357960dc6a3fb1223b73ad07e6a71627841ad71e4ada4713baf -c6f50ba7485fd50cf39bb0dc6146a72dd8bf546d89f0c571e68b4599e880a6a5 -c6fb800edb43fc29887604ebad38d4776099dbcbf0c91833e0cca4f3e2dcc3f7 -c6fe5944e2f6709c2bcd0e16658b2963fd1aa56dd40485439daaebc2f401a2f6 -c7001071aebbbe2c959aa991638e74e88ad586e5f2ad1067bd0d4a5b333a3b5d -c7019e71f62e560adad72051b1656ffb65067795506f7727e25876dd0b51f92d -c70d0354a6a19a417fae9f98e52d2a3fcffc32e73f7eec01b215857c8b69312b -c71345c6b7170620556ab227767a47234bb09ad74dade4ffd15458f39531925a -c719eaf14d93445ef16c959e7b130aad1842d0001f0d21f8c757b32bb09786b6 -c71c26e8981c0f65df9e2d093ad85566c08361198ce6b5006490e9ab93c2b1bb -c723c716a2a68d701b16c30683241508d43fbefe09be77d1c09edd1abac1855a -c725657ace22015787ec8673e2444640306f90ba87ef455172837c0a8efec601 -c726969bd74f11a8fd7cc93d35ecba3a27f5b38d2e52abe582817a071d3d0463 -c7377f6f9cbff4fa0efb9b73d1989906e4884b91fec4fda8500727c019d83cb3 -c748fa32a28ac1be1d6248b56568223b7e90002785d52c2defdd1b5c3619ac66 -c75e9cfa4667a25752cfbdd8cb4b650e9aa38651186b48d8eb61ba144eeaf845 -c777a35cd8a207b56c4b98104ec2784eab76b1db17149664e4253977670f1f3e -c77bf538c0b6c3ac3641b174a422e3c063e138688089c3ed807a8693b217cf74 -c784123e6783f7a8020d88185e6f7332fa85be2fab96b2312bcb58702eccb149 -c786d575950b0c9f06cb016faebb5d1b1bcd1f1da21f1bb57aff1a16e62447e7 -c789229335724ae47ab3c18860abd5fe1dfeb72061c3689527c96415e109f946 -c79715015c5aa89a1c0cf84887ba5b978584c63053b05bd98d6102c876f3759b -c7998e35204fe6c816fe4d5ef25c12c4bca1931d5a977e6ed51284105878fb2a -c79dccd698ed707e84c5a2f10715404d8938195591effd2c1acc4e731a68f789 -c7a877330ff3ada05103ee3af1cb4091969e6478cd9567ea1d2632c756850b01 -c7aa55d116e2687abd67b13bcd40076fcd5784840fc8dd282be4b9aa636cb63d -c7ab7f6df04d845196d7e36d46eccf8dc4e63f8881d89dbee9baa7a4b048b265 -c7b015d63f71d0802c585fe665b244bf1e5b41e97124a61fcc083bf9573a51d6 -c7b1b270743e3c6529ccae40ad4431f59a6b1bde4355774906bf6f054d45ebb9 -c7c2face5c077c9889964ffe96d05336bcc878ac96be1b2c5efa6e3cb245e2c2 -c7c74026851ae75cd99323da2bfcf918147f5b10f414789528d2a2e4c467ca00 -c7c80018fc084d2246ae3d088051ec1b674ef01eca0d7db884a9dc3540c5887c -c7cdc931fd688b50a79f09d91649e7a5fcc3dcb7d82d3b66395b1e522fb8d2a8 -c7d9752106e05861b3a2a99fd6b2a7a862028df215502d5d58ab55284071d01f -c7dd2182f1ef745df90de23c0386cd3476ac98c48674dd7a82e472170f4a1f00 -c7ea5c0bb44a1b7e3520b52af271fd8692274e30824c8bd279baf339f3651a8c -c7ed1d56e9ca1f549ab41aecda012a2f4a46eefdae904f12b87066d975045211 -c7f3312fac17621bb7423bf40934d380a1f158da9136609ec7ffd4537061b01e -c7f794025146651e6bb704cfdeb4bdae812f26c0c32a04e209fbcdee06b4239c -c7f8f7f1714bb0beae67bd69ce14fc8340f7f51bea7139117e2c8494cea711d2 -c7fb05a25fc913a608060e5dde2bcf0c06d3f3fe1e6291e1020c5e0b34c5ddaf -c7fe69bd3b13dd61248ef9e998381230d7c5508b6a38ccb1f8086d37a2aff48b -c8173bf30a4f98ca9757182ccaaf796380d81a97f97e0a3b0e7fd80c1180bc57 -c81cfbfd1cd1b69358f51609d1591b47bc181ab0e0929e9d8899574b888fd7c1 -c81e3eb2bf8edef1dc62c54f22039be333498dec6aacb8af71016b6104699050 -c82d4ad21615c351c228230c9c111191d21fb4245fb22efdb0a2725ca82a8765 -c8403ed0f7dda91fbf866093aacc975e4116f965b2693e3cb9a86bf3bf4fdbf2 -c845ce2e67d58d944b68ae313fdf47773cc60776fa4183aa3477633e11e2252d -c85d5baf711709e6f50385713814537c95eea5d57ff24a41a250289f78138358 -c85e2d5f190633ee5c1a6ce992589a631aa7961a195030e86c3c242165e38b9f -c8678d3343942915756abd34f11c6e2db3341c1faf3ddd25093a362ae2ee125b -c86a58e5cd03635f2c0fad92abce57ef5b988b3fc9e97fb797c7498f676c9037 -c86deaecd8ce50dbb2a8f4a9173300ff49bc264ba0bcd5bff4584e725ab92d5c -c8701dc8405ebf961a186ddb27a09dc6f13ae63f132d21d595e5d27366a74fd8 -c8775af2efa54481aa54867d955a5e33c14ad7c2ba29e3f0b9a036c629f9b7c5 -c882d9abab581edc1f7a79deb087343386fd1ecd7df812b805e83962fb1a89ed -c88c9f31171c73a4cd20e5c365823e51ebd4eec481cc6a34a8ff929cdc818fa5 -c8913be4e8997e1fb488ffee4e2b39933bb12545197b4609ac9969869626e52d -c893434203b27143875c120c4774eb3ab154ce5086cbb18e2f784fed5686b122 -c89e4c04e8a00acdd06e7746249569299f400b6aef42a702e31b7a2064b049fe -c8a5dbae4014df207ee8f957da98cf632bf5e9f48350f42238ea82689d382687 -c8ad605444e89abc492d1bc9f4ae426e4d9ed9542262801c88366a355a918a36 -c8b627d0f76fadd5e55c723d644e760c80b33488fd5f74afd6d121b48785454c -c8c324a517c7383024e7e3e814b7481664aa85a06d3822fafa758dba13993c9c -c8c81f859a135087451e52e8b5285e14640c57640a3b082f07573114ac7abf94 -c8cbfbf4121e79caa98d83a76a97050e900815218778d52b2bf2e35bd61463fd -c8d3039611136d3ee6ce1faf5e35eb0bb5147de8cf369f539311c181cdab4954 -c8eb42cdb909e5c0db6bb84af48e4399d2a07530566d6b28c523cc11a5425311 -c8ec535f10050434bcca85de676c47152cae7b494759cd1f3cefac2b9c5c3ad8 -c9027b9660c741f80a3718e7fb5b446ea25bdd22e2b49432eca92ba8f5958c6a -c90df76b21cad3ed49724801d3d8d6eba574934e7f5cf9910c5ade8ee25aca5d -c91927d8caacc849b454587fbc0607e2d19d849be145e6f364674adf15fed01d -c925c4dd3700d366e50cd0bb724cb341d581f5e8ea0534180156a7356c64afca -c92819a4f9e58335027afaf8bc35dcbed88c3c2aa4e71185b7a163f22f076a17 -c93c849c5f4bf810150368e8c038726b589ea7bf1f718dac7f1641e9dada917b -c93d34e05ab3383ee31e388dacb6b934ce5da45fabdbc9ddc31483304f7304ef -c9462664997b60a79d2fb866fe67bdf7c8e0b22c2597e58e390aaf6b1431b68c -c960f37b2e42c98b0ab787b0fcbcec6f46a252f6f061d42d69de2bb5e5c813fd -c96609f63123fa08d6247d3c1baf7b983e4a8317282f1f12416be006408f1096 -c98fe794c0921d39e28711d177f3a632cab6eabafe25f51eb48da4da26664b6a -c9925a2393a617cb496dac8557b2b3b18beb116ac2b3a8239004596482ecb441 -c99ccc6e7fdd6c2053a8f71371abcbf59b5b09e3e0a9ba9e1c675c6020b1de9a -c99f46d66cd4f63a8754c32c9661f0282643d5ceee8e156503b89392266136bf -c9b10c21779e12190f0c150de7aaa4fe8758a3013599e56d6b7ec8db17d97a00 -c9cabcc9b78fc04196720c737c826478218af50a6dbdfc877ab994c28380b12b -c9ceeb4977df35f40dd6cf27b60e8ddd75eb8344e2f5b088beb37890af361208 -c9d040bf4d263ba0168a53cdb8e49e8507e503592b7d8a65bcd870172dd3cc18 -c9e1175412f46f13c43352b4709e3db756281456c951bdc6793c5761ee832ce5 -c9e4079b4d3f3991e223e01cf79f77aa4a83bc6f43bdfc480efb952ee41b8aff -c9e76dbcd7f5cafe2fc0ee8db293a7c5254d2cbe56d4fe7cff8ac331492fa1aa -c9f425a59ded0fe16820c6688dddb2da14e3bed6d22fa7a060950baa4f748ccc -c9f758ee95dcd6a870794c04189ef5812114c5419ff238621c0e1dbe6719eeda -c9f7bc4c511de073dbc1050d9315faafe3bd27272955b7b1247f6f5662a1e227 -ca0c4d16bc95d6625e91dffe6a9b5d3c7b0aedf8774bd593bd793c4d656aac6d -ca12ee4b68f1c2929ac96143fe3012e2fd9d66306a2ef66c870fbd030238e437 -ca26db57baa233f7f26502ac132afdfe809928e433394518189c18c4db25a088 -ca296cd60521ee467f260f1f608316a4776a51dc525473ecb226e9d1f51546fa -ca352ea77b19eb477109875b3a94067a61b8a62785e70a5e943af206c4a76600 -ca3e1fe7f255e58703911ab60569d327f1eb177943093ae2be0be335755e8274 -ca446154688011ba24fb3b659f56e386f21ef4391469ecc3343f06541c6b9482 -ca50248e61d8ae0cd78d031052ea9d3cb4f4f8eb1f82a4e98624e63837f4e09e -ca52f3cebb7471e634c946e82009970a6e6f302f74b3d4c600238ba96f8cc73b -ca540da66d833782d083731fd5088c11930cbf2946af3c0921cd4c6608ad22da -ca557d7c7df7137a65ec12134e595cd1e9c43d8901516e4ddaf620d69900d91d -ca629ee783d9aaa6b6d3223fec5d875a5d8887cc7004bda298711011288e014a -ca64313be19481ba3092772ce4de5543362e20c363d772211d5d433a5b5c2ceb -ca6d3837771fe06dd208e925b648609e1f266313e1bb6d5cfdbb859992a1787d -ca70633db93d365e5932a96a6e293910cd069ddacf67388796e8eb1579cca6db -ca7108309884c11e370d457d690a48e35cc8608f5ad98bdb8a36cdd134368ec4 -ca72cb3cfa949fe52e54f8fbe319ecbef2cce6ca8a9ed07733ab1a562c2eee20 -ca7a592939f10233b3fe9e84a7f60f47a8804fd3b916d8c820e3ee0bc75f2ce7 -ca7aab4d0dbfdfeea4e03d69e5ed30590d548ff5e023dc67d81cac963f436fff -ca8054d8afbac4cc560e8496dbc7fd27d1d6562b00843a6eb717dbb9369db6be -ca8d1af0b808046c10e0587f6eb4c583016b5f03a145e34da11798632913885e -ca97fdf6f1d0deea6e72694f40262b3399aeab93ed8c59fc205d5b8db3d48684 -cab32c3a6f5d5e7e7ff6142609745f7bd7f9e5751a42157ded013151482c6473 -cab3ba1bbecbb8adc50e235e9ca005bfd49306fd84aec7d7d28c25b1dc87ffff -cab8a6b9bd3751b48691b290382bdf3b854265a3731650cfb7b9eed7d5e83130 -cabe210c69f369ed1dd66d5f6424fb704fcb0fd15b9afeb60659d70344f7a9e4 -cac25ac356c53f95de0343ebeeb11d7d0a5a49a6ecd03d71be7ac7b82d3896fd -cacd862469f322c4c829534e05ce15e5bcb0a99025366c3189af49e41a2ad947 -cacfdf52304337cd5ded84d09ae98c3e2fc3fa4dfbe67e7e9a95d1fc35a89dde -cad8d48ae8c7a72dbf6cf15721b593711de8dc464412f64b240ae6dc704f84be -cad8f4a6b20afb0059bf7af5dd30f65e38640f87022e849f892bb82ef2c4c722 -cadb7d18ce6816b0d435e781f9444ecb4631b64a0cdfa2f169852a5f9da81a89 -cadef24c47a16973529d632717e75e4af58cea8277e8da82b9c269e68893dbfb -cae0b90bf3b3d3708635edea51112830b2b18fbffcfce32121b808fed4253301 -caede997bf0c1db03b3271680a01628a11b2cece33f017f1971dd09805159f89 -cafe2d27bb352bc7ca1f4de544e207a141b939de2c85237dd59d06de689427a4 -cb0b0d9da61976d4e1a4ed57e0b52e140a741c4ca0b53ec227d4481886ddfbae -cb0f1bca151545a639d63856c1aa9b7d0a6eee8a1c015639dd37a2809321fcd6 -cb100b7dc02eab8ef8a6bb85ff9e52e64ed5224ba498bbdc71c9312c139113b4 -cb1d41c94d5344ca09cd8171e824368e895f956bc16ff3140f009253244e5887 -cb1d8d8d8ee8793bbe2459c1d2af86664d55bf14dc87fdef714a3cfa645addd5 -cb2a1cc5087d7268b948b8b39910d76efd655fe49bc3764a533d7b9e162200ad -cb2d298d502fe1026ca5e065600f08a993c92997ac45eb6d2d4c0ecd2cd7dcd3 -cb46581379f744badedec79c06a66baeba296990146882925cecbdf0910b367e -cb4931484d4077f7ce71447d10bac23e6bd9a5b69db5a08ccddac782dcdc20ab -cb4df9f46cebbb2046c3e9572d0773b91dc4a801c693d9501a31607eb865d161 -cb53015ed327a825970a03838fffa9a2dee360bafd0c46a3986e2d2ae82e481f -cb76a2db7e5295bbe60ed4a90baa8b4fc634eea29bd6f5e3548a1bf4c9894641 -cb81d4c1813f0af175a04db4a3def0cb5e81aa9c137b85d9242e70fb60fcfb53 -cb830bb73e8d8273e26177c1fafb8ae9bde1614ff17716bfc3c33e84e66878e3 -cb859ed9cc89750a0cdf974d50ed81a49168d65f9248dfd072dd800440ee8934 -cb8e26ed04954106989280d1d9c39aaf4f94f0048099e8cec9f337a7bf1a8dfd -cb927fd23d9c9c61c224ac025c5c1da644f118be5e34fa2587569879c09d785f -cbae248809b16fbbc1e418930d6e2e09a6e8330700fe7759b086418ed63a17c7 -cbaff4afa798934e35e85021b947c67c75460bced347be68d79359b46cc89233 -cbb4cccc7a811cee35c8f61517aac3f7957a8e3a41081f1c49cffa2f44d890cc -cbbcb6055dd6ff42fc92a8cd5f8753c4105b069d2c0ff989e2691720bdc6cde5 -cbbe94e5b909562176ae91539929ba0dafb00a222b325cbbda020b3379693d71 -cbbea8840a87478db72e6243767f9065f42316ca5f7ef9d6641f104ee9e32713 -cbd7931b762a56523d3ab44708df5341b93b31142fcd9d81a47df309f7f4df42 -cbf70f6aeaf0a61eb0f6b5a759ca08853b7f8cb7fc1939906fffeaffbfbe6b2e -cbf7e68b9f0579f868a045967ddf80457b87289d14a2009beb6cd62efade0701 -cbf840de606ec45738f270a9cc385611ccaf5edd3d20edbdf4b9a32f082141c3 -cbf9247608bb606454f6022c389e6ec61c7733ad36109c864bba92d910c7eca3 -cc05a49a15b7ad604973cfc667b7a3acf63555a8be2805f8e703ba8f5cbb1f5c -cc160193365769054c57c829e7cdc2e81bd0f7f03e96510264211b8f43724278 -cc22a913bfdf38fd423e42fa2be608fdee71f58b5fc7802768d1ee3c1f904939 -cc3216ceccaaa47b2f849e72e2cfa2b212202215abf8072b68ef3ec280c56d23 -cc42727b2c9c636888f924a151483380a44f72e585bf889cb39ebad634598437 -cc43b59d99a5ec46c6dbe72667d6867f9042b9b3aaf884c402ee5166616abb26 -cc455ee35a787e8b1e6a53df612a1368c5bbc13a61e9758cbf926a56fdeb94be -cc515faaeeab6f0fe6d55bd824efa9d016b9fcc69b4789d6709fdfd829841363 -cc55386e287515b96f09bf7496a785cf5d92b6f5f5fe553530b53dffb445a8f8 -cc5bbc6d0da8d83f597a77ca95fcdd4e93c17d1d00f903a097dbede7903f6d6f -cc5ef4f0705d4f00cd4a767758bc7f2bd89516945cee3f7df893e57f65f3d320 -cc626b15a145f9c5d37e2798683875e858fe4968f876d98e47528766418ba31b -cc6cb4391938ad38c8230fd1f83f62bbf5bd3a4c14b0f13571e03df9d3e98705 -cc78d4910e19d47664665443e2bd8854204f6ccd51639c02a3985b99a9cad611 -cc7b802288751c828dd0f77d0dac6d481671658dbc53ba62bcaaf9be9c6b241b -cc8364b194d22ab82ee342831a9cd37975b4b70a7e12758c576aef3706066000 -cc8662ea7b055c552b093a24dcea0c90fe72d27f1f4e6b82481a9b4c13dd5af0 -cc918893a9125c33b32891b436778544cddbda010885064782934ebf65a87263 -cc98b6ca89732e70b065a8f9b6cbfcc72973b233d9402c0181a30985752ed2e3 -cca747f521c9bfa023a58dff8c16738d9758aab98da5cf2e571f3874dbacf4cc -ccb51b0bfe1d09dc21f123fbc5b5ee599b444e89f48961c6d068472464c9d96c -ccdb9b53933481eb99ec13750c478ee65f7164a83115aff2b8060fb12800b216 -ccdd81b9cbbbb7598c64797d0b2c5c103e6114e5ba545fe6934d1488bef2f134 -cce3ca21b84eaf23bf0f2bb9aaeddbdb281251cb50acc062ba5bbf25a771e5dd -ccf7ddfa87bc2a8ad774e9e7d64011275f5a3c8be7855ed11baa522925a36224 -ccf9a42d3359d1a890381e562c6693e61e1a6baac68c131d172f551d9e98e206 -cd026cab0e012c9871a45279f369abd2d9dc185a98350c6972cce37911b6ed55 -cd029fff26883c46ebc9ae5cffbd53b420d1617458b8f6f62475be908625fedc -cd16f04c66f12329981ebc3e0e3565e9602f7797072b6b50d75522e5b8270a9d -cd2f47ca41a81ace5dc5be9ff08f042a5a6c653138be311fdb7bd34c5b7cd502 -cd37f0c22e62d1e7ec4d1748596ed6186c54b6de4efeb54777506cc75690db83 -cd392b0830ff29a09bc1f3c376bbd206895d0bf09ff534b679a2b3451daa08e0 -cd459756149b5030c79a66abc9c8631bb45617ab206543e17188ece3fc4298fb -cd4ad31e2e7bf7200e17d34f26b22b8d7bae8b4950931230e3da11e6e1b82c16 -cd4e6307bdac44bc7a6ba437f1b73bd97d97ca709499fbd9db6f889324263557 -cd54b9ac483fb05ce385bff39720ddb72f202d1f3e4264c7218510f41f9f61ef -cd68460544d5ae1e34bd6a9c3b12166ae42785c7714e829eba6af3c9d38d4b25 -cd6b481ddb831a18edf84a2e01696afa232681775c244301e890ba3a1dd042d4 -cd6c119a44a647652172b2d1f0bcb2ae1f280643af94fd0ddbd63f31f50a1fb6 -cd773590d44a2db9a6d86a91267966baedc6172fab992ee57b58d92dc186e644 -cd7e81a0f82a0d2eca840d09b7c816d2d94d25cfca2a3fafd4726b8c20e223b0 -cd8654da61224cf599fcbfa5bb8d08d9601ee7c96152b3f5a25baed004b5b24a -cd893819d51bab550f4e7cce44ed2872f6f7a62fcd7685dec9212e3b308e1689 -cd99f8588818325169334316e42160de0190c3d48aeb1db3edb9dae6a5d8fbc4 -cd9d011c7e7a249a634907be24f3973a1f2bcc8b0772cb5090c899a20608ac64 -cd9dbd569f759aaae24acf6eee5207fc8b4deda2eb0bd263811fa85f97b9e31c -cdb4d85b9b53363d73406a943028984f6cb879af88871149c5eedebfaa8ff945 -cdb6afaf6131633aa1be8c1100b802e01934add113e955531a71ea853bea75db -cdc2acf281d74cc61e84a59c59ac067aeff33e4aeb5773bb35a923e9a01ecb79 -cdc4cc68e676db421207971e341e6ac53a175860fc020bfa433f6c04f4c9d9e3 -cdcf1f6e89ab1eb71bfba29eee6e650b675b8268f1316b6c3d6d36c3044e154e -cdd2d825910d2ebaeeeb79b06ee80971dfb03e71ba5643e6ad6014de491fd645 -cdd816c2d9ccd4ff94301f363032d59f0c6fdd38969a3e90216a82e9aa14ced0 -cde27db34ce3f7f2203519fcb8d82d88645afbda2ac2035498e32d044528981b -cde6e12435d51f18d713c2262b848649abd9995fd004a2999ff91da043b91455 -cdee593568477f13230193caf838cfa187b037ae7b1fcdaea9525a9592c3a0dd -cdf5cdd72dd4dacdb4bea2fe98276eea8d972a216667b967931227da9a6ee620 -cdfd5d53d9cd4684a7fae8ee2b0e1a5e88eddb813dbf1bd6f3301baee2788fa2 -ce00216c6e9afb7febf26c69b441f237338f5379bf29b089a54608cfdf63dd5f -ce1fe14f7d954dad829cb243be1727356d047a8dfea47c230a673c0a4be1ae38 -ce2fcaaf2138bbac507e990e7b96ffb906999f665591ccb8a7490645ecfd6d12 -ce3f67bf2a3ab12c48c40091606c0ddc4c29c5aefef4ff7298573d485d314c97 -ce4f47eb63ed791883f97f9336bf274d53ebedeb21f646cdd516d29bb4d78966 -ce5c4f9e2355bad50ff4bf8f3e48b27567cf286ca47fb8e53bb3e6e3a5ee9df0 -ce5fa1b63d82f4697c0cd85280d526faf2a3dde12a3ae86979f6904258d68cf5 -ce643f977c75d033d709a822cc60bf91b3c8c71fcda78b63423c512c1f34b074 -ce68fa1c5d2d63c8071eeef19e505bdc6bbd89e19963d132e6d322f1b3a02692 -ce777fb091453887ce79cbc72da4681d23ff68c35a2a79bf12ba58641681501e -ce7f8732c48f1664d0a5e9f840e3abea575f088cb6b463c8882111b64c3c944e -ce84031bae32726c3c8707052ef5c0d5513be6e33013a1d3e7582d0c51aac77a -ce846949335e885dd180abe4fa32ccea837215395ce66bb78503ea0b51855b46 -ce9a530898bc9d88a0a6efd8d2ff83b4ef0adf68c28edcf11c8fce520141c0c6 -ce9a8da38a304479a77b3d18d6f0cc6accf5b4404916537ef4ab37fe04d3649a -ceb7dc754edb50d6f03e1a3dd491cd770fdba6704881743f1d29923349765b2f -cecd20828487500e495bc82cd43b7b4222e81424d9712b6351a0f41b3243e689 -ced6dce15c090ce16a673f46c0d5a2fd36fc28da8530ff0ae3f3de0a1d8598b8 -ceea8d0d58cf8d24c1eaf8fefdf5bf75fad9020be5576c73f4e9e71de6995cbc -ceeb246bef57838bcaab217e773e0a1f65fbe73ff7e654a10341e4229bda7e12 -cef3686bbf96841ba9dc34f3c0b786ba3613f4de57e4274d4b78710d6aff1ee7 -cf0d0226513705de05fd08cae3c6d29d7bdfc473291c68e7ed73ab2491bf03da -cf0ed6da8ec581a093bb55401c65ae27147e6ea0aa7e35da268e31c09b1bfcd3 -cf1de5675fe016d5daa32c3159291df1a81beb5b0e739e7efe0dec07da679625 -cf1ee1a6405255642f45a73852632443b4c13247db76069a99c1b58049b82c8f -cf20cecf4a289203eeb75e988d43b5ca1dd1244eb30763e5eada67935b2d754b -cf2c0b1d4c45f7def3e3928daf159159d1332ee5a4b82e7d95baf2e254575517 -cf3c8c5feb57a919ae2705f01ca5790b5962ede1f7efb161847786c446c4392f -cf54a8cd68772558c67678e061a0a5447f944c7db7269785d91e2683b27cc28b -cf5b296a47e579f639e0a13dfcbfa9f8df29e8ad04b0cea01b5990e746eff9b5 -cf5b96b5520e78f758d649982fac5b53968e20e95e00bd89db6438fa85e7df80 -cf648c8ea59212f0679eb975f096931ad043dc5a8bf1c95cfff0eb83a79ece6d -cf64ab0da866516ca2d0c6a4086f6054094fccff59bcac17b98473679105defc -cf6dac7825602b1b15c8f4c9c9becf5a46cb7da2523e4e6c62f35d02e72b4a11 -cf7515b6bb258693fb2fedd81813d04fcd11cd437b75b7ced3ab4ea9ea9378b7 -cf775a6e374d97b890647f1c4b214f71dbbe9493e1b4a5104c913e905fb98fae -cf88fa481f08f9b17f144f6a169228ce913219cb0e93e67ebcfe0fcd2911be75 -cf894a9f8b3294864ddf528809ba1b0c10da26efbb07dc21e10742ddfce295d9 -cf8c7efd7ee145bc97a71b7f39fa4c1073073b4651a728dca2d9d825c246fb8d -cf8f1bf780c93cae3d6ebca97bebfbe84078dfc2db0b0375ad5fb1641b98fa13 -cf90ff32976faf7fa5d20d1689619087332c578e7187cd482f00c2e19c7ec688 -cf92313057dda268b8e0576bdb1e5d22c485c366b327f7617579e565dceb0ddb -cf9b0718a7c346b7314e12356d540fdab1bb22eeff756d95c044773e78d898c2 -cf9d5f78ae30c80e2f32913a7e8b6f02cd5387b3333d036e592d961f4ef77109 -cfa1a06146e281e7e946c9097574a21eb8d89fc145611f43fb2de61b89ea73e9 -cfb13dd2b5246b142c24124e8ba4565bbcd8bec6ae471a6091c8bdb92f3c1c5d -cfb20198d2cb93457a1907e83872a4de7917d20a3b11967d321d05cfaab2097a -cfb6002995f46b1daa13e2a23de1e29fb6dc2b2ed22707fd337f491f402a8dd2 -cfc4057c0ac95a394c39d1efa3a290d9dcc5a429feacd647f7650740d96e3c79 -cfdaf6c189a7f1a47f854e95e7a53ecc7db89c04881606c755bf2009b1a1633d -cfe0dff80c8296f55069b04254fd20988df07630fed058ca9c0c2909612e4485 -cfe0fd9a37891d570edfd43e83ef95df48823e5763c247610f870e5ddf122873 -cfe1f14fa9a9bc52d0f8aab5b7efacbd082faa8b96bcf1f6af2003dd3d7e3cec -cfe428276f57f5e3aa51f8452ae0e8aab9a71c2ea764670d0c4432fad6c249ff -cfe92b9f65121f1f6e6c1c9f8fba4782c71cf9c5926cf940b14f45ba72c99734 -cfeb08fd79b5665735aa00e7ded33e2c5c7910d4a6348908616530dd562ffd85 -cfeb73bcb2171adde6abb358c3023c918e5b31c2cf08d513dece49770542958a -cff0a6c62fd549c52a102536e44cb982a763caf88fb8ccb1dfac4ae11291c05b -cff5b6630b7fa6b637ebec917aa3ebbe1df2274a0beab5b3c444b811334342a0 -cffb494fcfcbb04cfe05302158e905caae4312816791086a3c354041c94ba026 -cffdb23137c404799d6b3832798153e9b603c6564a5aae790736d6a79db1759f -d0006cceaa504ab14ec05eb04f9ff8e302311b9a4a5ade0442a585bde26bc33f -d002265af8d09020f20185a7df9fca1ac8267da456b69e1a133cd3151a90cab0 -d0158489fcee2307ee3dd030cacf942e029b1af1a988d32a62127eb700b17546 -d01596bdd6a266fd5ac930075783865d51d9b5a1206d1089f3e393bdcada383b -d015e398232551d50c62bb1463286141aad78df17a5685ea9220b17c78520bea -d017c3115851ccaf7abef7e0dc63eecfe8c1f5a92c59061e5617d94c30e681b3 -d01d03c5d3ff43097583dbd9f3a68eb5a5239093dfab409ed9a28f1816d80ac8 -d01e401943e4ba9754bebe932507b91184ddaa4bec2b507a257f6584238645c1 -d02a87ad20d80db0529d53292c95a15a1d77358bfbfa896d304eb94cd588fe3d -d04a9ec18406c4e0fb33932aaa029ca31336d48c3ff77948326021cf1f13e13d -d04eee02c542cb059950459e8db4a1725c8693f3bee9145864e0abc2d6adba82 -d0553858de3c33f67244103cdae1ab098fd338b18728d7333c303101bfcfb778 -d05e33c2667ec943ee4fbd1fa7ba96071369de0dcaa6493b3f0fe48b754b6691 -d05e7413d55dc72bd4ba216b7b268b9b4c588ce860ec6e507eeff7a6a330a4cc -d06289c15233defb30e5c42af6cd8ee024c9ed28c2e1228b384d4c8fb3c622eb -d062f8273d891431421333d9562317146460e1cc31d71696bdc3d118352c84b4 -d071e756e3f217e29fe3b6ebf215cbf61374b7066c4c6ae9054e0fa3e1d6ccbb -d075067885266778059cfb94b270d81ae570209c274b570cada5901d84925892 -d07c5f1d1ab0302d128f97ae800842d5accc2057c871347e08c100c9bf55c1b8 -d07c736412ffb86cf58efc5592c30e967cc99ebf426a767ea6339bf2b244a16b -d07f28fbe0e72e05ae00d308bcb298cc1bf238fdd106d48b9ec97831dca2925c -d084a6173a678ceec25657ef8e54b598076a391e5aa550d2ac09417324066089 -d085b38e42e3eee875709e8546e04acd05f7788e9a16b1bc0bd4cb26fdc4a983 -d08a134ebec0dbcb0da851ff4ee1b3650c4e9d7637e05a31cd62eb41107ac7b8 -d08e05c4e4f924b62441a7ed20d15fd870009f57a92a1e00f4a0c92e973c39cd -d091eaa1c126f6e931c9eecf018ae4b878a686ac664fed85fc6c5e88b167e0ba -d093682b674317ce1d1c09c2f099e1f8eab1ca0a5d0f9b8ffcaa9ea34cb4779b -d0a54e73f28a1b26a093352b91a1f2427b0aa516b8082793e46571ee915ecfa8 -d0ab5fab5a4e1baf0f9db3d1481c324a27fbe05c06ff5d27dcef7a1f1edba70b -d0af3743974d9d6141383b4301b43133c9811b1532da81b97233dcf3c8616172 -d0b88f38344be6e53d8f1f7d9abe6679832c162ba82f4ba3557870c224c9a60a -d0ba787551b89418b9bbc2b4cf55f0bbd756e8482ed1e7eba223a35d9d1a02df -d0bdd9bed819c0012089b185031409a065bc65830e3588b6ce334606a07ae861 -d0c58dd7952d128f7041a773beae772aa521bad66072adb4d86a14e4690918e5 -d0c59e31ebdde27a2b8c88a5354edaebeb6c50f3321c8bbae6976d75f4bde7f0 -d0db030d4a3bdaf0432d226003640c1cdd0f6f216ae6671626160d5559d9c52a -d0e59e209dee2c9f41725854e377eb8587d255abf2a855d97e99ef14fc0a2f3c -d0edaf6fcbb3b23783db117492a6154441e12c5aa8f40ec0e842f5c79ad21f3e -d0edb61e4b50f88c86626eec3a6b64b9bc7456b572ef337be00b47ef0aaf3cf9 -d0efa3d1931ca2b12a360b299ca85328b27d04c68c3b7243f16c6ede0182e6e6 -d0ff9e604f33fda3634372d10fa9ec3ecc9a8c626bbcd5cd99b05cbca61a6525 -d119e42e34f31664eb805e66d0a80b482380eb02de23083195a0c909424c7057 -d11c4bb0172e64c18eb9a7453e6f8085ac363aa7fdb768a2d1b5822913d0b368 -d11edcbcf262c35d7f91470da513650a48d494aca7b98c6fd2ab7b61ed39136a -d1252d2a7b464c8d1f1b6ac2ce1212fa561e0f68f50b07441febef536be8ef38 -d12a11119e413f62187b82b517d0e578411d553ecf5d3cde8f5f0d8677e145ad -d12fe2daa6dad80176ccdea7f3fc972e63389b4c70a609cde3143737ae410a65 -d13f0db1f26f98bfd2f3ecd17fd42e3fd7b6a24f47d5d97b3a75d0ee8a257b85 -d16e683c06871abd174e6bc0676f912cbc579f227e4a1ca09d58901c7e4759a2 -d18205410b8019a3c51be8e88b2d18b735e07654a219be423530418196fa115f -d1870841e289767f53d6477410c45333c67b60e45dd3446080e6203f69339eca -d189a2aa2eaa1dd22a5f3791744836eca188e5ce2a710778499b883739e0211b -d1a0e753b9a969a1347241783c57c31e979b4534c687b82ef164cc68a9154083 -d1a65831aa30b4437d7416bb395212a81cc4c109810d31a6a8e142d33eb1bdb2 -d1a74acff8c3113d3724e49d7b317b3e93f32e2f75ca388f0b13b8d0501d4ad6 -d1ab55841bd5cf20a824dd852e1e4e2fd00e8522125fd424852eb3bb03f366d3 -d1ba08c9a70d22cca125567e29c929af7ab7af5958adf2b9ad63fc59b9aa18d2 -d1c14b15a7faa51ab1dbbe08afece0bc929fbac483783bf06d8ae05ace3293ee -d1c64933016ee2ee734b175b4f0eb2aee20607fed4141fc381fbab93bdac13ec -d1c6ecc1cfbc23fc7f328dd1216bf2d925173aec6a63adadec534e05b2e5a05c -d1d5a90de947b7d539f4cf8f2d966cfc328b16251b6e4d26ae879a23cd605a31 -d1da4bc0dd45ceb9cae1900cf2c9e9544d24ef7c6b36243013e90d2b0a2ae335 -d1eabac3f153a378c4f04535a8cea8ab335fccbebf7cf6ce7b4733fb1f5c5115 -d1f5a719a3473bed7c7bb51f8d0a782aac5993c65a62f02fba429be6ac3d54cc -d1f606d5ff6bf9ce1b4e989c54e3f10b738324c4765aab9c796fca0b7b3753c2 -d206a3a53fdd4857e87895a307e012fad19421619936786961092f96e48bb1c9 -d208ae53a64427da37aaefbfc84ae9c657a12078d4450f0c14e0e1d238d1ed73 -d20a7de628370ac668d83d4695529ad83889e4877c94ffa52f3e50d73e2c45bc -d20cb1961cb8c1e7305257e15a0583941b379e8eac5744147624ed795a539724 -d21e5a86f743997bcfae2307eb19ed080a19cf8d8d4f909f3f65120be7bc799a -d228ed539df40b747d1543f38e6032bc2ef889262b7c6295642ff1089c5daf0c -d2337cb965c1341ccbf592b7109535f3b75526de617b71fda66f23023880f987 -d2380fe5c98250fd324912bd22e4b25e8fe03ccf41c2ea19a97680ac07ce9c3b -d2430ece22b9f65e7a052a4483093af7d48943af07bee7013b16c0c666bf808b -d24433a0cd15cdeee25e982000415e79930609379ff8e256b451d5a9cc4052e2 -d247785520f31cc190da0f79487be7f5e4f2ba6858c8ddec95b1b888e34963ae -d24febbce6163f598e1a4df843ce4ca4bb53f06f7dde08ccece8b22738128e72 -d258e53bb7acb1d199b7d1998272077d6a4ca72a46c1dd2011344b0e419c191c -d25a69090129316ed2a9f0b5b7fcae8cc31f12b89c10632dba15f4b19722dbfd -d263c5b9e67c5fad125b33d6cabe6589b007db275d54a94bddcc5495d51f8a1a -d265f82a03744833273339eb192e202d469aff9acff90ae7d205b00297438b80 -d2684bc4d29e9faa6195b1fe27b6f96328b814e9bb80576b38168e2436a2f50a -d26bd88fb7e5e996ef78451a9891771c404646e67ce1e222be510e61bb950871 -d26e85221177133c6391e53b3ae1c97a6c292c5df54599e013afcfbb630ca522 -d2720ff9b95f2d4732eef08c2ed8a6be3d1a393525ed031e5a4b92053079cc19 -d28fb30ad54af9a295a8d96bfc76d9ab5f5e17e76d077395aa5f3a7d3f99ccd0 -d2904ec875b2e2dbafd2fe14073b46c0c9797b1c0153e717198e5e29162c65bd -d29bc0faf208d948235ead21ad3f7b5246d99b6e4d7ee1378d18252be94f3936 -d2b20878662143b03e11143d5f9e17de8febc80d96bc749cc88f1a1164312d42 -d2b7bf0645cc0baf9727886e233cdec73800765b1e2142243b5b2682f3241e51 -d2bcc1eb10ae1241ffe141292e154f5b0a29f9e2ea645eb32dd49b461335f577 -d2d35c8acd0b03dd237176ad0cab0d17c78a67fb75f4d069ddad3cd2679d1d96 -d2ded6c2384eaeea22d98c3cd7b6a01730703710f4911f8e9715cc6b77e374e0 -d2e544ca93d7f63a76782c808da92b4da0e1621e441b342ab701b300da0e6499 -d2ed897e0a40e51dc3d3def9528bb21358b227a2d690b480e85908e5cb6496d9 -d2f2f07c12d7a7e5a389d063a6b75a14df801133cfe3d51ddd24cb1972f83590 -d2f48d00ffbfb3b86141013f198b2b5fc974101b796c3b7d0172cff49f207daf -d2fd5facff566e0e206d6fbfaf76491b175ab97cd81a31cdae9503dad128f49d -d3151e606bf9e1b66d89dd636ec47511d1eeece937dd75a84c4d5d1c5eb70f2a -d3461c3ceb9e06a512c70c4c059c3d4d8ee16d20cc6b0674e1e4014b36efce1b -d348275e39af9c2bde34c7d9a54d79cd4a009ef1735865d11ea0cb9c5ef7f426 -d34c7db29f620e09202db4bcbf8ab74fc72d8b15a58d5b6e76730b32170fa120 -d35cc84010be4e4ea952a30796817366976c7b347fd1ad3fce67017bd4f41bbd -d35f30194547e2d66066c3e77ee3cca58c78d1888eb930a7f8c85e62f3ee11b4 -d363984aa981bc3c5c50cd5697733dd91e3e5acc9f45f196a65a2828325c6d89 -d3768169ab630a35f7d79a55828369a8ce53ed02f4aeaecd68ff4255c05b6621 -d37779b5adaf76b6ac77fa35b3fe4520aaad6f7ca0442b0c166e50f90c73ad2e -d37a723dffc3b896a8a07a9bcf5acd055934a1a8580daf0d9260b1057c38168a -d37cbf48bf7e62da571be0c66b60e7a2ac08f70aa395f30e07c5df673f304a1b -d37d799546d59c24dc4da5290566d0dbabcc7b21ef4892254ddbc1967e1bca1f -d37dcb2b12a7582942bff593a98797e0cd2a70c0e61ae2ff1840c1c3750b4724 -d38b90e7110b3e88e7aea907b5151a8188f68736567b6db9eac886f7f2ea6828 -d39a1435c0833da935511183e8d42e6e3418403eb488ebaa434802061d39969f -d3b64aa7dc7f98785863165671d429a168585a234952384c76f065d233daa6e5 -d3b9cce54d7b5b264f04cb9d78e59b564ad58b9abca9b551df51eca0099e0f58 -d3bd234af290372991ac1c6135f97a04b3720c2f738b0c4158dbcbb6eca8f29b -d3bf489f44e2198951ff0c232412673988249020dfbdb8cd251b40f9cd84d9e0 -d3c3ce7b29b83008523d4153c720c091205a6052fe1c8af938ee6c65d3751d0a -d3c3fa92c9144d52b5703c7f2aa386c29a38dbeb3904a33ef76467d03136fe9d -d3c7def0c75b29fa9d6da5e4a0c9d41bbf832e05d13c324f0aa6e916ac2b49a4 -d3c8c6c94d275a88f42ec3bf32c1b0127a0b048df8ac15c1e19b00c0f2e76281 -d3c95670b4e034cf6f1a7f35c41b5d4c32572cd5bc8172aa08f637754b195cba -d3cc7dc55d950d36071bbdb856efda2598b288c835e8d820f5c2939fc69c0576 -d3d3368f7a426371d39b2d436286ad20a7772b7f728999e66d641091ae1ead9f -d3d3b4f0a5beafe5bf3f5fa8cef11b861d4c6ef1f16e4eb4a424aea050ef2334 -d3dca45eda88b18cfec048787e9bea0c02f0e4781d6df2ca81ba0bda62cd4b79 -d3e4d52837830eb1f47234b812ca0ea70b3bb7aee799945ef88359a00bf469b9 -d3f36aa28936ffd679f398d45a449de09d8406ff79038f08007ca794c3549bc3 -d3f37e195e83fff5d40c9805cff4fabe00313f452b25190484d92513c2b7b276 -d3f501e6aaf83203bc278ea08e643a14fa18601621610a8b696e9132a2ac1e25 -d3f5ea54cd5783bc708c9b537707cc1e765bc5d64cd3332453818aefaa182f02 -d401a36354757b758321476948b1bc2f393f73cf3cc68ed3743cbf4d461011d8 -d4066adec89d2e08d555f0e252b0ac15373120ad5c7193fddfe7b8a0f0dcd132 -d4096ac3baf9c4068d7185715cf466c143442c029fe182dba004c9e75d066eed -d40eac1e9fe7edd926f2a836859c56541bede995fcaaf3c4ae7ad39761c88de0 -d41a2ec226cf66332c3ed478a1038cdf76a2a3e1bd9cebc4d4197e6dc1e8e93f -d426b01f7be7af8da9d50d1e67d740ef167b75ba63c0b920b306703f1973cd87 -d4290e6a8eeedbdaf6b06deb77d5588094fb616c04f33dd67a9023a88c9182a3 -d430dd2fcbaa0b5dd7b24ce47d4349220f7f447a1de8dc49a96096c6171afbe4 -d43770660e79db5060ee1eca30e516ce2d146073913caa4bfbcad45d8ec5561e -d437b032752666328c9836008b9aaf2e3d3ed4fd5eb62d9e9408eb66c29e6730 -d442b3d9249ec3a0bc8f956805f7fd02476d89dbda623297d77f6eeb46eb41e0 -d458ccdd90202dea71db9309913dfaf8a021e03cc0168d4c61b079f9b2c72fdc -d4643e2262f71d0a20efb9e3e329d3c13406f8eec9d5a1a3e1e4753a04021f50 -d47b2d5dec08171699d61872c81f2035b7b5cfc02a1057385cdf07d4c2dfe14c -d4817870eb2781a18aa5c51719eb7a88b4d61aeedcb8132ffd57f0c445d1956f -d48388574a32a7f865b9f6dab37db00bd461648baa8effc2baad541b2ba7cb62 -d4890e15521dfd3cafe87fbc2dae5508440e189edf18e51f96769709f26247a8 -d49e374c8c496055993b04129dedc886975a9fcc4b45f8ff3cb8b3dfe1dacd74 -d4a0b6cb2b9bab1a3fcb413c9d6c632e98f6023765866a838651c28b27269e04 -d4af15cb298ee852a563b01459815d9b49c837a0a2f453462139cb529c86a401 -d4b040b1a1ad9ae64d723e776af00d784239c6176d5fed76c602426c675ececa -d4b439af014645b2528ace211b0f67a2b3acfa9cb22ea376cced5392ad212628 -d4be063bd3036ddd1d716eb5fb541913f3b52b5d09ca9126d0a2f21c6906d714 -d4c0b8843a1dbadcd74a6513a3b42aa6c221f632be89472e416be57ab48ed5e6 -d4c4ed0a716a2420596f3c5aac2d4973c438c95769b0fa3f5d89e535c10c7105 -d4e8682845bb4e1c630f211081160135f10fec0ae5ee645bae187ec6eb1277ba -d4eb6ce39711dfd8031f53252e5998c847c881da8e11160e2344bf97f02f0a7e -d4f10664383d8516ae19c729839012afdecaca78525bebbe69dc3ae6ee5a182f -d4f44d6569d94d9a60972decec10e44c20367a5248673f6ffdcacf15586db262 -d4f7d9a11fadaae862b9abd906c341d00566793995a2e7f600ae197bc22f29a8 -d4f97e37bf7a5e9ac80ba9fd2ec18146dcc9130e774daff409e5350a1be269d9 -d4fc2b8047c94795fe1c9a8904e37b0b2e40d6fb37a1473ededf26c2e5c479e5 -d504acbbd3ef226f78478675f4fde6e307451bb8809138d8f90ff98ae0449dc8 -d512026612848ab5436639694140ffbeeda7f48826423a8cbbdf282e225107a3 -d512a9aa0f968c3a3fc2436ee08c57c102dd515547ce7da475b8d8f8ed6d6c1a -d52f088c493afb0280d150f6e0e8dbdae3050ef9181f3e084f644f761ff426c7 -d53280fa129913850aa53d45d1f39695c12fcf1bd761ae537ec9f91175fb7399 -d542571c514dde6d1f80fba512a9778534ee30ca65368a6a515d949e0f11cf29 -d5438b653e704f6a47d0a101eacd8dfe5ab1499d7387a103b1e082802cf2a97f -d54d79003b08a1c9e27fef4cb8d97ee8db04e4dfaf09316bd33246c741b8a5ac -d57b0cd97779e9fce88a5180061b5b5acc27e4986c6e6a2c38676bb2b8a606c0 -d57c2b0ad7ddcac264c51d0c981266a6543eebef9f65477b9a6e297c851a1cac -d58e1a19cd9c781112fcac2be15fa5c2df635fdbc5d689afa9c95ef992d8c8ff -d597527f167877d92aa18fa6068ed2f648604a2b97bad62b9ac6dcbebb4248c3 -d59fa2aa3c1603f72949a768f5f979505d4ca3add4db024239f1b6a6c25c554b -d5a0cde2ca147365ae8c948566d867227c2c0e76ffa0fb919c27addbf1abec8d -d5afc01fc6e83daec1c0c27e5de7e1172fbfdb69d263082cf079515a64f5eb6c -d5b035207f6eb3ae2e9204a01692aa04b23766a799dab8e9bdda497b296f8894 -d5b1842661e89c3fce6f63031d0c2244f983cb5222fb9ce96778fbe3c71e3fb1 -d5be7646f88937d1ea4ea6ba66b33e16210afb784a5421db497d1f1e798b85bb -d5c1bd61f4d38b8abf204e62c8513c0d7fb21699965cd3361155b94b3ad7cd3a -d5d0951dc8b3f597f0d1730386b8ada675500fd8601f6480b164a84aa59eb1cc -d5dee52105eae293959ed642abe0007fa2cf4d6b147294e8b00bae00cbbfb82e -d5df45f8c42a15ccfc4d6a537a7c726db483b55a5e18a7949ba9efc233b0a227 -d5e1e9de16a3b79d7fdf5be2389e7b323c1b27283ae7eb64b927915c7fcd0467 -d5e44d143a69f29ee2154d00470443931a7ad91750b98a659edbc5aecc9c9dde -d5e640b0e99e18275819940fbf25b6ae08857c1af9bf680f0c62d4c2a58ace5b -d5ef23a88f16bafec1b6f5d3a74041cee8bb2164463c9c25f272769122875d52 -d5f4e3d67e8079346a5d7adf90cfb8b019ee5bf81f6e5ff56fb72ac763b8a464 -d5f9ad75c18758670d41a18cfe057d6ea7d65c490487a4d3575104ef9182cee7 -d5ffcae9121c0cada857f7069fa81d57a3e3fdd2a3553b1e8f9c7e369d208b1e -d605c54f2b27473726a35dfd74ef2c38f4ee8bde3c9c9274ec2134e1078f8730 -d61a6e3c6bf6c416c3cbb89533df0ecaf462b117afa54840f34d1b928914ae93 -d61c0dd1fa397556ba34e7d5da161f02fc58595d55f81339c0284d94b395645d -d61dcc64d0a1782e06cb3bddfbf597663843559b782cf17c6969bb347a8cef17 -d61ded516c3c0be95568ee618b7d7a04ea724cebc702b227dff74c3b608d495f -d627830ab2dbf4369a134e0f9291e65dfc9fbb0d67d86ae41c352abf952ac693 -d627ef18318854291e6594466a659e65ac5d6c8b41254eff6b604aaca3dc1548 -d635156c18ec40a89acfa68ccd98f51c8a34f366611d1d0cdffaf491bea1630c -d64dc4f0d3284b9757725fd6adc80a372bf8e94e7e2d1ba20a8bcf244899747c -d64f2f76cac19c2a36db3eff2bde5ecdd2f887cdad43ee137b1dc217f69eb944 -d64fc8cfd1eb00aa0fae15f9dcd60efd609546dc093a53715510858e4bfe2746 -d65afc7c703ea483f088ee06066d1d5956ffcfdfd55c51ffb18f34c4aef2025a -d65ff73ceaf0f59b41b0aee80d7ead2f3a82ef3b7879cdc4e4a707e02d00d592 -d666b50856e043456229f282ba380105beb94c818b809e514b8e01f836c1942e -d6725d5e38865ce3adb8ba92780294c691a1a0b66071a77d16e178187c099542 -d67a3909709591ee82d5b3ada4e51cf882ab1faee34475901baf0b9efd10085c -d681da0138a7e562e35a370497b6a6fee8463a8dd3f4437c863b42e90bdd877f -d69337966812f960b4ba0ae821a937e102dd4d95fd355ba7f00cef6f73a4bf95 -d69afc56565f0ddb430fed1d4ebbea3c4d62008cc7ba9e03159831810166cd52 -d69c8e89e2aef48b13a58d3f87b2cee355d770119fb2d178bb32341794f275b5 -d6aabccb385eb37b3e6245742668744917c07920f1c54e4b5f31fa519bba1a87 -d6adfd424411d1f4c186fe425362955c6e8ce37fa1791f23182644defe3aba9c -d6c17d211b4899869cd5c22ebaf4ebbce828de021b6a6c0eff3b32faa0da373d -d6c1f05132f67474ea223339ed35b53b193bedf203387c391bb75d0d8f3eaf18 -d6c9a3c9c3963ffe3d5128c8eeea96526afbf72aaf0b83e0638385b5c81d9ea6 -d6cb4cfac88031e7e4eb796c6801fa33d4d0e225d6be548894e9a447191f22a6 -d6d2b2fe64f0d05ab7418576f2a429bedcc9b64b2e232b4235e3e30d96e3ec78 -d6d867e5d0fedf4520a472f4a1a0d747a316d13032874c65bb63e720036c734b -d6f3ec276cd9fd540e4d4dcb7aa0b43ee3ee1c9ece9ca797c0683eedd589b85f -d6f86f0713a30593cce16daf465a21a2be637ed556af8abc0c88e1e6779392ee -d70fa8670b51075b278eda63362355a32d348bff82a2db2ca30b9084e91b5caa -d72cd04c91245462fa71de254ef424ca593edb68bded14876f7e0b613f7e218c -d72e172c01cb3ad8f88afca15540939325f2b415ce51330fa7578ab494410592 -d73a3eb18aee995709f88f0a9cdfae7b286e9b0e4c4fd2044bf5ab53e3e89f18 -d744caa93781f23b3b778dbdfce5ad9ef13fa4ce6b928670fd1cb064429a33f7 -d746edad6930914e4926a286b4e8f76474363b6267d24933a82668b3304b50e5 -d749ed6781e4497c69dc47ae0e00b79f3bd8cde4f6433e3925dd5aac38cf8911 -d74d4860a31d4f30a9404188729a18999f521df0dbb1e3741a7d29fb9b28ced9 -d75c26ae4dea18602f04f0026e2cb51ae40cc0ac26f3bf363e63057d184350cb -d7669b78b38402740d80253327313f1c01b40bbffeb2ebd18535f5db88f7248c -d767e55f47f5734eb14aa26ccbd6c26ceb24e928e223f02da8374acd4d06bd1a -d767ff8a0324c14909e229980dd69b1f74bb3081902024b5a48ec1234d872c1b -d76f25f64fee71c6061192ebda3fea6060fa4afff65edeee125224bc2da7b959 -d779a9e8d3712ee161f1c84a1f579e37801b8d7d19fce9266356c1d2c9d16bc5 -d77a9cdafa0a76168d716334f77910bef20050163f318e4708c715549ffd7e17 -d77dfe2d253a01658c88d0709b34bdebb7c469f7865ef22a6e7c220de9066940 -d77fbbbdcbb9e5cc9d9660bae8fbb52cdf4a16da604f4030f2e08fdbf2f534d1 -d7872d2be6322721d30d6a5e9562dd66db46945249c09dddc5de1a6149e86068 -d7a72675e2c3045b9f4c6c6e2f1857ce043f8143d919959fe49efe79ea9fc057 -d7ab09e4ccd1c3e353814bffcab9526803696cba830dc319c2ce60f7f90bd34e -d7ad951d85c2b9b6295a3ef6670747ae7d59c915d094f63b4af52e2f6d91e54e -d7b76a2e044962fa7c21f1079de891d475ce90c5c4a86aa7f7a531d8184fd296 -d7ba4e442f6c7da8b774a3963e6c08428e3651cc009c4975c2f2b31767dc82d9 -d7bce6d7ba26ca4e982f9a578cecc6b8bf910174dca511d7eadb07ba386ed32b -d7c5c3839ebaadb9668454901f937b1e435d2d2bbe0f5a6e9834f1688d5b4b3d -d7c7c2118fbfcca827b128b6dd0640b114e33c49912a76864e335ab92ad16321 -d7d03445a70169f9d79d0cf2527fc8eae46d1e6ca95af9284890db519be32bf3 -d7d498707cabce1dc4b668ffb768ef1cb1fe70332046ffe22e4db383d571b911 -d7d4d85b3e447d1d37cf02de7639859a7ac6741871cee53612a7cf919a63dacd -d7d6d10118f789f2313eee001fdb0799faf8dd6331307571e6b004141ba4b639 -d7dc42d98faf7390ecfdda87acc4191d5ec1efdbdd2772bce15f2e21c6e4f6ec -d7e2867a53a981c30e8816390010a786e14aa80116dfaf13c4232d530da83dae -d7f47606381469eabeec0e89b5be0a2cc261e535fac5c5f3fc76f557c7040f9e -d7f84292718d5f995566d024a1085b5d577080ebf1f3831c174b0de3ae016456 -d80fd9c60a00a48ab477e184e17db595913dd5e3f0684c001aacc7d63d091994 -d8150491dbc89e7e6180ec696aa9c1eed0f9cd47fb636ddc3ee5bd64f967948f -d817c4f64bc93614e5b2df64e9818e7a99855987d372d149e6469c497b199332 -d818cf310032c103bc0c58c96c015a76dd5732cf4a1039e66f0e237e6e8076e7 -d81aa203c26ee993fdb1376ee0a4eb1d8c45b14bd492d5fcfaad5ecf64c99921 -d81ada2fbf452f1deefe51be2fba7b4a82b64a5ae14c3c2f00a1f2e7579d8f37 -d81f677a942a2070f7ff19afdda0d76a5caf2a6d7aa1ec9e5bb8c37f16583914 -d8250921422ef7168a2203807106b6c4ea6a540d77b51940f559dc89118b1b7c -d82c6fe2efc833682b6e7fc3727e52ef505004772e6b9f51718e4b92053028e5 -d82e7e47fc5e260f5145c8391a6f18c925641c1f2bb12ea2e04b1a98fa5eac96 -d8372a154a8a4a6452e9cc27aa07ae05399cc77cef0c9ce05e35acf302a1ac35 -d838cfc738fee6fa658c094d27a11c343f236629d14b6e3880ef623d678e5b5c -d83caf72854dc1d1b753a6d2c0af7d3b87898b29531e03459b510aa6adfb35e5 -d843cc28abeff1d0f2b1f92e9da0b42657feaedb827c776b1f0ada16e20de079 -d845bf85e4389585819b95631ac25dd984e03f6732b923e1f00e7d2a2a8c05fe -d847a6617d7a7ed86b4691752013d581f9f23f73264f62da56d8fcefc6533b19 -d8562d212613f00cc209a8f468b7f42c46ee239deb54a2990da13773889c6e2b -d85925ee460ea01135b3c69a0203344baa9721bab5a369c47158f9f37e79c7ca -d85b0495aec10caa4f352e7e5f3c7c1f18e46be0bc46ecb673f485ab28d9a56d -d85f119e2d1f6e4edc07d5fc38fc76ed70b5c83be40ff50f9feeae9e56d1d90d -d863b235d971b2ebefa3686e7baedabc556ce674e451c2b6009e0a02b832b2a5 -d867603a16f9143928ed5e09fff0f0e537ce66893de803e12d244f75266c184f -d869ee7e3d74a1e0878875c7b47d9d7246e11da694d538ae18153583cf68ee97 -d86a5b114b7b0fe1208e924cf20225fd382f8cc7cc55423c766166b1554eb145 -d87541036e5dbdf608c7da595998905ce1779fb386857db663adf145a80dce6f -d875459acd3c55380af7c8c3cf2fc4bd7e18fd7314725a0e6a4b19d65b1eb74e -d87b6ed996e8911232ab4a2145ae335db203b4e009906ff4f512a2174be50e6e -d8890a7d24a0b1fb56f57c5e51ff5e260340c98d6b4b7faf34be0d55fa1b2673 -d893556377bc2efea5018240b524c52566042340f0039e4aa286b4cb209cccfe -d8941cb7a8922bcf9430b9765f2d7f8548668f5a600afcbc8f7d5d430b6da937 -d8956919e5407f93247428c5f14cd62e68b5e2b7d749bf175e83a97c5deafc3a -d899d7fd2a66fedfe5a4e81eb445bbff299275e5eae10dca41f25b6b152feb44 -d899e02103e3df065403b66ee98191224f30f8f51a4a95f8325d7fb534cddb22 -d89dc30e0617a5db2c52142b4e88b92fd672cc6850eea6659c017fd0614ba49d -d8a91fc26ba35b1d95f4881f439b90ea3a9129b6b632a9ccc132eb211001e797 -d8afc2bf670b02f298c07f459cb384e960a09bde41ffda24c8c9d3d3f4ed73c7 -d8b0eb99543cf3684baa2f72ef377fa0884358138c4b59cd052e4dd2c3ee838e -d8b59dae7e30bbff89e12a1e52fada87906980cddd9f11436ba139aaa85096bc -d8b7a9114a9b27f9fdba994bb9911da9d42dabbae9ac7408115a7b9d4a926eee -d8baab1da943b001cd746c004dc935a0e5f1cc5bc6ab8abecfc35cdf888d2f14 -d8bc0367c478666715c856513f560cedcf38477a316a1cf55c7470f7515e30a7 -d8be57d114474ee41c509bb385a21e402278af610635ea84cc7e9516c7f74a0e -d8bfe7cf5364acebe4983216301fe74c2d50e34b21cb441472e704810bbcdbe3 -d8c5af189c7208244bbdd2f5e107b84159efb208bb675c83f1b4d402e951d662 -d8ce1b23512930070a31a78495f82d79082d7c2199a5cf51a4615bf8b592e197 -d8d421fb2c9e8274001b6335ce0f895249d44b03ea008829d8fcee7eb2cc99bb -d8d699e0ad7128aa9b3758b8b55e27e4e6c453fa34da7b0b6ad686076d0bb033 -d8dad96e9fbe9babebccb20448bb8429d890c26114a2d279d82d65eb79bd637b -d8edffa6a4df4595562a615c5e699d6fdcf952ad763ba80e97d18a2dd9d8f525 -d8efdb5b466a3ed964f9c377995b6136010e6fe2604c2ad96039803263e8a698 -d8f2593bf71e51404d2a8dbf8064864f680cf57ee71267bcf15aa4c84ac6d30f -d90f9db297e4ad79344f5d0dafe018b39ddada049320a9dfcb058bfeeac18694 -d91348a7cbb41a388538dca62b4d18cd29c3a4b5f8987a1884bb16823cafabfb -d915ac79d8bf60cbe437ac7324c389a572589d32426056ad317454a3ee8d2cce -d91ac2e8029bea5f65b1d4e6f13a41e1051665ac07d14a1bef9a08fda57f2f5b -d92f7fcc8880e70cb61ab39a88985af6810bc533a5d7bd9331701484201617a7 -d92ff746e0c29e21713a66a31390fb360d20cf1294e8c09fcb7372e7fde2d0cf -d932adf3e250e8824e4e7d3b62a434e9123e8f0108285ab2ba0a21a10e06a10d -d93617eac26f7a742dec579bda95c59bd8df215c0e31e83225b0f70dc2a10609 -d936694c419e84ccf57d2f35d7df44a8cdeaf4f6af1a35830d7e63713876490a -d946ebf777d87abcd1f1ae9a483152093e3e4049209776bef83a38fe38245bc6 -d94cc9636a8063fccd7fa289f3a10b086d585429d20c906dfcb66145f8ee6331 -d957f28a73edaacb2edc1506fa3150648531a13a394b91eb758acc32bd3a98c8 -d959622f1df290de9c658c0289de5397f282d2af4050c863e9a6290746557325 -d95ab0c20527d7feca22c8b41f011a5c0a2e89a0e30190d4c8e58e5b9f10a6d0 -d95ccfeb3bde1410a490f1d9e64f0b8c9087da75b31c4f776b448b747b836c53 -d96193dda8b8d69d24a0c48d79571f46fa125baddc0044bcb110aee6a1cc55b6 -d9678ae6bb566e7fd804d37a180d5396af01ddf65d7eb5fc6a6bb2c85423c80f -d96a8107f458d06583f12dc302fddd90d2bb8d08e1cfa9931d451221bf7b5965 -d978a1b03586aa53768caaeeddc4f160d62944a89d96b83073f0b8290415a135 -d97a33275e92cb0ac7e8b4c3fb0e7b6a7796eaae0a28617b19d67c4b6e88da1d -d99b3253958c7ab1e4f2e292a9337e8fd88b217de0275eb180952a03c5d6c53d -d9a73ee56d20aaf146cef59f547720550b4a263c232b08e67d84e81caec4c100 -d9c816cbc69769dd35a2a22ed6813babe2a0b5572260277752c1645aa108ad14 -d9e93b7b244a0fbf95c066014ce742418098c0c98e95c5dc147cee97218f42a8 -d9f28dd8005bae4cae375a827b86d4b07f6ec3b7d2adfb85db1b298451e1dd93 -d9f73b8d03e29d8844631647277a9dfd78a96022f5d0009b06cfae068a7b0232 -da02d6ec3074a45c1f4859ae1b762ee49e13d71079fdfb27ea04fb6438c760bd -da2386fe27b324870904b01e9d7c40b2c8c11ee2b75792c16a8301f8bef7751d -da2f4c52dd8f87286c1d8a0598202c6cdcfc4fc37ca88233d33fe73204f7c562 -da2f5889581dc33def26009b633298cf1b0e4fc8a66b64517cbdb4b65cc66de6 -da390178b546f086b8079a0234bb843d474174e6b9b58f17e3a9c2c9883e98ab -da401d6e70a00d0029613e7875738ae5024dbaa128875ca5393e90220a234e80 -da43e0f542ab22623281b34cf659528a52d1d6f18a68d2c3bef52576bd9659f3 -da4c09ef658681d21cc2c7b08a25e9d3435c83469a0d432aaff02fc298034cf1 -da53c2a06f5905546a5ebb0deecf646f0db819d1b72487478cbc867d4e4f158a -da560f901646493b8ee35e84de2d268f5af60798b521c24f69192997c264c0b6 -da5cb26de4b0058bb0b06fd6d341312f6e45abf4830fcebcc8145954bb495792 -da5ec23821f1d664f98f12d8d6210c26669104705c5c3a1729edd9ac2feb7c28 -da6c902c43b9cf538e7b36f89f50b217a1fdf93de10cbb16bc538d1a8f548e60 -da6caf5b1a7f66a328dca0ebe3524085a837c98dd33a09085d5467b5be641875 -da720d354dc82516688e1c326d3ea49d02a12c68b930f4453dfa35665627d8ad -da7cb1611fe56e42619b590f19060ecb5df847c92ceafc68a1176db82407338f -da870ee36a9259d5032fc9be70db98e633e113e19b52f0e92c3ac00283d13385 -da9895f48f1075a2658da9e7b47b2c8836e10c4d8ff4b4202af43ffcf6860220 -da9bcef7d1eb341b478695f95ef59e9e7f78b4371613aa449a232b71d7ce2df2 -da9e90898f8a0e1a01676aab7fb9b8c09e3eb7d151455182fe9368ec6d64c7aa -daa28015ac380f4a299907589260e836b96daa676992161d0eec098ec70c86eb -daa3f636fc27fab3dc0d576ec37efd0e4b89af08c88236f56ec9c0856b725d73 -daa8992b3391f42d5bb2483ad5e60e170a3f38720a5eca160c369b9ea678f7ef -dabd3f9e10f62183c72dedb1ad7d3f1199ae4b49095215543d6fb72a22df7dc8 -dabda894259209767d014087034bfe7cd77479e7364f79ca73d976c0e875fad4 -dac150dc20867c314794186a19f91bb6a8ee29739ac8cdb3f49cdc2772dcd8f2 -dac9797eefffd3c5df81f3b2cc134c3ebbcc3882312b0bf13ec1368e1612940b -dacb71c3574a1be1b5aeadb17d6d9914a6ef765b38197f97d31568c1c31f225f -dadfa0db0664949e217dd16f89378800940a707f48f1e1cff10d57d97d81c547 -dae28ee9472be7e82f1f2367d552d64174a1c2dfc2c7c4b5b8747a8c2435157d -dae5de908ef168a61304eb5fae448b11a1ef03e0e4f5c0d5a1c604c3714bce10 -dae83c9a77988feb23fd418b86aeda4b7b4a40a6ff62116b52b4a1b6fffef767 -daed9aac49491991c27f0342036453ae92f8a6f191c6f95acf6d30a247d4ef26 -dafabe9df07bd6f4e04b2344057329bf97c430a4e894afc9fe27a89fe71d1d3c -db0acb70be9c1f5e356373c534e327c7bc0c6e2474e3aad23f21eeb30d666cf4 -db12cbb2a8d3e4402b9ad07bfe531c06ec1ec69364684069545bc87e1ab43f89 -db150eb99e20c5f78534a720daa3b35ac85599b229f75bc718f4351947b34983 -db1ddc496aa5f447e8ce161f6fdeb5011d7111ac87def075e7ec2ef4f698e1b5 -db23b258e4f9bb34b22d7a2e4c495999fd2b52e3e028da9113563600a68361b6 -db273fc40751925f2969a17b6ac168777a963516e96a1587a7c48548907ecfdb -db27a025184810c08855e14ea41c5aa59052fdf06668db8fbd7b732181146e97 -db3cf788359228775f31f753cc78595d1cbb2592b8f48b926f355836b8609954 -db4db797786e3b3f355e4a894d46675a6e5456e26a38db03b76df2387e9c7006 -db56638a90f0b36fa4bdca0e5c4eaa420cb67db967b294a7696e6a37a9cb7798 -db57ecf389c14af8fa5702dc48993474b15d7e344ed1d8ceed9194ab5dae4f0c -db5c6543b047a06cbcdba36f53393cf213318b2468cd32dd67d7873f61c562fc -db6aed2df8902eab1981805532322bc4f0c2d02e7aa12ef15a96a40ed8f75fcd -db6c2639861fe922c512f163f326ad3d70529b22acba651918ef0c96794c0c00 -db74697568902a232038690788c6b8d50024b994a1e1c3b0391d0709de220519 -db7555116cd31d4049385f3068f219bd30546c9e821458e98fd4a51f0fcb3407 -db7789efec85fce499ba8a7111639d3dae496ad38a510c7063df1e48edc89072 -db7c075097ce901ca4f64a7e00e6d25bf03c6f2d521dd9c2b7740bd7f8322bdd -db7f83440128904672783506c9d3d1c3fde2cc5b3eb9adf44cffca5f3a2f778a -db855e706871accaf50d5d38cd037773e4e4f9ea112aa1bb7267fa62b17ed6ea -db90f8cfa7b02cac54683d66d77f51503ae2c1e528b2248e19d1ab7789f489a5 -dba319368a3d15fa3ac07f28e2e57559be2f66df7983049a9342920c8efcd790 -dba394ae79aef959d72f2a11b6c6f1800eb8df55af2de2fdd6ae07e30078301c -dbc5515a8242df56ef615697efa3ca54459f22f49f78db6e91f96a25e0a90a20 -dbca77f74e0395b6fe66326d5f4f447cb6d69136e14aa789719934bb0705c021 -dbcab3fe207766e57f5827c115ce2d14ba81563678620027b0e7f5077c350845 -dbdc39d477ef6cde071d33da9f2279a33bc8f98793c984e9ab59985924c7d412 -dbdfb9d6e12a68c71ee00a5cc84c6979b99709e2b7582e30e8e6a0f9d311e002 -dbf05a510947fd07115ae594de9ccb46ff1b6175c30da127b08f636d0839004c -dbf1fc23ee23a1f1c785b94db9e55d9d07eba4f5755e826a7665240fafd0fa47 -dbf24e9d127adefbe4454242e42308eb9dfe9f8b999a9e6c01898523635b435c -dbfc7dc4aee8a2ce5fe3ef40b3df75fddff9435f9b6d778aba1f29d2d690e9e0 -dc180142593ac7e1653dcdf4a30ff330fba75f3ea86ec46e049d1290a31d1aef -dc1cd3cdf5c94f3e349eba31285d700588cabfd1061b65d48b3b27a46a5726e7 -dc1de9299a92b49df4bd1a230bb25aa35f43f16c2b5ea5953119e710d9faebaa -dc2175cd6ef9a5612200b47e3cee357ccd42fd093a4d2dcf199c26ed3f7fd3e7 -dc2890ab56309ec665b5e6672bf2c774487f07d8445ec9c0c0f18b5595414eff -dc28fbe695cc85a163e201bae3d3c5cd3faa0cfbbb4fb6b22838cb70079a1f8c -dc39715e4f4d744300b525690f00b767561638b553371601839d8909694e96eb -dc3ad24e6a0c43bb22a2076e5e01aa6ab5b5e2ecc40cb15b8121495f8fd09099 -dc4e3d13effdaf340cd89155ad26b67d91ad564b17308f0bb42c1e8b61fbef7b -dc59e4a18130683f7181292193166fe44438b7737aad48fe7112bdc26fe88848 -dc646a053fdc5d0fb9bc811ee78f5873f330870f73cb64a11a5b1d307815281d -dc72f4fe6a9f1905f2e18d10acc775a1086504c55d5a4d4aa065f3ea02b7ea66 -dc76857d1b640a3383342b6f9cf8fd3f216352cee5d447ca02551e96c9788d61 -dc79d8b0fd0dd7989c0a08f07c8f2b81bb1d2bdcddffb1c11186dc17d040ce52 -dc8926f87c12d7d3b308f6ab6392b8126266fe4fad79aa78feed90e942c2f5f9 -dc8df444b306ecdfb79cd8dcc6417feee073d3c450a34c3c4e1f66e1f2d478a6 -dcae47a8f0cd10ed1f1568b3b9d43b79adf86a3ad668d4aa6f7371b0f85a046e -dcbf430a4c7b2a9ea9eedd252a09d732aff6c4d8645c916a27d339992596398d -dcc16b793cfcf38cf024b39bb996aadd7c33a2a0d9c799bfe192262442142a39 -dcc8b184ec8379c35a96b46012d1ea0c7ec5db708c859be8d71d9fac12770209 -dcced4a3c023425ce24eba059d71c006e2b12f1513e0e834d6141292cc025aac -dccef20e6fd5af1b0ceb6f2591899e1db3e13a655a0ecb3dd0519fe2d39a1225 -dcd787b75e3bd24b67779a55478ed07b94821bc47e7360717295fc3f1d7db7da -dcddbe592c6425269b70720f6bbd3bb8214950b672fdd4a9caf5273d2a0b5126 -dce25b52eb9407894684e013f565717066f5adcbbc9518344cc2b82df818cf0b -dcedbcf2e34a5ec59cdd99bd24bf77240145aaf00b071f4cc488a81711fbe921 -dcf112ac7ac78bcd70b7dcd913966e5015ce80e40b1428330a78280a52c8d817 -dcf136e9156d1c2488cc93ec7a4ffa07f28ba76e6c0a48dc3608caeba9225b7e -dcf7bc681f12c5eca2459168bc8f2639388a2390c3ddd263396123fb116ed234 -dcf7c97d31297bcd262d9e628bca887e9e64735685e863716271c98b62daccf1 -dd0b94a4ef89877b42433fe81b3c1f525f09ca53050baf2b58ef4843e01366a3 -dd1854034a54e521be2830f67218f9824ae129c8f8331d715df2ebbea9a67363 -dd221201941638aa2879e723d3217d1813ff92fb822460b110892350852197c9 -dd24096a7e13e3760f19a53b617e6c2a6533374fed3157af1f7b0d4c1546be37 -dd266024b6bd795ba1711a21ab999f4983c54d9f37fa860d2e273dd7bd9611d4 -dd31984af777963aa15930c7314976954469626da3366e40ec30f00d665d99f6 -dd380080964c6d7e1f3dde0c9b9def4a91a0da911f3367267003c42952ccefed -dd5172780d4072aae735664aa9b6d6a6ebbf82e59b91ceb1a943a882a8d8fe20 -dd5b4bcbbbcaa65b85b5515ebbc0ee87c27b594e336f7065c77e96e136937561 -dd61de4204bd439fafc062db69c9e94079c6a30d8179b3da7ce122874a70d7b6 -dd639d9ade96bcd60424e79b9a1d7af5165ac09642b1705192bd8a3d8ee8a632 -dd7e2be93b804cb1d891eced45546632f520b0e3d37ef266194270f9d4b4643e -dd84004272631073994bdcde773fa6d200cca0720a85a22759163bda2d0f7908 -dd908787bcab9b4a155f79970959916216769769382b69a33f918a8a8770de96 -dd94e05068fe2530371911fd2d633c9805316dbf9b94b99c6f257b532426f9ee -dd998e006a2a76f9622c911d10a44391e9fd08f0923f202d146109ef8d00d5ad -dda15a181ce161e2e923dd706a4b76790c8c3226c0de6c9868ae7bca093f224b -dda46ad882c13f84a537ead2b824c8fc87b893f53db74c84ca55da1af251efd4 -ddaeba022e20bf6af720f6477fb520435c26400b55a6095ebcd60ec4edc8e9ff -ddb3d25da60487836ddcecd6249784cba660cc1f16cc123016eef350f1a28954 -ddb8c386d2bf789ddfa92b0e102893851b2761ae8734581e0726b47fe083554b -ddbc75bd97f27befd8a22fa1a4110ff41e9aff867fa41fc6adc246460ed1e5f7 -ddbd4ce0261538d5228bdfd160e6c236c5b85031c3e19d12b94d9a94c34d0767 -ddc6311ba2a6232c23ea24c286eee63025342bcb123d573198c2155ecc495c43 -ddcffae90fe35e5d99374f492274cc9f5330f8aeaca0637e51eb1577820e49d3 -ddd85d1978f7a85c38a0a7fb7d2318cda9d3185f5dc4620ad52efd35b3dfe490 -ddd91dbe5df2ec1b6eb9ab528590a81c9a164f5f0e28cf720aa6c232c255c647 -dddb0839f1c14e53b9f39dc1a54f00de60ad649e5e0da54ae503b144e7dc4077 -dddbc2104a958004d0e3f9d3a6574343aa578234be45c9428d1fc218c9be382c -dddced964d5c9de26e507a20678a70eebb388792bc5c742fc130745074bc481e -ddddb458a1bea6d1232022c70edbeefedce86f92174f9579a2f584b1f608eaba -ddddff24caf45818918a10140f9a0464fc67569bca2ece737d6ea4575f9994ae -ddf97d918fa3db0705e13f21ef1fecbada4984b6b2960093034db4098ba4316f -de070e22f0806e4a9eacd65234ee0ef0d642d2074222b55e34cb8acf771afb92 -de1aec8dddc28221b3cc8e617b41bbfb4fef08e53cfb2a049bd360e2f3de8415 -de2158b4f650078785ce801cd3c99aeac156d1cf735cef644d34dca9231b6e09 -de217606168bdba9a3f42afc4533fbce4020a228cfb5de63d2bb507a53ee863f -de28d3b0fec20c14ae5a29354f461fb56f28bd94bf36002d30f29312b8aed0d2 -de2c599239026038f475559485d273daa8466b7d7a996b3aa0ec91e94d382165 -de2dbe975210c088089d6c09b4d8b9ac1b51df2a8b6123cafce7b5850acfff2d -de314f211837989810c8fc1969e45bbd53e74c6d50b5f4e41904c43e91e42c84 -de328f39b382502e12fe7fca9c0f027edbef49aaa81fb115a56de182f3c91bca -de34d477a41cb4705c4178b5af9ff762c14f8fc3cee6c95154ed28874e9a8ec5 -de3bce9798b5c563aeabf4a7234d4d240ac20382ea166e1e23a59cd1fe3a1864 -de40bd4a88c345ac59092e3dc1321903f7025328b4333046b999dd022c445a8a -de42793ecc0c2f555f856e600653fd8e0b9cc09cbe595c72439290e8a5fa1780 -de477837a230c243eddd83b6f1ab7cadc6021a6952d55bb437708502a32b481b -de4a3081a6416bbea6f2beeae55ed525d13f2288683b4fb5ea34842b67f98c42 -de4a876d12553a06f79730e80574b800f3cb55d304c5ec3fb33a5adbd6d3949f -de4b036278d9912dc5818b41ed17b452d9bce043b4c87c792aee95e27279c591 -de5707f22264e22b47be9050f22e12b29397484cf93145636054b6546c4ca7b6 -de57446fe7c3c6da913c59a9975dfd9cf42368bc0bfbaba67e2b2a3909ad9c42 -de617b2dd98807c227400de1612fb77d0ad7818d6ccf9557d59b34c03e9b5420 -de64417e1a451458322a13f6a4e8c9481a2af2425ce01c149040778a63965976 -de64cbfbd781f8e86487bdfed13f88344ab5fdbb64687139b4d8b644c358a4d2 -de74a1a97dc5be158cc07771997492630fd54afe71cc07f677a2d64dff068e0b -de7701e8f7ee860bc3b2e75b108c355957c04e6351156d7734438862a7838e1e -de7fd3792e53c0d9dc1c6c22f09619edc3c6ffc3de57bcc7bc85490eb85ffacc -de94733d83ab47b933a22d2b51cd5d501c269abd534ee5af3645d3b47924d5cf -de96ef980259e07507e31fe7f24c0b93547ab4a737f321f7c76221eb2f5765cc -de994c81ad2d78150c4ca6f127159fa632b96e020c97dfa9f9929e491bcaf58c -de9e492c814f908ec970b237ccbb59823c83ada4f993ee7970b5b8a191efbbc0 -dea26b7bf413c88c32c21aa1a7e2dad685661edc93ae95892aea6223e92c5392 -dea6a53533517b6fdb87d951fb2d050c08dcac3bc2f58f8d739bd80c110e6435 -deab6369ae4391628b4db12c77f20254b5923312beca87eb39467e5bb5add7bd -deb6b2f8c88ae09617befe798843f94fc4e00fcc8d37c41825ae8d743012748b -deba3f68c234ac2f5eede972d8f50559378497b07b5bc98054e454f33d66c90a -debcd045e689fcbe3a4072de7e68466ea2748a69ee5128763f1ec8c7ed0c2ead -decb6a5d34c47ffcabdacb63a403a0bbdf99019b689812253919e5eb6cecd310 -ded359b49b3155bf77c5bcfc5497b7930007e1ce07c97b1d7d1ae6bd6825ab96 -ded44d5220954fee3566ddb51c9aeb224b7950b3c938a23ffb1c6902fbb63360 -dedc49dadafaee872402e111af1111dcfba59d679b43495ec0b2c2c50b38240d -dedfcbf44d12c9076ceae99b3f0266ffe308f0159f7cf4c84a9c6a3f53554a56 -dee2c0902f2477afae3af4b28d7eb2ed4a1568bf6be1e359968548876377bbda -dee4017a940c91e3e518f8aa02ba5012397d1eba06481078310bee329fcd5ebd -deecf2d76c40c5fa11631f74da70d0fe13a6e89d8835fecba39d7c77a5fbf18f -def23883e803a9ad61b30f055157801c22af59fbc9d895bc45fb07d8b7a73bfd -def9c696c788d879691f9592ffe6ab73abc7ac0a8b27cc2152295f7ea744bff9 -defd997e9875c7c46ded662aa77b63d815c7f2df91a36d68ea5f73aaabac6bab -df091a9ee5149b36cd1824116aeec427f6a652310ae42c0b98c94afbb7ced885 -df093975148ea4a263754e7d9ee661081de760c293cf0e07069ea199a139fb69 -df209d538e603fb88c6a48fb1460755dd3c604734e82e0b6490d2d4cb74f9f44 -df220c13333c38fd31cd9818328a8f8b3ecd9054f3d83f8fc049082ea8995fce -df25a773de4bbc5eedd20da8a683c11398c9a5c054d0d77b6202afd90fd2dbc3 -df32b4eea5962c3cb78ccf128a664d93c6f4f42ea42d965bd6aed46861152155 -df3877705d62bdf23e7538d5f9f86728f65768f4db9b496b491611306d0ee064 -df419779a6a351888c6f2b7dd164fd92c7b6b115adf084b7559087f49beda84b -df419e0a0548213c392dab092039f77fef6919299e3dbb5ca4fbcfde3cbbe109 -df491ec381aa8d4a21ee38e205c529ca237566212645bd90ef0866967cfcc976 -df5e286071e6fe2d25c9a26e7115461c7476dc34bd00a274c7dc95c964a1ce73 -df5f62c45d47db14284dc7ae2026cff567b3cc205f69c6550a89959a310dba3c -df6037ca36b4a20dc29e1449e0b1087e6e2f20abf2e56f6ef60ffc4038bbaf0a -df659f37348e19524cbde49f888bc123b34369724da50449c7b8cc8d7babd1c1 -df665e586a34b2b38775f5608da4fb450d568e0251b28900083e220df869fbb0 -df6c3753df2fecd5c920345d9fe1e8d17cd933972c2912e2daf71d76d21dcd4b -df6c634d86a6767bb603b99008eda3cfdd6eeae31f61f8bd733a96ceb30e2de2 -df70c99f25aad679725885402a35d431b72ffbcbff48199628ea665aa0258292 -df748aa395af972289b73a9be51d469431732c6b65d6241ee001b5218f238bdc -df7c3ad45f6df85a7f7b4d022cba7ab4bb65b583369cfd0c9ba304bc3ded0a19 -df871a595dfd256e2c349392e91acae9f415ca1f42ff1c77a2578fe62b634d24 -df891d2f66e48ed62d60c4b8c0fa3c087115d1f4b21858879096fd3b67caeda4 -df8c852f13df22a5fd00e53e28be9403a4574a705c1ff9b70dac5c2bd72c0d3c -df9092f42928fd125198503947d083d98bd965eb141592531891a96de4e2ec16 -df95688c1359556439961acbfedf32253c52818083b4c6d55416b4a16f3b44d4 -dfa1eab93e901da052953c4bfe73187f91e80d2199e83d6b6cf281c11fc62b29 -dfa6d91f313107bf23a4b70b8ccaf8df5b96f114e4efb0a1ec80ccf72e8b8fa8 -dfb3e964865d77f7ef3f83cbb513d28c23313e4705226d52dfb813104639c0b0 -dfc61e49ebb902c2f06f9cc3e76c44bc42a77be8b68666d90949560cca8e153e -dfc8fbf28f9e113fd8fbbf51f9a73ea7e3742655d0d227edc8222c487a50cdab -dfc9a0f0191f0c77c6c832548f87732f0a8bdcfe68a6601654c12ea688eeb0cc -dfd188aa6f6dd56aa7122d76585ca80293ed1ed732acdab0114e650e1e90506e -dfd28d28b21269e5b0b74d685ce2b5a717acc38297cd921bd93687678a8768f5 -dfd7d2388588884ad49a1b64e92d6e76ab1ebec2396061ff766756da32e69edf -dfd7fd3f94cd2c1fffddc6350fd557167dedd83494459f0820bb46c1f0539583 -dfdf1ec8d69071051305176c7e335a7460222b0bbba28a98f82a79837bd75b52 -dfe9bac7ef8c74ae6f38f569c6bc73e2109dbc9e1f7de203f7f36e2b9ca7ea54 -dfecc1d0ef1bb9052cc8d5a60f41e9ebc7a9957050c19be797c8d83736bdf449 -e00c53b7216c45c360db532124f7bf27577e204d13e04ea0207ba82145a4fb9e -e00ef970264f871a11c5e26cf51e5823fcead76811589b4585e625c7b7c1ee0a -e010f37c71f53ce41bad01c0aba0977008a2f9dc884e58742bd70c7f9e8617ef -e0190cde98023f7930417635446ec91f745d56c9f8adab137dab1be46bb7b8b2 -e01941ade3eb9f5bd5ce3e0872e4567294c72fa798610d85a62837bbf22d476d -e019e1c664d8f3018851dbc3dbd7571509a38a4e28a685b9d833441f3ead39f9 -e021c77dcd8bfc03bf2bc484f89944f456a1bf681890329aba5a3f0d04ab7eb9 -e02501e472c6f8f752b573eaf32ba7920cd72672b2267cfea6beebec9402615b -e027cd17bd57784c1856c5ce8e6d8428bd83471ee352ffa727a1a06c4c48400a -e02f749a8d85532caa55d94dd21d7c0b4deaef5c64974cebb53b0022ac490f13 -e035915ce906679129bbe005cff41134078f722691827d37f6e8f253938d00b1 -e039e6f518cf9b994d8721129b303e8958ca43ec4b4d9945d4daf39519360842 -e03e6824a73e092906ec14060e0d03c8e79c86e5a8180f1a154bce105a1e764a -e043a3eab461faf592eeb3cfdc39a489f14b9709d4102541b1d8d17938060a6f -e043b219c20780daca129d6efcc0162e4d50ca6dc759b5dcdc2a0cc418ccbc09 -e046c1a04b37cd9838100a3a9fd16a484242ba8bca5465a24714619a40b489ce -e04e5e8bb2c10a956dfe9aad3ab2630ac513ce6d277f72a3bb03820beb788bdb -e06ca4b85e83d5fed31df0cdcefec7137d6f1066b8ad36450ab5b6c9ad0c482c -e06d8c1b44e68aa63282daf40de8328ebba47f421ebd2a951bbea4f53f4c347f -e070069a4f9ad9cc18e77cd4dd99fd462b50a5c0c088b8a35997a0a4c6a9efa8 -e070dfb966d6a2c3bad0a83db1815400fa9a8f2eba0ffc23813c4292ae9c686d -e0819e2c5294e5f4ea80fb5f879293748806d76a24efe20fc0ea67a6c09cd0f5 -e086abe5b2b50917680e6666752648b53dd8e157a45be67cf5fe15eddcef3dd4 -e0882ee2fb9f31cebf41c7c756d0055ebdf660e38abb3cb30bdf265c6cbc156b -e08c7b013f007b41635b368a252b2b62692cfe00fa9859689e882c86948ba0d4 -e099d24fe6c2c0aa4184492bfc1313b62b2cd901fccb50124627e82ea85670ee -e09f0b7b39d8c4aba8efbc566ad30ca476bd61d1250c556c4b7d33fb93941fce -e0a4e11c840d2f0d7ec499a369744406a79d80f852cf79e80ec7c02a63b666df -e0af5a885e922bdacaa906baca218eceb8246b245a214372d9cc24a3c3739ad8 -e0b6b8fe5e00c6640ec52ca0caf4acf7a724e54d77397b23d78006269916f6e0 -e0cf1842e831fe26a6f03755d5e075cd38689646633ff60b99b3fca7f2299a6b -e0d038d9699ba76886be0d154644452dc1576dabb30a6e6ff6db477fda309e0b -e0d073cda5ac898a85c100a02aee2d2ebc1240c4daf98d929a7fe2aac589ec9f -e0d29d9e9b7493784d02508006237868ed52e21f7e11337166d89ead112a20c9 -e0dd090f6ac9462efb68d1dffa06d690e2ca6c5aaf4098294a95dce6375e52a3 -e0dfcb433f8230eb1db73cd6661efee6629e510a50d52be3bad6a649e8588516 -e0ea36f4715cf9ebdb65d00d5b56226d81eb0c30e06c557c87615910d573229b -e0ef539c3af785909d30d57f21cc891f79bf5937e5bae652751a0c9cc6b58b77 -e0efab415a700a40434381e0ebcff336a304745b1b59cc74cd2998ce90de2a5c -e0f63a9b23ad3934bc81f45c07ae4413d232f7159989ce9ce4ca0c4f1b3c2050 -e0fc8fcd5b755c2be2e763c15d29033fbbaa79da45824ae0faebe33b9af0d986 -e105055c863edc175843ee53d4d602c5b93152d0bab1fd658bcde55a2f610059 -e11733147f835da05b3861d300326b1d3e605acc6a3bb4eeea1b2a8f38675cf1 -e1275d77ea349718b27e18a9668f5559381b5c84c6f3ebf30b41d59629209e00 -e13d421168e920cddcb40da558d8403711d14c7c6ff93ee605504b8459f31f1b -e151c01853254c63634dd2becfc10168b3b8823777ab1d94e4880e46efcdfcbf -e15ad6ed2f69c2a59a77e8a6fb1696a7fde139e46dd842b64ca2ea3661d94256 -e15aff33108a7ef0dc2068e33a7859093844bacd6161e1ac3a45ce88ecf0dda1 -e15c87429bed591a83589f6ce9b68e931fac1b4e86f8f18f4bf548c79d2e7dcf -e15e43187f731caf4e1b9db898acbcfcef62bbc60dfd3059ac6902dd64f89594 -e15e7ae2dd2612139953c9d8a2ea53e6ac5c30d254141d116b5e52b124779216 -e161c33139311557186395d9676fd8e30d08f383a1b519769e2de9256920bc22 -e17366cabe496f3af9aa97726c92fff1ce05fc50f2006bbdd8323c1c343fdb52 -e17451f6ffea720d081d0b37bad2e8ee39b5d3aad2bad0301938bf352c8f31dd -e18c1f39b0c307c89230b9a79803f8c9abf5a2e61234af6ca38136a41ea7a8ad -e194d806e9af60da93cb3c975f86e0502b3a4613ec944884befef6a1657ff542 -e197d2d94db45503da05df2b90d482fdc754037810179718cfd674127561d0f8 -e1bdcaaf46314c5b2ce0c66eced44490b2edbdc46f80b7ea959c8f81b29d6428 -e1c0f2dc9933dc13091062494589da17f9c7c56d7c25c55c8fc2ea50af185d88 -e1c75e02001d4ee8bb09f0690795e42f01d27289560347bb8c618fbe107c5dd5 -e1c7d40c7993a1345f244e2b620daf7669830a242cc21a24ee15ca350502ed50 -e1c9e316d107fc7d3f4760eb063043c8c68065ebfc8abb985ab36e7898079bde -e1cdc7e0431d04f2acf0331aaada877337d180c8414bac8104c5cd346272e37e -e1ce5be3a72eecfa11f80065c7eced6c07291d7570e87a3e07088427925d1d54 -e1d81498edcacbf4c1d55e81e7bb88f837860e97f26d7618d638e890417b6d22 -e1e02215184d60290ae603baec2d0b0d286db28c8c75728762304a27dbc4a69d -e1e115e49bfbb633a33914699968391478e05cf81009bf099c990f14b0f7229c -e1e3f5c042a6339e4d437ff2436f2ba58df725c178558cac17c1d980eda67012 -e1e46ada33a8f3846751d1f006a60247adc46ecafd1a07bfd85d591258811f75 -e1f7502930bd999c5317f3d46eb41e178abe0db0f03a93845452cf2d68b3906d -e1fabd853e186046dff70bec69c604054f729432dbce74bf84aba1da2cba0a0f -e20c4d6bda60036b2546f453740dbeb6601e5a325389dde90277f0c2c8e4235e -e214f30ae6de0e651186692b2d083913a4311dcf6453730e972da322192464d7 -e21bc60e55dad2537aa5d9fccc576e77e3a5df0b68e9d9586e638ab58edf9396 -e21f1f432e224919fee8e6d8d38465dcd13950264795333ae2a8500b4820af81 -e222b6c2e22847c09d7c4940ec33f27544be7752cb046e4a81af75a97e81e42a -e22ed323d82327d55a75e7e4ede74d397ac0f652a7e652b1a5de8a94332af424 -e2326209a7b573877f82555ee53a889e6cc1b656e9f7d03616903ede551d2873 -e23d43af67521c837b9f1d74a733908bb817120d87546f67f570075fd2101f1d -e255fd2cde8b80d13d904cdb869d871325842b1be8c0401bc2b109b472675178 -e257a59aefb47451d779ffaf07d91744d7b807c10196cc122c6c54f231ae7fec -e2631911c9b039ade3760d25736ae3066b7bea891740fa68637a8818ecbfb75b -e26b9b1608ec6c2c693abbf931ab0ed4c2750dddd84becb47ab4ce1b969dda08 -e26d2c889cd81a9f69869afbc11ab8dba2246f18193d169c79ca5c38669c2b3b -e27cc7ebcdba15396fb4d2f1c6da3af9a5549bc9e65506ee9d6003848fdfa39f -e2809371de02cc90f4e22b2979dd66e6a738c16c004d784804dc0c027c0bf2a1 -e28a4a26033bcbc1d400940bfa76b108a161218797a8a1fcf88f314f61e668af -e28c8979784d5794bb46dc0c432460ffa1e74e8a2749c99242e4301a115273f0 -e292969e7aa860ed132c7269766acb9446529c68b89ccb684e2f9e3d4c38f2fe -e295a815838234fe183a87ced71554d1d1eaa24e27a42044f396545e23a73c76 -e29b15935fc33cbe00f6e5a057f48ff0a30f165f33f5c489dd7e7f3a05da4216 -e2a1525222bd89d979f78befe8d9c6089ae34e9cad4016110b13d6a3c5d7e5fb -e2a678086ac415db97491afda650e4de98760261bb8a146d23c672ebca1d092a -e2a817a53321777df1591a5198ca55c95087f87bc537ab947c7c1fec0d321e89 -e2a9a68c8f8c8f8a79da4c6883423b0276770bd43d7cf966100124f6f2dd8481 -e2baec9651ba2a98c3700d2e9ad45e885233608fb2b9784d1864f4ed24250642 -e2bc90d82aff164dd54445c8125aaaba57c0b879a3e4e13a45cd148f4acca564 -e2bead567029da25a6732289c347ea394d2d80d7fa91207501e7626b725fe7fb -e2bf9feb17fe784f91ae8fed66cf98746976c3902d6acba86c1264f59331df05 -e2c19bf8ea19eb0e7fd930f8898251c447c96269289c6bcd40b87293b2a509c6 -e2c6a3090eb27cb1c0b9fdcfe56e047d099767fe8255688fad9827f7ca1c3e6d -e2c793e12565f254896881ba04c07602e0b9cb69afc7a08c922d2684837d4b10 -e2d1c1fd0ed438959c12e422dab349e5be19ebcfe2d5656d0cfd58d571a836c9 -e2d42c4b0fc8cbb0a3d2349bb27371a12f7fdf191ca4281ddc83c74bed93b28b -e2dd120e0c2a12f6db71b1b54e2605adab2f4d118d1b5d66de2e452de7277936 -e2f8f82bea6c2def9543f1f561486b1faab90cd2ce9be379fe738acf1997944c -e2fa1640fb18ec42acfaaa0f1b5cc2efd53fcd1cbe79e6dff35b9f4df5f27989 -e2ff98571502bbd532da3f5f1b2804ef0021791064ecdca331d6f2d422e4ca5a -e306b28c80913193179fc80045a1138fd3804434c832265266df12932ba17ac6 -e30e5510c2b98fc91f54d6f0014cb42f04bebc3bb6ce6b9a10d1454923ccc673 -e3182b67ff04141147921d3f2b7aeb20f7b7776815e7176f7178428a06742a6b -e319418849ec3f90ca731bf9b28d641bc14077d7beb5dc84de80a541bb1a9b40 -e31e6111bde13a915cac10ead5782b4c55cbad37fb94d36e2419dd123037add5 -e3526800aeb82ea92e74dd02648200bd3118458771c23bb7cf7441b0607999d3 -e360929f2c54065ac2430e8df8f40b4f581cae1eff463cd0e8d835d22847e50f -e3667acfb491de68594a28f592991af3722d1ad17ff549b5dcdecca1a6c7319f -e36f49efbeb150957bd82bae812346f64aa2a6aae93ca1eebf305af6547d1732 -e372ff16b6dc52846fc7b8b47543461741cbf740b73614fbbc58ef49dc5dafd9 -e3740ed3f54c445d3f3b1bfe1c08dbe15cd5184b5b63c47dcf6d779ea106526f -e375ace8abf795b81134105c4cd6a7227b3d521d44a8d9e090eddbb8cf09b0a1 -e384a3fd861ef6df7713c854750f71a95fa77587902e61e85486791f14c45a9f -e392f43779319d016b408b5908dfec61bced28ba693d280f7fdd43e6f7d495e9 -e3969e3b356867ee54105b3b1290b77a1b341c4929d5df0498861c00f58d6616 -e39d0b294803b2ff8fbf024a6b345b2b75578a221608ad7f9a723c186b3f59e5 -e3a38d12401fa7f18c1cab21487c98cf20a7fdb050cdd74574dc14180e7f6c1b -e3a3a946d42641c1591cf740211891b8f9c0741d2055dbcd67f282a51983b2ec -e3b27fb5252372c57ae4faf6fb84b1cc2a7232ace49f451c6556f15527ae2826 -e3b391c5b38a6ed3e98e1d8ed6c27a8587cf36bbb5c04a6fa57c1d37933295a5 -e3bc324aef62420b8524583a6febe7c2b764709e47f4feec4f05186a486a91e8 -e3beab1aa775fbeeb58e29d87d2d914589cff1e904b62703f55681af605bd78c -e3d01f48e2e136924a71f1d7d4a1b0dcdece3f6049459f96814e87a35e1d56cd -e3db1bb5bb62a8f02f2bb93b756cf11c9885839f3c6a8ac73698d5ad0beb2a71 -e3e850dcd0cd893612c6c91d522b48d394abee3bc6db0919b3a5ef5722f29e85 -e3f1a29b7bade10fed9ef1d847cb5d0693f0cf9245bc092d3e35acfc6bab58eb -e3fd67f91dbdeec42c131d6fc7497afad0889bb96b9df10da91e609b5a627998 -e40626fc4fdbbcd7e95355c8b5374c3459eaccabff6d9f899deb6b9ea7d1ee7b -e4065314baf6af42d2f63cdec4268c6cbfcec96cec3d833cda2c61e57de82e30 -e406b951cd187b17e7d04dd3c4af77f70f45a0e5dd9292b587aab1da67ef35c3 -e40d624b29f56716cec8c9124522bae80a590ecd91fa623751124dead552c4c9 -e4127da4123e615e79bdac5cb0f2bb6e2bec65cd4d070bb51620c4f62c0e31c8 -e41b349a76bd5e0c0a78c5b2e1c4947af77637ac021eda84ed591cc9d08489fe -e41c10b6dabd191683ea42dae2eb327bed664a8b0ae2af07530ed21a804512b8 -e41c9f0acd0f4bd84b24146313f71a5b5e2a7a8205bfdb582d80bfb52f5e978d -e4241cb12c6ecc14e09e74881a71feb1f7ca900ed155296259c120c9bcb0dc32 -e42b66e27a8b05d84ffad1cefc817cab83b6d45ad969a619996ce3a4aee8552f -e432f67c0453c667147b2734f13c45617b484b863ee4548df0ad497b99539a49 -e43af18c43daaa7e51f76f90322173b153fc51c3a4af40cd872c14fe65b5023e -e46afbedefcbcb9194151984efc5061b0eeff72db865ba16254e7710275fa039 -e4707b887b7dbe9e6e757ad8c2295bc76e867b15189f56ad1c69cd4de92d1edc -e4718353705fb132a43704b5bf6fbec76818e1cad94316863fc38e70dd864a23 -e473be706a7c30068a7f76179b30a4267c3f836e755afa72e4d594e5e2f7cab7 -e47d67c65e8144f9f6c7dc25c83aab0ee5831ba8f1e2c968f26bd48a5d17e4af -e484656389ed33975328e27298b2e867ae6d4e31cc7f75b3d2344ab28206b2a6 -e498a40d1321cafbc260c66dc0ac163860a8a8c6e6799655c9e712eb0c9443ae -e498d2cd651888b3b8ce291dbaad3fe273f24f455d55a5499b0087f8e86cdc40 -e49d757cc53cc4401e9a92493eaf3560cc1d95a5566e8c53daa6b8b1917fb5d1 -e4a08024a237974b28bfca940cc9344582e04fba4de9d917f8bbfdb73d801a1c -e4a608d41e7c2def5549545ce7942513e6d8193c46e56d82b170a77cb5d6e793 -e4ab4b79835c24eef46851629305ca2928e45c3de8ba0778515fa7b16582d84d -e4af82449455e27e6cfd8b0a0e257c6aad68d0569c0eff6a25aad9eda50c872e -e4b0437b3a43c91f7a990be649ea73b41fe825d94f39be7a6aa3b0ed152496ad -e4b3a82f37cc23bf6c0f6b182f4a0d79533f96cd39adfec7cc8c281981fe7b3c -e4b778bf5aab9b78565b2fc58f740bfc44c3df980ff96113d51676db6f887c6e -e4c9229dfc69d83151624564f1f1401fa55ab31429b22895a3ae41a587fc511e -e4d826889c302d4cc3ccd3b4e49ec81d4f5712760b1c90d8144eb2857bbaeee1 -e4e24d89208299d7cec446a966f85773d944e9fb438a8b76f5ef60883eb06246 -e4f923f82fe12eaca9be3f69227185df663db6ba454aaf340f038c8c86f3fe6e -e504fc1368d3b771d810c312073c49ebb57379f8399b7dd80a625b78019ae7dc -e508a0de94e7a099d55baba9b02d2355cbf1a72f31c7d44b9f9ca3b2e0615921 -e50e558a8ef3ad7abde198b941e41e5d7043d46451bd4b6cab5c9497a3921979 -e521d94f73777b6ec76ee05b851e820219ad343e74d0f948869d40a153a124c9 -e5275e84b7bd01f6968c3ce232890322345d760292ec79b4dc2b8d30d999a4bc -e53f1e62298ad3e7d2370e855edb5e314445630b1dfbaa4a2b07ec06dc816c61 -e5443923198cef148d11542ee41a260a00a2cac0f3b8e7ca2741bfae687cf683 -e54ccc777f4de11343c0f7425adff4f26bf6db239c05bbdbcb9346667aa3d933 -e55273e63ad7d3c8be8976a940408728f3d6a3b1d3a9e870dbbe9c749cc366c7 -e554dc012b0a02df8e02fa1571200547947be8178a25564118c22db106b7f78c -e559e45795932b4ab634d3f5af65a6d4cfba8c1010cf7ad8275ba40a3d5dc6f5 -e55bc5f6520870ce7a1ef96592d3571bf5e330df3775f24f564b9889e051d476 -e565fc6b5001c8b26d9594fd905ac767a2c9fbb98c6284cee87d42a97c27667a -e56fbb68c08dbb9acaa61d6e83f726865617898ad4bfd5096096692b9510a6f5 -e57f88985d0e558cf1f5c7084dd5fb533c1208d27a9462846ce01d871b6fc40c -e57fad1f3f601dc484bf26c41c6408a5191b8521b0229d2bbb44b44e0a6552c9 -e5ab5a71efc05045e42563d411a5cea64d29c8e30cb03f6492e2db33a6b3ad01 -e5b405094bda608cbf74996b5202afe93fc4a8f60c92d3b931c607b58b85cbe7 -e5b4872602e8c8157584323ba1449467024fc6d840ba40e5d96a1d68255dea51 -e5c0fac6ce6294026cec63a90648cddad8326ab2af75c3316c1f605315e2a107 -e5c2bd4caea2c00064359ddebed3f7bebd69752c8ca0d6f2b30398756afaf53c -e5c619fe607110944c4429a2b9260707ab72fc13afdb9622ee54b281a944ff46 -e5cb42c5497bc75441218d04206f5ddede6549dd62b27b104c71d0073a7454ed -e5dba8c1f10247d742aee85329e24922ab09bbcfe936a4f4d480c43169935cdf -e5e5982cee3afa898a9be431f11bc4d5e2abe92c6435f7d0b7fedc0d6865278f -e5e8db6b13cd031bfce8fa24fdde97ed235d95df56cdb9bb1c6e31c52480bfc0 -e6016a7fa235437b5e53f2fab13083a61a356d670159db3fe768872203ebf410 -e60457a5bc50b7ab8af05f37a0761183e8cbc618d2471f699f797946279347e4 -e608085d71e748e434b66ea5bdfcee2a0e0f66d5c28261e4d2e322f0a0c621f4 -e608b1fccaaa5a00e158ba98ebbb646504bda79d86c28eb79ad6e83e4b854eef -e617a7d0a6083b9eff53219350a849e6b1a1541bd0979eb7de4979b5c85915af -e631893a5df86cdf222521ce5607a5dc3f9ed1113b4a6c3e665995a2e3d4496c -e6329e096ac52e48c6170ef2e31160e29b4dbd13c309adcbac0469ee4d0e6738 -e6335e43d8354c60c87b93b56be2178f29c526504ac48d0c9a742a8febb71bc1 -e63866472d3d223226f6e4e4e75a5796ab8bfba3a6a758e73907ea00d4194936 -e63c41599b310c56d85b5534db852df83d6a83028bf8e5e618215142ef994d10 -e63d1eb4ced02390a68d9eb8d4725bfade04764783b2c027fa4082aa2dfc7c03 -e63f525f2c7a5dbcd0790177df014f95e9b5a648282913636657fe228197f048 -e6447d38fb64a227ccba30310094c4b8954d5bed70d45e3c6a7947ef5d3508f0 -e64d1657d824ded865af12ed8b5d677bfdb0dabd33d89ffffaadda781c00870c -e65170885cd329f9b3c19358f75b719237b60316ba2fe5cc88996a141f4cb5b9 -e653ced61f72c5e155e9c3cfc227647656fff2e68477fab8e0677c5c6ba34761 -e65b05a3bf88a9fce99c73d8aab3a5613d33addb729018736700e98905dc976d -e6698318ed586c5c2ffff25faa5f29b57cd92dddcfb3d6416bdf9c409f8ee5d3 -e66d25de3eaf3f1ec7e5c707d05f68410f687727571a102892d3f40e46cd6aa1 -e679563b1787658cdb458af5e47aba31fa7e52326b291db230e74bb9e24437b9 -e67edb746d4f922f601b959626412bd0edf2a2fe354502a6997d5159e1d23186 -e6863f20d86598cc3fd258e86ca6e7cd938833811c80c614f1822d5f82fab1f6 -e68ff22a1886a6dcb4578c6ce3d2e59e5e21d36fda16eb1e759159724e4370a5 -e691313432ab6d149ac1255d1c37b19d7bdf24e08ab9e38ac86e71ae244b9f0b -e692e4d3d06d8bd2ee7a3c0bdeb6bc0957d0d727b763375db57e12ecd21c3189 -e69b19687e10c442111f6ad0c650c32cbe561d841453c0960a08f566ee7a8430 -e6a06247651fd260ad58a162ce3a4d6932f077307a610da4d82dcea42b7d3dc5 -e6a2312bdce6b381b89a19277800734e0d83927df62be46c967a994f193e3d6e -e6a5c2c0748cf3eaadd6399be6dc43f9a8c9b4717dccda23be73cfc7d22f559e -e6b1b3f98041a1a7498778f4d6a2b1e10c051ba7fd0d78729bc284b7b367a96b -e6b8ad93d19c69aa93ccc5c21474122fecd5d0dd41b57d8773588397f233f825 -e6b8f0d27d61b07037978e4be2dd2f337c369b1b4f1cff30c57d040aa336a25c -e6b9b5dec63c44e523ae2173a7804d138f7866000bfc94f6cadfc8efd117a6a4 -e6bb34bc14635ec08a2f292bb7a9d38c88cbaba52a42b17328ef2ce32dca4530 -e6bb5df4f339a2b9350504b53bfe2080d5dd39e13cb392ddfae032e8024ec9de -e6bbb9e68f49d674fa90e1b05919a8398a9cc37e8fd31815445de3b17f3b0b3d -e6c9285ad3255049efbb296ed38f8c3f024423c5ab7ffba5899ae8c7f18d534b -e6ce8fe4b95748363a50b3cef89aec7d3aa2420b86301479e0783bf5411c0a85 -e6dbc34ba2e0ffc55ceb78fdb783afa13f90196d31157c2fef17d7ef5663dcd4 -e6dd97ab827a4b1a9e005a4696ac7c8c0cdea7c5cf21d746d0a55653f977b284 -e6e69fba90a6835e4eeb33e885d592d3e22c1149aa2cb7da82e5a6f04c8c8bea -e6f6e0a9311985e18b35f6723298b35106752662e972d8cc19dc984d2e7638d1 -e6f84771cdd6cfe8c5388ad731fc9d5a7b850d4eb38b88ad89b30a0d8505529d -e6f8fd083bf3912cec89c84295bd4759235e289864aea3555d0f0831cfa23626 -e701c8f9e4ec7a1f845359636a99fbef89572f19076df717190b8610aefc7dba -e71213343e94fd32972b23c46c84bfec3740ef62b954dc9cc50eae4e4979fb8f -e71432789a9ce7ca0a606ef56491a93569534a551eaf827556fac7e2ca7e0385 -e71a18190f5675f315811a3655fceefb481c792c0327c8494d53226649997e23 -e720adaaa6a6655f44b43ac8e8b80d3c55bae74984231e1fb90559305151a257 -e720f9725e23baaccec27fb6f035ae2f360ad5bdc6655a6c90370319cf1ca15f -e7210c4fe93fa0a033e169995732f7379224aa6852264fb6a5baab3c85e819da -e72e2a5b59a9187152a75d47c7e42a8f3335732e055c396ee90eae741bd30583 -e739f6fbeae22b285484fab7b46b36d3723d7ef12bd498ad72cde3829ab3fa47 -e74a6d56492b7ba54b7e729f658a991450c0a5c4837ae3b79e40d962aa7b2b33 -e75ae0478419a31b43b8fc66a0fef52378e05d9e2e16827e1288177cea81cd10 -e75c798dac695a2428dd61259417a948779205776239f2b0351b69c27f465185 -e763d141d54e619bfbdf50ae1fc720bc9199f175ccc3a090a3e5a84f5b3f4ad8 -e767addeb5637491a60c4a2d9edddd0b74dc0667bcd0296d09a1357cc1c81450 -e7683b05678103111de2248fa1399c9119f70af24b521dc041bb43deaa0d9036 -e76aedb0652c1802455a66b5d34493ed1ef738f5c4ed556658f670d307b9b333 -e775b166bf0d3d47ee6dd5a3a36583a88aa28f0eb22b61c47589e7c015d17c9b -e77f0c5ceb1f5d9dc1427b8d7847f7ef9340076509c5f5ba508e75a86d2ef9bc -e77fc5f62aeb303dd4ccb45ee7556e997f643cc6f7043f806c4c19325223c395 -e7821f7cc97ff6217bee62797d3810c3775f2a2e4007377da4ccd6f6447c4c5e -e7885ff8aaeb1f51571e5abfeb315dec037782fc3928e881e237f5720a5b66d0 -e799ec331fa1f8511cfbd082b6a87f9b9484c28037e533eb3bbdce23b43652b1 -e7a0003b08a091cbbf0429cc1f8038e8d3edecda04e6ad74a0be86665706cff5 -e7a59f32c842fd33b2bc51eaae8d2ad833f5834dbe45175c76fe5fd2b3977a29 -e7a970793bdea42a5f789f18dff1d75a55aa47970155c39db68c4772d2535468 -e7b9a6d8dd35f05dfe7254ae3b7274ae71311f115fc3ca893c43b839a4145233 -e7cab4893a70bcedebfa9abdb62c82c18acc07c10fe46d7f380567df3a3990b2 -e7ce9c59a45e3c8790a019057cb3c30555de803a7595a301e9ceaf1709e9ed0e -e7cfc9a786413a17a9199702b5d83905b996fbd4da4d78d84bbe9301b3d9c7f3 -e7d00802b42a0fb54d47380142d5f869fc7151a0227c6f42c9e93e6428842e36 -e7e45b777de22879b9bdf0315527cf1428fa65d5fc15dc2d1dcc49aecb9c5d7a -e7e66a8f9ef9a05d8a7952c0b1bbff575619c067b4d9837ab5747dc05a52030a -e7e8122337e7b3d2c446e405dcfb482d15c34ec8ee7d85cbcb4bdf2ef1e7e285 -e7e9267cef35aa782324b7891bd0f8eb43e6b0488802573afa7342dcb80f5665 -e7ef9ac7c1ebb4d36d76b7049924efe178eb3c54d6995c4e55d08df0f2bb7e66 -e7f614afa66cac30a22ca555067eac62d393e0ccfa1090979ac126e6dbda851b -e7f98bb2f22b8999d52bfe4be89e147500f804018c336160abad7f135396b54c -e7fcf9d5821eac87b4b8904df761b9b824b3c3ad0e9cf1f77bc07abb4cacf447 -e7fe172e9f148ea185cc04137f91cd93df02f8812ec5cb93a3c0e0d65ef31ee8 -e8001c0ba49d1344096972839e1c10a5943fcc0ea7833bb376d5b928b38246e9 -e80500579af3265067976e56599c99d24d076a2c4395e7e06343e4afe59d0e09 -e80a01dc9e71c563868da37368e0e57d28cc26746f21c8295af414168c565a11 -e814dba7f90f4dd535edf54c9339ca6e6e69ae12c439a6f9c8669a72c8fe8d00 -e82b7606e3b6f32162852e48ad3edced75f2ae103fe0e576b1fbfc95cd6dfcde -e86843f057a097f910e5423737af0fbcc2442323a2b06b00fcde321c67c2ba60 -e86a717bf88bfc9ba4ffb827a66428f8c3ddc6714f8a7ba6adef247cf6e1981f -e86d16444e729a2361423c910263276c8914bd9576ac11cf6ec585cd812ff5d2 -e870d58f098d5781d9442db61ccde8bd224bddc410ac5be3724f7240cfdaddf6 -e8748e211747d613c508b88c1332ed7ce0068b3305f2e911a52d18a7f9e76e26 -e8756a124bb68504a2507e619cc5847882d6a17f487580bede620dde0a7c2cb7 -e889e62107016f59d53d106294084017f3e2789e9cb2f8a7116c70c957ad6d38 -e890828bce95a7a17baef248fbc1b651c78abc14f17227cdb2c53ef5c6ff5a04 -e89795164442ebfff06f3a2441c32f361bc4cf3b371a96fe0fb6cec27d919d91 -e897b493091d515b57776d13995a421df4e39148b3fbf745932ec27519cc3934 -e89c963b8522bbb39b4e49f2f957b8e9cd6ac14d5ba650a21d89fa7a32808ca2 -e8aa5b4fce54313c083bd328b8956008b37652079d8c95439456dd927e6a064c -e8aee0c4c42959c8b2d8970efe19fd25473744ff693b5bc790130feeecb0c640 -e8b8146acd4723a86ddbfa44fbeee6a49e7599b4c12965d26c97a1d7b51a5b37 -e8be5b8923b1b48579b39898207755fdb1deb144060db543c780722d86116371 -e8d0147be0cdbe9af572bfb7af01abdd70f88f94eae7ddf8e674ba4899cfe5f8 -e8da26b79235fc44dde6cfb5eefdda93e03d776c8417f7d92a6b3be03c74679b -e8e0dbccefa29d8337879e7adda0c2013f6e2963432621c4025e7a2d9d32f2f8 -e8e14e93923e96c1ebeda019dba08916f66d722cd7a03ff0b194a3b2bb093ac5 -e8e2b2393a3bce83046dbd6840155c65d2c78e4f746d88f114363c8e0b122704 -e8e558e1b09b3e322a2aa1740c537d7560e4c3cb9a3aadfee5638d07e144f0b4 -e8ef8ac108aaab7ebe6d6b56cf7005b0515acf603d01937edd990ad1ede83927 -e8f9e3b33eb24a9ac245b6281bd17ae9e0aa04963537d87c9cee5a85c37a4edb -e90193c1ff0bf3c80d9b58ae663cb64d9af61b58222b25f3788aef3ef66feb6a -e9040020dfcb61e5c4fb4ff3a409ab189d55d21ed37690b457583ecca8d51558 -e91748ffd0d02b3e106238a0f9788979a4706aaa27500b63b8b190db7873bc6e -e919ef314f1f753bf8004a29ac40652bf0941192fe18e26339c1b3a005ed584e -e91c7d216966ebfdc07b8df46e6c355210240d817c5f9e31ab4e37fd5e8aca7e -e91fc8f720a538d33ed3571e83d8b1ff9a3e42d6498c3e92bbbe7b9e194fbc33 -e92b7178b0cc5350bb7e2aa55a97cbc2484e5489d20a4077313a627bfe47c428 -e9364b30322b3826c668cce54aab9ee8c755e5764d04e00af06653c98e0a784a -e9441ae41fe13e2801063621c34807761d630994899d1dd2d3c1476f3838b8a1 -e963f121a493ae4b7699b15ba8008152e15ff2479947fd2ffd19da6a563bb5b5 -e96a6b96b3f42916526e10c611a87ab4014fa34ee32e5ee7603cea859b5551c0 -e96d38e63f662e88ca3d9a3cc9729bc789a25f4d07e1c480356888d143ae8517 -e96e0100e3a0974fd3b273b0fe58e8ec576ed3110b96a22c2e26c4438f74fc7d -e9768a1c9db24d6ed13684958b7bd81e3f0a948cbedd3ec15b0370a2fa7b0820 -e9842d042d284f171d5253d4fdbe5b6d241791787a02f0d818452b48da48d4ff -e985a64d7448457cf510dcb7c831ae6520a09acc0e8e6a3126d4c5f2a3c11565 -e98ff95732a579608f2dd825c8dded1cf9f276c5eb229662d5b09319dd4de664 -e990ea7414b1fbf6ed8c8c539e3a0e2d9014e6925f91a0c07024e7ced544da79 -e9a632d5a547cee8b2ede38bdaaa649265e28494569bcf16163952eb90964c0c -e9abc388ccd744d4d5cf17e22261c94c7ac0e46946fe52cc0ee44a7a2f12d8aa -e9b5804306f993a9910e5e6140daf3e46b1c71bacd2b274fac8b97c3cc9b288b -e9ba591d9c92c40d0ca8992f58c721d61cc67383940e5bccc6a759c40cd4fc15 -e9ba72b411c7da3cadc07aec039716468eb7692261a193d2c2eb6ab0ad8b4467 -e9c0f9bd3af62f95555aed592d5c187fd3ad7b07b8e96d5fb8be1a6eddf8ee9c -e9c15bd4ce57b98e0cf906315fceada24f017c72bc597969e22021a8401e2827 -e9d157238fabc0cf3b5969fc2554496e6c3816656f0c7ad42146a39c2aa7064c -e9d643556937b21109848b245c2fc7415bcf44ae9a1bccfcb7e8b51b747ee0f3 -e9d8d5e004c3b3c7872727766c785f31be7878343f87536f59b3a39c2f090a88 -e9df3c799a71abb5ed1609a45c1b56f7cad2e5b8588497387b2d8e6686c24458 -e9f1e9e1ee7fc349dbe1d1e18a692065a2fc306f0ed2ee805908cd1a1b6f14ee -e9f221ae53a10d4d2f195305ec1594c49041a33dc8f6c3167c982ff935ce44ce -e9fbbdbcf289d8bd55453365e6b36a5a449bcff8ec8e231c5ac9bce704accd79 -ea067654a270895fb8b2234cc0d54839274ab7ce993ff36b39f813495e575bba -ea0b657bafc14076bc01ef096e56fce73cadbf91e5aba2723c1588a0c439559d -ea0ebb2dfa162ea4008f50c9f4045af4ec86bf7b7013143e173572797555c02c -ea0edb95db903a75c6e77f5b132e10950513be5ba86db1b32bc0cdd75abe6e9f -ea16a4c9b5f8a3261caf6e23b35153bfd713d67ceebad470c2253d0bd4e8d64f -ea223d4af02f87e98d423e506f2502282915d057834e98e3fab820a0dfc4f1f5 -ea22d01ce22410179e0d5c84f0c55f27cb821113885c919432d944f8413bb4fd -ea40d77ac9a491fa14eb122b50166612b4044e0ecceb42a1058fe7ad975d7e9a -ea4161422802deeb8dee411727a0b1f358433f6b88aa31b02b357bc8be8e58c6 -ea43472e164d86342e1274ec3ae5d9beccfc67f616fa7da0d8285fc61a0992ff -ea55fbf3d365507a867285c100381a1682664734b06f0ce3eddcf2aa0ebeb9e1 -ea57e17defeeaedde7922c0b209f65b37241af667c43ee982f1fdfec00d277b0 -ea5a687aeeec7dff94dfef8feaea37be0284deefdc099c5c614bc73023efda61 -ea5ed575fea551c556ee5330f509040a52b4a4ebc5502f0526178d966cfc4ff4 -ea60a72cb152406e8147540809cd34f9a0bfb05c5a428b18d8244a715a0919af -ea68d44872f84b2bc79a48e2ee470ff43ba3869bb2de06553bc4e544e4be36a5 -ea74f47dff7d85d6d40242a07ac834771de28e66b891a34681321f866809f37a -ea7544a21de6bbbaa8b31a0ddae8c68314242140210a19c98ed9aa3ea87626c3 -ea7658c15c4db5b9c5b3106e1daf7bd0b9f544442a9d26765172f3a33744b3c0 -ea7975e3272552c5931dff35b832ed3090f358147d233537550ed2819f181fa3 -ea8095b51e63bab37c96bbaa25ab52cd367c1a797aaf9b95a8323d8e805682b3 -ea8b66a2c3b27d5f5ff7bb73fc67789d6a47dc14d6b61f6dc4ec18484085e45f -ea9658a789e336d286cbdb70c41297bc0b93fe568e61c54066635ec54c963c10 -ea990bf6f6f1665a58601c6ae52cb9ae976e163a08e6f3399c9fd3d412f5133d -ea9a731cdb9fa77cb6642e64f17e567c225f5a5b1f54c922e203350e95b8ede0 -eaaee5c0a5c5bce09d46ec744eb3ab2861abe7f667e72e0056f150464b4e5e8c -eabb9ac58d949568cf4585426f1f15309a49f8cd4d55cd76be8eb46169e57ec2 -eabbb9615b73090e3518d9c83348c95b160bf5ad3534af3465634b3f161fc8cf -eabffb638d85278e98b506d9859d2ebed0ee3f28038910945b34c771d5091fd5 -eac3c790655c02fe30e2792aa6d62eb2ab7b25fc9a5e6a75d0d91b93eed2bbac -eacc973ab852046e40817b1db84770e4a3e8b50b4b8a457d45aec1a3494e7f5c -ead797ccfbb75c7ee2f00cb8a2109cedf05acc96012aedc27f06a78726f26277 -eaebddc3741acf6a95d455c795463b698e02ea165775ae1d425dff5480d918c9 -eaec52bfb8da8e83fb3fa62e579090a90056462e4a1ad11956a9beec5c2b2d36 -eaed2ec1ece16c73495503ecc7b998b7ef910d34490d16c6bf3dfddb0eb15ea9 -eaedd99d714daa85379004c810fa19ec049eb3b73f603b487eccf16c73c55cf6 -eb0060c775386760fc8008cc6d9e44d9c89961b9b38db2b7f84f884e458ccaa8 -eb00de1344646284f076dff2a8a14833e71296373a6f6fa69c1e54c8bcb450e4 -eb11759223d13d742eef2817eb847fbc19dd2c7b42ff6fd0cb747187febca613 -eb1217785690aba35188f032af3421c3674857d0b730f4d17048016cfb942ebe -eb13674c54d2bdd0492b266fe9d57c468c71ee5895f38f73c15b63a6802437ee -eb1a21bba281728ab4bd5abb5b4f3eab6cb691b2e951fa449a196f72e5775502 -eb1d5d8b33a585d59b7f89502765fe4ca4750c9a8f37427bb4e1be9bad977a16 -eb2dbd26343cc5d7f91ab951710aeb7e9133400831475ab77a9adec920bbf67e -eb2ec79838c98d3958e3a5d0d780352a18a43b41d4fe2d60bc3a0f69763a497e -eb4b4d45379862a8af88a1625a10f9dbbb68ef33831354d1fd484fc4f6fab451 -eb4f3c43c034b4052205fc770f0db040f6146e531a6341991200597bb031d530 -eb5999511c370c8a3afc7b35989305e77d5acc5c3da8944011114a762e2a18d3 -eb60ae46b47260bca2e0aa47039e95484c262b028b7066d699174a3ea7a0d2ce -eb62bb6007c7c63c962b0d5a0b0a07f358d9e846b803f26e00290b9c56450da9 -eb62c4d77814b568773b8b4894f9361e02383c86c86406a7326a10b098ee3459 -eb639dd0c0aac077b01dec79b5f4425d9375a8982943d568b035be0b50bd525a -eb69f29d0ad0b7bf0724ab282188c4b42aeb878ad2a7600c2428769f298a59a8 -eb6fbe33ac40c19969f754b7f0b6be4083ede50e87c4b40eef7e75ad6c882a98 -eb8196fa6f1800d0ae2b03a778855d37b66db72cf6f078718cb32aabe1f38654 -eb872cc706a2d9280bd56068883e45594c2f518a872a5d8ebb2ba3cdb70fb9f8 -eb8a509d58505d6720b77f6823defa28a240e471b44f4653214714846856bceb -eb90b4ab505559c2a1751d8beb36dfc41c73c55f4d53752f8b5e89451c918084 -eb95de1c4a36c3b8ccd55f4b3bcec56edcadc50ede52ea6a62bdfc987b822d24 -ebca6d94db09c45d552d3d38f424261eb744054cd5dd001f8573e9187ff5bcb0 -ebd0a0b875561ae8b60dc2e4de44cf041ecda09471675cce6bb2ef4855709b30 -ebd2479ecc29491b5965db91671e889e628b6e0d571a51f4cc7d322094d718b5 -ebd92780b7870e3dee08fa3dd3f481dd5c4e6bf5ae075f74d32a80c5989915e2 -ebdf59aec4ebebe981ec2276ef09cb06c188be838622a57b7e4c65184bbd4b89 -ebe0f7290c5ff1402457beda3b61fd622431f7c41f5336c24337ff56d96c575b -ebea911680a435f6b0df520b4d9306bffa1860aca60b59a61d9dc3ecef8dcce9 -ec00f8110dab263185545606c79904556b56cf89fc659ebe3d7e9451e6808e4c -ec04cc71eb0bdb437fc1e69581637b3be986b33316218c72066de6801af1bc2d -ec0a46d746f6426adfa986becd5a61bfd06ae243073d41c6decebdb9bbb05db8 -ec0c694ff2272514f1a8c34f8166e90f1ce9b0325d21c2e8f73064bc2453f578 -ec0d6ee94b8b1ae99dcc52102927ad90ee2ce56577ae52853e80ac6febdfa5b8 -ec0fe7bf445049857fc98bf2f83f03a64a2bad7696d70d6805ea7dad2fecc614 -ec1210bbf5d135da4ec774949bcf63674157d80dbaee51a6a3d693ad5d0403f4 -ec15c0bee39f0865d6b5ab24608ebc36234b9f7672d23383e16d7485622d19a6 -ec176a91881b3cf650573ce76ca934a5f4ed343589e3538f8c2364676e3c3f8d -ec1b6ad916337b58a4cbc7bbfa6bb89bf980b5c70dce6a76006ea4578fcbdae3 -ec1cc18c5490f4328925b4fa414a44ea68ff9c0d3ca80fd6164416b48fa2fdcc -ec2373390d42fb08d13832816d4c20a3aded42b2464333e30d579cb7c667e58e -ec2a4e602ede703b7afdbd3cfce85ce8c331ef8f3ba34e81e362e6ad6a0b91ab -ec337223526531deffe197c8eb8ffa42cf0b50d6974d1f249a36edb45817542a -ec38a440886aab831ec1403b3b6ec0f84bba5e19146c7d2fcf4f1edcc052455d -ec3e699cebbf71b28c8a0b77f00f6c51721a3c502804969fead7de11dd030dbe -ec51720291552f4d725bb128d96b908e6c7a4b333adc88eac1e3ebd651e5dc67 -ec5c2b30820940cf06845a20f1a37d6900c0be2f6e47fc49b14120b39f636d57 -ec62601a80489a151b41b23302c26cb97e7e72b8e127fc007f1f70ad12a2480a -ec6491ccbbf57168fb34cf16958f5fa0d3fd924f47b4d70a85007d8e19b3f75d -ec66dda5aa72f57ea108f4522ecf2898b0044cc183983b9607005a52b596b3a8 -ec72389397c4a5e3ac828a699b1c8543285e2e1867b9483ba6a5c9734ba5eec5 -ec73ba1f94266cadb19c228d9b537c56b1efd5ed06080bacda5b39b63dfca48d -ec884d1f4032da7d3134c56380b44d4737efe4863b4c7505998508c5781e1b86 -ec8a6a845200313b3d86d6b7428ab3b7e369700789ad254211c548d6c73f4817 -ec8ba244d320eb12c26f200182ee8f139497b02675e9514edca7177a0d21decd -ec8e5bd246f8fcf623584d0c93d12d36c973934b13a26257715153a8134b3411 -ec9333dfce79f757ef4c85a13a45b52cf5cdb7eb585c16c04b8f1e86fb57ca14 -ec990854327d96986bfd212ba29eb932e784de2cf289ddc488e742a0a6f7f274 -ec9edeb7ea8bde0866e6bcb0c8fbcf639b481e21ace2de527275a421d883b9f3 -eca8f516792af653f8c5dd53f12846fb80676db5d975f2fdba7cfb8f3b3c5543 -ecc2881f5d6128c0b7f36895e7d03d3de42046d07dad5928925136f1b0099024 -ecd688231a157add4fcf6deb687add1d2c973ae0117697f0a9054bb6b4b96175 -ecda30135e19624f394d26e8107d792eea32ebb827d1f4d07cbcc4ebc87b34f4 -ecf1ce7eabb28d3d4ba5f0d0c438758cf1fca082b9ccd84df7a939a56b819f46 -ecf53ba340bf0160b4455d496a41aec308c74790a868b7a3089f226270173c90 -ed00a3ca77301127cdd10a03dd0208b3ff4c98b0f64a03555ced00479ff561fc -ed01175420b72d79a5929e10be3ed56eb2a26ea2a3167ec87ebce5157451a52e -ed0186178a11f8610bf58436f9a4cea06633234e3086605991a41cf046576363 -ed02f024c8b71336f4da89f0f436d94e7548b7e3ed59bc04e2a1020cdc7d7f5d -ed041675d2ffaa91a994873636fef8c46290ce448e87af745dbecded6de7f059 -ed0a55f5d5dfeb6d74e7ac01b70472edca2dd4d4a92faea197ade9575506c9ca -ed0f07bd4d3585657870bdcf585cb6ba69adfce996f62be0e888ef975519745e -ed12a6de5510a30b984bf7f6439a19adac4b541d1a30ed999a459d0cc0adda36 -ed1391c80679c27f14d7cf9d8a54194f7e48ef0678e1c1bf7089f8f821fefc13 -ed15c80a46a4b250a19826ca599bd33b36b0b2803c988a5ed099e46b1ca5158f -ed25562778ad9d53cf357bb4788d2ea64c0deb54958913f957028131519ffaa1 -ed265dc758e70a2f7eb4a5f9334bbd94e22890cf447b9c3613c05596221250e4 -ed29074fdda2a3d4c515bd490e0da86320d3a6590314c597cc9668275a8e1e53 -ed32eb67763a74db7d3c0456fbf3def860f392d9cbdbbafad95a8c8610b6950d -ed363df98bd32d509db66a98760b53e4f00f95d34e1e1ed459ab38f5beebbb63 -ed40c4fe184339306661f009dbc9027d6b3f117dff16990d503d20c14a688af1 -ed41ea6a32268fd9444d0624563359684a75350fd2daf5dc936c8c85d79051a5 -ed4262a85c69fc23d23eaf1131115cb925d9a6d6af4d52a31fc53c17a26e116b -ed4f055ea7f0949d0ae2555005a29902ecb2da57cffa9d230351408c548f1054 -ed5931bd81d4c694f16b3cc5b0126f4a21f9700882d56ee07ced44ce829399e6 -ed5f772487747fc5a4599be4540bdc66b9fa14cbda8f6ee23a9c2b49cc5c4014 -ed81c043e432b654904c1214bad8c5e13ee2091acd5e30762a32c9d732d640d5 -ed84932aa7ef218841414c86f115f11294f4d655f9d530cca561b13b392f0c7b -ed86d5dfe95e7260b06be4f60ec895e320f51b813e2d819ef2e09c6edf92b864 -ed8dbccbc63b89422c79e0ae172f0f3082f701eb67d285d1b7d4a12c88d83157 -ed91922645ac4a68668a6af5e4f1b8e46d316bca92e8b235990766344119f798 -ed91d15c55992636440628e02f4d2e9715f85c4b82bdf3084e912fb7ae7a8fa8 -ed932306247ca5fd7020bbd4a91dbbb82c27d7f177b1da3776095ddeacd1e53a -eda0d4f05dc397b26b456008733b3612f1baec3804b40526e7913b89a14581a8 -eda5fc73d9ec51bb42700c4e09d2277cc926f250616f9b7c0b6147e8d24cc5ca -eda91483f686b1fbee2806f3840a52e9acdec0a05827a769d3623536232ed432 -edb055a0fd667b87ef8f0bfd9a2f05989c243371ef550b2e852a3701f1d31fd5 -edb32f72c162133a2028d09d6da48c8139321f106fd00c450d71b28dd27f7089 -edb582fa7c973bc917f663fcea42b7fbc69770c40882c98e393f35112f208b53 -edcc103050b769c79dc20852568d96658fef5c0ab2080bb6c7874fe2828b6c3c -edd1363ee95377a86444147c1143ef277bfed61f8f48b3eb47d13d085792b4b8 -edd1c9356ebfb88f96b8badc1513f84873c615efc259a75b85983dbe2859d2c1 -eddca62dc58197a55bcbc9f7bd2a5898c36d32e059717da452241effea8185d1 -ede7cf1646ab2bcf4d862a0d6b9067490d827bb4dea7959bf6d79c465fbb8fbb -edec6b7f80ad374572de3a68147b11bd16be345fe074acce9c6af0365c2d68aa -edefa616c4e7596b649b30c32adde9e3542bbf54177586d51f7ee93ec56f796e -edf480b3491fa38dcec6f4b3a9ceb00d1a1b0b5ac0fbb8baa68413a61ba4cba5 -edf8235d4e80728bd16af95540902b1a9da66b5b2612be8b1377ca937b8c6cd5 -ee0018fafaad4421f964f1f6a771cd70055d7a864a649f81a7241dd13e01c413 -ee05cb22c66028de3c22a61bfa3066ae01d40718959b25562c65407261cfa598 -ee0bd36a6b663f9ce9b4bb6f78a58b8dc3378dbe6fe934693c4cdcd40125b24f -ee0dd8c890236df8081ec54d92704cd34f58a646fd2f5dbaabdff79c9a4bb8e7 -ee1044297784ed2fe053f3ea9241238c53b817f266dceb0159d4516a433eba94 -ee11d81ec21d93b76c7be4458335e2f4d9af9dc5c258ae945f2b0c277c80e8f0 -ee1339242485609b1bce2e5eefc04fcb6b9419e14e67b022c0785e073fab1e7b -ee1348efbda4b677454b971c03a51315f0f309971a5b464eff2d51ca65530a09 -ee1a4712ecbbdda59c5366d12b33fe4eb31fc57b0575741df9432bca38a0032a -ee1d6bfb981ba80af5ccb57ea71f31c0629c17eed6bdf60e0c6bd741c3de6f88 -ee1e607196b26bea9684a7efa9902085bcd97ba77e8536798fa369ee1b00408b -ee2bcd20e17eddf611b90e431721f9e38260963b3cdf5e2121cb20befa55dea3 -ee2f4a27cf3fb879aa19e4b0b091591b3ada03111efe31aa90df140a778f711a -ee45084afb98f6be31c64833c70f8361d68df389ab812e43f56f6b8ebb658402 -ee4649bbdf35bdb9c45c3f0c2844e5c29fb635c417f3f3384ab7c3ac64b2d503 -ee47c22b78182a5e15630e6451a63ebfd313222edcb688608cb3a630150356c1 -ee506eb25b4d823f75f013e82ce828da6a13f1f1eb620d922039fbebb418b851 -ee55ebccc472c190ed504e3f5d13d8a180fc2d7a3c3a22556c1cf57eae9de8b3 -ee583131e5ecb2002fa2af93aa6efae28937364010d38bffa1c47cfabef1aec9 -ee60fe9bfe66d72f681635c89a83ab4fbe4c5f09ed8e348ece0b799785372b33 -ee6c4e5f653608cc77abd8b8a9e786b55870a78d9f55094f745bc54d6aad10b1 -ee6ff1cf7e72ae218685803ee7c4880373f879325dc94aae71631e6f2a5bcbf1 -ee7d5179b5972f619828b70b6833d8ab4dc6919ae86632c76a979baf249ac0a5 -ee887099318569afb2b0cfc23f87968d0b685b17b11b6d52f8272af1f8641709 -ee933b1ef217833b57b5e3ba70b9cb3189f65fa5efb074f8746fa293f2104997 -ee9572890cc4673854cf8ce11ae0e5ea91cbeb4d235558fc0eb9c654841c2fa9 -eea7ee1bb76db52e69c02afd67d34ae59fb49a25121177d00ebbe44f4e0036ad -eeb2ac57d6bf49aae2074118f9bceb313b511ec24dd3a7a98a5b6f2a5b3af542 -eeb79373c37abfef14a762b0e25704709936972c228aa7b6b56b812dcbd040c5 -eed8cecb9617865f2c62c3c2f04162aedab3f3384b49ca390ff22efdcb1e7c36 -eedf38eb8ee7fcd5a8d4d6cd4703d4e72b00e87d783f847a1e79ada614d6fd03 -eef34237b79ba44a224deef61dc7870ca3f9f575aa17ef1c12c6b492962966f8 -eef5b74398c1df09f8c5ceb7f9b4e0453e41438ea5bc26b12e0933c4fa68e93e -eefee27866ef499a424ea0a9195320ad4f92352a36750ec352cc44d0f20fd71d -ef03bc54591b29010ff282cc886a6437f32ad19db33265dd15481028e95645f6 -ef13eb52baa66e8ad271fceb24597d50b64ba90dccf4e64d6da223b048e6c603 -ef2e0171587cd669b4beae4e48b44651aa03e2a1eeff34c9a710c16cbaabe356 -ef37596aef1fc36d90982fcba5b7619bb4b813ba8e912a12d7213a0f36762cdb -ef416f48e049fd6f3ef893dc4e8e65b9a04a3ddd610110e05eac2af685a3f004 -ef5cae572c5f87978a3e1b80b566c80d448e76ff08e3e51c706e7e3c5d9ceea3 -ef5d599c6c0219b3d234141c11c9f9a94edf766f66c60aa5d92dc7c43fb3808d -ef6877f6e77feccade4fd89efa0aa78f3fa41069a5298b5c538c4d8416782467 -ef6882e85da4df118e5186ceedf606c51b5109fc5ae77ff092a66ba284f60d6c -ef6b999963fae202eb5713dae536ca4031aba694c08d7192213c41708e1444b6 -ef772cad53f4fddccb2c9e28f73103e0f4d20a3626939a246d983cae836d9de4 -ef8242be22e8dd8b23797146d2e429e1f4c908bf163100fd27edf035e6e709a1 -ef85951160ae3d364cf3fd56b3eaab57a01985eb88a1ad6d34f06426dae5f681 -ef868cf5049c1b70df8cd59d06ae45de19d050d80626c0870027185ea60cb2fa -ef8c2b26ec7026da096bc15b0997f0d53c777711acf06684f136b0c85cdc6dab -efbdc3bca47895ade9ad71aea30b6767bfcdb5af09287f49e5800d8b11105338 -efbf4112a164336e00c6a6a6eacb339512df1245b8efd01528b9b0f107aeafa3 -efc30f56595c4c7d23cb5c7298d03bd34fc1295fc8a9d2a38e9cc93721738e52 -efc609aa68b3307555d82d1a9fb20a2e287f86f742791843fa461dc86a1ca4ec -efce449c943d68679020efd4f61db1ddd7afa24dfeb3d88146600b910c6d8664 -efd2c553a5182ca05b98b6135778ae9a3c5409fa69f430974afb931535927d28 -efd7c19eded4cd39c2a7a3240897ccbabc05565fb90427818b22ebb7bfe61866 -efe7aacee3de1a2a503664a4436f25be0a55fcbc3f99375e024a96f0527c4658 -effa581c20fbc614a4e4ce923f6f8fbbf9dc0e1d0ad722ba2a301f611d14b12d -efff37e9582dc818c678d01535f46c1519cdd88cde87549335a39b7864ac5a30 -f0019505e3d00e709c0e1f75e913dc38efb2ee87437dd99ad8ca1dafe0f5f4f0 -f002459850f75572d208458582809675094dd373bba64871f18b8168dd07152f -f00a221483c7afdd90419ccfd99c436e1e0c0cf3847d6f8b9cdd6f0f12c06157 -f0103983944e3617c08c9bc4287ecbc42831d66aa21bee297ea8e307e3cf4dad -f0149781fbe19495a8d19f74ce94a3a00b39f7c627b8ea80bf1d0b6f9e20b619 -f01b9ee5ae4c6cd0d786b4a1a9bfbef972466713fc243b906ffc12198810ecd2 -f02fe60fe1b46f4bf42688b05f9060b6cec7eeb667bec15f0551f7da3112aef6 -f030ff658ed95afdba4d6ee5fc5b2592ce1ee1eadfb16e87432fb6fea88347e1 -f04b3752ded6ccbe00ed57298b8d4b7180053a20a3daf7643b05da6344c577ca -f04cfbc126ce9463e80478268b97463f940203e9db34cf114515fd490452eaf8 -f051d3b98861345109806d19cfe3a364dbca165a41984f7026ce81a83091ed37 -f0555ed14abe3e020feb621316774ff4d5d8a7abe0f69291ab94206d1f3404a8 -f057b62dcac8b2c1cb9b28c025b7ac66792ce4d751ef2b16ea905cc1c06573c0 -f0591ad40a1ed57b63a6eaeacbf0dfbde6c8f656b6632b8fdafa093a369383f3 -f08280733b4d314f0da2f585cc212960f1b99c08120fdebccd387f98f486810f -f087fec64c7915b5eb2e4c74f010131b2a901db1924d521c54eabb83d5aa3015 -f089add8c256858166b8d54f5d6365cc6937589b42956de50c06d2d2b3b9eb19 -f089bd164eca8392c64ee2077fcc44df0423b8b686fbd8d969d30ae3bec7c507 -f092d96bdab98b800f0b5cf066a450a5d8105092c231ebb8ee6eacf708183ab6 -f09702a17d835c9c6186337c9da32d46cecb169adbfe32593d178b003291ebc5 -f097b7bdb330bcf5ecabadccb20f924badf4460800699c07594cf673e9c63f0f -f09e1edb4e4a7146f529ce7f284b794f7d5cc125946c76e1ef45d7ca55773d3a -f0ad7386df6b42bc0b5107084941b8d8a6e12faae62fcbddff365a509d289117 -f0ae64c358574f55a16fd6a6a733f887c6142f2cdf044e1649b15087bd87918f -f0b9d4aa6a0af768d915491267d255604044f058e5f347b5519bb74e2d5fdf0f -f0c30a3940ef93b3872df3521734dc1fcb4a9498dad72f02c2b12c1491a5b512 -f0cd3c561e4f37a7d9009fa215d8f236b76f6bb35b776dc22831e7c88e5a0a7c -f0cf1dbd898166e4f7aa729d037d7b4649940bac9ad0525d5470ae67d2b849d1 -f0d437a8d7753935b740e7d7a5d3e5338c919034dd3a1c0805130fbcd38fc24f -f0e0dd447097e81a8630a542510e0e1fcbc1a4e9bd9654bf69ddc88757c2f6b9 -f0ecef0b68842b90cd0151eec8b0c5dccf9b72c33eba0909503869de33166af8 -f0f1ddc77c17ca8851999a6043ea3b1a22fbc9bdde14d848c9403b6c0b95e7a6 -f0f202d025ba75e26b03b39d64360ee3ea3b7c85ca62e4d0cf4dae8d59732696 -f0f78b5c44e7a79b1549bbf05c1e8eafca57a4f0ad724a374d27f1af068d38d2 -f0fc89a5bf851906955a97882481e345ab1f62f52856b96e6a571f9a0a083582 -f0fe5b2ff448f35b8c79df5dec9549054071fee815b619c02d7c595afe03f5ed -f0fffac4c009e61079e5b0a5d95960090d1350f86287829cc4330c8fe5de2319 -f10144a9a42a35262bcd4e3064047be6a8635283b473600d6577c08b7290d1bb -f1093b178f71e8beae8b0f3314791260ddae5a869aaeb74e5a3d3155162dac5c -f10abe9599dc387693fc1afcd50e347840085d2840cb0a6f5333030256626bec -f10ae0d4d83018722b875949b654802cc90991ba237a2316c8441606adb2d646 -f12fe448626badc46c47fa2c06665ba5063e10fc8eb5082571ab554b7d7a7e90 -f146c046fa31dee8f7b8e630a13705b9670924a9b6711230a9de44628b3e8f4d -f14a31afc625c9223a65076dffd0554382577fb46bd1bdc93a4ce05de1d8f47d -f14f03e538f8bac90512ba3b917680653bd267ebcd2ecd5f72e0ccd0b49b11d2 -f1528351a702550cde25abe1c303f58e845a4ae3ee71ce40cd3338b43fa6ba0c -f159716991a5022f8b5cd9439c28e1e88e98f2e12c112bd20309f1d5e4bcfeb8 -f15c58a1cad5f6c12f30b56857e940bc1e79c2478a24326a82e1466972addcb5 -f15dce853ae0c3c21b48518f33dc90beb5c9f5f3b1ff06dbe1833b24c1541b7b -f16864ea868ce5a9d7936dd7a07b7d9a1d96e9df6e55afc6b5d8f8e25dc13f6a -f16fd5271fc39bc86fc3e763961933f6250a5f1dfc8ab7b5816dfe635e679de2 -f17ee9f1d34c2e342c477658285d14a8f19b58108617ebe7793728e964cd18a6 -f188d0d736418b1e36a52d6a445f6f6008d32dd2fa9e08f6f394ce7a03520661 -f1a241378a919bf9c15d3a443a036e0b68caa531115c53b40e8ad0d578bba3ab -f1a26bf68e8b412e7c8d7bb7c17305bc39398e6f8c2a6ff8c16254ed6af1a552 -f1a5ac360cc944603097449b61d770d55c01f9d3ee450104557c5594921ccc97 -f1a8641fcbcfe88e81b6c632c68a42aef8adde12164ed082b29369edc61e12b6 -f1c000cba6d25204705bf2d53b70a5be4d2b3a83f332e23e43590b89f13322cc -f1c6326e9fa4a387a224a2d0b5108cc8973883fe0a87392597626853d138e5dc -f1c9d6924ed05e56e8024c64b6fcbb6fffbf562118ee925e6423fddb36528da4 -f1e0cc415b4f01f1a56a883224ec44ebf137f7dcb930a75eac17fe47089ebf36 -f1e68e04a4c58ecccb253a31f7f7cdf9172c4b9df8c57239240a5cbf1d590a99 -f1ec36d999a80e66a1570cbb6c0153ed62f43d8bef32e46a513dde5d1da3ca00 -f1f8f981d765afea950bae5fb3f997d0f5fcdee9a8a965fa89886454683b93e8 -f200a5a6cb7452263341cb84213e00830a65f97f0daebc820f3414eb6509d852 -f2063dd66c634a10ea920eb3f593cba2365f50604e09b16c494f58fe5e511b67 -f21480e2fbcbc1cc881623763dd7886787e8675fe1e6d67317e083642cabea68 -f21ee32bfb5dba35f081b65a2c44f849fe603bacebf8b51e5e2cf1ea63c2a333 -f22399807a87aa72b907cb1f5bcee49b52b2467baa58b5772f645903cba378dd -f22c6a23ce90dbe90d1f2cc42a4e8db82f6ba90d9bdc3d0fbcbba3e960305323 -f23115acacbafe8965c90ed7575095bf8b13864690f944e5170c5671e8359234 -f23de5859fadef8c3efbed5acffec782d120117728b3d8ad9b6726cffe695c3f -f23e21950e241cca17d9fbd13e207863169762a096601e6c41f30686ccb8450e -f23f9c006633b59f383b927bdbbaba1a33ce8de3464480ff8be8690a2108ed8c -f23fd858c1748330c2d9fe117ffedaf005090a8a1d21565106b3e0376de0e402 -f2450e9c97db34cad6e342db1779b3b3d6e4585c6aa35390855847719e632501 -f247a6fbacec2dda2a6fa869ad45b39565fd63726d0bb41961a2e31c3214db43 -f248c35bb77a1642ed91938051848453f9605027d7934c804a19d290ea5cef9f -f25a329ec2fe819fd0f7ee004d5de7f52511c4cea3bc1ea6d951d20da8090050 -f25f20551758f4b98ca979783e440ae6ea9d95b026d187692cd853449e50094b -f26122aa16ed27d50204501069e6413d9b86f6eb668d43d86913671ad358f7fb -f277b4571969b5c2432ba1f9ac92b4830cf744e58b796d10d867d67ec2609b8d -f290efe4f6fd011a5598cb9650d6ee91bfedb15ce7edb4b7fe9b5fce98af3552 -f2924516b80faea98dc72d6ecee9514758c8dfbb32329eab4518a015cddf4aeb -f296075a94fabe06c404309730e88245c66166d265db70e40568d30dd81c70e0 -f2a8c99c7be1531df210aa5c7acb98d33b31f0948aa389a5e4d4120f0015ecb3 -f2aac7cfdb6cc99676bc383b0ffa8f6bef7412c430e9990d977f451057fc7c5b -f2abc196fb8379b792095d5283326d943d9e173889a8f712c087be70b612a9ab -f2ad01019f45e1e5ea7979c93bd4c8c92b45e661d786a7f0877ea17dbfe3aa4a -f2b0cc76d4825701f975adb3f8db2026aa7ff98a12c3318aefe7105728764fcd -f2b55966f5a130acb8bff4c971bff4f06ef282a76328f4072a9cebd4c76bea25 -f2b67eacc62440408f6dbff59585a18b88266c338a1e125861890ee09f43a8f3 -f2b8c6364c3c060cd23112e284cdd09c86e576d6f3648216da31e09a81f38237 -f2bacc46fc8373b08e19f042c8e5864f980624347c069d28f375b23077e9e3e6 -f2bc42503e6a737b808f833f7bb407e62eb7942d78898a1dcfc770e5ab1b65ca -f2be65640e0e133ddf2b4dbfed2ba8b730fdff8f477311fcdb0e405472c0dd2a -f2cce301aec9bc18080eb59213137da56917240e3112887cc8df57ca66b02d1b -f2d539c7ea497e235bd717b612a995fde3b0cecfc3a3ef4ea96ffb0114589206 -f2d83fe0d18fb819a250565ea7a485d665132c9fc845417a9d53f7b8c18d7eeb -f2d91f3ecc057b958282582500a7fc49421b21cd91d142b633c248e816fa4a04 -f2dd513799eebd4df328e10cc88e1e93e4dbd44f2450a1c83b8c9bcb562d90da -f2ddb181b3217d8aaa44ab8ac19fea86ed047fcb456f9560b9d754bbbf055532 -f2e6269c470e0d7eeb600e85612a94cd9a9b7149e5c8889dfe4ad195b3f7ccb8 -f30153812ddcd7c81e401bccd41409c727d3094339f605aa1c18bd64fcd99d94 -f312f85b1a0eae9a584c200681807a2c767d83b151a124cde14ccf015d621da0 -f31aeb2252d7308752391ea37c206ae13a7b3f9400a7bbce2b2a8dcf3b9be3e2 -f320e6aa921c22ff1f980c59c9946bedd7f8ae9602637b744525d20fb4d35da5 -f32376bb590c45c10fabb75f4144d4b3831101bcfb9eda31f3ea1c708972246f -f332655e779bf4826356d8855fecfd25d6f7e74c75ae138613669bb6555329d5 -f3329dc4a2b78c4a59e22a5e5947331370caffd260f12d6a2ba5db87d841e381 -f3389bcd5327a47657ed19e75bda76fe5ecf89d18c56251e4bcc92b08b2184a2 -f3538e40d35d313472c80807840032fcb30b66548d9ed490961958b81914c2d9 -f365ddacfb516608b38a0ffd6c0aa9a29c7fdc7e84579736f78586cbe3fb1d59 -f36c3164e1740a6ef2b2b474c0a4aeac837f7408078b475e4586305f3cff0ca4 -f374fe0546994a70dfab2acaef1241e2c84382755bc0beb28bb5985ebd094e6e -f37d7dec1312abc84ef12a23dfc6b1a43ae7c0a0f04424e2cc270a03c6e3bf28 -f37fcf98692250c6785113438d944361eea14d393a26130e9d48ced1a655a75b -f384963480ed1bc58820fa24f3533ad8bbe9dfd6ef0e01c9cb21e08e677e1791 -f388cb537dc67cd06eaaa50c2edb7166a358b51ae93510405eb5975cad814c8b -f38aab37397302e45022839443114da8ca509e794a88b470952077ef3ad6b405 -f38f85af9dd964d329305934c8a0d1131a53b8b4af2174b084820a81eb306668 -f39a8d0c44ddec55cd7c50a6e09d7a3fc1b447896f994c9283519a9eb6f86d72 -f39be354603f92c6b0af71db62a6e7baa947ff52e7cf3fc5e9bd10e26e766ddd -f3a28f551c78e6d20599d49a3fd28c5da3c8fbe120940e1a2290bd8ac3c0a9c6 -f3a5b3f255dd20b332adb3a6b85f62b0028e59aee5cdb9b0ae817a3c829fb3db -f3b190751cdc73664c7480a413b6e7ece69d56a56d53243c7932b90255aac6d2 -f3b36a4b56b8cc15cccda93c2ab39da760a514bbc78c0f9ff359d6a1940896d3 -f3b93b5fb98fe04e37fd96c45199d4d5ec902a071890df8605d61d5df2445788 -f3b952b4366953db72f794431b3728b56a69b5eceaf1ef651e4c4bd6968c18ec -f3bbeb3049921446f5311c07cd0cdb6c797416616bf5717af6de6c852e64f18d -f3c12207d2e0ed57ba5a01bd8e281570230d0cfab61412bbeb6be831ccfcc139 -f3cc1747b8734a06a0c94ee3b616ee3d4f7a919f566d73c4cfc18b846f01b804 -f3da41cde0a9d37b7c7d3fefa9e75b99bd67aefaf5369a4d12286322721f34eb -f3dd29bb90fcfb591f587f15d97a84a59867e73df1e786e66aba50092a347de2 -f3e9f0ac7ff8107f5c51e0d55087a056e4c8d6a32e1f7986227f9e777ee9bf76 -f3f85baf5559b23d8dce362621385cdb2d182aaf21d613af97df17bd4ac06c28 -f3fadfcb8ca8d3fa6c70e4f0e3c309f7645ee559449b812e110e54f8d0d9278a -f400d83a528bad2db943fa903a4b7bd13914c035a3eeccd113591bf217082b28 -f41a3a57f0535ab51d52776425a81a81d06583e1d297f543d98598e266654262 -f41dc7447c8aea4db5cc37c066d3e3b305db8ca4d0dd3dc40f4362f4e2607e60 -f42ccf62b347a3c11bb13689325d96d1781a2347aaa0411d15887fc1cac5b6b4 -f4340bf14755b34e6ca5b9ebd723f92a6fe2429c4f00dac45054a087478755b9 -f43af061e9963c9f034ce4523de227babe74699973497412e8b4f17455dfb388 -f44c664cf1f5e6053f627e6643620531cb043431eaa34bbc22f9322d9d68b31d -f44d15031c89373d59d82ac61b8bbcb1b4f78476b6bde3755521a12e5ddd0850 -f46330cab9daef8ac8d4f742239998f71feb6b8d0fa06aa23de0f97634178b72 -f4669cbbdf3eb65f6cf77e7270e526d16185eb6365ebf66dec14199bc841b43a -f46db3ed7ab3c11d93720c0999e4c3d66e3850be25cdf1dfc6817d0663a5d3fd -f46f94269f1979535d868dc5061c447ace6a494d9ec83a80a91524f4f72278eb -f471825500e5c9e75a40ad0f3eb3dfcfbfcea0d8d9ba979e72ec144d52a2058b -f47b6b6cf66535ec684254f28fb8912b0b4f5aa2d8b5eeb19ac5c90f22069f8b -f47f9b6528a546da80bef3eccd8dc123291fa6deabdcdabbc127133a9ace9095 -f4869020fffd3eb9cd8e69a82e85eabbf52c97c29acd566e09b37ac784bec62f -f4889c428cefe4a3e0fc54cd34e2ce2f1f9ecb324061a0e4617617ad0926525b -f489206e9c10e96b33c1b5ceede46806c60b76b1aa98c38e9d5916984638d853 -f48ae410f4a29c05332617fba2ad8411ebc615bee759160cd80b1f01bfdc7498 -f490415943d4ea46ac08dcbd5a63acd08e843cd130c60fbe1654759c830e5786 -f49301d93cfd60e0a098b211f0280a0660b1117dedfb7395239f992cd662f74f -f49e8ff00a65f52e463d1775337a4c620d4a725d605dff6048662545d9492cd1 -f4a137426d6143209400c799b3d22895d2b25dfc532d28318e1dcd8c9b24577b -f4a43ded2f986346f96db1f5d45d6f4afe271cf8807f67ff7d2180a41aca4099 -f4a6f283b051bcc823ac6280e868b5443d4493223a8c33fcbf75f66d1ee23b5c -f4b492ac636e66bcd70bd2717c86d5f64fcd2d923d7ec5ce646b8f47a13fd057 -f4ba7e39087bbe6adc263c9edce0b5126427890aea60465e2833bc140bc4e702 -f4c4f4878c73b42b7be9fe2c553a8111b9eed403cf8f349ec016a3b1a8c05a1a -f4cf355cfe7a75b8d03ad9e4ae3b558715e3f9bc352d0dc597b5fa14faa12474 -f4d0e35b37a69cfdbae745317c80c5b7cc833b0fed9f84e785423de7fe667f40 -f4dae1f4b5538719c4495abced0357bc4365abaebec85f26a19ed444d00e5517 -f4dcaf43d3372f21091495a4bd9c26af95b935fe59685fb8bd873109ff01c4f0 -f4e01e956361322ea2cdb53b7e885903ce90d63e6639c7ec09fe3f58daa70e39 -f4e7031c8c33466cc43844d618f73301bb069ad78c2afe384cbbbf1aa847db47 -f4ec13e75a8f33513fb27f64a947ee5ffd0bfcd51b2fdf881cf8e1bbaa7c05a0 -f4ec2ca5934c5a3ecc9b86b8245eee528ce46669739a7696b9f83e2491c5a37e -f4ef2a4c9c422d295f4dc55eaf9c5ba29a37b1a63665fdecf41a5c35164ae4ee -f4f6fbde68081a97f533e65c4f783fd4ca7c226ff8c5aad4541c77d731ffbafe -f4f9b647af0104e4eaeed34a59b05a20cf64bc91c826b8444122fa6d7d812a88 -f507b427fe4f20d8d246333dceb27a5e770e25dd94cac2675cfce918ebb424c3 -f507c53f5273ef7eaf62bfc10a56112333b55812ec0e13141587219aaac473a0 -f50ae9fa3b200e84c4e7002ea52802fbfe74998c36a3a523a36ed3d433686694 -f50ddca26e89c77ad400035eab1910e200ba9487893ce2f50d2f12c942daf87f -f51afbfaf9988edced7a0d47b9a7d32df67ccc62abd21fe197bdcb2c6813aee9 -f51f34849d60b51852bcb72aa6f51b59514176eb389c94756406e22a1a72bd08 -f52c6c1f9f103bd0ee7fc7d9caba7d2c93e2ac4f07a0a6d69a383a343bdf32cb -f53d7b90d97a6394032e44c7916178bd81c5ea0c6656033d5b6b5ea993721ea9 -f5402b9beffa88025ee79be05ef86aafc879b2d13d191df047cfa20292fde408 -f5424ad5f55d549d8e1dae40bf52cced664a1de33f0b141fc30263f28c7c69a7 -f542b3db965bfc2d07af654784982c1476beb499f6b75a6851b18c8d1e278977 -f546f14324efc36bd66e2f1c5b2d5a4cf3afecd96a829d6b1ffd0c4ab3ac309e -f54822fd2db39d9d3d6986eef28cb670b0d7b4ea5905de5d634351b463d276c3 -f54a4199b7de088793fe75fd9cdff73178fc08154139e072cadaf621f6a3af04 -f54de606bdf79bffc83a869239ed36a2916251946c38546f520b89b4189fac97 -f54e53fb7fa1793f311b81e4c71313cd213ed67605ed16fea58688574656af7d -f55dbabc0cc51d4dc128f446fd595e48133b0da5764526753608584a007b7c55 -f55fdeccae919fdea4fdf3bb271b20d780133831c7513074a16718afa1b95c8f -f5680cc0ff2a759f7c8a5bb374b17e384bb125a41df186d1621673cbc1b34031 -f56ddf88b76ec5de4a39c005eb5adf7d17e7bcbbdb987c9e087a6d2dc339198c -f570795f1d5bbe5995a955fe3117817767823d8561c4541394b0e518e9515290 -f577ac76a4b6b0156e07843876b07910982bab07b714949caec629fee34e5bd4 -f5797a4fc375f9e5abaad3a0dc08876c9ae184c8539a01379b1d30ddff9a0908 -f579ddfca9a728a805133195dafd0c716b7e64eddb4c56e257ac079908c626b0 -f58d9aebd101d301f400a14bddb76518e78a6047c868ad807d9ad19b2d0e3716 -f593193b19e389df1e992b1307883657f83cc3dc905b84cf0b4b42378c6c2077 -f59b36b1ea886f1f4512959a49736110aa37decee2d2ec7f28a1eb27e8a51dcc -f59b6f89d26ef3cf6d97bcac5e2294cf35f58b4014e48b662d31c3a7c5e37cde -f59d18872675e2c8abf57c21b50127c0f0a3e6edd8f4382bd8e5616d7cfb0f12 -f5a9ea97988a5037fb79b5ac0a49f2ef36453e59da2195ead1632ddf1626b5be -f5b33b1bd84e7058af036c93b1011d26d2299aa56b9a4cb8dac7eb1c5e34c7c5 -f5b39359a69d3148b514f8184a7f180f984a3d03fc0e446ac223488ef2f8eeaa -f5b8a613d83006fe33ce5cc2ef91870e8207a8f147aa55b971aa6463ff4ae7a0 -f5bea482f876c88bf318c9337811229aff11d75d2bb5e6cfb9e734bd3f1db5af -f5c588e9703f37b5f272e216329af97c251d78d13d3c7676a6a71f6ad78a1edb -f5c6fc9ba43f08cca87259e7a7dc1bce39a9f9ecd955ad7a3cc78e3714d49778 -f5c91c581481f3f53ca7f8987a1a3573e24770bb90483c86ee3fe1710dc5a54e -f5cc69b70eea7c995845a0414032e9917b134b11ac887a0d48298a5fde974957 -f5d1db3ef1ca0910cb468d3fdfb43586fe6f66f9b95ea40aaa0e94b863e2020d -f5dd4bf8ef5fab4c7f25e3359161dbaea342fdfc226d533302fd3065a27cd320 -f5e348425b2ddc52c26094a71dfbec6271c2f499d5f1f7f3151388e85bb06443 -f5f26726afc7fc19f40d38c7e6d9172616196c0401ae15da7d55741dc363f2bd -f602945730369dcce9d235cb56e8dafe74873e897f6b0739f4194a9adcecd8c8 -f6045160429f3b3ebd1fc646d04151d12341751344ef84e388d5b6ef894c82d6 -f60ed1587a857a8eecd493103a20338e0cc6d86d11ac991a3354b746f3ccae31 -f62397aca92872ede1db9c1e3d031f6e858b3400ac5d764744775c5b74ff61ca -f624622d8ba13d9c004afb53c469e8bb81316f0f210e06c1d9bccb7a4b06a521 -f62938bf2d7ebf154f1a06b2fd61a097390faee5f9bccba06a39966d1e08add4 -f6297f79e8ac113b75547628cd1fcb4c48ee3c30ad3b8d464f7a858614ba420f -f6461e948e90c0829e14d93fc247fb979c4a65ea2ede10c178fd13599fec5233 -f64b96540c9c2fdd4841ee4b0a794bdb0a56317d76a96ada71ae0058558a1bef -f652c2057175885d7970fdb76af37e08e4c435f1b43b2f05498ed01444449bd0 -f6549f8c752812db7e8c7b33643b8daec60e708c9da7485b0dbede89194c3675 -f668434a1379364deee0dac2f5a8ce330d80d1488c23c2b4c9a0eeab66b39f3a -f6698cff10f0eb0886f326e3902450d6d4554064a400841f07c2199587f0283d -f66a5f1457df3bfe890ec6a5e2fe5b675059d3fd51970d4fc9104056cd6adfcc -f67dfcce9126ca9be8e36ad020a5913d605987b05083309798a6af3892973a9b -f683662b61384118f64040cfc8e73b083ab2724f1dd23f74a5c9ba6c3e064103 -f68730a26ec0fda6cee1be1628c12d688dd8151be179f2391f59734957022ef5 -f688ce4ccb0d30d81d86b3d8822f68f61436353f6c1571618804620a2368bee8 -f690e579bc21372746d89a01128e150991bcad7e250877011632d02a56cf31dd -f69344ee99f6a13e181f0c4997f2a5102d188b08454c52100f6128c64276657d -f693b07ba5231154865f1d2576bb283ffb25361132867538d3bcd1e7215bf4bf -f697e7686a3f9261af955c3a58b7142471ed90c805413c8f9968b2d7ea9b5abb -f69ef20285c5048eb946032056d7666f18f8253c4a6d27ce101ae81960bcaba2 -f6a8cf514c7905fbf4630fc1df49de7b73d596557ced5d62d4a83bdc97efe2d2 -f6be1c5aaeb8766981128e4bae9790efc23313ce5885ef24af5acff4815c3f4b -f6bfc99c451547b16fc1d97f11b0f1de78d4356a256e6e2f160b6fac9993ae0f -f6c287b1b79350219f23a485f51bfce4a19deb9b0f673ab36552e2cfdd071c5d -f6c5c618e4a99ad78fd5c9916747abe485f96a2a27a6eef3fda47bf7632222d5 -f6c9f23098d0219b3e72d846779339b7d08fa9d42866652562c818ee20623e0b -f6d51d1a6740d635d4ca1a4a808c7691b41110ffa91e87ed973212c674f92d30 -f6d8897a6a2c517ed18bba27bc5447da3c37d108f09a68f36c38e2daeed4d628 -f6e188e8314bca908366a5c2a2ac527485813e5dad0f942094ba14081ec17579 -f6e3a7dfaea1fa722d9a255561fb52c6330c0d386b7f397b86684a2fe55f913f -f6e5921a8a73477f7457caab3ad9f1afebfb7b4ee81fb9cb963af494b1062e9d -f6f3df5059cbd3b308fc859dd9faa802395259f4a79a8e25475cc91e7838c236 -f6f8a69e5e973797f38b5691f9f088256f2543a9fe14acb1f733de1a936fd5de -f70d8d00f95518b2716e6c77c4c9893c8e4a94cdcccfe6c468122cf44f7a43cc -f710a1312c8d8e5676b707c05f869ff352fa0d652316586c43a665ae8d95e8bc -f71872b173763c63c797ceb8698acf0d48d2834b798e6e253618edaef499fbd0 -f73045af3f1f7b973c6e02a6cc25470f6c1ecac75788f4c2fdc047a337b1b36b -f730d24c04ae3fab39e0e7e6849cb10d84430243cd65fe0236707b349a90d261 -f7330df8832f8eca9f65ebe8a3614a4280a66e9af436b5869739f0019ac6981f -f73a3915394bcc9500033739859190fbd98d97042eefecab106b7f739bddaa53 -f73b76a978a3eea1f50bbe252d6f072b0888939e424e42db177709b3b102d644 -f740c0704a3192ad4d825f94f4699bbc8fb6da6472565684df14445ccd996b25 -f744e7f47e9b3fa7925bb64c978425bfde0c588a649969e421884417e32e66ba -f75c9512579c13674235d61c194e3dedba12819c8c3a4c2fbb33e52d45f9484c -f7666deee14b8ac178a659f63fc57a86ec62a86d004129b11d3c6743b0a556e1 -f76e0aa4c982d9255892e2cd84f7d35128c15ea78951fb66fe16f16fdf045705 -f7702e5b15137ede44d411bc27a4f2c009375eef7592ff8fba92cb9877f17704 -f7708651b0fdef4cbca3ea2d7ab54d0f4804b2be949cb350ab4830119204e82c -f7724488fbd424e1e081da22164ddbebc75df9765a556d148110d4a822a36495 -f77836a793916aeea63b8e4c6d0711af5aa3a7413d3d09e4ce8b2f394ffb4beb -f77c538d5f2c5ffad903142aa0a3b7989aecbd7efc9cebc3562c1ccf0bbf819e -f77da16ddcde365bdf6cd87dcb98fcf76a8bcdf6f2ba8b0837d576a544fc852a -f790a4b88bf26cb5d3620f4bb74b777d6badc268a9a89b567243a946bdfbccac -f794ed4d535e22471913abc1d8f4daf780c5bdabd4def86e510a945bc0956976 -f7999d0984d45c4097568570524fa5f527f2b1da67c452577e28de8ec5fb05ed -f79b620afcd78052a80fca9f8a58dc7015ee2cfc1a60d3fc96abcb5e170741b7 -f79deb828a16971cc6942d2d375d24912a48a3ec9acca804b58120e21b25f794 -f7a793ae93e86bda367ef380a525d6c20626563965b7cc11fdff80b860be90d8 -f7b269be12ae10d081a480ec47f2c6543c06fed28840e633a2297fa6e6f56bb3 -f7ba0a19e5f0d3e28198fd268bb48837827c6ce003ed888c03bbdc87bf0e67c1 -f7bf59b409b0756788057de2809d0cc87fe31bfa1fdfdc89b2a8dc5db326b79e -f7c538d348f6933e3de5bd3d6bf1767720da07996c2e9ee69bfd634fc25e951a -f7c746e7da259b409096faa71e346571c79639f8208eb73f1d765aaf210b855e -f7d678d7181921206751cb74e6a4ca95927e74a43ad4f02d29caa55e05f2bb90 -f7dedb9ee717f1742b5435b1f6fefbb4a0153f6e48039149ad8f29f2295122b1 -f7e08ef3bb6a5c44368d6d65475cfdf6b461d66c7ca43219ed18795a0992d757 -f7e39df83e75aded8881095f8e2cd6fb542572bea3d5f87a0f2887364d7b9f70 -f7e7224ea7794f0f3df700479aaabed5ff2f18fd3a09ed70bfa41dfda17c89df -f7ee1ae084b9a665e160b4b1be684969faeca69214513bd33f3bb6036e5861f8 -f7f2d7c3f7e21c24c153c95ad61749f687f1f65ad978762237003bd1a5b9619f -f803f9c6bf80e93ea5e1e7904ef8d61cfa5e5017e04065bc2ef2e01567fc1148 -f81290bc788b17dc6558722ea3df96a0ff93e93914c3a5d58a6827a1012b3482 -f81700e30368e0d5e94f7a8c25baf9e548fed9607644cfaa8c3013dce82fae74 -f81c98afb8f9fef750a9a2de507d4a7b41d548b3ef43d4ed253a41efacac3a35 -f8363eaec5b4d18f0f6a8d2de07b51fb9297fb8b529759c6e33d15274e615ca0 -f83ab32c3a4ed013b0c3242e54ae1ffd21015b96d97a2fdb9882bbe4e19af4a2 -f83caadc1f858c7b8d163bd1f851b393f4fb93f0ca30b4662074e4863bb9a4ac -f840aecb0c41ab7ad5155c96a48c92eeff06986801f77136ff24b92de2780ed1 -f84555115ef0644b7a7ccd0c39aac6e9e9eeec8f9834fc7631ee87788c133e7b -f8496c3a8400754fd5a3384b21c785b376ddaed92513927f74944a7387d34bc6 -f84b928f406e30b1b983295314a7de17caba8441d81bc7f6931005ec030840e6 -f85294d81d99024322744ad44745cd505d87c9c9c6b93a94ed5c0fcd6cab68b7 -f85bb0d402035530d6e45dd9ac25db43aef66c9473ce01a3fdfa0c5bc60aab2d -f86073b0ce6e3b18d5298e7eac1a82657db380c290c0f5fb5a5c775a19da2b71 -f8632cba817eb90196e2d7c75c12411b9d93fda0031eded498627961952931d9 -f86eaa3bbc056603cf1256815207882f01b1a87e71111603c4c3de070ce90282 -f872168a7a5e75719c8194cdd59470e1966c26bcebfc2d103e85c9ddfba27ccb -f880d6122c62b5e1cc290151da99ac193f607bb9c375453dc39aa47ddb82d523 -f881cda39660c642486db7320c228e2b7f5c942a88fecf0c1477eeefe448fbc5 -f88896499c2ab2ba5e638ac80e31131bb1babebf0fa20d76db2db7959b98f21d -f891e7d5fe190be22307783b3c54c2ae407d49a0884931d05f03b8ed1da1d4d6 -f8943a7cc02f0c8eb2930d44148f6bcadabc2ae829eea51862722f59fce25681 -f89b34c585b5a2788413ee88531d6acfed2ef7ed6f611758446862938341a35e -f8ac4f00d0420acd49cb22d5d2d5aca8d0647aab604a67d97f51d85e1a9540fa -f8bb62d63b902688cc2c845009be5cba51e8cb5f3099c8fe1ed7d133971ab185 -f8cd56b02638831219e2a70af7832947f7be61bae3209dd4620448d42af94af8 -f8d29dcc7f01646b87d05c48df522f6ca5b7eb0c677f1a27edd7b74947c5464a -f8d428d71dc81f9f509a004ff64bd0875ec0d3353a5093828f7bf68affcfe335 -f8d80b52ed0a2ddb8346cf3db334bafc5773c3c38ee48ffde8a9b7ad8762c72b -f8da3c63b218ba9ca3ee12da86fa80bab9099604d67ba82d3c4041863dd747dd -f8de9bfaae6ba0faae778468d9ff0f8d2ecec933e914fbb25216fb274e696b82 -f8e205db38a23bd3560893a106a368e068805fbf624abd6aa5abbaf9b03ff51b -f8f62db70eedd64b50c31264165dbd0cedbeafe8db9f6aa2df6a90f3a65990ef -f8f86785e23c7d74f0ac9565d76c9bc2ce5337f1a4c0620ac8f8607926e760a4 -f8fc7027474c1bdedb00d21b9e3e55279b968b1904c6e3918c184baada3cb2e9 -f905b25a1297085ca96f169f7121d4f5e7f80f82927dae766d7b91424c68b46b -f90784acf335acacfc441a9fca445c3e5a9379836c9b2573cd85cab5f90b2d8a -f90f8e7e55704040bf55055f322b929e6eb30978ce509609a3a610237622ac0a -f914c8321d6c94cd51ac3d213834196824e68d1362e741c2b2ff8b9335ff9c3b -f91739871f2140847a54e3945683232623b0fe2823542fd46b1647e8aa246819 -f926aeefd5a0cb342efc9cd729da520bbe0c357fc50f65393e655ebf5d6ba1ba -f9272f201d3cb9458d10c4d5a03a70a0b30509b3126dbb91f93a7c4b043a7fd1 -f93b76b276f8f18da5ad9f2e4eff4458870a86970f187ac247e1da0536312426 -f93b85235c1a57712e502c0b6f1e399c51dd534ce49df52efe2b0564e40a795a -f93c5b024da1a4a1b8843df74c75a39b38ccefc9324ecfb88778f69e5704d451 -f93dc6121641c60395ba83cbe114726860cca73739bbba5c5f2ce23e16c257b0 -f93ec2c88e541eabfc7500cb2a4468ed933f579e14625dbbc8664a1852e5c855 -f941063cbd735c4c397be7adeaf96aa58fa366f38101d14be18b267dcb3636f1 -f94929e43e37d37cf0a0d0d6e8ace9e4482658595306e7425ef096a673080e3b -f94f282dcd091a9a814d4ca4ccdc7e253fdc3aad1cd283dc921b4b005a8992b5 -f950800e908bb7e9d4580a2d3faa4949145c76cc2b73ce78ba2a5efdb030a1aa -f9521a443e08e4401346410625c83af3d853407fa39471505bc2262a85437180 -f958c617310f3d3654fc3755693cb34e2ac18f86d7fd3c58d94d5a54b17999c1 -f95bb46d36d1a535b81b74dc94d8b93ebff48f08127571524c5700d5d1c433a0 -f95eebc64a3ebd4f2a114dcb8d0353f1a8c6c04f169832d671c04acdbb56ebe1 -f95f23561c4fba63d61a3f26e8258a8b0f1b1bc63bfd4a4fbf340d4e7f19ab75 -f960bad7b9e755ddeab184a5f5aca421b2736cce7b7f2aba685715269bd7202a -f9627b10f4527bd30839f823743fe17f91d5b031735be0b1b076257b56c146b1 -f9649ce6cd1a02f70e80d78875fb6dd9b51910ac4eb12d498904cf1b63d2f7bc -f968235cf091b32ded752ae4322d5065a1913953aefb926adb06db2576aaf114 -f9690f7789ff6b4300ff489aab650b212dfd8735b28144b40317c2ed9f40b850 -f97da04dc6d9a319b14303758f22b227e0adaae9995661f18485fab1f12ba009 -f9a3fb4ce1973c8e046741a6dc064ff17cb128ee12f484593d15bde22518c94e -f9a7754f5f1a08bfe58e81cb27ca76a35bf1e603af4a2a8e8ed5cc80d4bdf817 -f9b4263aa509fb741476d78e673a1bc4d62d110cf09076830b46763b5776a972 -f9bbb981ad84017598a57bf27a0538bc4e25e1086acebd8de6f8fda37c260db5 -f9c3b44b6d68fc140720d52632848c8835a7f28bd4cb1221a5b59caad31d8e03 -f9cc05dbe17c2a540faa6f8bd8da73b5b35154cd655a02c576448cbda513558a -f9cc4c41cc6443991e89faece38f266cf0e55d16da8bdbd82c9720259425642b -f9d068214de0f189ab3c314b325514e814468fdc3f81f714929fe2561cdc3636 -f9db7446c2e2ba1ddf85638a2da69e51c5f9bc771317e9b3d865bc01796062fc -f9dd81cee59e7f22085bce8d06e75cdc6377c07d6cbb907d497f9c110938bb1c -f9eaa46c535c492ca185df074d0a37a3a09ed17efd159b347a0b6adc48df1d8f -f9eb345c7c377bd1adcd1f686abe69f721cc7b7073b4efda392c273b10362bba -f9efb407237ccd5b5b21a6e5c35f90414b6e8ea63c112e7e8f6430f7a35e4f8d -f9f83cf2ed9da021a15dbd5532a0b3c0410bda9d12a8d634cda5f900a0d27cf0 -f9f85048d9053a8dc610e300ffe123aea05fdb9a9c7d076af194dbf897275354 -f9fa611d27cbbb71df1e38beca4386c8e3f13488f53dc221ee0c96726d38cef2 -fa02dbc14ab3030c6162b37df132064ce3764d06491c092301e4919d2a81673f -fa15d9783f1f36c70e0394060638ed252f966bc3936f9f41e354ca14f87941de -fa1d96d603af9d4b2a6765801d6121cffb1a0ba8f666e3d102cb57006ce2a16f -fa231960860fb417b1b313d9977a41312482fc22ff2d722ea49e814f50774cdd -fa2521bb22b0d99a7eaaf262f83ab1b14f708ab40cfdc6f728b252546ecf161e -fa2665d9b180ea65ef2e7957def0254dcbd47a3901a43cc37fdae2562b966c5f -fa3c1921d20acb54fdb61c63c9a0180999ccd71e0bb38f4d07a5d40fd90bb802 -fa48c4d0efd796d5d45e13e11235706fe90d79936a1d3ec2b3b4eb85e8141a35 -fa4b407c6a06920011b7be348e28019a0bde8c24a0ffb461624d6bd255efe7f8 -fa514248afd309cc9dad89fbfc1e1ec12cd52a8d59e177435be3f8ff75a87b38 -fa5398527d67b5727a075156f65ac189c2cb25f1d8c7270c51d7cc78ef5a5722 -fa58dda6bd1e27ab928bb66cdc2e1c0a30311283d2b7cb0b455d2b95f704925c -fa6f56248a8253d739b03a9c17d1b9bb6bb53fa3dcce8b5a9a9f8a56df11c561 -fa7b9133f526d39be41324c65990b7c8285869d5a82b516298ac6cf2040ad866 -fa863b16961ab9575e8d2f2b985ba43c7b6e73b6f0f349e4858d0e70be2a3f3b -fa87a2c3d29f5f4c1607c8126e6f1646afe276f543b5ada61bf798fb02af394f -fa8fe0246ac6da2e342fcce3eb79d099412164cc3a2beb2fb231406debf3cc0c -fa90c0c043b27ac2359475f12fb1efceda6c2abf2b3c48e49bdf1fc09908790e -fa91e3eb2a73c2c325cc1ee415e9e54b35674552ce123b62f7936d295d2720d7 -faa5d9c76bab751e33745703280fd5ca1f13d578e99fe7e3d882cb47909b630f -faae3913831eaeab0ac09f2cda51890f6aa28e860d6f2fa8911638b4d05fd316 -fabc492f7e8a9c12d3a7a6b793c9567b44cadadeb499a5cced6d19534dfdc209 -fad3e060886ecf68bdf5868b0135b796b263ff883264461ec21faedb2fe0aa8a -fae418e9b8c1847767e0cb5318fc92d48967d0832a3bc736f5e1827211f540ea -fae79dde2b21ad8a96c0ebe585e1bc1832f6775c5c320b692a53003da141ced8 -faea26e337ac0d46aacecebb607d63e6a78c716be0057f1f2b82f84e83c454ce -faf649cc09b9c048a5ea0f07d4263b8d18fc9758c8a013e279f0e267401ef75d -fb019c07e9c7ca2bba0c9d33f4647fb393b688cdb91790eb28c81a712f800758 -fb04a3aa9e6851bf9af2b4fb8f2cbe9ae620dc5642fad2e90e63987df2af3cda -fb05fa2925727f63f10d6d55b4be452802d575f4d215c67d8368fd52c699d6f2 -fb0b497c168aeabb3749156db67c644743796362d40c95b98dc62978bfa45841 -fb131cf6a69f4e00cbea1a8d4eec1058dcc13c027f2bda5f08daa91e28566d7a -fb1bd9d85401d58abd23c47fe461ad0ac5098fb3dd1a3403a61e4061483bbb87 -fb248fb81ba250bc285b494276fccc264649177ac6ae7fb8a4990dff9176557b -fb26c2531c3deea5f203f584b8c0ec5aeffe4267f355d35434d719a2b9e95ef0 -fb2c0b67b37d07b57c33464833c8ca278f1afad545a922979e9e2209c98d543c -fb3bd7276c539627a5a186a18d5b165af16da5396ba20c673ea6f9bc0be6b2b7 -fb3e62a510b095eb00e69c4eb4f77286553db0eb4e26cf441497e3d1a0e47aeb -fb4868d8ad8e6b3b57bff4f215ee77d8c291228cc2b5b943a2158a088ca4f42d -fb51a6af920e7e57a2f59adfc5f44df27059e36aa673daaa5a1641f1cbdbca7d -fb5e1615d77591d36997e9c2fbb53a1bbe4e21b38fbfcb7c11b8c47233c0bd23 -fb65fce047b666317a2b42794288541665af8f36f603c7595c5943167648fd3e -fb6fe065d7c7a0dabc34ff9077db6725a6f6111446670391b5f4f99c0b0456d9 -fb74d3e66032f969da541522041412f2f78b26263467fc8752a7a5656bd11f80 -fb7ba21470a3d42ff46b3e3c1141a641dee7d45a7721fafc3c996d8b93f474d0 -fb83df3d48197a1f7d532aed222855eb1b2b00ac7f58fb2c23ee952e6b16427a -fb860f89466ffe742029e66f07b433bc6a2dbe3179f7ad9f918d4e36b8dedbec -fb8768b36637f98504faa4fc66d937d882719b6b5cb3f0fb43a1a41955717d26 -fb89cd8059513dc04102344a42ad3a76d4437fc8e3c9865de7e22d95b8937191 -fb90207fd88de379f79a836302b6f11334b58d8fa25e978fccee85073ab321fa -fb91b5d36e022e276c124d6e8ac394ac1dd6c666b3cb64147875926c351a13f9 -fb977ccef0160648cc5d79efba2d4a9c9ff7da8ca3446e9982317e83ea445203 -fb9dc6e56ef97a72d0ebf26b30f2f22b69f9b3af2d8fccc84f33a1e71b961b07 -fb9ed7beacdfa438709692e7185960f98aec296f8dc03a133b20a0628803cb69 -fbaebcb1996316aa17cdeef460fc853d0c79ffd3f1a23cc55c3c9d99c72ac69e -fbaf6b14eacab601dc563d5bd4fea95af360d8fbc4a53ad05ebe326902ebbe86 -fbc7b29300af140d16ad753b3170a3af613a02b05daaebea68612db94939d666 -fbcb794d3e2721b3e1bfadd97a19daa9bf3eeb0edbab37381764ab6468c2cd25 -fbe4b7cc33d5a45139d62c5f269652f553e28d66d377b2c3399b4ccbd12d1e11 -fbe73152c47766a79b2970f43cff060261585c395a36ba21c7a0309c3b7e9498 -fbebf5a994ea0975f1bdf6d6e5d6496e729b0a76967a52c60d22f3a962c15c61 -fbf8402c85c8d7fb45c0b66f5ebb321395beb32c676cb7656b5fab7636966370 -fbf9efc94331f6c996526ad4e7e6f14da20f89360ce3d013864f4385068faa40 -fbfb18a0ee34ee75a4e32933b607ae99faaa5949ad6306b6bac6ebd06765e9a8 -fc07c7ed7943d6ec479e9af81da25b9612a16cf8583b266bb1c294567a3382da -fc0cdfc5f03e78e35b928b04b65b8caec2529508dc9fc602661c9714e4a22dff -fc158b8731151de71e85e0e3670314c7f0a81fda23e7dbf509485158252f8653 -fc1965491f6694ee6bf38d44bf53d713c83a54747de2798e0631608beec9b0c0 -fc19e8df8833a97eb2624c95d9c22aee5f9daf80ca795b8bfd3a0e59cf5b5a14 -fc21f4218cbabd067ad690eca84e1502fc6dec087f51f90b09c9a594d4ee8625 -fc2cee74814e58993d9e80e933d9ebe9a54c9cc9791f7810b0858b073077bb6d -fc44d43577618f26ffe4f1717296957a424c9922d58a50578705b4aba705881c -fc4c82c16e315d1f53255314e43345e20e6e7b15a36730e29563049cf1904bac -fc4cf199a339c51ed9cfdac57797c0f66989cc386336d7fdc3c4664743fbc7bc -fc53fe6edb5bce16c6fc278112081fa938806c1cffd4021150e248c56bb799a2 -fc5dc904a89387aff15f0eb4dc6c99bea1f9c3a2ff64c12ff9d19ade270631bb -fc6281783c80da8fde94a3ba20e519ce94d3a71a290d2860cef7c0f9b8ad4dd4 -fc6fbe2fd509fd4998d5feb3d3d7a2ecbc3ed974929d1719566fb5c9a4895ab3 -fc78c63678da4d99afb63a16a714bf03d17a165d91d50760c4ff9afdc51a3343 -fc7e88c730888ab06ef665b0ac48a920f9e065e081bae88ad9d69d502d7d4fb2 -fc895e9d80055191dd511f6f44dcc57caf6d7a856523d413165862d79d205552 -fc8bd407f1d9fd3a2a75e8bdb672eb3cce1daf18bc4004341b6917f58eb752f6 -fc9204e1548e9def5c4b99dedee75f2769e92353dc30588c2324edb19cc76e16 -fca113bf8258b1499d5d40719cbf4b374739e4355f68bc2cf31bf9cbfee6cd89 -fca9d8a8a011a33b77280df9384e63df2e1b736a6ae769af6206e2076c53380e -fcb01ec83e183fe90c1b628773bec0c8cf23528571a4c5f3bbdd9c640dc5141f -fcbe1a5c36b2ed53110df6819fef6702bfa9a93c2f278001cc7cc6b14a26d3be -fccfdc3ea245adeac43098f9e8ce22b6937ee0233ed03bc31b9f3ac6c42ebde5 -fcda6a99f6e9f4f531b3351cecfb9cda678a07357b31ae57449c8276c1db0237 -fcdd7fbf928a5d88259fd9247909cccc7f017c06a5a5162a50212f69c57f22f7 -fce039f9497e4c6ee9634fd541399bce37826c180efe097dea45ebb8b32309c1 -fce54dcb036c0fd8990eb1b4660d5514ed008b71169ce4eab800b4b5bf1885ab -fce7c5ed6101de757c9b656deafbc97c66028937e526ff3b24d24a380c025632 -fcea912992cd312107f82f395b5f0a9b41890883aeecb7080242d06762be625e -fcf5e21ef4fe4c3f54856f1ff6105a4230a27c5b1ff389fe189881c3fa399151 -fd0712c3a4c1d336a4d372f212b2c901d165978aef415b2e812af46f2244f22d -fd1792a36ed915206ab1b55bfa402ad9fde61826ec74f809821572d883497c72 -fd20648f26e90e2bdc19e0cb8cc6201db1a877a5670376c1ee5756f33a268abc -fd301d1ca2326237474942948e470f97e0d7c91f720c31a15983d1fa4ec331ba -fd30e7d11f1a71e535d1891c27c05060f34b952e3c6dff711959d0fac315d825 -fd39d6a54c124f1356b219702e3841e72b1808c68efa798ebd5109095a1a2e5b -fd4d804bc2a3597a3ac64d5242bdb3bf870dc5eec2e94ef0513a340257d162f9 -fd524b4bac5a91fc9ab13c0b03466b52d4142433328e70456a46a39e5a53d8b7 -fd6507282131af41e0edbf58f941752d8803cce30508cfaaf9d003b091516cf7 -fd66021c1d2a2b88a83faa4711310fc60cc3d79a603a7430f4fa4dd5ee65a573 -fd6c84706e40530cc3f5cea1b0ac602d008be0b605ae54de5072936342927bc7 -fd713df6f7cba1d17a56d272fd817d44fe1bb8972a9728a3c81e5c86a095e391 -fd731681652240b74659e78c081eaddd4ae2cdcd7d7f76203fad74f14c63b825 -fd73c88411e41b0606c9e2fe0b6824b5fa90d17d165ac9aa28351c9c548af642 -fd75cf21ae2169fd7e04c32d539669f4c58dde907517cd0bedb2ae47fd850072 -fd7fb042d93dfd76b77bd113ff6948029a3b7959943d9cdbe9926b1969e7bf68 -fd80001356c6e8f5b66ab587843089092f3c295116302dc34418b132e6108340 -fd84cee9f00fa0e04e70d6da05137c02637d4820d1fd356b71c483ba2c75e885 -fd8db338459fde7474d1bea1e6c5c1a99bf3c91ceda50006a99286f9138a1c2f -fd935ec4ab965c154ba32a72563e0e889f7d91c9211e225d5f235cd816bd9027 -fd9675cb975f6f1e24289eca205402e737f6c76a9ce85f2f478336efb0ccca7d -fd9dd1fe2a6242e2c0203902b796abdf6813432bfcbb9523ea4d3bd0e04e6dce -fda3bb787118f549e3b661c2371533c66f760b9a5b15609efafa5a4bf7de9522 -fdb1ca7680960312e2c4665d59b0f6aac999325ddc2f1c795e700e838974558c -fdb71c35141c34b75e963b8206ac0258e022442dab8341436027971a360aa56d -fdbb84d62f8ef7a469b421c377d25283a5d68586c6f55f025cc629e0346b0bdc -fdbc4349074b4d6290aabdc08ca0bfbdeac55f78dddb047b2bf7d611ac84dded -fdbe890313309ca10f0f096c305a0f75b251fd726e99b7102152733e130e173d -fdca6600e968a669862900fb411903c609d97ba8868388ece1c8cc65bc844b8a -fdcc5f15ffd892f48511d9235d14bb5e06fb98d1b56f0271b2cb27c1e7ae7834 -fdcebfd2c7860564c7ac8015ab344a3d269978b0f9f88980be49650614f61a03 -fdced30e241210a4ac0ebdd1141c5ddc5ab2872a8db9ca8f20b26271940b5357 -fdd829c2129a94db71cc722c30b4864eacf6422d469fd2b0f616c914de86c28c -fdde3fa577a4dd64117b9a00b4e3822b52319d9b502141a3b4a73d232b47c924 -fde38bb02a528adbb55e691e7061ae3de17d86601510508e843277ccab8d05fa -fdf118e06c89f5115870493a7ff16bed46c0379841efc2ab169c02d18e843abb -fdf437b5b68289afc6a2aa8d11fa2cb7f46616514f296be055a85bf6102a7c2d -fe01a1845be03eea687caf37bf7eb9c2e3f56e688330d7ad40c514c5da04f7ff -fe0ddc9f1511445d5a2a1f67dd6a9e38366388a44fb49cb8838f424a65094309 -fe0f97cdf5bf23412338221b5e04648b957cd09e23a9c9c39903fd4eb6e4a750 -fe13121c1f6d5fec94d40d90ff2330833cdb49cd46a2d56273b1ede60d8bab8c -fe1e863ba73bb018e52dab20ddb7f1746c562892dcdcd1d3c4ec73f32021519b -fe22ca177ae8ab2eb6cbdc92cd59160e12c2d0ba9ec0e778dedac4da9785c996 -fe2f4225477134aeb60ed431b068a8c038c2c8e6f4b9800a9d37af060a5d117c -fe356756e3df9e3e1fe19490c7bd43c8584eff7e3fedda722c84196228a04b76 -fe380193776787c71aa799cc2856fbd0d34c73109a8fec64e4db0dde499b6052 -fe430485b2cb91f7f42669a47dfcbb13f61c53be14869cc5c5cca15c175363d4 -fe43f056c091898645d60e475de70d111f8ca733bb6c56ec70aafcbada232327 -fe50fe213f020e9a36f53990ebdfb3717159bef100b742875f3a1f5e18a28216 -fe53316323f7fda27551910df635316db275a137b426a9ed3c44722b9a3cb981 -fe6b634bf0075856f40fb1bd3e55c622d5835ba3d9437f83a5b6fabcc0878b1f -fe70e9523692057162585585afb726769c89a1b2533ec23f073b33f06553d96c -fe7204d27b92bdcc0bd2091dac904e86753544fd8966c33a5a99deab51d20c21 -fe7372a8ba6acd6ac4849e8d8ac5848a7d2e068bc6a1c7b609363f0764bf9559 -fe76538e8a45988cf7cc00a314d0fa39ea6404a37fcd4be2e987e55e646ebaeb -fe7c46c700b8b41ef3f1065ded7af1354415153e46bb4d3d3bf9ae386f69b098 -fe803773bad05fc8dd49bdb23bc4a51a4d0dfe865cb25247d04f920370329685 -fe83a5f65f38725fd6d92cdd2f1a9b7137c23f60ac0dbc1a5565c6bb98dd4a1e -fe8e12c1ba23064a78fefe21d1104161afe9f8303d99be56796226400b181ee2 -fe975c79428d644569fa0a514e65b9c42a5081d77c82ae5295369d89fbaa2fbc -fe9855b6b886273aa76c4184070b6fbed0a666bb79ae4ed5936de573e53aa1d5 -feaa379aa98cee8916f9e8beec2f2f40b01b041f615972229a92e245844377ed -feaa5257e12b1baf3c2ef611d53761f8ca956a528ef02cbf6ac4e40c9e0da0c2 -fead49bd2db2d86a70d8c125a9decc6554dc71a3309f81803f3bf9a3212a10e6 -feb7e61b5e84cc65bf15afdb8c9cdedff9beb3edf575fcdf81e691668a96e0ad -febae1ae5ad42572a2111c6903eecbeeecd1efd331546efb8ca5363e0eaad52d -fecb838d2c1fb5c9a076233d9c996dea42d3f74fade0cde2c2f5ac93f015373d -fed50b028fd82a613564abfa95e811f922fb94cf15208976efc3e39449df1355 -fedef8f1a87c8aa8007c95decdfc47e89525e6a523836ef6d2a91f7f8c3803bd -fee0d8604987f375f982353dcf382b1c5bea85ed02c2909861e1c0e9219d24ca -fee99edac0b595aa9d225646193bb4d4c7f08a187325163f7019da3bf82cdf5d -feeb68da57d6f7072750e5e3b9eab2de287c0a774d48e7e782b59f60f3077795 -fef8f2961d06c98b60cf43a97f86e7a00015acba07c9ac7323ed9c71dcc4c618 -ff1728808947af8b912fc29341632763c7f7c76e2cab717a08cfdf69b987b457 -ff216d99c3da6153c92650574c5c2536714427feb5129cd47c337665aa6e865b -ff25eb85bc7992c6b084082a66b077777be0b4db2e0b0c24fcc9622e78b27604 -ff3ce936f67444ecefa7f9738d041c6e2c240c11feceda0e78eb4da2e9064f51 -ff3e9797b0a74c34ab342afa67467a47abb70972050322f817ca75d98d1ce480 -ff5bfbb55535ec6567689039f75e85c375a83c9a242153e557506a51aa15b705 -ff5dc732102b0b889ced0422b760b3b9fb5263cbb76e5d1fab398a6001b505ca -ff704f919ca3c924ea981bba06fffac56d6fcb3a67323877320004cd4e6b0a14 -ff7b4095488574a6c17f4ef5ffd34e6e6860e32de02d93c3cbb9e5ffee986d75 -ff879c611f6594576032bf36dbaae7d89d5cc1069320ba9cd7bd724b6a6963c4 -ff8b6f47aca21b3861a6f52b737df710ea71e37d7c9c4a68f448ae2bab50f34e -ff930ef507a1e17166e669cc6eed813b7ef4840290d74c94decbe8e42984c0ff -ff9e3b671f9b3b0f158d005999be258d07c49edea03c9b5759013a7f82ff1ba8 -ffa54d64739b1b2abaf3d1489e0fcce10d39478cba4381e1f7a28363fcb79452 -ffaa8704235a2293fa16d49bdc1605156ca304d95a87e7472101cd98577d76c6 -ffab4e408f33e4a265bfb40f18b1f0f0d674db6ac1fd5d6105b8f50a7010ae87 -ffb282d6fa4352a269da0b1bb3e9b7ba1dbcdbcde5fdda9a855f85d3c8a85089 -ffb6f20d288513a0c7aaf1e0a68c4d3deb614e146d22d1a1524867b8cc25cde3 -ffbbbec6f56de422a9a683c73c3adad73a906b1aa57b61d3af2563b2e9b12f4d -ffc64c46ffadb41c4c1f69b704dc9343fb7a73c2015430137dbafd0d6f863855 -ffc9701af58947d292651a3f0c082391941d73e65046880f45c59cd0e4cbf98f -ffce3d71c545484edb9a1156512390aa54e16efff45e0616737c64e4b351d2fb -ffe720fee9d3d016d8f30e2755654b9b62b1b61969f626d8df47608ba523e2f2 -fff0120b04c7a7f0dfc4fb404a2f480cfa70b1ce7fd852a4e7e4fae28811c84c -fffa34d343bd6cee927abee100f61d0a27b8cf32b3e3a6a0834ba2d7d4129c5f -f56599f4353c6f5d4d01cf9a9c2548cc2a70d3684c127962515b681692ab2b3e diff --git a/assets/tron_node_list.yml b/assets/tron_node_list.yml deleted file mode 100644 index 1e34de712..000000000 --- a/assets/tron_node_list.yml +++ /dev/null @@ -1,11 +0,0 @@ -- - uri: tron-rpc.publicnode.com:443 - is_default: false - useSSL: true -- - uri: api.trongrid.io - is_default: true - useSSL: true -- - uri: trx.nownodes.io - useSSL: true \ No newline at end of file diff --git a/assets/wownero_node_list.yml b/assets/wownero_node_list.yml deleted file mode 100644 index a4873b3e4..000000000 --- a/assets/wownero_node_list.yml +++ /dev/null @@ -1,12 +0,0 @@ -- - uri: node3.monerodevs.org:34568 - is_default: true - useSSL: false -- - uri: node2.monerodevs.org:34568 - is_default: false - useSSL: false -- - uri: node.monerodevs.org:34568 - is_default: false - useSSL: false diff --git a/assets/zano_node_list.yml b/assets/zano_node_list.yml deleted file mode 100644 index f7b874fcb..000000000 --- a/assets/zano_node_list.yml +++ /dev/null @@ -1,7 +0,0 @@ -- - uri: 37.27.100.59:10500 - useSSL: false -- - uri: zano.cakewallet.com:11211 - is_default: true - useSSL: false \ No newline at end of file diff --git a/cakewallet.bat b/cakewallet.bat deleted file mode 100644 index 1904c5710..000000000 --- a/cakewallet.bat +++ /dev/null @@ -1,51 +0,0 @@ -@echo off -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron -set cw_root=%cd% -set cw_archive_name=Cake Wallet.zip -set cw_archive_path=%cw_root%\%cw_archive_name% -set secrets_file_path=lib\.secrets.g.dart -set release_dir=build\windows\x64\runner\Release -@REM Path could be different -if [%~1]==[] (set tools_root=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.38.33135\x64\Microsoft.VC143.CRT) else (set tools_root=%1) -@REM Generate android manifest file -cd scripts -bash.exe gen_android_manifest.sh -cd /d %cw_root% -echo === Generating pubspec.yaml === -copy /Y pubspec_description.yaml pubspec.yaml > nul -call flutter pub get > nul -call dart run tool\generate_pubspec.dart -call flutter pub get > nul -call dart run tool\configure.dart %cw_win_app_config% - -IF NOT EXIST "%secrets_file_path%" ( - echo === Generating new secrets file === - call dart run tool\generate_new_secrets.dart -) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===) - -echo === Generating mobx models === -for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron .) do ( - cd %%i - call flutter pub get > nul - call dart run build_runner build --delete-conflicting-outputs > nul - cd /d %cw_root% -) - -echo === Generating localization files === -call dart run tool\generate_localization.dart - -echo === Building the application executable file === -call flutter build windows --dart-define-from-file=env.json --release - -echo === Prepare distribution actions. Copy needed files to the application bundle === -copy /Y "%tools_root%\msvcp140.dll" "%release_dir%\" > nul -copy /Y "%tools_root%\vcruntime140.dll" "%release_dir%\" > nul -copy /Y "%tools_root%\vcruntime140_1.dll" "%release_dir%\" > nul - -echo === Generate the application archive === -xcopy /s /e /v /Y "%release_dir%\*.*" "build\Cake Wallet\" > nul -tar acf "%cw_archive_name%" -C build\ "Cake Wallet" - -echo === Open Explorer with the application archive === -echo Cake Wallet created archive at: %cw_archive_path% -%SystemRoot%\explorer.exe /select, %cw_archive_path% diff --git a/com.cakewallet.CakeWallet.yml b/com.cakewallet.CakeWallet.yml deleted file mode 100644 index 6a19c3dda..000000000 --- a/com.cakewallet.CakeWallet.yml +++ /dev/null @@ -1,33 +0,0 @@ -app-id: com.cakewallet.CakeWallet -runtime: org.freedesktop.Platform -runtime-version: '24.08' -sdk: org.freedesktop.Sdk -command: cake_wallet -separate-locales: false -finish-args: - - --share=ipc - - --socket=fallback-x11 - - --socket=wayland - - --device=dri - - --socket=pulseaudio - - --share=network - - --filesystem=home -modules: - - name: cake_wallet - buildsystem: simple - build-commands: - - "cp -R bundle /app/cake_wallet" - - "chmod +x /app/cake_wallet/cake_wallet" - - "mkdir -p /app/bin" - - "ln -s /app/cake_wallet/cake_wallet /app/bin/cake_wallet" - - "mkdir -p /app/share/icons/hicolor/scalable/apps" - - "cp cakewallet_icon_180.png /app/share/icons/hicolor/scalable/apps/com.cakewallet.CakeWallet.png" - - "mkdir -p /app/share/applications" - - "cp com.cakewallet.CakeWallet.desktop /app/share/applications" - sources: - - type: dir - path: build/linux/x64/release - - type: file - path: assets/images/cakewallet_icon_180.png - - type: file - path: linux/com.cakewallet.CakeWallet.desktop diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh deleted file mode 100755 index a083ec7ff..000000000 --- a/configure_cake_wallet.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -set -x -e -IOS="ios" -ANDROID="android" -MACOS="macos" -LINUX="linux" - -PLATFORMS=($IOS $ANDROID $MACOS $LINUX) -PLATFORM=$1 - -if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then - echo "specify platform: ./configure_cake_wallet.sh ios|android|macos|linux" - exit 1 -fi - -if [ "$PLATFORM" == "$IOS" ]; then - echo "Configuring for iOS" - cd scripts/ios -fi - -if [ "$PLATFORM" == "$MACOS" ]; then - echo "Configuring for macOS" - cd scripts/macos -fi - -if [ "$PLATFORM" == "$ANDROID" ]; then - echo "Configuring for Android" - cd scripts/android -fi - -if [ "$PLATFORM" == "$LINUX" ]; then - echo "Configuring for linux" - cd scripts/linux -fi - -source ./app_env.sh cakewallet -./app_config.sh -cd ../.. && flutter pub get -dart run tool/generate_localization.dart -#./model_generator.sh -#cd macos && pod install diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet_android.sh new file mode 100755 index 000000000..b8aa433de --- /dev/null +++ b/configure_cake_wallet_android.sh @@ -0,0 +1,12 @@ +cd scripts/android +source ./app_env.sh cakewallet +./app_config.sh +cd ../.. && flutter pub get +cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart index 0d985b237..d06ffe402 100644 --- a/cw_bitcoin/lib/address_from_output.dart +++ b/cw_bitcoin/lib/address_from_output.dart @@ -1,38 +1,23 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; +import 'dart:typed_data'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; -String addressFromOutputScript(Script script, BasedUtxoNetwork network) { +String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) { try { - return addressFromScript(script, network).toAddress(network); + return bitcoin.P2PKH( + data: PaymentData(output: script), + network: networkType) + .data + .address!; } catch (_) {} + try { + return bitcoin.P2WPKH( + data: PaymentData(output: script), + network: networkType) + .data + .address!; + } catch(_) {} + return ''; -} - -BitcoinBaseAddress addressFromScript(Script script, - [BasedUtxoNetwork network = BitcoinNetwork.mainnet]) { - final addressType = script.getAddressType(); - if (addressType == null) { - throw ArgumentError("Invalid script"); - } - - switch (addressType) { - case P2pkhAddressType.p2pkh: - return P2pkhAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); - case P2shAddressType.p2pkhInP2sh: - case P2shAddressType.p2pkInP2sh: - return P2shAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); - case SegwitAddresType.p2wpkh: - return P2wpkhAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); - case SegwitAddresType.p2wsh: - return P2wshAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); - case SegwitAddresType.p2tr: - return P2trAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); - } - - throw ArgumentError("Invalid script"); -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/address_to_output_script.dart b/cw_bitcoin/lib/address_to_output_script.dart new file mode 100644 index 000000000..01c7b67a5 --- /dev/null +++ b/cw_bitcoin/lib/address_to_output_script.dart @@ -0,0 +1,29 @@ +import 'dart:typed_data'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bitcoin_flutter/src/utils/constants/op.dart'; +import 'package:bitcoin_flutter/src/utils/script.dart' as bscript; +import 'package:bitcoin_flutter/src/address.dart'; + +Uint8List p2shAddressToOutputScript(String address) { + final decodeBase58 = bs58check.decode(address); + final hash = decodeBase58.sublist(1); + return bscript.compile([OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]); +} + +Uint8List addressToOutputScript( + String address, bitcoin.NetworkType networkType) { + try { + // FIXME: improve validation for p2sh addresses + // 3 for bitcoin + // m for litecoin + if (address.startsWith('3') || address.toLowerCase().startsWith('m')) { + return p2shAddressToOutputScript(address); + } + + return Address.addressToOutputScript(address, networkType); + } catch (err) { + print(err); + return Uint8List(0); + } +} diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 1509f913a..392771ab0 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,165 +1,40 @@ import 'dart:convert'; -import 'package:mobx/mobx.dart'; -import 'package:bitcoin_base/bitcoin_base.dart'; +class BitcoinAddressRecord { + BitcoinAddressRecord(this.address, + {required this.index, this.isHidden = false, bool isUsed = false}) + : _isUsed = isUsed; -abstract class BaseBitcoinAddressRecord { - BaseBitcoinAddressRecord( - this.address, { - required this.index, - this.isHidden = false, - int txCount = 0, - int balance = 0, - String name = '', - bool isUsed = false, - required this.type, - required this.network, - }) : _txCount = txCount, - _balance = balance, - _name = name, - _isUsed = Observable(isUsed); - - @override - bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; - - final String address; - bool isHidden; - final int index; - int _txCount; - int _balance; - String _name; - final Observable _isUsed; - BasedUtxoNetwork? network; - - int get txCount => _txCount; - - String get name => _name; - - int get balance => _balance; - - set txCount(int value) => _txCount = value; - - set balance(int value) => _balance = value; - - bool get isUsed => _isUsed.value; - - void setAsUsed() => _isUsed.value = true; - void setNewName(String label) => _name = label; - - int get hashCode => address.hashCode; - - BitcoinAddressType type; - - String toJSON(); -} - -class BitcoinAddressRecord extends BaseBitcoinAddressRecord { - BitcoinAddressRecord( - super.address, { - required super.index, - super.isHidden = false, - super.txCount = 0, - super.balance = 0, - super.name = '', - super.isUsed = false, - required super.type, - String? scriptHash, - required super.network, - }) : scriptHash = scriptHash ?? - (network != null ? BitcoinAddressUtils.scriptHash(address, network: network) : null); - - factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) { + factory BitcoinAddressRecord.fromJSON(String jsonSource) { final decoded = json.decode(jsonSource) as Map; return BitcoinAddressRecord( decoded['address'] as String, index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool? ?? false, - isUsed: decoded['isUsed'] as bool? ?? false, - txCount: decoded['txCount'] as int? ?? 0, - name: decoded['name'] as String? ?? '', - balance: decoded['balance'] as int? ?? 0, - type: decoded['type'] != null && decoded['type'] != '' - ? BitcoinAddressType.values - .firstWhere((type) => type.toString() == decoded['type'] as String) - : SegwitAddresType.p2wpkh, - scriptHash: decoded['scriptHash'] as String?, - network: network, - ); - } - - String? scriptHash; - - String getScriptHash(BasedUtxoNetwork network) { - if (scriptHash != null) return scriptHash!; - scriptHash = BitcoinAddressUtils.scriptHash(address, network: network); - return scriptHash!; + isUsed: decoded['isUsed'] as bool? ?? false); } @override - String toJSON() => json.encode({ - 'address': address, - 'index': index, - 'isHidden': isHidden, - 'isUsed': isUsed, - 'txCount': txCount, - 'name': name, - 'balance': balance, - 'type': type.toString(), - 'scriptHash': scriptHash, - }); -} + bool operator ==(Object o) => + o is BitcoinAddressRecord && address == o.address; -class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { - BitcoinSilentPaymentAddressRecord( - super.address, { - required super.index, - super.isHidden = false, - super.txCount = 0, - super.balance = 0, - super.name = '', - super.isUsed = false, - required this.silentPaymentTweak, - required super.network, - required super.type, - }) : super(); - - factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource, - {BasedUtxoNetwork? network}) { - final decoded = json.decode(jsonSource) as Map; - - return BitcoinSilentPaymentAddressRecord( - decoded['address'] as String, - index: decoded['index'] as int, - isHidden: decoded['isHidden'] as bool? ?? false, - isUsed: decoded['isUsed'] as bool? ?? false, - txCount: decoded['txCount'] as int? ?? 0, - name: decoded['name'] as String? ?? '', - balance: decoded['balance'] as int? ?? 0, - network: (decoded['network'] as String?) == null - ? network - : BasedUtxoNetwork.fromName(decoded['network'] as String), - silentPaymentTweak: decoded['silent_payment_tweak'] as String?, - type: decoded['type'] != null && decoded['type'] != '' - ? BitcoinAddressType.values - .firstWhere((type) => type.toString() == decoded['type'] as String) - : SilentPaymentsAddresType.p2sp, - ); - } - - final String? silentPaymentTweak; + final String address; + final bool isHidden; + final int index; + bool get isUsed => _isUsed; @override - String toJSON() => json.encode({ + int get hashCode => address.hashCode; + + bool _isUsed; + + void setAsUsed() => _isUsed = true; + + String toJSON() => + json.encode({ 'address': address, 'index': index, 'isHidden': isHidden, - 'isUsed': isUsed, - 'txCount': txCount, - 'name': name, - 'balance': balance, - 'type': type.toString(), - 'network': network?.value, - 'silent_payment_tweak': silentPaymentTweak, - }); + 'isUsed': isUsed}); } diff --git a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart index 7bf488f3f..3e21bae81 100644 --- a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart +++ b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart @@ -1,8 +1,4 @@ class BitcoinCommitTransactionException implements Exception { - String errorMessage; - BitcoinCommitTransactionException(this.errorMessage); - @override - String toString() => errorMessage; -} - + String toString() => 'Transaction commit is failed.'; +} \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart deleted file mode 100644 index c8715b239..000000000 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/hardware/hardware_account_data.dart'; -import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -import 'package:cw_core/utils/print_verbose.dart'; - -class BitcoinHardwareWalletService { - BitcoinHardwareWalletService(this.ledgerConnection); - - final LedgerConnection ledgerConnection; - - Future> getAvailableAccounts( - {int index = 0, int limit = 5}) async { - final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection); - - final masterFp = await bitcoinLedgerApp.getMasterFingerprint(); - - final accounts = []; - final indexRange = List.generate(limit, (i) => i + index); - - for (final i in indexRange) { - final derivationPath = "m/84'/0'/$i'"; - final xpub = - await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath); - Bip32Slip10Secp256k1 hd = - Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); - - final address = generateP2WPKHAddress( - hd: hd, index: 0, network: BitcoinNetwork.mainnet); - - accounts.add(HardwareAccountData( - address: address, - accountIndex: i, - derivationPath: derivationPath, - masterFingerprint: masterFp, - xpub: xpub, - )); - } - - return accounts; - } -} diff --git a/cw_bitcoin/lib/bitcoin_mnemonic.dart b/cw_bitcoin/lib/bitcoin_mnemonic.dart index 21ff3891e..9163fcb11 100644 --- a/cw_bitcoin/lib/bitcoin_mnemonic.dart +++ b/cw_bitcoin/lib/bitcoin_mnemonic.dart @@ -1,14 +1,12 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; - import 'package:crypto/crypto.dart'; +import 'package:unorm_dart/unorm_dart.dart' as unorm; import 'package:cryptography/cryptography.dart' as cryptography; import 'package:cw_core/sec_random_native.dart'; -import 'package:cw_core/utils/text_normalizer.dart'; const segwit = '100'; -const mweb = 'eb'; final wordlist = englishWordlist; double logBase(num x, num base) => log(x) / log(base); @@ -61,7 +59,11 @@ void maskBytes(Uint8List bytes, int bits) { } } -String bufferToBin(Uint8List data) => data.map((e) => e.toRadixString(2).padLeft(8, '0')).join(''); +String bufferToBin(Uint8List data) { + final q1 = data.map((e) => e.toRadixString(2).padLeft(8, '0')); + final q2 = q1.join(''); + return q2; +} String encode(Uint8List data) { final dataBitLen = data.length * 8; @@ -88,7 +90,8 @@ List prefixMatches(String source, List prefixes) { return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList(); } -Future generateElectrumMnemonic({int strength = 264, String prefix = segwit}) async { +Future generateMnemonic( + {int strength = 264, String prefix = segwit}) async { final wordBitlen = logBase(wordlist.length, 2).ceil(); final wordCount = strength / wordBitlen; final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil(); @@ -103,30 +106,22 @@ Future generateElectrumMnemonic({int strength = 264, String prefix = seg return result; } -Future checkIfMnemonicIsElectrum2(String mnemonic) async { - return prefixMatches(mnemonic, [segwit]).first; -} - -Future getMnemonicHash(String mnemonic) async { - final hmacSha512 = Hmac(sha512, utf8.encode('Seed version')); - final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic))); - return digest.toString(); -} - -Future mnemonicToSeedBytes(String mnemonic, - {String prefix = segwit, String passphrase = ''}) async { - final pbkdf2 = - cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512); +Future mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async { + final pbkdf2 = cryptography.Pbkdf2( + macAlgorithm: cryptography.Hmac.sha512(), + iterations: 2048, + bits: 512); final text = normalizeText(mnemonic); - final passphraseBytes = utf8.encode(normalizeText(passphrase)); + // pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce) final key = await pbkdf2.deriveKey( secretKey: cryptography.SecretKey(text.codeUnits), - nonce: [...'electrum'.codeUnits, ...passphraseBytes]); + nonce: 'electrum'.codeUnits); final bytes = await key.extractBytes(); return Uint8List.fromList(bytes); } -bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit, mweb]).any((el) => el); +bool matchesAnyPrefix(String mnemonic) => + prefixMatches(mnemonic, [segwit]).any((el) => el); bool validateMnemonic(String mnemonic, {String prefix = segwit}) { try { @@ -136,6 +131,123 @@ bool validateMnemonic(String mnemonic, {String prefix = segwit}) { } } +final COMBININGCODEPOINTS = combiningcodepoints(); + +List combiningcodepoints() { + final source = '300:34e|350:36f|483:487|591:5bd|5bf|5c1|5c2|5c4|5c5|5c7|610:61a|64b:65f|670|' + + '6d6:6dc|6df:6e4|6e7|6e8|6ea:6ed|711|730:74a|7eb:7f3|816:819|81b:823|825:827|' + + '829:82d|859:85b|8d4:8e1|8e3:8ff|93c|94d|951:954|9bc|9cd|a3c|a4d|abc|acd|b3c|' + + 'b4d|bcd|c4d|c55|c56|cbc|ccd|d4d|dca|e38:e3a|e48:e4b|eb8|eb9|ec8:ecb|f18|f19|' + + 'f35|f37|f39|f71|f72|f74|f7a:f7d|f80|f82:f84|f86|f87|fc6|1037|1039|103a|108d|' + + '135d:135f|1714|1734|17d2|17dd|18a9|1939:193b|1a17|1a18|1a60|1a75:1a7c|1a7f|' + + '1ab0:1abd|1b34|1b44|1b6b:1b73|1baa|1bab|1be6|1bf2|1bf3|1c37|1cd0:1cd2|' + + '1cd4:1ce0|1ce2:1ce8|1ced|1cf4|1cf8|1cf9|1dc0:1df5|1dfb:1dff|20d0:20dc|20e1|' + + '20e5:20f0|2cef:2cf1|2d7f|2de0:2dff|302a:302f|3099|309a|a66f|a674:a67d|a69e|' + + 'a69f|a6f0|a6f1|a806|a8c4|a8e0:a8f1|a92b:a92d|a953|a9b3|a9c0|aab0|aab2:aab4|' + + 'aab7|aab8|aabe|aabf|aac1|aaf6|abed|fb1e|fe20:fe2f|101fd|102e0|10376:1037a|' + + '10a0d|10a0f|10a38:10a3a|10a3f|10ae5|10ae6|11046|1107f|110b9|110ba|11100:11102|' + + '11133|11134|11173|111c0|111ca|11235|11236|112e9|112ea|1133c|1134d|11366:1136c|' + + '11370:11374|11442|11446|114c2|114c3|115bf|115c0|1163f|116b6|116b7|1172b|11c3f|' + + '16af0:16af4|16b30:16b36|1bc9e|1d165:1d169|1d16d:1d172|1d17b:1d182|1d185:1d18b|' + + '1d1aa:1d1ad|1d242:1d244|1e000:1e006|1e008:1e018|1e01b:1e021|1e023|1e024|' + + '1e026:1e02a|1e8d0:1e8d6|1e944:1e94a'; + + return source.split('|').map((e) { + if (e.contains(':')) { + return e.split(':').map((hex) => int.parse(hex, radix: 16)); + } + + return int.parse(e, radix: 16); + }).fold([], (List acc, element) { + if (element is List) { + for (var i = element[0] as int; i <= (element[1] as int); i++) {} + } else if (element is int) { + acc.add(element); + } + + return acc; + }).toList(); +} + +String removeCombiningCharacters(String source) { + return source + .split('') + .where((char) => !COMBININGCODEPOINTS.contains(char.codeUnits.first)) + .join(''); +} + +bool isCJK(String char) { + final n = char.codeUnitAt(0); + + for (var x in CJKINTERVALS) { + final imin = x[0] as num; + final imax = x[1] as num; + + if (n >= imin && n <= imax) return true; + } + + return false; +} + +String removeCJKSpaces(String source) { + final splitted = source.split(''); + final filtered = []; + + for (var i = 0; i < splitted.length; i++) { + final char = splitted[i]; + final isSpace = char.trim() == ''; + final prevIsCJK = i != 0 && isCJK(splitted[i - 1]); + final nextIsCJK = i != splitted.length - 1 && isCJK(splitted[i + 1]); + + if (!(isSpace && prevIsCJK && nextIsCJK)) { + filtered.add(char); + } + } + + return filtered.join(''); +} + +String normalizeText(String source) { + final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase()) + .trim() + .split('/\s+/') + .join(' '); + + return removeCJKSpaces(res); +} + +const CJKINTERVALS = [ + [0x4e00, 0x9fff, 'CJK Unified Ideographs'], + [0x3400, 0x4dbf, 'CJK Unified Ideographs Extension A'], + [0x20000, 0x2a6df, 'CJK Unified Ideographs Extension B'], + [0x2a700, 0x2b73f, 'CJK Unified Ideographs Extension C'], + [0x2b740, 0x2b81f, 'CJK Unified Ideographs Extension D'], + [0xf900, 0xfaff, 'CJK Compatibility Ideographs'], + [0x2f800, 0x2fa1d, 'CJK Compatibility Ideographs Supplement'], + [0x3190, 0x319f, 'Kanbun'], + [0x2e80, 0x2eff, 'CJK Radicals Supplement'], + [0x2f00, 0x2fdf, 'CJK Radicals'], + [0x31c0, 0x31ef, 'CJK Strokes'], + [0x2ff0, 0x2fff, 'Ideographic Description Characters'], + [0xe0100, 0xe01ef, 'Variation Selectors Supplement'], + [0x3100, 0x312f, 'Bopomofo'], + [0x31a0, 0x31bf, 'Bopomofo Extended'], + [0xff00, 0xffef, 'Halfwidth and Fullwidth Forms'], + [0x3040, 0x309f, 'Hiragana'], + [0x30a0, 0x30ff, 'Katakana'], + [0x31f0, 0x31ff, 'Katakana Phonetic Extensions'], + [0x1b000, 0x1b0ff, 'Kana Supplement'], + [0xac00, 0xd7af, 'Hangul Syllables'], + [0x1100, 0x11ff, 'Hangul Jamo'], + [0xa960, 0xa97f, 'Hangul Jamo Extended A'], + [0xd7b0, 0xd7ff, 'Hangul Jamo Extended B'], + [0x3130, 0x318f, 'Hangul Compatibility Jamo'], + [0xa4d0, 0xa4ff, 'Lisu'], + [0x16f00, 0x16f9f, 'Miao'], + [0xa000, 0xa48f, 'Yi Syllables'], + [0xa490, 0xa4cf, 'Yi Radicals'], +]; + final englishWordlist = [ 'abandon', 'ability', @@ -2185,4 +2297,4 @@ final englishWordlist = [ 'zero', 'zone', 'zoo' -]; +]; \ No newline at end of file diff --git a/cw_bitcoin/lib/mnemonic_is_incorrect_exception.dart b/cw_bitcoin/lib/bitcoin_mnemonic_is_incorrect_exception.dart similarity index 50% rename from cw_bitcoin/lib/mnemonic_is_incorrect_exception.dart rename to cw_bitcoin/lib/bitcoin_mnemonic_is_incorrect_exception.dart index 779bd3ea2..8d0583ce5 100644 --- a/cw_bitcoin/lib/mnemonic_is_incorrect_exception.dart +++ b/cw_bitcoin/lib/bitcoin_mnemonic_is_incorrect_exception.dart @@ -3,9 +3,3 @@ class BitcoinMnemonicIsIncorrectException implements Exception { String toString() => 'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; } - -class LitecoinMnemonicIsIncorrectException implements Exception { - @override - String toString() => - 'Litecoin mnemonic has incorrect format. Mnemonic should contain 24 words separated by space.'; -} diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart deleted file mode 100644 index 07083e111..000000000 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_core/receive_page_option.dart'; - -class BitcoinReceivePageOption implements ReceivePageOption { - static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH) (Default)'); - static const p2sh = BitcoinReceivePageOption._('Segwit-Compatible (P2SH)'); - static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)'); - static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)'); - static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)'); - static const mweb = BitcoinReceivePageOption._('MWEB'); - - static const silent_payments = BitcoinReceivePageOption._('Silent Payments'); - - const BitcoinReceivePageOption._(this.value); - - final String value; - - String toString() { - return value; - } - - static const all = [ - BitcoinReceivePageOption.silent_payments, - BitcoinReceivePageOption.p2wpkh, - BitcoinReceivePageOption.p2tr, - BitcoinReceivePageOption.p2wsh, - BitcoinReceivePageOption.p2sh, - BitcoinReceivePageOption.p2pkh - ]; - - static const allLitecoin = [ - BitcoinReceivePageOption.p2wpkh, - BitcoinReceivePageOption.mweb, - ]; - - BitcoinAddressType toType() { - switch (this) { - case BitcoinReceivePageOption.p2tr: - return SegwitAddresType.p2tr; - case BitcoinReceivePageOption.p2wsh: - return SegwitAddresType.p2wsh; - case BitcoinReceivePageOption.p2pkh: - return P2pkhAddressType.p2pkh; - case BitcoinReceivePageOption.p2sh: - return P2shAddressType.p2wpkhInP2sh; - case BitcoinReceivePageOption.silent_payments: - return SilentPaymentsAddresType.p2sp; - case BitcoinReceivePageOption.mweb: - return SegwitAddresType.mweb; - case BitcoinReceivePageOption.p2wpkh: - default: - return SegwitAddresType.p2wpkh; - } - } - - factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) { - switch (type) { - case SegwitAddresType.p2tr: - return BitcoinReceivePageOption.p2tr; - case SegwitAddresType.p2wsh: - return BitcoinReceivePageOption.p2wsh; - case SegwitAddresType.mweb: - return BitcoinReceivePageOption.mweb; - case P2pkhAddressType.p2pkh: - return BitcoinReceivePageOption.p2pkh; - case P2shAddressType.p2wpkhInP2sh: - return BitcoinReceivePageOption.p2sh; - case SilentPaymentsAddresType.p2sp: - return BitcoinReceivePageOption.silent_payments; - case SegwitAddresType.p2wpkh: - default: - return BitcoinReceivePageOption.p2wpkh; - } - } -} diff --git a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart index 7d6894e14..bd8f1763c 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -1,19 +1,10 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; -import 'package:cw_core/unspent_coin_type.dart'; class BitcoinTransactionCredentials { - BitcoinTransactionCredentials( - this.outputs, { - required this.priority, - this.feeRate, - this.coinTypeToSpendFrom = UnspentCoinType.any, - this.payjoinUri, - }); + BitcoinTransactionCredentials(this.outputs, {required this.priority, this.feeRate}); final List outputs; final BitcoinTransactionPriority? priority; final int? feeRate; - final UnspentCoinType coinTypeToSpendFrom; - final String? payjoinUri; } diff --git a/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart b/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart new file mode 100644 index 000000000..fac7e93c4 --- /dev/null +++ b/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart @@ -0,0 +1,4 @@ +class BitcoinTransactionNoInputsException implements Exception { + @override + String toString() => 'Not enough inputs available. Please select more under Coin Control'; +} diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index d1f45a545..10953a2e0 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -4,15 +4,13 @@ class BitcoinTransactionPriority extends TransactionPriority { const BitcoinTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); - static const List all = [fast, medium, slow, custom]; + static const List all = [fast, medium, slow]; static const BitcoinTransactionPriority slow = BitcoinTransactionPriority(title: 'Slow', raw: 0); static const BitcoinTransactionPriority medium = BitcoinTransactionPriority(title: 'Medium', raw: 1); static const BitcoinTransactionPriority fast = BitcoinTransactionPriority(title: 'Fast', raw: 2); - static const BitcoinTransactionPriority custom = - BitcoinTransactionPriority(title: 'Custom', raw: 3); static BitcoinTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -22,8 +20,6 @@ class BitcoinTransactionPriority extends TransactionPriority { return medium; case 2: return fast; - case 3: - return custom; default: throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize'); } @@ -37,16 +33,13 @@ class BitcoinTransactionPriority extends TransactionPriority { switch (this) { case BitcoinTransactionPriority.slow: - label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs'; + label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs'; break; case BitcoinTransactionPriority.medium: label = 'Medium'; // S.current.transaction_priority_medium; break; case BitcoinTransactionPriority.fast: - label = 'Fast'; - break; // S.current.transaction_priority_fast; - case BitcoinTransactionPriority.custom: - label = 'Custom'; + label = 'Fast'; // S.current.transaction_priority_fast; break; default: break; @@ -55,10 +48,7 @@ class BitcoinTransactionPriority extends TransactionPriority { return label; } - String labelWithRate(int rate, int? customRate) { - final rateValue = this == custom ? customRate ??= 0 : rate; - return '${toString()} ($rateValue ${units}/byte)'; - } + String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)'; } class LitecoinTransactionPriority extends BitcoinTransactionPriority { @@ -87,7 +77,7 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { } @override - String get units => 'Litoshi'; + String get units => 'Latoshi'; @override String toString() { diff --git a/cw_bitcoin/lib/bitcoin_transaction_wrong_balance_exception.dart b/cw_bitcoin/lib/bitcoin_transaction_wrong_balance_exception.dart new file mode 100644 index 000000000..3f379bea0 --- /dev/null +++ b/cw_bitcoin/lib/bitcoin_transaction_wrong_balance_exception.dart @@ -0,0 +1,10 @@ +import 'package:cw_core/crypto_currency.dart'; + +class BitcoinTransactionWrongBalanceException implements Exception { + BitcoinTransactionWrongBalanceException(this.currency); + + final CryptoCurrency currency; + + @override + String toString() => 'You do not have enough ${currency.title} to send this amount.'; +} \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index 3691a7a22..9c198c27c 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -2,66 +2,14 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; class BitcoinUnspent extends Unspent { - BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout) + BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout) : bitcoinAddressRecord = addressRecord, super(addressRecord.address, hash, value, vout, null); - factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map json) => - BitcoinUnspent( - address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()), - json['tx_hash'] as String, - json['value'] as int, - json['tx_pos'] as int, - ); + factory BitcoinUnspent.fromJSON( + BitcoinAddressRecord address, Map json) => + BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int, + json['tx_pos'] as int); - Map toJson() { - final json = { - 'address_record': bitcoinAddressRecord.toJSON(), - 'tx_hash': hash, - 'value': value, - 'tx_pos': vout, - }; - return json; - } - - final BaseBitcoinAddressRecord bitcoinAddressRecord; -} - -class BitcoinSilentPaymentsUnspent extends BitcoinUnspent { - BitcoinSilentPaymentsUnspent( - BitcoinSilentPaymentAddressRecord addressRecord, - String hash, - int value, - int vout, { - required this.silentPaymentTweak, - required this.silentPaymentLabel, - }) : super(addressRecord, hash, value, vout); - - @override - factory BitcoinSilentPaymentsUnspent.fromJSON( - BitcoinSilentPaymentAddressRecord? address, Map json) => - BitcoinSilentPaymentsUnspent( - address ?? BitcoinSilentPaymentAddressRecord.fromJSON(json['address_record'].toString()), - json['tx_hash'] as String, - json['value'] as int, - json['tx_pos'] as int, - silentPaymentTweak: json['silent_payment_tweak'] as String?, - silentPaymentLabel: json['silent_payment_label'] as String?, - ); - - @override - Map toJson() { - final json = { - 'address_record': bitcoinAddressRecord.toJSON(), - 'tx_hash': hash, - 'value': value, - 'tx_pos': vout, - 'silent_payment_tweak': silentPaymentTweak, - 'silent_payment_label': silentPaymentLabel, - }; - return json; - } - - String? silentPaymentTweak; - String? silentPaymentLabel; + final BitcoinAddressRecord bitcoinAddressRecord; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 9231022f6..2c66d02fe 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -1,453 +1,92 @@ -import 'dart:convert'; - -import 'package:bip39/bip39.dart' as bip39; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/address_from_output.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; -import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin/payjoin/manager.dart'; -import 'package:cw_bitcoin/payjoin/storage.dart'; -import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; -import 'package:cw_bitcoin/psbt/signer.dart'; -import 'package:cw_bitcoin/psbt/transaction_builder.dart'; -import 'package:cw_bitcoin/psbt/v0_deserialize.dart'; -import 'package:cw_bitcoin/psbt/v0_finalizer.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_core/payjoin_session.dart'; -import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_keys_file.dart'; -import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_bitcoin/ledger_bitcoin.dart'; -import 'package:ledger_bitcoin/psbt.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; part 'bitcoin_wallet.g.dart'; class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; abstract class BitcoinWalletBase extends ElectrumWallet with Store { - BitcoinWalletBase({ - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required Box payjoinBox, - required EncryptionFileUtils encryptionFileUtils, - Uint8List? seedBytes, - String? mnemonic, - String? xpub, - String? addressPageType, - BasedUtxoNetwork? networkParam, - List? initialAddresses, - ElectrumBalance? initialBalance, - Map? initialRegularAddressIndex, - Map? initialChangeAddressIndex, - String? passphrase, - List? initialSilentAddresses, - int initialSilentAddressIndex = 0, - bool? alwaysScan, - }) : super( - mnemonic: mnemonic, - passphrase: passphrase, - xpub: xpub, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - network: networkParam == null - ? BitcoinNetwork.mainnet - : networkParam == BitcoinNetwork.mainnet - ? BitcoinNetwork.mainnet - : BitcoinNetwork.testnet, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: seedBytes, - encryptionFileUtils: encryptionFileUtils, - currency: networkParam == BitcoinNetwork.testnet - ? CryptoCurrency.tbtc - : CryptoCurrency.btc, - alwaysScan: alwaysScan, - ) { - // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) - // the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here) - // String derivationPath = walletInfo.derivationInfo!.derivationPath!; - // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1"; - // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); - - payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this); - walletAddresses = BitcoinWalletAddresses(walletInfo, + BitcoinWalletBase( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: bitcoin.bitcoin, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.btc) { + walletAddresses = BitcoinWalletAddresses( + walletInfo, + electrumClient: electrumClient, initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, - initialSilentAddresses: initialSilentAddresses, - initialSilentAddressIndex: initialSilentAddressIndex, mainHd: hd, - sideHd: accountHD.childKey(Bip32KeyIndex(1)), - network: networkParam ?? network, - masterHd: - seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, - isHardwareWallet: walletInfo.isHardwareWallet, - payjoinManager: payjoinManager); - - autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = - this.isEnabledAutoGenerateSubaddress; - }); + sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) + .derivePath("m/0'/1"), + networkType: networkType); } - @override - bool get hasRescan => true; - static Future create({ required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required Box payjoinBox, - required EncryptionFileUtils encryptionFileUtils, - String? passphrase, - String? addressPageType, - BasedUtxoNetwork? network, List? initialAddresses, - List? initialSilentAddresses, ElectrumBalance? initialBalance, - Map? initialRegularAddressIndex, - Map? initialChangeAddressIndex, - int initialSilentAddressIndex = 0, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0 }) async { - late Uint8List seedBytes; - - switch (walletInfo.derivationInfo?.derivationType) { - case DerivationType.bip39: - seedBytes = await bip39.mnemonicToSeed( - mnemonic, - passphrase: passphrase ?? "", - ); - break; - case DerivationType.electrum: - default: - seedBytes = - await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); - break; - } - return BitcoinWallet( - mnemonic: mnemonic, - passphrase: passphrase ?? "", - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: initialAddresses, - initialSilentAddresses: initialSilentAddresses, - initialSilentAddressIndex: initialSilentAddressIndex, - initialBalance: initialBalance, - encryptionFileUtils: encryptionFileUtils, - seedBytes: seedBytes, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - addressPageType: addressPageType, - networkParam: network, - payjoinBox: payjoinBox, - ); + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await mnemonicToSeedBytes(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex); } static Future open({ required String name, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required Box payjoinBox, required String password, - required EncryptionFileUtils encryptionFileUtils, - required bool alwaysScan, }) async { - final network = walletInfo.network != null - ? BasedUtxoNetwork.fromName(walletInfo.network!) - : BitcoinNetwork.mainnet; - - final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); - - ElectrumWalletSnapshot? snp = null; - - try { - snp = await ElectrumWalletSnapshot.load( - encryptionFileUtils, - name, - walletInfo.type, - password, - network, - ); - } catch (e) { - if (!hasKeysFile) rethrow; - } - - final WalletKeysData keysData; - // Migrate wallet from the old scheme to then new .keys file scheme - if (!hasKeysFile) { - keysData = WalletKeysData( - mnemonic: snp!.mnemonic, - xPub: snp.xpub, - passphrase: snp.passphrase, - ); - } else { - keysData = await WalletKeysFile.readKeysFile( - name, - walletInfo.type, - password, - encryptionFileUtils, - ); - } - - walletInfo.derivationInfo ??= DerivationInfo(); - - // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= - snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= - snp?.derivationType ?? DerivationType.electrum; - - Uint8List? seedBytes = null; - final mnemonic = keysData.mnemonic; - final passphrase = keysData.passphrase; - - if (mnemonic != null) { - switch (walletInfo.derivationInfo!.derivationType) { - case DerivationType.electrum: - seedBytes = - await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); - break; - case DerivationType.bip39: - default: - seedBytes = await bip39.mnemonicToSeed( - mnemonic, - passphrase: passphrase ?? '', - ); - break; - } - } - + final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); return BitcoinWallet( - mnemonic: mnemonic, - xpub: keysData.xPub, + mnemonic: snp.mnemonic, password: password, - passphrase: passphrase, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp?.addresses, - initialSilentAddresses: snp?.silentAddresses, - initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, - initialBalance: snp?.balance, - encryptionFileUtils: encryptionFileUtils, - seedBytes: seedBytes, - initialRegularAddressIndex: snp?.regularAddressIndex, - initialChangeAddressIndex: snp?.changeAddressIndex, - addressPageType: snp?.addressPageType, - networkParam: network, - alwaysScan: alwaysScan, - payjoinBox: payjoinBox); + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await mnemonicToSeedBytes(snp.mnemonic), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex); } - - LedgerConnection? _ledgerConnection; - BitcoinLedgerApp? _bitcoinLedgerApp; - - @override - void setLedgerConnection(LedgerConnection connection) { - _ledgerConnection = connection; - _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!, - derivationPath: walletInfo.derivationInfo!.derivationPath!); - } - - @override - Future close({bool shouldCleanup = false}) async { - payjoinManager.cleanupSessions(); - super.close(shouldCleanup: shouldCleanup); - } - - late final PayjoinManager payjoinManager; - - bool get isPayjoinAvailable => unspentCoinsInfo.values - .where((element) => - element.walletId == id && element.isSending && !element.isFrozen) - .isNotEmpty; - - Future buildPsbt({ - required List outputs, - required BigInt fee, - required BasedUtxoNetwork network, - required List utxos, - required Map publicKeys, - required Uint8List masterFingerprint, - String? memo, - bool enableRBF = false, - BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, - BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, - }) async { - final psbtReadyInputs = []; - for (final utxo in utxos) { - final rawTx = - await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); - final publicKeyAndDerivationPath = - publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - - psbtReadyInputs.add(PSBTReadyUtxoWithAddress( - utxo: utxo.utxo, - rawTx: rawTx, - ownerDetails: utxo.ownerDetails, - ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - ownerMasterFingerprint: masterFingerprint, - ownerPublicKey: publicKeyAndDerivationPath.publicKey, - )); - } - - return PSBTTransactionBuild( - inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF) - .psbt; - } - - @override - Future buildHardwareWalletTransaction({ - required List outputs, - required BigInt fee, - required BasedUtxoNetwork network, - required List utxos, - required Map publicKeys, - String? memo, - bool enableRBF = false, - BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, - BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, - }) async { - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); - - final psbt = await buildPsbt( - outputs: outputs, - fee: fee, - network: network, - utxos: utxos, - publicKeys: publicKeys, - masterFingerprint: masterFingerprint, - memo: memo, - enableRBF: enableRBF, - inputOrdering: inputOrdering, - outputOrdering: outputOrdering, - ); - - final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt); - return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); - } - - @override - Future createTransaction(Object credentials) async { - credentials = credentials as BitcoinTransactionCredentials; - - final tx = (await super.createTransaction(credentials)) - as PendingBitcoinTransaction; - - final payjoinUri = credentials.payjoinUri; - if (payjoinUri == null) return tx; - - final transaction = await buildPsbt( - utxos: tx.utxos, - outputs: tx.outputs - .map((e) => BitcoinOutput( - address: addressFromScript(e.scriptPubKey), - value: e.amount, - isSilentPayment: e.isSilentPayment, - isChange: e.isChange, - )) - .toList(), - fee: BigInt.from(tx.fee), - network: network, - memo: credentials.outputs.first.memo, - outputOrdering: BitcoinOrdering.none, - enableRBF: true, - publicKeys: tx.publicKeys!, - masterFingerprint: Uint8List(0)); - - final originalPsbt = await signPsbt( - base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys()); - - tx.commitOverride = () async { - final sender = await payjoinManager.initSender( - payjoinUri, originalPsbt, int.parse(tx.feeRate)); - payjoinManager.spawnNewSender( - sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); - }; - - return tx; - } - - List getUtxoWithPrivateKeys() => unspentCoins - .where((e) => (e.isSending && !e.isFrozen)) - .map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this)) - .toList(); - - Future commitPsbt(String finalizedPsbt) { - final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt)); - - final btcTx = - BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract())); - - return PendingBitcoinTransaction( - btcTx, - type, - electrumClient: electrumClient, - amount: 0, - fee: 0, - feeRate: "", - network: network, - hasChange: true, - ).commit(); - } - - Future signPsbt( - String preProcessedPsbt, List utxos) async { - final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt)); - - await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) { - return utxo.utxo.isP2tr() - ? key.signTapRoot( - txDigest, - sighash: sighash, - tweak: utxo.utxo.isSilentPayment != true, - ) - : key.signInput(txDigest, sigHash: sighash); - }, (txId, vout) async { - final txHex = await electrumClient.getTransactionHex(hash: txId); - final output = BtcTransaction.fromRaw(txHex).outputs[vout]; - return TaprootAmountScriptPair(output.amount, output.scriptPubKey); - }); - - psbt.finalizeV0(); - return base64Encode(psbt.asPsbtV0()); - } - - @override - Future signMessage(String message, {String? address = null}) async { - if (walletInfo.isHardwareWallet) { - final addressEntry = address != null - ? walletAddresses.allAddresses - .firstWhere((element) => element.address == address) - : null; - final index = addressEntry?.index ?? 0; - final isChange = addressEntry?.isHidden == true ? 1 : 0; - final accountPath = walletInfo.derivationInfo?.derivationPath; - final derivationPath = - accountPath != null ? "$accountPath/$isChange/$index" : null; - - final signature = await _bitcoinLedgerApp!.signMessage( - message: ascii.encode(message), signDerivationPath: derivationPath); - return base64Encode(signature); - } - - return super.signMessage(message, address: address); - } -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index d84d958be..36d37127d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,88 +1,34 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/bip/bip/bip32/bip32.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; -import 'package:cw_bitcoin/payjoin/manager.dart'; import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; -import 'package:payjoin_flutter/receive.dart' as payjoin; part 'bitcoin_wallet_addresses.g.dart'; class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses; abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { - BitcoinWalletAddressesBase( - WalletInfo walletInfo, { - required super.mainHd, - required super.sideHd, - required super.network, - required super.isHardwareWallet, - required this.payjoinManager, - super.initialAddresses, - super.initialRegularAddressIndex, - super.initialChangeAddressIndex, - super.initialSilentAddresses, - super.initialSilentAddressIndex = 0, - super.masterHd, - }) : super(walletInfo); - - final PayjoinManager payjoinManager; - - payjoin.Receiver? currentPayjoinReceiver; - - @observable - String? payjoinEndpoint = null; + BitcoinWalletAddressesBase(WalletInfo walletInfo, + {required bitcoin.HDWallet mainHd, + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); @override - String getAddress( - {required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) { - if (addressType == P2pkhAddressType.p2pkh) - return generateP2PKHAddress(hd: hd, index: index, network: network); - - if (addressType == SegwitAddresType.p2tr) - return generateP2TRAddress(hd: hd, index: index, network: network); - - if (addressType == SegwitAddresType.p2wsh) - return generateP2WSHAddress(hd: hd, index: index, network: network); - - if (addressType == P2shAddressType.p2wpkhInP2sh) - return generateP2SHAddress(hd: hd, index: index, network: network); - - return generateP2WPKHAddress(hd: hd, index: index, network: network); - } - - @action - Future initPayjoin() async { - try { - await payjoinManager.initPayjoin(); - currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress); - payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint(); - - payjoinManager.resumeSessions(); - } catch (e) { - printV(e); - // Ignore Connectivity errors - if (!e.toString().contains("error sending request for url")) rethrow; - } - } - - @action - Future newPayjoinReceiver() async { - try { - currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress); - payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint(); - - payjoinManager.spawnReceiver(receiver: currentPayjoinReceiver!); - } catch (e) { - printV(e); - // Ignore Connectivity errors - if (!e.toString().contains("error sending request for url")) rethrow; - } - } + String getAddress({required int index, required bitcoin.HDWallet hd}) => + generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); } diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 177d61e87..37b272a1b 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -1,65 +1,23 @@ -import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; class BitcoinNewWalletCredentials extends WalletCredentials { - BitcoinNewWalletCredentials({ - required String name, - WalletInfo? walletInfo, - String? password, - DerivationType? derivationType, - String? derivationPath, - String? passphrase, - this.mnemonic, - }) : super( - name: name, - walletInfo: walletInfo, - password: password, - passphrase: passphrase, - ); - - final String? mnemonic; + BitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); } class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { - BitcoinRestoreWalletFromSeedCredentials({ - required String name, - required String password, - required this.mnemonic, - WalletInfo? walletInfo, - required DerivationType derivationType, - required String derivationPath, - String? passphrase, - }) : super( - name: name, - password: password, - passphrase: passphrase, - walletInfo: walletInfo, - derivationInfo: DerivationInfo( - derivationType: derivationType, - derivationPath: derivationPath, - )); + BitcoinRestoreWalletFromSeedCredentials( + {required String name, required String password, required this.mnemonic, WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); final String mnemonic; } class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { - BitcoinRestoreWalletFromWIFCredentials({ - required String name, - required String password, - required this.wif, - WalletInfo? walletInfo, - }) : super(name: name, password: password, walletInfo: walletInfo); + BitcoinRestoreWalletFromWIFCredentials( + {required String name, required String password, required this.wif, WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); final String wif; -} - -class BitcoinRestoreWalletFromHardware extends WalletCredentials { - BitcoinRestoreWalletFromHardware({ - required String name, - required this.hwAccountData, - WalletInfo? walletInfo, - }) : super(name: name, walletInfo: walletInfo); - - final HardwareAccountData hwAccountData; -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 317b25bcd..3a97e0682 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -1,11 +1,7 @@ import 'dart:io'; -import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; -import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; -import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_service.dart'; @@ -15,57 +11,28 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:collection/collection.dart'; -import 'package:bip39/bip39.dart' as bip39; class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, - BitcoinRestoreWalletFromWIFCredentials, - BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, - this.payjoinSessionSource, this.alwaysScan, this.isDirect); + BitcoinRestoreWalletFromWIFCredentials> { + BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; final Box unspentCoinsInfoSource; - final Box payjoinSessionSource; - final bool alwaysScan; - final bool isDirect; @override WalletType getType() => WalletType.bitcoin; @override - Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { - final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; - credentials.walletInfo?.network = network.value; - - final String mnemonic; - switch ( credentials.walletInfo?.derivationInfo?.derivationType) { - case DerivationType.bip39: - final strength = credentials.seedPhraseLength == 24 ? 256 : 128; - - mnemonic = credentials.mnemonic ?? await MnemonicBip39.generate(strength: strength); - break; - case DerivationType.electrum: - default: - mnemonic = await generateElectrumMnemonic(); - break; - } - + Future create(BitcoinNewWalletCredentials credentials) async { final wallet = await BitcoinWalletBase.create( - mnemonic: mnemonic, - password: credentials.password!, - passphrase: credentials.passphrase, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, - network: network, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); - + mnemonic: await generateMnemonic(), + password: credentials.password!, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); - return wallet; } @@ -75,70 +42,35 @@ class BitcoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; - try { - final wallet = await BitcoinWalletBase.open( - password: password, - name: name, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, - alwaysScan: alwaysScan, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); - await wallet.init(); - saveBackup(name); - return wallet; - } catch (_) { - await restoreWalletFilesFromBackup(name); - final wallet = await BitcoinWalletBase.open( - password: password, - name: name, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, - alwaysScan: alwaysScan, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); - await wallet.init(); - return wallet; - } + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; + final wallet = await BitcoinWalletBase.open( + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.init(); + return wallet; } @override Future remove(String wallet) async { - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; + File(await pathForWalletDir(name: wallet, type: getType())) + .delete(recursive: true); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(wallet, getType()))!; await walletInfoSource.delete(walletInfo.key); - - final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( - (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); - - final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); - - if (keysToDelete.isNotEmpty) { - await unspentCoinsInfoSource.deleteAll(keysToDelete); - } } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(currentName, getType()))!; final currentWallet = await BitcoinWalletBase.open( - password: password, - name: currentName, - walletInfo: currentWalletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, - alwaysScan: alwaysScan, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); + password: password, + name: currentName, + walletInfo: currentWalletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await currentWallet.renameWalletFiles(newName); - await saveBackup(newName); final newWalletInfo = currentWalletInfo; newWalletInfo.id = WalletBase.idFor(newName, getType()); @@ -148,53 +80,24 @@ class BitcoinWalletService extends WalletService< } @override - Future restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials, - {bool? isTestnet}) async { - final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; - credentials.walletInfo?.network = network.value; - credentials.walletInfo?.derivationInfo?.derivationPath = - credentials.hwAccountData.derivationPath; - final wallet = await BitcoinWallet( - password: credentials.password!, - xpub: credentials.hwAccountData.xpub, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - networkParam: network, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - payjoinBox: payjoinSessionSource, - ); - await wallet.save(); - await wallet.init(); - return wallet; - } - - @override - Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, - {bool? isTestnet}) async => + Future restoreFromKeys( + BitcoinRestoreWalletFromWIFCredentials credentials) async => throw UnimplementedError(); @override - Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, - {bool? isTestnet}) async { - if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) { + Future restoreFromSeed( + BitcoinRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { throw BitcoinMnemonicIsIncorrectException(); } - final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; - credentials.walletInfo?.network = network.value; - final wallet = await BitcoinWalletBase.create( - password: credentials.password!, - passphrase: credentials.passphrase, - mnemonic: credentials.mnemonic, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, - network: network, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); return wallet; } -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 2ddd30df6..a05c251fe 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -2,18 +2,15 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/utils/proxy_socket/abstract.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; - -enum ConnectionStatus { connected, disconnected, connecting, failed } +import 'package:collection/collection.dart'; String jsonrpcparams(List params) { - final _params = params.map((val) => '"${val.toString()}"').join(','); + final _params = params?.map((val) => '"${val.toString()}"')?.join(','); return '[$_params]'; } @@ -25,7 +22,10 @@ String jsonrpc( '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n'; class SocketTask { - SocketTask({required this.isSubscription, this.completer, this.subject}); + SocketTask({ + required this.isSubscription, + this.completer, + this.subject}); final Completer? completer; final BehaviorSubject? subject; @@ -37,106 +37,53 @@ class ElectrumClient { : _id = 0, _isConnected = false, _tasks = {}, - _errors = {}, unterminatedString = ''; static const connectionTimeout = Duration(seconds: 5); static const aliveTimerDuration = Duration(seconds: 4); bool get isConnected => _isConnected; - ProxySocket? socket; - void Function(ConnectionStatus)? onConnectionStatusChange; + Socket? socket; + void Function(bool)? onConnectionStatusChange; int _id; final Map _tasks; - Map get tasks => _tasks; - final Map _errors; - ConnectionStatus _connectionStatus = ConnectionStatus.disconnected; bool _isConnected; Timer? _aliveTimer; String unterminatedString; - Uri? uri; - bool? useSSL; - - Future connectToUri(Uri uri, {bool? useSSL}) async { - this.uri = uri; - if (useSSL != null) { - this.useSSL = useSSL; - } + Future connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port); - } Future connect({required String host, required int port}) async { - _setConnectionStatus(ConnectionStatus.connecting); - try { await socket?.close(); } catch (_) {} - socket = null; - final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))); - try { - socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout); - } catch (e) { - printV("connect: $e"); - if (e is HandshakeException) { - useSSL = !(useSSL ?? false); - } + socket = await SecureSocket.connect(host, port, + timeout: connectionTimeout, onBadCertificate: (_) => true); + _setIsConnected(true); - if (_connectionStatus != ConnectionStatus.connecting) { - _setConnectionStatus(ConnectionStatus.failed); - } - - return; - } - - if (socket == null) { - if (_connectionStatus != ConnectionStatus.connecting) { - _setConnectionStatus(ConnectionStatus.failed); - } - - return; - } - - // use ping to determine actual connection status since we could've just not timed out yet: - // _setConnectionStatus(ConnectionStatus.connected); - socket!.listen( - (Uint8List event) { - try { - final msg = utf8.decode(event.toList()); - final messagesList = msg.split("\n"); - for (var message in messagesList) { - if (message.isEmpty) { - continue; - } - _parseResponse(message); + socket!.listen((Uint8List event) { + try { + final msg = utf8.decode(event.toList()); + final messagesList = msg.split("\n"); + for (var message in messagesList) { + if (message.isEmpty) { + continue; } - } catch (e) { - printV("socket.listen: $e"); + _parseResponse(message); } - }, - onError: (Object error) { - final errorMsg = error.toString(); - printV(errorMsg); - unterminatedString = ''; - socket = null; - }, - onDone: () { - printV("SOCKET CLOSED!!!!!"); - unterminatedString = ''; - try { - if (host == socket?.address.host || socket == null) { - _setConnectionStatus(ConnectionStatus.disconnected); - socket?.destroy(); - socket = null; - } - } catch (e) { - printV("onDone: $e"); - } - }, - cancelOnError: true, - ); - + } catch (e) { + print(e.toString()); + } + }, onError: (Object error) { + print(error.toString()); + unterminatedString = ''; + _setIsConnected(false); + }, onDone: () { + unterminatedString = ''; + _setIsConnected(false); + }); keepAlive(); } @@ -157,26 +104,27 @@ class ElectrumClient { } if (isJSONStringCorrect(unterminatedString)) { - final response = json.decode(unterminatedString) as Map; + final response = + json.decode(unterminatedString) as Map; _handleResponse(response); unterminatedString = ''; } } on TypeError catch (e) { - if (!e.toString().contains('Map') && - !e.toString().contains('Map')) { + if (!e.toString().contains('Map') && !e.toString().contains('Map')) { return; } unterminatedString += message; if (isJSONStringCorrect(unterminatedString)) { - final response = json.decode(unterminatedString) as Map; + final response = + json.decode(unterminatedString) as Map; _handleResponse(response); // unterminatedString = null; unterminatedString = ''; } } catch (e) { - printV("parse $e"); + print(e.toString()); } } @@ -188,14 +136,14 @@ class ElectrumClient { Future ping() async { try { await callWithTimeout(method: 'server.ping'); - _setConnectionStatus(ConnectionStatus.connected); - } catch (_) { - _setConnectionStatus(ConnectionStatus.disconnected); + _setIsConnected(true); + } on RequestFailedTimeoutException catch (_) { + _setIsConnected(false); } } Future> version() => - call(method: 'server.version', params: ["", "1.4"]).then((dynamic result) { + call(method: 'server.version').then((dynamic result) { if (result is List) { return result.map((dynamic val) => val.toString()).toList(); } @@ -229,21 +177,41 @@ class ElectrumClient { return []; }); - Future>?> getListUnspent(String scriptHash) async { - final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]); + Future>> getListUnspentWithAddress( + String address, NetworkType networkType) => + call( + method: 'blockchain.scripthash.listunspent', + params: [scriptHash(address, networkType: networkType)]) + .then((dynamic result) { + if (result is List) { + return result.map((dynamic val) { + if (val is Map) { + val['address'] = address; + return val; + } - if (result is List) { - return result.map((dynamic val) { - if (val is Map) { - return val; + return {}; + }).toList(); } - return {}; - }).toList(); - } + return []; + }); - return null; - } + Future>> getListUnspent(String scriptHash) => + call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]) + .then((dynamic result) { + if (result is List) { + return result.map((dynamic val) { + if (val is Map) { + return val; + } + + return {}; + }).toList(); + } + + return []; + }); Future>> getMempool(String scriptHash) => call(method: 'blockchain.scripthash.get_mempool', params: [scriptHash]) @@ -261,20 +229,10 @@ class ElectrumClient { return []; }); - Future getTransaction({required String hash, required bool verbose}) async { - try { - final result = await callWithTimeout( - method: 'blockchain.transaction.get', params: [hash, verbose], timeout: 10000); - return result; - } on RequestFailedTimeoutException catch (_) { - return {}; - } catch (e) { - return {}; - } - } - - Future> getTransactionVerbose({required String hash}) => - getTransaction(hash: hash, verbose: true).then((dynamic result) { + Future> getTransactionRaw( + {required String hash}) async => + callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000) + .then((dynamic result) { if (result is Map) { return result; } @@ -282,23 +240,9 @@ class ElectrumClient { return {}; }); - Future getTransactionHex({required String hash}) => - getTransaction(hash: hash, verbose: false).then((dynamic result) { - if (result is String) { - return result; - } - - return ''; - }); - - Future broadcastTransaction( - {required String transactionRaw, - BasedUtxoNetwork? network, - Function(int)? idCallback}) async => - call( - method: 'blockchain.transaction.broadcast', - params: [transactionRaw], - idCallback: idCallback) + Future getTransactionHex( + {required String hash}) async => + callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000) .then((dynamic result) { if (result is String) { return result; @@ -307,26 +251,30 @@ class ElectrumClient { return ''; }); - Future> getMerkle({required String hash, required int height}) async => - await call(method: 'blockchain.transaction.get_merkle', params: [hash, height]) - as Map; + Future broadcastTransaction( + {required String transactionRaw}) async => + call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) + .then((dynamic result) { + if (result is String) { + return result; + } + + return ''; + }); + + Future> getMerkle( + {required String hash, required int height}) async => + await call( + method: 'blockchain.transaction.get_merkle', + params: [hash, height]) as Map; Future> getHeader({required int height}) async => - await call(method: 'blockchain.block.get_header', params: [height]) as Map; - - BehaviorSubject? tweaksSubscribe({required int height, required int count}) { - return subscribe( - id: 'blockchain.tweaks.subscribe', - method: 'blockchain.tweaks.subscribe', - params: [height, count, false], - ); - } - - Future getTweaks({required int height}) async => - await callWithTimeout(method: 'blockchain.tweaks.subscribe', params: [height, 1, false]); + await call(method: 'blockchain.block.get_header', params: [height]) + as Map; Future estimatefee({required int p}) => - call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { + call(method: 'blockchain.estimatefee', params: [p]) + .then((dynamic result) { if (result is double) { return result; } @@ -366,14 +314,20 @@ class ElectrumClient { return []; }); - Future> feeRates({BasedUtxoNetwork? network}) async { + Future> feeRates() async { try { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); - final bottomDoubleString = await estimatefee(p: 10); - final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); - final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); - final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); + final bottomDoubleString = await estimatefee(p: 100); + final top = + (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) + .round(); + final middle = + (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000) + .round(); + final bottom = + (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000) + .round(); return [bottom, middle, top]; } catch (_) { @@ -381,34 +335,6 @@ class ElectrumClient { } } - // https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe - // example response: - // { - // "height": 520481, - // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" - // } - - Future getCurrentBlockChainTip() async { - try { - final result = await callWithTimeout(method: 'blockchain.headers.subscribe'); - if (result is Map) { - return result["height"] as int; - } - return null; - } on RequestFailedTimeoutException catch (_) { - return null; - } catch (e) { - printV("getCurrentBlockChainTip: ${e.toString()}"); - return null; - } - } - - BehaviorSubject? chainTipSubscribe() { - _id += 1; - return subscribe( - id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe'); - } - BehaviorSubject? scripthashUpdate(String scripthash) { _id += 1; return subscribe( @@ -418,31 +344,25 @@ class ElectrumClient { } BehaviorSubject? subscribe( - {required String id, required String method, List params = const []}) { + {required String id, + required String method, + List params = const []}) { try { - if (socket == null) { - return null; - } final subscription = BehaviorSubject(); _regisrySubscription(id, subscription); socket!.write(jsonrpc(method: method, id: _id, params: params)); return subscription; - } catch (e) { - printV("subscribe $e"); + } catch(e) { + print(e.toString()); return null; } } - Future call( - {required String method, List params = const [], Function(int)? idCallback}) async { - if (socket == null) { - return null; - } + Future call({required String method, List params = const []}) async { final completer = Completer(); _id += 1; final id = _id; - idCallback?.call(id); _registryTask(id, completer); socket!.write(jsonrpc(method: method, id: id, params: params)); @@ -450,11 +370,10 @@ class ElectrumClient { } Future callWithTimeout( - {required String method, List params = const [], int timeout = 5000}) async { + {required String method, + List params = const [], + int timeout = 4000}) async { try { - if (socket == null) { - return null; - } final completer = Completer(); _id += 1; final id = _id; @@ -467,23 +386,19 @@ class ElectrumClient { }); return completer.future; - } catch (e) { - printV("callWithTimeout $e"); - rethrow; + } catch(e) { + print(e.toString()); } } Future close() async { _aliveTimer?.cancel(); - try { - await socket?.close(); - socket = null; - } catch (_) {} + await socket?.close(); onConnectionStatusChange = null; } - void _registryTask(int id, Completer completer) => - _tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false); + void _registryTask(int id, Completer completer) => _tasks[id.toString()] = + SocketTask(completer: completer, isSubscription: false); void _regisrySubscription(String id, BehaviorSubject subject) => _tasks[id] = SocketTask(subject: subject, isSubscription: true); @@ -504,14 +419,9 @@ class ElectrumClient { } } - void _methodHandler({required String method, required Map request}) { + void _methodHandler( + {required String method, required Map request}) { switch (method) { - case 'blockchain.headers.subscribe': - final params = request['params'] as List; - final id = 'blockchain.headers.subscribe'; - - _tasks[id]?.subject?.add(params.last); - break; case 'blockchain.scripthash.subscribe': final params = request['params'] as List; final scripthash = params.first as String?; @@ -519,29 +429,17 @@ class ElectrumClient { _tasks[id]?.subject?.add(params.last); break; - case 'blockchain.headers.subscribe': - final params = request['params'] as List; - _tasks[method]?.subject?.add(params.last); - break; - case 'blockchain.tweaks.subscribe': - final params = request['params'] as List; - _tasks[_tasks.keys.first]?.subject?.add(params.last); - break; default: break; } } - void _setConnectionStatus(ConnectionStatus status) { - onConnectionStatusChange?.call(status); - _connectionStatus = status; - _isConnected = status == ConnectionStatus.connected; - if (!_isConnected) { - try { - socket?.destroy(); - } catch (_) {} - socket = null; + void _setIsConnected(bool isConnected) { + if (_isConnected != isConnected) { + onConnectionStatusChange?.call(isConnected); } + + _isConnected = isConnected; } void _handleResponse(Map response) { @@ -549,34 +447,15 @@ class ElectrumClient { final id = response['id'] as String?; final result = response['result']; - try { - final error = response['error'] as Map?; - if (error != null) { - final errorMessage = error['message'] as String?; - if (errorMessage != null) { - _errors[id!] = errorMessage; - } - } - } catch (_) {} - - try { - final error = response['error'] as String?; - if (error != null) { - _errors[id!] = error; - } - } catch (_) {} - if (method is String) { _methodHandler(method: method, request: response); return; } - - if (id != null) { + + if (id != null){ _finish(id, result); } } - - String getErrorMessage(int id) => _errors[id.toString()] ?? ''; } // FIXME: move me diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index aeb06f1f0..0a9a33d54 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -1,20 +1,11 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/balance.dart'; class ElectrumBalance extends Balance { - ElectrumBalance({ - required this.confirmed, - required this.unconfirmed, - required this.frozen, - this.secondConfirmed = 0, - this.secondUnconfirmed = 0, - }) : super( - confirmed, - unconfirmed, - secondAvailable: secondConfirmed, - secondAdditional: secondUnconfirmed, - ); + const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen}) + : super(confirmed, unconfirmed); static ElectrumBalance? fromJSON(String? jsonSource) { if (jsonSource == null) { @@ -24,47 +15,26 @@ class ElectrumBalance extends Balance { final decoded = json.decode(jsonSource) as Map; return ElectrumBalance( - confirmed: decoded['confirmed'] as int? ?? 0, - unconfirmed: decoded['unconfirmed'] as int? ?? 0, - frozen: decoded['frozen'] as int? ?? 0, - secondConfirmed: decoded['secondConfirmed'] as int? ?? 0, - secondUnconfirmed: decoded['secondUnconfirmed'] as int? ?? 0, - ); + confirmed: decoded['confirmed'] as int? ?? 0, + unconfirmed: decoded['unconfirmed'] as int? ?? 0, + frozen: decoded['frozen'] as int? ?? 0); } - int confirmed; - int unconfirmed; + final int confirmed; + final int unconfirmed; final int frozen; - int secondConfirmed = 0; - int secondUnconfirmed = 0; @override - String get formattedAvailableBalance => bitcoinAmountToString(amount: ((confirmed + unconfirmed) - frozen) ); + String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen); @override String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); - @override - String get formattedUnAvailableBalance { + String get formattedFrozenBalance { final frozenFormatted = bitcoinAmountToString(amount: frozen); return frozenFormatted == '0.0' ? '' : frozenFormatted; } - @override - String get formattedSecondAvailableBalance => bitcoinAmountToString(amount: secondConfirmed); - - @override - String get formattedSecondAdditionalBalance => bitcoinAmountToString(amount: secondUnconfirmed); - - @override - String get formattedFullAvailableBalance => - bitcoinAmountToString(amount: (confirmed + unconfirmed) + secondConfirmed - frozen); - - String toJSON() => json.encode({ - 'confirmed': confirmed, - 'unconfirmed': unconfirmed, - 'frozen': frozen, - 'secondConfirmed': secondConfirmed, - 'secondUnconfirmed': secondUnconfirmed, - }); + String toJSON() => + json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); } diff --git a/cw_bitcoin/lib/electrum_derivations.dart b/cw_bitcoin/lib/electrum_derivations.dart deleted file mode 100644 index 81a3626d2..000000000 --- a/cw_bitcoin/lib/electrum_derivations.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:cw_core/wallet_info.dart'; - -Map> electrum_derivations = { - DerivationType.electrum: [ - DerivationInfo( - derivationType: DerivationType.electrum, - derivationPath: "m/0'", - description: "Electrum", - scriptType: "p2wpkh", - ), - ], - DerivationType.bip39: [ - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/0'/0'", - description: "Standard BIP44", - scriptType: "p2pkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/49'/0'/0'", - description: "Standard BIP49 compatibility segwit", - scriptType: "p2wpkh-p2sh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/84'/0'/0'", - description: "Standard BIP84 native segwit", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/86'/0'/0'", - description: "Standard BIP86 Taproot", - scriptType: "p2tr", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/0'", - description: "Non-standard legacy", - scriptType: "p2pkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/0'", - description: "Non-standard compatibility segwit", - scriptType: "p2wpkh-p2sh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/0'", - description: "Non-standard native segwit", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/0'/0'", - description: "Samourai Deposit", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/49'/0'/0'", - description: "Samourai Deposit", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/84'/0'/2147483644'", - description: "Samourai Bad Bank (toxic change)", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/84'/0'/2147483645'", - description: "Samourai Whirlpool Pre Mix", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/84'/0'/2147483646'", - description: "Samourai Whirlpool Post Mix", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/44'/0'/2147483647'", - description: "Samourai Ricochet legacy", - scriptType: "p2pkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/49'/0'/2147483647'", - description: "Samourai Ricochet compatibility segwit", - scriptType: "p2wpkh-p2sh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/84'/0'/2147483647'", - description: "Samourai Ricochet native segwit", - scriptType: "p2wpkh", - ), - DerivationInfo( - derivationType: DerivationType.bip39, - derivationPath: "m/84'/2'/0'", - description: "Default Litecoin", - scriptType: "p2wpkh", - ), - ], -}; - -String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!; diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index d096d0e7b..be039fa36 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -1,43 +1,36 @@ import 'dart:convert'; -import 'package:cw_core/encryption_file_utils.dart'; - -import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/transaction_history.dart'; -import 'package:cw_core/utils/file.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/transaction_history.dart'; +import 'package:cw_bitcoin/file.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; part 'electrum_transaction_history.g.dart'; const transactionsHistoryFileName = 'transactions.json'; -class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory; +class ElectrumTransactionHistory = ElectrumTransactionHistoryBase + with _$ElectrumTransactionHistory; abstract class ElectrumTransactionHistoryBase extends TransactionHistoryBase with Store { ElectrumTransactionHistoryBase( - {required this.walletInfo, required String password, required this.encryptionFileUtils}) + {required this.walletInfo, required String password}) : _password = password, _height = 0 { transactions = ObservableMap(); } final WalletInfo walletInfo; - final EncryptionFileUtils encryptionFileUtils; String _password; int _height; - Future init() async { - clear(); - await _load(); - } + Future init() async => await _load(); @override - void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction; + void addOne(ElectrumTransactionInfo transaction) => + transactions[transaction.id] = transaction; @override void addMany(Map transactions) => @@ -46,16 +39,14 @@ abstract class ElectrumTransactionHistoryBase @override Future save() async { try { - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final dirPath = + await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); final path = '$dirPath/$transactionsHistoryFileName'; - final txjson = {}; - for (final tx in transactions.entries) { - txjson[tx.key] = tx.value.toJson(); - } - final data = json.encode({'height': _height, 'transactions': txjson}); - await encryptionFileUtils.write(path: path, password: _password, data: data); + final data = + json.encode({'height': _height, 'transactions': transactions}); + await writeData(path: path, password: _password, data: data); } catch (e) { - printV('Error while save bitcoin transaction history: ${e.toString()}'); + print('Error while save bitcoin transaction history: ${e.toString()}'); } } @@ -65,9 +56,10 @@ abstract class ElectrumTransactionHistoryBase } Future> _read() async { - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final dirPath = + await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); final path = '$dirPath/$transactionsHistoryFileName'; - final content = await encryptionFileUtils.read(path: path, password: _password); + final content = await read(path: path, password: _password); return json.decode(content) as Map; } @@ -80,21 +72,18 @@ abstract class ElectrumTransactionHistoryBase final val = entry.value; if (val is Map) { - // removing transactions with invalid date - if (val['date'] == 1168650000) { - transactions.remove(entry.key); - } else { - final tx = ElectrumTransactionInfo.fromJson(val, walletInfo.type); - _update(tx); - } + final tx = ElectrumTransactionInfo.fromJson(val, walletInfo.type); + _update(tx); } }); _height = content['height'] as int; } catch (e) { - printV(e); + print(e); } } - void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction; + void _update(ElectrumTransactionInfo transaction) => + transactions[transaction.id] = transaction; + } diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 7a8b3b951..bf5ec2c4f 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -1,64 +1,46 @@ -import 'dart:convert'; - -import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/format_amount.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:hex/hex.dart'; class ElectrumTransactionBundle { ElectrumTransactionBundle(this.originalTransaction, - {required this.ins, required this.confirmations, this.time}); - - final BtcTransaction originalTransaction; - final List ins; + {required this.ins, + required this.confirmations, + this.time}); + final bitcoin.Transaction originalTransaction; + final List ins; final int? time; final int confirmations; } class ElectrumTransactionInfo extends TransactionInfo { - List? unspents; - bool isReceivedSilentPayment; - - ElectrumTransactionInfo( - this.type, { - required String id, - int? height, - required int amount, - int? fee, - List? inputAddresses, - List? outputAddresses, - required TransactionDirection direction, - required bool isPending, - bool isReplaced = false, - required DateTime date, - required int confirmations, - String? to, - this.unspents, - this.isReceivedSilentPayment = false, - Map? additionalInfo, - }) { + ElectrumTransactionInfo(this.type, + {required String id, + required int height, + required int amount, + int? fee, + required TransactionDirection direction, + required bool isPending, + required DateTime date, + required int confirmations}) { this.id = id; this.height = height; this.amount = amount; - this.inputAddresses = inputAddresses; - this.outputAddresses = outputAddresses; this.fee = fee; this.direction = direction; this.date = date; this.isPending = isPending; - this.isReplaced = isReplaced; this.confirmations = confirmations; - this.to = to; - this.additionalInfo = additionalInfo ?? {}; } - factory ElectrumTransactionInfo.fromElectrumVerbose(Map obj, WalletType type, + factory ElectrumTransactionInfo.fromElectrumVerbose( + Map obj, WalletType type, {required List addresses, required int height}) { final addressesSet = addresses.map((addr) => addr.address).toSet(); final id = obj['txid'] as String; @@ -76,8 +58,10 @@ class ElectrumTransactionInfo extends TransactionInfo { for (dynamic vin in vins) { final vout = vin['vout'] as int; final out = vin['tx']['vout'][vout] as Map; - final outAddresses = (out['scriptPubKey']['addresses'] as List?)?.toSet(); - inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString()); + final outAddresses = + (out['scriptPubKey']['addresses'] as List?)?.toSet(); + inputsAmount += + stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString()); if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) { direction = TransactionDirection.outgoing; @@ -85,9 +69,11 @@ class ElectrumTransactionInfo extends TransactionInfo { } for (dynamic out in vout) { - final outAddresses = out['scriptPubKey']['addresses'] as List? ?? []; + final outAddresses = + out['scriptPubKey']['addresses'] as List? ?? []; final ntrs = outAddresses.toSet().intersection(addressesSet); - final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString()); + final value = stringDoubleToBitcoinAmount( + (out['value'] as double? ?? 0.0).toString()); totalOutAmount += value; if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || @@ -102,7 +88,6 @@ class ElectrumTransactionInfo extends TransactionInfo { id: id, height: height, isPending: false, - isReplaced: false, fee: fee, direction: direction, amount: amount, @@ -111,76 +96,46 @@ class ElectrumTransactionInfo extends TransactionInfo { } factory ElectrumTransactionInfo.fromElectrumBundle( - ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network, - {required Set addresses, int? height}) { + ElectrumTransactionBundle bundle, + WalletType type, + bitcoin.NetworkType networkType, + {required Set addresses, + required int height}) { final date = bundle.time != null - ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) - : DateTime.now(); + ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) + : DateTime.now(); var direction = TransactionDirection.incoming; var amount = 0; var inputAmount = 0; var totalOutAmount = 0; - List inputAddresses = []; - List outputAddresses = []; - for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { - final input = bundle.originalTransaction.inputs[i]; + for (var i = 0; i < bundle.originalTransaction.ins.length; i++) { + final input = bundle.originalTransaction.ins[i]; final inputTransaction = bundle.ins[i]; - final outTransaction = inputTransaction.outputs[input.txIndex]; - inputAmount += outTransaction.amount.toInt(); - if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) { + final vout = input.index; + final outTransaction = inputTransaction.outs[vout!]; + final address = addressFromOutput(outTransaction.script!, networkType); + inputAmount += outTransaction.value!; + if (addresses.contains(address)) { direction = TransactionDirection.outgoing; - inputAddresses.add(addressFromOutputScript(outTransaction.scriptPubKey, network)); } } - final receivedAmounts = []; - for (final out in bundle.originalTransaction.outputs) { - totalOutAmount += out.amount.toInt(); - final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network)); - final address = addressFromOutputScript(out.scriptPubKey, network); - - if (address.isNotEmpty) outputAddresses.add(address); - - // Check if the script contains OP_RETURN - final script = out.scriptPubKey.script; - if (script.contains('OP_RETURN')) { - final index = script.indexOf('OP_RETURN'); - if (index + 1 <= script.length) { - try { - final opReturnData = script[index + 1].toString(); - final decodedString = utf8.decode(HEX.decode(opReturnData)); - outputAddresses.add('OP_RETURN:$decodedString'); - } catch (_) { - outputAddresses.add('OP_RETURN:'); - } - } - } - - if (addressExists) { - receivedAmounts.add(out.amount.toInt()); - } - + for (final out in bundle.originalTransaction.outs) { + totalOutAmount += out.value!; + final address = addressFromOutput(out.script!, networkType); + final addressExists = addresses.contains(address); if ((direction == TransactionDirection.incoming && addressExists) || (direction == TransactionDirection.outgoing && !addressExists)) { - amount += out.amount.toInt(); + amount += out.value!; } } - if (receivedAmounts.length == bundle.originalTransaction.outputs.length) { - // Self-send - direction = TransactionDirection.incoming; - amount = receivedAmounts.reduce((a, b) => a + b); - } - final fee = inputAmount - totalOutAmount; return ElectrumTransactionInfo(type, - id: bundle.originalTransaction.txId(), + id: bundle.originalTransaction.getId(), height: height, isPending: bundle.confirmations == 0, - isReplaced: false, - inputAddresses: inputAddresses, - outputAddresses: outputAddresses, fee: fee, direction: direction, amount: amount, @@ -188,34 +143,52 @@ class ElectrumTransactionInfo extends TransactionInfo { confirmations: bundle.confirmations); } - factory ElectrumTransactionInfo.fromJson(Map data, WalletType type) { - final inputAddresses = data['inputAddresses'] as List? ?? []; - final outputAddresses = data['outputAddresses'] as List? ?? []; - final unspents = data['unspents'] as List? ?? []; + factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex, + {List? addresses, required int height, int? timestamp, required int confirmations}) { + final tx = bitcoin.Transaction.fromHex(hex); + var exist = false; + var amount = 0; - return ElectrumTransactionInfo( - type, - id: data['id'] as String, - height: data['height'] as int, - amount: data['amount'] as int, - fee: data['fee'] as int, - direction: parseTransactionDirectionFromInt(data['direction'] as int), - date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), - isPending: data['isPending'] as bool, - isReplaced: data['isReplaced'] as bool? ?? false, - confirmations: data['confirmations'] as int, - inputAddresses: - inputAddresses.isEmpty ? [] : inputAddresses.map((e) => e.toString()).toList(), - outputAddresses: - outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(), - to: data['to'] as String?, - unspents: unspents - .map((unspent) => - BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map)) - .toList(), - isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false, - additionalInfo: data['additionalInfo'] as Map?, - ); + if (addresses != null) { + tx.outs.forEach((out) { + try { + final p2pkh = bitcoin.P2PKH( + data: PaymentData(output: out.script), network: bitcoin.bitcoin); + exist = addresses.contains(p2pkh.data.address); + + if (exist) { + amount += out.value!; + } + } catch (_) {} + }); + } + + final date = timestamp != null + ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) + : DateTime.now(); + + return ElectrumTransactionInfo(type, + id: tx.getId(), + height: height, + isPending: false, + fee: null, + direction: TransactionDirection.incoming, + amount: amount, + date: date, + confirmations: confirmations); + } + + factory ElectrumTransactionInfo.fromJson( + Map data, WalletType type) { + return ElectrumTransactionInfo(type, + id: data['id'] as String, + height: data['height'] as int, + amount: data['amount'] as int, + fee: data['fee'] as int, + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + confirmations: data['confirmations'] as int); } final WalletType type; @@ -246,11 +219,7 @@ class ElectrumTransactionInfo extends TransactionInfo { direction: direction, date: date, isPending: isPending, - isReplaced: isReplaced ?? false, - inputAddresses: inputAddresses, - outputAddresses: outputAddresses, - confirmations: info.confirmations, - additionalInfo: additionalInfo); + confirmations: info.confirmations); } Map toJson() { @@ -261,19 +230,8 @@ class ElectrumTransactionInfo extends TransactionInfo { m['direction'] = direction.index; m['date'] = date.millisecondsSinceEpoch; m['isPending'] = isPending; - m['isReplaced'] = isReplaced; m['confirmations'] = confirmations; m['fee'] = fee; - m['to'] = to; - m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? []; - m['inputAddresses'] = inputAddresses; - m['outputAddresses'] = outputAddresses; - m['isReceivedSilentPayment'] = isReceivedSilentPayment; - m['additionalInfo'] = additionalInfo; return m; } - - String toString() { - return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)'; - } } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index bb9cea1bc..804b53379 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1,55 +1,42 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:isolate'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_core/format_amount.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; -import 'package:cw_bitcoin/litecoin_wallet.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:collection/collection.dart'; -import 'package:cw_bitcoin/address_from_output.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; -import 'package:cw_bitcoin/bitcoin_wallet_keys.dart'; -import 'package:cw_bitcoin/electrum.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; -import 'package:cw_bitcoin/electrum_transaction_history.dart'; -import 'package:cw_bitcoin/electrum_transaction_info.dart'; -import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; -import 'package:cw_bitcoin/exceptions.dart'; -import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_core/get_height_by_date.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_core/pathForWallet.dart'; +import 'dart:math'; import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; -import 'package:sp_scanner/sp_scanner.dart'; +import 'package:flutter/foundation.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/electrum_transaction_info.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_bitcoin/address_to_output_script.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; +import 'package:cw_bitcoin/electrum_transaction_history.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_keys.dart'; +import 'package:cw_bitcoin/file.dart'; +import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; +import 'package:cw_bitcoin/script_hash.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_bitcoin/electrum.dart'; import 'package:hex/hex.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:collection/collection.dart'; +import 'package:bip32/bip32.dart'; part 'electrum_wallet.g.dart'; @@ -57,115 +44,49 @@ class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; abstract class ElectrumWalletBase extends WalletBase - with Store, WalletKeysFile { - ElectrumWalletBase({ - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required this.network, - required this.encryptionFileUtils, - String? xpub, - String? mnemonic, - Uint8List? seedBytes, - this.passphrase, - List? initialAddresses, - ElectrumClient? electrumClient, - ElectrumBalance? initialBalance, - CryptoCurrency? currency, - this.alwaysScan, - }) : accountHD = - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), + with Store { + ElectrumWalletBase( + {required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required this.networkType, + required this.mnemonic, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumClient? electrumClient, + ElectrumBalance? initialBalance, + CryptoCurrency? currency}) + : hd = currency == CryptoCurrency.bch + ? bitcoinCashHDWallet(seedBytes) + : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], _isTransactionUpdating = false, - isEnabledAutoGenerateSubaddress = true, unspentCoins = [], _scripthashesUpdateSubject = {}, balance = ObservableMap.of(currency != null ? { - currency: initialBalance ?? - ElectrumBalance( - confirmed: 0, - unconfirmed: 0, - frozen: 0, - ) - } + currency: + initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0) + } : {}), this.unspentCoinsInfo = unspentCoinsInfo, - this.isTestnet = !network.isMainnet, - this._mnemonic = mnemonic, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; - transactionHistory = ElectrumTransactionHistory( - walletInfo: walletInfo, - password: password, - encryptionFileUtils: encryptionFileUtils, - ); - - reaction((_) => syncStatus, _syncStatusReaction); - - sharedPrefs.complete(SharedPreferences.getInstance()); + transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); } - static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network, - Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) { - if (seedBytes == null && xpub == null) { - throw Exception( - "To create a Wallet you need either a seed or an xpub. This should not happen"); - } - - if (seedBytes != null) { - switch (currency) { - case CryptoCurrency.btc: - case CryptoCurrency.ltc: - case CryptoCurrency.tbtc: - return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath( - _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) - as Bip32Slip10Secp256k1; - case CryptoCurrency.bch: - return bitcoinCashHDWallet(seedBytes); - default: - throw Exception("Unsupported currency"); - } - } - - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network)); - } - - static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => - Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; + static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => + bitcoin.HDWallet.fromSeed(seedBytes) + .derivePath("m/44'/145'/0'/0"); static int estimatedTransactionSize(int inputsCount, int outputsCounts) => - inputsCount * 68 + outputsCounts * 34 + 10; + inputsCount * 146 + outputsCounts * 33 + 8; - static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) { - switch (network) { - case LitecoinNetwork.mainnet: - return Bip44Conf.litecoinMainNet.altKeyNetVer; - default: - return null; - } - } - - bool? alwaysScan; - - final Bip32Slip10Secp256k1 accountHD; - final String? _mnemonic; - - Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0)); - - Bip32Slip10Secp256k1 get sideHd => accountHD.childKey(Bip32KeyIndex(1)); - - final EncryptionFileUtils encryptionFileUtils; - - @override - final String? passphrase; - - @override - @observable - bool isEnabledAutoGenerateSubaddress; + final bitcoin.HDWallet hd; + final String mnemonic; late ElectrumClient electrumClient; Box unspentCoinsInfo; @@ -181,1065 +102,257 @@ abstract class ElectrumWalletBase @observable SyncStatus syncStatus; - Set get addressesSet => walletAddresses.allAddresses - .where((element) => element.type != SegwitAddresType.mweb) - .map((addr) => addr.address) - .toSet(); - - List get scriptHashes => walletAddresses.addressesByReceiveType - .where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress) - .map((addr) => (addr as BitcoinAddressRecord).getScriptHash(network)) + List get scriptHashes => walletAddresses.addresses + .map((addr) => scriptHash(addr.address, networkType: networkType)) .toList(); - List get publicScriptHashes => walletAddresses.allAddresses + List get publicScriptHashes => walletAddresses.addresses .where((addr) => !addr.isHidden) - .where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress) - .map((addr) => addr.getScriptHash(network)) + .map((addr) => scriptHash(addr.address, networkType: networkType)) .toList(); - String get xpub => accountHD.publicKey.toExtended; + String get xpub => hd.base58!; @override - String? get seed => _mnemonic; + String get seed => mnemonic; + + bitcoin.NetworkType networkType; @override - WalletKeysData get walletKeysData => - WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase); - - @override - String get password => _password; - - BasedUtxoNetwork network; - - @override - bool isTestnet; - - bool get hasSilentPaymentsScanning => type == WalletType.bitcoin; - - @observable - bool nodeSupportsSilentPayments = true; - @observable - bool silentPaymentsScanningActive = false; - - bool _isTryingToConnect = false; - - Completer sharedPrefs = Completer(); - - Future checkIfMempoolAPIIsEnabled() async { - bool isMempoolAPIEnabled = (await sharedPrefs.future).getBool("use_mempool_fee_api") ?? true; - return isMempoolAPIEnabled; - } - - @action - Future setSilentPaymentsScanning(bool active) async { - silentPaymentsScanningActive = active; - - if (active) { - syncStatus = AttemptingScanSyncStatus(); - - final tip = await getUpdatedChainTip(); - - if (tip == walletInfo.restoreHeight) { - syncStatus = SyncedTipSyncStatus(tip); - return; - } - - if (tip > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight, chainTipParam: currentChainTip); - } - } else { - alwaysScan = false; - - _isolate?.then((value) => value.kill(priority: Isolate.immediate)); - - if (electrumClient.isConnected) { - syncStatus = SyncedSyncStatus(); - } else { - syncStatus = NotConnectedSyncStatus(); - } - } - } - - int? currentChainTip; - - Future getCurrentChainTip() async { - if ((currentChainTip ?? 0) > 0) { - return currentChainTip!; - } - currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0; - - return currentChainTip!; - } - - Future getUpdatedChainTip() async { - final newTip = await electrumClient.getCurrentBlockChainTip(); - if (newTip != null && newTip > (currentChainTip ?? 0)) { - currentChainTip = newTip; - } - return currentChainTip ?? 0; - } - - @override - BitcoinWalletKeys get keys => BitcoinWalletKeys( - wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer), - privateKey: hd.privateKey.toHex(), - publicKey: hd.publicKey.toHex(), - ); + BitcoinWalletKeys get keys => + BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); String _password; List unspentCoins; List _feeRates; - - // ignore: prefer_final_fields Map?> _scripthashesUpdateSubject; - - // ignore: prefer_final_fields - BehaviorSubject? _chainTipUpdateSubject; bool _isTransactionUpdating; - Future? _isolate; void Function(FlutterErrorDetails)? _onError; - Timer? _autoSaveTimer; - StreamSubscription? _receiveStream; - Timer? _updateFeeRateTimer; - static const int _autoSaveInterval = 1; Future init() async { await walletAddresses.init(); await transactionHistory.init(); - await cleanUpDuplicateUnspentCoins(); await save(); - - _autoSaveTimer = - Timer.periodic(Duration(minutes: _autoSaveInterval), (_) async => await save()); - } - - @action - Future _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async { - if (this is! BitcoinWallet) return; - final chainTip = chainTipParam ?? await getUpdatedChainTip(); - - if (chainTip == height) { - syncStatus = SyncedSyncStatus(); - return; - } - - syncStatus = AttemptingScanSyncStatus(); - - if (_isolate != null) { - final runningIsolate = await _isolate!; - runningIsolate.kill(priority: Isolate.immediate); - } - - final receivePort = ReceivePort(); - _isolate = Isolate.spawn( - startRefresh, - ScanData( - sendPort: receivePort.sendPort, - silentAddress: walletAddresses.silentAddress!, - network: network, - height: height, - chainTip: chainTip, - electrumClient: ElectrumClient(), - transactionHistoryIds: transactionHistory.transactions.keys.toList(), - node: (await getNodeSupportsSilentPayments()) == true - ? ScanNode(node!.uri, node!.useSSL) - : null, - labels: walletAddresses.labels, - labelIndexes: walletAddresses.silentAddresses - .where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1) - .map((addr) => addr.index) - .toList(), - isSingleScan: doSingleScan ?? false, - )); - - await _receiveStream?.cancel(); - _receiveStream = receivePort.listen((var message) async { - if (message is Map) { - for (final map in message.entries) { - final txid = map.key; - final tx = map.value; - - if (tx.unspents != null) { - final existingTxInfo = transactionHistory.transactions[txid]; - final txAlreadyExisted = existingTxInfo != null; - - // Updating tx after re-scanned - if (txAlreadyExisted) { - existingTxInfo.amount = tx.amount; - existingTxInfo.confirmations = tx.confirmations; - existingTxInfo.height = tx.height; - - final newUnspents = tx.unspents! - .where((unspent) => !(existingTxInfo.unspents?.any((element) => - element.hash.contains(unspent.hash) && - element.vout == unspent.vout && - element.value == unspent.value) ?? - false)) - .toList(); - - if (newUnspents.isNotEmpty) { - newUnspents.forEach(_updateSilentAddressRecord); - - existingTxInfo.unspents ??= []; - existingTxInfo.unspents!.addAll(newUnspents); - - final newAmount = newUnspents.length > 1 - ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) - : newUnspents[0].value; - - if (existingTxInfo.direction == TransactionDirection.incoming) { - existingTxInfo.amount += newAmount; - } - - // Updates existing TX - transactionHistory.addOne(existingTxInfo); - // Update balance record - balance[currency]!.confirmed += newAmount; - } - } else { - // else: First time seeing this TX after scanning - tx.unspents!.forEach(_updateSilentAddressRecord); - - // Add new TX record - transactionHistory.addMany(message); - // Update balance record - balance[currency]!.confirmed += tx.amount; - } - - await updateAllUnspents(); - } - } - } - - if (message is SyncResponse) { - if (message.syncStatus is UnsupportedSyncStatus) { - nodeSupportsSilentPayments = false; - } - - if (message.syncStatus is SyncingSyncStatus) { - var status = message.syncStatus as SyncingSyncStatus; - syncStatus = SyncingSyncStatus(status.blocksLeft, status.ptc); - } else { - syncStatus = message.syncStatus; - } - - await walletInfo.updateRestoreHeight(message.height); - } - }); - } - - void _updateSilentAddressRecord(BitcoinSilentPaymentsUnspent unspent) { - final silentAddress = walletAddresses.silentAddress!; - final silentPaymentAddress = SilentPaymentAddress( - version: silentAddress.version, - B_scan: silentAddress.B_scan, - B_spend: unspent.silentPaymentLabel != null - ? silentAddress.B_spend.tweakAdd( - BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)), - ) - : silentAddress.B_spend, - network: network, - ); - - final addressRecord = walletAddresses.silentAddresses - .firstWhereOrNull((address) => address.address == silentPaymentAddress.toString()); - addressRecord?.txCount += 1; - addressRecord?.balance += unspent.value; - - walletAddresses.addSilentAddresses( - [unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord], - ); } @action @override Future startSync() async { try { - if (syncStatus is SyncronizingSyncStatus) { - return; - } - - syncStatus = SyncronizingSyncStatus(); - - if (hasSilentPaymentsScanning) { - await _setInitialHeight(); - } - - await subscribeForUpdates(); + syncStatus = AttemptingSyncStatus(); + await walletAddresses.discoverAddresses(); await updateTransactions(); - - await updateAllUnspents(); + _subscribeForUpdates(); + await updateUnspent(); await updateBalance(); - await updateFeeRates(); + _feeRates = await electrumClient.feeRates(); - _updateFeeRateTimer ??= - Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); + Timer.periodic( + const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); - if (alwaysScan == true) { - _setListeners(walletInfo.restoreHeight); - } else { - if (syncStatus is LostConnectionSyncStatus) return; - syncStatus = SyncedSyncStatus(); - } + syncStatus = SyncedSyncStatus(); } catch (e, stacktrace) { - printV(stacktrace); - printV("startSync $e"); + print(stacktrace); + print(e.toString()); syncStatus = FailedSyncStatus(); } } - @action - Future updateFeeRates() async { - if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) { - try { - final response = await ProxyWrapper() - .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended")) - .timeout(Duration(seconds: 15)); - final result = json.decode(response.body) as Map; - final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0; - int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0; - int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0; - if (slowFee == mediumFee) { - mediumFee++; - } - while (fastFee <= mediumFee) { - fastFee++; - } - _feeRates = [slowFee, mediumFee, fastFee]; - return; - } catch (e) { - printV(e); - } - } - - final feeRates = await electrumClient.feeRates(network: network); - if (feeRates != [0, 0, 0]) { - _feeRates = feeRates; - } else if (isTestnet) { - _feeRates = [1, 1, 1]; - } - } - - Node? node; - - Future getNodeIsElectrs() async { - if (node == null) { - return false; - } - - final version = await electrumClient.version(); - - if (version.isNotEmpty) { - final server = version[0]; - - if (server.toLowerCase().contains('electrs')) { - node!.isElectrs = true; - node!.save(); - return node!.isElectrs!; - } - } - - node!.isElectrs = false; - node!.save(); - return node!.isElectrs!; - } - - Future getNodeSupportsSilentPayments() async { - // As of today (august 2024), only ElectrumRS supports silent payments - if (!(await getNodeIsElectrs())) { - return false; - } - - if (node == null) { - return false; - } - - try { - final tweaksResponse = await electrumClient.getTweaks(height: 0); - - if (tweaksResponse != null) { - node!.supportsSilentPayments = true; - node!.save(); - return node!.supportsSilentPayments!; - } - } on RequestFailedTimeoutException catch (_) { - node!.supportsSilentPayments = false; - node!.save(); - return node!.supportsSilentPayments!; - } catch (_) {} - - node!.supportsSilentPayments = false; - node!.save(); - return node!.supportsSilentPayments!; - } - @action @override Future connectToNode({required Node node}) async { - this.node = node; - - if (syncStatus is ConnectingSyncStatus) return; - try { syncStatus = ConnectingSyncStatus(); - - await _receiveStream?.cancel(); - await electrumClient.close(); - - electrumClient.onConnectionStatusChange = _onConnectionStatusChange; - - await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); - } catch (e, stacktrace) { - printV(stacktrace); - printV("connectToNode $e"); + await electrumClient.connectToUri(node.uri); + electrumClient.onConnectionStatusChange = (bool isConnected) { + if (!isConnected) { + syncStatus = LostConnectionSyncStatus(); + } + }; + syncStatus = ConnectedSyncStatus(); + } catch (e) { + print(e.toString()); syncStatus = FailedSyncStatus(); } } - int get _dustAmount => 546; + @override + Future createTransaction(Object credentials) async { + const minAmount = 546; + final transactionCredentials = credentials as BitcoinTransactionCredentials; + final inputs = []; + final outputs = transactionCredentials.outputs; + final hasMultiDestination = outputs.length > 1; + var allInputsAmount = 0; - bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; + if (unspentCoins.isEmpty) { + await updateUnspent(); + } - UtxoDetails _createUTXOS({ - required bool sendAll, - required bool paysToSilentPayment, - int credentialsAmount = 0, - int? inputsCount, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, - }) { - List utxos = []; - List vinOutpoints = []; - List inputPrivKeyInfos = []; - final publicKeys = {}; - int allInputsAmount = 0; - bool spendsSilentPayment = false; - bool spendsUnconfirmedTX = false; + for (final utx in unspentCoins) { + if (utx.isSending) { + allInputsAmount += utx.value; + inputs.add(utx); + } + } - int leftAmount = credentialsAmount; - var availableInputs = unspentCoins.where((utx) { - if (!utx.isSending || utx.isFrozen) { - return false; + if (inputs.isEmpty) { + throw BitcoinTransactionNoInputsException(); + } + + final allAmountFee = transactionCredentials.feeRate != null + ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length) + : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length); + + final allAmount = allInputsAmount - allAmountFee; + + var credentialsAmount = 0; + var amount = 0; + var fee = 0; + + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { + throw BitcoinTransactionWrongBalanceException(currency); } - switch (coinTypeToSpendFrom) { - case UnspentCoinType.mweb: - return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb; - case UnspentCoinType.nonMweb: - return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb; - case UnspentCoinType.any: - return true; - } - }).toList(); - final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); - // sort the unconfirmed coins so that mweb coins are last: - availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1); - - for (int i = 0; i < availableInputs.length; i++) { - final utx = availableInputs[i]; - if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0; - - if (paysToSilentPayment) { - // Check inputs for shared secret derivation - if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) { - throw BitcoinTransactionSilentPaymentsNotSupported(); - } + if (allAmount - credentialsAmount < minAmount) { + throw BitcoinTransactionWrongBalanceException(currency); } - allInputsAmount += utx.value; - leftAmount = leftAmount - utx.value; + amount = credentialsAmount; - final address = RegexUtils.addressTypeFromStr(utx.address, network); - ECPrivate? privkey; - bool? isSilentPayment = false; - - final hd = - utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; - - if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; - privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( - BigintUtils.fromBytes( - BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), - ), - ); - spendsSilentPayment = true; - isSilentPayment = true; - } else if (!isHardwareWallet) { - privkey = - generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); - } - - vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); - String pubKeyHex; - - if (privkey != null) { - inputPrivKeyInfos.add(ECPrivateInfo( - privkey, - address.type == SegwitAddresType.p2tr, - tweak: !isSilentPayment, - )); - - pubKeyHex = privkey.getPublic().toHex(); + if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, + outputsCount: outputs.length + 1); } else { - pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); + fee = calculateEstimatedFee(transactionCredentials.priority, amount, + outputsCount: outputs.length + 1); + } + } else { + final output = outputs.first; + credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; + + if (credentialsAmount > allAmount) { + throw BitcoinTransactionWrongBalanceException(currency); } - final derivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? electrum_path)}" - "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" - "/${utx.bitcoinAddressRecord.index}"; - publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); + amount = output.sendAll || allAmount - credentialsAmount < minAmount + ? allAmount + : credentialsAmount; - utxos.add( - UtxoWithAddress( - utxo: BitcoinUtxo( - txHash: utx.hash, - value: BigInt.from(utx.value), - vout: utx.vout, - scriptType: _getScriptType(address), - isSilentPayment: isSilentPayment, - ), - ownerDetails: UtxoAddressDetails( - publicKey: pubKeyHex, - address: address, - ), - ), - ); + if (output.sendAll || amount == allAmount) { + fee = allAmountFee; + } else if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount); + } + } - // sendAll continues for all inputs - if (!sendAll) { - bool amountIsAcquired = leftAmount <= 0; - if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { + if (fee == 0) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final totalAmount = amount + fee; + + if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final txb = bitcoin.TransactionBuilder(network: networkType); + final changeAddress = await walletAddresses.getChangeAddress(); + var leftAmount = totalAmount; + var totalInputAmount = 0; + + inputs.clear(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + leftAmount = leftAmount - utx.value; + totalInputAmount += utx.value; + inputs.add(utx); + + if (leftAmount <= 0) { break; } } } - if (utxos.isEmpty) { + if (inputs.isEmpty) { throw BitcoinTransactionNoInputsException(); } - return UtxoDetails( - availableInputs: availableInputs, - unconfirmedCoins: unconfirmedCoins, - utxos: utxos, - vinOutpoints: vinOutpoints, - inputPrivKeyInfos: inputPrivKeyInfos, - publicKeys: publicKeys, - allInputsAmount: allInputsAmount, - spendsSilentPayment: spendsSilentPayment, - spendsUnconfirmedTX: spendsUnconfirmedTX, - ); - } - - Future estimateSendAllTx( - List outputs, - int feeRate, { - String? memo, - bool hasSilentPayment = false, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, - }) async { - final utxoDetails = _createUTXOS( - sendAll: true, - paysToSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); - - int fee = await calcFee( - utxos: utxoDetails.utxos, - outputs: outputs, - network: network, - memo: memo, - feeRate: feeRate, - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, - vinOutpoints: utxoDetails.vinOutpoints, - ); - - if (fee == 0) { - throw BitcoinTransactionNoFeeException(); + if (amount <= 0 || totalInputAmount < totalAmount) { + throw BitcoinTransactionWrongBalanceException(currency); } - // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change - int amount = utxoDetails.allInputsAmount - fee; + txb.setVersion(1); + inputs.forEach((input) { + if (input.isP2wpkh) { + final p2wpkh = bitcoin + .P2WPKH( + data: generatePaymentData( + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: input.bitcoinAddressRecord.index), + network: networkType) + .data; - if (amount <= 0) { - throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); - } - - // Attempting to send less than the dust limit - if (_isBelowDust(amount)) { - throw BitcoinTransactionNoDustException(); - } - - if (outputs.length == 1) { - outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); - } - - return EstimatedTxResult( - utxos: utxoDetails.utxos, - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, - publicKeys: utxoDetails.publicKeys, - fee: fee, - amount: amount, - isSendAll: true, - hasChange: false, - memo: memo, - spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, - spendsSilentPayment: utxoDetails.spendsSilentPayment, - ); - } - - Future estimateTxForAmount( - int credentialsAmount, - List outputs, - List updatedOutputs, - int feeRate, { - int? inputsCount, - String? memo, - bool? useUnconfirmed, - bool hasSilentPayment = false, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, - }) async { - // Attempting to send less than the dust limit - if (_isBelowDust(credentialsAmount)) { - throw BitcoinTransactionNoDustException(); - } - - final utxoDetails = _createUTXOS( - sendAll: false, - credentialsAmount: credentialsAmount, - inputsCount: inputsCount, - paysToSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); - - final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length; - final spendingAllConfirmedCoins = !utxoDetails.spendsUnconfirmedTX && - utxoDetails.utxos.length == - utxoDetails.availableInputs.length - utxoDetails.unconfirmedCoins.length; - - // How much is being spent - how much is being sent - int amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount; - - if (amountLeftForChangeAndFee <= 0) { - if (!spendingAllCoins) { - return estimateTxForAmount( - credentialsAmount, - outputs, - updatedOutputs, - feeRate, - inputsCount: utxoDetails.utxos.length + 1, - memo: memo, - hasSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); - } - - throw BitcoinTransactionWrongBalanceException(); - } - - final changeAddress = await walletAddresses.getChangeAddress( - inputs: utxoDetails.availableInputs, - outputs: updatedOutputs, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); - final address = RegexUtils.addressTypeFromStr(changeAddress.address, network); - updatedOutputs.add(BitcoinOutput( - address: address, - value: BigInt.from(amountLeftForChangeAndFee), - isChange: true, - )); - outputs.add(BitcoinOutput( - address: address, - value: BigInt.from(amountLeftForChangeAndFee), - isChange: true, - )); - - // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets - final changeDerivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" - "/${changeAddress.isHidden ? "1" : "0"}" - "/${changeAddress.index}"; - utxoDetails.publicKeys[address.pubKeyHash()] = - PublicKeyWithDerivationPath('', changeDerivationPath); - - // calcFee updates the silent payment outputs to calculate the tx size accounting - // for taproot addresses, but if more inputs are needed to make up for fees, - // the silent payment outputs need to be recalculated for the new inputs - var temp = outputs.map((output) => output).toList(); - int fee = await calcFee( - utxos: utxoDetails.utxos, - // Always take only not updated bitcoin outputs here so for every estimation - // the SP outputs are re-generated to the proper taproot addresses - outputs: temp, - network: network, - memo: memo, - feeRate: feeRate, - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, - vinOutpoints: utxoDetails.vinOutpoints, - ); - - updatedOutputs.clear(); - updatedOutputs.addAll(temp); - - if (fee == 0) { - throw BitcoinTransactionNoFeeException(); - } - - int amount = credentialsAmount; - final lastOutput = updatedOutputs.last; - final amountLeftForChange = amountLeftForChangeAndFee - fee; - - if (_isBelowDust(amountLeftForChange)) { - // If has change that is lower than dust, will end up with tx rejected by network rules - // so remove the change amount - updatedOutputs.removeLast(); - outputs.removeLast(); - - if (amountLeftForChange < 0) { - if (!spendingAllCoins) { - return estimateTxForAmount( - credentialsAmount, - outputs, - updatedOutputs, - feeRate, - inputsCount: utxoDetails.utxos.length + 1, - memo: memo, - useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, - hasSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); - } else { - throw BitcoinTransactionWrongBalanceException(); - } - } - - // if the amount left for change is less than dust, but not less than 0 - // then add it to the fees - fee += amountLeftForChange; - - return EstimatedTxResult( - utxos: utxoDetails.utxos, - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, - publicKeys: utxoDetails.publicKeys, - fee: fee, - amount: amount, - hasChange: false, - isSendAll: spendingAllCoins, - memo: memo, - spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, - spendsSilentPayment: utxoDetails.spendsSilentPayment, - ); - } else { - // Here, lastOutput already is change, return the amount left without the fee to the user's address. - updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput( - address: lastOutput.address, - value: BigInt.from(amountLeftForChange), - isSilentPayment: lastOutput.isSilentPayment, - isChange: true, - ); - outputs[outputs.length - 1] = BitcoinOutput( - address: lastOutput.address, - value: BigInt.from(amountLeftForChange), - isSilentPayment: lastOutput.isSilentPayment, - isChange: true, - ); - - return EstimatedTxResult( - utxos: utxoDetails.utxos, - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, - publicKeys: utxoDetails.publicKeys, - fee: fee, - amount: amount, - hasChange: true, - isSendAll: spendingAllCoins, - memo: memo, - spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, - spendsSilentPayment: utxoDetails.spendsSilentPayment, - ); - } - } - - Future calcFee({ - required List utxos, - required List outputs, - required BasedUtxoNetwork network, - String? memo, - required int feeRate, - List? inputPrivKeyInfos, - List? vinOutpoints, - }) async { - int estimatedSize; - if (network is BitcoinCashNetwork) { - estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( - utxos: utxos, - outputs: outputs, - network: network, - memo: memo, - ); - } else { - estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( - utxos: utxos, - outputs: outputs, - network: network, - memo: memo, - inputPrivKeyInfos: inputPrivKeyInfos, - vinOutpoints: vinOutpoints, - ); - } - - return feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); - } - - @override - Future createTransaction(Object credentials) async { - try { - // start by updating unspent coins - await updateAllUnspents(); - - final outputs = []; - final transactionCredentials = credentials as BitcoinTransactionCredentials; - final hasMultiDestination = transactionCredentials.outputs.length > 1; - final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; - final memo = transactionCredentials.outputs.first.memo; - final coinTypeToSpendFrom = transactionCredentials.coinTypeToSpendFrom; - - int credentialsAmount = 0; - bool hasSilentPayment = false; - - for (final out in transactionCredentials.outputs) { - final outputAmount = out.formattedCryptoAmount!; - - if (!sendAll && _isBelowDust(outputAmount)) { - throw BitcoinTransactionNoDustException(); - } - - if (hasMultiDestination) { - if (out.sendAll) { - throw BitcoinTransactionWrongBalanceException(); - } - } - - credentialsAmount += outputAmount; - - final address = RegexUtils.addressTypeFromStr( - out.isParsedAddress ? out.extractedAddress! : out.address, network); - final isSilentPayment = address is SilentPaymentAddress; - - if (isSilentPayment) { - hasSilentPayment = true; - } - - if (sendAll) { - // The value will be changed after estimating the Tx size and deducting the fee from the total to be sent - outputs.add(BitcoinOutput( - address: address, - value: BigInt.from(0), - isSilentPayment: isSilentPayment, - )); - } else { - outputs.add(BitcoinOutput( - address: address, - value: BigInt.from(outputAmount), - isSilentPayment: isSilentPayment, - )); - } - } - - final feeRateInt = transactionCredentials.feeRate != null - ? transactionCredentials.feeRate! - : feeRate(transactionCredentials.priority!); - - EstimatedTxResult estimatedTx; - final updatedOutputs = outputs - .map((e) => BitcoinOutput( - address: e.address, - value: e.value, - isSilentPayment: e.isSilentPayment, - isChange: e.isChange, - )) - .toList(); - - if (sendAll) { - estimatedTx = await estimateSendAllTx( - updatedOutputs, - feeRateInt, - memo: memo, - hasSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); + txb.addInput(input.hash, input.vout, null, p2wpkh.output); } else { - estimatedTx = await estimateTxForAmount( - credentialsAmount, - outputs, - updatedOutputs, - feeRateInt, - memo: memo, - hasSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, - ); + txb.addInput(input.hash, input.vout); } + }); - if (walletInfo.isHardwareWallet) { - final transaction = await buildHardwareWalletTransaction( - utxos: estimatedTx.utxos, - outputs: updatedOutputs, - publicKeys: estimatedTx.publicKeys, - fee: BigInt.from(estimatedTx.fee), - network: network, - memo: estimatedTx.memo, - outputOrdering: BitcoinOrdering.none, - enableRBF: true, - ); + outputs.forEach((item) { + final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; + final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!); + }); - return PendingBitcoinTransaction( - transaction, - type, - electrumClient: electrumClient, - amount: estimatedTx.amount, - fee: estimatedTx.fee, - feeRate: feeRateInt.toString(), - network: network, - hasChange: estimatedTx.hasChange, - isSendAll: estimatedTx.isSendAll, - hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot - )..addListener((transaction) async { - transactionHistory.addOne(transaction); - await updateBalance(); - await updateAllUnspents(); - }); - } + final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1); + var feeAmount = 0; - BasedBitcoinTransacationBuilder txb; - if (network is BitcoinCashNetwork) { - txb = ForkedTransactionBuilder( - utxos: estimatedTx.utxos, - outputs: updatedOutputs, - fee: BigInt.from(estimatedTx.fee), - network: network, - memo: estimatedTx.memo, - outputOrdering: BitcoinOrdering.none, - enableRBF: !estimatedTx.spendsUnconfirmedTX, - ); - } else { - txb = BitcoinTransactionBuilder( - utxos: estimatedTx.utxos, - outputs: updatedOutputs, - fee: BigInt.from(estimatedTx.fee), - network: network, - memo: estimatedTx.memo, - outputOrdering: BitcoinOrdering.none, - enableRBF: !estimatedTx.spendsUnconfirmedTX, - ); - } + if (transactionCredentials.feeRate != null) { + feeAmount = transactionCredentials.feeRate! * estimatedSize; + } else { + feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; + } - bool hasTaprootInputs = false; + final changeValue = totalInputAmount - amount - feeAmount; - final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { - String error = "Cannot find private key."; + if (changeValue > minAmount) { + txb.addOutput(changeAddress, changeValue); + } - ECPrivateInfo? key; + for (var i = 0; i < inputs.length; i++) { + final input = inputs[i]; + final keyPair = generateKeyPair( + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: input.bitcoinAddressRecord.index, + network: networkType); + final witnessValue = input.isP2wpkh ? input.value : null; - if (estimatedTx.inputPrivKeyInfos.isEmpty) { - error += "\nNo private keys generated."; - } else { - error += "\nAddress: ${utxo.ownerDetails.address.toAddress(network)}"; + txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue); + } - key = estimatedTx.inputPrivKeyInfos.firstWhereOrNull((element) { - final elemPubkey = element.privkey.getPublic().toHex(); - if (elemPubkey == publicKey) { - return true; - } else { - error += "\nExpected: $publicKey"; - error += "\nPubkey: $elemPubkey"; - return false; - } - }); - } - - if (key == null) { - throw Exception(error); - } - - if (utxo.utxo.isP2tr()) { - hasTaprootInputs = true; - return key.privkey.signTapRoot( - txDigest, - sighash: sighash, - tweak: utxo.utxo.isSilentPayment != true, - ); - } else { - return key.privkey.signInput(txDigest, sigHash: sighash); - } + return PendingBitcoinTransaction(txb.build(), type, + electrumClient: electrumClient, amount: amount, fee: fee) + ..addListener((transaction) async { + transactionHistory.addOne(transaction); + await updateBalance(); }); - - return PendingBitcoinTransaction(transaction, type, - electrumClient: electrumClient, - amount: estimatedTx.amount, - fee: estimatedTx.fee, - feeRate: feeRateInt.toString(), - network: network, - hasChange: estimatedTx.hasChange, - isSendAll: estimatedTx.isSendAll, - hasTaprootInputs: hasTaprootInputs, - utxos: estimatedTx.utxos, - publicKeys: estimatedTx.publicKeys) - ..addListener((transaction) async { - transactionHistory.addOne(transaction); - if (estimatedTx.spendsSilentPayment) { - transactionHistory.transactions.values.forEach((tx) { - tx.unspents?.removeWhere( - (unspent) => estimatedTx.utxos.any((e) => e.utxo.txHash == unspent.hash)); - transactionHistory.addOne(tx); - }); - } - - unspentCoins - .removeWhere((utxo) => estimatedTx.utxos.any((e) => e.utxo.txHash == utxo.hash)); - - await updateBalance(); - await updateAllUnspents(); - }); - } catch (e) { - throw e; - } } - void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError(); - - Future buildHardwareWalletTransaction({ - required List outputs, - required BigInt fee, - required BasedUtxoNetwork network, - required List utxos, - required Map publicKeys, - String? memo, - bool enableRBF = false, - BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, - BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, - }) async => - throw UnimplementedError(); - String toJSON() => json.encode({ - 'mnemonic': _mnemonic, - 'xpub': xpub, - 'passphrase': passphrase ?? '', - 'account_index': walletAddresses.currentReceiveAddressIndexByType, - 'change_address_index': walletAddresses.currentChangeAddressIndexByType, - 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(), - 'address_page_type': walletInfo.addressPageType == null - ? SegwitAddresType.p2wpkh.toString() - : walletInfo.addressPageType.toString(), - 'balance': balance[currency]?.toJSON(), - 'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index, - 'derivationPath': walletInfo.derivationInfo?.derivationPath, - 'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(), - 'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(), - 'mweb_addresses': walletAddresses.mwebAddresses.map((addr) => addr.toJSON()).toList(), - 'alwaysScan': alwaysScan, - }); + 'mnemonic': mnemonic, + 'account_index': walletAddresses.currentReceiveAddressIndex.toString(), + 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), + 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), + 'balance': balance[currency]?.toJSON() + }); int feeRate(TransactionPriority priority) { try { @@ -1253,29 +366,24 @@ abstract class ElectrumWalletBase } } - int feeAmountForPriority(TransactionPriority priority, int inputsCount, int outputsCount, - {int? size}) => - feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount)); + int feeAmountForPriority( + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => + feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); - int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) => - feeRate * (size ?? estimatedTransactionSize(inputsCount, outputsCount)); + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => + feeRate * estimatedTransactionSize(inputsCount, outputsCount); @override - int calculateEstimatedFee(TransactionPriority? priority, int? amount, - {int? outputsCount, int? size}) { + int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) { if (priority is BitcoinTransactionPriority) { return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount, - outputsCount: outputsCount, size: size); + outputsCount: outputsCount); } return 0; } - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) { - if (size != null) { - return feeAmountWithFeeRate(feeRate, 0, 0, size: size); - } - + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { int inputsCount = 0; if (amount != null) { @@ -1309,13 +417,8 @@ abstract class ElectrumWalletBase @override Future save() async { - if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { - await saveKeysFile(_password, encryptionFileUtils); - saveKeysFile(_password, encryptionFileUtils, true); - } - final path = await makePath(); - await encryptionFileUtils.write(path: path, password: _password, data: toJSON()); + await write(path: path, password: _password, data: toJSON()); await transactionHistory.save(); } @@ -1348,917 +451,233 @@ abstract class ElectrumWalletBase await transactionHistory.changePassword(password); } - @action - @override - Future rescan({required int height, bool? doSingleScan}) async { - silentPaymentsScanningActive = true; - _setListeners(height, doSingleScan: doSingleScan); - } + bitcoin.ECPair keyPairFor({required int index}) => + generateKeyPair(hd: hd, index: index, network: networkType); @override - Future close({bool shouldCleanup = false}) async { + Future rescan({required int height}) async => throw UnimplementedError(); + + @override + Future close() async { try { - await _receiveStream?.cancel(); await electrumClient.close(); } catch (_) {} - _autoSaveTimer?.cancel(); - _updateFeeRateTimer?.cancel(); } - @action - Future updateAllUnspents() async { - List updatedUnspentCoins = []; + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); - final previousUnspentCoins = List.from(unspentCoins.where((utxo) => - utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb && - utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)); + Future updateUnspent() async { + final unspent = await Future.wait(walletAddresses + .addresses.map((address) => electrumClient + .getListUnspentWithAddress(address.address, networkType) + .then((unspent) => unspent + .map((unspent) { + try { + return BitcoinUnspent.fromJSON(address, unspent); + } catch(_) { + return null; + } + }).whereNotNull()))); + unspentCoins = unspent.expand((e) => e).toList(); - if (hasSilentPaymentsScanning) { - // Update unspents stored from scanned silent payment transactions - transactionHistory.transactions.values.forEach((tx) { - if (tx.unspents != null) { - updatedUnspentCoins.addAll(tx.unspents!); + if (unspentCoinsInfo.isEmpty) { + unspentCoins.forEach((coin) => _addCoinInfo(coin)); + return; + } + + if (unspentCoins.isNotEmpty) { + unspentCoins.forEach((coin) { + final coinInfoList = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash)); + + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; + + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + } else { + _addCoinInfo(coin); } }); } - // Set the balance of all non-silent payment and non-mweb addresses to 0 before updating - walletAddresses.allAddresses - .where((element) => element.type != SegwitAddresType.mweb) - .forEach((addr) { - if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0; - }); - - final addressFutures = walletAddresses.allAddresses - .where((element) => element.type != SegwitAddresType.mweb) - .map((address) => fetchUnspent(address)) - .toList(); - - final results = await Future.wait(addressFutures); - final failedCount = results.where((result) => result == null).length; - - if (failedCount == 0) { - for (final result in results) { - updatedUnspentCoins.addAll(result!); - } - unspentCoins = updatedUnspentCoins; - } else { - unspentCoins = handleFailedUtxoFetch( - failedCount: failedCount, - previousUnspentCoins: previousUnspentCoins, - updatedUnspentCoins: updatedUnspentCoins, - results: results, - ); - } - - final currentWalletUnspentCoins = - unspentCoinsInfo.values.where((element) => element.walletId == id); - - if (currentWalletUnspentCoins.length != updatedUnspentCoins.length) { - unspentCoins.forEach((coin) => addCoinInfo(coin)); - } - - await updateCoins(unspentCoins); await _refreshUnspentCoinsInfo(); } - List handleFailedUtxoFetch({ - required int failedCount, - required List previousUnspentCoins, - required List updatedUnspentCoins, - required List?> results, - }) { - if (failedCount == results.length) { - printV("All UTXOs failed to fetch, falling back to previous UTXOs"); - return previousUnspentCoins; - } + Future _addCoinInfo(BitcoinUnspent coin) async { + final newInfo = UnspentCoinsInfo( + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + noteRaw: coin.note, + address: coin.bitcoinAddressRecord.address, + value: coin.value, + vout: coin.vout, + ); - final successfulUtxos = []; - for (final result in results) { - if (result != null) { - successfulUtxos.addAll(result); - } - } - - if (failedCount > 0 && successfulUtxos.isEmpty) { - printV("Some UTXOs failed, but no successful UTXOs, falling back to previous UTXOs"); - return previousUnspentCoins; - } - - if (failedCount > 0) { - printV("Some UTXOs failed, updating with successful UTXOs"); - updatedUnspentCoins.addAll(successfulUtxos); - } - - return updatedUnspentCoins; - } - - Future updateCoins(List newUnspentCoins) async { - if (newUnspentCoins.isEmpty) { - return; - } - - newUnspentCoins.forEach((coin) { - final coinInfoList = unspentCoinsInfo.values.where( - (element) => - element.walletId.contains(id) && - element.hash.contains(coin.hash) && - element.vout == coin.vout, - ); - - if (coinInfoList.isNotEmpty) { - final coinInfo = coinInfoList.first; - - coin.isFrozen = coinInfo.isFrozen; - coin.isSending = coinInfo.isSending; - coin.note = coinInfo.note; - - if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord) - coin.bitcoinAddressRecord.balance += coinInfo.value; - } else { - addCoinInfo(coin); - } - }); - } - - @action - Future updateUnspentsForAddress(BitcoinAddressRecord address) async { - final newUnspentCoins = await fetchUnspent(address); - await updateCoins(newUnspentCoins ?? []); - } - - @action - Future?> fetchUnspent(BitcoinAddressRecord address) async { - List updatedUnspentCoins = []; - - final unspents = await electrumClient.getListUnspent(address.getScriptHash(network)); - - // Failed to fetch unspents - if (unspents == null) return null; - - await Future.wait(unspents.map((unspent) async { - try { - final coin = BitcoinUnspent.fromJSON(address, unspent); - final tx = await fetchTransactionInfo(hash: coin.hash); - coin.isChange = address.isHidden; - coin.confirmations = tx?.confirmations; - - updatedUnspentCoins.add(coin); - } catch (_) {} - })); - - return updatedUnspentCoins; - } - - @action - Future addCoinInfo(BitcoinUnspent coin) async { - // Check if the coin is already in the unspentCoinsInfo for the wallet - final existingCoinInfo = unspentCoinsInfo.values - .firstWhereOrNull((element) => element.walletId == walletInfo.id && element == coin); - - if (existingCoinInfo == null) { - final newInfo = UnspentCoinsInfo( - walletId: id, - hash: coin.hash, - isFrozen: coin.isFrozen, - isSending: coin.isSending, - noteRaw: coin.note, - address: coin.bitcoinAddressRecord.address, - value: coin.value, - vout: coin.vout, - isChange: coin.isChange, - isSilentPayment: coin is BitcoinSilentPaymentsUnspent, - ); - - await unspentCoinsInfo.add(newInfo); - } + await unspentCoinsInfo.add(newInfo); } Future _refreshUnspentCoinsInfo() async { try { - final List keys = []; + final List keys = []; final currentWalletUnspentCoins = - unspentCoinsInfo.values.where((record) => record.walletId == id); + unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); - for (final element in currentWalletUnspentCoins) { - if (RegexUtils.addressTypeFromStr(element.address, network) is MwebAddress) continue; + if (currentWalletUnspentCoins.isNotEmpty) { + currentWalletUnspentCoins.forEach((element) { + final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash)); - final existUnspentCoins = unspentCoins.where((coin) => element == coin); - - if (existUnspentCoins.isEmpty) { - keys.add(element.key); - } + if (existUnspentCoins.isEmpty) { + keys.add(element.key); + } + }); } if (keys.isNotEmpty) { await unspentCoinsInfo.deleteAll(keys); } } catch (e) { - printV("refreshUnspentCoinsInfo $e"); - } - } - - Future cleanUpDuplicateUnspentCoins() async { - final currentWalletUnspentCoins = - unspentCoinsInfo.values.where((element) => element.walletId == id); - final Map uniqueUnspentCoins = {}; - final List duplicateKeys = []; - - for (final unspentCoin in currentWalletUnspentCoins) { - final key = '${unspentCoin.hash}:${unspentCoin.vout}'; - if (!uniqueUnspentCoins.containsKey(key)) { - uniqueUnspentCoins[key] = unspentCoin; - } else { - duplicateKeys.add(unspentCoin.key); - } - } - - if (duplicateKeys.isNotEmpty) await unspentCoinsInfo.deleteAll(duplicateKeys); - } - - int transactionVSize(String transactionHex) => BtcTransaction.fromRaw(transactionHex).getVSize(); - - Future canReplaceByFee(ElectrumTransactionInfo tx) async { - try { - final bundle = await getTransactionExpanded(hash: tx.txHash); - _updateInputsAndOutputs(tx, bundle); - if (bundle.confirmations > 0) return null; - return bundle.originalTransaction.canReplaceByFee ? bundle.originalTransaction.toHex() : null; - } catch (e) { - return null; - } - } - - Future isChangeSufficientForFee(String txId, int newFee) async { - final bundle = await getTransactionExpanded(hash: txId); - final outputs = bundle.originalTransaction.outputs; - - final ownAddresses = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); - - final receiverAmount = outputs - .where((output) => - !ownAddresses.contains(addressFromOutputScript(output.scriptPubKey, network))) - .fold(0, (sum, output) => sum + output.amount.toInt()); - - if (receiverAmount == 0) { - throw Exception("Receiver output not found."); - } - - final availableInputs = unspentCoins.where((utxo) => utxo.isSending && !utxo.isFrozen).toList(); - int totalBalance = availableInputs.fold( - 0, (previousValue, element) => previousValue + element.value.toInt()); - - int allInputsAmount = 0; - for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) { - final input = bundle.originalTransaction.inputs[i]; - final inputTransaction = bundle.ins[i]; - final vout = input.txIndex; - final outTransaction = inputTransaction.outputs[vout]; - allInputsAmount += outTransaction.amount.toInt(); - } - - int totalOutAmount = bundle.originalTransaction.outputs - .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); - var currentFee = allInputsAmount - totalOutAmount; - - int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee; - return totalBalance - receiverAmount - remainingFee >= _dustAmount; - } - - Future replaceByFee(String hash, int newFee) async { - try { - final bundle = await getTransactionExpanded(hash: hash); - - final utxos = []; - final outputs = []; - List privateKeys = []; - - var allInputsAmount = 0; - String? memo; - - // Add original inputs - for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { - final input = bundle.originalTransaction.inputs[i]; - final inputTransaction = bundle.ins[i]; - final vout = input.txIndex; - final outTransaction = inputTransaction.outputs[vout]; - final address = addressFromOutputScript(outTransaction.scriptPubKey, network); - allInputsAmount += outTransaction.amount.toInt(); - - final addressRecord = - walletAddresses.allAddresses.firstWhere((element) => element.address == address); - final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network); - final privkey = generateECPrivate( - hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: addressRecord.index, - network: network); - - privateKeys.add(privkey); - - utxos.add( - UtxoWithAddress( - utxo: BitcoinUtxo( - txHash: input.txId, - value: outTransaction.amount, - vout: vout, - scriptType: _getScriptType(btcAddress), - ), - ownerDetails: - UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: btcAddress), - ), - ); - } - - // Add original outputs - for (final out in bundle.originalTransaction.outputs) { - final script = out.scriptPubKey.script; - if (script.contains('OP_RETURN') && memo == null) { - final index = script.indexOf('OP_RETURN'); - if (index + 1 <= script.length) { - try { - final opReturnData = script[index + 1].toString(); - memo = utf8.decode(HEX.decode(opReturnData)); - continue; - } catch (_) { - throw Exception('Cannot decode OP_RETURN data'); - } - } - } - - final address = addressFromOutputScript(out.scriptPubKey, network); - final btcAddress = RegexUtils.addressTypeFromStr(address, network); - outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(out.amount.toInt()))); - } - - // Calculate the total amount and fees - int totalOutAmount = - outputs.fold(0, (previousValue, output) => previousValue + output.value.toInt()); - int currentFee = allInputsAmount - totalOutAmount; - int remainingFee = newFee - currentFee; - - if (remainingFee <= 0) { - throw Exception("New fee must be higher than the current fee."); - } - - // Deduct fee from change outputs first, if possible - if (remainingFee > 0) { - final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); - for (int i = outputs.length - 1; i >= 0; i--) { - final output = outputs[i]; - final isChange = changeAddresses - .any((element) => element.address == output.address.toAddress(network)); - - if (isChange) { - int outputAmount = output.value.toInt(); - if (outputAmount > _dustAmount) { - int deduction = (outputAmount - _dustAmount >= remainingFee) - ? remainingFee - : outputAmount - _dustAmount; - outputs[i] = BitcoinOutput( - address: output.address, value: BigInt.from(outputAmount - deduction)); - remainingFee -= deduction; - - if (remainingFee <= 0) break; - } - } - } - } - - // If still not enough, add UTXOs until the fee is covered - if (remainingFee > 0) { - final unusedUtxos = unspentCoins - .where((utxo) => utxo.isSending && !utxo.isFrozen && utxo.confirmations! > 0) - .toList(); - - for (final utxo in unusedUtxos) { - final address = RegexUtils.addressTypeFromStr(utxo.address, network); - final privkey = generateECPrivate( - hd: utxo.bitcoinAddressRecord.isHidden - ? walletAddresses.sideHd - : walletAddresses.mainHd, - index: utxo.bitcoinAddressRecord.index, - network: network, - ); - privateKeys.add(privkey); - - utxos.add(UtxoWithAddress( - utxo: BitcoinUtxo( - txHash: utxo.hash, - value: BigInt.from(utxo.value), - vout: utxo.vout, - scriptType: _getScriptType(address)), - ownerDetails: - UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address), - )); - - allInputsAmount += utxo.value; - remainingFee -= utxo.value; - - if (remainingFee < 0) { - final changeOutput = outputs.firstWhereOrNull((output) => walletAddresses.allAddresses - .any((addr) => addr.address == output.address.toAddress(network))); - if (changeOutput != null) { - final newValue = changeOutput.value.toInt() + (-remainingFee); - outputs[outputs.indexOf(changeOutput)] = - BitcoinOutput(address: changeOutput.address, value: BigInt.from(newValue)); - } else { - final changeAddress = await walletAddresses.getChangeAddress(); - outputs.add(BitcoinOutput( - address: RegexUtils.addressTypeFromStr(changeAddress.address, network), - value: BigInt.from(-remainingFee))); - } - - remainingFee = 0; - break; - } - - if (remainingFee <= 0) break; - } - } - - // Deduct from the receiver's output if remaining fee is still greater than 0 - if (remainingFee > 0) { - for (int i = 0; i < outputs.length; i++) { - final output = outputs[i]; - int outputAmount = output.value.toInt(); - - if (outputAmount > _dustAmount) { - int deduction = (outputAmount - _dustAmount >= remainingFee) - ? remainingFee - : outputAmount - _dustAmount; - - outputs[i] = BitcoinOutput( - address: output.address, value: BigInt.from(outputAmount - deduction)); - remainingFee -= deduction; - - if (remainingFee <= 0) break; - } - } - } - - // Final check if the remaining fee couldn't be deducted - if (remainingFee > 0) { - throw Exception("Not enough funds to cover the fee."); - } - - // Identify all change outputs - final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); - final List changeOutputs = outputs - .where((output) => changeAddresses - .any((element) => element.address == output.address.toAddress(network))) - .toList(); - - int totalChangeAmount = - changeOutputs.fold(0, (sum, output) => sum + output.value.toInt()); - - // The final amount that the receiver will receive - int sendingAmount = allInputsAmount - newFee - totalChangeAmount; - - final txb = BitcoinTransactionBuilder( - utxos: utxos, - outputs: outputs, - fee: BigInt.from(newFee), - network: network, - memo: memo, - outputOrdering: BitcoinOrdering.none, - enableRBF: true, - ); - - final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { - final key = - privateKeys.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey); - if (key == null) { - throw Exception("Cannot find private key"); - } - - if (utxo.utxo.isP2tr()) { - return key.signTapRoot(txDigest, sighash: sighash); - } else { - return key.signInput(txDigest, sigHash: sighash); - } - }); - - return PendingBitcoinTransaction( - transaction, - type, - electrumClient: electrumClient, - amount: sendingAmount, - fee: newFee, - network: network, - hasChange: changeOutputs.isNotEmpty, - feeRate: newFee.toString(), - )..addListener((transaction) async { - transactionHistory.transactions.values.forEach((tx) { - if (tx.id == hash) { - tx.isReplaced = true; - tx.isPending = false; - transactionHistory.addOne(tx); - } - }); - transactionHistory.addOne(transaction); - await updateBalance(); - await updateAllUnspents(); - }); - } catch (e) { - throw e; + print(e.toString()); } } Future getTransactionExpanded( - {required String hash, int? height}) async { - String transactionHex; - int? time; - int? confirmations; + {required String hash, required int height}) async { + final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); + final transactionHex = verboseTransaction['hex'] as String; + final original = bitcoin.Transaction.fromHex(transactionHex); + final ins = []; + final time = verboseTransaction['time'] as int?; + final confirmations = verboseTransaction['confirmations'] as int? ?? 0; - final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); - - if (verboseTransaction.isEmpty) { - transactionHex = await electrumClient.getTransactionHex(hash: hash); - - if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) { - try { - final blockHash = await ProxyWrapper() - .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height")) - .timeout(Duration(seconds: 15)); - - if (blockHash.statusCode == 200 && - blockHash.body.isNotEmpty && - jsonDecode(blockHash.body) != null) { - final blockResponse = await ProxyWrapper() - .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block/${blockHash}")) - .timeout(Duration(seconds: 15)); - - if (blockResponse.statusCode == 200 && - blockResponse.body.isNotEmpty && - jsonDecode(blockResponse.body)['timestamp'] != null) { - time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString()); - } - } - } catch (_) {} - } - } else { - transactionHex = verboseTransaction['hex'] as String; - time = verboseTransaction['time'] as int?; - confirmations = verboseTransaction['confirmations'] as int?; + for (final vin in original.ins) { + final id = HEX.encode(vin.hash!.reversed.toList()); + final txHex = await electrumClient.getTransactionHex(hash: id); + final tx = bitcoin.Transaction.fromHex(txHex); + ins.add(tx); } - if (height != null) { - if (time == null && height > 0) { - time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round(); - } - - if (confirmations == null) { - final tip = await getUpdatedChainTip(); - if (tip > 0 && height > 0) { - // Add one because the block itself is the first confirmation - confirmations = tip - height + 1; - } - } - } - - final original = BtcTransaction.fromRaw(transactionHex); - final ins = []; - - for (final vin in original.inputs) { - final verboseTransaction = await electrumClient.getTransactionVerbose(hash: vin.txId); - - final String inputTransactionHex; - - if (verboseTransaction.isEmpty) { - inputTransactionHex = await electrumClient.getTransactionHex(hash: hash); - } else { - inputTransactionHex = verboseTransaction['hex'] as String; - } - - ins.add(BtcTransaction.fromRaw(inputTransactionHex)); - } - - return ElectrumTransactionBundle( - original, - ins: ins, - time: time, - confirmations: confirmations ?? 0, - ); + return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations); } Future fetchTransactionInfo( - {required String hash, int? height, bool? retryOnFailure}) async { + {required String hash, required int height}) async { try { - return ElectrumTransactionInfo.fromElectrumBundle( - await getTransactionExpanded(hash: hash, height: height), - walletInfo.type, - network, - addresses: addressesSet, - height: height, - ); - } catch (e) { - if (e is FormatException && retryOnFailure == true) { - await Future.delayed(const Duration(seconds: 2)); - return fetchTransactionInfo(hash: hash, height: height); - } + final tx = await getTransactionExpanded(hash: hash, height: height); + final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); + return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType, + addresses: addresses, height: height); + } catch (_) { return null; } } - bool isMine(Script script) { - final derivedAddress = addressFromOutputScript(script, network); - return addressesSet.contains(derivedAddress); - } - @override Future> fetchTransactions() async { - try { - final Map historiesWithDetails = {}; - - if (type == WalletType.bitcoin) { - await Future.wait(BITCOIN_ADDRESS_TYPES - .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); - } else if (type == WalletType.bitcoinCash) { - await Future.wait(BITCOIN_CASH_ADDRESS_TYPES - .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); - } else if (type == WalletType.litecoin) { - await Future.wait(LITECOIN_ADDRESS_TYPES - .where((type) => type != SegwitAddresType.mweb) - .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); - } - - transactionHistory.transactions.values.forEach((tx) async { - final isPendingSilentPaymentUtxo = - (tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null; - - if (isPendingSilentPaymentUtxo) { - final info = - await fetchTransactionInfo(hash: tx.id, height: tx.height, retryOnFailure: true); - - if (info != null) { - tx.confirmations = info.confirmations; - tx.isPending = tx.confirmations == 0; - transactionHistory.addOne(tx); - await transactionHistory.save(); - } + final addressHashes = {}; + final normalizedHistories = >[]; + walletAddresses.addresses.forEach((addressRecord) { + final sh = scriptHash(addressRecord.address, networkType: networkType); + addressHashes[sh] = addressRecord; + }); + final histories = addressHashes.keys.map((scriptHash) => + electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); + final historyResults = await Future.wait(histories); + historyResults.forEach((history) { + history.entries.forEach((historyItem) { + if (historyItem.value.isNotEmpty) { + final address = addressHashes[historyItem.key]; + address?.setAsUsed(); + normalizedHistories.addAll(historyItem.value); } }); - - return historiesWithDetails; - } catch (e) { - printV("fetchTransactions $e"); - return {}; - } - } - - Future fetchTransactionsForAddressType( - Map historiesWithDetails, - BitcoinAddressType type, - ) async { - final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); - final hiddenAddresses = addressesByType.where((addr) => addr.isHidden == true); - final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); - walletAddresses.hiddenAddresses.addAll(hiddenAddresses.map((e) => e.address)); - await walletAddresses.saveAddressesInBox(); - await Future.wait(addressesByType.map((addressRecord) async { - final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip()); - - if (history.isNotEmpty) { - addressRecord.txCount = history.length; - historiesWithDetails.addAll(history); - - final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses; - final isUsedAddressUnderGap = matchedAddresses.toList().indexOf(addressRecord) >= - matchedAddresses.length - - (addressRecord.isHidden - ? ElectrumWalletAddressesBase.defaultChangeAddressesCount - : ElectrumWalletAddressesBase.defaultReceiveAddressesCount); - - if (isUsedAddressUnderGap) { - final prevLength = walletAddresses.allAddresses.length; - - // Discover new addresses for the same address type until the gap limit is respected - await walletAddresses.discoverAddresses( - matchedAddresses.toList(), - addressRecord.isHidden, - (address) async { - await subscribeForUpdates(); - return _fetchAddressHistory(address, await getCurrentChainTip()) - .then((history) => history.isNotEmpty ? address.address : null); - }, - type: type, - ); - - final newLength = walletAddresses.allAddresses.length; - - if (newLength > prevLength) { - await fetchTransactionsForAddressType(historiesWithDetails, type); - } - } + }); + final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) { + try { + return fetchTransactionInfo( + hash: transaction['tx_hash'] as String, height: transaction['height'] as int); + } catch (_) { + return Future.value(null); } })); - } - - Future> _fetchAddressHistory( - BitcoinAddressRecord addressRecord, int? currentHeight) async { - String txid = ""; - - try { - final Map historiesWithDetails = {}; - - final history = await electrumClient.getHistory(addressRecord.getScriptHash(network)); - - if (history.isNotEmpty) { - addressRecord.setAsUsed(); - - await Future.wait(history.map((transaction) async { - txid = transaction['tx_hash'] as String; - final height = transaction['height'] as int; - final storedTx = transactionHistory.transactions[txid]; - - if (storedTx != null) { - if (height > 0) { - storedTx.height = height; - // the tx's block itself is the first confirmation so add 1 - if ((currentHeight ?? 0) > 0) { - storedTx.confirmations = currentHeight! - height + 1; - } - storedTx.isPending = storedTx.confirmations == 0; - } - - historiesWithDetails[txid] = storedTx; - } else { - final tx = await fetchTransactionInfo(hash: txid, height: height, retryOnFailure: true); - - if (tx != null) { - historiesWithDetails[txid] = tx; - - // Got a new transaction fetched, add it to the transaction history - // instead of waiting all to finish, and next time it will be faster - - if (this is LitecoinWallet) { - // if we have a peg out transaction with the same value - // that matches this received transaction, mark it as being from a peg out: - for (final tx2 in transactionHistory.transactions.values) { - final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs(); - // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other - if (tx2.additionalInfo["isPegOut"] == true && - tx2.amount == tx.amount && - heightDiff <= 5) { - tx.additionalInfo["fromPegOut"] = true; - } - } - } - transactionHistory.addOne(tx); - await transactionHistory.save(); - } - } - - return Future.value(null); - })); + return historiesWithDetails + .fold>({}, (acc, tx) { + if (tx == null) { + return acc; } - - return historiesWithDetails; - } catch (e, stacktrace) { - _onError?.call(FlutterErrorDetails( - exception: "$txid - $e", - stack: stacktrace, - library: this.runtimeType.toString(), - )); - return {}; - } + acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; + return acc; + }); } Future updateTransactions() async { - printV("updateTransactions() called!"); try { if (_isTransactionUpdating) { return; } - currentChainTip = await getUpdatedChainTip(); - - bool updated = false; - transactionHistory.transactions.values.forEach((tx) { - if ((tx.height ?? 0) > 0 && (currentChainTip ?? 0) > 0) { - var confirmations = currentChainTip! - tx.height! + 1; - if (confirmations < 0) { - // if our chain tip is outdated then it could lead to negative confirmations so this is just a failsafe: - confirmations = 0; - } - if (confirmations != tx.confirmations) { - updated = true; - tx.confirmations = confirmations; - transactionHistory.addOne(tx); - } - } - }); - - if (updated) { - await transactionHistory.save(); - } _isTransactionUpdating = true; - await fetchTransactions(); + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); walletAddresses.updateReceiveAddresses(); + await transactionHistory.save(); _isTransactionUpdating = false; } catch (e, stacktrace) { - printV(stacktrace); - printV(e); + print(stacktrace); + print(e); _isTransactionUpdating = false; } } - Future subscribeForUpdates() async { - final unsubscribedScriptHashes = walletAddresses.allAddresses.where( - (address) => - !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) && - address.type != SegwitAddresType.mweb, - ); - - await Future.wait(unsubscribedScriptHashes.map((address) async { - final sh = address.getScriptHash(network); - if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) { - try { - await _scripthashesUpdateSubject[sh]?.close(); - } catch (e) { - printV("failed to close: $e"); - } - } - try { - _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh); - } catch (e) { - printV("failed scripthashUpdate: $e"); - } + void _subscribeForUpdates() { + scriptHashes.forEach((sh) async { + await _scripthashesUpdateSubject[sh]?.close(); + _scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh]?.listen((event) async { try { - await updateUnspentsForAddress(address); - + await updateUnspent(); await updateBalance(); - - await _fetchAddressHistory(address, await getCurrentChainTip()); + await updateTransactions(); } catch (e, s) { - printV("sub error: $e"); + print(e.toString()); _onError?.call(FlutterErrorDetails( exception: e, stack: s, library: this.runtimeType.toString(), )); } - }, onError: (e, s) { - printV("sub_listen error: $e $s"); }); - })); + }); } - Future fetchBalances() async { - final addresses = walletAddresses.allAddresses - .where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress) - .toList(); + Future _fetchBalances() async { + final addresses = walletAddresses.addresses.toList(); final balanceFutures = >>[]; for (var i = 0; i < addresses.length; i++) { - final addressRecord = addresses[i]; - final sh = addressRecord.getScriptHash(network); + final addressRecord = addresses[i] ; + final sh = scriptHash(addressRecord.address, networkType: networkType); final balanceFuture = electrumClient.getBalance(sh); balanceFutures.add(balanceFuture); } var totalFrozen = 0; - var totalConfirmed = 0; - var totalUnconfirmed = 0; - - if (hasSilentPaymentsScanning) { - // Add values from unspent coins that are not fetched by the address list - // i.e. scanned silent payments - transactionHistory.transactions.values.forEach((tx) { - if (tx.unspents != null) { - tx.unspents!.forEach((unspent) { - if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { - if (unspent.isFrozen) totalFrozen += unspent.value; - totalConfirmed += unspent.value; - } - }); - } - }); - } - unspentCoinsInfo.values.forEach((info) { unspentCoins.forEach((element) { - if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) return; - if (element.hash == info.hash && - element.vout == info.vout && + info.isFrozen && element.bitcoinAddressRecord.address == info.address && element.value == info.value) { - if (info.isFrozen) { - totalFrozen += element.value; - } + totalFrozen += element.value; } }); }); final balances = await Future.wait(balanceFutures); - - if (balances.isNotEmpty && balances.first['confirmed'] == null) { - // if we got null balance responses from the server, set our connection status to lost and return our last known balance: - printV("got null balance responses from the server, setting connection status to lost"); - syncStatus = LostConnectionSyncStatus(); - return balance[currency] ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); - } + var totalConfirmed = 0; + var totalUnconfirmed = 0; for (var i = 0; i < balances.length; i++) { final addressRecord = addresses[i]; @@ -2268,570 +687,42 @@ abstract class ElectrumWalletBase totalConfirmed += confirmed; totalUnconfirmed += unconfirmed; - addressRecord.balance = confirmed + unconfirmed; if (confirmed > 0 || unconfirmed > 0) { addressRecord.setAsUsed(); } } return ElectrumBalance( - confirmed: totalConfirmed, - unconfirmed: totalUnconfirmed, - frozen: totalFrozen, - ); + confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen); } Future updateBalance() async { - printV("updateBalance() called!"); - balance[currency] = await fetchBalances(); + balance[currency] = await _fetchBalances(); await save(); } + String getChangeAddress() { + const minCountOfHiddenAddresses = 5; + final random = Random(); + var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList(); + + if (addresses.length < minCountOfHiddenAddresses) { + addresses = walletAddresses.addresses.toList(); + } + + return addresses[random.nextInt(addresses.length)].address; + } + @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; @override - Future signMessage(String message, {String? address = null}) async { + String signMessage(String message, {String? address = null}) { final index = address != null - ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index + ? walletAddresses.addresses.firstWhere((element) => element.address == address).index : null; - final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); - final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex()); - - String messagePrefix = '\x18Bitcoin Signed Message:\n'; - final hexEncoded = priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix); - final decodedSig = hex.decode(hexEncoded); - return base64Encode(decodedSig); - } - - @override - Future verifyMessage(String message, String signature, {String? address = null}) async { - if (address == null) { - return false; - } - - List sigDecodedBytes = []; - - if (signature.endsWith('=')) { - sigDecodedBytes = base64.decode(signature); - } else { - sigDecodedBytes = hex.decode(signature); - } - - if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) { - throw ArgumentException( - "signature must be 64 bytes without recover-id or 65 bytes with recover-id"); - } - - String messagePrefix = '\x18Bitcoin Signed Message:\n'; - final messageHash = QuickCrypto.sha256Hash( - BitcoinSignerUtils.magicMessage(utf8.encode(message), messagePrefix)); - - List correctSignature = - sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes); - List rBytes = correctSignature.sublist(0, 32); - List sBytes = correctSignature.sublist(32); - final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes)); - - List possibleRecoverIds = [0, 1]; - - final baseAddress = RegexUtils.addressTypeFromStr(address, network); - - for (int recoveryId in possibleRecoverIds) { - final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId); - - final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes()); - - String? recoveredAddress; - - if (baseAddress is P2pkAddress) { - recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network); - } else if (baseAddress is P2pkhAddress) { - recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network); - } else if (baseAddress is P2wshAddress) { - recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network); - } else if (baseAddress is P2wpkhAddress) { - recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network); - } - - if (recoveredAddress == address) { - return true; - } - } - - return false; - } - - Future _setInitialHeight() async { - if (_chainTipUpdateSubject != null) return; - - currentChainTip = await getUpdatedChainTip(); - - if ((currentChainTip == null || currentChainTip! == 0) && walletInfo.restoreHeight == 0) { - await walletInfo.updateRestoreHeight(currentChainTip!); - } - - _chainTipUpdateSubject = electrumClient.chainTipSubscribe(); - _chainTipUpdateSubject?.listen((e) async { - final event = e as Map; - final height = int.tryParse(event['height'].toString()); - - if (height != null) { - currentChainTip = height; - - if (alwaysScan == true && syncStatus is SyncedSyncStatus) { - _setListeners(walletInfo.restoreHeight); - } - } - }); - } - - static String _hardenedDerivationPath(String derivationPath) => - derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1); - - @action - void _onConnectionStatusChange(ConnectionStatus status) { - switch (status) { - case ConnectionStatus.connected: - if (syncStatus is NotConnectedSyncStatus || - syncStatus is LostConnectionSyncStatus || - syncStatus is ConnectingSyncStatus) { - syncStatus = ConnectedSyncStatus(); - } - - break; - case ConnectionStatus.disconnected: - if (syncStatus is! NotConnectedSyncStatus && - syncStatus is! ConnectingSyncStatus && - syncStatus is! SyncronizingSyncStatus) { - syncStatus = NotConnectedSyncStatus(); - } - break; - case ConnectionStatus.failed: - if (syncStatus is! LostConnectionSyncStatus) { - syncStatus = LostConnectionSyncStatus(); - } - break; - case ConnectionStatus.connecting: - if (syncStatus is! ConnectingSyncStatus) { - syncStatus = ConnectingSyncStatus(); - } - break; - default: - } - } - - void _syncStatusReaction(SyncStatus syncStatus) async { - printV("SYNC_STATUS_CHANGE: ${syncStatus}"); - if (syncStatus is SyncingSyncStatus) { - return; - } - - if (syncStatus is NotConnectedSyncStatus || syncStatus is LostConnectionSyncStatus) { - // Needs to re-subscribe to all scripthashes when reconnected - _scripthashesUpdateSubject = {}; - - if (_isTryingToConnect) return; - - _isTryingToConnect = true; - - Timer(Duration(seconds: 5), () { - if (this.syncStatus is NotConnectedSyncStatus || - this.syncStatus is LostConnectionSyncStatus) { - this.electrumClient.connectToUri( - node!.uri, - useSSL: node!.useSSL ?? false, - ); - } - _isTryingToConnect = false; - }); - } - - // Message is shown on the UI for 3 seconds, revert to synced - if (syncStatus is SyncedTipSyncStatus) { - Timer(Duration(seconds: 3), () { - if (this.syncStatus is SyncedTipSyncStatus) this.syncStatus = SyncedSyncStatus(); - }); - } - } - - void _updateInputsAndOutputs(ElectrumTransactionInfo tx, ElectrumTransactionBundle bundle) { - tx.inputAddresses = tx.inputAddresses?.where((address) => address.isNotEmpty).toList(); - - if (tx.inputAddresses == null || - tx.inputAddresses!.isEmpty || - tx.outputAddresses == null || - tx.outputAddresses!.isEmpty) { - List inputAddresses = []; - List outputAddresses = []; - - for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) { - final input = bundle.originalTransaction.inputs[i]; - final inputTransaction = bundle.ins[i]; - final vout = input.txIndex; - final outTransaction = inputTransaction.outputs[vout]; - final address = addressFromOutputScript(outTransaction.scriptPubKey, network); - - if (address.isNotEmpty) inputAddresses.add(address); - } - - for (int i = 0; i < bundle.originalTransaction.outputs.length; i++) { - final out = bundle.originalTransaction.outputs[i]; - final address = addressFromOutputScript(out.scriptPubKey, network); - - if (address.isNotEmpty) outputAddresses.add(address); - - // Check if the script contains OP_RETURN - final script = out.scriptPubKey.script; - if (script.contains('OP_RETURN')) { - final index = script.indexOf('OP_RETURN'); - if (index + 1 <= script.length) { - try { - final opReturnData = script[index + 1].toString(); - final decodedString = utf8.decode(HEX.decode(opReturnData)); - outputAddresses.add('OP_RETURN:$decodedString'); - } catch (_) { - outputAddresses.add('OP_RETURN:'); - } - } - } - } - tx.inputAddresses = inputAddresses; - tx.outputAddresses = outputAddresses; - - transactionHistory.addOne(tx); - } - } - - @override - String formatCryptoAmount(String amount) { - final amountInt = int.parse(amount); - return bitcoinAmountToString(amount: amountInt); + return index == null + ? base64Encode(hd.sign(message)) + : base64Encode(hd.derive(index).sign(message)); } } - -class ScanNode { - final Uri uri; - final bool? useSSL; - - ScanNode(this.uri, this.useSSL); -} - -class ScanData { - final SendPort sendPort; - final SilentPaymentOwner silentAddress; - final int height; - final ScanNode? node; - final BasedUtxoNetwork network; - final int chainTip; - final ElectrumClient electrumClient; - final List transactionHistoryIds; - final Map labels; - final List labelIndexes; - final bool isSingleScan; - - ScanData({ - required this.sendPort, - required this.silentAddress, - required this.height, - required this.node, - required this.network, - required this.chainTip, - required this.electrumClient, - required this.transactionHistoryIds, - required this.labels, - required this.labelIndexes, - required this.isSingleScan, - }); - - factory ScanData.fromHeight(ScanData scanData, int newHeight) { - return ScanData( - sendPort: scanData.sendPort, - silentAddress: scanData.silentAddress, - height: newHeight, - node: scanData.node, - network: scanData.network, - chainTip: scanData.chainTip, - transactionHistoryIds: scanData.transactionHistoryIds, - electrumClient: scanData.electrumClient, - labels: scanData.labels, - labelIndexes: scanData.labelIndexes, - isSingleScan: scanData.isSingleScan, - ); - } -} - -class SyncResponse { - final int height; - final SyncStatus syncStatus; - - SyncResponse(this.height, this.syncStatus); -} - -Future startRefresh(ScanData scanData) async { - int syncHeight = scanData.height; - int initialSyncHeight = syncHeight; - - BehaviorSubject? tweaksSubscription = null; - - final electrumClient = scanData.electrumClient; - await electrumClient.connectToUri( - scanData.node?.uri ?? Uri.parse("tcp://electrs.cakewallet.com:50001"), - useSSL: scanData.node?.useSSL ?? false, - ); - - int getCountPerRequest(int syncHeight) { - if (scanData.isSingleScan) { - return 1; - } - - final amountLeft = scanData.chainTip - syncHeight + 1; - return amountLeft; - } - - if (tweaksSubscription == null) { - final receiver = Receiver( - scanData.silentAddress.b_scan.toHex(), - scanData.silentAddress.B_spend.toHex(), - scanData.network == BitcoinNetwork.testnet, - scanData.labelIndexes, - scanData.labelIndexes.length, - ); - - // Initial status UI update, send how many blocks in total to scan - final initialCount = getCountPerRequest(syncHeight); - scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight))); - - tweaksSubscription = await electrumClient.tweaksSubscribe( - height: syncHeight, - count: initialCount, - ); - - Future listenFn(t) async { - final tweaks = t as Map; - final msg = tweaks["message"]; - // success or error msg - final noData = msg != null; - - if (noData) { - // re-subscribe to continue receiving messages, starting from the next unscanned height - final nextHeight = syncHeight + 1; - final nextCount = getCountPerRequest(nextHeight); - - if (nextCount > 0) { - tweaksSubscription?.close(); - - final nextTweaksSubscription = electrumClient.tweaksSubscribe( - height: nextHeight, - count: nextCount, - ); - nextTweaksSubscription?.listen(listenFn); - } - - return; - } - - // Continuous status UI update, send how many blocks left to scan - final syncingStatus = scanData.isSingleScan - ? SyncingSyncStatus(1, 0) - : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); - scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); - - final blockHeight = tweaks.keys.first; - final tweakHeight = int.parse(blockHeight); - - try { - final blockTweaks = tweaks[blockHeight] as Map; - - for (var j = 0; j < blockTweaks.keys.length; j++) { - final txid = blockTweaks.keys.elementAt(j); - final details = blockTweaks[txid] as Map; - final outputPubkeys = (details["output_pubkeys"] as Map); - final tweak = details["tweak"].toString(); - - try { - // scanOutputs called from rust here - final addToWallet = scanOutputs( - outputPubkeys.values.toList(), - tweak, - receiver, - ); - - if (addToWallet.isEmpty) { - // no results tx, continue to next tx - continue; - } - - // placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) - final txInfo = ElectrumTransactionInfo( - WalletType.bitcoin, - id: txid, - height: tweakHeight, - amount: 0, - fee: 0, - direction: TransactionDirection.incoming, - isPending: false, - isReplaced: false, - date: scanData.network == BitcoinNetwork.mainnet - ? getDateByBitcoinHeight(tweakHeight) - : DateTime.now(), - confirmations: scanData.chainTip - tweakHeight + 1, - unspents: [], - isReceivedSilentPayment: true, - ); - - addToWallet.forEach((label, value) { - (value as Map).forEach((output, tweak) { - final t_k = tweak.toString(); - - final receivingOutputAddress = ECPublic.fromHex(output) - .toTaprootAddress(tweak: false) - .toAddress(scanData.network); - - int? amount; - int? pos; - outputPubkeys.entries.firstWhere((k) { - final isMatchingOutput = k.value[0] == output; - if (isMatchingOutput) { - amount = int.parse(k.value[1].toString()); - pos = int.parse(k.key.toString()); - return true; - } - return false; - }); - - final receivedAddressRecord = BitcoinSilentPaymentAddressRecord( - receivingOutputAddress, - index: 0, - isHidden: false, - isUsed: true, - network: scanData.network, - silentPaymentTweak: t_k, - type: SegwitAddresType.p2tr, - txCount: 1, - balance: amount!, - ); - - final unspent = BitcoinSilentPaymentsUnspent( - receivedAddressRecord, - txid, - amount!, - pos!, - silentPaymentTweak: t_k, - silentPaymentLabel: label == "None" ? null : label, - ); - - txInfo.unspents!.add(unspent); - txInfo.amount += unspent.value; - }); - }); - - scanData.sendPort.send({txInfo.id: txInfo}); - } catch (_) {} - } - } catch (_) {} - - syncHeight = tweakHeight; - - if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) { - if (tweakHeight >= scanData.chainTip) - scanData.sendPort.send(SyncResponse( - syncHeight, - SyncedTipSyncStatus(scanData.chainTip), - )); - - if (scanData.isSingleScan) { - scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus())); - } - - await tweaksSubscription!.close(); - await electrumClient.close(); - } - } - - tweaksSubscription?.listen(listenFn); - } - - if (tweaksSubscription == null) { - return scanData.sendPort.send( - SyncResponse(syncHeight, UnsupportedSyncStatus()), - ); - } -} - -class EstimatedTxResult { - EstimatedTxResult({ - required this.utxos, - required this.inputPrivKeyInfos, - required this.publicKeys, - required this.fee, - required this.amount, - required this.hasChange, - required this.isSendAll, - this.memo, - required this.spendsSilentPayment, - required this.spendsUnconfirmedTX, - }); - - final List utxos; - final List inputPrivKeyInfos; - final Map publicKeys; // PubKey to derivationPath - final int fee; - final int amount; - final bool spendsSilentPayment; - - // final bool sendsToSilentPayment; - final bool hasChange; - final bool isSendAll; - final String? memo; - final bool spendsUnconfirmedTX; -} - -class PublicKeyWithDerivationPath { - const PublicKeyWithDerivationPath(this.publicKey, this.derivationPath); - - final String derivationPath; - final String publicKey; -} - -BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { - if (type is P2pkhAddress) { - return P2pkhAddressType.p2pkh; - } else if (type is P2shAddress) { - return P2shAddressType.p2wpkhInP2sh; - } else if (type is P2wshAddress) { - return SegwitAddresType.p2wsh; - } else if (type is P2trAddress) { - return SegwitAddresType.p2tr; - } else if (type is MwebAddress) { - return SegwitAddresType.mweb; - } else if (type is SilentPaymentsAddresType) { - return SilentPaymentsAddresType.p2sp; - } else { - return SegwitAddresType.p2wpkh; - } -} - -class UtxoDetails { - final List availableInputs; - final List unconfirmedCoins; - final List utxos; - final List vinOutpoints; - final List inputPrivKeyInfos; - final Map publicKeys; // PubKey to derivationPath - final int allInputsAmount; - final bool spendsSilentPayment; - final bool spendsUnconfirmedTX; - - UtxoDetails({ - required this.availableInputs, - required this.unconfirmedCoins, - required this.utxos, - required this.vinOutpoints, - required this.inputPrivKeyInfos, - required this.publicKeys, - required this.allInputsAmount, - required this.spendsSilentPayment, - required this.spendsUnconfirmedTX, - }); -} diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 614a06a3b..ab99a875c 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,12 +1,8 @@ -import 'dart:io' show Platform; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:bitbox/bitbox.dart' as bitbox; import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/electrum.dart'; +import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -14,259 +10,93 @@ import 'package:mobx/mobx.dart'; part 'electrum_wallet_addresses.g.dart'; -class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; - -const List BITCOIN_ADDRESS_TYPES = [ - SegwitAddresType.p2wpkh, - P2pkhAddressType.p2pkh, - SegwitAddresType.p2tr, - SegwitAddresType.p2wsh, - P2shAddressType.p2wpkhInP2sh, -]; - -const List LITECOIN_ADDRESS_TYPES = [ - SegwitAddresType.p2wpkh, - SegwitAddresType.mweb, -]; - -const List BITCOIN_CASH_ADDRESS_TYPES = [ - P2pkhAddressType.p2pkh, -]; +class ElectrumWalletAddresses = ElectrumWalletAddressesBase + with _$ElectrumWalletAddresses; abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { - ElectrumWalletAddressesBase( - WalletInfo walletInfo, { - required this.mainHd, - required this.sideHd, - required this.network, - required this.isHardwareWallet, - List? initialAddresses, - Map? initialRegularAddressIndex, - Map? initialChangeAddressIndex, - List? initialSilentAddresses, - int initialSilentAddressIndex = 0, - List? initialMwebAddresses, - Bip32Slip10Secp256k1? masterHd, - BitcoinAddressType? initialAddressPageType, - }) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()), - addressesByReceiveType = - ObservableList.of(([]).toSet()), - receiveAddresses = ObservableList.of((initialAddresses ?? []) + ElectrumWalletAddressesBase(WalletInfo walletInfo, + {required this.mainHd, + required this.sideHd, + required this.electrumClient, + required this.networkType, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : addresses = ObservableList.of( + (initialAddresses ?? []).toSet()), + receiveAddresses = ObservableList.of( + (initialAddresses ?? []) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), - changeAddresses = ObservableList.of((initialAddresses ?? []) + .toSet()), + changeAddresses = ObservableList.of( + (initialAddresses ?? []) .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed) - .toSet()), - currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {}, - currentChangeAddressIndexByType = initialChangeAddressIndex ?? {}, - _addressPageType = initialAddressPageType ?? - (walletInfo.addressPageType != null - ? BitcoinAddressType.fromValue(walletInfo.addressPageType!) - : SegwitAddresType.p2wpkh), - silentAddresses = ObservableList.of( - (initialSilentAddresses ?? []).toSet()), - currentSilentAddressIndex = initialSilentAddressIndex, - mwebAddresses = - ObservableList.of((initialMwebAddresses ?? []).toSet()), - super(walletInfo) { - if (masterHd != null) { - silentAddress = SilentPaymentOwner.fromPrivateKeys( - b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()), - b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()), - network: network, - ); - - if (silentAddresses.length == 0) { - silentAddresses.add(BitcoinSilentPaymentAddressRecord( - silentAddress.toString(), - index: 0, - isHidden: false, - name: "", - silentPaymentTweak: null, - network: network, - type: SilentPaymentsAddresType.p2sp, - )); - silentAddresses.add(BitcoinSilentPaymentAddressRecord( - silentAddress!.toLabeledSilentPaymentAddress(0).toString(), - index: 0, - isHidden: true, - name: "", - silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)), - network: network, - type: SilentPaymentsAddresType.p2sp, - )); - } - } - - updateAddressesByMatch(); - } + .toSet()), + currentReceiveAddressIndex = initialRegularAddressIndex, + currentChangeAddressIndex = initialChangeAddressIndex, + super(walletInfo); static const defaultReceiveAddressesCount = 22; static const defaultChangeAddressesCount = 17; static const gap = 20; - final ObservableList _addresses; - final ObservableList addressesByReceiveType; + static String toCashAddr(String address) => bitbox.Address.toCashAddress(address); + + final ObservableList addresses; final ObservableList receiveAddresses; final ObservableList changeAddresses; - // TODO: add this variable in `bitcoin_wallet_addresses` and just add a cast in cw_bitcoin to use it - final ObservableList silentAddresses; - // TODO: add this variable in `litecoin_wallet_addresses` and just add a cast in cw_bitcoin to use it - final ObservableList mwebAddresses; - final BasedUtxoNetwork network; - final Bip32Slip10Secp256k1 mainHd; - final Bip32Slip10Secp256k1 sideHd; - final bool isHardwareWallet; - - @observable - SilentPaymentOwner? silentAddress; - - @observable - late BitcoinAddressType _addressPageType; - - @computed - BitcoinAddressType get addressPageType => _addressPageType; - - @observable - String? activeSilentAddress; - - @computed - List get allAddresses => _addresses; + final ElectrumClient electrumClient; + final bitcoin.NetworkType networkType; + final bitcoin.HDWallet mainHd; + final bitcoin.HDWallet sideHd; @override @computed String get address { - if (addressPageType == SilentPaymentsAddresType.p2sp) { - if (activeSilentAddress != null) { - return activeSilentAddress!; - } - - return silentAddress.toString(); + if (receiveAddresses.isEmpty) { + final address = generateNewAddress().address; + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address; } + final receiveAddress = receiveAddresses.first.address; - final typeMatchingAddresses = _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList(); - final typeMatchingReceiveAddresses = typeMatchingAddresses.where((addr) => !addr.isUsed).toList(); - - if (!isEnabledAutoGenerateSubaddress) { - if (previousAddressRecord != null && - previousAddressRecord!.type == addressPageType) { - return previousAddressRecord!.address; - } - - if (typeMatchingAddresses.isNotEmpty) { - return typeMatchingAddresses.first.address; - } - - return generateNewAddress().address; - } - - if (typeMatchingAddresses.isEmpty || typeMatchingReceiveAddresses.isEmpty) { - return generateNewAddress().address; - } - - final prev = previousAddressRecord; - if (prev != null && prev.type == addressPageType && !prev.isUsed) { - return prev.address; - } - - return typeMatchingReceiveAddresses.first.address; - } - - @observable - bool isEnabledAutoGenerateSubaddress = true; - - @override - set address(String addr) { - if (addr == "Silent Payments" && SilentPaymentsAddresType.p2sp != addressPageType) { - return; - } - if (addressPageType == SilentPaymentsAddresType.p2sp) { - final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr); - - if (selected.silentPaymentTweak != null && silentAddress != null) { - activeSilentAddress = - silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString(); - } else { - activeSilentAddress = silentAddress!.toString(); - } - return; - } - try { - final addressRecord = _addresses.firstWhere( - (addressRecord) => addressRecord.address == addr, - ); - - previousAddressRecord = addressRecord; - receiveAddresses.remove(addressRecord); - receiveAddresses.insert(0, addressRecord); - } catch (e) { - printV("ElectrumWalletAddressBase: set address ($addr): $e"); - } + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress; } @override - String get primaryAddress => getAddress(index: 0, hd: mainHd, addressType: addressPageType); + set address(String addr) => null; - Map currentReceiveAddressIndexByType; - - int get currentReceiveAddressIndex => - currentReceiveAddressIndexByType[_addressPageType.toString()] ?? 0; - - void set currentReceiveAddressIndex(int index) => - currentReceiveAddressIndexByType[_addressPageType.toString()] = index; - - Map currentChangeAddressIndexByType; - - int get currentChangeAddressIndex => - currentChangeAddressIndexByType[_addressPageType.toString()] ?? 0; - - void set currentChangeAddressIndex(int index) => - currentChangeAddressIndexByType[_addressPageType.toString()] = index; - - int currentSilentAddressIndex; - - @observable - BitcoinAddressRecord? previousAddressRecord; + int currentReceiveAddressIndex; + int currentChangeAddressIndex; @computed - int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) { - if (!addressRecord.isHidden) { - return acc + 1; - } - return acc; - }); + int get totalCountOfReceiveAddresses => + addresses.fold(0, (acc, addressRecord) { + if (!addressRecord.isHidden) { + return acc + 1; + } + return acc; + }); @computed - int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) { - if (addressRecord.isHidden) { - return acc + 1; - } - return acc; - }); + int get totalCountOfChangeAddresses => + addresses.fold(0, (acc, addressRecord) { + if (addressRecord.isHidden) { + return acc + 1; + } + return acc; + }); + + Future discoverAddresses() async { + await _discoverAddresses(mainHd, false); + await _discoverAddresses(sideHd, true); + await updateAddressesInBox(); + } @override Future init() async { - if (walletInfo.type == WalletType.bitcoinCash) { - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - } else if (walletInfo.type == WalletType.litecoin) { - await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); - if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { - await _generateInitialAddresses(type: SegwitAddresType.mweb); - } - } else if (walletInfo.type == WalletType.bitcoin) { - await _generateInitialAddresses(); - if (!isHardwareWallet) { - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); - await _generateInitialAddresses(type: SegwitAddresType.p2tr); - await _generateInitialAddresses(type: SegwitAddresType.p2wsh); - } - } - - updateAddressesByMatch(); + await _generateInitialAddresses(); updateReceiveAddresses(); updateChangeAddresses(); - _validateAddresses(); await updateAddressesInBox(); if (currentReceiveAddressIndex >= receiveAddresses.length) { @@ -279,17 +109,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } @action - Future getChangeAddress( - {List? inputs, - List? outputs, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) async { + Future getChangeAddress() async { updateChangeAddresses(); if (changeAddresses.isEmpty) { final newAddresses = await _createNewAddresses(gap, - startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0, - isHidden: true); - addAddresses(newAddresses); + hd: sideHd, + startIndex: totalCountOfChangeAddresses > 0 + ? totalCountOfChangeAddresses - 1 + : 0, + isHidden: true); + _addAddresses(newAddresses); } if (currentChangeAddressIndex >= changeAddresses.length) { @@ -297,408 +127,152 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } updateChangeAddresses(); - final address = changeAddresses[currentChangeAddressIndex]; + final address = changeAddresses[currentChangeAddressIndex].address; currentChangeAddressIndex += 1; return address; } - Map get labels { - final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32)); - final labels = {}; - for (int i = 0; i < silentAddresses.length; i++) { - final silentAddressRecord = silentAddresses[i]; - final silentPaymentTweak = silentAddressRecord.silentPaymentTweak; - - if (silentPaymentTweak != null && - SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) { - labels[G - .tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak))) - .toHex()] = silentPaymentTweak; - } - } - return labels; - } - - @action - BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { - if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) { - final currentSilentAddressIndex = silentAddresses - .where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr) - .length - - 1; - - this.currentSilentAddressIndex = currentSilentAddressIndex; - - final address = BitcoinSilentPaymentAddressRecord( - silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(), - index: currentSilentAddressIndex, - isHidden: false, - name: label, - silentPaymentTweak: - BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)), - network: network, - type: SilentPaymentsAddresType.p2sp, - ); - - silentAddresses.add(address); - Future.delayed(Duration.zero, () => updateAddressesByMatch()); - - return address; - } - - final newAddressIndex = addressesByReceiveType.fold( - 0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc); - + BitcoinAddressRecord generateNewAddress( + {bitcoin.HDWallet? hd, bool isHidden = false}) { + currentReceiveAddressIndex += 1; + // FIX-ME: Check logic for whichi HD should be used here ??? final address = BitcoinAddressRecord( - getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType), - index: newAddressIndex, - isHidden: false, - name: label, - type: addressPageType, - network: network, - ); - Future.delayed(Duration.zero, () { - _addresses.add(address); - updateAddressesByMatch(); - }); + getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd), + index: currentReceiveAddressIndex, + isHidden: isHidden); + addresses.add(address); return address; } - String getAddress({ - required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - }) => - ''; - - Future getAddressAsync({ - required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - }) async => - getAddress(index: index, hd: hd, addressType: addressType); - - void addBitcoinAddressTypes() { - final lastP2wpkh = _addresses - .where((addressRecord) => - _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh)) - .toList() - .last; - if (lastP2wpkh.address != address) { - addressesMap[lastP2wpkh.address] = 'P2WPKH'; - } else { - addressesMap[address] = 'Active - P2WPKH'; - } - - final lastP2pkh = _addresses.firstWhere( - (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh)); - if (lastP2pkh.address != address) { - addressesMap[lastP2pkh.address] = 'P2PKH'; - } else { - addressesMap[address] = 'Active - P2PKH'; - } - - final lastP2sh = _addresses.firstWhere((addressRecord) => - _isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh)); - if (lastP2sh.address != address) { - addressesMap[lastP2sh.address] = 'P2SH'; - } else { - addressesMap[address] = 'Active - P2SH'; - } - - final lastP2tr = _addresses.firstWhere( - (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr)); - if (lastP2tr.address != address) { - addressesMap[lastP2tr.address] = 'P2TR'; - } else { - addressesMap[address] = 'Active - P2TR'; - } - - final lastP2wsh = _addresses.firstWhere( - (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh)); - if (lastP2wsh.address != address) { - addressesMap[lastP2wsh.address] = 'P2WSH'; - } else { - addressesMap[address] = 'Active - P2WSH'; - } - - silentAddresses.forEach((addressRecord) { - if (addressRecord.type != SilentPaymentsAddresType.p2sp || addressRecord.isHidden) { - return; - } - - if (addressRecord.address != address) { - addressesMap[addressRecord.address] = addressRecord.name.isEmpty - ? "Silent Payments" - : "Silent Payments - " + addressRecord.name; - } else { - addressesMap[address] = 'Active - Silent Payments'; - } - }); - } - - void addLitecoinAddressTypes() { - final lastP2wpkh = _addresses - .where((addressRecord) => - _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh)) - .toList() - .last; - if (lastP2wpkh.address != address) { - addressesMap[lastP2wpkh.address] = 'P2WPKH'; - } else { - addressesMap[address] = 'Active - P2WPKH'; - } - - final lastMweb = _addresses.firstWhere( - (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb)); - if (lastMweb.address != address) { - addressesMap[lastMweb.address] = 'MWEB'; - } else { - addressesMap[address] = 'Active - MWEB'; - } - } - - void addBitcoinCashAddressTypes() { - final lastP2pkh = _addresses.firstWhere( - (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh)); - if (lastP2pkh.address != address) { - addressesMap[lastP2pkh.address] = 'P2PKH'; - } else { - addressesMap[address] = 'Active - P2PKH'; - } - } + String getAddress({required int index, required bitcoin.HDWallet hd}) => ''; @override Future updateAddressesInBox() async { try { addressesMap.clear(); - addressesMap[address] = 'Active'; - - allAddressesMap.clear(); - _addresses.forEach((addressRecord) { - allAddressesMap[addressRecord.address] = addressRecord.name; - }); - - switch (walletInfo.type) { - case WalletType.bitcoin: - addBitcoinAddressTypes(); - break; - case WalletType.litecoin: - addLitecoinAddressTypes(); - break; - case WalletType.bitcoinCash: - addBitcoinCashAddressTypes(); - break; - default: - break; - } - + addressesMap[address] = ''; await saveAddressesInBox(); } catch (e) { - printV("updateAddresses $e"); + print(e.toString()); } } - @action - void updateAddress(String address, String label) { - BaseBitcoinAddressRecord? foundAddress; - _addresses.forEach((addressRecord) { - if (addressRecord.address == address) { - foundAddress = addressRecord; - } - }); - silentAddresses.forEach((addressRecord) { - if (addressRecord.address == address) { - foundAddress = addressRecord; - } - }); - mwebAddresses.forEach((addressRecord) { - if (addressRecord.address == address) { - foundAddress = addressRecord; - } - }); - - if (foundAddress != null) { - foundAddress!.setNewName(label); - - if (foundAddress is BitcoinAddressRecord) { - final index = _addresses.indexOf(foundAddress); - _addresses.remove(foundAddress); - _addresses.insert(index, foundAddress as BitcoinAddressRecord); - } else { - final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord); - silentAddresses.remove(foundAddress); - silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord); - } - } - } - - @action - void updateAddressesByMatch() { - if (addressPageType == SilentPaymentsAddresType.p2sp) { - addressesByReceiveType.clear(); - addressesByReceiveType.addAll(silentAddresses); - return; - } - - addressesByReceiveType.clear(); - addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList()); - } - @action void updateReceiveAddresses() { receiveAddresses.removeRange(0, receiveAddresses.length); - final newAddresses = - _addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); - receiveAddresses.addAll(newAddresses); + final newAdresses = addresses + .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); + receiveAddresses.addAll(newAdresses); } @action void updateChangeAddresses() { changeAddresses.removeRange(0, changeAddresses.length); - final newAddresses = _addresses.where((addressRecord) => - addressRecord.isHidden && - !addressRecord.isUsed && - // TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type - (walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh)); - changeAddresses.addAll(newAddresses); + final newAdresses = addresses + .where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed); + changeAddresses.addAll(newAdresses); } - @action - Future discoverAddresses(List addressList, bool isHidden, - Future Function(BitcoinAddressRecord) getAddressHistory, - {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async { - final newAddresses = await _createNewAddresses(gap, - startIndex: addressList.length, isHidden: isHidden, type: type); - addAddresses(newAddresses); + Future _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async { + var hasAddrUse = true; + List addrs; - final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory)); - final isLastAddressUsed = addressesWithHistory.last == addressList.last.address; + if (addresses.isNotEmpty) { + addrs = addresses + .where((addr) => addr.isHidden == isHidden) + .toList(); + } else { + addrs = await _createNewAddresses( + isHidden + ? defaultChangeAddressesCount + : defaultReceiveAddressesCount, + startIndex: 0, + hd: hd, + isHidden: isHidden); + } - if (isLastAddressUsed) { - discoverAddresses(addressList, isHidden, getAddressHistory, type: type); + while(hasAddrUse) { + final addr = addrs.last.address; + hasAddrUse = await _hasAddressUsed(addr); + + if (!hasAddrUse) { + break; + } + + final start = addrs.length; + final count = start + gap; + final batch = await _createNewAddresses( + count, + startIndex: start, + hd: hd, + isHidden: isHidden); + addrs.addAll(batch); + } + + if (addresses.length < addrs.length) { + _addAddresses(addrs); } } - Future _generateInitialAddresses( - {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async { + Future _generateInitialAddresses() async { var countOfReceiveAddresses = 0; var countOfHiddenAddresses = 0; - _addresses.forEach((addr) { - if (addr.type == type) { - if (addr.isHidden) { - countOfHiddenAddresses += 1; - return; - } - - countOfReceiveAddresses += 1; + addresses.forEach((addr) { + if (addr.isHidden) { + countOfHiddenAddresses += 1; + return; } + + countOfReceiveAddresses += 1; }); if (countOfReceiveAddresses < defaultReceiveAddressesCount) { final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; - final newAddresses = await _createNewAddresses(addressesCount, - startIndex: countOfReceiveAddresses, isHidden: false, type: type); - addAddresses(newAddresses); + final newAddresses = await _createNewAddresses( + addressesCount, + startIndex: countOfReceiveAddresses, + hd: mainHd, + isHidden: false); + addresses.addAll(newAddresses); } if (countOfHiddenAddresses < defaultChangeAddressesCount) { final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; - final newAddresses = await _createNewAddresses(addressesCount, - startIndex: countOfHiddenAddresses, isHidden: true, type: type); - addAddresses(newAddresses); + final newAddresses = await _createNewAddresses( + addressesCount, + startIndex: countOfHiddenAddresses, + hd: sideHd, + isHidden: true); + addresses.addAll(newAddresses); } } Future> _createNewAddresses(int count, - {int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async { + {required bitcoin.HDWallet hd, int startIndex = 0, bool isHidden = false}) async { final list = []; for (var i = startIndex; i < count + startIndex; i++) { final address = BitcoinAddressRecord( - await getAddressAsync(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType), - index: i, - isHidden: isHidden, - type: type ?? addressPageType, - network: network, - ); + getAddress(index: i, hd: hd), + index: i, + isHidden: isHidden); list.add(address); } return list; } - @action - void addAddresses(Iterable addresses) { - final addressesSet = this._addresses.toSet(); + void _addAddresses(Iterable addresses) { + final addressesSet = this.addresses.toSet(); addressesSet.addAll(addresses); - this._addresses.clear(); - this._addresses.addAll(addressesSet); - updateAddressesByMatch(); + this.addresses.removeRange(0, this.addresses.length); + this.addresses.addAll(addressesSet); } - @action - void addSilentAddresses(Iterable addresses) { - final addressesSet = this.silentAddresses.toSet(); - addressesSet.addAll(addresses); - this.silentAddresses.clear(); - this.silentAddresses.addAll(addressesSet); - updateAddressesByMatch(); + Future _hasAddressUsed(String address) async { + final sh = scriptHash(address, networkType: networkType); + final transactionHistory = await electrumClient.getHistory(sh); + return transactionHistory.isNotEmpty; } - - @action - void addMwebAddresses(Iterable addresses) { - final addressesSet = this.mwebAddresses.toSet(); - addressesSet.addAll(addresses); - this.mwebAddresses.clear(); - this.mwebAddresses.addAll(addressesSet); - updateAddressesByMatch(); - } - - void _validateAddresses() { - _addresses.forEach((element) async { - if (element.type == SegwitAddresType.mweb) { - // this would add a ton of startup lag for mweb addresses since we have 1000 of them - return; - } - if (!element.isHidden && - element.address != - await getAddressAsync(index: element.index, hd: mainHd, addressType: element.type)) { - element.isHidden = true; - } else if (element.isHidden && - element.address != - await getAddressAsync(index: element.index, hd: sideHd, addressType: element.type)) { - element.isHidden = false; - } - }); - } - - @action - Future setAddressType(BitcoinAddressType type) async { - _addressPageType = type; - updateAddressesByMatch(); - walletInfo.addressPageType = addressPageType.toString(); - await walletInfo.save(); - } - - bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) { - return _isAddressByType(addressRecord, addressPageType); - } - - Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd; - - bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; - - bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => - !addr.isHidden && !addr.isUsed && addr.type == type; - - @action - void deleteSilentPaymentAddress(String address) { - final addressRecord = silentAddresses.firstWhere((addressRecord) => - addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address); - - silentAddresses.remove(addressRecord); - updateAddressesByMatch(); - } -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 990719089..def991ebe 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -1,134 +1,59 @@ import 'dart:convert'; -import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; +import 'package:cw_bitcoin/file.dart'; import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_type.dart'; -class ElectrumWalletSnapshot { - ElectrumWalletSnapshot({ +class ElectrumWallletSnapshot { + ElectrumWallletSnapshot({ required this.name, required this.type, required this.password, required this.mnemonic, - required this.xpub, required this.addresses, required this.balance, required this.regularAddressIndex, - required this.changeAddressIndex, - required this.addressPageType, - required this.silentAddresses, - required this.silentAddressIndex, - required this.mwebAddresses, - required this.alwaysScan, - this.passphrase, - this.derivationType, - this.derivationPath, - }); + required this.changeAddressIndex}); final String name; final String password; final WalletType type; - final String? addressPageType; - - @deprecated - String? mnemonic; - - @deprecated - String? xpub; - - @deprecated - String? passphrase; + String mnemonic; List addresses; - List silentAddresses; - List mwebAddresses; - bool alwaysScan; - ElectrumBalance balance; - Map regularAddressIndex; - Map changeAddressIndex; - int silentAddressIndex; - DerivationType? derivationType; - String? derivationPath; + int regularAddressIndex; + int changeAddressIndex; - static Future load(EncryptionFileUtils encryptionFileUtils, String name, - WalletType type, String password, BasedUtxoNetwork network) async { + static Future load(String name, WalletType type, String password) async { final path = await pathForWallet(name: name, type: type); - final jsonSource = await encryptionFileUtils.read(path: path, password: password); + final jsonSource = await read(path: path, password: password); final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String?; - final xpub = data['xpub'] as String?; - final passphrase = data['passphrase'] as String? ?? ''; - final addressesTmp = data['addresses'] as List? ?? []; + final mnemonic = data['mnemonic'] as String; final addresses = addressesTmp .whereType() - .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network)) + .map((addr) => BitcoinAddressRecord.fromJSON(addr)) .toList(); - - final silentAddressesTmp = data['silent_addresses'] as List? ?? []; - final silentAddresses = silentAddressesTmp - .whereType() - .map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network)) - .toList(); - - final mwebAddressTmp = data['mweb_addresses'] as List? ?? []; - final mwebAddresses = mwebAddressTmp - .whereType() - .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network)) - .toList(); - - final alwaysScan = data['alwaysScan'] as bool? ?? false; - - final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ?? + final balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); - var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; - var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; - var silentAddressIndex = 0; - - final derivationType = DerivationType - .values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index]; - final derivationPath = data['derivationPath'] as String? ?? electrum_path; + var regularAddressIndex = 0; + var changeAddressIndex = 0; try { - regularAddressIndexByType = { - SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0') - }; - changeAddressIndexByType = { - SegwitAddresType.p2wpkh.toString(): - int.parse(data['change_address_index'] as String? ?? '0') - }; - silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0'); - } catch (_) { - try { - regularAddressIndexByType = data["account_index"] as Map? ?? {}; - changeAddressIndexByType = data["change_address_index"] as Map? ?? {}; - } catch (_) {} - } + regularAddressIndex = int.parse(data['account_index'] as String? ?? '0'); + changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0'); + } catch (_) {} - return ElectrumWalletSnapshot( + return ElectrumWallletSnapshot( name: name, type: type, password: password, - passphrase: passphrase, mnemonic: mnemonic, - xpub: xpub, addresses: addresses, balance: balance, - regularAddressIndex: regularAddressIndexByType, - changeAddressIndex: changeAddressIndexByType, - addressPageType: data['address_page_type'] as String?, - derivationType: derivationType, - derivationPath: derivationPath, - silentAddresses: silentAddresses, - silentAddressIndex: silentAddressIndex, - mwebAddresses: mwebAddresses, - alwaysScan: alwaysScan, - ); + regularAddressIndex: regularAddressIndex, + changeAddressIndex: changeAddressIndex); } -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/exceptions.dart b/cw_bitcoin/lib/exceptions.dart deleted file mode 100644 index 9bdb66eef..000000000 --- a/cw_bitcoin/lib/exceptions.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/exceptions.dart'; - -class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException { - BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc); - - @override - String toString() { - return "BitcoinTransactionWrongBalanceException: $amount, $currency"; - } -} - -class BitcoinTransactionNoInputsException extends TransactionNoInputsException {} - -class BitcoinTransactionNoFeeException extends TransactionNoFeeException {} - -class BitcoinTransactionNoDustException extends TransactionNoDustException {} - -class BitcoinTransactionNoDustOnChangeException extends TransactionNoDustOnChangeException { - BitcoinTransactionNoDustOnChangeException(super.max, super.min); - - @override - String toString() { - return "BitcoinTransactionNoDustOnChangeException: max: $max, min: $min"; - } -} - -class BitcoinTransactionCommitFailed extends TransactionCommitFailed { - BitcoinTransactionCommitFailed({super.errorMessage}); - - @override - String toString() { - return errorMessage??"unknown error"; - } -} - -class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {} - -class BitcoinTransactionCommitFailedDustOutput extends TransactionCommitFailedDustOutput {} - -class BitcoinTransactionCommitFailedDustOutputSendAll - extends TransactionCommitFailedDustOutputSendAll {} - -class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {} - -class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {} - -class BitcoinTransactionCommitFailedLessThanMin extends TransactionCommitFailedLessThanMin {} - -class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {} diff --git a/cw_core/lib/utils/file.dart b/cw_bitcoin/lib/file.dart similarity index 66% rename from cw_core/lib/utils/file.dart rename to cw_bitcoin/lib/file.dart index 0b1c5cffd..8fd236ec3 100644 --- a/cw_core/lib/utils/file.dart +++ b/cw_bitcoin/lib/file.dart @@ -2,8 +2,17 @@ import 'dart:io'; import 'package:cw_core/key.dart'; import 'package:encrypt/encrypt.dart' as encrypt; -Future write({required String path, required String password, required String data}) async => - writeData(path: path, password: password, data: data); +Future write( + {required String path, + required String password, + required String data}) async { + final keys = extractKeys(password); + final key = encrypt.Key.fromBase64(keys.first); + final iv = encrypt.IV.fromBase64(keys.last); + final encrypted = await encode(key: key, iv: iv, data: data); + final f = File(path); + f.writeAsStringSync(encrypted); +} Future writeData( {required String path, diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart deleted file mode 100644 index 62840933c..000000000 --- a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_core/hardware/hardware_account_data.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -import 'package:ledger_litecoin/ledger_litecoin.dart'; - -class LitecoinHardwareWalletService { - LitecoinHardwareWalletService(this.ledgerConnection); - - final LedgerConnection ledgerConnection; - - Future> getAvailableAccounts( - {int index = 0, int limit = 5}) async { - final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection); - - await litecoinLedgerApp.getVersion(); - - final accounts = []; - final indexRange = List.generate(limit, (i) => i + index); - final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer; - - for (final i in indexRange) { - final derivationPath = "m/84'/2'/$i'"; - final xpub = await litecoinLedgerApp.getXPubKey( - accountsDerivationPath: derivationPath, - xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16)); - final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion) - .childKey(Bip32KeyIndex(0)); - - final address = generateP2WPKHAddress( - hd: hd, index: 0, network: LitecoinNetwork.mainnet); - - accounts.add(HardwareAccountData( - address: address, - accountIndex: i, - derivationPath: derivationPath, - xpub: xpub, - )); - } - - return accounts; - } -} diff --git a/cw_bitcoin/lib/litecoin_network.dart b/cw_bitcoin/lib/litecoin_network.dart new file mode 100644 index 000000000..d7ad2f837 --- /dev/null +++ b/cw_bitcoin/lib/litecoin_network.dart @@ -0,0 +1,9 @@ +import 'package:bitcoin_flutter/bitcoin_flutter.dart'; + +final litecoinNetwork = NetworkType( + messagePrefix: '\x19Litecoin Signed Message:\n', + bech32: 'ltc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 08c56c600..6bf1c5735 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,208 +1,78 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:convert/convert.dart' as convert; -import 'dart:math'; -import 'package:collection/collection.dart'; -import 'package:crypto/crypto.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; -import 'package:cw_core/cake_hive.dart'; -import 'package:cw_core/mweb_utxo.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_mweb/mwebd.pbgrpc.dart'; -import 'package:fixnum/fixnum.dart'; -import 'package:bip39/bip39.dart' as bip39; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:blockchain_utils/signer/ecdsa_signing_key.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; -import 'package:cw_bitcoin/electrum_transaction_info.dart'; -import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; -import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; -import 'package:grpc/grpc.dart'; import 'package:hive/hive.dart'; -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; -import 'package:ledger_litecoin/ledger_litecoin.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cw_mweb/cw_mweb.dart'; -import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart'; -import 'package:pointycastle/ecc/api.dart'; -import 'package:pointycastle/ecc/curves/secp256k1.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/litecoin_network.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; part 'litecoin_wallet.g.dart'; class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; abstract class LitecoinWalletBase extends ElectrumWallet with Store { - LitecoinWalletBase({ - required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required EncryptionFileUtils encryptionFileUtils, - Uint8List? seedBytes, - String? mnemonic, - String? xpub, - String? passphrase, - String? addressPageType, - List? initialAddresses, - List? initialMwebAddresses, - ElectrumBalance? initialBalance, - Map? initialRegularAddressIndex, - Map? initialChangeAddressIndex, - int? initialMwebHeight, - bool? alwaysScan, - }) : super( - mnemonic: mnemonic, - password: password, - passphrase: passphrase, - xpub: xpub, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - network: LitecoinNetwork.mainnet, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: seedBytes, - encryptionFileUtils: encryptionFileUtils, - currency: CryptoCurrency.ltc, - alwaysScan: alwaysScan, - ) { - if (seedBytes != null) { - mwebHd = - Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1; - mwebEnabled = alwaysScan ?? false; - } else { - mwebHd = null; - mwebEnabled = false; - } - walletAddresses = LitecoinWalletAddresses( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - initialMwebAddresses: initialMwebAddresses, - mainHd: hd, - sideHd: accountHD.childKey(Bip32KeyIndex(1)), - network: network, - mwebHd: mwebHd, - mwebEnabled: mwebEnabled, - isHardwareWallet: walletInfo.isHardwareWallet, - ); - autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; - }); - reaction((_) => mwebSyncStatus, (status) async { - if (mwebSyncStatus is FailedSyncStatus) { - // we failed to connect to mweb, check if we are connected to the litecoin node: - late int nodeHeight; - try { - nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0; - } catch (_) { - nodeHeight = 0; - } - - if (nodeHeight == 0) { - // we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us - } else { - // we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds: - await CwMweb.stop(); - await Future.delayed(const Duration(seconds: 5)); - startSync(); - } - } else if (mwebSyncStatus is SyncingSyncStatus) { - syncStatus = mwebSyncStatus; - } else if (mwebSyncStatus is SyncronizingSyncStatus) { - if (syncStatus is! SyncronizingSyncStatus) { - syncStatus = mwebSyncStatus; - } - } else if (mwebSyncStatus is SyncedSyncStatus) { - if (syncStatus is! SyncedSyncStatus) { - syncStatus = mwebSyncStatus; - } - } - }); - } - late final Bip32Slip10Secp256k1? mwebHd; - late final Box mwebUtxosBox; - Timer? _syncTimer; - Timer? _feeRatesTimer; - Timer? _processingTimer; - StreamSubscription? _utxoStream; - late bool mwebEnabled; - bool processingUtxos = false; - - @observable - SyncStatus mwebSyncStatus = NotConnectedSyncStatus(); - - @override - bool get hasRescan => true; - - List get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; - List get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw; - - static Future create( + LitecoinWalletBase( {required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required EncryptionFileUtils encryptionFileUtils, - String? passphrase, - String? addressPageType, + required Uint8List seedBytes, List? initialAddresses, - List? initialMwebAddresses, ElectrumBalance? initialBalance, - Map? initialRegularAddressIndex, - Map? initialChangeAddressIndex}) async { - late Uint8List seedBytes; + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: litecoinNetwork, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.ltc) { + walletAddresses = LitecoinWalletAddresses( + walletInfo, + electrumClient: electrumClient, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: bitcoin.HDWallet + .fromSeed(seedBytes, network: networkType) + .derivePath("m/0'/1"), + networkType: networkType,); + } - switch (walletInfo.derivationInfo?.derivationType) { - case DerivationType.bip39: - seedBytes = await bip39.mnemonicToSeed( - mnemonic, - passphrase: passphrase ?? "", - ); - break; - case DerivationType.electrum: - default: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); - break; - } + static Future create({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0 + }) async { return LitecoinWallet( - mnemonic: mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: initialAddresses, - initialMwebAddresses: initialMwebAddresses, - initialBalance: initialBalance, - encryptionFileUtils: encryptionFileUtils, - passphrase: passphrase, - seedBytes: seedBytes, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - addressPageType: addressPageType, - ); + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await mnemonicToSeedBytes(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex); } static Future open({ @@ -210,739 +80,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { required WalletInfo walletInfo, required Box unspentCoinsInfo, required String password, - required bool alwaysScan, - required EncryptionFileUtils encryptionFileUtils, }) async { - final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); - - ElectrumWalletSnapshot? snp = null; - - try { - snp = await ElectrumWalletSnapshot.load( - encryptionFileUtils, - name, - walletInfo.type, - password, - LitecoinNetwork.mainnet, - ); - } catch (e) { - if (!hasKeysFile) rethrow; - } - - final WalletKeysData keysData; - // Migrate wallet from the old scheme to then new .keys file scheme - if (!hasKeysFile) { - keysData = - WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); - } else { - keysData = await WalletKeysFile.readKeysFile( - name, - walletInfo.type, - password, - encryptionFileUtils, - ); - } - - walletInfo.derivationInfo ??= DerivationInfo(); - - // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; - - Uint8List? seedBytes = null; - final mnemonic = keysData.mnemonic; - final passphrase = keysData.passphrase; - - if (mnemonic != null) { - switch (walletInfo.derivationInfo?.derivationType) { - case DerivationType.bip39: - seedBytes = await bip39.mnemonicToSeed( - mnemonic, - passphrase: passphrase ?? "", - ); - break; - case DerivationType.electrum: - default: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); - break; - } - } - + final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password); return LitecoinWallet( - mnemonic: keysData.mnemonic, - xpub: keysData.xPub, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp?.addresses, - initialMwebAddresses: snp?.mwebAddresses, - initialBalance: snp?.balance, - seedBytes: seedBytes, - passphrase: passphrase, - encryptionFileUtils: encryptionFileUtils, - initialRegularAddressIndex: snp?.regularAddressIndex, - initialChangeAddressIndex: snp?.changeAddressIndex, - addressPageType: snp?.addressPageType, - alwaysScan: snp?.alwaysScan, - ); - } - - Future waitForMwebAddresses() async { - printV("waitForMwebAddresses() called!"); - // ensure that we have the full 1000 mweb addresses generated before continuing: - // should no longer be needed, but leaving here just in case - await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020); - } - - @action - @override - Future connectToNode({required Node node}) async { - await super.connectToNode(node: node); - - final prefs = await SharedPreferences.getInstance(); - final mwebNodeUri = prefs.getString("mwebNodeUri") ?? "ltc-electrum.cakewallet.com:9333"; - await CwMweb.setNodeUriOverride(mwebNodeUri); - } - - @action - @override - Future startSync() async { - printV("startSync() called!"); - printV("STARTING SYNC - MWEB ENABLED: $mwebEnabled"); - if (!mwebEnabled) { - try { - // in case we're switching from a litecoin wallet that had mweb enabled - CwMweb.stop(); - } catch (_) {} - super.startSync(); - return; - } - - if (mwebSyncStatus is SyncronizingSyncStatus) { - return; - } - - printV("STARTING SYNC - MWEB ENABLED: $mwebEnabled"); - _syncTimer?.cancel(); - try { - mwebSyncStatus = SyncronizingSyncStatus(); - try { - await subscribeForUpdates(); - } catch (e) { - printV("failed to subscribe for updates: $e"); - } - updateFeeRates(); - _feeRatesTimer?.cancel(); - _feeRatesTimer = - Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); - - printV("START SYNC FUNCS"); - await waitForMwebAddresses(); - await processMwebUtxos(); - await updateTransactions(); - await updateUnspent(); - await updateBalance(); - } catch (e) { - printV("failed to start mweb sync: $e"); - syncStatus = FailedSyncStatus(); - return; - } - - _syncTimer?.cancel(); - _syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async { - if (mwebSyncStatus is FailedSyncStatus) { - _syncTimer?.cancel(); - return; - } - - final nodeHeight = - await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node - - if (nodeHeight == 0) { - // we aren't connected to the ltc node yet - if (mwebSyncStatus is! NotConnectedSyncStatus) { - mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected"); - } - return; - } - - // update the current chain tip so that confirmation calculations are accurate: - currentChainTip = nodeHeight; - - final resp = await CwMweb.status(StatusRequest()); - - try { - if (resp.blockHeaderHeight < nodeHeight) { - int h = resp.blockHeaderHeight; - mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight); - } else if (resp.mwebHeaderHeight < nodeHeight) { - int h = resp.mwebHeaderHeight; - mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight); - } else if (resp.mwebUtxosHeight < nodeHeight) { - mwebSyncStatus = SyncingSyncStatus(1, 0.999); - } else { - bool confirmationsUpdated = false; - if (resp.mwebUtxosHeight > walletInfo.restoreHeight) { - await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight); - await checkMwebUtxosSpent(); - // update the confirmations for each transaction: - for (final tx in transactionHistory.transactions.values) { - if (tx.height == null || tx.height == 0) { - // update with first confirmation on next block since it hasn't been confirmed yet: - tx.height = resp.mwebUtxosHeight; - continue; - } - - final confirmations = (resp.mwebUtxosHeight - tx.height!) + 1; - - // if the confirmations haven't changed, skip updating: - if (tx.confirmations == confirmations) continue; - - // if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin): - if (confirmations >= 2 && - tx.direction == TransactionDirection.outgoing && - tx.unspents != null) { - for (var coin in tx.unspents!) { - final utxo = mwebUtxosBox.get(coin.address); - if (utxo != null) { - printV("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); - await mwebUtxosBox.delete(coin.address); - } - } - } - - tx.confirmations = confirmations; - tx.isPending = false; - transactionHistory.addOne(tx); - confirmationsUpdated = true; - } - if (confirmationsUpdated) { - await transactionHistory.save(); - await updateTransactions(); - } - } - - // prevent unnecessary reaction triggers: - if (mwebSyncStatus is! SyncedSyncStatus) { - // mwebd is synced, but we could still be processing incoming utxos: - if (!processingUtxos) { - mwebSyncStatus = SyncedSyncStatus(); - } - } - return; - } - } catch (e) { - printV("error syncing: $e"); - mwebSyncStatus = FailedSyncStatus(error: e.toString()); - } - }); - } - - @action - @override - Future stopSync() async { - printV("stopSync() called!"); - _syncTimer?.cancel(); - _utxoStream?.cancel(); - _feeRatesTimer?.cancel(); - await CwMweb.stop(); - printV("stopped syncing!"); - } - - Future initMwebUtxosBox() async { - final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${MwebUtxo.boxName}"; - - mwebUtxosBox = await CakeHive.openBox(boxName); - } - - @override - Future renameWalletFiles(String newWalletName) async { - // rename the hive box: - final oldBoxName = "${walletInfo.name.replaceAll(" ", "_")}_${MwebUtxo.boxName}"; - final newBoxName = "${newWalletName.replaceAll(" ", "_")}_${MwebUtxo.boxName}"; - - final oldBox = await CakeHive.openBox(oldBoxName); - mwebUtxosBox = await CakeHive.openBox(newBoxName); - for (final key in oldBox.keys) { - final value = oldBox.get(key); - await oldBox.delete(key); - await mwebUtxosBox.put(key, value!); - } - oldBox.deleteFromDisk(); - - await super.renameWalletFiles(newWalletName); - } - - @action - @override - Future rescan({ - required int height, - int? chainTip, - ScanData? scanData, - bool? doSingleScan, - bool? usingElectrs, - }) async { - _syncTimer?.cancel(); - await walletInfo.updateRestoreHeight(height); - - // go through mwebUtxos and clear any that are above the new restore height: - if (height == 0) { - await mwebUtxosBox.clear(); - transactionHistory.clear(); - } else { - for (final utxo in mwebUtxosBox.values) { - if (utxo.height > height) { - await mwebUtxosBox.delete(utxo.outputId); - } - } - // TODO: remove transactions that are above the new restore height! - } - - // reset coin balances and txCount to 0: - unspentCoins.forEach((coin) { - if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord) - coin.bitcoinAddressRecord.balance = 0; - coin.bitcoinAddressRecord.txCount = 0; - }); - - for (var addressRecord in walletAddresses.allAddresses) { - addressRecord.balance = 0; - addressRecord.txCount = 0; - } - - await startSync(); - } - - @override - Future init() async { - await super.init(); - await initMwebUtxosBox(); - } - - Future handleIncoming(MwebUtxo utxo) async { - printV("handleIncoming() called!"); - final status = await CwMweb.status(StatusRequest()); - var date = DateTime.now(); - var confirmations = 0; - if (utxo.height > 0) { - date = DateTime.fromMillisecondsSinceEpoch(utxo.blockTime * 1000); - confirmations = status.blockHeaderHeight - utxo.height + 1; - } - var tx = transactionHistory.transactions.values - .firstWhereOrNull((tx) => tx.outputAddresses?.contains(utxo.outputId) ?? false); - - if (tx == null) { - tx = ElectrumTransactionInfo( - WalletType.litecoin, - id: utxo.outputId, - height: utxo.height, - amount: utxo.value.toInt(), - fee: 0, - direction: TransactionDirection.incoming, - isPending: utxo.height == 0, - date: date, - confirmations: confirmations, - inputAddresses: [], - outputAddresses: [utxo.outputId], - isReplaced: false, - ); - } else { - if (tx.confirmations != confirmations || tx.height != utxo.height) { - tx.height = utxo.height; - tx.confirmations = confirmations; - tx.isPending = utxo.height == 0; - } - } - - bool isNew = transactionHistory.transactions[tx.id] == null; - - if (!(tx.outputAddresses?.contains(utxo.address) ?? false)) { - tx.outputAddresses?.add(utxo.address); - isNew = true; - } - - if (isNew) { - final addressRecord = walletAddresses.allAddresses - .firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address); - if (addressRecord == null) { - printV("we don't have this address in the wallet! ${utxo.address}"); - return; - } - - // update the txCount: - addressRecord.txCount++; - addressRecord.balance += utxo.value.toInt(); - addressRecord.setAsUsed(); - } - - transactionHistory.addOne(tx); - - if (isNew) { - // update the unconfirmed balance when a new tx is added: - // we do this after adding the tx to the history so that sub address balances are updated correctly - // (since that calculation is based on the tx history) - await updateBalance(); - } - } - - Future processMwebUtxos() async { - printV("processMwebUtxos() called!"); - if (!mwebEnabled) { - return; - } - - int restoreHeight = walletInfo.restoreHeight; - printV("SCANNING FROM HEIGHT: $restoreHeight"); - final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight); - - // process new utxos as they come in: - await _utxoStream?.cancel(); - ResponseStream? responseStream = await CwMweb.utxos(req); - if (responseStream == null) { - throw Exception("failed to get utxos stream!"); - } - _utxoStream = responseStream.listen( - (Utxo sUtxo) async { - // we're processing utxos, so our balance could still be inaccurate: - if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) { - mwebSyncStatus = SyncronizingSyncStatus(); - processingUtxos = true; - _processingTimer?.cancel(); - _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async { - processingUtxos = false; - timer.cancel(); - }); - } - - final utxo = MwebUtxo( - address: sUtxo.address, - blockTime: sUtxo.blockTime, - height: sUtxo.height, - outputId: sUtxo.outputId, - value: sUtxo.value.toInt(), - ); - - if (mwebUtxosBox.containsKey(utxo.outputId)) { - // we've already stored this utxo, skip it: - // but do update the utxo height if it's somehow different: - final existingUtxo = mwebUtxosBox.get(utxo.outputId); - if (existingUtxo!.height != utxo.height) { - printV( - "updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}"); - existingUtxo.height = utxo.height; - await mwebUtxosBox.put(utxo.outputId, existingUtxo); - } - return; - } - - await updateUnspent(); - await updateBalance(); - - final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs; - - // don't process utxos with addresses that are not in the mwebAddrs list: - if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) { - return; - } - - await mwebUtxosBox.put(utxo.outputId, utxo); - - await handleIncoming(utxo); - }, - onError: (error) { - printV("error in utxo stream: $error"); - mwebSyncStatus = FailedSyncStatus(error: error.toString()); - }, - cancelOnError: true, - ); - } - - Future deleteSpentUtxos() async { - printV("deleteSpentUtxos() called!"); - final chainHeight = await electrumClient.getCurrentBlockChainTip(); - final status = await CwMweb.status(StatusRequest()); - if (chainHeight == null || status.blockHeaderHeight != chainHeight) return; - if (status.mwebUtxosHeight != chainHeight) return; // we aren't synced - - // delete any spent utxos with >= 2 confirmations: - final spentOutputIds = mwebUtxosBox.values - .where((utxo) => utxo.spent && (chainHeight - utxo.height) >= 2) - .map((utxo) => utxo.outputId) - .toList(); - - if (spentOutputIds.isEmpty) return; - - final resp = await CwMweb.spent(SpentRequest(outputId: spentOutputIds)); - final spent = resp.outputId; - if (spent.isEmpty) return; - - for (final outputId in spent) { - await mwebUtxosBox.delete(outputId); - } - } - - Future checkMwebUtxosSpent() async { - printV("checkMwebUtxosSpent() called!"); - if (!mwebEnabled) { - return; - } - - final pendingOutgoingTransactions = transactionHistory.transactions.values - .where((tx) => tx.direction == TransactionDirection.outgoing && tx.isPending); - - // check if any of the pending outgoing transactions are now confirmed: - bool updatedAny = false; - for (final tx in pendingOutgoingTransactions) { - updatedAny = await isConfirmed(tx) || updatedAny; - } - - await deleteSpentUtxos(); - - // get output ids of all the mweb utxos that have > 0 height: - final outputIds = mwebUtxosBox.values - .where((utxo) => utxo.height > 0 && !utxo.spent) - .map((utxo) => utxo.outputId) - .toList(); - - final resp = await CwMweb.spent(SpentRequest(outputId: outputIds)); - final spent = resp.outputId; - if (spent.isEmpty) return; - - final status = await CwMweb.status(StatusRequest()); - final height = await electrumClient.getCurrentBlockChainTip(); - if (height == null || status.blockHeaderHeight != height) return; - if (status.mwebUtxosHeight != height) return; // we aren't synced - int amount = 0; - Set inputAddresses = {}; - var output = convert.AccumulatorSink(); - var input = sha256.startChunkedConversion(output); - - for (final outputId in spent) { - final utxo = mwebUtxosBox.get(outputId); - await mwebUtxosBox.delete(outputId); - if (utxo == null) continue; - final addressRecord = walletAddresses.allAddresses - .firstWhere((addressRecord) => addressRecord.address == utxo.address); - if (!inputAddresses.contains(utxo.address)) { - addressRecord.txCount++; - } - addressRecord.balance -= utxo.value.toInt(); - amount += utxo.value.toInt(); - inputAddresses.add(utxo.address); - input.add(hex.decode(outputId)); - } - - if (inputAddresses.isEmpty) return; - input.close(); - var digest = output.events.single; - final tx = ElectrumTransactionInfo( - WalletType.litecoin, - id: digest.toString(), - height: height, - amount: amount, - fee: 0, - direction: TransactionDirection.outgoing, - isPending: false, - date: DateTime.fromMillisecondsSinceEpoch(status.blockTime * 1000), - confirmations: 1, - inputAddresses: inputAddresses.toList(), - outputAddresses: [], - isReplaced: false, - ); - - transactionHistory.addOne(tx); - await transactionHistory.save(); - - if (updatedAny) { - await updateBalance(); - } - } - - // checks if a pending transaction is now confirmed, and updates the tx info accordingly: - Future isConfirmed(ElectrumTransactionInfo tx) async { - if (!mwebEnabled) return false; - if (!tx.isPending) return false; - - final isMwebTx = (tx.inputAddresses?.any((addr) => addr.contains("mweb")) ?? false) || - (tx.outputAddresses?.any((addr) => addr.contains("mweb")) ?? false); - - if (!isMwebTx) { - return false; - } - - final outputId = [], target = {}; - final isHash = RegExp(r'^[a-f0-9]{64}$').hasMatch; - final spendingOutputIds = tx.inputAddresses?.where(isHash) ?? []; - final payingToOutputIds = tx.outputAddresses?.where(isHash) ?? []; - outputId.addAll(spendingOutputIds); - outputId.addAll(payingToOutputIds); - target.addAll(spendingOutputIds); - - for (final outputId in payingToOutputIds) { - final spendingTx = transactionHistory.transactions.values - .firstWhereOrNull((tx) => tx.inputAddresses?.contains(outputId) ?? false); - if (spendingTx != null && !spendingTx.isPending) { - target.add(outputId); - } - } - - if (outputId.isEmpty) { - return false; - } - - final resp = await CwMweb.spent(SpentRequest(outputId: outputId)); - if (!setEquals(resp.outputId.toSet(), target)) { - return false; - } - - final status = await CwMweb.status(StatusRequest()); - tx.height = status.mwebUtxosHeight; - tx.confirmations = 1; - tx.isPending = false; - await transactionHistory.save(); - return true; - } - - Future updateUnspent() async { - printV("updateUnspent() called!"); - await checkMwebUtxosSpent(); - await updateAllUnspents(); - } - - @override - @action - Future updateAllUnspents() async { - if (!mwebEnabled) { - await super.updateAllUnspents(); - return; - } - - // add the mweb unspents to the list: - List mwebUnspentCoins = []; - // update mweb unspents: - final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs; - mwebUtxosBox.keys.forEach((dynamic oId) { - final String outputId = oId as String; - final utxo = mwebUtxosBox.get(outputId); - if (utxo == null || utxo.spent) { - return; - } - if (utxo.address.isEmpty) { - // not sure if a bug or a special case but we definitely ignore these - return; - } - final addressRecord = walletAddresses.allAddresses - .firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address); - - if (addressRecord == null) { - printV("utxo contains an address that is not in the wallet: ${utxo.address}"); - return; - } - final unspent = BitcoinUnspent( - addressRecord, - outputId, - utxo.value.toInt(), - mwebAddrs.indexOf(utxo.address), - ); - if (unspent.vout == 0) { - unspent.isChange = true; - } - mwebUnspentCoins.add(unspent); - }); - - // copy coin control attributes to mwebCoins: - await updateCoins(mwebUnspentCoins); - // get regular ltc unspents (this resets unspentCoins): - await super.updateAllUnspents(); - // add the mwebCoins: - unspentCoins.addAll(mwebUnspentCoins); - } - - @override - Future fetchBalances() async { - final balance = await super.fetchBalances(); - if (!mwebEnabled) { - return balance; - } - - // update unspent balances: - await updateUnspent(); - - int confirmed = balance.confirmed; - int unconfirmed = balance.unconfirmed; - int confirmedMweb = 0; - int unconfirmedMweb = 0; - try { - mwebUtxosBox.values.forEach((utxo) { - bool isConfirmed = utxo.height > 0; - - printV( - "utxo: ${isConfirmed ? "confirmed" : "unconfirmed"} ${utxo.spent ? "spent" : "unspent"} ${utxo.outputId} ${utxo.height} ${utxo.value}"); - - if (isConfirmed) { - confirmedMweb += utxo.value.toInt(); - } - - if (isConfirmed && utxo.spent) { - unconfirmedMweb -= utxo.value.toInt(); - } - - if (!isConfirmed && !utxo.spent) { - unconfirmedMweb += utxo.value.toInt(); - } - }); - } catch (_) {} - - for (var addressRecord in walletAddresses.allAddresses) { - addressRecord.balance = 0; - addressRecord.txCount = 0; - } - - unspentCoins.forEach((coin) { - final coinInfoList = unspentCoinsInfo.values.where( - (element) => - element.walletId.contains(id) && - element.hash.contains(coin.hash) && - element.vout == coin.vout, - ); - - if (coinInfoList.isNotEmpty) { - final coinInfo = coinInfoList.first; - - coin.isFrozen = coinInfo.isFrozen; - coin.isSending = coinInfo.isSending; - coin.note = coinInfo.note; - if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord) - coin.bitcoinAddressRecord.balance += coinInfo.value; - } else { - super.addCoinInfo(coin); - } - }); - - // update the txCount for each address using the tx history, since we can't rely on mwebd - // to have an accurate count, we should just keep it in sync with what we know from the tx history: - for (final tx in transactionHistory.transactions.values) { - if (tx.inputAddresses == null || tx.outputAddresses == null) { - continue; - } - final txAddresses = tx.inputAddresses! + tx.outputAddresses!; - for (final address in txAddresses) { - final addressRecord = walletAddresses.allAddresses - .firstWhereOrNull((addressRecord) => addressRecord.address == address); - if (addressRecord == null) { - continue; - } - addressRecord.txCount++; - } - } - - return ElectrumBalance( - confirmed: confirmed, - unconfirmed: unconfirmed, - frozen: balance.frozen, - secondConfirmed: confirmedMweb, - secondUnconfirmed: unconfirmedMweb, - ); + mnemonic: snp.mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await mnemonicToSeedBytes(snp.mnemonic), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex); } @override @@ -960,469 +109,4 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return 0; } - - @override - Future calcFee({ - required List utxos, - required List outputs, - required BasedUtxoNetwork network, - String? memo, - required int feeRate, - List? inputPrivKeyInfos, - List? vinOutpoints, - }) async { - bool spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb); - bool paysToMweb = outputs - .any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb); - - bool isRegular = !spendsMweb && !paysToMweb; - bool isMweb = spendsMweb || paysToMweb; - - if (isMweb && !mwebEnabled) { - throw Exception("MWEB is not enabled! can't calculate fee without starting the mweb server!"); - // TODO: likely the change address is mweb and just not updated - } - - if (isRegular) { - return await super.calcFee( - utxos: utxos, - outputs: outputs, - network: network, - memo: memo, - feeRate: feeRate, - inputPrivKeyInfos: inputPrivKeyInfos, - vinOutpoints: vinOutpoints, - ); - } - - if (outputs.length == 1 && outputs[0].toOutput.amount == BigInt.zero) { - outputs = [ - BitcoinScriptOutput( - script: outputs[0].toOutput.scriptPubKey, value: utxos.sumOfUtxosValue()) - ]; - } - - // https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation - final preOutputSum = - outputs.fold(BigInt.zero, (acc, output) => acc + output.toOutput.amount); - var fee = utxos.sumOfUtxosValue() - preOutputSum; - - // determines if the fee is correct: - BigInt _sumOutputAmounts(List outputs) { - BigInt sum = BigInt.zero; - for (final e in outputs) { - sum += e.amount; - } - return sum; - } - - final sum1 = _sumOutputAmounts(outputs.map((e) => e.toOutput).toList()) + fee; - final sum2 = utxos.sumOfUtxosValue(); - if (sum1 != sum2) { - printV("@@@@@ WE HAD TO ADJUST THE FEE! @@@@@@@@"); - final diff = sum2 - sum1; - // add the difference to the fee (abs value): - fee += diff.abs(); - } - - final txb = - BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network); - final resp = await CwMweb.create(CreateRequest( - rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(), - scanSecret: scanSecret, - spendSecret: spendSecret, - feeRatePerKb: Int64(feeRate * 1000), - dryRun: true)); - final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx)); - final posUtxos = utxos - .where((utxo) => tx.inputs - .any((input) => input.txId == utxo.utxo.txHash && input.txIndex == utxo.utxo.vout)) - .toList(); - final posOutputSum = tx.outputs.fold(0, (acc, output) => acc + output.amount.toInt()); - final mwebInputSum = utxos.sumOfUtxosValue() - posUtxos.sumOfUtxosValue(); - final expectedPegin = max(0, (preOutputSum - mwebInputSum).toInt()); - var feeIncrease = posOutputSum - expectedPegin; - if (expectedPegin > 0 && fee == BigInt.zero) { - feeIncrease += await super.calcFee( - utxos: posUtxos, - outputs: tx.outputs - .map((output) => - BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount)) - .toList(), - network: network, - memo: memo, - feeRate: feeRate) + - feeRate * 41; - } - return fee.toInt() + feeIncrease; - } - - @override - Future createTransaction(Object credentials) async { - try { - var tx = await super.createTransaction(credentials) as PendingBitcoinTransaction; - tx.isMweb = mwebEnabled; - - if (!mwebEnabled) { - tx.changeAddressOverride = - (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb)) - .address; - return tx; - } - await waitForMwebAddresses(); - - final resp = await CwMweb.create(CreateRequest( - rawTx: hex.decode(tx.hex), - scanSecret: scanSecret, - spendSecret: spendSecret, - feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000, - )); - final tx2 = BtcTransaction.fromRaw(hex.encode(resp.rawTx)); - - // check if the transaction doesn't contain any mweb inputs or outputs: - final transactionCredentials = credentials as BitcoinTransactionCredentials; - - bool hasMwebInput = false; - bool hasMwebOutput = false; - bool hasRegularOutput = false; - - for (final output in transactionCredentials.outputs) { - final address = output.address.toLowerCase(); - final extractedAddress = output.extractedAddress?.toLowerCase(); - - if (address.contains("mweb")) { - hasMwebOutput = true; - } - if (!address.contains("mweb")) { - hasRegularOutput = true; - } - if (extractedAddress != null && extractedAddress.isNotEmpty) { - if (extractedAddress.contains("mweb")) { - hasMwebOutput = true; - } - if (!extractedAddress.contains("mweb")) { - hasRegularOutput = true; - } - } - } - - // check if mweb inputs are used: - for (final utxo in tx.utxos) { - if (utxo.utxo.scriptType == SegwitAddresType.mweb) { - hasMwebInput = true; - } - } - - // could probably be simplified but left for clarity: - bool isPegIn = !hasMwebInput && hasMwebOutput; - bool isPegOut = hasMwebInput && hasRegularOutput; - bool isRegular = !hasMwebInput && !hasMwebOutput; - bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput; - tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses) - .getChangeAddress(coinTypeToSpendFrom: shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any)) - .address; - if (isRegular) { - tx.isMweb = false; - return tx; - } - - // check if any of the inputs of this transaction are hog-ex: - // this list is only non-mweb inputs: - tx2.inputs.forEach((txInput) { - bool isHogEx = true; - - final utxo = unspentCoins - .firstWhere((utxo) => utxo.hash == txInput.txId && utxo.vout == txInput.txIndex); - - // TODO: detect actual hog-ex inputs - - if (!isHogEx) { - return; - } - - int confirmations = utxo.confirmations ?? 0; - if (confirmations < 6) { - throw Exception( - "A transaction input has less than 6 confirmations, please try again later."); - } - }); - - tx.hexOverride = tx2 - .copyWith( - witnesses: tx2.inputs.asMap().entries.map((e) { - final utxo = unspentCoins - .firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex); - final key = generateECPrivate( - hd: utxo.bitcoinAddressRecord.isHidden - ? walletAddresses.sideHd - : walletAddresses.mainHd, - index: utxo.bitcoinAddressRecord.index, - network: network); - final digest = tx2.getTransactionSegwitDigit( - txInIndex: e.key, - script: key.getPublic().toP2pkhAddress().toScriptPubKey(), - amount: BigInt.from(utxo.value), - ); - return TxWitnessInput(stack: [key.signInput(digest), key.getPublic().toHex()]); - }).toList()) - .toHex(); - tx.outputAddresses = resp.outputId; - - return tx - ..addListener((transaction) async { - final addresses = {}; - transaction.inputAddresses?.forEach((id) async { - final utxo = mwebUtxosBox.get(id); - // await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent - if (utxo == null) return; - // mark utxo as spent so we add it to the unconfirmed balance (as negative): - utxo.spent = true; - await mwebUtxosBox.put(id, utxo); - final addressRecord = walletAddresses.allAddresses - .firstWhere((addressRecord) => addressRecord.address == utxo.address); - if (!addresses.contains(utxo.address)) { - addresses.add(utxo.address); - } - addressRecord.balance -= utxo.value.toInt(); - }); - transaction.inputAddresses?.addAll(addresses); - printV("isPegIn: $isPegIn, isPegOut: $isPegOut"); - transaction.additionalInfo["isPegIn"] = isPegIn; - transaction.additionalInfo["isPegOut"] = isPegOut; - transactionHistory.addOne(transaction); - await updateUnspent(); - await updateBalance(); - }); - } catch (e, s) { - printV(e); - printV(s); - if (e.toString().contains("commit failed")) { - printV(e); - throw Exception("Transaction commit failed (no peers responded), please try again."); - } - rethrow; - } - } - - @override - Future save() async { - await super.save(); - } - - @override - Future close({bool shouldCleanup = false}) async { - _utxoStream?.cancel(); - _feeRatesTimer?.cancel(); - _syncTimer?.cancel(); - _processingTimer?.cancel(); - if (shouldCleanup) { - try { - await stopSync(); - } catch (_) {} - } - await super.close(shouldCleanup: shouldCleanup); - } - - Future setMwebEnabled(bool enabled) async { - if (mwebEnabled == enabled && - alwaysScan == enabled && - (walletAddresses as LitecoinWalletAddresses).mwebEnabled == enabled) { - return; - } - - alwaysScan = enabled; - mwebEnabled = enabled; - (walletAddresses as LitecoinWalletAddresses).mwebEnabled = enabled; - await save(); - try { - await stopSync(); - } catch (_) {} - await startSync(); - } - - Future getStatusRequest() async { - final resp = await CwMweb.status(StatusRequest()); - return resp; - } - - @override - Future signMessage(String message, {String? address = null}) async { - final index = address != null - ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index - : null; - final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); - final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex()); - - final privateKey = ECDSAPrivateKey.fromBytes( - priv.toBytes(), - Curves.generatorSecp256k1, - ); - - final signature = - signLitecoinMessage(utf8.encode(message), privateKey: privateKey, bipPrive: priv.prive); - - return base64Encode(signature); - } - - List _magicPrefix(List message, List messagePrefix) { - final encodeLength = IntUtils.encodeVarint(message.length); - - return [...messagePrefix, ...encodeLength, ...message]; - } - - List signLitecoinMessage(List message, - {required ECDSAPrivateKey privateKey, required Bip32PrivateKey bipPrive}) { - String messagePrefix = '\x19Litecoin Signed Message:\n'; - final messageHash = QuickCrypto.sha256Hash(magicMessage(message, messagePrefix)); - final signingKey = EcdsaSigningKey(privateKey); - ECDSASignature ecdsaSign = - signingKey.signDigestDeterminstic(digest: messageHash, hashFunc: () => SHA256()); - final n = Curves.generatorSecp256k1.order! >> 1; - BigInt newS; - if (ecdsaSign.s.compareTo(n) > 0) { - newS = Curves.generatorSecp256k1.order! - ecdsaSign.s; - } else { - newS = ecdsaSign.s; - } - final rawSig = ECDSASignature(ecdsaSign.r, newS); - final rawSigBytes = rawSig.toBytes(BitcoinSignerUtils.baselen); - - final pub = bipPrive.publicKey; - final ECDomainParameters curve = ECCurve_secp256k1(); - final point = curve.curve.decodePoint(pub.point.toBytes()); - - final rawSigEc = ECSignature(rawSig.r, rawSig.s); - - final recId = SignUtils.findRecoveryId( - SignUtils.getHexString(messageHash, offset: 0, length: messageHash.length), - rawSigEc, - Uint8List.fromList(pub.uncompressed), - ); - - final v = recId + 27 + (point!.isCompressed ? 4 : 0); - - final combined = Uint8List.fromList([v, ...rawSigBytes]); - - return combined; - } - - List magicMessage(List message, String messagePrefix) { - final prefixBytes = StringUtils.encode(messagePrefix); - final magic = _magicPrefix(message, prefixBytes); - return QuickCrypto.sha256Hash(magic); - } - - @override - Future verifyMessage(String message, String signature, {String? address = null}) async { - if (address == null) { - return false; - } - - List sigDecodedBytes = []; - - if (signature.endsWith('=')) { - sigDecodedBytes = base64.decode(signature); - } else { - sigDecodedBytes = hex.decode(signature); - } - - if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) { - throw ArgumentException( - "litecoin signature must be 64 bytes without recover-id or 65 bytes with recover-id"); - } - - String messagePrefix = '\x19Litecoin Signed Message:\n'; - final messageHash = QuickCrypto.sha256Hash(magicMessage(utf8.encode(message), messagePrefix)); - - List correctSignature = - sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes); - List rBytes = correctSignature.sublist(0, 32); - List sBytes = correctSignature.sublist(32); - final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes)); - - List possibleRecoverIds = [0, 1]; - - final baseAddress = RegexUtils.addressTypeFromStr(address, network); - - for (int recoveryId in possibleRecoverIds) { - final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId); - final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes()); - - String? recoveredAddress; - - if (baseAddress is P2pkAddress) { - recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network); - } else if (baseAddress is P2pkhAddress) { - recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network); - } else if (baseAddress is P2wshAddress) { - recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network); - } else if (baseAddress is P2wpkhAddress) { - recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network); - } - - if (recoveredAddress == address) { - return true; - } - } - - return false; - } - - LedgerConnection? _ledgerConnection; - LitecoinLedgerApp? _litecoinLedgerApp; - - @override - void setLedgerConnection(LedgerConnection connection) { - _ledgerConnection = connection; - _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!, - derivationPath: walletInfo.derivationInfo!.derivationPath!); - } - - @override - Future buildHardwareWalletTransaction({ - required List outputs, - required BigInt fee, - required BasedUtxoNetwork network, - required List utxos, - required Map publicKeys, - String? memo, - bool enableRBF = false, - BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, - BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, - }) async { - final readyInputs = []; - for (final utxo in utxos) { - final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); - final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - - readyInputs.add(LedgerTransaction( - rawTx: rawTx, - outputIndex: utxo.utxo.vout, - ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)), - ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - // sequence: enableRBF ? 0x1 : 0xffffffff, - sequence: 0xffffffff, - )); - } - - String? changePath; - for (final output in outputs) { - final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()]; - if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; - } - - final rawHex = await _litecoinLedgerApp!.createTransaction( - inputs: readyInputs, - outputs: outputs - .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value, - Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) - .toList(), - changePath: changePath, - sigHashType: 0x01, - additionals: ["bech32"], - isSegWit: true, - useTrustedInputForSegwit: true); - - return BtcTransaction.fromRaw(rawHex); - } } diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index bbb987766..a317fa9f2 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -1,213 +1,39 @@ -import 'dart:async'; -import 'dart:io' show Platform; -import 'dart:typed_data'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; -import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_mweb/cw_mweb.dart'; import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'litecoin_wallet_addresses.g.dart'; -class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses; +class LitecoinWalletAddresses = LitecoinWalletAddressesBase + with _$LitecoinWalletAddresses; -abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store { +abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses + with Store { LitecoinWalletAddressesBase( - WalletInfo walletInfo, { - required super.mainHd, - required super.sideHd, - required super.network, - required super.isHardwareWallet, - required this.mwebHd, - required this.mwebEnabled, - super.initialAddresses, - super.initialMwebAddresses, - super.initialRegularAddressIndex, - super.initialChangeAddressIndex, - }) : super(walletInfo) { - for (int i = 0; i < mwebAddresses.length; i++) { - mwebAddrs.add(mwebAddresses[i].address); - } - printV("initialized with ${mwebAddrs.length} mweb addresses"); - } - - final Bip32Slip10Secp256k1? mwebHd; - bool mwebEnabled; - int mwebTopUpIndex = 1000; - List mwebAddrs = []; - bool generating = false; - - List get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; - List get spendPubkey => - mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; + WalletInfo walletInfo, + {required bitcoin.HDWallet mainHd, + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super( + walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); @override - Future init() async { - if (!isHardwareWallet) await initMwebAddresses(); - await super.init(); - } - - @computed - @override - List get allAddresses { - return List.from(super.allAddresses)..addAll(mwebAddresses); - } - - Future ensureMwebAddressUpToIndexExists(int index) async { - if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { - return null; - } - - Uint8List scan = Uint8List.fromList(scanSecret); - Uint8List spend = Uint8List.fromList(spendPubkey); - - if (index < mwebAddresses.length && index < mwebAddrs.length) { - return; - } - - while (generating) { - printV("generating....."); - // this function was called multiple times in multiple places: - await Future.delayed(const Duration(milliseconds: 100)); - } - - printV("Generating MWEB addresses up to index $index"); - generating = true; - try { - while (mwebAddrs.length <= (index + 1)) { - final addresses = - await CwMweb.addresses(scan, spend, mwebAddrs.length, mwebAddrs.length + 50); - printV("generated up to index ${mwebAddrs.length}"); - // sleep for a bit to avoid making the main thread unresponsive: - await Future.delayed(Duration(milliseconds: 200)); - mwebAddrs.addAll(addresses!); - } - } catch (_) {} - generating = false; - printV("Done generating MWEB addresses len: ${mwebAddrs.length}"); - - // ensure mweb addresses are up to date: - // This is the Case if the Litecoin Wallet is a hardware Wallet - if (mwebHd == null) return; - - if (mwebAddresses.length < mwebAddrs.length) { - List addressRecords = mwebAddrs - .asMap() - .entries - .map((e) => BitcoinAddressRecord( - e.value, - index: e.key, - type: SegwitAddresType.mweb, - network: network, - )) - .toList(); - addMwebAddresses(addressRecords); - printV("set ${addressRecords.length} mweb addresses"); - } - } - - Future initMwebAddresses() async { - if (mwebAddrs.length < 1000) { - await ensureMwebAddressUpToIndexExists(20); - return; - } - } - - @override - String getAddress({ - required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - }) { - if (addressType == SegwitAddresType.mweb) { - return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1]; - } - return generateP2WPKHAddress(hd: hd, index: index, network: network); - } - - @override - Future getAddressAsync({ - required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - }) async { - if (addressType == SegwitAddresType.mweb) { - await ensureMwebAddressUpToIndexExists(index); - } - return getAddress(index: index, hd: hd, addressType: addressType); - } - - @action - @override - Future getChangeAddress( - {List? inputs, - List? outputs, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) async { - // use regular change address on peg in, otherwise use mweb for change address: - - if (!mwebEnabled || coinTypeToSpendFrom == UnspentCoinType.nonMweb) { - return super.getChangeAddress(); - } - - if (inputs != null && outputs != null) { - // check if this is a PEGIN: - bool outputsToMweb = false; - bool comesFromMweb = false; - - for (var i = 0; i < outputs.length; i++) { - // TODO: probably not the best way to tell if this is an mweb address - // (but it doesn't contain the "mweb" text at this stage) - if (outputs[i].address.toAddress(network).length > 110) { - outputsToMweb = true; - } - } - - inputs.forEach((element) { - if (!element.isSending || element.isFrozen) { - return; - } - if (element.address.contains("mweb")) { - comesFromMweb = true; - } - }); - - bool isPegIn = !comesFromMweb && outputsToMweb; - bool isNonMweb = !comesFromMweb && !outputsToMweb; - - // use regular change address if it's not an mweb tx or if it's a peg in: - if (isPegIn || isNonMweb) { - return super.getChangeAddress(); - } - } - - if (mwebEnabled) { - await ensureMwebAddressUpToIndexExists(1); - updateChangeAddresses(); - return BitcoinAddressRecord( - mwebAddrs[0], - index: 0, - type: SegwitAddresType.mweb, - network: network, - ); - } - - return super.getChangeAddress(); - } - - @override - String get addressForExchange { - // don't use mweb addresses for exchange refund address: - final addresses = receiveAddresses - .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed); - return addresses.first.address; - } -} + String getAddress({required int index, required bitcoin.HDWallet hd}) => + generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); +} \ No newline at end of file diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 89ae384d4..b13ac7a7f 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -1,11 +1,8 @@ import 'dart:io'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; -import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; -import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:hive/hive.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_core/wallet_service.dart'; @@ -14,48 +11,26 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:collection/collection.dart'; -import 'package:bip39/bip39.dart' as bip39; -import 'package:path_provider/path_provider.dart'; class LitecoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, - BitcoinRestoreWalletFromWIFCredentials, - BitcoinRestoreWalletFromHardware> { - LitecoinWalletService( - this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect); + BitcoinRestoreWalletFromWIFCredentials> { + LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; final Box unspentCoinsInfoSource; - final bool alwaysScan; - final bool isDirect; @override WalletType getType() => WalletType.litecoin; @override - Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { - final String mnemonic; - switch (credentials.walletInfo?.derivationInfo?.derivationType) { - case DerivationType.bip39: - final strength = credentials.seedPhraseLength == 24 ? 256 : 128; - - mnemonic = credentials.mnemonic ?? await MnemonicBip39.generate(strength: strength); - break; - case DerivationType.electrum: - default: - mnemonic = await generateElectrumMnemonic(); - break; - } - + Future create(BitcoinNewWalletCredentials credentials) async { final wallet = await LitecoinWalletBase.create( - mnemonic: mnemonic, - password: credentials.password!, - passphrase: credentials.passphrase, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); + mnemonic: await generateMnemonic(), + password: credentials.password!, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); @@ -68,90 +43,35 @@ class LitecoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; - - try { - final wallet = await LitecoinWalletBase.open( - password: password, - name: name, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - alwaysScan: alwaysScan, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); - await wallet.init(); - saveBackup(name); - return wallet; - } catch (_) { - await restoreWalletFilesFromBackup(name); - final wallet = await LitecoinWalletBase.open( - password: password, - name: name, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - alwaysScan: alwaysScan, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); - await wallet.init(); - return wallet; - } + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; + final wallet = await LitecoinWalletBase.open( + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.init(); + return wallet; } @override Future remove(String wallet) async { - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; + File(await pathForWalletDir(name: wallet, type: getType())) + .delete(recursive: true); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(wallet, getType()))!; await walletInfoSource.delete(walletInfo.key); - - // if there are no more litecoin wallets left, cleanup the neutrino db and other files created by mwebd: - if (walletInfoSource.values.where((info) => info.type == WalletType.litecoin).isEmpty) { - final appDirPath = (await getApplicationSupportDirectory()).path; - File neturinoDb = File('$appDirPath/neutrino.db'); - File blockHeaders = File('$appDirPath/block_headers.bin'); - File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin'); - File mwebdLogs = File('$appDirPath/logs/debug.log'); - if (neturinoDb.existsSync()) { - neturinoDb.deleteSync(); - } - if (blockHeaders.existsSync()) { - blockHeaders.deleteSync(); - } - if (regFilterHeaders.existsSync()) { - regFilterHeaders.deleteSync(); - } - if (mwebdLogs.existsSync()) { - mwebdLogs.deleteSync(); - } - } - - final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( - (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); - - final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); - - if (keysToDelete.isNotEmpty) { - await unspentCoinsInfoSource.deleteAll(keysToDelete); - } } @override Future rename(String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(currentName, getType()))!; final currentWallet = await LitecoinWalletBase.open( - password: password, - name: currentName, - walletInfo: currentWalletInfo, - unspentCoinsInfo: unspentCoinsInfoSource, - alwaysScan: alwaysScan, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); + password: password, + name: currentName, + walletInfo: currentWalletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await currentWallet.renameWalletFiles(newName); - await saveBackup(newName); final newWalletInfo = currentWalletInfo; newWalletInfo.id = WalletBase.idFor(newName, getType()); @@ -161,45 +81,22 @@ class LitecoinWalletService extends WalletService< } @override - Future restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials, - {bool? isTestnet}) async { - final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; - credentials.walletInfo?.network = network.value; - credentials.walletInfo?.derivationInfo?.derivationPath = - credentials.hwAccountData.derivationPath; - - final wallet = await LitecoinWallet( - password: credentials.password!, - xpub: credentials.hwAccountData.xpub, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); - await wallet.save(); - await wallet.init(); - return wallet; - } - - @override - Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, - {bool? isTestnet}) async => + Future restoreFromKeys( + BitcoinRestoreWalletFromWIFCredentials credentials) async => throw UnimplementedError(); @override - Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, - {bool? isTestnet}) async { - if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) { - throw LitecoinMnemonicIsIncorrectException(); + Future restoreFromSeed( + BitcoinRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw BitcoinMnemonicIsIncorrectException(); } final wallet = await LitecoinWalletBase.create( - password: credentials.password!, - passphrase: credentials.passphrase, - mnemonic: credentials.mnemonic, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); return wallet; diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart deleted file mode 100644 index 95a523d89..000000000 --- a/cw_bitcoin/lib/payjoin/manager.dart +++ /dev/null @@ -1,310 +0,0 @@ -import 'dart:async'; -import 'dart:isolate'; -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; -import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_persister.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; -import 'package:cw_bitcoin/payjoin/storage.dart'; -import 'package:cw_bitcoin/psbt/signer.dart'; -import 'package:cw_bitcoin/psbt/utils.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:payjoin_flutter/common.dart'; -import 'package:payjoin_flutter/receive.dart'; -import 'package:payjoin_flutter/send.dart'; -import 'package:payjoin_flutter/src/config.dart' as pj_config; -import 'package:payjoin_flutter/uri.dart' as PayjoinUri; - -class PayjoinManager { - PayjoinManager(this._payjoinStorage, this._wallet); - - final PayjoinStorage _payjoinStorage; - final BitcoinWalletBase _wallet; - final Map _activePollers = {}; - - static const List ohttpRelayUrls = [ - 'https://pj.bobspacebkk.com', - 'https://ohttp.achow101.com', - 'https://ohttp.cakewallet.com', - ]; - - static String randomOhttpRelayUrl() => - ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]; - - static const payjoinDirectoryUrl = 'https://payjo.in'; - - Future initPayjoin() => pj_config.PConfig.initializeApp(); - - Future resumeSessions() async { - final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id); - - final spawnedSessions = allSessions.map((session) { - if (session.isSenderSession) { - printV("Resuming Payjoin Sender Session ${session.pjUri!}"); - return _spawnSender( - sender: Sender.fromJson(json: session.sender!), - pjUri: session.pjUri!, - ); - } - final receiver = Receiver.fromJson(json: session.receiver!); - printV("Resuming Payjoin Receiver Session ${receiver.id()}"); - return spawnReceiver(receiver: receiver); - }); - - printV("Resumed ${spawnedSessions.length} Payjoin Sessions"); - await Future.wait(spawnedSessions); - } - - Future initSender( - String pjUriString, String originalPsbt, int networkFeesSatPerVb) async { - try { - final pjUri = - (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); - final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250); - final senderBuilder = await SenderBuilder.fromPsbtAndUri( - psbtBase64: originalPsbt, - pjUri: pjUri, - ); - final persister = PayjoinSenderPersister.impl(); - final newSender = - await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); - final senderToken = await newSender.persist(persister: persister); - - return Sender.load(token: senderToken, persister: persister); - } catch (e) { - throw Exception('Error initializing Payjoin Sender: $e'); - } - } - - Future spawnNewSender({ - required Sender sender, - required String pjUrl, - required BigInt amount, - bool isTestnet = false, - }) async { - final pjUri = Uri.parse(pjUrl).queryParameters['pj']!; - await _payjoinStorage.insertSenderSession( - sender, pjUri, _wallet.id, amount); - - return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri); - } - - Future _spawnSender({ - required Sender sender, - required String pjUri, - bool isTestnet = false, - }) async { - final completer = Completer(); - final receivePort = ReceivePort(); - - receivePort.listen((message) async { - if (message is Map) { - try { - switch (message['type'] as PayjoinSenderRequestTypes) { - case PayjoinSenderRequestTypes.requestPosted: - return; - case PayjoinSenderRequestTypes.psbtToSign: - final proposalPsbt = message['psbt'] as String; - final utxos = _wallet.getUtxoWithPrivateKeys(); - final finalizedPsbt = await _wallet.signPsbt(proposalPsbt, utxos); - final txId = getTxIdFromPsbtV0(finalizedPsbt); - _wallet.commitPsbt(finalizedPsbt); - - _cleanupSession(pjUri); - await _payjoinStorage.markSenderSessionComplete(pjUri, txId); - completer.complete(); - } - } catch (e) { - _cleanupSession(pjUri); - await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, e.toString()); - completer.complete(); - } - } else if (message is PayjoinSessionError) { - _cleanupSession(pjUri); - if (message is UnrecoverableError) { - await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, message.message); - completer.complete(); - } else if (message is RecoverableError) { - completer.complete(); - } else { - completer.completeError(message); - } - } - }); - - final isolate = await Isolate.spawn( - PayjoinSenderWorker.run, - [receivePort.sendPort, sender.toJson(), pjUri], - ); - - _activePollers[pjUri] = PayjoinPollerSession(isolate, receivePort); - - return completer.future; - } - - Future getUnusedReceiver(String address, - [bool isTestnet = false]) async { - final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id); - - if (session != null) { - await PayjoinUri.Url.fromStr(payjoinDirectoryUrl); - - return Receiver.fromJson(json: session.receiver!); - } - - return initReceiver(address); - } - - Future initReceiver(String address, [bool isTestnet = false]) async { - final ohttpKeys = await PayjoinUri.fetchOhttpKeys( - ohttpRelay: await randomOhttpRelayUrl(), - payjoinDirectory: payjoinDirectoryUrl, - ); - - final newReceiver = await NewReceiver.create( - address: address, - network: isTestnet ? Network.testnet : Network.bitcoin, - directory: payjoinDirectoryUrl, - ohttpKeys: ohttpKeys, - ); - final persister = PayjoinReceiverPersister.impl(); - final receiverToken = await newReceiver.persist(persister: persister); - final receiver = await Receiver.load(persister: persister, token: receiverToken); - - await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); - - return receiver; - } - - Future spawnReceiver({ - required Receiver receiver, - bool isTestnet = false, - }) async { - final completer = Completer(); - final receivePort = ReceivePort(); - - SendPort? mainToIsolateSendPort; - List utxos = []; - String rawAmount = '0'; - - receivePort.listen((message) async { - if (message is Map) { - try { - switch (message['type'] as PayjoinReceiverRequestTypes) { - case PayjoinReceiverRequestTypes.processOriginalTx: - final tx = message['tx'] as String; - rawAmount = getOutputAmountFromTx(tx, _wallet); - break; - case PayjoinReceiverRequestTypes.checkIsOwned: - (_wallet.walletAddresses as BitcoinWalletAddresses) - .newPayjoinReceiver(); - _payjoinStorage.markReceiverSessionInProgress(receiver.id()); - - final inputScript = message['input_script'] as Uint8List; - final isOwned = - _wallet.isMine(Script.fromRaw(byteData: inputScript)); - mainToIsolateSendPort?.send({ - 'requestId': message['requestId'], - 'result': isOwned, - }); - break; - - case PayjoinReceiverRequestTypes.checkIsReceiverOutput: - final outputScript = message['output_script'] as Uint8List; - final isReceiverOutput = - _wallet.isMine(Script.fromRaw(byteData: outputScript)); - mainToIsolateSendPort?.send({ - 'requestId': message['requestId'], - 'result': isReceiverOutput, - }); - break; - - case PayjoinReceiverRequestTypes.getCandidateInputs: - utxos = _wallet.getUtxoWithPrivateKeys(); - if (utxos.isEmpty) { - await _wallet.updateAllUnspents(); - utxos = _wallet.getUtxoWithPrivateKeys(); - } - mainToIsolateSendPort?.send({ - 'requestId': message['requestId'], - 'result': utxos, - }); - break; - - case PayjoinReceiverRequestTypes.processPsbt: - final psbt = message['psbt'] as String; - final signedPsbt = await _wallet.signPsbt(psbt, utxos); - mainToIsolateSendPort?.send({ - 'requestId': message['requestId'], - 'result': signedPsbt, - }); - break; - - case PayjoinReceiverRequestTypes.proposalSent: - _cleanupSession(receiver.id()); - final psbt = message['psbt'] as String; - await _payjoinStorage.markReceiverSessionComplete( - receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount); - completer.complete(); - } - } catch (e) { - _cleanupSession(receiver.id()); - await _payjoinStorage.markReceiverSessionUnrecoverable( - receiver.id(), e.toString()); - completer.completeError(e); - } - } else if (message is PayjoinSessionError) { - _cleanupSession(receiver.id()); - if (message is UnrecoverableError) { - await _payjoinStorage.markReceiverSessionUnrecoverable( - receiver.id(), message.message); - completer.complete(); - } else if (message is RecoverableError) { - completer.complete(); - } else { - completer.completeError(message); - } - } else if (message is SendPort) { - mainToIsolateSendPort = message; - } - }); - - final isolate = await Isolate.spawn( - PayjoinReceiverWorker.run, - [receivePort.sendPort, receiver.toJson()], - ); - - _activePollers[receiver.id()] = PayjoinPollerSession(isolate, receivePort); - - return completer.future; - } - - void cleanupSessions() { - final sessionIds = _activePollers.keys.toList(); - for (final sessionId in sessionIds) { - _cleanupSession(sessionId); - } - } - - void _cleanupSession(String sessionId) { - _activePollers[sessionId]?.close(); - _activePollers.remove(sessionId); - } -} - -class PayjoinPollerSession { - final Isolate isolate; - final ReceivePort port; - - PayjoinPollerSession(this.isolate, this.port); - - void close() { - isolate.kill(); - port.close(); - } -} diff --git a/cw_bitcoin/lib/payjoin/payjoin_persister.dart b/cw_bitcoin/lib/payjoin/payjoin_persister.dart deleted file mode 100644 index 4e395e36a..000000000 --- a/cw_bitcoin/lib/payjoin/payjoin_persister.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:payjoin_flutter/src/generated/api/receive.dart'; -import 'package:payjoin_flutter/src/generated/api/send.dart'; - -class PayjoinSenderPersister implements DartSenderPersister { - static DartSenderPersister impl() { - final impl = PayjoinSenderPersister(); - return DartSenderPersister( - save: (sender) => impl.save(sender: sender), - load: (token) => impl.load(token: token), - ); - } - - final Map _store = {}; - - Future save({required FfiSender sender}) async { - final token = sender.key(); - _store[token.toBytes().toString()] = sender; - return token; - } - - Future load({required SenderToken token}) async { - final sender = _store[token.toBytes().toString()]; - if (sender == null) { - throw Exception('Sender not found for the provided token.'); - } - return sender; - } - - @override - void dispose() => _store.clear(); - - @override - bool get isDisposed => _store.isEmpty; -} - -class PayjoinReceiverPersister implements DartReceiverPersister { - static DartReceiverPersister impl() { - final impl = PayjoinReceiverPersister(); - return DartReceiverPersister( - save: (receiver) => impl.save(receiver: receiver), - load: (token) => impl.load(token: token), - ); - } - - final Map _store = {}; - - Future save({required FfiReceiver receiver}) async { - final token = receiver.key(); - _store[token.toBytes().toString()] = receiver; - return token; - } - - Future load({required ReceiverToken token}) async { - final receiver = _store[token.toBytes().toString()]; - if (receiver == null) { - throw Exception('Receiver not found for the provided token.'); - } - return receiver; - } - - @override - void dispose() => _store.clear(); - - @override - bool get isDisposed => _store.isEmpty; -} diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart deleted file mode 100644 index c56148de2..000000000 --- a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; -import 'dart:typed_data'; - -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:cw_bitcoin/payjoin/manager.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; -import 'package:cw_bitcoin/psbt/signer.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:payjoin_flutter/bitcoin_ffi.dart'; -import 'package:payjoin_flutter/common.dart'; -import 'package:payjoin_flutter/receive.dart'; -import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; -import 'package:http/http.dart' as very_insecure_http_do_not_use; // for errors - -enum PayjoinReceiverRequestTypes { - processOriginalTx, - proposalSent, - getCandidateInputs, - checkIsOwned, - checkIsReceiverOutput, - processPsbt; -} - -class PayjoinReceiverWorker { - final SendPort sendPort; - final pendingRequests = >{}; - - PayjoinReceiverWorker._(this.sendPort); - static final client = ProxyWrapper().getHttpIOClient(); - static Future run(List args) async { - await pj.core.init(); - - final sendPort = args[0] as SendPort; - final receiverJson = args[1] as String; - - final worker = PayjoinReceiverWorker._(sendPort); - final receivePort = ReceivePort(); - - sendPort.send(receivePort.sendPort); - receivePort.listen(worker.handleMessage); - - try { - final receiver = Receiver.fromJson(json: receiverJson); - - final uncheckedProposal = - await worker.receiveUncheckedProposal(receiver); - - final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); - sendPort.send({ - 'type': PayjoinReceiverRequestTypes.processOriginalTx, - 'tx': BytesUtils.toHexString(originalTx), - }); - - final payjoinProposal = await worker.processPayjoinProposal( - uncheckedProposal, - ); - final psbt = await worker.sendFinalProposal(payjoinProposal); - sendPort.send({ - 'type': PayjoinReceiverRequestTypes.proposalSent, - 'psbt': psbt, - }); - } catch (e) { - if (e is HttpException || - (e is very_insecure_http_do_not_use.ClientException && - e.message.contains("Software caused connection abort"))) { - sendPort.send(PayjoinSessionError.recoverable(e.toString())); - } else { - sendPort.send(PayjoinSessionError.unrecoverable(e.toString())); - } - } - } - - void handleMessage(dynamic message) async { - if (message is Map) { - final requestId = message['requestId'] as String?; - if (requestId != null && pendingRequests.containsKey(requestId)) { - pendingRequests[requestId]!.complete(message['result']); - pendingRequests.remove(requestId); - } - } - } - - Future _sendRequest(PayjoinReceiverRequestTypes type, - [Map data = const {}]) async { - final completer = Completer(); - final requestId = DateTime.now().millisecondsSinceEpoch.toString(); - pendingRequests[requestId] = completer; - - sendPort.send({ - ...data, - 'type': type, - 'requestId': requestId, - }); - - return completer.future; - } - - Future receiveUncheckedProposal(Receiver session) async { - while (true) { - printV("Polling for Proposal (${session.id()})"); - final extractReq = await session.extractReq( - ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(), - ); - final request = extractReq.$1; - - final url = Uri.parse(request.url.asString()); - final httpRequest = await client.post(url, - headers: {'Content-Type': request.contentType}, body: request.body); - - final proposal = await session.processRes( - body: httpRequest.bodyBytes, ctx: extractReq.$2); - if (proposal != null) return proposal; - } - } - - Future sendFinalProposal(PayjoinProposal finalProposal) async { - final req = await finalProposal.extractReq( - ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(), - ); - final proposalReq = req.$1; - final proposalCtx = req.$2; - - final request = await client.post( - Uri.parse(proposalReq.url.asString()), - headers: {"Content-Type": proposalReq.contentType}, - body: proposalReq.body, - ); - - await finalProposal.processRes( - res: request.bodyBytes, - ohttpContext: proposalCtx, - ); - - return await finalProposal.psbt(); - } - - Future processPayjoinProposal( - UncheckedProposal proposal) async { - await proposal.extractTxToScheduleBroadcast(); - // TODO Handle this. send to the main port on a timer? - - try { - // Receive Check 1: can broadcast - final pj1 = await proposal.assumeInteractiveReceiver(); - - // Receive Check 2: original PSBT has no receiver-owned inputs - final pj2 = await pj1.checkInputsNotOwned( - isOwned: (inputScript) async { - final result = await _sendRequest( - PayjoinReceiverRequestTypes.checkIsOwned, - {'input_script': inputScript}, - ); - return result as bool; - }, - ); - // Receive Check 3: sender inputs have not been seen before (prevent probing attacks) - final pj3 = await pj2.checkNoInputsSeenBefore(isKnown: (input) => false); - - // Identify receiver outputs - final pj4 = await pj3.identifyReceiverOutputs( - isReceiverOutput: (outputScript) async { - final result = await _sendRequest( - PayjoinReceiverRequestTypes.checkIsReceiverOutput, - {'output_script': outputScript}, - ); - return result as bool; - }, - ); - final pj5 = await pj4.commitOutputs(); - - final listUnspent = - await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); - final unspent = listUnspent as List; - if (unspent.isEmpty) throw RecoverableError('No unspent outputs available'); - - final selectedUtxo = await _inputPairFromUtxo(unspent[0]); - final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]); - final pj7 = await pj6.commitInputs(); - - // Finalize proposal - final payjoinProposal = await pj7.finalizeProposal( - processPsbt: (String psbt) async { - final result = await _sendRequest( - PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); - return result as String; - }, - // TODO set maxFeeRateSatPerVb - maxFeeRateSatPerVb: BigInt.from(10000), - ); - return payjoinProposal; - } catch (e) { - printV('Error occurred while finalizing proposal: $e'); - rethrow; - } - } - - Future _inputPairFromUtxo(UtxoWithPrivateKey utxo) async { - final txout = TxOut( - value: utxo.utxo.value, - scriptPubkey: Uint8List.fromList( - utxo.ownerDetails.address.toScriptPubKey().toBytes()), - ); - - final psbtin = - PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); - - final previousOutput = - OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); - - final txin = TxIn( - previousOutput: previousOutput, - scriptSig: await Script.newInstance(rawOutputScript: []), - witness: [], - sequence: 0, - ); - - return InputPair.newInstance(txin: txin, psbtin: psbtin); - } -} diff --git a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart deleted file mode 100644 index 7e85cc773..000000000 --- a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; - -import 'package:cw_bitcoin/payjoin/manager.dart'; -import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:payjoin_flutter/common.dart'; -import 'package:payjoin_flutter/send.dart'; -import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; -import 'package:payjoin_flutter/src/generated/api/send/error.dart' as pj_error; -import 'package:payjoin_flutter/uri.dart' as pj_uri; - -enum PayjoinSenderRequestTypes { - requestPosted, - psbtToSign; -} - -class PayjoinSenderWorker { - final SendPort sendPort; - final pendingRequests = >{}; - final String pjUrl; - - PayjoinSenderWorker._(this.sendPort, this.pjUrl); - - static Future run(List args) async { - await pj.core.init(); - - final sendPort = args[0] as SendPort; - final senderJson = args[1] as String; - final pjUrl = args[2] as String; - - final sender = Sender.fromJson(json: senderJson); - final worker = PayjoinSenderWorker._(sendPort, pjUrl); - - try { - final proposalPsbt = await worker.runSender(sender); - sendPort.send({ - 'type': PayjoinSenderRequestTypes.psbtToSign, - 'psbt': proposalPsbt, - }); - } catch (e) { - sendPort.send(e); - } - } - final client = ProxyWrapper().getHttpIOClient(); - - /// Run a payjoin sender (V2 protocol first, fallback to V1). - Future runSender(Sender sender) async { - - try { - return await _runSenderV2(sender); - } catch (e) { - printV(e); - if (e is pj_error.FfiCreateRequestError) { - return await _runSenderV1(sender); - } else if (e is HttpException) { - printV(e); - throw Exception(PayjoinSessionError.recoverable(e.toString())); - } else { - throw Exception(PayjoinSessionError.unrecoverable(e.toString())); - } - } - } - - /// Attempt to send payjoin using the V2 of the protocol. - Future _runSenderV2(Sender sender) async { - try { - final postRequest = await sender.extractV2( - ohttpProxyUrl: - await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), - ); - - final postResult = await _postRequest(postRequest.$1); - final getContext = - await postRequest.$2.processResponse(response: postResult); - - sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl}); - - while (true) { - printV('Polling V2 Proposal Request (${pjUrl})'); - - final getRequest = await getContext.extractReq( - ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(), - ); - final getRes = await _postRequest(getRequest.$1); - final proposalPsbt = await getContext.processResponse( - response: getRes, - ohttpCtx: getRequest.$2, - ); - printV("$proposalPsbt"); - if (proposalPsbt != null) return proposalPsbt; - } - } catch (e) { - rethrow; - } - } - - /// Attempt to send payjoin using the V1 of the protocol. - Future _runSenderV1(Sender sender) async { - try { - final postRequest = await sender.extractV1(); - final response = await _postRequest(postRequest.$1); - - sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted}); - - return await postRequest.$2.processResponse(response: response); - } catch (e, stack) { - throw PayjoinSessionError.unrecoverable('Send V1 payjoin error: $e, $stack'); - } - } - - Future> _postRequest(Request req) async { - final httpRequest = await client.post(Uri.parse(req.url.asString()), - headers: {'Content-Type': req.contentType}, body: req.body); - - return httpRequest.bodyBytes; - } -} diff --git a/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart b/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart deleted file mode 100644 index 06e0a5431..000000000 --- a/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart +++ /dev/null @@ -1,16 +0,0 @@ -class PayjoinSessionError { - final String message; - - const PayjoinSessionError._(this.message); - - factory PayjoinSessionError.recoverable(String message) = RecoverableError; - factory PayjoinSessionError.unrecoverable(String message) = UnrecoverableError; -} - -class RecoverableError extends PayjoinSessionError { - const RecoverableError(super.message) : super._(); -} - -class UnrecoverableError extends PayjoinSessionError { - const UnrecoverableError(super.message) : super._(); -} diff --git a/cw_bitcoin/lib/payjoin/storage.dart b/cw_bitcoin/lib/payjoin/storage.dart deleted file mode 100644 index 5fb9d5716..000000000 --- a/cw_bitcoin/lib/payjoin/storage.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:cw_core/payjoin_session.dart'; -import 'package:hive/hive.dart'; -import 'package:payjoin_flutter/receive.dart'; -import 'package:payjoin_flutter/send.dart'; - -class PayjoinStorage { - PayjoinStorage(this._payjoinSessionSources); - - final Box _payjoinSessionSources; - - static const String _receiverPrefix = 'pj_recv_'; - static const String _senderPrefix = 'pj_send_'; - - Future insertReceiverSession( - Receiver receiver, - String walletId, - ) => - _payjoinSessionSources.put( - "$_receiverPrefix${receiver.id()}", - PayjoinSession( - walletId: walletId, - receiver: receiver.toJson(), - ), - ); - - PayjoinSession? getUnusedActiveReceiverSession(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - session.status == PayjoinSessionStatus.created.name && - !session.isSenderSession) - .firstOrNull; - - Future markReceiverSessionComplete( - String sessionId, String txId, String amount) async { - final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; - - session.status = PayjoinSessionStatus.success.name; - session.txId = txId; - session.rawAmount = amount; - await session.save(); - } - - Future markReceiverSessionUnrecoverable( - String sessionId, String reason) async { - final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; - - session.status = PayjoinSessionStatus.unrecoverable.name; - session.error = reason; - await session.save(); - } - - Future markReceiverSessionInProgress(String sessionId) async { - final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; - - session.status = PayjoinSessionStatus.inProgress.name; - session.inProgressSince = DateTime.now(); - await session.save(); - } - - Future insertSenderSession( - Sender sender, - String pjUrl, - String walletId, - BigInt amount, - ) => - _payjoinSessionSources.put( - "$_senderPrefix$pjUrl", - PayjoinSession( - walletId: walletId, - pjUri: pjUrl, - sender: sender.toJson(), - status: PayjoinSessionStatus.inProgress.name, - inProgressSince: DateTime.now(), - rawAmount: amount.toString(), - ), - ); - - Future markSenderSessionComplete(String pjUrl, String txId) async { - final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!; - - session.status = PayjoinSessionStatus.success.name; - session.txId = txId; - await session.save(); - } - - Future markSenderSessionUnrecoverable(String pjUrl, String reason) async { - final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!; - - session.status = PayjoinSessionStatus.unrecoverable.name; - session.error = reason; - await session.save(); - } - - List readAllOpenSessions(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - ![ - PayjoinSessionStatus.success.name, - PayjoinSessionStatus.unrecoverable.name - ].contains(session.status)) - .toList(); -} diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 6930524eb..e2dc10bfb 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,59 +1,30 @@ -import 'package:cw_bitcoin/electrum_wallet.dart'; -import 'package:grpc/grpc.dart'; -import 'package:cw_bitcoin/exceptions.dart'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_mweb/cw_mweb.dart'; -import 'package:cw_mweb/mwebd.pb.dart'; class PendingBitcoinTransaction with PendingTransaction { - PendingBitcoinTransaction( - this._tx, - this.type, { - required this.electrumClient, - required this.amount, - required this.fee, - required this.feeRate, - this.network, - required this.hasChange, - this.isSendAll = false, - this.hasTaprootInputs = false, - this.isMweb = false, - this.utxos = const [], - this.publicKeys, - this.commitOverride, - }) : _listeners = []; + PendingBitcoinTransaction(this._tx, this.type, + {required this.electrumClient, + required this.amount, + required this.fee}) + : _listeners = []; final WalletType type; - final BtcTransaction _tx; + final bitcoin.Transaction _tx; final ElectrumClient electrumClient; final int amount; final int fee; - final String feeRate; - final BasedUtxoNetwork? network; - final bool isSendAll; - final bool hasChange; - final bool hasTaprootInputs; - List utxos; - bool isMweb; - String? changeAddressOverride; - String? idOverride; - String? hexOverride; - List? outputAddresses; - final Map? publicKeys; - Future Function()? commitOverride; @override - String get id => idOverride ?? _tx.txId(); + String get id => _tx.getId(); @override - String get hex => hexOverride ?? _tx.serialize(); + String get hex => _tx.toHex(); @override String get amountFormatted => bitcoinAmountToString(amount: amount); @@ -61,93 +32,22 @@ class PendingBitcoinTransaction with PendingTransaction { @override String get feeFormatted => bitcoinAmountToString(amount: fee); - @override - int? get outputCount => _tx.outputs.length; - - List get outputs => _tx.outputs; - - bool get hasSilentPayment => _tx.hasSilentPayment; - - PendingChange? get change { - try { - final change = _tx.outputs.firstWhere((out) => out.isChange); - if (changeAddressOverride != null) { - return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount)); - } - return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount)); - } catch (_) { - return null; - } - } - final List _listeners; - Future _commit() async { - int? callId; - - final result = await electrumClient.broadcastTransaction( - transactionRaw: hex, network: network, idCallback: (id) => callId = id); - - if (result.isEmpty) { - if (callId != null) { - final error = electrumClient.getErrorMessage(callId!); - - if (error.contains("dust")) { - if (hasChange) { - throw BitcoinTransactionCommitFailedDustChange(); - } else if (!isSendAll) { - throw BitcoinTransactionCommitFailedDustOutput(); - } else { - throw BitcoinTransactionCommitFailedDustOutputSendAll(); - } - } - - if (error.contains("bad-txns-vout-negative")) { - throw BitcoinTransactionCommitFailedVoutNegative(); - } - - if (error.contains("non-BIP68-final")) { - throw BitcoinTransactionCommitFailedBIP68Final(); - } - - if (error.contains("min fee not met")) { - throw BitcoinTransactionCommitFailedLessThanMin(); - } - - throw BitcoinTransactionCommitFailed(errorMessage: error); - } - - throw BitcoinTransactionCommitFailed(); - } - } - - Future _ltcCommit() async { - try { - final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex))); - idOverride = resp.txid; - } on GrpcError catch (e) { - throw BitcoinTransactionCommitFailed(errorMessage: e.message); - } catch (e) { - throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}"); - } - } - @override Future commit() async { - if (commitOverride != null) { - return commitOverride?.call(); + final result = + await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex()); + + if (result.isEmpty) { + throw BitcoinCommitTransactionException(); } - if (isMweb) { - await _ltcCommit(); - } else { - await _commit(); - } - - _listeners.forEach((listener) => listener(transactionInfo())); + _listeners?.forEach((listener) => listener(transactionInfo())); } - void addListener(void Function(ElectrumTransactionInfo transaction) listener) => + void addListener( + void Function(ElectrumTransactionInfo transaction) listener) => _listeners.add(listener); ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, @@ -157,14 +57,6 @@ class PendingBitcoinTransaction with PendingTransaction { direction: TransactionDirection.outgoing, date: DateTime.now(), isPending: true, - isReplaced: false, confirmations: 0, - inputAddresses: _tx.inputs.map((input) => input.txId).toList(), - outputAddresses: outputAddresses, fee: fee); - - @override - Future commitUR() { - throw UnimplementedError(); - } } diff --git a/cw_bitcoin/lib/psbt/signer.dart b/cw_bitcoin/lib/psbt/signer.dart deleted file mode 100644 index 1d0ceba8b..000000000 --- a/cw_bitcoin/lib/psbt/signer.dart +++ /dev/null @@ -1,263 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:collection/collection.dart'; -import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; -import 'package:cw_bitcoin/utils.dart'; -import 'package:ledger_bitcoin/psbt.dart'; -import 'package:ledger_bitcoin/src/utils/buffer_writer.dart'; - -extension PsbtSigner on PsbtV2 { - Uint8List extractUnsignedTX({bool getSegwit = true}) { - final tx = BufferWriter()..writeUInt32(getGlobalTxVersion()); - - final isSegwit = getInputWitnessUtxo(0) != null; - if (isSegwit && getSegwit) { - tx.writeSlice(Uint8List.fromList([0, 1])); - } - - final inputCount = getGlobalInputCount(); - tx.writeVarInt(inputCount); - - for (var i = 0; i < inputCount; i++) { - tx - ..writeSlice(getInputPreviousTxid(i)) - ..writeUInt32(getInputOutputIndex(i)) - ..writeVarSlice(Uint8List(0)) - ..writeUInt32(getInputSequence(i)); - } - - final outputCount = getGlobalOutputCount(); - tx.writeVarInt(outputCount); - for (var i = 0; i < outputCount; i++) { - tx.writeUInt64(getOutputAmount(i)); - tx.writeVarSlice(getOutputScript(i)); - } - tx.writeUInt32(getGlobalFallbackLocktime() ?? 0); - return tx.buffer(); - } - - Future signWithUTXO( - List utxos, UTXOSignerCallBack signer, - [UTXOGetterCallBack? getTaprootPair]) async { - final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false)); - final tx = BtcTransaction.fromRaw(raw); - - /// when the transaction is taproot and we must use getTaproot transaction - /// digest we need all of inputs amounts and owner script pub keys - List taprootAmounts = []; - List