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 similarity index 100% rename from .github/ISSUE_TEMPLATE/bug-report.md rename to .github/ISSUE_TEMPLATE/bug-report-🪲-.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f8cc8f9ca..d7a1a3ed9 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,8 @@ 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 + url: https://guides.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! + about: Use our live chat or send a support email! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md new file mode 100644 index 000000000..20bf2d53f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md @@ -0,0 +1,20 @@ +--- +name: Feature or Enhancement Request ✨ +about: Suggest an idea for Cake Wallet +title: '' +labels: Enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. 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..bf0d0f7bc --- /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: '11.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..69c632967 --- /dev/null +++ b/.github/workflows/pr_test_build.yml @@ -0,0 +1,199 @@ +name: PR Test Build + +on: + pull_request: + branches: [main] + workflow_dispatch: + inputs: + branch: + description: "Branch name to build" + required: true + default: "main" + +jobs: + PR_test_build: + runs-on: ubuntu-20.04 + 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 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: "11.x" + + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: "3.19.5" + 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 + -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/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 + ./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_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 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 ankrApiKey = '${{ secrets.ANKR_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 exolixApiKey = '${{ 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 nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/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: 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 ${{ env.BRANCH_NAME }} \ + # --app Cake-Labs/Cake-Wallet \ + # --token ${{ secrets.APP_CENTER_TOKEN }} \ + # --quiet + + - 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 + + - 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 }} 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..d0952ca98 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ .history .svn/ .fvm/ -.fvmrc # IntelliJ related *.iml @@ -127,7 +126,7 @@ 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 @@ -137,32 +136,10 @@ 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,9 +156,6 @@ 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 @@ -192,38 +166,3 @@ 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..76cfcc4d3 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -5,7 +5,7 @@ Last modified: January 24, 2024 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. diff --git a/README.md b/README.md index ea796dbf2..7823734fb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-![logo](.github/assets/Logo_CakeWallet.png) +
@@ -18,7 +18,7 @@ # 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 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) @@ -26,13 +26,10 @@ Cake Wallet includes support for several cryptocurrencies, including: * Ethereum (ETH) * Litecoin (LTC) * Bitcoin Cash (BCH) -* Polygon (POL) +* Polygon (MATIC) * Solana (SOL) -* Tron (TRX) * Nano (XNO) -* Zano (ZANO) -* Decred (DCR) -* Wownero (WOW) +* Haven (XHV) ## Features @@ -47,7 +44,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 +81,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 +99,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 +161,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/docs/SECURITY.md b/SECURITY.md similarity index 85% rename from docs/SECURITY.md rename to SECURITY.md index e7c6baa02..a1b489b76 100644 --- a/docs/SECURITY.md +++ b/SECURITY.md @@ -9,4 +9,4 @@ If you need to report a vulnerability, please either: ## Supported Versions -As we don't maintain previous versions of the app, only the latest release for each platform is supported and any updates will bump the version number. +As we don't maintain prevoius versions of the app, only the latest release for each platform is supported and any updates will bump the version number. 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..5e27aeb9e 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 34 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,15 @@ android { buildTypes { release { signingConfig signingConfigs.release + + shrinkResources false + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } - debug { - signingConfig signingConfigs.release - } } - ndkVersion "27.0.12077973" + ndkVersion "25.1.8937393" } flutter { @@ -99,10 +91,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..57462099c 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -1,34 +1,35 @@ - + - - + + + + + - + + - - - + + + - - - - - + + + - - - - + - + - - - - - - + android:requestLegacyExternalStorage="true"> - - - - - - - - - @@ -111,15 +98,10 @@ - - - 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) { @@ -73,6 +85,23 @@ public class MainActivity extends FlutterFragmentActivity { } } + 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"); + + 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("")); + } + }); + } + private void disableBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); 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..d0a465d22 100644 --- a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java @@ -19,10 +19,14 @@ 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,6 +51,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 "disableBatteryOptimization": disableBatteryOptimization(); handler.post(() -> result.success(null)); @@ -63,6 +75,23 @@ public class MainActivity extends FlutterFragmentActivity { } } + 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"); + + 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("")); + } + }); + } + private void disableBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); 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..49c368ec7 100644 --- a/android/app/src/main/java/com/monero/app/MainActivity.java +++ b/android/app/src/main/java/com/monero/app/MainActivity.java @@ -19,10 +19,14 @@ 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 +52,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) { @@ -72,6 +84,23 @@ public class MainActivity extends FlutterFragmentActivity { } } + 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"); + + 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("")); + } + }); + } + private void disableBatteryOptimization() { String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); 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/.gitkeep b/android/app/src/main/jniLibs/x86/.gitkeep deleted file mode 100644 index e69de29bb..000000000 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/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..aa9f5005d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,8 +1,21 @@ +buildscript { + ext.kotlin_version = '1.8.21' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + 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/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_pl.json b/assets/faq/faq_pl.json index b41841cd8..a38d79068 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?", 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/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/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..59cc69414 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..00d924171 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..10d0a1a82 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..5b0fde827 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..9c16f0a27 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..8c59ec33e 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..5d25e42e7 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..021fe65de 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..10c3acd7f 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..c4b66dc58 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..b440b154d 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..813a3678d 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..75dc0219d 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..90afb19e8 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..671422b96 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_adaptive_back.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 000000000..46b1e2cb1 Binary files /dev/null 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..0a2025220 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_background.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_background.png deleted file mode 100644 index 66a5487a2..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_background.png and /dev/null 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..59cc69414 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 index 6ac112eae..cbba87372 100644 Binary files a/assets/images/dfx_dark.png and b/assets/images/dfx_dark.png differ diff --git a/assets/images/dfx_light.png b/assets/images/dfx_light.png index a045d3e68..e4836be3e 100644 Binary files a/assets/images/dfx_light.png and b/assets/images/dfx_light.png 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/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/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/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/ledger_nano.png b/assets/images/ledger_nano.png new file mode 100644 index 000000000..bb61ba175 Binary files /dev/null and b/assets/images/ledger_nano.png 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 index c465fa26c..73101354a 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_1024.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_1024.png 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 index e79db4d99..9ceee3c5e 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_128.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_128.png 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 index 2d030845b..ef46cd805 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_16.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_16.png 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 index eef607c2c..6547a1b1b 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_256.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_256.png 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 index a09896830..e436872e8 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_32.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_32.png 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 index a98c5faf8..157b00493 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_512.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_512.png 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 index d8141a250..a46ed4535 100644 Binary files a/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_64.png and b/assets/images/macos_icons/cakewallet_macos_icons/cakewallet_macos_64.png 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/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml b/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml index c8bd4b26c..00d924171 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 @@ -2,5 +2,4 @@ - \ No newline at end of file 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_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_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_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_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 index 21de98eb4..872e322e2 100644 Binary files a/assets/images/moonpay_dark.png and b/assets/images/moonpay_dark.png differ diff --git a/assets/images/moonpay_light.png b/assets/images/moonpay_light.png index 3d3de2e4f..c76ae6e74 100644 Binary files a/assets/images/moonpay_light.png and b/assets/images/moonpay_light.png 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 index 360d0b4e6..099039e67 100644 --- a/assets/images/notification_icon.svg +++ b/assets/images/notification_icon.svg @@ -1,3 +1,69 @@ - - - + + + +image/svg+xml \ No newline at end of file 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/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/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/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/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/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..2e4d1ec3c 100644 --- a/assets/nano_node_list.yml +++ b/assets/nano_node_list.yml @@ -1,10 +1,7 @@ -- - uri: nano.nownodes.io - useSSL: true - uri: rpc.nano.to - is_default: true useSSL: true + is_default: true - uri: node.nautilus.io path: /api 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 index 3b2cdcdc3..34504269d 100644 --- a/assets/polygon_node_list.yml +++ b/assets/polygon_node_list.yml @@ -1,10 +1,6 @@ - uri: polygon-rpc.com - - uri: polygon-bor-rpc.publicnode.com - useSSL: true - isDefault: true + uri: polygon-bor.publicnode.com - - uri: polygon.llamarpc.com -- - uri: matic.nownodes.io \ No newline at end of file + uri: polygon.llamarpc.com \ No newline at end of file diff --git a/assets/solana_node_list.yml b/assets/solana_node_list.yml index 3ba74d980..4a2e12161 100644 --- a/assets/solana_node_list.yml +++ b/assets/solana_node_list.yml @@ -1,13 +1,4 @@ - 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 + is_default: true + useSSL: 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..d5297ebe1 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,4 +1 @@ -Add built-in Tor support (experimental) -Ledger improvements -UI/UX improvements -Bug fixes \ No newline at end of file +Generic bug fixes and enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index c49b895e3..f9b05cea2 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 +Hardware wallets support for Bitcoin, Ethereum and Polygon +Security enhancements +Bug fixes and generic enhancements \ No newline at end of file diff --git a/assets/tron_node_list.yml b/assets/tron_node_list.yml index 1e34de712..d28e38f2e 100644 --- a/assets/tron_node_list.yml +++ b/assets/tron_node_list.yml @@ -1,11 +1,8 @@ - uri: tron-rpc.publicnode.com:443 - is_default: false + is_default: true useSSL: true - uri: api.trongrid.io - is_default: true - useSSL: true -- - uri: trx.nownodes.io + is_default: false 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 index a083ec7ff..837a002e9 100755 --- a/configure_cake_wallet.sh +++ b/configure_cake_wallet.sh @@ -1,15 +1,11 @@ -#!/bin/bash -set -x -e IOS="ios" ANDROID="android" -MACOS="macos" -LINUX="linux" -PLATFORMS=($IOS $ANDROID $MACOS $LINUX) +PLATFORMS=($IOS $ANDROID) PLATFORM=$1 if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then - echo "specify platform: ./configure_cake_wallet.sh ios|android|macos|linux" + echo "specify platform: ./configure_cake_wallet.sh ios|android" exit 1 fi @@ -18,24 +14,13 @@ if [ "$PLATFORM" == "$IOS" ]; then 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 +flutter packages pub run tool/generate_localization.dart +./model_generator.sh diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart index 0d985b237..73bc101c4 100644 --- a/cw_bitcoin/lib/address_from_output.dart +++ b/cw_bitcoin/lib/address_from_output.dart @@ -2,37 +2,22 @@ import 'package:bitcoin_base/bitcoin_base.dart'; String addressFromOutputScript(Script script, BasedUtxoNetwork network) { try { - return addressFromScript(script, network).toAddress(network); + switch (script.getAddressType()) { + case P2pkhAddressType.p2pkh: + return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network); + case P2shAddressType.p2pkInP2sh: + return P2shAddress.fromScriptPubkey(script: script).toAddress(network); + case SegwitAddresType.p2wpkh: + return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network); + case P2shAddressType.p2pkhInP2sh: + return P2shAddress.fromScriptPubkey(script: script).toAddress(network); + case SegwitAddresType.p2wsh: + return P2wshAddress.fromScriptPubkey(script: script).toAddress(network); + case SegwitAddresType.p2tr: + return P2trAddress.fromScriptPubkey(script: script).toAddress(network); + default: + } } 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"); -} 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..892f7a0d6 --- /dev/null +++ b/cw_bitcoin/lib/address_to_output_script.dart @@ -0,0 +1,14 @@ +import 'dart:typed_data'; +import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin; + +List addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) { + try { + if (network == bitcoin.BitcoinCashNetwork.mainnet) { + return bitcoin.BitcoinCashAddress(address).baseAddress.toScriptPubKey().toBytes(); + } + return bitcoin.addressToOutputScript(address: address, network: network); + } 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..d1c3b6a61 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,10 +1,10 @@ import 'dart:convert'; -import 'package:mobx/mobx.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/script_hash.dart' as sh; -abstract class BaseBitcoinAddressRecord { - BaseBitcoinAddressRecord( +class BitcoinAddressRecord { + BitcoinAddressRecord( this.address, { required this.index, this.isHidden = false, @@ -13,62 +13,15 @@ abstract class BaseBitcoinAddressRecord { String name = '', bool isUsed = false, required this.type, + String? scriptHash, required this.network, }) : _txCount = txCount, _balance = balance, _name = name, - _isUsed = Observable(isUsed); + _isUsed = isUsed, + scriptHash = scriptHash ?? sh.scriptHash(address, network: network); - @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, BasedUtxoNetwork network) { final decoded = json.decode(jsonSource) as Map; return BitcoinAddressRecord( @@ -88,15 +41,44 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { ); } - String? scriptHash; + @override + bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address; - String getScriptHash(BasedUtxoNetwork network) { - if (scriptHash != null) return scriptHash!; - scriptHash = BitcoinAddressUtils.scriptHash(address, network: network); + final String address; + bool isHidden; + final int index; + int _txCount; + int _balance; + String _name; + bool _isUsed; + String? scriptHash; + 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; + + void setAsUsed() => _isUsed = true; + void setNewName(String label) => _name = label; + + @override + int get hashCode => address.hashCode; + + BitcoinAddressType type; + + String updateScriptHash(BasedUtxoNetwork network) { + scriptHash = sh.scriptHash(address, network: network); return scriptHash!; } - @override String toJSON() => json.encode({ 'address': address, 'index': index, @@ -109,57 +91,3 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { 'scriptHash': scriptHash, }); } - -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; - - @override - 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, - }); -} diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart index c8715b239..345d645d1 100644 --- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart @@ -1,36 +1,33 @@ import 'dart:async'; import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.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'; +import 'package:ledger_flutter/ledger_flutter.dart'; class BitcoinHardwareWalletService { - BitcoinHardwareWalletService(this.ledgerConnection); + BitcoinHardwareWalletService(this.ledger, this.device); - final LedgerConnection ledgerConnection; + final Ledger ledger; + final LedgerDevice device; - Future> getAvailableAccounts( - {int index = 0, int limit = 5}) async { - final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection); + Future> getAvailableAccounts({int index = 0, int limit = 5}) async { + final bitcoinLedgerApp = BitcoinLedgerApp(ledger); - final masterFp = await bitcoinLedgerApp.getMasterFingerprint(); + final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device); + print(masterFp); 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 xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath); + HDWallet hd = HDWallet.fromBase58(xpub).derive(0); - final address = generateP2WPKHAddress( - hd: hd, index: 0, network: BitcoinNetwork.mainnet); + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); accounts.add(HardwareAccountData( address: address, diff --git a/cw_bitcoin/lib/bitcoin_mnemonic.dart b/cw_bitcoin/lib/bitcoin_mnemonic.dart index 21ff3891e..4a01d6ddc 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; @@ -110,23 +112,22 @@ Future checkIfMnemonicIsElectrum2(String mnemonic) async { Future getMnemonicHash(String mnemonic) async { final hmacSha512 = Hmac(sha512, utf8.encode('Seed version')); final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic))); - return digest.toString(); + final hx = digest.toString(); + return hx; } -Future mnemonicToSeedBytes(String mnemonic, - {String prefix = segwit, String passphrase = ''}) async { +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]); + secretKey: cryptography.SecretKey(text.codeUnits), 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 +137,121 @@ 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 +2301,4 @@ final englishWordlist = [ 'zero', 'zone', 'zoo' -]; +]; \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart index 07083e111..2d2339a41 100644 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart @@ -7,9 +7,6 @@ class BitcoinReceivePageOption implements ReceivePageOption { 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); @@ -20,7 +17,6 @@ class BitcoinReceivePageOption implements ReceivePageOption { } static const all = [ - BitcoinReceivePageOption.silent_payments, BitcoinReceivePageOption.p2wpkh, BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2wsh, @@ -28,45 +24,16 @@ class BitcoinReceivePageOption implements ReceivePageOption { 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..bda7c39ae 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -1,19 +1,11 @@ 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_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index d1f45a545..7c4dcfd5f 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -87,7 +87,7 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { } @override - String get units => 'Litoshi'; + String get units => 'Latoshi'; @override String toString() { diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index 3691a7a22..52edea091 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -2,66 +2,13 @@ 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) => + factory BitcoinUnspent.fromJSON(BitcoinAddressRecord 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, - ); + 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..f96b0e4da 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -1,37 +1,23 @@ -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:convert/convert.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_bitcoin/psbt_transaction_builder.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:ledger_flutter/ledger_flutter.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'; +import 'package:bip39/bip39.dart' as bip39; part 'bitcoin_wallet.g.dart'; @@ -42,8 +28,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required Box payjoinBox, - required EncryptionFileUtils encryptionFileUtils, Uint8List? seedBytes, String? mnemonic, String? xpub, @@ -54,76 +38,54 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { 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, - ) { + mnemonic: mnemonic, + passphrase: passphrase, + xpub: xpub, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: networkParam == null + ? bitcoin.bitcoin + : networkParam == BitcoinNetwork.mainnet + ? bitcoin.bitcoin + : bitcoin.testnet, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.btc) { // 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, - 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); - + walletAddresses = BitcoinWalletAddresses( + walletInfo, + electrumClient: electrumClient, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: accountHD.derive(1), + network: networkParam ?? network, + ); autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = - this.isEnabledAutoGenerateSubaddress; + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); } - @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, }) async { late Uint8List seedBytes; @@ -136,11 +98,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { break; case DerivationType.electrum: default: - seedBytes = - await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = await mnemonicToSeedBytes(mnemonic); break; } - return BitcoinWallet( mnemonic: mnemonic, passphrase: passphrase ?? "", @@ -148,16 +108,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { 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, ); } @@ -165,152 +121,64 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { 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 snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network); - 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(); + walletInfo.derivationInfo ??= DerivationInfo( + derivationType: snp.derivationType ?? DerivationType.electrum, + derivationPath: snp.derivationPath, + ); // set the default if not present: - walletInfo.derivationInfo!.derivationPath ??= - snp?.derivationPath ?? electrum_path; - walletInfo.derivationInfo!.derivationType ??= - snp?.derivationType ?? DerivationType.electrum; + walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0"; + walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum; Uint8List? seedBytes = null; - final mnemonic = keysData.mnemonic; - final passphrase = keysData.passphrase; - if (mnemonic != null) { + if (snp.mnemonic != null) { switch (walletInfo.derivationInfo!.derivationType) { case DerivationType.electrum: - seedBytes = - await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = await mnemonicToSeedBytes(snp.mnemonic!); break; case DerivationType.bip39: default: seedBytes = await bip39.mnemonicToSeed( - mnemonic, - passphrase: passphrase ?? '', + snp.mnemonic!, + passphrase: snp.passphrase ?? '', ); break; } } return BitcoinWallet( - mnemonic: mnemonic, - xpub: keysData.xPub, - 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); + mnemonic: snp.mnemonic, + xpub: snp.xpub, + password: password, + passphrase: snp.passphrase, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: seedBytes, + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex, + addressPageType: snp.addressPageType, + networkParam: network, + ); } - LedgerConnection? _ledgerConnection; + Ledger? _ledger; + LedgerDevice? _ledgerDevice; 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; + void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { + _ledger = setLedger; + _ledgerDevice = setLedgerDevice; + _bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); } @override @@ -325,129 +193,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, }) async { - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!); - 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 psbtReadyInputs = []; + for (final utxo in utxos) { + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; - 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); + psbtReadyInputs.add(PSBTReadyUtxoWithAddress( + utxo: utxo.utxo, + rawTx: rawTx, + ownerDetails: utxo.ownerDetails, + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, + ownerMasterFingerprint: masterFingerprint, + ownerPublicKey: publicKeyAndDerivationPath.publicKey, + )); } - return super.signMessage(message, address: address); + final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); + + final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); + return BtcTransaction.fromRaw(hex.encode(rawHex)); } } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index d84d958be..f12577492 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,13 +1,9 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/bip/bip/bip32/bip32.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.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'; @@ -19,29 +15,14 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required super.mainHd, required super.sideHd, required super.network, - required super.isHardwareWallet, - required this.payjoinManager, + required super.electrumClient, 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; - @override - String getAddress( - {required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) { + String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) { if (addressType == P2pkhAddressType.p2pkh) return generateP2PKHAddress(hd: hd, index: index, network: network); @@ -56,33 +37,4 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S 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; - } - } } diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 177d61e87..915d7cc10 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -3,22 +3,15 @@ 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( + BitcoinNewWalletCredentials( + {required String name, + WalletInfo? walletInfo, + DerivationType? derivationType, + String? derivationPath}) + : super( name: name, walletInfo: walletInfo, - password: password, - passphrase: passphrase, ); - - final String? mnemonic; } class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 317b25bcd..cf99324da 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -1,11 +1,8 @@ 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_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'; @@ -22,14 +19,10 @@ class BitcoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials, BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, - this.payjoinSessionSource, this.alwaysScan, this.isDirect); + 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; @@ -39,33 +32,16 @@ class BitcoinWalletService extends WalletService< 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; - } - final wallet = await BitcoinWalletBase.create( - mnemonic: mnemonic, + mnemonic: await generateElectrumMnemonic(), password: credentials.password!, passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, network: network, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); - await wallet.save(); await wallet.init(); - return wallet; } @@ -79,28 +55,20 @@ class BitcoinWalletService extends WalletService< .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), - ); + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); 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), - ); + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; } @@ -112,15 +80,6 @@ class BitcoinWalletService extends WalletService< 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 @@ -128,14 +87,10 @@ class BitcoinWalletService extends WalletService< 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); @@ -150,18 +105,16 @@ 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!, + 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(); @@ -170,7 +123,7 @@ class BitcoinWalletService extends WalletService< @override Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, - {bool? isTestnet}) async => + {bool? isTestnet}) async => throw UnimplementedError(); @override @@ -189,9 +142,7 @@ class BitcoinWalletService extends WalletService< mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, - payjoinBox: payjoinSessionSource, network: network, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); await wallet.save(); await wallet.init(); diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 2ddd30df6..0553170cc 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -4,14 +4,10 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.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 } - String jsonrpcparams(List params) { final _params = params.map((val) => '"${val.toString()}"').join(','); return '[$_params]'; @@ -44,99 +40,47 @@ class ElectrumClient { 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; - } - await connect(host: uri.host, port: uri.port); - } + 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(); } @@ -176,7 +120,7 @@ class ElectrumClient { unterminatedString = ''; } } catch (e) { - printV("parse $e"); + print(e.toString()); } } @@ -188,14 +132,13 @@ 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) { + Future> version() => call(method: 'server.version').then((dynamic result) { if (result is List) { return result.map((dynamic val) => val.toString()).toList(); } @@ -229,21 +172,40 @@ class ElectrumClient { return []; }); - Future>?> getListUnspent(String scriptHash) async { - final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]); + Future>> getListUnspentWithAddress( + String address, BasedUtxoNetwork network) => + call( + method: 'blockchain.scripthash.listunspent', + params: [scriptHash(address, network: network)]).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 +223,9 @@ 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,8 +233,9 @@ class ElectrumClient { return {}; }); - Future getTransactionHex({required String hash}) => - getTransaction(hash: hash, verbose: false).then((dynamic result) { + 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; } @@ -314,17 +266,6 @@ class ElectrumClient { 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]); - Future estimatefee({required int p}) => call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { if (result is double) { @@ -367,10 +308,13 @@ class ElectrumClient { }); Future> feeRates({BasedUtxoNetwork? network}) async { + if (network == BitcoinNetwork.testnet) { + return [1, 1, 1]; + } try { final topDoubleString = await estimatefee(p: 1); final middleDoubleString = await estimatefee(p: 5); - final bottomDoubleString = await estimatefee(p: 10); + 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(); @@ -387,27 +331,14 @@ class ElectrumClient { // "height": 520481, // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" // } + Future getCurrentBlockChainTip() => + call(method: 'blockchain.headers.subscribe').then((result) { + if (result is Map) { + return result["height"] as int; + } - 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'); - } + return null; + }); BehaviorSubject? scripthashUpdate(String scripthash) { _id += 1; @@ -420,25 +351,19 @@ class ElectrumClient { BehaviorSubject? subscribe( {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"); + print(e.toString()); return null; } } Future call( {required String method, List params = const [], Function(int)? idCallback}) async { - if (socket == null) { - return null; - } final completer = Completer(); _id += 1; final id = _id; @@ -450,11 +375,8 @@ 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; @@ -468,17 +390,13 @@ class ElectrumClient { return completer.future; } catch (e) { - printV("callWithTimeout $e"); - rethrow; + print(e.toString()); } } Future close() async { _aliveTimer?.cancel(); - try { - await socket?.close(); - socket = null; - } catch (_) {} + await socket?.close(); onConnectionStatusChange = null; } @@ -506,12 +424,6 @@ class ElectrumClient { 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 +431,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) { diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index aeb06f1f0..165ea447e 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -3,18 +3,8 @@ 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,22 +14,17 @@ 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); @@ -50,21 +35,6 @@ class ElectrumBalance extends Balance { 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 index 81a3626d2..19d444a41 100644 --- a/cw_bitcoin/lib/electrum_derivations.dart +++ b/cw_bitcoin/lib/electrum_derivations.dart @@ -28,12 +28,6 @@ Map> electrum_derivations = { 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'", @@ -108,5 +102,3 @@ Map> electrum_derivations = { ), ], }; - -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..d478c3b12 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -1,43 +1,37 @@ 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/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 +40,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 +57,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 +73,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..f980bd884 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -1,20 +1,17 @@ -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; final int? time; @@ -22,27 +19,17 @@ class ElectrumTransactionBundle { } 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, + List? inputAddresses, + List? outputAddresses, + required TransactionDirection direction, + required bool isPending, + required DateTime date, + required int confirmations}) { this.id = id; this.height = height; this.amount = amount; @@ -52,10 +39,7 @@ class ElectrumTransactionInfo extends TransactionInfo { 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, @@ -102,7 +86,6 @@ class ElectrumTransactionInfo extends TransactionInfo { id: id, height: height, isPending: false, - isReplaced: false, fee: fee, direction: direction, amount: amount, @@ -112,7 +95,7 @@ class ElectrumTransactionInfo extends TransactionInfo { factory ElectrumTransactionInfo.fromElectrumBundle( ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network, - {required Set addresses, int? height}) { + {required Set addresses, required int height}) { final date = bundle.time != null ? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000) : DateTime.now(); @@ -138,24 +121,7 @@ class ElectrumTransactionInfo extends TransactionInfo { 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:'); - } - } - } + outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network)); if (addressExists) { receivedAmounts.add(out.amount.toInt()); @@ -178,7 +144,6 @@ class ElectrumTransactionInfo extends TransactionInfo { id: bundle.originalTransaction.txId(), height: height, isPending: bundle.confirmations == 0, - isReplaced: false, inputAddresses: inputAddresses, outputAddresses: outputAddresses, fee: fee, @@ -188,34 +153,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, + inputAddresses: data['inputAddresses'] as List, + outputAddresses: data['outputAddresses'] as List, + confirmations: data['confirmations'] as int); } final WalletType type; @@ -246,11 +229,9 @@ 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 +242,10 @@ 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..783eb10d7 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1,36 +1,30 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:isolate'; +import 'dart:math'; 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:bitcoin_base/bitcoin_base.dart' as bitcoin_base; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; 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_amount_format.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/litecoin_network.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/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 'package:cw_core/pending_transaction.dart'; @@ -38,18 +32,14 @@ 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/utils/file.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:http/http.dart' as http; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; -import 'package:sp_scanner/sp_scanner.dart'; -import 'package:hex/hex.dart'; part 'electrum_wallet.g.dart'; @@ -57,24 +47,22 @@ 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, + String? xpub, + String? mnemonic, + Uint8List? seedBytes, + this.passphrase, + List? initialAddresses, + ElectrumClient? electrumClient, + ElectrumBalance? initialBalance, + CryptoCurrency? currency}) + : accountHD = + getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], @@ -84,83 +72,51 @@ abstract class ElectrumWalletBase _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.network = _getNetwork(networkType, currency), + this.isTestnet = networkType == bitcoin.testnet, 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) { + static bitcoin.HDWallet getAccountHDWallet( + CryptoCurrency? currency, + bitcoin.NetworkType networkType, + 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 currency == CryptoCurrency.bch + ? bitcoinCashHDWallet(seedBytes) + : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) + .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'")); } - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network)); + return bitcoin.HDWallet.fromBase58(xpub!); } - 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'"); static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; - static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) { - switch (network) { - case LitecoinNetwork.mainnet: - return Bip44Conf.litecoinMainNet.altKeyNetVer; - default: - return null; - } - } - - bool? alwaysScan; - - final Bip32Slip10Secp256k1 accountHD; + final bitcoin.HDWallet accountHD; final String? _mnemonic; - Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0)); - - Bip32Slip10Secp256k1 get sideHd => accountHD.childKey(Bip32KeyIndex(1)); - - final EncryptionFileUtils encryptionFileUtils; - - @override + bitcoin.HDWallet get hd => accountHD.derive(0); final String? passphrase; @override @@ -181,418 +137,80 @@ 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)) + .map((addr) => scriptHash(addr.address, network: network)) .toList(); List get publicScriptHashes => walletAddresses.allAddresses .where((addr) => !addr.isHidden) - .where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress) - .map((addr) => addr.getScriptHash(network)) + .map((addr) => scriptHash(addr.address, network: network)) .toList(); - String get xpub => accountHD.publicKey.toExtended; + String get xpub => accountHD.base58!; @override String? get seed => _mnemonic; - @override - WalletKeysData get walletKeysData => - WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase); - - @override - String get password => _password; - + bitcoin.NetworkType networkType; 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; - } + bool? isTestnet; @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 updateTransactions(); - - await updateAllUnspents(); + _subscribeForUpdates(); + await updateUnspent(); await updateBalance(); - await updateFeeRates(); + _feeRates = await electrumClient.feeRates(network: network); - _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(); } } @@ -601,119 +219,60 @@ abstract class ElectrumWalletBase bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; - UtxoDetails _createUTXOS({ - required bool sendAll, - required bool paysToSilentPayment, + Future estimateSendAllTx( + List outputs, + int feeRate, { + String? memo, int credentialsAmount = 0, - int? inputsCount, - UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, - }) { - List utxos = []; - List vinOutpoints = []; - List inputPrivKeyInfos = []; + }) async { + final utxos = []; + final privateKeys = []; final publicKeys = {}; + int allInputsAmount = 0; - bool spendsSilentPayment = false; + bool spendsUnconfirmedTX = false; - int leftAmount = credentialsAmount; - var availableInputs = unspentCoins.where((utx) { - if (!utx.isSending || utx.isFrozen) { - return false; - } + for (int i = 0; i < unspentCoins.length; i++) { + final utx = unspentCoins[i]; - 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(); + if (utx.isSending && !utx.isFrozen) { + if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0; - // sort the unconfirmed coins so that mweb coins are last: - availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1); + allInputsAmount += utx.value; - for (int i = 0; i < availableInputs.length; i++) { - final utx = availableInputs[i]; - if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0; + final address = addressTypeFromStr(utx.address, network); + final hd = + utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; + final derivationPath = + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" + "/${utx.bitcoinAddressRecord.index}"; + final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!; - if (paysToSilentPayment) { - // Check inputs for shared secret derivation - if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) { - throw BitcoinTransactionSilentPaymentsNotSupported(); + publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); + + if (!walletInfo.isHardwareWallet) { + final privkey = + generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); + + privateKeys.add(privkey); } - } - allInputsAmount += utx.value; - leftAmount = leftAmount - utx.value; - - 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!), + utxos.add( + UtxoWithAddress( + utxo: BitcoinUtxo( + txHash: utx.hash, + value: BigInt.from(utx.value), + vout: utx.vout, + scriptType: _getScriptType(address), + ), + ownerDetails: UtxoAddressDetails( + publicKey: pubKeyHex, + address: address, + ), ), ); - 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(); - } else { - pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex(); - } - - final derivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? electrum_path)}" - "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" - "/${utx.bitcoinAddressRecord.index}"; - publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); - - 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, - ), - ), - ); - - // sendAll continues for all inputs - if (!sendAll) { - bool amountIsAcquired = leftAmount <= 0; - if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { - break; - } } } @@ -721,262 +280,12 @@ abstract class ElectrumWalletBase 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(); - } - - // 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; - - 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, + network: network as BitcoinCashNetwork, memo: memo, ); } else { @@ -985,29 +294,271 @@ abstract class ElectrumWalletBase outputs: outputs, network: network, memo: memo, - inputPrivKeyInfos: inputPrivKeyInfos, - vinOutpoints: vinOutpoints, ); } - return feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); + int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); + + if (fee == 0) { + throw BitcoinTransactionNoFeeException(); + } + + // 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 = allInputsAmount - fee; + + if (amount <= 0) { + throw BitcoinTransactionWrongBalanceException(); + } + + // Attempting to send less than the dust limit + if (_isBelowDust(amount)) { + throw BitcoinTransactionNoDustException(); + } + + if (credentialsAmount > 0) { + final amountLeftForFee = amount - credentialsAmount; + if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) { + amount -= amountLeftForFee; + fee += amountLeftForFee; + } + } + + outputs[outputs.length - 1] = + BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); + + return EstimatedTxResult( + utxos: utxos, + privateKeys: privateKeys, + publicKeys: publicKeys, + fee: fee, + amount: amount, + isSendAll: true, + hasChange: false, + memo: memo, + spendsUnconfirmedTX: spendsUnconfirmedTX, + ); + } + + Future estimateTxForAmount( + int credentialsAmount, + List outputs, + int feeRate, { + int? inputsCount, + String? memo, + bool? useUnconfirmed, + }) async { + final utxos = []; + final privateKeys = []; + final publicKeys = {}; + + int allInputsAmount = 0; + bool spendsUnconfirmedTX = false; + + int leftAmount = credentialsAmount; + final sendingCoins = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); + final unconfirmedCoins = sendingCoins.where((utx) => utx.confirmations == 0).toList(); + + for (int i = 0; i < sendingCoins.length; i++) { + final utx = sendingCoins[i]; + + final isUncormirmed = utx.confirmations == 0; + if (useUnconfirmed != true && isUncormirmed) continue; + + if (!spendsUnconfirmedTX) spendsUnconfirmedTX = isUncormirmed; + + allInputsAmount += utx.value; + leftAmount = leftAmount - utx.value; + + final address = addressTypeFromStr(utx.address, network); + + final hd = + utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; + final derivationPath = + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" + "/${utx.bitcoinAddressRecord.index}"; + final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!; + + publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); + + if (!walletInfo.isHardwareWallet) { + final privkey = + generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); + + privateKeys.add(privkey); + } + + utxos.add( + UtxoWithAddress( + utxo: BitcoinUtxo( + txHash: utx.hash, + value: BigInt.from(utx.value), + vout: utx.vout, + scriptType: _getScriptType(address), + ), + ownerDetails: UtxoAddressDetails( + publicKey: pubKeyHex, + address: address, + ), + ), + ); + + bool amountIsAcquired = leftAmount <= 0; + if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { + break; + } + } + + if (utxos.isEmpty) { + throw BitcoinTransactionNoInputsException(); + } + + final spendingAllCoins = sendingCoins.length == utxos.length; + final spendingAllConfirmedCoins = + !spendsUnconfirmedTX && utxos.length == sendingCoins.length - unconfirmedCoins.length; + + // How much is being spent - how much is being sent + int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount; + + if (amountLeftForChangeAndFee <= 0) { + if (!spendingAllCoins) { + return estimateTxForAmount( + credentialsAmount, + outputs, + feeRate, + inputsCount: utxos.length + 1, + memo: memo, + useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, + ); + } + throw BitcoinTransactionWrongBalanceException(); + } + + final changeAddress = await walletAddresses.getChangeAddress(); + final address = addressTypeFromStr(changeAddress, network); + outputs.add(BitcoinOutput( + address: address, + value: BigInt.from(amountLeftForChangeAndFee), + )); + + int estimatedSize; + if (network is BitcoinCashNetwork) { + estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: outputs, + network: network as BitcoinCashNetwork, + memo: memo, + ); + } else { + estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: outputs, + network: network, + memo: memo, + ); + } + + int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); + + if (fee == 0) { + throw BitcoinTransactionNoFeeException(); + } + + int amount = credentialsAmount; + final lastOutput = outputs.last; + final amountLeftForChange = amountLeftForChangeAndFee - fee; + + if (!_isBelowDust(amountLeftForChange)) { + // Here, lastOutput already is change, return the amount left without the fee to the user's address. + outputs[outputs.length - 1] = + BitcoinOutput(address: lastOutput.address, value: BigInt.from(amountLeftForChange)); + } else { + // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change + outputs.removeLast(); + + // Still has inputs to spend before failing + if (!spendingAllCoins) { + return estimateTxForAmount( + credentialsAmount, + outputs, + feeRate, + inputsCount: utxos.length + 1, + memo: memo, + useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, + ); + } + + final estimatedSendAll = await estimateSendAllTx( + outputs, + feeRate, + memo: memo, + ); + + if (estimatedSendAll.amount == credentialsAmount) { + return estimatedSendAll; + } + + // Estimate to user how much is needed to send to cover the fee + final maxAmountWithReturningChange = allInputsAmount - _dustAmount - fee - 1; + throw BitcoinTransactionNoDustOnChangeException( + bitcoinAmountToString(amount: maxAmountWithReturningChange), + bitcoinAmountToString(amount: estimatedSendAll.amount), + ); + } + + // Attempting to send less than the dust limit + if (_isBelowDust(amount)) { + throw BitcoinTransactionNoDustException(); + } + + final totalAmount = amount + fee; + + if (totalAmount > balance[currency]!.confirmed) { + throw BitcoinTransactionWrongBalanceException(); + } + + if (totalAmount > allInputsAmount) { + if (spendingAllCoins) { + throw BitcoinTransactionWrongBalanceException(); + } else { + if (amountLeftForChangeAndFee > fee) { + outputs.removeLast(); + } + + return estimateTxForAmount( + credentialsAmount, + outputs, + feeRate, + inputsCount: utxos.length + 1, + memo: memo, + useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, + ); + } + } + + return EstimatedTxResult( + utxos: utxos, + privateKeys: privateKeys, + publicKeys: publicKeys, + fee: fee, + amount: amount, + hasChange: true, + isSendAll: false, + memo: memo, + spendsUnconfirmedTX: spendsUnconfirmedTX, + ); } @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!; @@ -1024,27 +575,14 @@ abstract class ElectrumWalletBase credentialsAmount += outputAmount; - final address = RegexUtils.addressTypeFromStr( - out.isParsedAddress ? out.extractedAddress! : out.address, network); - final isSilentPayment = address is SilentPaymentAddress; - - if (isSilentPayment) { - hasSilentPayment = true; - } + final address = + addressTypeFromStr(out.isParsedAddress ? out.extractedAddress! : out.address, network); 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, - )); + outputs.add(BitcoinOutput(address: address, value: BigInt.from(0))); } else { - outputs.add(BitcoinOutput( - address: address, - value: BigInt.from(outputAmount), - isSilentPayment: isSilentPayment, - )); + outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount))); } } @@ -1053,39 +591,26 @@ abstract class ElectrumWalletBase : 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, + outputs, feeRateInt, memo: memo, - hasSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, + credentialsAmount: credentialsAmount, ); } else { estimatedTx = await estimateTxForAmount( credentialsAmount, outputs, - updatedOutputs, feeRateInt, memo: memo, - hasSilentPayment: hasSilentPayment, - coinTypeToSpendFrom: coinTypeToSpendFrom, ); } if (walletInfo.isHardwareWallet) { final transaction = await buildHardwareWalletTransaction( utxos: estimatedTx.utxos, - outputs: updatedOutputs, + outputs: outputs, publicKeys: estimatedTx.publicKeys, fee: BigInt.from(estimatedTx.fee), network: network, @@ -1108,7 +633,6 @@ abstract class ElectrumWalletBase )..addListener((transaction) async { transactionHistory.addOne(transaction); await updateBalance(); - await updateAllUnspents(); }); } @@ -1116,7 +640,7 @@ abstract class ElectrumWalletBase if (network is BitcoinCashNetwork) { txb = ForkedTransactionBuilder( utxos: estimatedTx.utxos, - outputs: updatedOutputs, + outputs: outputs, fee: BigInt.from(estimatedTx.fee), network: network, memo: estimatedTx.memo, @@ -1126,7 +650,7 @@ abstract class ElectrumWalletBase } else { txb = BitcoinTransactionBuilder( utxos: estimatedTx.utxos, - outputs: updatedOutputs, + outputs: outputs, fee: BigInt.from(estimatedTx.fee), network: network, memo: estimatedTx.memo, @@ -1138,77 +662,41 @@ abstract class ElectrumWalletBase bool hasTaprootInputs = false; final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { - String error = "Cannot find private key."; - - ECPrivateInfo? key; - - if (estimatedTx.inputPrivKeyInfos.isEmpty) { - error += "\nNo private keys generated."; - } else { - error += "\nAddress: ${utxo.ownerDetails.address.toAddress(network)}"; - - 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; - } - }); - } + final key = estimatedTx.privateKeys + .firstWhereOrNull((element) => element.getPublic().toHex() == publicKey); if (key == null) { - throw Exception(error); + throw Exception("Cannot find private key"); } if (utxo.utxo.isP2tr()) { hasTaprootInputs = true; - return key.privkey.signTapRoot( - txDigest, - sighash: sighash, - tweak: utxo.utxo.isSilentPayment != true, - ); + return key.signTapRoot(txDigest, sighash: sighash); } else { - return key.privkey.signInput(txDigest, sigHash: sighash); + return key.signInput(txDigest, sigHash: sighash); } }); - 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 { + 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, + )..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, @@ -1235,10 +723,6 @@ abstract class ElectrumWalletBase '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, }); int feeRate(TransactionPriority priority) { @@ -1309,13 +793,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,267 +827,136 @@ abstract class ElectrumWalletBase await transactionHistory.changePassword(password); } - @action @override - Future rescan({required int height, bool? doSingleScan}) async { - silentPaymentsScanningActive = true; - _setListeners(height, doSingleScan: doSingleScan); - } + Future rescan({required int height}) async => throw UnimplementedError(); @override - Future close({bool shouldCleanup = false}) async { + Future close() async { try { - await _receiveStream?.cancel(); await electrumClient.close(); } catch (_) {} - _autoSaveTimer?.cancel(); - _updateFeeRateTimer?.cancel(); } - @action - Future updateAllUnspents() async { + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + Future updateUnspent() async { List updatedUnspentCoins = []; - final previousUnspentCoins = List.from(unspentCoins.where((utxo) => - utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb && - utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)); + final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); - if (hasSilentPaymentsScanning) { - // Update unspents stored from scanned silent payment transactions - transactionHistory.transactions.values.forEach((tx) { - if (tx.unspents != null) { - updatedUnspentCoins.addAll(tx.unspents!); + await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient + .getListUnspentWithAddress(address.address, network) + .then((unspent) => Future.forEach>(unspent, (unspent) async { + try { + final coin = BitcoinUnspent.fromJSON(address, unspent); + final tx = await fetchTransactionInfo( + hash: coin.hash, height: 0, myAddresses: addressesSet); + coin.isChange = tx?.direction == TransactionDirection.outgoing; + coin.confirmations = tx?.confirmations; + updatedUnspentCoins.add(coin); + } catch (_) {} + })))); + + unspentCoins = updatedUnspentCoins; + + 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) && + element.vout == coin.vout); + + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; + + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + coin.bitcoinAddressRecord.balance += coinInfo.value; + } 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, + isChange: coin.isChange, + ); - 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) && element.vout == coin.vout); - 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"); + print(e.toString()); } } - Future cleanUpDuplicateUnspentCoins() async { - final currentWalletUnspentCoins = - unspentCoinsInfo.values.where((element) => element.walletId == id); - final Map uniqueUnspentCoins = {}; - final List duplicateKeys = []; + Future canReplaceByFee(String hash) async { + final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); + final confirmations = verboseTransaction['confirmations'] as int? ?? 0; + final transactionHex = verboseTransaction['hex'] as String?; - for (final unspentCoin in currentWalletUnspentCoins) { - final key = '${unspentCoin.hash}:${unspentCoin.vout}'; - if (!uniqueUnspentCoins.containsKey(key)) { - uniqueUnspentCoins[key] = unspentCoin; - } else { - duplicateKeys.add(unspentCoin.key); - } + if (confirmations > 0) return false; + + if (transactionHex == null) { + return false; } - if (duplicateKeys.isNotEmpty) await unspentCoinsInfo.deleteAll(duplicateKeys); - } + final original = bitcoin.Transaction.fromHex(transactionHex); - 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; - } + return original.ins + .any((element) => element.sequence != null && element.sequence! < 4294967293); } 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 changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); - final receiverAmount = outputs - .where((output) => - !ownAddresses.contains(addressFromOutputScript(output.scriptPubKey, network))) - .fold(0, (sum, output) => sum + output.amount.toInt()); + // look for a change address in the outputs + final changeOutput = outputs.firstWhereOrNull((output) => changeAddresses.any( + (element) => element.address == addressFromOutputScript(output.scriptPubKey, network))); - if (receiverAmount == 0) { - throw Exception("Receiver output not found."); - } + var allInputsAmount = 0; - 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]; @@ -1619,10 +967,12 @@ abstract class ElectrumWalletBase 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; + + return changeOutput != null && changeOutput.amount.toInt() - remainingFee >= 0; } Future replaceByFee(String hash, int newFee) async { @@ -1630,13 +980,11 @@ abstract class ElectrumWalletBase final bundle = await getTransactionExpanded(hash: hash); final utxos = []; - final outputs = []; List privateKeys = []; var allInputsAmount = 0; - String? memo; - // Add original inputs + // Add inputs for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { final input = bundle.originalTransaction.inputs[i]; final inputTransaction = bundle.ins[i]; @@ -1647,7 +995,8 @@ abstract class ElectrumWalletBase final addressRecord = walletAddresses.allAddresses.firstWhere((element) => element.address == address); - final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network); + + final btcAddress = addressTypeFromStr(addressRecord.address, network); final privkey = generateECPrivate( hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, index: addressRecord.index, @@ -1669,164 +1018,60 @@ abstract class ElectrumWalletBase ); } - // 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'); - } - } - } + int totalOutAmount = bundle.originalTransaction.outputs + .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); - 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; + var currentFee = allInputsAmount - totalOutAmount; int remainingFee = newFee - currentFee; - if (remainingFee <= 0) { - throw Exception("New fee must be higher than the current fee."); - } + final outputs = []; - // 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)); + // Add outputs and deduct the fees from it + for (int i = bundle.originalTransaction.outputs.length - 1; i >= 0; i--) { + final out = bundle.originalTransaction.outputs[i]; + final address = addressFromOutputScript(out.scriptPubKey, network); + final btcAddress = addressTypeFromStr(address, 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; + int newAmount; + if (out.amount.toInt() >= remainingFee) { + newAmount = out.amount.toInt() - remainingFee; + remainingFee = 0; - if (remainingFee <= 0) break; - } + // if new amount of output is less than dust amount, then don't add this output as well + if (newAmount <= _dustAmount) { + continue; } + } else { + remainingFee -= out.amount.toInt(); + continue; } + + outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(newAmount))); } - // 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()); + // look for a change address in the outputs + final changeOutput = outputs.firstWhereOrNull((output) => + changeAddresses.any((element) => element.address == output.address.toAddress(network))); - // The final amount that the receiver will receive - int sendingAmount = allInputsAmount - newFee - totalChangeAmount; + // deduct the change amount from the output amount + if (changeOutput != null) { + totalOutAmount -= changeOutput.value.toInt(); + } 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"); } @@ -1842,232 +1087,135 @@ abstract class ElectrumWalletBase transaction, type, electrumClient: electrumClient, - amount: sendingAmount, + amount: totalOutAmount, fee: newFee, network: network, - hasChange: changeOutputs.isNotEmpty, + hasChange: changeOutput != null, 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; } } - Future getTransactionExpanded( - {required String hash, int? height}) async { + Future getTransactionExpanded({required String hash}) async { String transactionHex; int? time; - int? confirmations; - - final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash); - - if (verboseTransaction.isEmpty) { + int confirmations = 0; + if (network == BitcoinNetwork.testnet) { + // Testnet public electrum server does not support verbose transaction fetching 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)); + final status = json.decode( + (await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body); - 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 (_) {} - } + time = status["block_time"] as int?; + final tip = await electrumClient.getCurrentBlockChainTip() ?? 0; + confirmations = tip - (status["block_height"] as int? ?? 0); } else { + final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); + transactionHex = verboseTransaction['hex'] as String; time = verboseTransaction['time'] as int?; - confirmations = verboseTransaction['confirmations'] as int?; + confirmations = verboseTransaction['confirmations'] as int? ?? 0; } - 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 = []; + final original = bitcoin_base.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)); + final txHex = await electrumClient.getTransactionHex(hash: vin.txId); + final tx = bitcoin_base.BtcTransaction.fromRaw(txHex); + ins.add(tx); } return ElectrumTransactionBundle( original, ins: ins, time: time, - confirmations: confirmations ?? 0, + confirmations: confirmations, ); } Future fetchTransactionInfo( - {required String hash, int? height, bool? retryOnFailure}) async { + {required String hash, + required int height, + required Set myAddresses, + bool? retryOnFailure}) async { try { return ElectrumTransactionInfo.fromElectrumBundle( - await getTransactionExpanded(hash: hash, height: height), - walletInfo.type, - network, - addresses: addressesSet, - height: height, - ); + await getTransactionExpanded(hash: hash), walletInfo.type, network, + addresses: myAddresses, height: height); } catch (e) { if (e is FormatException && retryOnFailure == true) { await Future.delayed(const Duration(seconds: 2)); - return fetchTransactionInfo(hash: hash, height: height); + return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses); } return null; } } - bool isMine(Script script) { - final derivedAddress = addressFromOutputScript(script, network); - return addressesSet.contains(derivedAddress); - } - @override Future> fetchTransactions() async { try { final Map historiesWithDetails = {}; + final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); + final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0; - 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))); - } + await Future.wait(ADDRESS_TYPES.map((type) { + final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type); - transactionHistory.transactions.values.forEach((tx) async { - final isPendingSilentPaymentUtxo = - (tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null; + return Future.wait(addressesByType.map((addressRecord) async { + final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight); + final balance = await electrumClient.getBalance(addressRecord.scriptHash!); - if (isPendingSilentPaymentUtxo) { - final info = - await fetchTransactionInfo(hash: tx.id, height: tx.height, retryOnFailure: true); + if (history.isNotEmpty) { + addressRecord.txCount = history.length; + addressRecord.balance = balance['confirmed'] as int? ?? 0; + historiesWithDetails.addAll(history); - if (info != null) { - tx.confirmations = info.confirmations; - tx.isPending = tx.confirmations == 0; - transactionHistory.addOne(tx); - await transactionHistory.save(); + final matchedAddresses = + addressesByType.where((addr) => addr.isHidden == addressRecord.isHidden); + + final isLastUsedAddress = + history.isNotEmpty && addressRecord.address == matchedAddresses.last.address; + + if (isLastUsedAddress) { + await walletAddresses.discoverAddresses( + matchedAddresses.toList(), + addressRecord.isHidden, + (address, addressesSet) => + _fetchAddressHistory(address, addressesSet, currentHeight) + .then((history) => history.isNotEmpty ? address.address : null), + type: type); + } } - } - }); + })); + })); return historiesWithDetails; } catch (e) { - printV("fetchTransactions $e"); + print(e.toString()); 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); - } - } - } - })); - } - Future> _fetchAddressHistory( - BitcoinAddressRecord addressRecord, int? currentHeight) async { - String txid = ""; - + BitcoinAddressRecord addressRecord, Set addressesSet, int currentHeight) async { try { final Map historiesWithDetails = {}; - final history = await electrumClient.getHistory(addressRecord.getScriptHash(network)); + final history = await electrumClient + .getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network)); if (history.isNotEmpty) { addressRecord.setAsUsed(); await Future.wait(history.map((transaction) async { - txid = transaction['tx_hash'] as String; + final txid = transaction['tx_hash'] as String; final height = transaction['height'] as int; final storedTx = transactionHistory.transactions[txid]; @@ -2075,35 +1223,20 @@ abstract class ElectrumWalletBase 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.confirmations = currentHeight - height + 1; storedTx.isPending = storedTx.confirmations == 0; } historiesWithDetails[txid] = storedTx; } else { - final tx = await fetchTransactionInfo(hash: txid, height: height, retryOnFailure: true); + final tx = await fetchTransactionInfo( + hash: txid, height: height, myAddresses: addressesSet, 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(); } @@ -2114,151 +1247,76 @@ abstract class ElectrumWalletBase } return historiesWithDetails; - } catch (e, stacktrace) { - _onError?.call(FlutterErrorDetails( - exception: "$txid - $e", - stack: stacktrace, - library: this.runtimeType.toString(), - )); + } catch (e) { + print(e.toString()); return {}; } } 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(); walletAddresses.updateReceiveAddresses(); _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.allAddresses.toList(); final balanceFutures = >>[]; for (var i = 0; i < addresses.length; i++) { final addressRecord = addresses[i]; - final sh = addressRecord.getScriptHash(network); + final sh = scriptHash(addressRecord.address, network: network); 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,25 +1326,32 @@ 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.allAddresses.where((addr) => addr.isHidden).toList(); + + if (addresses.length < minCountOfHiddenAddresses) { + addresses = walletAddresses.allAddresses.toList(); + } + + return addresses[random.nextInt(addresses.length)].address; + } + @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; @@ -2295,492 +1360,63 @@ abstract class ElectrumWalletBase 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()); - - String messagePrefix = '\x18Bitcoin Signed Message:\n'; - final hexEncoded = priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix); - final decodedSig = hex.decode(hexEncoded); - return base64Encode(decodedSig); + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } - @override - Future verifyMessage(String message, String signature, {String? address = null}) async { - if (address == null) { - return false; + static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) { + if (networkType == bitcoin.bitcoin && currency == CryptoCurrency.bch) { + return BitcoinCashNetwork.mainnet; } - List sigDecodedBytes = []; - - if (signature.endsWith('=')) { - sigDecodedBytes = base64.decode(signature); - } else { - sigDecodedBytes = hex.decode(signature); + if (networkType == litecoinNetwork) { + return LitecoinNetwork.mainnet; } - if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) { - throw ArgumentException( - "signature must be 64 bytes without recover-id or 65 bytes with recover-id"); + if (networkType == bitcoin.testnet) { + return BitcoinNetwork.testnet; } - 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); - } - } - }); + return BitcoinNetwork.mainnet; } 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); - } } -class ScanNode { - final Uri uri; - final bool? useSSL; +class EstimateTxParams { + EstimateTxParams( + {required this.amount, + required this.feeRate, + required this.priority, + required this.outputsCount, + required this.size}); - 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()), - ); - } + final int amount; + final int feeRate; + final TransactionPriority priority; + final int outputsCount; + final int size; } class EstimatedTxResult { EstimatedTxResult({ required this.utxos, - required this.inputPrivKeyInfos, + required this.privateKeys, 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 List privateKeys; 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; @@ -2794,6 +1430,29 @@ class PublicKeyWithDerivationPath { final String publicKey; } +BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { + if (network is BitcoinCashNetwork) { + if (!address.startsWith("bitcoincash:") && + (address.startsWith("q") || address.startsWith("p"))) { + address = "bitcoincash:$address"; + } + + return BitcoinCashAddress(address).baseAddress; + } + + if (P2pkhAddress.regex.hasMatch(address)) { + return P2pkhAddress.fromAddress(address: address, network: network); + } else if (P2shAddress.regex.hasMatch(address)) { + return P2shAddress.fromAddress(address: address, network: network); + } else if (P2wshAddress.regex.hasMatch(address)) { + return P2wshAddress.fromAddress(address: address, network: network); + } else if (P2trAddress.regex.hasMatch(address)) { + return P2trAddress.fromAddress(address: address, network: network); + } else { + return P2wpkhAddress.fromAddress(address: address, network: network); + } +} + BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { if (type is P2pkhAddress) { return P2pkhAddressType.p2pkh; @@ -2803,35 +1462,7 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) { 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..c43d4988a 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,12 +1,7 @@ -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: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_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -16,7 +11,7 @@ part 'electrum_wallet_addresses.g.dart'; class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; -const List BITCOIN_ADDRESS_TYPES = [ +const List ADDRESS_TYPES = [ SegwitAddresType.p2wpkh, P2pkhAddressType.p2pkh, SegwitAddresType.p2tr, @@ -24,33 +19,20 @@ const List BITCOIN_ADDRESS_TYPES = [ P2shAddressType.p2wpkhInP2sh, ]; -const List LITECOIN_ADDRESS_TYPES = [ - SegwitAddresType.p2wpkh, - SegwitAddresType.mweb, -]; - -const List BITCOIN_CASH_ADDRESS_TYPES = [ - P2pkhAddressType.p2pkh, -]; - abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ElectrumWalletAddressesBase( WalletInfo walletInfo, { required this.mainHd, required this.sideHd, + required this.electrumClient, 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()), + ObservableList.of(([]).toSet()), receiveAddresses = ObservableList.of((initialAddresses ?? []) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) .toSet()), @@ -63,41 +45,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { (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(); } @@ -106,20 +54,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { static const gap = 20; final ObservableList _addresses; - final ObservableList addressesByReceiveType; + // Matched by addressPageType + late ObservableList addressesByReceiveType; 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 ElectrumClient electrumClient; final BasedUtxoNetwork network; - final Bip32Slip10Secp256k1 mainHd; - final Bip32Slip10Secp256k1 sideHd; - final bool isHardwareWallet; - - @observable - SilentPaymentOwner? silentAddress; + final bitcoin.HDWallet mainHd; + final bitcoin.HDWallet sideHd; @observable late BitcoinAddressType _addressPageType; @@ -127,49 +69,33 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @computed BitcoinAddressType get addressPageType => _addressPageType; - @observable - String? activeSilentAddress; - @computed List get allAddresses => _addresses; @override @computed String get address { - if (addressPageType == SilentPaymentsAddresType.p2sp) { - if (activeSilentAddress != null) { - return activeSilentAddress!; + String receiveAddress; + + final typeMatchingReceiveAddresses = + receiveAddresses.where(_isAddressPageTypeMatch).where((addr) => !addr.isUsed); + + if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || + typeMatchingReceiveAddresses.isEmpty) { + receiveAddress = generateNewAddress().address; + } else { + final previousAddressMatchesType = + previousAddressRecord != null && previousAddressRecord!.type == addressPageType; + + if (previousAddressMatchesType && + typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) { + receiveAddress = previousAddressRecord!.address; + } else { + receiveAddress = typeMatchingReceiveAddresses.first.address; } - - return silentAddress.toString(); } - 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; + return receiveAddress; } @observable @@ -177,31 +103,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @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); + final addressRecord = _addresses.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"); - } + previousAddressRecord = addressRecord; + receiveAddresses.remove(addressRecord); + receiveAddresses.insert(0, addressRecord); } @override @@ -223,8 +129,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { void set currentChangeAddressIndex(int index) => currentChangeAddressIndexByType[_addressPageType.toString()] = index; - int currentSilentAddressIndex; - @observable BitcoinAddressRecord? previousAddressRecord; @@ -249,24 +153,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { 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); - } + await _generateInitialAddresses(); } 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); - } + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); + await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); + await _generateInitialAddresses(type: SegwitAddresType.p2tr); + await _generateInitialAddresses(type: SegwitAddresType.p2wsh); } - updateAddressesByMatch(); updateReceiveAddresses(); updateChangeAddresses(); - _validateAddresses(); await updateAddressesInBox(); if (currentReceiveAddressIndex >= receiveAddresses.length) { @@ -279,10 +176,7 @@ 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) { @@ -297,55 +191,12 @@ 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; - } - + BitcoinAddressRecord generateNewAddress({String label = ''}) { final newAddressIndex = addressesByReceiveType.fold( 0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc); @@ -357,190 +208,45 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { type: addressPageType, network: network, ); - Future.delayed(Duration.zero, () { - _addresses.add(address); - updateAddressesByMatch(); - }); + _addresses.add(address); + updateAddressesByMatch(); return address; } - String getAddress({ - required int index, - required Bip32Slip10Secp256k1 hd, - BitcoinAddressType? addressType, - }) => + String getAddress( + {required int index, required bitcoin.HDWallet 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'; - } - } - @override Future updateAddressesInBox() async { try { addressesMap.clear(); - addressesMap[address] = 'Active'; + addressesMap[address] = ''; 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; - } - 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; - } - }); + final addressRecord = + _addresses.firstWhere((addressRecord) => addressRecord.address == address); + addressRecord.setNewName(label); + final index = _addresses.indexOf(addressRecord); + _addresses.remove(addressRecord); + _addresses.insert(index, 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); - } - } + updateAddressesByMatch(); } @action void updateAddressesByMatch() { - if (addressPageType == SilentPaymentsAddresType.p2sp) { - addressesByReceiveType.clear(); - addressesByReceiveType.addAll(silentAddresses); - return; - } - addressesByReceiveType.clear(); addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList()); } @@ -566,13 +272,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future discoverAddresses(List addressList, bool isHidden, - Future Function(BitcoinAddressRecord) getAddressHistory, + Future Function(BitcoinAddressRecord, Set) getAddressHistory, {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async { + if (!isHidden) { + _validateSideHdAddresses(addressList.toList()); + } + final newAddresses = await _createNewAddresses(gap, startIndex: addressList.length, isHidden: isHidden, type: type); addAddresses(newAddresses); - final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory)); + final addressesWithHistory = await Future.wait(newAddresses + .map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet()))); final isLastAddressUsed = addressesWithHistory.last == addressList.last.address; if (isLastAddressUsed) { @@ -617,7 +328,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { for (var i = startIndex; i < count + startIndex; i++) { final address = BitcoinAddressRecord( - await getAddressAsync(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType), + getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType), index: i, isHidden: isHidden, type: type ?? addressPageType, @@ -638,39 +349,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { updateAddressesByMatch(); } - @action - void addSilentAddresses(Iterable addresses) { - final addressesSet = this.silentAddresses.toSet(); - addressesSet.addAll(addresses); - this.silentAddresses.clear(); - this.silentAddresses.addAll(addressesSet); - updateAddressesByMatch(); - } - - @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)) { + void _validateSideHdAddresses(List addrWithTransactions) { + addrWithTransactions.forEach((element) { + if (element.address != + getAddress(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; - } }); } @@ -686,19 +369,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return _isAddressByType(addressRecord, addressPageType); } - Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd; - + bitcoin.HDWallet _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(); - } } diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 990719089..340b17cfb 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -2,8 +2,6 @@ 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_core/pathForWallet.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/utils/file.dart'; @@ -21,10 +19,6 @@ class ElectrumWalletSnapshot { 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, @@ -35,65 +29,37 @@ class ElectrumWalletSnapshot { final WalletType type; final String? addressPageType; - @deprecated String? mnemonic; - - @deprecated String? xpub; - - @deprecated - String? passphrase; - List addresses; - List silentAddresses; - List mwebAddresses; - bool alwaysScan; - ElectrumBalance balance; Map regularAddressIndex; Map changeAddressIndex; - int silentAddressIndex; + String? passphrase; DerivationType? derivationType; String? derivationPath; - static Future load(EncryptionFileUtils encryptionFileUtils, String name, - WalletType type, String password, BasedUtxoNetwork network) async { + static Future load( + String name, WalletType type, String password, BasedUtxoNetwork network) 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 addressesTmp = data['addresses'] as List? ?? []; 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 addresses = addressesTmp .whereType() - .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network)) + .map((addr) => BitcoinAddressRecord.fromJSON(addr, network)) .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; + final derivationType = + DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index]; + final derivationPath = data['derivationPath'] as String? ?? "m/0'/0"; try { regularAddressIndexByType = { @@ -103,7 +69,6 @@ class ElectrumWalletSnapshot { 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? ?? {}; @@ -125,10 +90,6 @@ class ElectrumWalletSnapshot { addressPageType: data['address_page_type'] as String?, derivationType: derivationType, derivationPath: derivationPath, - silentAddresses: silentAddresses, - silentAddressIndex: silentAddressIndex, - mwebAddresses: mwebAddresses, - alwaysScan: alwaysScan, ); } } diff --git a/cw_bitcoin/lib/exceptions.dart b/cw_bitcoin/lib/exceptions.dart index 9bdb66eef..979c1a433 100644 --- a/cw_bitcoin/lib/exceptions.dart +++ b/cw_bitcoin/lib/exceptions.dart @@ -2,12 +2,7 @@ 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"; - } + BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc); } class BitcoinTransactionNoInputsException extends TransactionNoInputsException {} @@ -18,20 +13,10 @@ 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 {} @@ -42,9 +27,3 @@ class BitcoinTransactionCommitFailedDustOutputSendAll extends TransactionCommitFailedDustOutputSendAll {} class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {} - -class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {} - -class BitcoinTransactionCommitFailedLessThanMin extends TransactionCommitFailedLessThanMin {} - -class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {} 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..2ffb99405 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,55 +1,21 @@ -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; +import 'package:bip39/bip39.dart' as bip39; part 'litecoin_wallet.g.dart'; @@ -57,120 +23,49 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; abstract class LitecoinWalletBase extends ElectrumWallet with Store { LitecoinWalletBase({ + required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required EncryptionFileUtils encryptionFileUtils, - Uint8List? seedBytes, - String? mnemonic, - String? xpub, - String? passphrase, + required Uint8List seedBytes, 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; - } + 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, - initialMwebAddresses: initialMwebAddresses, mainHd: hd, - sideHd: accountHD.childKey(Bip32KeyIndex(1)), + sideHd: accountHD.derive(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( {required String mnemonic, required String password, required WalletInfo walletInfo, required Box unspentCoinsInfo, - required EncryptionFileUtils encryptionFileUtils, String? passphrase, String? addressPageType, List? initialAddresses, - List? initialMwebAddresses, ElectrumBalance? initialBalance, Map? initialRegularAddressIndex, Map? initialChangeAddressIndex}) async { @@ -185,7 +80,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { break; case DerivationType.electrum: default: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = await mnemonicToSeedBytes(mnemonic); break; } return LitecoinWallet( @@ -194,10 +89,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, initialAddresses: initialAddresses, - initialMwebAddresses: initialMwebAddresses, initialBalance: initialBalance, - encryptionFileUtils: encryptionFileUtils, - passphrase: passphrase, seedBytes: seedBytes, initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex, @@ -210,738 +102,20 @@ 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 ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet); return LitecoinWallet( - mnemonic: keysData.mnemonic, - xpub: keysData.xPub, + mnemonic: snp.mnemonic!, 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, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await mnemonicToSeedBytes(snp.mnemonic!), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex, + addressPageType: snp.addressPageType, ); } @@ -960,469 +134,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..993d17933 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -1,19 +1,8 @@ -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/utils.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'; @@ -26,188 +15,14 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with required super.mainHd, required super.sideHd, required super.network, - required super.isHardwareWallet, - required this.mwebHd, - required this.mwebEnabled, + required super.electrumClient, 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; + }) : super(walletInfo); @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, BitcoinAddressType? addressType}) => + generateP2WPKHAddress(hd: hd, index: index, network: network); } diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 89ae384d4..bb51a4eaa 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/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'; @@ -15,47 +12,27 @@ 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,BitcoinNewWalletCredentials> { + 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; - } - final wallet = await LitecoinWalletBase.create( - mnemonic: mnemonic, - password: credentials.password!, - passphrase: credentials.passphrase, - walletInfo: credentials.walletInfo!, - unspentCoinsInfo: unspentCoinsInfoSource, - encryptionFileUtils: encryptionFileUtilsFor(isDirect), - ); + mnemonic: await generateElectrumMnemonic(), + password: credentials.password!, + passphrase: credentials.passphrase, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.save(); await wallet.init(); @@ -68,32 +45,21 @@ class LitecoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - - final walletInfo = walletInfoSource.values - .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + 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), - ); + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); 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), - ); + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; } @@ -101,54 +67,22 @@ class LitecoinWalletService extends WalletService< @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); @@ -161,45 +95,28 @@ 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; + Future restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) { + throw UnimplementedError("Restoring a Litecoin wallet from a hardware wallet is not yet supported!"); } @override - Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, - {bool? isTestnet}) async => + Future restoreFromKeys( + BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async => throw UnimplementedError(); @override - Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, - {bool? isTestnet}) async { + Future restoreFromSeed( + BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) { throw LitecoinMnemonicIsIncorrectException(); } 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!, + passphrase: credentials.passphrase, + 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..a59b4f429 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,16 +1,11 @@ -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_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( @@ -24,10 +19,6 @@ class PendingBitcoinTransaction with PendingTransaction { required this.hasChange, this.isSendAll = false, this.hasTaprootInputs = false, - this.isMweb = false, - this.utxos = const [], - this.publicKeys, - this.commitOverride, }) : _listeners = []; final WalletType type; @@ -37,23 +28,15 @@ class PendingBitcoinTransaction with PendingTransaction { final int fee; final String feeRate; final BasedUtxoNetwork? network; - final bool isSendAll; final bool hasChange; + final bool isSendAll; 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.txId(); @override - String get hex => hexOverride ?? _tx.serialize(); + String get hex => _tx.serialize(); @override String get amountFormatted => bitcoinAmountToString(amount: amount); @@ -64,25 +47,10 @@ class PendingBitcoinTransaction with PendingTransaction { @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 { + @override + Future commit() async { int? callId; final result = await electrumClient.broadcastTransaction( @@ -105,44 +73,11 @@ class PendingBitcoinTransaction with PendingTransaction { 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(); - } - - if (isMweb) { - await _ltcCommit(); - } else { - await _commit(); - } _listeners.forEach((listener) => listener(transactionInfo())); } @@ -157,14 +92,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