diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..f59ec20aa --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 18ad16e4b..272f7bbee 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,3 +11,4 @@ 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 index 539513111..47b08c44d 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -12,7 +12,7 @@ on: jobs: Automated_integration_test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -55,7 +55,7 @@ jobs: - name: Flutter action uses: subosito/flutter-action@v1 with: - flutter-version: "3.24.0" + flutter-version: "3.27.0" channel: stable - name: Install package dependencies @@ -153,8 +153,8 @@ jobs: 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 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 @@ -168,6 +168,7 @@ jobs: 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 @@ -178,7 +179,8 @@ jobs: 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 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 diff --git a/.github/workflows/no_http_imports.yaml b/.github/workflows/no_http_imports.yaml new file mode 100644 index 000000000..dad6821ac --- /dev/null +++ b/.github/workflows/no_http_imports.yaml @@ -0,0 +1,21 @@ +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 index 8cd24edfe..507793bd8 100644 --- a/.github/workflows/no_print_in_dart.yaml +++ b/.github/workflows/no_print_in_dart.yaml @@ -1,21 +1,19 @@ name: No print statements in dart files -on: - pull_request: - branches: [main] +on: [pull_request] jobs: PR_test_build: - runs-on: ubuntu-20.04 + 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) || true)" + 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" + 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 new file mode 100644 index 000000000..03c3de018 --- /dev/null +++ b/.github/workflows/no_restricted_imports.yaml @@ -0,0 +1,47 @@ +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_android.yml b/.github/workflows/pr_test_build_android.yml index 762144ac1..f7c226ce4 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -9,7 +9,7 @@ jobs: PR_test_build: runs-on: linux-amd64 container: - image: ghcr.io/cake-tech/cake_wallet:main-linux + 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 @@ -47,6 +47,7 @@ jobs: echo "message<> $GITHUB_ENV echo "$FULL_MESSAGE" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + - name: Add secrets run: | touch lib/.secrets.g.dart @@ -97,8 +98,8 @@ jobs: else echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart fi - 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 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 @@ -112,6 +113,7 @@ jobs: 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 @@ -123,7 +125,8 @@ jobs: 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 exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> 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 @@ -243,8 +246,20 @@ jobs: ./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 @@ -266,7 +281,7 @@ jobs: - name: Build run: | - flutter build apk --release --split-per-abi + flutter build apk --dart-define=hasDevOptions=true --release --split-per-abi - name: Rename apk file run: | @@ -281,7 +296,7 @@ jobs: 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 @@ -294,9 +309,9 @@ jobs: - 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" \ No newline at end of file + name: "android apk" diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index c5cb26dd9..f057b19e5 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -9,7 +9,7 @@ jobs: PR_test_build: runs-on: linux-amd64 container: - image: ghcr.io/cake-tech/cake_wallet:main-linux + 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 @@ -22,9 +22,6 @@ jobs: - /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 - strategy: - matrix: - api-level: [29] steps: - name: Fix github actions messing up $HOME... @@ -32,6 +29,7 @@ jobs: - 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 '*' @@ -93,8 +91,8 @@ jobs: else echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart fi - 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 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 @@ -108,6 +106,7 @@ jobs: 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 @@ -119,7 +118,8 @@ jobs: 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 exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> 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 @@ -227,7 +227,7 @@ jobs: - name: Build linux run: | - flutter build linux --release + flutter build linux --dart-define=hasDevOptions=true --release - name: Compress release run: | @@ -285,6 +285,9 @@ jobs: 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: | diff --git a/.gitignore b/.gitignore index c431a7f60..84a7ecdcd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .history .svn/ .fvm/ +.fvmrc # IntelliJ related *.iml @@ -138,10 +139,30 @@ 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 @@ -171,6 +192,7 @@ 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 @@ -182,3 +204,26 @@ 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/Dockerfile b/Dockerfile new file mode 100644 index 000000000..151b7af20 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,190 @@ +# 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/README.md b/README.md index ea8f34624..ea796dbf2 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,13 @@ Cake Wallet includes support for several cryptocurrencies, including: * Ethereum (ETH) * Litecoin (LTC) * Bitcoin Cash (BCH) -* Polygon (Pol) +* Polygon (POL) * Solana (SOL) +* Tron (TRX) * Nano (XNO) -* Haven (XHV) +* Zano (ZANO) +* Decred (DCR) +* Wownero (WOW) ## Features @@ -81,10 +84,6 @@ 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 diff --git a/android/app/build.gradle b/android/app/build.gradle index b65c54108..4a8045bb3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,9 @@ +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()) { @@ -6,11 +12,6 @@ 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' @@ -21,9 +22,6 @@ 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()) { @@ -37,13 +35,21 @@ if (appPropertiesFile.exists()) { } android { - compileSdkVersion 34 - buildToolsVersion "34.0.0" + compileSdkVersion 35 + buildToolsVersion "35.0.0" lintOptions { disable 'InvalidPackage' } + compileOptions { + coreLibraryDesugaringEnabled true + + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + namespace "com.cakewallet.cake_wallet" defaultConfig { @@ -75,12 +81,11 @@ 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" @@ -94,6 +99,7 @@ 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' diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index d24d7f10a..a733bae9e 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -5,4 +5,98 @@ -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } --dontwarn io.flutter.embedding.** \ No newline at end of file +-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 diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 5a1824a17..8283a7c8c 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -24,6 +24,10 @@ + + + + + + + + @@ -100,10 +111,15 @@ + + + plugins.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory +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 } + +include ":app" \ No newline at end of file diff --git a/assets/decred_node_list.yml b/assets/decred_node_list.yml new file mode 100644 index 000000000..cb171e701 --- /dev/null +++ b/assets/decred_node_list.yml @@ -0,0 +1,6 @@ +- + uri: default-spv-nodes + is_default: true +- + uri: dcrd.sethforprivacy.com:9108 + useSSL: true \ No newline at end of file diff --git a/assets/faq/faq_pl.json b/assets/faq/faq_pl.json index a38d79068..b41841cd8 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 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" + "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" }, { "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 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" : "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" : "Czy zbierasz jakieś informacje o moim portfelu?", + "question" : "Czy zbieracie 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 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" + "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" }, { "question" : "Co to jest ID transakcji?", diff --git a/assets/images/2.0x/decred.png b/assets/images/2.0x/decred.png new file mode 100644 index 000000000..2f4919cec Binary files /dev/null and b/assets/images/2.0x/decred.png differ diff --git a/assets/images/2.0x/decred_menu.png b/assets/images/2.0x/decred_menu.png new file mode 100644 index 000000000..4a41efef1 Binary files /dev/null and b/assets/images/2.0x/decred_menu.png differ diff --git a/assets/images/2fa.png b/assets/images/2fa.png new file mode 100644 index 000000000..36c99beab Binary files /dev/null and b/assets/images/2fa.png differ diff --git a/assets/images/2fa_warning_dark.svg b/assets/images/2fa_warning_dark.svg new file mode 100644 index 000000000..c9fcad341 --- /dev/null +++ b/assets/images/2fa_warning_dark.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/2fa_warning_light.svg b/assets/images/2fa_warning_light.svg new file mode 100644 index 000000000..087d8e99b --- /dev/null +++ b/assets/images/2fa_warning_light.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/3.0x/decred.png b/assets/images/3.0x/decred.png new file mode 100644 index 000000000..b2c9ac818 Binary files /dev/null and b/assets/images/3.0x/decred.png differ diff --git a/assets/images/3.0x/decred_menu.png b/assets/images/3.0x/decred_menu.png new file mode 100644 index 000000000..e55b3fb5c Binary files /dev/null and b/assets/images/3.0x/decred_menu.png differ diff --git a/assets/images/birthday_cake.png b/assets/images/birthday_cake.png index 84b084fba..293cd10f6 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 new file mode 100644 index 000000000..f5b3d7e27 Binary files /dev/null and b/assets/images/btc_lock_dark.png differ diff --git a/assets/images/btc_lock_light.png b/assets/images/btc_lock_light.png new file mode 100644 index 000000000..4320d96e3 Binary files /dev/null and b/assets/images/btc_lock_light.png differ diff --git a/assets/images/buy.png b/assets/images/buy.png index ff4549d5a..32c116e6b 100644 Binary files a/assets/images/buy.png and b/assets/images/buy.png differ diff --git a/assets/images/cake_logo.png b/assets/images/cake_logo.png index 8a85bf225..abbb4e62b 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 new file mode 100644 index 000000000..095077443 --- /dev/null +++ b/assets/images/cake_logo_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/cake_logo_light.svg b/assets/images/cake_logo_light.svg new file mode 100644 index 000000000..767e205d1 --- /dev/null +++ b/assets/images/cake_logo_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/cakewallet_android_icon.png b/assets/images/cakewallet_android_icon.png old mode 100755 new mode 100644 index 59cc69414..7f15c62f5 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 c8bd4b26c..345888d26 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,6 @@ - - - + + + \ 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 10d0a1a82..89c9b0571 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 deleted file mode 100644 index 5b0fde827..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_back.png and /dev/null 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 deleted file mode 100644 index 9c16f0a27..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 27f939b41..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png and /dev/null 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 new file mode 100644 index 000000000..19669488f Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_background.png 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 new file mode 100644 index 000000000..1411a5da5 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_foreground.png 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 new file mode 100644 index 000000000..1411a5da5 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_monochrome.png 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 8c59ec33e..8f1d1c28b 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 deleted file mode 100644 index 5d25e42e7..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_back.png and /dev/null 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 deleted file mode 100644 index 021fe65de..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 7bdb298c1..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png and /dev/null 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 new file mode 100644 index 000000000..75025cfd5 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_background.png 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 new file mode 100644 index 000000000..e8c47adb3 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_foreground.png 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 new file mode 100644 index 000000000..e8c47adb3 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_monochrome.png 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 10c3acd7f..f775a8fac 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 deleted file mode 100644 index c4b66dc58..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_back.png and /dev/null 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 deleted file mode 100644 index b440b154d..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 79e9df08d..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png and /dev/null 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 new file mode 100644 index 000000000..9784f16c8 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_background.png 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 new file mode 100644 index 000000000..6ba8eb301 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_foreground.png 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 new file mode 100644 index 000000000..6ba8eb301 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_monochrome.png 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 813a3678d..31458fa02 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 deleted file mode 100644 index 75dc0219d..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_back.png and /dev/null 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 deleted file mode 100644 index 90afb19e8..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 0bb1c7430..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png and /dev/null 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 new file mode 100644 index 000000000..04ef206c8 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_background.png 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 new file mode 100644 index 000000000..cc93d633b Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_foreground.png 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 new file mode 100644 index 000000000..cc93d633b Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_monochrome.png 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 671422b96..158afbbf9 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 deleted file mode 100644 index 46b1e2cb1..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_back.png and /dev/null 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 deleted file mode 100644 index 0a2025220..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png and /dev/null differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png deleted file mode 100644 index 205f2ad2a..000000000 Binary files a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png and /dev/null 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 new file mode 100644 index 000000000..66a5487a2 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_foreground.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..0ecd56e8c Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_foreground.png 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 new file mode 100644 index 000000000..0ecd56e8c Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/assets/images/cakewallet_app_logo.png b/assets/images/cakewallet_app_logo.png deleted file mode 100644 index 59cc69414..000000000 Binary files a/assets/images/cakewallet_app_logo.png and /dev/null differ diff --git a/assets/images/cakewallet_icon_1024.png b/assets/images/cakewallet_icon_1024.png index 64682cd1d..35cf42245 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 1a2c1b99c..6e45f6423 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 ff69a866a..da585ca42 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 bf6896ad2..c465fa26c 100644 Binary files a/assets/images/cakewallet_logo.png and b/assets/images/cakewallet_logo.png differ diff --git a/assets/images/contact.png b/assets/images/contact.png new file mode 100644 index 000000000..5cf96694b Binary files /dev/null and b/assets/images/contact.png differ diff --git a/assets/images/contact_icon.svg b/assets/images/contact_icon.svg new file mode 100644 index 000000000..6dbfcd5f4 --- /dev/null +++ b/assets/images/contact_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/dcr_icon.png b/assets/images/dcr_icon.png index 609873611..757cd0388 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 new file mode 100644 index 000000000..0b12f2ef0 Binary files /dev/null and b/assets/images/decred.png differ diff --git a/assets/images/decred_icon.png b/assets/images/decred_icon.png new file mode 100644 index 000000000..9391abc3d Binary files /dev/null and b/assets/images/decred_icon.png differ diff --git a/assets/images/decred_menu.png b/assets/images/decred_menu.png new file mode 100644 index 000000000..5c67923c5 Binary files /dev/null and b/assets/images/decred_menu.png differ diff --git a/assets/images/deuro_icon.png b/assets/images/deuro_icon.png new file mode 100644 index 000000000..4dc068ff8 Binary files /dev/null and b/assets/images/deuro_icon.png differ diff --git a/assets/images/hero/cw_welcome_dark.svg b/assets/images/hero/cw_welcome_dark.svg new file mode 100644 index 000000000..5479cb1ee --- /dev/null +++ b/assets/images/hero/cw_welcome_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/hero/cw_welcome_light.svg b/assets/images/hero/cw_welcome_light.svg new file mode 100644 index 000000000..ece7d1f84 --- /dev/null +++ b/assets/images/hero/cw_welcome_light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/history.svg b/assets/images/history.svg new file mode 100644 index 000000000..f308ab7e3 --- /dev/null +++ b/assets/images/history.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/home_screen_setting_icon.svg b/assets/images/home_screen_setting_icon.svg new file mode 100644 index 000000000..7b3aa7b4c --- /dev/null +++ b/assets/images/home_screen_setting_icon.svg @@ -0,0 +1,6 @@ + + + + + + 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 new file mode 100644 index 000000000..3fd15f3ce Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x.png 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 new file mode 100644 index 000000000..3fd15f3ce Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@2x~ipad.png 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 new file mode 100644 index 000000000..b6ff994f6 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20@3x.png 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 new file mode 100644 index 000000000..4be1a2317 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-20~ipad.png 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 new file mode 100644 index 000000000..219f2c6be Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29.png 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 new file mode 100644 index 000000000..ae94bb0ac Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x.png 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 new file mode 100644 index 000000000..ae94bb0ac Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@2x~ipad.png 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 new file mode 100644 index 000000000..0ef9d3bbf Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29@3x.png 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 new file mode 100644 index 000000000..219f2c6be Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-29~ipad.png 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 new file mode 100644 index 000000000..9fdb32376 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x.png 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 new file mode 100644 index 000000000..9fdb32376 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@2x~ipad.png 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 new file mode 100644 index 000000000..485f8b37b Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40@3x.png 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 new file mode 100644 index 000000000..3fd15f3ce Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-40~ipad.png 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 new file mode 100644 index 000000000..485f8b37b Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@2x~car.png 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 new file mode 100644 index 000000000..50148e6dc Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-60@3x~car.png 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 new file mode 100644 index 000000000..8f290ada2 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon-83.5@2x~ipad.png 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 new file mode 100644 index 000000000..485f8b37b Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x.png 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 new file mode 100644 index 000000000..b11d7332f Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@2x~ipad.png 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 new file mode 100644 index 000000000..50148e6dc Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon@3x.png 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 new file mode 100644 index 000000000..1fce95553 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ios-marketing.png 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 new file mode 100644 index 000000000..7d4a82186 Binary files /dev/null and b/assets/images/ios_icons/cakewallet_ios_icons/AppIcon~ipad.png 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 new file mode 100644 index 000000000..7ea540caf Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x.png 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 new file mode 100644 index 000000000..7ea540caf Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@2x~ipad.png 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 new file mode 100644 index 000000000..6ac773754 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-20@3x.png 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 new file mode 100644 index 000000000..57864a9b3 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-20~ipad.png 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 new file mode 100644 index 000000000..27f817dfc Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-29.png 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 new file mode 100644 index 000000000..0455b4409 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x.png 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 new file mode 100644 index 000000000..0455b4409 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-29@2x~ipad.png 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 new file mode 100644 index 000000000..1b8a73481 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-29@3x.png 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 new file mode 100644 index 000000000..27f817dfc Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-29~ipad.png 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 new file mode 100644 index 000000000..963612d0c Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x.png 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 new file mode 100644 index 000000000..963612d0c Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@2x~ipad.png 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 new file mode 100644 index 000000000..b6da404cb Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-40@3x.png 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 new file mode 100644 index 000000000..7ea540caf Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-40~ipad.png 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 new file mode 100644 index 000000000..b6da404cb Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-60@2x~car.png 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 new file mode 100644 index 000000000..37f7651a5 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-60@3x~car.png 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 new file mode 100644 index 000000000..21aa12463 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon-83.5@2x~ipad.png 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 new file mode 100644 index 000000000..b6da404cb Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon@2x.png 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 new file mode 100644 index 000000000..b6b63a61e Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon@2x~ipad.png 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 new file mode 100644 index 000000000..37f7651a5 Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon@3x.png 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 new file mode 100644 index 000000000..0c977110a Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon~ios-marketing.png 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 new file mode 100644 index 000000000..849c5612a Binary files /dev/null and b/assets/images/ios_icons/monero_ios_icons/AppIcon~ipad.png differ 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 73101354a..c465fa26c 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 9ceee3c5e..e79db4d99 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 ef46cd805..2d030845b 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 6547a1b1b..eef607c2c 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 e436872e8..a09896830 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 157b00493..a98c5faf8 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 a46ed4535..d8141a250 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/menu.svg b/assets/images/menu.svg new file mode 100644 index 000000000..0a4cc3784 --- /dev/null +++ b/assets/images/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/notif.svg b/assets/images/notif.svg new file mode 100644 index 000000000..b1ff5b4fa --- /dev/null +++ b/assets/images/notif.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/notification_icon.svg b/assets/images/notification_icon.svg index 099039e67..360d0b4e6 100644 --- a/assets/images/notification_icon.svg +++ b/assets/images/notification_icon.svg @@ -1,69 +1,3 @@ - - - -image/svg+xml \ No newline at end of file + + + diff --git a/assets/images/passphrase_dark.png b/assets/images/passphrase_dark.png new file mode 100644 index 000000000..f72d1e1a1 Binary files /dev/null and b/assets/images/passphrase_dark.png differ diff --git a/assets/images/passphrase_key.svg b/assets/images/passphrase_key.svg new file mode 100644 index 000000000..c577dc30a --- /dev/null +++ b/assets/images/passphrase_key.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/passphrase_light.png b/assets/images/passphrase_light.png new file mode 100644 index 000000000..f86f68156 Binary files /dev/null and b/assets/images/passphrase_light.png differ diff --git a/assets/images/payjoin.png b/assets/images/payjoin.png new file mode 100644 index 000000000..1ba3dccdb Binary files /dev/null and b/assets/images/payjoin.png differ diff --git a/assets/images/qr-cake.png b/assets/images/qr-cake.png new file mode 100644 index 000000000..7c54dedb0 Binary files /dev/null and b/assets/images/qr-cake.png differ diff --git a/assets/images/receive.png b/assets/images/receive.png new file mode 100644 index 000000000..180a4e5b3 Binary files /dev/null and b/assets/images/receive.png differ diff --git a/assets/images/seed_verified_dark.png b/assets/images/seed_verified_dark.png new file mode 100644 index 000000000..cbefa5d8c Binary files /dev/null and b/assets/images/seed_verified_dark.png differ diff --git a/assets/images/seed_verified_light.png b/assets/images/seed_verified_light.png new file mode 100644 index 000000000..a51eddcd7 Binary files /dev/null and b/assets/images/seed_verified_light.png differ diff --git a/assets/images/seed_warning_dark.svg b/assets/images/seed_warning_dark.svg new file mode 100644 index 000000000..0a47254ff --- /dev/null +++ b/assets/images/seed_warning_dark.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/seed_warning_light.svg b/assets/images/seed_warning_light.svg new file mode 100644 index 000000000..cab27ec31 --- /dev/null +++ b/assets/images/seed_warning_light.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/send.png b/assets/images/send.png deleted file mode 100644 index aef504999..000000000 Binary files a/assets/images/send.png and /dev/null differ diff --git a/assets/images/send2.png b/assets/images/send2.png new file mode 100644 index 000000000..85fc570a8 Binary files /dev/null and b/assets/images/send2.png differ diff --git a/assets/images/swap.png b/assets/images/swap.png new file mode 100644 index 000000000..fe3fc0893 Binary files /dev/null and b/assets/images/swap.png differ diff --git a/assets/images/tor_logo.svg b/assets/images/tor_logo.svg new file mode 100644 index 000000000..ebd00324d --- /dev/null +++ b/assets/images/tor_logo.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/usdtbsc_icon.png b/assets/images/usdtbsc_icon.png new file mode 100644 index 000000000..9f2cda237 Binary files /dev/null and b/assets/images/usdtbsc_icon.png differ diff --git a/assets/images/wallet_group_bright.png b/assets/images/wallet_group_bright.png deleted file mode 100644 index 263361db6..000000000 Binary files a/assets/images/wallet_group_bright.png and /dev/null differ diff --git a/assets/images/wallet_group_confirmed_dark.png b/assets/images/wallet_group_confirmed_dark.png new file mode 100644 index 000000000..a047cb29c Binary files /dev/null and b/assets/images/wallet_group_confirmed_dark.png differ diff --git a/assets/images/wallet_group_confirmed_light.png b/assets/images/wallet_group_confirmed_light.png new file mode 100644 index 000000000..851d32300 Binary files /dev/null and b/assets/images/wallet_group_confirmed_light.png differ diff --git a/assets/images/wallet_group_dark.png b/assets/images/wallet_group_dark.png deleted file mode 100644 index 7cd08d2cd..000000000 Binary files a/assets/images/wallet_group_dark.png and /dev/null differ diff --git a/assets/images/wallet_group_empty_dark.png b/assets/images/wallet_group_empty_dark.png new file mode 100644 index 000000000..e613d876e Binary files /dev/null and b/assets/images/wallet_group_empty_dark.png differ diff --git a/assets/images/wallet_group_empty_light.png b/assets/images/wallet_group_empty_light.png new file mode 100644 index 000000000..f795648ae Binary files /dev/null and b/assets/images/wallet_group_empty_light.png differ diff --git a/assets/images/wallet_group_light.png b/assets/images/wallet_group_light.png deleted file mode 100644 index 7827971e7..000000000 Binary files a/assets/images/wallet_group_light.png and /dev/null differ diff --git a/assets/images/wallet_group_options_dark.png b/assets/images/wallet_group_options_dark.png new file mode 100644 index 000000000..479aac57c Binary files /dev/null and b/assets/images/wallet_group_options_dark.png differ diff --git a/assets/images/wallet_group_options_light.png b/assets/images/wallet_group_options_light.png new file mode 100644 index 000000000..308930520 Binary files /dev/null and b/assets/images/wallet_group_options_light.png differ diff --git a/assets/images/wallet_name.png b/assets/images/wallet_name.png deleted file mode 100644 index f586682bd..000000000 Binary files a/assets/images/wallet_name.png and /dev/null differ diff --git a/assets/images/wallet_name_light.png b/assets/images/wallet_name_light.png deleted file mode 100644 index 0199c1b30..000000000 Binary files a/assets/images/wallet_name_light.png and /dev/null differ diff --git a/assets/images/wallet_type.png b/assets/images/wallet_type.png deleted file mode 100644 index 4e0eba8b5..000000000 Binary files a/assets/images/wallet_type.png and /dev/null differ diff --git a/assets/images/wallet_type_light.png b/assets/images/wallet_type_light.png deleted file mode 100644 index e36c0d3aa..000000000 Binary files a/assets/images/wallet_type_light.png and /dev/null differ diff --git a/assets/images/wallet_type_wallet_dark.png b/assets/images/wallet_type_wallet_dark.png new file mode 100644 index 000000000..b840f5547 Binary files /dev/null and b/assets/images/wallet_type_wallet_dark.png differ diff --git a/assets/images/wallet_type_wallet_light.png b/assets/images/wallet_type_wallet_light.png new file mode 100644 index 000000000..ee759a109 Binary files /dev/null and b/assets/images/wallet_type_wallet_light.png differ diff --git a/assets/images/wallets.png b/assets/images/wallets.png new file mode 100644 index 000000000..62ea20039 Binary files /dev/null and b/assets/images/wallets.png differ diff --git a/assets/images/welcome.png b/assets/images/welcome.png deleted file mode 100644 index f1132d253..000000000 Binary files a/assets/images/welcome.png and /dev/null differ diff --git a/assets/images/welcome_dark_theme.svg b/assets/images/welcome_dark_theme.svg new file mode 100644 index 000000000..8f60c931a --- /dev/null +++ b/assets/images/welcome_dark_theme.svg @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/welcome_light.png b/assets/images/welcome_light.png deleted file mode 100644 index 6feff85d1..000000000 Binary files a/assets/images/welcome_light.png and /dev/null differ diff --git a/assets/images/welcome_light_theme.svg b/assets/images/welcome_light_theme.svg new file mode 100644 index 000000000..178b2853d --- /dev/null +++ b/assets/images/welcome_light_theme.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/images/welcome_wallet_dark.png b/assets/images/welcome_wallet_dark.png new file mode 100644 index 000000000..771a600d3 Binary files /dev/null and b/assets/images/welcome_wallet_dark.png differ diff --git a/assets/images/welcome_wallet_light.png b/assets/images/welcome_wallet_light.png new file mode 100644 index 000000000..2a738be0b Binary files /dev/null and b/assets/images/welcome_wallet_light.png differ diff --git a/assets/images/wyre-icon.png b/assets/images/wyre-icon.png deleted file mode 100644 index a2810948e..000000000 Binary files a/assets/images/wyre-icon.png and /dev/null differ diff --git a/assets/images/wyre.png b/assets/images/wyre.png deleted file mode 100644 index a16bbdc8b..000000000 Binary files a/assets/images/wyre.png and /dev/null differ diff --git a/assets/images/xoswap.svg b/assets/images/xoswap.svg new file mode 100644 index 000000000..ef83c58b1 --- /dev/null +++ b/assets/images/xoswap.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/yat_crypto.png b/assets/images/yat_crypto.png deleted file mode 100644 index fbd5d2483..000000000 Binary files a/assets/images/yat_crypto.png and /dev/null differ diff --git a/assets/nano_node_list.yml b/assets/nano_node_list.yml index be550177e..cda931b5e 100644 --- a/assets/nano_node_list.yml +++ b/assets/nano_node_list.yml @@ -1,9 +1,9 @@ - uri: nano.nownodes.io useSSL: true - is_default: true - uri: rpc.nano.to + is_default: true useSSL: true - uri: node.nautilus.io diff --git a/assets/node_list.yml b/assets/node_list.yml index 49cc00a94..917dadfd9 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -13,9 +13,3 @@ - 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 diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index d1f91139b..faf57258a 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,4 @@ -UI/UX enhancements -Stability improvements +Add built-in Tor support (experimental) +Ledger improvements +UI/UX improvements Bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index d1f91139b..c49b895e3 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,3 +1,9 @@ -UI/UX enhancements -Stability improvements +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 diff --git a/build-guide-linux.md b/build-guide-linux.md deleted file mode 100644 index df5f0f601..000000000 --- a/build-guide-linux.md +++ /dev/null @@ -1,176 +0,0 @@ -# Building CakeWallet for Linux - -## Requirements and Setup - -The following are the system requirements to build CakeWallet for your Linux device. - -``` -Ubuntu >= 16.04 -Flutter 3.10.x -``` - -## Building CakeWallet on Linux - -These steps will help you configure and execute a build of CakeWallet from its source code. - -### 1. Installing Package Dependencies - -CakeWallet requires some packages to be installed on your build system. You may easily install them on your build system with the following command: - -`$ sudo apt install build-essential cmake pkg-config git curl autoconf libtool` - -> [!WARNING] -> -> ### Check gcc version -> -> It is needed to use gcc 10 or 9 to successfully link dependencies with flutter.\ -> To check what gcc version you are using: -> -> ```bash -> $ gcc --version -> $ g++ --version -> ``` -> -> If you are using gcc version newer than 10, then you need to downgrade to version 10.4.0: -> -> ```bash -> $ sudo apt install gcc-10 g++-10 -> $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10 -> $ sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10 -> ``` - -> [!NOTE] -> -> Alternatively, you can use the [nix-shell](https://nixos.org/) with the `gcc10.nix` file\ -> present on `scripts/linux` like so: -> ```bash -> $ nix-shell gcc10.nix -> ``` -> This will get you in a nix environment with all the required dependencies that you can use to build the software from,\ -> and it works in any linux distro. - -### 2. Installing Flutter - -Need to install flutter. For this please check section [How to install flutter on Linux](https://docs.flutter.dev/get-started/install/linux). - -### 3. Verify Installations - -Verify that the Flutter has been correctly installed on your system with the following command: - -`$ flutter doctor` - -The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding. - -``` -Doctor summary (to see all details, run flutter doctor -v): -[✓] Flutter (Channel stable, 3.10.x, on Linux, locale en_US.UTF-8) -``` - -### 4. Acquiring the CakeWallet Source Code - -Download CakeWallet source code - -`$ git clone https://github.com/cake-tech/cake_wallet.git --branch linux/password-direct-input` - -Proceed into the source code before proceeding with the next steps: - -`$ cd cake_wallet/scripts/linux/` - -To configure some project properties run: - -`$ ./cakewallet.sh` - -Build the Monero libraries and their dependencies: - -`$ ./build_all.sh` - -Now the dependencies need to be copied into the CakeWallet project with this command: - -`$ ./setup.sh` - -It is now time to change back to the base directory of the CakeWallet source code: - -`$ cd ../../` - -Install Flutter package dependencies with this command: - -`$ flutter pub get` - -> #### If you will get an error like: -> -> ``` -> The plugin `cw_shared_external` requires your app to be migrated to the Android embedding v2. Follow the steps on the migration doc above and re-run -> this command. -> ``` -> -> Then need to config Android project settings. For this open `scripts/android` (`$ cd scripts/android`) directory and run followed commands: -> -> ``` -> $ source ./app_env.sh cakewallet -> $ ./app_config.sh -> $ cd ../.. -> ``` -> -> Then re-configure Linux project again. For this open `scripts/linux` (`$cd scripts/linux`) directory and run: -> `$ ./cakewallet.sh` -> and back to project root directory: -> `$ cd ../..` -> and fetch dependencies again -> `$ flutter pub get` - -Your CakeWallet binary will be built with some specific keys for iterate with 3rd party services. You may generate these secret keys placeholders with the following command: - -`$ dart run tool/generate_new_secrets.dart` - -We will generate mobx models for the project. - -`$ ./model_generator.sh` - -Then we need to generate localization files. - -`$ dart run tool/generate_localization.dart` - -### 5. Build! - -`$ flutter build linux --release` - -Path to executable file will be: - -`build/linux/x64/release/bundle/cake_wallet` - -> ### Troubleshooting -> -> If you got an error while building the application with `$ flutter build linux --release` command, add `-v` argument to the command (`$ flutter build linux -v --release`) to get details.\ -> If you got in flutter build logs: undefined reference to `hid_free_enumeration`, or another error with undefined reference to `hid_*`, then rebuild monero lib without hidapi lib. Check does exists `libhidapi-dev` in your scope and remove it from your scope for build without it. - -# Flatpak - -For package the built application into flatpak you need firstly to install `flatpak` and `flatpak-builder`: - -`$ sudo apt install flatpak flatpak-builder` - -Then need to [add flathub](https://flatpak.org/setup/Ubuntu) (or just `$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo`). Then need to install freedesktop runtime and sdk: - -`$ flatpak install flathub org.freedesktop.Platform//22.08 org.freedesktop.Sdk//22.08` - -To build with using of `flatpak-build` directory run next: - -`$ flatpak-builder --force-clean flatpak-build com.cakewallet.CakeWallet.yml` - -And then export bundle: - -`$ flatpak build-export export flatpak-build` - -`$ flatpak build-bundle export cake_wallet.flatpak com.cakewallet.CakeWallet` - -Result file: `cake_wallet.flatpak` should be generated in the current directory. - -For install generated flatpak file use: - -`$ flatpak --user install cake_wallet.flatpak` - -For run the installed application run: - -`$ flatpak run com.cakewallet.CakeWallet` - -Copyright (c) 2023 Cake Technologies LLC. diff --git a/build-guide-win.md b/build-guide-win.md deleted file mode 100644 index 8cfd02c4c..000000000 --- a/build-guide-win.md +++ /dev/null @@ -1,38 +0,0 @@ -# Building CakeWallet for Windows - -## Requirements and Setup - -The following are the system requirements to build CakeWallet for your Windows PC. - -``` -Windows 10 or later (64-bit), x86-64 based -Flutter 3 or above -``` - -## Building CakeWallet on Windows - -These steps will help you configure and execute a build of CakeWallet from its source code. - -### 1. Installing Package Dependencies - -For build CakeWallet windows application from sources you will be needed to have: -> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windows`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. -> [Install WSL] for building monero dependencies need to install Windows WSL (https://learn.microsoft.com/en-us/windows/wsl/install) and required packages for WSL (Ubuntu): -`$ sudo apt update ` -`$ sudo apt build-essential cmake gcc-mingw-w64 g++-mingw-w64 autoconf libtool pkg-config` - -### 2. Pull CakeWallet source code - -You can download CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: -`$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart` -OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip) - -### 3. Build Monero, Monero_c and their dependencies - -For use monero in the application need to build Monero wrapper - Monero_C which will be used by monero.dart package. For that need to run shell (bash - typically same named utility should be available after WSL is enabled in your system) with previously installed WSL, then change current directory to the application project directory with your used shell and then change current directory to `scripts/windows`: `$ cd scripts/windows`. Run build script: `$ ./build_all.sh`. - -### 4. Configure and build CakeWallet application - -To configure the application open directory where you have downloaded or unarchived CakeWallet sources and run `cakewallet.bat`. -Or if you used WSL and have active shell session you can run `$ ./cakewallet.sh` script in `scripts/windows` which will run `cakewallet.bat` in WSL. -After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contains `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application. diff --git a/com.cakewallet.CakeWallet.yml b/com.cakewallet.CakeWallet.yml index 83efa1388..6a19c3dda 100644 --- a/com.cakewallet.CakeWallet.yml +++ b/com.cakewallet.CakeWallet.yml @@ -1,6 +1,6 @@ app-id: com.cakewallet.CakeWallet runtime: org.freedesktop.Platform -runtime-version: '22.08' +runtime-version: '24.08' sdk: org.freedesktop.Sdk command: cake_wallet separate-locales: false @@ -15,8 +15,6 @@ finish-args: modules: - name: cake_wallet buildsystem: simple - only-arches: - - x86_64 build-commands: - "cp -R bundle /app/cake_wallet" - "chmod +x /app/cake_wallet/cake_wallet" diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart index 73bc101c4..0d985b237 100644 --- a/cw_bitcoin/lib/address_from_output.dart +++ b/cw_bitcoin/lib/address_from_output.dart @@ -2,22 +2,37 @@ import 'package:bitcoin_base/bitcoin_base.dart'; String addressFromOutputScript(Script script, BasedUtxoNetwork network) { try { - 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: - } + return addressFromScript(script, network).toAddress(network); } 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/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 7e4b5f58f..1509f913a 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:mobx/mobx.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; @@ -16,7 +17,7 @@ abstract class BaseBitcoinAddressRecord { }) : _txCount = txCount, _balance = balance, _name = name, - _isUsed = isUsed; + _isUsed = Observable(isUsed); @override bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; @@ -27,7 +28,7 @@ abstract class BaseBitcoinAddressRecord { int _txCount; int _balance; String _name; - bool _isUsed; + final Observable _isUsed; BasedUtxoNetwork? network; int get txCount => _txCount; @@ -40,9 +41,9 @@ abstract class BaseBitcoinAddressRecord { set balance(int value) => _balance = value; - bool get isUsed => _isUsed; + bool get isUsed => _isUsed.value; - void setAsUsed() => _isUsed = true; + void setAsUsed() => _isUsed.value = true; void setNewName(String label) => _name = label; int get hashCode => address.hashCode; diff --git a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart index 01e905fb0..7d6894e14 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -3,11 +3,17 @@ 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}); + BitcoinTransactionCredentials( + this.outputs, { + required this.priority, + this.feeRate, + this.coinTypeToSpendFrom = UnspentCoinType.any, + this.payjoinUri, + }); final List outputs; final BitcoinTransactionPriority? priority; final int? feeRate; final UnspentCoinType coinTypeToSpendFrom; + final String? payjoinUri; } diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 908897845..9231022f6 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -3,22 +3,33 @@ import 'dart:convert'; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; -import 'package:cw_bitcoin/psbt_transaction_builder.dart'; -import 'package:cw_core/encryption_file_utils.dart'; -import 'package:cw_bitcoin/electrum_derivations.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin/payjoin/manager.dart'; +import 'package:cw_bitcoin/payjoin/storage.dart'; +import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; +import 'package:cw_bitcoin/psbt/signer.dart'; +import 'package:cw_bitcoin/psbt/transaction_builder.dart'; +import 'package:cw_bitcoin/psbt/v0_deserialize.dart'; +import 'package:cw_bitcoin/psbt/v0_finalizer.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/payjoin_session.dart'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart'; +import 'package:ledger_bitcoin/psbt.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; @@ -31,6 +42,7 @@ 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, @@ -71,20 +83,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { // String derivationPath = walletInfo.derivationInfo!.derivationPath!; // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1"; // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); - 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(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); autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = @@ -92,11 +105,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { }); } + @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, @@ -119,9 +136,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { break; case DerivationType.electrum: default: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = + await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); break; } + return BitcoinWallet( mnemonic: mnemonic, passphrase: passphrase ?? "", @@ -138,6 +157,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialChangeAddressIndex: initialChangeAddressIndex, addressPageType: addressPageType, networkParam: network, + payjoinBox: payjoinBox, ); } @@ -145,6 +165,7 @@ 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, @@ -201,7 +222,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { if (mnemonic != null) { switch (walletInfo.derivationInfo!.derivationType) { case DerivationType.electrum: - seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); + seedBytes = + await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); break; case DerivationType.bip39: default: @@ -214,24 +236,24 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } 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, - ); + 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); } LedgerConnection? _ledgerConnection; @@ -245,19 +267,30 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } @override - Future buildHardwareWalletTransaction({ + 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 masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); - final psbtReadyInputs = []; for (final utxo in utxos) { final rawTx = @@ -275,13 +308,128 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { )); } - final psbt = PSBTTransactionBuild( - inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); + return PSBTTransactionBuild( + inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF) + .psbt; + } - final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt); + @override + Future buildHardwareWalletTransaction({ + required List outputs, + required BigInt fee, + required BasedUtxoNetwork network, + required List utxos, + required Map publicKeys, + String? memo, + bool enableRBF = false, + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, + }) async { + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(); + + final psbt = await buildPsbt( + outputs: outputs, + fee: fee, + network: network, + utxos: utxos, + publicKeys: publicKeys, + masterFingerprint: masterFingerprint, + memo: memo, + enableRBF: enableRBF, + inputOrdering: inputOrdering, + outputOrdering: outputOrdering, + ); + + final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt); return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); } + @override + Future createTransaction(Object credentials) async { + credentials = credentials as BitcoinTransactionCredentials; + + final tx = (await super.createTransaction(credentials)) + as PendingBitcoinTransaction; + + final payjoinUri = credentials.payjoinUri; + if (payjoinUri == null) return tx; + + final transaction = await buildPsbt( + utxos: tx.utxos, + outputs: tx.outputs + .map((e) => BitcoinOutput( + address: addressFromScript(e.scriptPubKey), + value: e.amount, + isSilentPayment: e.isSilentPayment, + isChange: e.isChange, + )) + .toList(), + fee: BigInt.from(tx.fee), + network: network, + memo: credentials.outputs.first.memo, + outputOrdering: BitcoinOrdering.none, + enableRBF: true, + publicKeys: tx.publicKeys!, + masterFingerprint: Uint8List(0)); + + final originalPsbt = await signPsbt( + base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys()); + + tx.commitOverride = () async { + final sender = await payjoinManager.initSender( + payjoinUri, originalPsbt, int.parse(tx.feeRate)); + payjoinManager.spawnNewSender( + sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); + }; + + return tx; + } + + List getUtxoWithPrivateKeys() => unspentCoins + .where((e) => (e.isSending && !e.isFrozen)) + .map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this)) + .toList(); + + Future commitPsbt(String finalizedPsbt) { + final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt)); + + final btcTx = + BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract())); + + return PendingBitcoinTransaction( + btcTx, + type, + electrumClient: electrumClient, + amount: 0, + fee: 0, + feeRate: "", + network: network, + hasChange: true, + ).commit(); + } + + Future signPsbt( + String preProcessedPsbt, List utxos) async { + final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt)); + + await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) { + return utxo.utxo.isP2tr() + ? key.signTapRoot( + txDigest, + sighash: sighash, + tweak: utxo.utxo.isSilentPayment != true, + ) + : key.signInput(txDigest, sigHash: sighash); + }, (txId, vout) async { + final txHex = await electrumClient.getTransactionHex(hash: txId); + final output = BtcTransaction.fromRaw(txHex).outputs[vout]; + return TaprootAmountScriptPair(output.amount, output.scriptPubKey); + }); + + psbt.finalizeV0(); + return base64Encode(psbt.asPsbtV0()); + } + @override Future signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 1a122ef9e..d84d958be 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,10 +1,13 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/bip/bip/bip32/bip32.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'; @@ -17,6 +20,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required super.sideHd, required super.network, required super.isHardwareWallet, + required this.payjoinManager, super.initialAddresses, super.initialRegularAddressIndex, super.initialChangeAddressIndex, @@ -25,6 +29,13 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S super.masterHd, }) : super(walletInfo); + final PayjoinManager payjoinManager; + + payjoin.Receiver? currentPayjoinReceiver; + + @observable + String? payjoinEndpoint = null; + @override String getAddress( {required int index, @@ -45,4 +56,33 @@ 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_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 7ee1534bf..317b25bcd 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -5,6 +5,7 @@ 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'; @@ -21,10 +22,12 @@ class BitcoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials, BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect); + BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, + this.payjoinSessionSource, this.alwaysScan, this.isDirect); final Box walletInfoSource; final Box unspentCoinsInfoSource; + final Box payjoinSessionSource; final bool alwaysScan; final bool isDirect; @@ -55,6 +58,7 @@ class BitcoinWalletService extends WalletService< passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, network: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -79,6 +83,7 @@ class BitcoinWalletService extends WalletService< name: name, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, alwaysScan: alwaysScan, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -92,6 +97,7 @@ class BitcoinWalletService extends WalletService< name: name, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, alwaysScan: alwaysScan, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -126,6 +132,7 @@ class BitcoinWalletService extends WalletService< name: currentName, walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, alwaysScan: alwaysScan, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -147,7 +154,6 @@ class BitcoinWalletService extends WalletService< credentials.walletInfo?.network = network.value; credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath; - final wallet = await BitcoinWallet( password: credentials.password!, xpub: credentials.hwAccountData.xpub, @@ -155,6 +161,7 @@ class BitcoinWalletService extends WalletService< unspentCoinsInfo: unspentCoinsInfoSource, networkParam: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), + payjoinBox: payjoinSessionSource, ); await wallet.save(); await wallet.init(); @@ -182,6 +189,7 @@ class BitcoinWalletService extends WalletService< mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, + payjoinBox: payjoinSessionSource, network: network, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 1f5c369e3..2ddd30df6 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -5,6 +5,8 @@ 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:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; @@ -42,7 +44,7 @@ class ElectrumClient { static const aliveTimerDuration = Duration(seconds: 4); bool get isConnected => _isConnected; - Socket? socket; + ProxySocket? socket; void Function(ConnectionStatus)? onConnectionStatusChange; int _id; final Map _tasks; @@ -72,18 +74,11 @@ class ElectrumClient { } catch (_) {} socket = null; + final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))); try { - if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { - socket = await Socket.connect(host, port, timeout: connectionTimeout); - } else { - socket = await SecureSocket.connect( - host, - port, - timeout: connectionTimeout, - onBadCertificate: (_) => true, - ); - } + socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout); } catch (e) { + printV("connect: $e"); if (e is HandshakeException) { useSSL = !(useSSL ?? false); } @@ -105,7 +100,6 @@ class ElectrumClient { // use ping to determine actual connection status since we could've just not timed out yet: // _setConnectionStatus(ConnectionStatus.connected); - socket!.listen( (Uint8List event) { try { diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index 37c34058b..aeb06f1f0 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -65,6 +65,6 @@ class ElectrumBalance extends Balance { 'unconfirmed': unconfirmed, 'frozen': frozen, 'secondConfirmed': secondConfirmed, - 'secondUnconfirmed': secondUnconfirmed + 'secondUnconfirmed': secondUnconfirmed, }); } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 64595b253..bb9cea1bc 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,6 +4,9 @@ import 'dart:io'; import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_core/format_amount.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart'; @@ -47,7 +50,6 @@ import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; import 'package:hex/hex.dart'; -import 'package:http/http.dart' as http; part 'electrum_wallet.g.dart'; @@ -491,10 +493,9 @@ abstract class ElectrumWalletBase Future updateFeeRates() async { if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) { try { - final response = await http - .get(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended")) - .timeout(Duration(seconds: 5)); - + 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; @@ -632,8 +633,8 @@ abstract class ElectrumWalletBase }).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); - // sort the unconfirmed coins so that mweb coins are first: - availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1); + // sort the unconfirmed coins so that mweb coins are last: + availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1); for (int i = 0; i < availableInputs.length; i++) { final utx = availableInputs[i]; @@ -1174,19 +1175,18 @@ abstract class ElectrumWalletBase } }); - 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, - )..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, + utxos: estimatedTx.utxos, + publicKeys: estimatedTx.publicKeys) + ..addListener((transaction) async { transactionHistory.addOne(transaction); if (estimatedTx.spendsSilentPayment) { transactionHistory.transactions.values.forEach((tx) { @@ -1877,20 +1877,17 @@ abstract class ElectrumWalletBase if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) { try { - final blockHash = await http.get( - Uri.parse( - "https://mempool.cakewallet.com/api/v1/block-height/$height", - ), - ); + final blockHash = await ProxyWrapper() + .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height")) + .timeout(Duration(seconds: 15)); if (blockHash.statusCode == 200 && blockHash.body.isNotEmpty && jsonDecode(blockHash.body) != null) { - final blockResponse = await http.get( - Uri.parse( - "https://mempool.cakewallet.com/api/v1/block/${blockHash.body}", - ), - ); + 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) { @@ -1963,6 +1960,11 @@ abstract class ElectrumWalletBase } } + bool isMine(Script script) { + final derivedAddress = addressFromOutputScript(script, network); + return addressesSet.contains(derivedAddress); + } + @override Future> fetchTransactions() async { try { @@ -2240,10 +2242,11 @@ abstract class ElectrumWalletBase if (element.hash == info.hash && element.vout == info.vout && - info.isFrozen && element.bitcoinAddressRecord.address == info.address && element.value == info.value) { - totalFrozen += element.value; + if (info.isFrozen) { + totalFrozen += element.value; + } } }); }); @@ -2499,6 +2502,12 @@ abstract class ElectrumWalletBase transactionHistory.addOne(tx); } } + + @override + String formatCryptoAmount(String amount) { + final amountInt = int.parse(amount); + return bitcoinAmountToString(amount: amountInt); + } } class ScanNode { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 35c15e578..614a06a3b 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -144,27 +144,32 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return silentAddress.toString(); } - String receiveAddress; + final typeMatchingAddresses = _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList(); + final typeMatchingReceiveAddresses = typeMatchingAddresses.where((addr) => !addr.isUsed).toList(); - 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; + if (!isEnabledAutoGenerateSubaddress) { + if (previousAddressRecord != null && + previousAddressRecord!.type == addressPageType) { + return previousAddressRecord!.address; } + + if (typeMatchingAddresses.isNotEmpty) { + return typeMatchingAddresses.first.address; + } + + return generateNewAddress().address; } - return receiveAddress; + if (typeMatchingAddresses.isEmpty || typeMatchingReceiveAddresses.isEmpty) { + return generateNewAddress().address; + } + + final prev = previousAddressRecord; + if (prev != null && prev.type == addressPageType && !prev.isUsed) { + return prev.address; + } + + return typeMatchingReceiveAddresses.first.address; } @observable diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 5bd25785f..08c56c600 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; import 'package:convert/convert.dart' as convert; import 'dart:math'; @@ -77,6 +76,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { }) : super( mnemonic: mnemonic, password: password, + passphrase: passphrase, xpub: xpub, walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfo, @@ -155,6 +155,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @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; @@ -326,7 +329,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { try { await subscribeForUpdates(); } catch (e) { - printV("failed to subcribe for updates: $e"); + printV("failed to subscribe for updates: $e"); } updateFeeRates(); _feeRatesTimer?.cancel(); @@ -461,7 +464,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final oldBox = await CakeHive.openBox(oldBoxName); mwebUtxosBox = await CakeHive.openBox(newBoxName); for (final key in oldBox.keys) { - await mwebUtxosBox.put(key, oldBox.get(key)!); + final value = oldBox.get(key); + await oldBox.delete(key); + await mwebUtxosBox.put(key, value!); } oldBox.deleteFromDisk(); @@ -598,7 +603,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } _utxoStream = responseStream.listen( (Utxo sUtxo) async { - // we're processing utxos, so our balance could still be innacurate: + // we're processing utxos, so our balance could still be inaccurate: if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) { mwebSyncStatus = SyncronizingSyncStatus(); processingUtxos = true; diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart new file mode 100644 index 000000000..95a523d89 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/manager.dart @@ -0,0 +1,310 @@ +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 new file mode 100644 index 000000000..4e395e36a --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_persister.dart @@ -0,0 +1,66 @@ +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 new file mode 100644 index 000000000..c56148de2 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart @@ -0,0 +1,222 @@ +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 new file mode 100644 index 000000000..7e85cc773 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart @@ -0,0 +1,120 @@ +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 new file mode 100644 index 000000000..06e0a5431 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/payjoin_session_errors.dart @@ -0,0 +1,16 @@ +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 new file mode 100644 index 000000000..5fb9d5716 --- /dev/null +++ b/cw_bitcoin/lib/payjoin/storage.dart @@ -0,0 +1,104 @@ +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 411c7de16..6930524eb 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,3 +1,4 @@ +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'; @@ -25,6 +26,8 @@ class PendingBitcoinTransaction with PendingTransaction { this.hasTaprootInputs = false, this.isMweb = false, this.utxos = const [], + this.publicKeys, + this.commitOverride, }) : _listeners = []; final WalletType type; @@ -43,6 +46,8 @@ class PendingBitcoinTransaction with PendingTransaction { String? idOverride; String? hexOverride; List? outputAddresses; + final Map? publicKeys; + Future Function()? commitOverride; @override String get id => idOverride ?? _tx.txId(); @@ -129,6 +134,10 @@ class PendingBitcoinTransaction with PendingTransaction { @override Future commit() async { + if (commitOverride != null) { + return commitOverride?.call(); + } + if (isMweb) { await _ltcCommit(); } else { diff --git a/cw_bitcoin/lib/psbt/signer.dart b/cw_bitcoin/lib/psbt/signer.dart new file mode 100644 index 000000000..1d0ceba8b --- /dev/null +++ b/cw_bitcoin/lib/psbt/signer.dart @@ -0,0 +1,263 @@ +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