Merge branch 'main' into main

This commit is contained in:
GiMa-Maya 2024-11-17 21:06:07 +01:00 committed by GitHub
commit 3b4df45ee5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
184 changed files with 8474 additions and 1182 deletions

View file

@ -62,10 +62,22 @@ jobs:
/opt/android/cake_wallet/cw_haven/android/.cxx /opt/android/cake_wallet/cw_haven/android/.cxx
/opt/android/cake_wallet/scripts/monero_c/release /opt/android/cake_wallet/scripts/monero_c/release
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
name: Generate Externals name: Generate Externals
run: | run: |
cd /opt/android/cake_wallet/scripts/android/ cd /opt/android/cake_wallet/scripts/android/
source ./app_env.sh cakewallet source ./app_env.sh cakewallet
./build_monero_all.sh ./build_monero_all.sh
- name: Cache Keystore
id: cache-keystore
uses: actions/cache@v3
with:
path: /opt/android/cake_wallet/android/app/key.jks
key: $STORE_PASS
- if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
name: Generate KeyStore
run: |
cd /opt/android/cake_wallet/android/app
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS

View file

@ -115,6 +115,14 @@ jobs:
cd /opt/android/cake_wallet/scripts/android/ cd /opt/android/cake_wallet/scripts/android/
./build_mwebd.sh --dont-install ./build_mwebd.sh --dont-install
# - name: Cache Keystore
# id: cache-keystore
# uses: actions/cache@v3
# with:
# path: /opt/android/cake_wallet/android/app/key.jks
# key: $STORE_PASS
#
# - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
- name: Generate KeyStore - name: Generate KeyStore
run: | run: |
cd /opt/android/cake_wallet/android/app cd /opt/android/cake_wallet/android/app
@ -192,6 +200,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
@ -201,6 +211,36 @@ jobs:
run: | run: |
echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
# Step 3: Download previous build number
- name: Download previous build number
id: download-build-number
run: |
# Download the artifact if it exists
if [[ ! -f build_number.txt ]]; then
echo "1" > build_number.txt
fi
# Step 4: Read and Increment Build Number
- name: Increment Build Number
id: increment-build-number
run: |
# Read current build number from file
BUILD_NUMBER=$(cat build_number.txt)
BUILD_NUMBER=$((BUILD_NUMBER + 1))
echo "New build number: $BUILD_NUMBER"
# Save the incremented build number
echo "$BUILD_NUMBER" > build_number.txt
# Export the build number to use in later steps
echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV
# Step 5: Update pubspec.yaml with new build number
- name: Update build number
run: |
cd /opt/android/cake_wallet
sed -i "s/^version: .*/version: 1.0.$BUILD_NUMBER/" pubspec.yaml
- name: Build - name: Build
run: | run: |
cd /opt/android/cake_wallet cd /opt/android/cake_wallet
@ -231,6 +271,13 @@ jobs:
with: with:
path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/ path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/
# Re-upload updated build number for the next run
- name: Upload updated build number
uses: actions/upload-artifact@v3
with:
name: build_number
path: build_number.txt
- name: Send Test APK - name: Send Test APK
continue-on-error: true continue-on-error: true
uses: adrey/slack-file-upload-action@1.0.5 uses: adrey/slack-file-upload-action@1.0.5
@ -241,3 +288,4 @@ jobs:
title: "${{ env.BRANCH_NAME }}.apk" title: "${{ env.BRANCH_NAME }}.apk"
filename: ${{ env.BRANCH_NAME }}.apk filename: ${{ env.BRANCH_NAME }}.apk
initial_comment: ${{ github.event.head_commit.message }} initial_comment: ${{ github.event.head_commit.message }}

View file

@ -175,6 +175,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart

View file

@ -92,3 +92,8 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
} }
configurations {
implementation.exclude module:'proto-google-common-protos'
implementation.exclude module:'protolite-well-known-types'
implementation.exclude module:'protobuf-javalite'
}

View file

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx3072M
android.enableR8=true android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<defs>
</defs>
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
<path d="M 24.144 34.985 c -0.77 0.912 -2.003 1.631 -3.236 1.528 c -0.154 -1.233 0.449 -2.542 1.156 -3.351 c 0.77 -0.937 2.119 -1.605 3.21 -1.656 C 25.402 32.789 24.902 34.047 24.144 34.985 M 25.261 36.757 c -1.785 -0.103 -3.313 1.014 -4.16 1.014 c -0.86 0 -2.157 -0.963 -3.57 -0.937 c -1.836 0.026 -3.544 1.066 -4.481 2.722 c -1.926 3.313 -0.501 8.218 1.361 10.914 c 0.912 1.335 2.003 2.799 3.441 2.748 c 1.361 -0.051 1.9 -0.886 3.544 -0.886 c 1.656 0 2.131 0.886 3.57 0.86 c 1.489 -0.026 2.427 -1.335 3.338 -2.671 c 1.04 -1.515 1.464 -2.992 1.489 -3.069 c -0.026 -0.026 -2.876 -1.117 -2.902 -4.404 c -0.026 -2.748 2.247 -4.057 2.35 -4.134 C 27.958 37.014 25.954 36.808 25.261 36.757 M 35.572 33.033 v 20.018 h 3.107 v -6.844 h 4.301 c 3.929 0 6.69 -2.696 6.69 -6.6 s -2.709 -6.574 -6.587 -6.574 H 35.572 L 35.572 33.033 z M 38.679 35.652 h 3.582 c 2.696 0 4.237 1.438 4.237 3.968 c 0 2.529 -1.541 3.98 -4.25 3.98 h -3.57 V 35.652 z M 55.345 53.205 c 1.952 0 3.762 -0.989 4.584 -2.555 h 0.064 v 2.401 h 2.876 v -9.964 c 0 -2.889 -2.311 -4.751 -5.868 -4.751 c -3.3 0 -5.739 1.887 -5.829 4.481 h 2.799 c 0.231 -1.233 1.374 -2.042 2.94 -2.042 c 1.9 0 2.966 0.886 2.966 2.517 v 1.104 L 56 44.628 c -3.608 0.218 -5.56 1.695 -5.56 4.263 C 50.44 51.484 52.456 53.205 55.345 53.205 z M 56.18 50.829 c -1.656 0 -2.709 -0.796 -2.709 -2.016 c 0 -1.258 1.014 -1.99 2.953 -2.106 l 3.454 -0.218 v 1.13 C 59.878 49.494 58.286 50.829 56.18 50.829 z M 66.709 58.495 c 3.03 0 4.455 -1.156 5.701 -4.661 l 5.457 -15.305 h -3.159 l -3.659 11.826 h -0.064 l -3.659 -11.826 h -3.249 l 5.264 14.573 l -0.282 0.886 c -0.475 1.502 -1.245 2.08 -2.619 2.08 c -0.244 0 -0.719 -0.026 -0.912 -0.051 v 2.401 C 65.707 58.469 66.478 58.495 66.709 58.495 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: white; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 45 90 C 20.187 90 0 69.813 0 45 C 0 20.187 20.187 0 45 0 c 24.813 0 45 20.187 45 45 C 90 69.813 69.813 90 45 90 z M 45 3 C 21.841 3 3 21.841 3 45 c 0 23.159 18.841 42 42 42 c 23.159 0 42 -18.841 42 -42 C 87 21.841 68.159 3 45 3 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: white; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<defs>
</defs>
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
<path d="M 24.144 34.985 c -0.77 0.912 -2.003 1.631 -3.236 1.528 c -0.154 -1.233 0.449 -2.542 1.156 -3.351 c 0.77 -0.937 2.119 -1.605 3.21 -1.656 C 25.402 32.789 24.902 34.047 24.144 34.985 M 25.261 36.757 c -1.785 -0.103 -3.313 1.014 -4.16 1.014 c -0.86 0 -2.157 -0.963 -3.57 -0.937 c -1.836 0.026 -3.544 1.066 -4.481 2.722 c -1.926 3.313 -0.501 8.218 1.361 10.914 c 0.912 1.335 2.003 2.799 3.441 2.748 c 1.361 -0.051 1.9 -0.886 3.544 -0.886 c 1.656 0 2.131 0.886 3.57 0.86 c 1.489 -0.026 2.427 -1.335 3.338 -2.671 c 1.04 -1.515 1.464 -2.992 1.489 -3.069 c -0.026 -0.026 -2.876 -1.117 -2.902 -4.404 c -0.026 -2.748 2.247 -4.057 2.35 -4.134 C 27.958 37.014 25.954 36.808 25.261 36.757 M 35.572 33.033 v 20.018 h 3.107 v -6.844 h 4.301 c 3.929 0 6.69 -2.696 6.69 -6.6 s -2.709 -6.574 -6.587 -6.574 H 35.572 L 35.572 33.033 z M 38.679 35.652 h 3.582 c 2.696 0 4.237 1.438 4.237 3.968 c 0 2.529 -1.541 3.98 -4.25 3.98 h -3.57 V 35.652 z M 55.345 53.205 c 1.952 0 3.762 -0.989 4.584 -2.555 h 0.064 v 2.401 h 2.876 v -9.964 c 0 -2.889 -2.311 -4.751 -5.868 -4.751 c -3.3 0 -5.739 1.887 -5.829 4.481 h 2.799 c 0.231 -1.233 1.374 -2.042 2.94 -2.042 c 1.9 0 2.966 0.886 2.966 2.517 v 1.104 L 56 44.628 c -3.608 0.218 -5.56 1.695 -5.56 4.263 C 50.44 51.484 52.456 53.205 55.345 53.205 z M 56.18 50.829 c -1.656 0 -2.709 -0.796 -2.709 -2.016 c 0 -1.258 1.014 -1.99 2.953 -2.106 l 3.454 -0.218 v 1.13 C 59.878 49.494 58.286 50.829 56.18 50.829 z M 66.709 58.495 c 3.03 0 4.455 -1.156 5.701 -4.661 l 5.457 -15.305 h -3.159 l -3.659 11.826 h -0.064 l -3.659 -11.826 h -3.249 l 5.264 14.573 l -0.282 0.886 c -0.475 1.502 -1.245 2.08 -2.619 2.08 c -0.244 0 -0.719 -0.026 -0.912 -0.051 v 2.401 C 65.707 58.469 66.478 58.495 66.709 58.495 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 45 90 C 20.187 90 0 69.813 0 45 C 0 20.187 20.187 0 45 0 c 24.813 0 45 20.187 45 45 C 90 69.813 69.813 90 45 90 z M 45 3 C 21.841 3 3 21.841 3 45 c 0 23.159 18.841 42 42 42 c 23.159 0 42 -18.841 42 -42 C 87 21.841 68.159 3 45 3 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/images/bank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<defs>
</defs>
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: white; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
<path d="M 84.668 38.004 v -6.27 H 90 V 20 L 45 3.034 L 0 20 v 11.734 h 5.332 v 6.27 h 4.818 v 30.892 H 5.332 v 6.271 H 0 v 11.8 h 90 v -11.8 h -5.332 v -6.271 H 79.85 V 38.004 H 84.668 z M 81.668 35.004 H 66.332 v -3.27 h 15.336 V 35.004 z M 63.332 68.896 v 6.271 h -7.664 v -6.271 H 50.85 V 38.004 h 4.818 v -6.27 h 7.664 v 6.27 h 4.818 v 30.892 H 63.332 z M 26.668 38.004 v -6.27 h 7.664 v 6.27 h 4.818 v 30.892 h -4.818 v 6.271 h -7.664 v -6.271 H 21.85 V 38.004 H 26.668 z M 42.15 68.896 V 38.004 h 5.7 v 30.892 H 42.15 z M 37.332 35.004 v -3.27 h 15.336 v 3.27 H 37.332 z M 37.332 71.896 h 15.336 v 3.271 H 37.332 V 71.896 z M 3 22.075 L 45 6.24 l 42 15.835 v 6.659 H 3 V 22.075 z M 8.332 31.734 h 15.336 v 3.27 H 8.332 V 31.734 z M 13.15 38.004 h 5.7 v 30.892 h -5.7 V 38.004 z M 8.332 71.896 h 15.336 v 3.271 H 8.332 V 71.896 z M 87 83.966 H 3 v -5.8 h 84 V 83.966 z M 81.668 75.166 H 66.332 v -3.271 h 15.336 V 75.166 z M 76.85 68.896 H 71.15 V 38.004 h 5.699 V 68.896 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: white; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<defs>
</defs>
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: black; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
<path d="M 84.668 38.004 v -6.27 H 90 V 20 L 45 3.034 L 0 20 v 11.734 h 5.332 v 6.27 h 4.818 v 30.892 H 5.332 v 6.271 H 0 v 11.8 h 90 v -11.8 h -5.332 v -6.271 H 79.85 V 38.004 H 84.668 z M 81.668 35.004 H 66.332 v -3.27 h 15.336 V 35.004 z M 63.332 68.896 v 6.271 h -7.664 v -6.271 H 50.85 V 38.004 h 4.818 v -6.27 h 7.664 v 6.27 h 4.818 v 30.892 H 63.332 z M 26.668 38.004 v -6.27 h 7.664 v 6.27 h 4.818 v 30.892 h -4.818 v 6.271 h -7.664 v -6.271 H 21.85 V 38.004 H 26.668 z M 42.15 68.896 V 38.004 h 5.7 v 30.892 H 42.15 z M 37.332 35.004 v -3.27 h 15.336 v 3.27 H 37.332 z M 37.332 71.896 h 15.336 v 3.271 H 37.332 V 71.896 z M 3 22.075 L 45 6.24 l 42 15.835 v 6.659 H 3 V 22.075 z M 8.332 31.734 h 15.336 v 3.27 H 8.332 V 31.734 z M 13.15 38.004 h 5.7 v 30.892 h -5.7 V 38.004 z M 8.332 71.896 h 15.336 v 3.271 H 8.332 V 71.896 z M 87 83.966 H 3 v -5.8 h 84 V 83.966 z M 81.668 75.166 H 66.332 v -3.271 h 15.336 V 75.166 z M 76.85 68.896 H 71.15 V 38.004 h 5.699 V 68.896 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: black; fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/images/buy_sell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

1
assets/images/card.svg Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="iso-8859-1"?><!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50" width="100px" height="100px"><path style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;" d="M43,40H7c-2.209,0-4-1.791-4-4V14c0-2.209,1.791-4,4-4h36c2.209,0,4,1.791,4,4v22C47,38.209,45.209,40,43,40z"/><rect x="3" y="16" width="44" height="5"/><line style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:10;" x1="9" y1="25" x2="25" y2="25"/></svg>

After

Width:  |  Height:  |  Size: 633 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50" width="100px" height="100px">
<path style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;" d="M43,40H7c-2.209,0-4-1.791-4-4V14c0-2.209,1.791-4,4-4h36c2.209,0,4,1.791,4,4v22C47,38.209,45.209,40,43,40z"/>
<rect x="3" y="16" width="44" height="5" style="fill:white;stroke:#ffffff;stroke-width:2;"/>
<line style="fill:none;stroke:#ffffff;stroke-width:2;stroke-miterlimit:10;" x1="9" y1="25" x2="25" y2="25"/>
</svg>

After

Width:  |  Height:  |  Size: 701 B

View file

@ -0,0 +1,12 @@
<?xml version="1.0" ?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<g id="Outline">
<g data-name="Outline" id="Outline-2">
<path d="M39,36.852a6.8,6.8,0,0,0-6.793-6.793h-.319A3.716,3.716,0,1,1,35.6,26.344a1,1,0,0,0,2,0,5.725,5.725,0,0,0-4.561-5.6V18.09a1,1,0,0,0-2,0V20.7a5.712,5.712,0,0,0,.846,11.361h.319a4.793,4.793,0,1,1-4.793,4.793,1,1,0,0,0-2,0A6.8,6.8,0,0,0,31.451,43.6v2.947a1,1,0,0,0,2,0v-3c0-.008,0-.014,0-.021A6.8,6.8,0,0,0,39,36.852Z"/>
<path d="M32,2A30,30,0,1,0,62,32,30.034,30.034,0,0,0,32,2Zm0,58A28,28,0,1,1,60,32,28.032,28.032,0,0,1,32,60Z"/>
<path d="M49.655,16.793a3.172,3.172,0,1,0-3.172,3.172,3.137,3.137,0,0,0,1.263-.266A19.994,19.994,0,0,1,22.692,49.707a1,1,0,0,0-.933,1.769,21.986,21.986,0,0,0,27.47-33.124A3.141,3.141,0,0,0,49.655,16.793Zm-4.344,0a1.172,1.172,0,1,1,1.172,1.172A1.172,1.172,0,0,1,45.311,16.793Z"/>
<path d="M16.793,44.035a3.164,3.164,0,0,0-.692.081A19.779,19.779,0,0,1,12,32,20.023,20.023,0,0,1,32,12a19.811,19.811,0,0,1,8.463,1.874,1,1,0,0,0,.848-1.812A21.989,21.989,0,0,0,14.39,45.16a3.141,3.141,0,0,0-.769,2.047,3.172,3.172,0,1,0,3.172-3.172Zm0,4.344a1.172,1.172,0,1,1,1.172-1.172A1.172,1.172,0,0,1,16.793,48.379Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,30 @@
<svg width="40" height="42" viewBox="0 0 40 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.7" d="M19.9526 41.9076L7.3877 34.655V26.1226L19.9526 33.3751V41.9076Z" fill="url(#paint0_linear_2113_32117)"/>
<path opacity="0.7" d="M19.9521 41.9076L32.5171 34.655V26.1226L19.9521 33.3751V41.9076Z" fill="url(#paint1_linear_2113_32117)"/>
<path opacity="0.7" d="M39.9095 7.34521V21.8562L32.5166 26.1225V11.6114L39.9095 7.34521Z" fill="url(#paint2_linear_2113_32117)"/>
<path d="M39.9099 7.34536L27.345 0.0927734L19.9521 4.359L32.5171 11.6116L39.9099 7.34536Z" fill="url(#paint3_linear_2113_32117)"/>
<path d="M0 7.34536L12.5649 0.0927734L19.9519 4.359L7.387 11.6116L0 7.34536Z" fill="#F969D3"/>
<path opacity="0.7" d="M0 7.34521V21.8562L7.387 26.1225V11.6114L0 7.34521Z" fill="url(#paint4_linear_2113_32117)"/>
<defs>
<linearGradient id="paint0_linear_2113_32117" x1="18.6099" y1="41.8335" x2="7.73529" y2="8.31842" gradientUnits="userSpaceOnUse">
<stop stop-color="#E98ADA"/>
<stop offset="1" stop-color="#7E4DBD"/>
</linearGradient>
<linearGradient id="paint1_linear_2113_32117" x1="26.2346" y1="26.1226" x2="26.2346" y2="41.9076" gradientUnits="userSpaceOnUse">
<stop stop-color="#719DED"/>
<stop offset="1" stop-color="#2545BE"/>
</linearGradient>
<linearGradient id="paint2_linear_2113_32117" x1="36.213" y1="7.34521" x2="36.213" y2="26.1225" gradientUnits="userSpaceOnUse">
<stop stop-color="#93EBFF"/>
<stop offset="1" stop-color="#197DDB"/>
</linearGradient>
<linearGradient id="paint3_linear_2113_32117" x1="29.931" y1="0.0927734" x2="38.2156" y2="14.8448" gradientUnits="userSpaceOnUse">
<stop stop-color="#F969D3"/>
<stop offset="1" stop-color="#4F51C0"/>
</linearGradient>
<linearGradient id="paint4_linear_2113_32117" x1="18.1251" y1="44.2539" x2="-7.06792" y2="15.2763" gradientUnits="userSpaceOnUse">
<stop stop-color="#E98ADA"/>
<stop offset="1" stop-color="#7E4DBD"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/images/revolut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

15
assets/images/skrill.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
<defs>
</defs>
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
<circle cx="45" cy="45" r="45" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(127,33,99); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
<polygon points="69.59,36.9 69.59,54.86 74.5,54.86 74.5,36.02 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
<polygon points="62.42,36.9 67.33,36.02 67.33,54.87 62.42,54.87 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
<rect x="55.43" y="41.08" rx="0" ry="0" width="4.91" height="13.78" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
<path d="M 57.879 39.76 c 1.332 0 2.425 -1.082 2.425 -2.415 s -1.082 -2.425 -2.425 -2.425 c -1.332 0 -2.415 1.082 -2.415 2.425 C 55.465 38.677 56.547 39.76 57.879 39.76 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 52.665 40.884 c -4.538 0.146 -6.838 2.186 -6.838 6.234 v 7.754 h 4.954 v -6.328 c 0 -2.425 0.312 -3.466 3.195 -3.559 v -4.038 C 53.477 40.853 52.665 40.884 52.665 40.884 L 52.665 40.884 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 39.344 41.071 c -0.104 0.271 -0.895 2.498 -2.8 4.798 v -9.845 l -5.068 0.999 v 17.838 h 5.068 v -5.516 c 1.467 2.206 2.196 5.516 2.196 5.516 h 6.068 c -0.604 -2.498 -3.226 -7.098 -3.226 -7.098 c 2.352 -2.987 3.393 -6.172 3.559 -6.702 h -5.797 L 39.344 41.071 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
<path d="M 22.973 43.028 c -0.624 -0.042 -2.061 -0.135 -2.061 -1.426 c 0 -1.561 2.071 -1.561 2.841 -1.561 c 1.363 0 3.133 0.406 4.392 0.781 c 0 0 0.708 0.25 1.301 0.5 l 0.052 0.01 v -4.267 l -0.073 -0.021 c -1.488 -0.52 -3.216 -1.02 -6.432 -1.02 c -5.537 0 -7.493 3.226 -7.493 5.984 c 0 1.592 0.687 5.339 7.025 5.776 c 0.541 0.031 1.967 0.114 1.967 1.457 c 0 1.103 -1.166 1.759 -3.133 1.759 c -2.154 0 -4.236 -0.552 -5.506 -1.072 v 4.402 c 1.894 0.5 4.038 0.749 6.546 0.749 c 5.412 0 7.837 -3.049 7.837 -6.078 C 30.237 45.567 27.531 43.34 22.973 43.028 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(249,249,249); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512">
<path d="M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12,12-5.383,12-12S18.617,0,12,0Zm0,23c-6.065,0-11-4.935-11-11S5.935,1,12,1s11,4.935,11,11-4.935,11-11,11Zm4-8.626c0,1.448-1.178,2.626-2.626,2.626h-.874v1.5c0,.276-.224,.5-.5,.5s-.5-.224-.5-.5v-1.5h-.926c-.979,0-1.893-.526-2.382-1.375-.139-.239-.057-.545,.183-.683,.238-.14,.544-.057,.683,.183,.312,.54,.894,.875,1.517,.875h2.8c.896,0,1.626-.729,1.626-1.626,0-.803-.575-1.478-1.368-1.605l-3.422-.55c-1.28-.206-2.21-1.296-2.21-2.593,0-1.448,1.178-2.626,2.626-2.626h.874v-1.5c0-.276,.224-.5,.5-.5s.5,.224,.5,.5v1.5h.926c.979,0,1.892,.527,2.382,1.375,.139,.239,.057,.545-.183,.683-.236,.136-.545,.057-.683-.183-.312-.54-.894-.875-1.517-.875h-2.8c-.896,0-1.626,.729-1.626,1.626,0,.803,.575,1.478,1.368,1.605l3.422,.55c1.28,.206,2.21,1.297,2.21,2.593Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 978 B

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M12,0C5.383,0,0,5.383,0,12s5.383,12,12,12,12-5.383,12-12S18.617,0,12,0Zm0,23c-6.065,0-11-4.935-11-11S5.935,1,12,1s11,4.935,11,11-4.935,11-11,11Zm4-8.626c0,1.448-1.178,2.626-2.626,2.626h-.874v1.5c0,.276-.224,.5-.5,.5s-.5-.224-.5-.5v-1.5h-.926c-.979,0-1.893-.526-2.382-1.375-.139-.239-.057-.545,.183-.683,.238-.14,.544-.057,.683,.183,.312,.54,.894,.875,1.517,.875h2.8c.896,0,1.626-.729,1.626-1.626,0-.803-.575-1.478-1.368-1.605l-3.422-.55c-1.28-.206-2.21-1.296-2.21-2.593,0-1.448,1.178-2.626,2.626-2.626h.874v-1.5c0-.276,.224-.5,.5-.5s.5,.224,.5,.5v1.5h.926c.979,0,1.892,.527,2.382,1.375,.139,.239,.057,.545-.183,.683-.236,.136-.545,.057-.683-.183-.312-.54-.894-.875-1.517-.875h-2.8c-.896,0-1.626,.729-1.626,1.626,0,.803,.575,1.478,1.368,1.605l3.422,.55c1.28,.206,2.21,1.297,2.21,2.593Z"/></svg>

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -1,6 +1,7 @@
- -
uri: xmr-node.cakewallet.com:18081 uri: xmr-node.cakewallet.com:18081
is_default: true is_default: true
trusted: true
- -
uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
is_default: false is_default: false

View file

@ -1,3 +1,3 @@
Monero enhancements Add airgapped Monero wallet support (best used with our new offline app Cupcake)
Introducing StealthEx and LetxExchange New Buy & Sell flow
Bug fixes Bug fixes

View file

@ -1,7 +1,5 @@
Added Litecoin MWEB Add Litecoin Ledger support
Added wallet groups Add airgapped Monero wallet support (best used with our new offline app Cupcake)
Silent Payment enhancements for speed & reliability MWEB fixes and enhancements
Monero enhancements New Buy & Sell flow
Introducing StealthEx and LetxExchange
Additional ERC20 tokens scam detection
Bug fixes Bug fixes

38
build-guide-win.md Normal file
View file

@ -0,0 +1,38 @@
# 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 Windwos`, `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 downlaod 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.

View file

@ -574,6 +574,8 @@ abstract class ElectrumWalletBase
Future<void> connectToNode({required Node node}) async { Future<void> connectToNode({required Node node}) async {
this.node = node; this.node = node;
if (syncStatus is ConnectingSyncStatus) return;
try { try {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
@ -2216,13 +2218,14 @@ abstract class ElectrumWalletBase
if (syncStatus is NotConnectedSyncStatus || if (syncStatus is NotConnectedSyncStatus ||
syncStatus is LostConnectionSyncStatus || syncStatus is LostConnectionSyncStatus ||
syncStatus is ConnectingSyncStatus) { syncStatus is ConnectingSyncStatus) {
syncStatus = AttemptingSyncStatus(); syncStatus = ConnectedSyncStatus();
startSync();
} }
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
if (syncStatus is! NotConnectedSyncStatus) { if (syncStatus is! NotConnectedSyncStatus &&
syncStatus is! ConnectingSyncStatus &&
syncStatus is! SyncronizingSyncStatus) {
syncStatus = NotConnectedSyncStatus(); syncStatus = NotConnectedSyncStatus();
} }
break; break;

View file

@ -153,4 +153,9 @@ class PendingBitcoinTransaction with PendingTransaction {
inputAddresses: _tx.inputs.map((input) => input.txId).toList(), inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
outputAddresses: outputAddresses, outputAddresses: outputAddresses,
fee: fee); fee: fee);
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -87,8 +87,8 @@ packages:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
path: "." path: "."
ref: cake-update-v8 ref: cake-update-v9
resolved-ref: fc045a11db3d85d806ca67f75e8b916c706745a2 resolved-ref: "86969a14e337383e14965f5fb45a72a63e5009bc"
url: "https://github.com/cake-tech/bitcoin_base" url: "https://github.com/cake-tech/bitcoin_base"
source: git source: git
version: "4.7.0" version: "4.7.0"
@ -386,10 +386,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_web_bluetooth name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02" sha256: fcd03e2e5f82edcedcbc940f1b6a0635a50757374183254f447640886c53208e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.3" version: "0.2.4"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -560,7 +560,7 @@ packages:
description: description:
path: "packages/ledger-bitcoin" path: "packages/ledger-bitcoin"
ref: HEAD ref: HEAD
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git source: git
version: "0.0.3" version: "0.0.3"
@ -577,7 +577,7 @@ packages:
description: description:
path: "packages/ledger-litecoin" path: "packages/ledger-litecoin"
ref: HEAD ref: HEAD
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git source: git
version: "0.0.2" version: "0.0.2"

View file

@ -85,4 +85,8 @@ class PendingBitcoinCashTransaction with PendingTransaction {
fee: fee, fee: fee,
isReplaced: false, isReplaced: false,
); );
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -35,3 +35,34 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
} }
} }
WalletType? walletTypeForCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.btc:
return WalletType.bitcoin;
case CryptoCurrency.xmr:
return WalletType.monero;
case CryptoCurrency.ltc:
return WalletType.litecoin;
case CryptoCurrency.xhv:
return WalletType.haven;
case CryptoCurrency.eth:
return WalletType.ethereum;
case CryptoCurrency.bch:
return WalletType.bitcoinCash;
case CryptoCurrency.nano:
return WalletType.nano;
case CryptoCurrency.banano:
return WalletType.banano;
case CryptoCurrency.maticpoly:
return WalletType.polygon;
case CryptoCurrency.sol:
return WalletType.solana;
case CryptoCurrency.trx:
return WalletType.tron;
case CryptoCurrency.wow:
return WalletType.wownero;
default:
return null;
}
}

View file

@ -7,6 +7,7 @@ enum DeviceConnectionType {
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType, static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
[bool isIOS = false]) { [bool isIOS = false]) {
switch (walletType) { switch (walletType) {
// case WalletType.monero:
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:

View file

@ -1,10 +1,12 @@
class MoneroWalletKeys { class MoneroWalletKeys {
const MoneroWalletKeys( const MoneroWalletKeys(
{required this.privateSpendKey, {required this.primaryAddress,
required this.privateSpendKey,
required this.privateViewKey, required this.privateViewKey,
required this.publicSpendKey, required this.publicSpendKey,
required this.publicViewKey}); required this.publicViewKey});
final String primaryAddress;
final String publicViewKey; final String publicViewKey;
final String privateViewKey; final String privateViewKey;
final String publicSpendKey; final String publicSpendKey;

View file

@ -14,5 +14,8 @@ mixin PendingTransaction {
int? get outputCount => null; int? get outputCount => null;
PendingChange? change; PendingChange? change;
bool shouldCommitUR() => false;
Future<void> commit(); Future<void> commit();
Future<String?> commitUR();
} }

View file

@ -61,4 +61,8 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
return ''; return '';
} }
} }
/// Check if the Wallet requires a hardware wallet to be connected during
/// the opening flow. (Currently only the case for Monero)
bool requireHardwareWalletConnection(String name) => false;
} }

View file

@ -47,4 +47,9 @@ class PendingEVMChainTransaction with PendingTransaction {
return '0x${Hex.HEX.encode(txid)}'; return '0x${Hex.HEX.encode(txid)}';
} }
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -2,4 +2,9 @@ class WalletRestoreFromKeysException implements Exception {
WalletRestoreFromKeysException({required this.message}); WalletRestoreFromKeysException({required this.message});
final String message; final String message;
@override
String toString() {
return message;
}
} }

View file

@ -73,6 +73,7 @@ abstract class HavenWalletBase
@override @override
MoneroWalletKeys get keys => MoneroWalletKeys( MoneroWalletKeys get keys => MoneroWalletKeys(
primaryAddress: haven_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: haven_wallet.getSecretSpendKey(), privateSpendKey: haven_wallet.getSecretSpendKey(),
privateViewKey: haven_wallet.getSecretViewKey(), privateViewKey: haven_wallet.getSecretViewKey(),
publicSpendKey: haven_wallet.getPublicSpendKey(), publicSpendKey: haven_wallet.getPublicSpendKey(),

View file

@ -48,4 +48,9 @@ class PendingHavenTransaction with PendingTransaction {
rethrow; rethrow;
} }
} }
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -2,6 +2,7 @@ import 'package:cw_monero/api/wallet.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
monero.wallet? wptr = null; monero.wallet? wptr = null;
bool get isViewOnly => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
int _wlptrForW = 0; int _wlptrForW = 0;
monero.WalletListener? _wlptr = null; monero.WalletListener? _wlptr = null;

View file

@ -13,7 +13,13 @@ import 'package:mutex/mutex.dart';
String getTxKey(String txId) { String getTxKey(String txId) {
return monero.Wallet_getTxKey(wptr!, txid: txId); final txKey = monero.Wallet_getTxKey(wptr!, txid: txId);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final error = monero.Wallet_errorString(wptr!);
return txId+"_"+error;
}
return txKey;
} }
final txHistoryMutex = Mutex(); final txHistoryMutex = Mutex();
monero.TransactionHistory? txhistory; monero.TransactionHistory? txhistory;
@ -178,12 +184,13 @@ PendingTransactionDescription createTransactionMultDestSync(
); );
} }
void commitTransactionFromPointerAddress({required int address}) => String? commitTransactionFromPointerAddress({required int address, required bool useUR}) =>
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address)); commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
void commitTransaction({required monero.PendingTransaction transactionPointer}) { String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
final txCommit = useUR
final txCommit = monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false); ? monero.PendingTransaction_commitUR(transactionPointer, 120)
: monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
final String? error = (() { final String? error = (() {
final status = monero.PendingTransaction_status(transactionPointer.cast()); final status = monero.PendingTransaction_status(transactionPointer.cast());
@ -196,6 +203,11 @@ void commitTransaction({required monero.PendingTransaction transactionPointer})
if (error != null) { if (error != null) {
throw CreationTransactionException(message: error); throw CreationTransactionException(message: error);
} }
if (useUR) {
return txCommit as String?;
} else {
return null;
}
} }
Future<PendingTransactionDescription> _createTransactionSync(Map args) async { Future<PendingTransactionDescription> _createTransactionSync(Map args) async {

View file

@ -119,7 +119,7 @@ Future<bool> setupNodeSync(
daemonUsername: login ?? '', daemonUsername: login ?? '',
daemonPassword: password ?? ''); daemonPassword: password ?? '');
}); });
// monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true); // monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '');
final status = monero.Wallet_status(wptr!); final status = monero.Wallet_status(wptr!);
@ -330,4 +330,4 @@ String signMessage(String message, {String address = ""}) {
bool verifyMessage(String message, String address, String signature) { bool verifyMessage(String message, String address, String signature) {
return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
} }

View file

@ -7,19 +7,18 @@ import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/api/transaction_history.dart'; import 'package:cw_monero/api/transaction_history.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/ledger.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
class MoneroCException implements Exception { class MoneroCException implements Exception {
final String message; final String message;
MoneroCException(this.message); MoneroCException(this.message);
@override @override
String toString() { String toString() => message;
return message;
}
} }
void checkIfMoneroCIsFine() { void checkIfMoneroCIsFine() {
@ -43,7 +42,6 @@ void checkIfMoneroCIsFine() {
throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'"); throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'");
} }
} }
monero.WalletManager? _wmPtr; monero.WalletManager? _wmPtr;
final monero.WalletManager wmPtr = Pointer.fromAddress((() { final monero.WalletManager wmPtr = Pointer.fromAddress((() {
try { try {
@ -60,6 +58,13 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() {
return _wmPtr!.address; return _wmPtr!.address;
})()); })());
void createWalletPointer() {
final newWptr = monero.WalletManager_createWallet(wmPtr,
path: "", password: "", language: "", networkType: 0);
wptr = newWptr;
}
void createWalletSync( void createWalletSync(
{required String path, {required String path,
required String password, required String password,
@ -124,24 +129,24 @@ void restoreWalletFromKeysSync(
int restoreHeight = 0}) { int restoreHeight = 0}) {
txhistory = null; txhistory = null;
var newWptr = (spendKey != "") var newWptr = (spendKey != "")
? monero.WalletManager_createDeterministicWalletFromSpendKey( ? monero.WalletManager_createDeterministicWalletFromSpendKey(wmPtr,
wmPtr, path: path,
path: path, password: password,
password: password, language: language,
language: language, spendKeyString: spendKey,
spendKeyString: spendKey, newWallet: true,
newWallet: true, // TODO(mrcyjanek): safe to remove // TODO(mrcyjanek): safe to remove
restoreHeight: restoreHeight) restoreHeight: restoreHeight)
: monero.WalletManager_createWalletFromKeys( : monero.WalletManager_createWalletFromKeys(
wmPtr, wmPtr,
path: path, path: path,
password: password, password: password,
restoreHeight: restoreHeight, restoreHeight: restoreHeight,
addressString: address, addressString: address,
viewKeyString: viewKey, viewKeyString: viewKey,
spendKeyString: spendKey, spendKeyString: spendKey,
nettype: 0, nettype: 0,
); );
final status = monero.Wallet_status(newWptr); final status = monero.Wallet_status(newWptr);
if (status != 0) { if (status != 0) {
@ -156,7 +161,7 @@ void restoreWalletFromKeysSync(
if (viewKey != viewKeyRestored && viewKey != "") { if (viewKey != viewKeyRestored && viewKey != "") {
monero.WalletManager_closeWallet(wmPtr, newWptr, false); monero.WalletManager_closeWallet(wmPtr, newWptr, false);
File(path).deleteSync(); File(path).deleteSync();
File(path+".keys").deleteSync(); File(path + ".keys").deleteSync();
newWptr = monero.WalletManager_createWalletFromKeys( newWptr = monero.WalletManager_createWalletFromKeys(
wmPtr, wmPtr,
path: path, path: path,
@ -199,7 +204,7 @@ void restoreWalletFromSpendKeySync(
// viewKeyString: '', // viewKeyString: '',
// nettype: 0, // nettype: 0,
// ); // );
txhistory = null; txhistory = null;
final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey( final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey(
wmPtr, wmPtr,
@ -230,41 +235,39 @@ void restoreWalletFromSpendKeySync(
String _lastOpenedWallet = ""; String _lastOpenedWallet = "";
// void restoreMoneroWalletFromDevice( Future<void> restoreWalletFromHardwareWallet(
// {required String path, {required String path,
// required String password, required String password,
// required String deviceName, required String deviceName,
// int nettype = 0, int nettype = 0,
// int restoreHeight = 0}) { int restoreHeight = 0}) async {
// txhistory = null;
// final pathPointer = path.toNativeUtf8();
// final passwordPointer = password.toNativeUtf8(); final newWptrAddr = await Isolate.run(() {
// final deviceNamePointer = deviceName.toNativeUtf8(); return monero.WalletManager_createWalletFromDevice(wmPtr,
// final errorMessagePointer = ''.toNativeUtf8(); path: path,
// password: password,
// final isWalletRestored = restoreWalletFromDeviceNative( restoreHeight: restoreHeight,
// pathPointer, deviceName: deviceName)
// passwordPointer, .address;
// deviceNamePointer, });
// nettype, final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
// restoreHeight,
// errorMessagePointer) != 0; final status = monero.Wallet_status(newWptr);
//
// calloc.free(pathPointer); if (status != 0) {
// calloc.free(passwordPointer); final error = monero.Wallet_errorString(newWptr);
// throw WalletRestoreFromSeedException(message: error);
// storeSync(); }
// wptr = newWptr;
// if (!isWalletRestored) {
// throw WalletRestoreFromKeysException( openedWalletsByPath[path] = wptr!;
// message: convertUTF8ToString(pointer: errorMessagePointer)); }
// }
// }
Map<String, monero.wallet> openedWalletsByPath = {}; Map<String, monero.wallet> openedWalletsByPath = {};
void loadWallet( Future<void> loadWallet(
{required String path, required String password, int nettype = 0}) { {required String path, required String password, int nettype = 0}) async {
if (openedWalletsByPath[path] != null) { if (openedWalletsByPath[path] != null) {
txhistory = null; txhistory = null;
wptr = openedWalletsByPath[path]!; wptr = openedWalletsByPath[path]!;
@ -278,8 +281,29 @@ void loadWallet(
}); });
} }
txhistory = null; txhistory = null;
final newWptr = monero.WalletManager_openWallet(wmPtr,
path: path, password: password); /// Get the device type
/// 0: Software Wallet
/// 1: Ledger
/// 2: Trezor
final deviceType = monero.WalletManager_queryWalletDevice(wmPtr,
keysFileName: "$path.keys", password: password, kdfRounds: 1);
if (deviceType == 1) {
final dummyWPtr = wptr ??
monero.WalletManager_openWallet(wmPtr, path: '', password: '');
enableLedgerExchange(dummyWPtr, gLedger!);
}
final addr = wmPtr.address;
final newWptrAddr = await Isolate.run(() {
return monero.WalletManager_openWallet(Pointer.fromAddress(addr),
path: path, password: password)
.address;
});
final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
_lastOpenedWallet = path; _lastOpenedWallet = path;
final status = monero.Wallet_status(newWptr); final status = monero.Wallet_status(newWptr);
if (status != 0) { if (status != 0) {
@ -287,6 +311,7 @@ void loadWallet(
print(err); print(err);
throw WalletOpeningException(message: err); throw WalletOpeningException(message: err);
} }
wptr = newWptr; wptr = newWptr;
openedWalletsByPath[path] = wptr!; openedWalletsByPath[path] = wptr!;
} }
@ -351,7 +376,7 @@ Future<void> _openWallet(Map<String, String> args) async => loadWallet(
bool _isWalletExist(String path) => isWalletExistSync(path: path); bool _isWalletExist(String path) => isWalletExistSync(path: path);
void openWallet( Future<void> openWallet(
{required String path, {required String path,
required String password, required String password,
int nettype = 0}) async => int nettype = 0}) async =>
@ -425,3 +450,5 @@ Future<void> restoreFromSpendKey(
}); });
bool isWalletExist({required String path}) => _isWalletExist(path); bool isWalletExist({required String path}) => _isWalletExist(path);
bool isViewOnlyBySpendKey() => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;

84
cw_monero/lib/ledger.dart Normal file
View file

@ -0,0 +1,84 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
import 'package:monero/monero.dart' as monero;
// import 'package:polyseed/polyseed.dart';
LedgerConnection? gLedger;
Timer? _ledgerExchangeTimer;
Timer? _ledgerKeepAlive;
void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
_ledgerExchangeTimer?.cancel();
_ledgerExchangeTimer = Timer.periodic(Duration(milliseconds: 1), (_) async {
final ledgerRequestLength = monero.Wallet_getSendToDeviceLength(ptr);
final ledgerRequest = monero.Wallet_getSendToDevice(ptr)
.cast<Uint8>()
.asTypedList(ledgerRequestLength);
if (ledgerRequestLength > 0) {
_ledgerKeepAlive?.cancel();
final Pointer<Uint8> emptyPointer = malloc<Uint8>(0);
monero.Wallet_setDeviceSendData(
ptr, emptyPointer.cast<UnsignedChar>(), 0);
malloc.free(emptyPointer);
// print("> ${ledgerRequest.toHexString()}");
final response = await exchange(connection, ledgerRequest);
// print("< ${response.toHexString()}");
final Pointer<Uint8> result = malloc<Uint8>(response.length);
for (var i = 0; i < response.length; i++) {
result.asTypedList(response.length)[i] = response[i];
}
monero.Wallet_setDeviceReceivedData(
ptr, result.cast<UnsignedChar>(), response.length);
malloc.free(result);
keepAlive(connection);
}
});
}
void keepAlive(LedgerConnection connection) {
if (connection.connectionType == ConnectionType.ble) {
_ledgerKeepAlive = Timer.periodic(Duration(seconds: 10), (_) async {
try {
UniversalBle.setNotifiable(
connection.device.id,
connection.device.deviceInfo.serviceId,
connection.device.deviceInfo.notifyCharacteristicKey,
BleInputProperty.notification,
);
} catch (_) {}
});
}
}
void disableLedgerExchange() {
_ledgerExchangeTimer?.cancel();
_ledgerKeepAlive?.cancel();
gLedger?.disconnect();
gLedger = null;
}
Future<Uint8List> exchange(LedgerConnection connection, Uint8List data) async =>
connection.sendOperation<Uint8List>(ExchangeOperation(data));
class ExchangeOperation extends LedgerRawOperation<Uint8List> {
final Uint8List inputData;
ExchangeOperation(this.inputData);
@override
Future<Uint8List> read(ByteDataReader reader) async =>
reader.read(reader.remainingLength);
@override
Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData];
}

View file

@ -19,6 +19,7 @@ import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/coins_info.dart';
import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/monero_output.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart';
@ -27,6 +28,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet;
import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart';
import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart'; import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_transaction_creation_credentials.dart'; import 'package:cw_monero/monero_transaction_creation_credentials.dart';
import 'package:cw_monero/monero_transaction_history.dart'; import 'package:cw_monero/monero_transaction_history.dart';
import 'package:cw_monero/monero_transaction_info.dart'; import 'package:cw_monero/monero_transaction_info.dart';
@ -35,6 +37,7 @@ import 'package:cw_monero/monero_wallet_addresses.dart';
import 'package:cw_monero/pending_monero_transaction.dart'; import 'package:cw_monero/pending_monero_transaction.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
@ -121,6 +124,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override @override
MoneroWalletKeys get keys => MoneroWalletKeys( MoneroWalletKeys get keys => MoneroWalletKeys(
primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: monero_wallet.getSecretSpendKey(), privateSpendKey: monero_wallet.getSecretSpendKey(),
privateViewKey: monero_wallet.getSecretViewKey(), privateViewKey: monero_wallet.getSecretViewKey(),
publicSpendKey: monero_wallet.getPublicSpendKey(), publicSpendKey: monero_wallet.getPublicSpendKey(),
@ -230,6 +234,36 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
} }
} }
Future<bool> submitTransactionUR(String ur) async {
final retStatus = monero.Wallet_submitTransactionUR(wptr!, ur);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw MoneroTransactionCreationException("unable to broadcast signed transaction: $err");
}
return retStatus;
}
bool importKeyImagesUR(String ur) {
final retStatus = monero.Wallet_importKeyImagesUR(wptr!, ur);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw Exception("unable to import key images: $err");
}
return retStatus;
}
String exportOutputsUR(bool all) {
final str = monero.Wallet_exportOutputsUR(wptr!, all: all);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw MoneroTransactionCreationException("unable to export UR: $err");
}
return str;
}
@override @override
Future<PendingTransaction> createTransaction(Object credentials) async { Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials; final _credentials = credentials as MoneroTransactionCreationCredentials;
@ -796,4 +830,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
return monero_wallet.verifyMessage(message, address, signature); return monero_wallet.verifyMessage(message, address, signature);
} }
void setLedgerConnection(LedgerConnection connection) {
final dummyWPtr = wptr ??
monero.WalletManager_openWallet(wmPtr, path: '', password: '');
enableLedgerExchange(dummyWPtr, connection);
}
} }

View file

@ -9,10 +9,13 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_wallet.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:polyseed/polyseed.dart'; import 'package:polyseed/polyseed.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
@ -25,6 +28,15 @@ class MoneroNewWalletCredentials extends WalletCredentials {
final bool isPolyseed; final bool isPolyseed;
} }
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
MoneroRestoreWalletFromHardwareCredentials({required String name,
required this.ledgerConnection,
int height = 0,
String? password})
: super(name: name, password: password, height: height);
LedgerConnection ledgerConnection;
}
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials { class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
MoneroRestoreWalletFromSeedCredentials( MoneroRestoreWalletFromSeedCredentials(
{required String name, required this.mnemonic, int height = 0, String? password}) {required String name, required this.mnemonic, int height = 0, String? password})
@ -39,14 +51,13 @@ class MoneroWalletLoadingException implements Exception {
} }
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials { class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
MoneroRestoreWalletFromKeysCredentials( MoneroRestoreWalletFromKeysCredentials({required String name,
{required String name, required String password,
required String password, required this.language,
required this.language, required this.address,
required this.address, required this.viewKey,
required this.viewKey, required this.spendKey,
required this.spendKey, int height = 0})
int height = 0})
: super(name: name, password: password, height: height); : super(name: name, password: password, height: height);
final String language; final String language;
@ -59,7 +70,7 @@ class MoneroWalletService extends WalletService<
MoneroNewWalletCredentials, MoneroNewWalletCredentials,
MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromSeedCredentials,
MoneroRestoreWalletFromKeysCredentials, MoneroRestoreWalletFromKeysCredentials,
MoneroNewWalletCredentials> { MoneroRestoreWalletFromHardwareCredentials> {
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@ -81,7 +92,7 @@ class MoneroWalletService extends WalletService<
final lang = PolyseedLang.getByEnglishName(credentials.language); final lang = PolyseedLang.getByEnglishName(credentials.language);
final heightOverride = final heightOverride =
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2))); getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
return _restoreFromPolyseed( return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang, path, credentials.password!, polyseed, credentials.walletInfo!, lang,
@ -91,9 +102,9 @@ class MoneroWalletService extends WalletService<
await monero_wallet_manager.createWallet( await monero_wallet_manager.createWallet(
path: path, password: credentials.password!, language: credentials.language); path: path, password: credentials.password!, language: credentials.language);
final wallet = MoneroWallet( final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
password: credentials.password!); password: credentials.password!);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -128,13 +139,18 @@ class MoneroWalletService extends WalletService<
await monero_wallet_manager await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password}); .openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values.firstWhere( final walletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(name, getType())); (info) => info.id == WalletBase.idFor(name, getType()));
final wallet = MoneroWallet( final wallet = MoneroWallet(
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
password: password); password: password);
final isValid = wallet.walletAddresses.validate(); final isValid = wallet.walletAddresses.validate();
if (wallet.isHardwareWallet) {
wallet.setLedgerConnection(gLedger!);
gLedger = null;
}
if (!isValid) { if (!isValid) {
await restoreOrResetWalletFiles(name); await restoreOrResetWalletFiles(name);
wallet.close(shouldCleanup: false); wallet.close(shouldCleanup: false);
@ -185,10 +201,9 @@ class MoneroWalletService extends WalletService<
} }
@override @override
Future<void> rename( Future<void> rename(String currentName, String password, String newName) async {
String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhere( final currentWalletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(currentName, getType())); (info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = MoneroWallet( final currentWallet = MoneroWallet(
walletInfo: currentWalletInfo, walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
@ -218,9 +233,9 @@ class MoneroWalletService extends WalletService<
viewKey: credentials.viewKey, viewKey: credentials.viewKey,
spendKey: credentials.spendKey); spendKey: credentials.spendKey);
final wallet = MoneroWallet( final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
password: credentials.password!); password: credentials.password!);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -232,9 +247,34 @@ class MoneroWalletService extends WalletService<
} }
@override @override
Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) { Future<MoneroWallet> restoreFromHardwareWallet(
throw UnimplementedError( MoneroRestoreWalletFromHardwareCredentials credentials) async {
"Restoring a Monero wallet from a hardware wallet is not yet supported!"); try {
final path = await pathForWallet(name: credentials.name, type: getType());
final password = credentials.password;
final height = credentials.height;
if (wptr == null ) monero_wallet_manager.createWalletPointer();
enableLedgerExchange(wptr!, credentials.ledgerConnection);
await monero_wallet_manager.restoreWalletFromHardwareWallet(
path: path,
password: password!,
restoreHeight: height!,
deviceName: 'Ledger');
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
password: credentials.password!);
await wallet.init();
return wallet;
} catch (e) {
// TODO: Implement Exception for wallet list service.
print('MoneroWalletsManager Error: $e');
rethrow;
}
} }
@override @override
@ -253,9 +293,9 @@ class MoneroWalletService extends WalletService<
seed: credentials.mnemonic, seed: credentials.mnemonic,
restoreHeight: credentials.height!); restoreHeight: credentials.height!);
final wallet = MoneroWallet( final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
password: credentials.password!); password: credentials.password!);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -283,8 +323,8 @@ class MoneroWalletService extends WalletService<
} }
} }
Future<MoneroWallet> _restoreFromPolyseed( Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async { {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
final height = overrideHeight ?? final height = overrideHeight ??
getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
@ -329,7 +369,9 @@ class MoneroWalletService extends WalletService<
dir.listSync().forEach((f) { dir.listSync().forEach((f) {
final file = File(f.path); final file = File(f.path);
final name = f.path.split('/').last; final name = f.path
.split('/')
.last;
final newPath = newWalletDirPath + '/$name'; final newPath = newWalletDirPath + '/$name';
final newFile = File(newPath); final newFile = File(newPath);
@ -366,4 +408,11 @@ class MoneroWalletService extends WalletService<
return ''; return '';
} }
} }
@override
bool requireHardwareWalletConnection(String name) {
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
return walletInfo.isHardwareWallet;
}
} }

View file

@ -1,3 +1,4 @@
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/transaction_history.dart' import 'package:cw_monero/api/transaction_history.dart'
as monero_transaction_history; as monero_transaction_history;
@ -35,11 +36,32 @@ class PendingMoneroTransaction with PendingTransaction {
String get feeFormatted => AmountConverter.amountIntToString( String get feeFormatted => AmountConverter.amountIntToString(
CryptoCurrency.xmr, pendingTransactionDescription.fee); CryptoCurrency.xmr, pendingTransactionDescription.fee);
bool shouldCommitUR() => isViewOnly;
@override @override
Future<void> commit() async { Future<void> commit() async {
try { try {
monero_transaction_history.commitTransactionFromPointerAddress( monero_transaction_history.commitTransactionFromPointerAddress(
address: pendingTransactionDescription.pointerAddress); address: pendingTransactionDescription.pointerAddress,
useUR: false);
} catch (e) {
final message = e.toString();
if (message.contains('Reason: double spend')) {
throw DoubleSpendException();
}
rethrow;
}
}
@override
Future<String?> commitUR() async {
try {
final ret = monero_transaction_history.commitTransactionFromPointerAddress(
address: pendingTransactionDescription.pointerAddress,
useUR: true);
return ret;
} catch (e) { } catch (e) {
final message = e.toString(); final message = e.toString();

View file

@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.3" version: "1.5.5"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
bluez:
dependency: transitive
description:
name: bluez
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev"
source: hosted
version: "0.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -209,6 +217,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.2.4"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
encrypt: encrypt:
dependency: "direct main" dependency: "direct main"
description: description:
@ -229,10 +245,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: ffi name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -267,6 +283,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_bluetooth:
dependency: transitive
description:
name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -295,18 +319,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: hashlib name: hashlib
sha256: d41795742c10947930630118c6836608deeb9047cd05aee32d2baeb697afd66a sha256: f572f2abce09fc7aee53f15927052b9732ea1053e540af8cae211111ee0b99b1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.2" version: "1.21.0"
hashlib_codecs: hashlib_codecs:
dependency: transitive dependency: transitive
description: description:
name: hashlib_codecs name: hashlib_codecs
sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f" sha256: "8cea9ccafcfeaa7324d2ae52c61c69f7ff71f4237507a018caab31b9e416e3b1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.6.0"
hive: hive:
dependency: transitive dependency: transitive
description: description:
@ -327,10 +351,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.2"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -403,6 +427,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
ledger_flutter_plus:
dependency: "direct main"
description:
name: ledger_flutter_plus
sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -439,10 +479,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: mime name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.6"
mobx: mobx:
dependency: "direct main" dependency: "direct main"
description: description:
@ -463,8 +503,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "impls/monero.dart" path: "impls/monero.dart"
ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b" ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b" resolved-ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
url: "https://github.com/mrcyjanek/monero_c" url: "https://github.com/mrcyjanek/monero_c"
source: git source: git
version: "0.0.0" version: "0.0.0"
@ -504,10 +544,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
@ -544,10 +584,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -612,6 +660,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -737,6 +793,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
universal_ble:
dependency: transitive
description:
name: universal_ble
sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
url: "https://pub.dev"
source: hosted
version: "0.12.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
unorm_dart: unorm_dart:
dependency: transitive dependency: transitive
description: description:
@ -785,22 +857,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.5" version: "2.4.5"
win32:
dependency: transitive
description:
name: win32
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev"
source: hosted
version: "5.5.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -811,4 +883,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.6" flutter: ">=3.19.0"

View file

@ -25,9 +25,11 @@ dependencies:
monero: monero:
git: git:
url: https://github.com/mrcyjanek/monero_c url: https://github.com/mrcyjanek/monero_c
ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart path: impls/monero.dart
mutex: ^3.1.0 mutex: ^3.1.0
ledger_flutter_plus: ^1.4.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -37,4 +37,9 @@ class PendingNanoTransaction with PendingTransaction {
await nanoClient.processBlock(block, "send"); await nanoClient.processBlock(block, "send");
} }
} }
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -0,0 +1,189 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View file

@ -40,4 +40,9 @@ class PendingSolanaTransaction with PendingTransaction {
@override @override
String get id => ''; String get id => '';
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -30,4 +30,9 @@ class PendingTronTransaction with PendingTransaction {
@override @override
String get id => ''; String get id => '';
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -0,0 +1,5 @@
class ConnectionToNodeException implements Exception {
ConnectionToNodeException({required this.message});
final String message;
}

View file

@ -3,5 +3,6 @@ class WalletRestoreFromKeysException implements Exception {
final String message; final String message;
@override
String toString() => message; String toString() => message;
} }

View file

@ -0,0 +1,12 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class AccountRow extends Struct {
@Int64()
external int id;
external Pointer<Utf8> label;
String getLabel() => label.toDartString();
int getId() => id;
}

View file

@ -0,0 +1,73 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class CoinsInfoRow extends Struct {
@Int64()
external int blockHeight;
external Pointer<Utf8> hash;
@Uint64()
external int internalOutputIndex;
@Uint64()
external int globalOutputIndex;
@Int8()
external int spent;
@Int8()
external int frozen;
@Uint64()
external int spentHeight;
@Uint64()
external int amount;
@Int8()
external int rct;
@Int8()
external int keyImageKnown;
@Uint64()
external int pkIndex;
@Uint32()
external int subaddrIndex;
@Uint32()
external int subaddrAccount;
external Pointer<Utf8> address;
external Pointer<Utf8> addressLabel;
external Pointer<Utf8> keyImage;
@Uint64()
external int unlockTime;
@Int8()
external int unlocked;
external Pointer<Utf8> pubKey;
@Int8()
external int coinbase;
external Pointer<Utf8> description;
String getHash() => hash.toDartString();
String getAddress() => address.toDartString();
String getAddressLabel() => addressLabel.toDartString();
String getKeyImage() => keyImage.toDartString();
String getPubKey() => pubKey.toDartString();
String getDescription() => description.toDartString();
}

View file

@ -0,0 +1,15 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class SubaddressRow extends Struct {
@Int64()
external int id;
external Pointer<Utf8> address;
external Pointer<Utf8> label;
String getLabel() => label.toDartString();
String getAddress() => address.toDartString();
int getId() => id;
}

View file

@ -0,0 +1,41 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class TransactionInfoRow extends Struct {
@Uint64()
external int amount;
@Uint64()
external int fee;
@Uint64()
external int blockHeight;
@Uint64()
external int confirmations;
@Uint32()
external int subaddrAccount;
@Int8()
external int direction;
@Int8()
external int isPending;
@Uint32()
external int subaddrIndex;
external Pointer<Utf8> hash;
external Pointer<Utf8> paymentId;
@Int64()
external int datetime;
int getDatetime() => datetime;
int getAmount() => amount >= 0 ? amount : amount * -1;
bool getIsPending() => isPending != 0;
String getHash() => hash.toDartString();
String getPaymentId() => paymentId.toDartString();
}

View file

@ -0,0 +1,8 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class Utf8Box extends Struct {
external Pointer<Utf8> value;
String getValue() => value.toDartString();
}

View file

@ -0,0 +1,8 @@
import 'cw_wownero_platform_interface.dart';
class CwWownero {
Future<String?> getPlatformVersion() {
return CwWowneroPlatform.instance.getPlatformVersion();
}
}

View file

@ -0,0 +1,17 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'cw_wownero_platform_interface.dart';
/// An implementation of [CwWowneroPlatform] that uses method channels.
class MethodChannelCwWownero extends CwWowneroPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('cw_wownero');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

View file

@ -0,0 +1,29 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'cw_wownero_method_channel.dart';
abstract class CwWowneroPlatform extends PlatformInterface {
/// Constructs a CwWowneroPlatform.
CwWowneroPlatform() : super(token: _token);
static final Object _token = Object();
static CwWowneroPlatform _instance = MethodChannelCwWownero();
/// The default instance of [CwWowneroPlatform] to use.
///
/// Defaults to [MethodChannelCwWownero].
static CwWowneroPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [CwWowneroPlatform] when
/// they register themselves.
static set instance(CwWowneroPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

File diff suppressed because it is too large Load diff

View file

@ -50,4 +50,9 @@ class PendingWowneroTransaction with PendingTransaction {
rethrow; rethrow;
} }
} }
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
} }

View file

@ -120,6 +120,7 @@ abstract class WowneroWalletBase
@override @override
MoneroWalletKeys get keys => MoneroWalletKeys( MoneroWalletKeys get keys => MoneroWalletKeys(
primaryAddress: wownero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: wownero_wallet.getSecretSpendKey(), privateSpendKey: wownero_wallet.getSecretSpendKey(),
privateViewKey: wownero_wallet.getSecretViewKey(), privateViewKey: wownero_wallet.getSecretViewKey(),
publicSpendKey: wownero_wallet.getPublicSpendKey(), publicSpendKey: wownero_wallet.getPublicSpendKey(),

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "impls/monero.dart" path: "impls/monero.dart"
ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b" ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b" resolved-ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
url: "https://github.com/mrcyjanek/monero_c" url: "https://github.com/mrcyjanek/monero_c"
source: git source: git
version: "0.0.0" version: "0.0.0"
@ -552,10 +552,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.8"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:

View file

@ -25,7 +25,8 @@ dependencies:
monero: monero:
git: git:
url: https://github.com/mrcyjanek/monero_c url: https://github.com/mrcyjanek/monero_c
ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart path: impls/monero.dart
mutex: ^3.1.0 mutex: ^3.1.0

View file

@ -1,8 +1,4 @@
PODS: PODS:
- barcode_scan2 (0.0.1):
- Flutter
- MTBBarcodeScanner
- SwiftProtobuf
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- ReachabilitySwift - ReachabilitySwift
@ -76,6 +72,8 @@ PODS:
- DKPhotoGallery/Resource (0.0.19): - DKPhotoGallery/Resource (0.0.19):
- SDWebImage - SDWebImage
- SwiftyGif - SwiftyGif
- fast_scanner (5.1.1):
- Flutter
- file_picker (0.0.1): - file_picker (0.0.1):
- DKImagePickerController/PhotoGallery - DKImagePickerController/PhotoGallery
- Flutter - Flutter
@ -100,7 +98,6 @@ PODS:
- Flutter - Flutter
- integration_test (0.0.1): - integration_test (0.0.1):
- Flutter - Flutter
- MTBBarcodeScanner (5.0.11)
- OrderedSet (5.0.0) - OrderedSet (5.0.0)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
@ -109,12 +106,7 @@ PODS:
- FlutterMacOS - FlutterMacOS
- permission_handler_apple (9.1.1): - permission_handler_apple (9.1.1):
- Flutter - Flutter
- Protobuf (3.28.2)
- ReachabilitySwift (5.2.3) - ReachabilitySwift (5.2.3)
- reactive_ble_mobile (0.0.1):
- Flutter
- Protobuf (~> 3.5)
- SwiftProtobuf (~> 1.0)
- SDWebImage (5.19.7): - SDWebImage (5.19.7):
- SDWebImage/Core (= 5.19.7) - SDWebImage/Core (= 5.19.7)
- SDWebImage/Core (5.19.7) - SDWebImage/Core (5.19.7)
@ -127,11 +119,13 @@ PODS:
- FlutterMacOS - FlutterMacOS
- sp_scanner (0.0.1): - sp_scanner (0.0.1):
- Flutter - Flutter
- SwiftProtobuf (1.27.1)
- SwiftyGif (5.4.5) - SwiftyGif (5.4.5)
- Toast (4.1.1) - Toast (4.1.1)
- uni_links (0.0.1): - uni_links (0.0.1):
- Flutter - Flutter
- universal_ble (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- wakelock_plus (0.0.1): - wakelock_plus (0.0.1):
@ -140,7 +134,6 @@ PODS:
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- CryptoSwift - CryptoSwift
- cw_haven (from `.symlinks/plugins/cw_haven/ios`) - cw_haven (from `.symlinks/plugins/cw_haven/ios`)
@ -149,6 +142,7 @@ DEPENDENCIES:
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- devicelocale (from `.symlinks/plugins/devicelocale/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`)
- fast_scanner (from `.symlinks/plugins/fast_scanner/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
@ -161,12 +155,12 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- reactive_ble_mobile (from `.symlinks/plugins/reactive_ble_mobile/ios`)
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`)
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- workmanager (from `.symlinks/plugins/workmanager/ios`) - workmanager (from `.symlinks/plugins/workmanager/ios`)
@ -176,18 +170,13 @@ SPEC REPOS:
- CryptoSwift - CryptoSwift
- DKImagePickerController - DKImagePickerController
- DKPhotoGallery - DKPhotoGallery
- MTBBarcodeScanner
- OrderedSet - OrderedSet
- Protobuf
- ReachabilitySwift - ReachabilitySwift
- SDWebImage - SDWebImage
- SwiftProtobuf
- SwiftyGif - SwiftyGif
- Toast - Toast
EXTERNAL SOURCES: EXTERNAL SOURCES:
barcode_scan2:
:path: ".symlinks/plugins/barcode_scan2/ios"
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios" :path: ".symlinks/plugins/connectivity_plus/ios"
cw_haven: cw_haven:
@ -202,6 +191,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
devicelocale: devicelocale:
:path: ".symlinks/plugins/devicelocale/ios" :path: ".symlinks/plugins/devicelocale/ios"
fast_scanner:
:path: ".symlinks/plugins/fast_scanner/ios"
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
@ -226,8 +217,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
reactive_ble_mobile:
:path: ".symlinks/plugins/reactive_ble_mobile/ios"
sensitive_clipboard: sensitive_clipboard:
:path: ".symlinks/plugins/sensitive_clipboard/ios" :path: ".symlinks/plugins/sensitive_clipboard/ios"
share_plus: share_plus:
@ -238,6 +227,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sp_scanner/ios" :path: ".symlinks/plugins/sp_scanner/ios"
uni_links: uni_links:
:path: ".symlinks/plugins/uni_links/ios" :path: ".symlinks/plugins/uni_links/ios"
universal_ble:
:path: ".symlinks/plugins/universal_ble/darwin"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock_plus: wakelock_plus:
@ -246,7 +237,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/workmanager/ios" :path: ".symlinks/plugins/workmanager/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
@ -257,6 +247,7 @@ SPEC CHECKSUMS:
devicelocale: b22617f40038496deffba44747101255cee005b0 devicelocale: b22617f40038496deffba44747101255cee005b0
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
@ -266,23 +257,20 @@ SPEC CHECKSUMS:
fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
integration_test: 13825b8a9334a850581300559b8839134b124670 integration_test: 13825b8a9334a850581300559b8839134b124670
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
Protobuf: 28c89b24435762f60244e691544ed80f50d82701
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
SwiftProtobuf: b109bd17979d7993a84da14b1e1fdd8b0ded934a
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6

View file

@ -230,6 +230,7 @@
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
F6F67323547956BC4F7B67F1 /* [CP] Embed Pods Frameworks */, F6F67323547956BC4F7B67F1 /* [CP] Embed Pods Frameworks */,
777FE2B16F25A3E820834145 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@ -343,6 +344,23 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
}; };
777FE2B16F25A3E820834145 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;

View file

@ -1,6 +1,10 @@
import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -23,14 +27,38 @@ abstract class BuyProvider {
String get darkIcon; String get darkIcon;
bool get isAggregator;
@override @override
String toString() => title; String toString() => title;
Future<void> launchProvider(BuildContext context, bool? isBuyAction); Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) =>
null;
Future<String> requestUrl(String amount, String sourceCurrency) => throw UnimplementedError(); Future<String> requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
Future<Order> findOrderById(String id) => throw UnimplementedError(); Future<Order> findOrderById(String id) => throw UnimplementedError();
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError(); Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) =>
throw UnimplementedError();
Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async =>
[];
Future<List<Quote>?> fetchQuote(
{required CryptoCurrency cryptoCurrency,
required FiatCurrency fiatCurrency,
required double amount,
required bool isBuyAction,
required String walletAddress,
PaymentType? paymentType,
String? countryCode}) async =>
null;
} }

302
lib/buy/buy_quote.dart Normal file
View file

@ -0,0 +1,302 @@
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/core/selectable_option.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cw_core/crypto_currency.dart';
enum ProviderRecommendation { bestRate, lowKyc, successRate }
extension RecommendationTitle on ProviderRecommendation {
String get title {
switch (this) {
case ProviderRecommendation.bestRate:
return 'BEST RATE';
case ProviderRecommendation.lowKyc:
return 'LOW KYC';
case ProviderRecommendation.successRate:
return 'SUCCESS RATE';
}
}
}
ProviderRecommendation? getRecommendationFromString(String title) {
switch (title) {
case 'BEST RATE':
return ProviderRecommendation.bestRate;
case 'LowKyc':
return ProviderRecommendation.lowKyc;
case 'SuccessRate':
return ProviderRecommendation.successRate;
default:
return null;
}
}
class Quote extends SelectableOption {
Quote({
required this.rate,
required this.feeAmount,
required this.networkFee,
required this.transactionFee,
required this.payout,
required this.provider,
required this.paymentType,
required this.recommendations,
this.isBuyAction = true,
this.quoteId,
this.rampId,
this.rampName,
this.rampIconPath,
this.limits,
}) : super(title: provider.isAggregator ? rampName ?? '' : provider.title);
final double rate;
final double feeAmount;
final double networkFee;
final double transactionFee;
final double payout;
final PaymentType paymentType;
final BuyProvider provider;
final String? quoteId;
final List<ProviderRecommendation> recommendations;
String? rampId;
String? rampName;
String? rampIconPath;
bool _isSelected = false;
bool _isBestRate = false;
bool isBuyAction;
Limits? limits;
late FiatCurrency _fiatCurrency;
late CryptoCurrency _cryptoCurrency;
bool get isSelected => _isSelected;
bool get isBestRate => _isBestRate;
FiatCurrency get fiatCurrency => _fiatCurrency;
CryptoCurrency get cryptoCurrency => _cryptoCurrency;
@override
bool get isOptionSelected => this._isSelected;
@override
String get lightIconPath =>
provider.isAggregator ? rampIconPath ?? provider.lightIcon : provider.lightIcon;
@override
String get darkIconPath =>
provider.isAggregator ? rampIconPath ?? provider.darkIcon : provider.darkIcon;
@override
List<String> get badges => recommendations.map((e) => e.title).toList();
@override
String get topLeftSubTitle =>
this.rate > 0 ? '1 $cryptoName = ${rate.toStringAsFixed(2)} $fiatName' : '';
@override
String get bottomLeftSubTitle {
if (limits != null) {
final min = limits!.min;
final max = limits!.max;
return 'min: ${min} ${fiatCurrency.toString()} | max: ${max == double.infinity ? '' : '${max} ${fiatCurrency.toString()}'}';
}
return '';
}
String get fiatName => isBuyAction ? fiatCurrency.toString() : cryptoCurrency.toString();
String get cryptoName => isBuyAction ? cryptoCurrency.toString() : fiatCurrency.toString();
@override
String? get topRightSubTitle => '';
@override
String get topRightSubTitleLightIconPath => provider.isAggregator ? provider.lightIcon : '';
@override
String get topRightSubTitleDarkIconPath => provider.isAggregator ? provider.darkIcon : '';
String get quoteTitle => '${provider.title} - ${paymentType.name}';
String get formatedFee => '$feeAmount ${isBuyAction ? fiatCurrency : cryptoCurrency}';
set setIsSelected(bool isSelected) => _isSelected = isSelected;
set setIsBestRate(bool isBestRate) => _isBestRate = isBestRate;
set setFiatCurrency(FiatCurrency fiatCurrency) => _fiatCurrency = fiatCurrency;
set setCryptoCurrency(CryptoCurrency cryptoCurrency) => _cryptoCurrency = cryptoCurrency;
set setLimits(Limits limits) => this.limits = limits;
factory Quote.fromOnramperJson(Map<String, dynamic> json, bool isBuyAction,
Map<String, dynamic> metaData, PaymentType paymentType) {
final rate = _toDouble(json['rate']) ?? 0.0;
final networkFee = _toDouble(json['networkFee']) ?? 0.0;
final transactionFee = _toDouble(json['transactionFee']) ?? 0.0;
final feeAmount = double.parse((networkFee + transactionFee).toStringAsFixed(2));
final rampId = json['ramp'] as String? ?? '';
final rampData = metaData[rampId] ?? {};
final rampName = rampData['displayName'] as String? ?? '';
final rampIconPath = rampData['svg'] as String? ?? '';
final recommendations = json['recommendations'] != null
? List<String>.from(json['recommendations'] as List<dynamic>)
: <String>[];
final enumRecommendations = recommendations
.map((e) => getRecommendationFromString(e))
.whereType<ProviderRecommendation>()
.toList();
final availablePaymentMethods = json['availablePaymentMethods'] as List<dynamic>? ?? [];
double minLimit = 0.0;
double maxLimit = double.infinity;
for (var paymentMethod in availablePaymentMethods) {
if (paymentMethod is Map<String, dynamic>) {
final details = paymentMethod['details'] as Map<String, dynamic>?;
if (details != null) {
final limits = details['limits'] as Map<String, dynamic>?;
if (limits != null && limits.isNotEmpty) {
final firstLimitEntry = limits.values.first as Map<String, dynamic>?;
if (firstLimitEntry != null) {
minLimit = _toDouble(firstLimitEntry['min'])?.roundToDouble() ?? 0.0;
maxLimit = _toDouble(firstLimitEntry['max'])?.roundToDouble() ?? double.infinity;
break;
}
}
}
}
}
return Quote(
rate: rate,
feeAmount: feeAmount,
networkFee: networkFee,
transactionFee: transactionFee,
payout: json['payout'] as double? ?? 0.0,
rampId: rampId,
rampName: rampName,
rampIconPath: rampIconPath,
paymentType: paymentType,
quoteId: json['quoteId'] as String? ?? '',
recommendations: enumRecommendations,
provider: ProvidersHelper.getProviderByType(ProviderType.onramper)!,
isBuyAction: isBuyAction,
limits: Limits(min: minLimit, max: maxLimit),
);
}
factory Quote.fromMoonPayJson(
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
final rate = isBuyAction
? json['quoteCurrencyPrice'] as double? ?? 0.0
: json['baseCurrencyPrice'] as double? ?? 0.0;
final fee = _toDouble(json['feeAmount']) ?? 0.0;
final networkFee = _toDouble(json['networkFeeAmount']) ?? 0.0;
final transactionFee = _toDouble(json['extraFeeAmount']) ?? 0.0;
final feeAmount = double.parse((fee + networkFee + transactionFee).toStringAsFixed(2));
final baseCurrency = json['baseCurrency'] as Map<String, dynamic>?;
double minLimit = 0.0;
double maxLimit = double.infinity;
if (baseCurrency != null) {
minLimit = _toDouble(baseCurrency['minAmount']) ?? minLimit;
maxLimit = _toDouble(baseCurrency['maxAmount']) ?? maxLimit;
}
return Quote(
rate: rate,
feeAmount: feeAmount,
networkFee: networkFee,
transactionFee: transactionFee,
payout: _toDouble(json['quoteCurrencyAmount']) ?? 0.0,
paymentType: paymentType,
recommendations: [],
quoteId: json['signature'] as String? ?? '',
provider: ProvidersHelper.getProviderByType(ProviderType.moonpay)!,
isBuyAction: isBuyAction,
limits: Limits(min: minLimit, max: maxLimit),
);
}
factory Quote.fromDFXJson(
Map<String, dynamic> json,
bool isBuyAction,
PaymentType paymentType,
) {
final rate = _toDouble(json['exchangeRate']) ?? 0.0;
final fees = json['fees'] as Map<String, dynamic>;
final minVolume = _toDouble(json['minVolume']) ?? 0.0;
final maxVolume = _toDouble(json['maxVolume']) ?? double.infinity;
return Quote(
rate: isBuyAction ? rate : 1 / rate,
feeAmount: _toDouble(json['feeAmount']) ?? 0.0,
networkFee: _toDouble(fees['network']) ?? 0.0,
transactionFee: _toDouble(fees['rate']) ?? 0.0,
payout: _toDouble(json['payout']) ?? 0.0,
paymentType: paymentType,
recommendations: [ProviderRecommendation.lowKyc],
provider: ProvidersHelper.getProviderByType(ProviderType.dfx)!,
isBuyAction: isBuyAction,
limits: Limits(min: minVolume, max: maxVolume),
);
}
factory Quote.fromRobinhoodJson(
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
final networkFee = json['networkFee'] as Map<String, dynamic>;
final processingFee = json['processingFee'] as Map<String, dynamic>;
final networkFeeAmount = _toDouble(networkFee['fiatAmount']) ?? 0.0;
final transactionFeeAmount = _toDouble(processingFee['fiatAmount']) ?? 0.0;
final feeAmount = double.parse((networkFeeAmount + transactionFeeAmount).toStringAsFixed(2));
return Quote(
rate: _toDouble(json['price']) ?? 0.0,
feeAmount: feeAmount,
networkFee: _toDouble(networkFee['fiatAmount']) ?? 0.0,
transactionFee: _toDouble(processingFee['fiatAmount']) ?? 0.0,
payout: _toDouble(json['cryptoAmount']) ?? 0.0,
paymentType: paymentType,
recommendations: [],
provider: ProvidersHelper.getProviderByType(ProviderType.robinhood)!,
isBuyAction: isBuyAction,
limits: Limits(min: 0.0, max: double.infinity),
);
}
factory Quote.fromMeldJson(Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
final quotes = json['quotes'][0] as Map<String, dynamic>;
return Quote(
rate: quotes['exchangeRate'] as double? ?? 0.0,
feeAmount: quotes['totalFee'] as double? ?? 0.0,
networkFee: quotes['networkFee'] as double? ?? 0.0,
transactionFee: quotes['transactionFee'] as double? ?? 0.0,
payout: quotes['payout'] as double? ?? 0.0,
paymentType: paymentType,
recommendations: [],
provider: ProvidersHelper.getProviderByType(ProviderType.meld)!,
isBuyAction: isBuyAction,
limits: Limits(min: 0.0, max: double.infinity),
);
}
static double? _toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
} else if (value is double) {
return value;
} else if (value is String) {
return double.tryParse(value);
}
return null;
}
}

View file

@ -1,13 +1,17 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -15,10 +19,12 @@ import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider extends BuyProvider { class DFXBuyProvider extends BuyProvider {
DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM}) DFXBuyProvider(
{required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM); : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM);
static const _baseUrl = 'api.dfx.swiss'; static const _baseUrl = 'api.dfx.swiss';
// static const _signMessagePath = '/v1/auth/signMessage'; // static const _signMessagePath = '/v1/auth/signMessage';
static const _authPath = '/v1/auth'; static const _authPath = '/v1/auth';
static const walletName = 'CakeWallet'; static const walletName = 'CakeWallet';
@ -35,24 +41,8 @@ class DFXBuyProvider extends BuyProvider {
@override @override
String get darkIcon => 'assets/images/dfx_dark.png'; String get darkIcon => 'assets/images/dfx_dark.png';
String get assetOut { @override
switch (wallet.type) { bool get isAggregator => false;
case WalletType.bitcoin:
return 'BTC';
case WalletType.bitcoinCash:
return 'BCH';
case WalletType.litecoin:
return 'LTC';
case WalletType.monero:
return 'XMR';
case WalletType.ethereum:
return 'ETH';
case WalletType.polygon:
return 'MATIC';
default:
throw Exception("WalletType is not available for DFX ${wallet.type}");
}
}
String get blockchain { String get blockchain {
switch (wallet.type) { switch (wallet.type) {
@ -60,21 +50,13 @@ class DFXBuyProvider extends BuyProvider {
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
case WalletType.litecoin: case WalletType.litecoin:
return 'Bitcoin'; return 'Bitcoin';
case WalletType.monero:
return 'Monero';
case WalletType.ethereum:
return 'Ethereum';
case WalletType.polygon:
return 'Polygon';
default: default:
throw Exception("WalletType is not available for DFX ${wallet.type}"); return walletTypeToString(wallet.type);
} }
} }
String get walletAddress =>
wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
Future<String> getSignMessage() async => Future<String> getSignMessage(String walletAddress) async =>
"By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_Blockchain_address._Your_ID:_$walletAddress"; "By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_Blockchain_address._Your_ID:_$walletAddress";
// // Lets keep this just in case, but we can avoid this API Call // // Lets keep this just in case, but we can avoid this API Call
@ -92,8 +74,9 @@ class DFXBuyProvider extends BuyProvider {
// } // }
// } // }
Future<String> auth() async { Future<String> auth(String walletAddress) async {
final signMessage = await getSignature(await getSignMessage()); final signMessage = await getSignature(
await getSignMessage(walletAddress), walletAddress);
final requestBody = jsonEncode({ final requestBody = jsonEncode({
'wallet': walletName, 'wallet': walletName,
@ -120,7 +103,7 @@ class DFXBuyProvider extends BuyProvider {
} }
} }
Future<String> getSignature(String message) async { Future<String> getSignature(String message, String walletAddress) async {
switch (wallet.type) { switch (wallet.type) {
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
@ -135,8 +118,178 @@ class DFXBuyProvider extends BuyProvider {
} }
} }
Future<Map<String, dynamic>> fetchFiatCredentials(String fiatCurrency) async {
final url = Uri.https(_baseUrl, '/v1/fiat');
try {
final response = await http.get(url, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as List<dynamic>;
for (final item in data) {
if (item['name'] == fiatCurrency) return item as Map<String, dynamic>;
}
log('DFX does not support fiat: $fiatCurrency');
return {};
} else {
log('DFX Failed to fetch fiat currencies: ${response.statusCode}');
return {};
}
} catch (e) {
print('DFX Error fetching fiat currencies: $e');
return {};
}
}
Future<Map<String, dynamic>> fetchAssetCredential(String assetsName) async {
final url = Uri.https(_baseUrl, '/v1/asset', {'blockchains': blockchain});
try {
final response = await http.get(url, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
if (responseData is List && responseData.isNotEmpty) {
return responseData.first as Map<String, dynamic>;
} else if (responseData is Map<String, dynamic>) {
return responseData;
} else {
log('DFX: Does not support this asset name : ${blockchain}');
}
} else {
log('DFX: Failed to fetch assets: ${response.statusCode}');
}
} catch (e) {
log('DFX: Error fetching assets: $e');
}
return {};
}
Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
final List<PaymentMethod> paymentMethods = [];
if (isBuyAction) {
final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency);
if (fiatBuyCredentials.isNotEmpty) {
fiatBuyCredentials.forEach((key, value) {
if (key == 'limits') {
final limits = value as Map<String, dynamic>;
limits.forEach((paymentMethodKey, paymentMethodValue) {
final min = _toDouble(paymentMethodValue['minVolume']);
final max = _toDouble(paymentMethodValue['maxVolume']);
if (min != null && max != null && min > 0 && max > 0) {
final paymentMethod = PaymentMethod.fromDFX(
paymentMethodKey, _getPaymentTypeByString(paymentMethodKey));
paymentMethods.add(paymentMethod);
}
});
}
});
}
} else {
final assetCredentials = await fetchAssetCredential(cryptoCurrency);
if (assetCredentials.isNotEmpty) {
if (assetCredentials['sellable'] == true) {
final availablePaymentTypes = [
PaymentType.bankTransfer,
PaymentType.creditCard,
PaymentType.sepa
];
availablePaymentTypes.forEach((element) {
final paymentMethod = PaymentMethod.fromDFX(normalizePaymentMethod(element)!, element);
paymentMethods.add(paymentMethod);
});
}
}
}
return paymentMethods;
}
@override @override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async { Future<List<Quote>?> fetchQuote(
{required CryptoCurrency cryptoCurrency,
required FiatCurrency fiatCurrency,
required double amount,
required bool isBuyAction,
required String walletAddress,
PaymentType? paymentType,
String? countryCode}) async {
/// if buying with any currency other than eur or chf then DFX is not supported
if (isBuyAction && (fiatCurrency != FiatCurrency.eur && fiatCurrency != FiatCurrency.chf)) {
return null;
}
String? paymentMethod;
if (paymentType != null && paymentType != PaymentType.all) {
paymentMethod = normalizePaymentMethod(paymentType);
if (paymentMethod == null) paymentMethod = paymentType.name;
} else {
paymentMethod = 'Bank';
}
final action = isBuyAction ? 'buy' : 'sell';
if (isBuyAction && cryptoCurrency != wallet.currency) return null;
final fiatCredentials = await fetchFiatCredentials(fiatCurrency.name.toString());
if (fiatCredentials['id'] == null) return null;
final assetCredentials = await fetchAssetCredential(cryptoCurrency.title.toString());
if (assetCredentials['id'] == null) return null;
log('DFX: Fetching $action quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod');
final url = Uri.https(_baseUrl, '/v1/$action/quote');
final headers = {'accept': 'application/json', 'Content-Type': 'application/json'};
final body = jsonEncode({
'currency': {'id': fiatCredentials['id'] as int},
'asset': {'id': assetCredentials['id']},
'amount': amount,
'targetAmount': 0,
'paymentMethod': paymentMethod,
'discountCode': ''
});
try {
final response = await http.put(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
if (response.statusCode == 200) {
if (responseData is Map<String, dynamic>) {
final paymentType = _getPaymentTypeByString(responseData['paymentMethod'] as String?);
final quote = Quote.fromDFXJson(responseData, isBuyAction, paymentType);
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
return [quote];
} else {
print('DFX: Unexpected data type: ${responseData.runtimeType}');
return null;
}
} else {
if (responseData is Map<String, dynamic> && responseData.containsKey('message')) {
print('DFX Error: ${responseData['message']}');
} else {
print('DFX Failed to fetch buy quote: ${response.statusCode}');
}
return null;
}
} catch (e) {
print('DFX Error fetching buy quote: $e');
return null;
}
}
Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) { if (wallet.isHardwareWallet) {
if (!ledgerVM!.isConnected) { if (!ledgerVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices, await Navigator.of(context).pushNamed(Routes.connectDevices,
@ -152,26 +305,21 @@ class DFXBuyProvider extends BuyProvider {
} }
try { try {
final assetOut = this.assetOut; final actionType = isBuyAction ? '/buy' : '/sell';
final blockchain = this.blockchain;
final actionType = isBuyAction == true ? '/buy' : '/sell';
final accessToken = await auth(); final accessToken = await auth(cryptoCurrencyAddress);
final uri = Uri.https('services.dfx.swiss', actionType, { final uri = Uri.https('services.dfx.swiss', actionType, {
'session': accessToken, 'session': accessToken,
'lang': 'en', 'lang': 'en',
'asset-out': assetOut, 'asset-out': isBuyAction ? quote.cryptoCurrency.toString() : quote.fiatCurrency.toString(),
'blockchain': blockchain, 'blockchain': blockchain,
'asset-in': 'EUR', 'asset-in': isBuyAction ? quote.fiatCurrency.toString() : quote.cryptoCurrency.toString(),
'amount': amount.toString() //TODO: Amount does not work
}); });
if (await canLaunchUrl(uri)) { if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) { await launchUrl(uri, mode: LaunchMode.externalApplication);
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [title, uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else { } else {
throw Exception('Could not launch URL'); throw Exception('Could not launch URL');
} }
@ -187,4 +335,39 @@ class DFXBuyProvider extends BuyProvider {
}); });
} }
} }
String? normalizePaymentMethod(PaymentType paymentMethod) {
switch (paymentMethod) {
case PaymentType.bankTransfer:
return 'Bank';
case PaymentType.creditCard:
return 'Card';
case PaymentType.sepa:
return 'Instant';
default:
return null;
}
}
PaymentType _getPaymentTypeByString(String? paymentMethod) {
switch (paymentMethod) {
case 'Bank':
return PaymentType.bankTransfer;
case 'Card':
return PaymentType.creditCard;
case 'Instant':
return PaymentType.sepa;
default:
return PaymentType.all;
}
}
double? _toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
} else if (value is double) {
return value;
}
return null;
}
} }

View file

@ -0,0 +1,262 @@
import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class MeldBuyProvider extends BuyProvider {
MeldBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
static const _isProduction = false;
static const _baseUrl = _isProduction ? 'api.meld.io' : 'api-sb.meld.io';
static const _providersProperties = '/service-providers/properties';
static const _paymentMethodsPath = '/payment-methods';
static const _quotePath = '/payments/crypto/quote';
static const String sandboxUrl = 'sb.fluidmoney.xyz';
static const String productionUrl = 'fluidmoney.xyz';
static const String _baseWidgetUrl = _isProduction ? productionUrl : sandboxUrl;
static String get _testApiKey => secrets.meldTestApiKey;
static String get _testPublicKey => '' ; //secrets.meldTestPublicKey;
@override
String get title => 'Meld';
@override
String get providerDescription => 'Meld Buy Provider';
@override
String get lightIcon => 'assets/images/meld_logo.svg';
@override
String get darkIcon => 'assets/images/meld_logo.svg';
@override
bool get isAggregator => true;
@override
Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
final params = {'fiatCurrencies': fiatCurrency, 'statuses': 'LIVE,RECENTLY_ADDED,BUILDING'};
final path = '$_providersProperties$_paymentMethodsPath';
final url = Uri.https(_baseUrl, path, params);
try {
final response = await http.get(
url,
headers: {
'Authorization': _isProduction ? '' : _testApiKey,
'Meld-Version': '2023-12-19',
'accept': 'application/json',
'content-type': 'application/json',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as List<dynamic>;
final paymentMethods =
data.map((e) => PaymentMethod.fromMeldJson(e as Map<String, dynamic>)).toList();
return paymentMethods;
} else {
print('Meld: Failed to fetch payment types');
return List<PaymentMethod>.empty();
}
} catch (e) {
print('Meld: Failed to fetch payment types: $e');
return List<PaymentMethod>.empty();
}
}
@override
Future<List<Quote>?> fetchQuote(
{required CryptoCurrency cryptoCurrency,
required FiatCurrency fiatCurrency,
required double amount,
required bool isBuyAction,
required String walletAddress,
PaymentType? paymentType,
String? countryCode}) async {
String? paymentMethod;
if (paymentType != null && paymentType != PaymentType.all) {
paymentMethod = normalizePaymentMethod(paymentType);
if (paymentMethod == null) paymentMethod = paymentType.name;
}
log('Meld: Fetching buy quote: ${isBuyAction ? cryptoCurrency : fiatCurrency} -> ${isBuyAction ? fiatCurrency : cryptoCurrency}, amount: $amount');
final url = Uri.https(_baseUrl, _quotePath);
final headers = {
'Authorization': _testApiKey,
'Meld-Version': '2023-12-19',
'accept': 'application/json',
'content-type': 'application/json',
};
final body = jsonEncode({
'countryCode': countryCode,
'destinationCurrencyCode': isBuyAction ? fiatCurrency.name : cryptoCurrency.title,
'sourceAmount': amount,
'sourceCurrencyCode': isBuyAction ? cryptoCurrency.title : fiatCurrency.name,
if (paymentMethod != null) 'paymentMethod': paymentMethod,
});
try {
final response = await http.post(url, headers: headers, body: body);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
final paymentType = _getPaymentTypeByString(data['paymentMethodType'] as String?);
final quote = Quote.fromMeldJson(data, isBuyAction, paymentType);
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
return [quote];
} else {
return null;
}
} catch (e) {
print('Error fetching buy quote: $e');
return null;
}
}
Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
final actionType = isBuyAction ? 'BUY' : 'SELL';
final params = {
'publicKey': _isProduction ? '' : _testPublicKey,
'countryCode': countryCode,
//'paymentMethodType': normalizePaymentMethod(paymentMethod.paymentMethodType),
'sourceAmount': amount.toString(),
'sourceCurrencyCode': quote.fiatCurrency,
'destinationCurrencyCode': quote.cryptoCurrency,
'walletAddress': cryptoCurrencyAddress,
'transactionType': actionType
};
final uri = Uri.https(_baseWidgetUrl, '', params);
try {
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "Meld",
alertContent: S.of(context).buy_provider_unavailable + ': $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
}
String? normalizePaymentMethod(PaymentType paymentType) {
switch (paymentType) {
case PaymentType.creditCard:
return 'CREDIT_DEBIT_CARD';
case PaymentType.applePay:
return 'APPLE_PAY';
case PaymentType.googlePay:
return 'GOOGLE_PAY';
case PaymentType.neteller:
return 'NETELLER';
case PaymentType.skrill:
return 'SKRILL';
case PaymentType.sepa:
return 'SEPA';
case PaymentType.sepaInstant:
return 'SEPA_INSTANT';
case PaymentType.ach:
return 'ACH';
case PaymentType.achInstant:
return 'INSTANT_ACH';
case PaymentType.Khipu:
return 'KHIPU';
case PaymentType.ovo:
return 'OVO';
case PaymentType.zaloPay:
return 'ZALOPAY';
case PaymentType.zaloBankTransfer:
return 'ZA_BANK_TRANSFER';
case PaymentType.gcash:
return 'GCASH';
case PaymentType.imps:
return 'IMPS';
case PaymentType.dana:
return 'DANA';
case PaymentType.ideal:
return 'IDEAL';
default:
return null;
}
}
PaymentType _getPaymentTypeByString(String? paymentMethod) {
switch (paymentMethod?.toUpperCase()) {
case 'CREDIT_DEBIT_CARD':
return PaymentType.creditCard;
case 'APPLE_PAY':
return PaymentType.applePay;
case 'GOOGLE_PAY':
return PaymentType.googlePay;
case 'NETELLER':
return PaymentType.neteller;
case 'SKRILL':
return PaymentType.skrill;
case 'SEPA':
return PaymentType.sepa;
case 'SEPA_INSTANT':
return PaymentType.sepaInstant;
case 'ACH':
return PaymentType.ach;
case 'INSTANT_ACH':
return PaymentType.achInstant;
case 'KHIPU':
return PaymentType.Khipu;
case 'OVO':
return PaymentType.ovo;
case 'ZALOPAY':
return PaymentType.zaloPay;
case 'ZA_BANK_TRANSFER':
return PaymentType.zaloBankTransfer;
case 'GCASH':
return PaymentType.gcash;
case 'IMPS':
return PaymentType.imps;
case 'DANA':
return PaymentType.dana;
case 'IDEAL':
return PaymentType.ideal;
default:
return PaymentType.all;
}
}
}

View file

@ -1,19 +1,20 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_exception.dart'; import 'package:cake_wallet/buy/buy_exception.dart';
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_provider_description.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -39,6 +40,15 @@ class MoonPayProvider extends BuyProvider {
static const _baseBuyProductUrl = 'buy.moonpay.com'; static const _baseBuyProductUrl = 'buy.moonpay.com';
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
static const _apiUrl = 'https://api.moonpay.com'; static const _apiUrl = 'https://api.moonpay.com';
static const _baseUrl = 'api.moonpay.com';
static const _currenciesPath = '/v3/currencies';
static const _buyQuote = '/buy_quote';
static const _sellQuote = '/sell_quote';
static const _transactionsSuffix = '/v1/transactions';
final String baseBuyUrl;
final String baseSellUrl;
@override @override
String get providerDescription => String get providerDescription =>
@ -53,6 +63,17 @@ class MoonPayProvider extends BuyProvider {
@override @override
String get darkIcon => 'assets/images/moonpay_dark.png'; String get darkIcon => 'assets/images/moonpay_dark.png';
@override
bool get isAggregator => false;
static String get _apiKey => secrets.moonPayApiKey;
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId=';
static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
static String themeToMoonPayTheme(ThemeBase theme) { static String themeToMoonPayTheme(ThemeBase theme) {
switch (theme.type) { switch (theme.type) {
case ThemeType.bright: case ThemeType.bright:
@ -63,28 +84,12 @@ class MoonPayProvider extends BuyProvider {
} }
} }
static String get _apiKey => secrets.moonPayApiKey;
final String baseBuyUrl;
final String baseSellUrl;
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId=';
static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
Future<String> getMoonpaySignature(String query) async { Future<String> getMoonpaySignature(String query) async {
final uri = Uri.https(_cIdBaseUrl, "/api/moonpay"); final uri = Uri.https(_cIdBaseUrl, "/api/moonpay");
final response = await post( final response = await post(uri,
uri, headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey},
headers: { body: json.encode({'query': query}));
'Content-Type': 'application/json',
'x-api-key': _exchangeHelperApiKey,
},
body: json.encode({'query': query}),
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
return (jsonDecode(response.body) as Map<String, dynamic>)['signature'] as String; return (jsonDecode(response.body) as Map<String, dynamic>)['signature'] as String;
@ -94,85 +99,195 @@ class MoonPayProvider extends BuyProvider {
} }
} }
Future<Uri> requestSellMoonPayUrl({ Future<Map<String, dynamic>> fetchFiatCredentials(
required CryptoCurrency currency, String fiatCurrency, String cryptocurrency, String? paymentMethod) async {
required String refundWalletAddress, final params = {'baseCurrencyCode': fiatCurrency.toLowerCase(), 'apiKey': _apiKey};
required SettingsStore settingsStore,
}) async {
final params = {
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
'language': settingsStore.languageCode,
'colorCode': settingsStore.currentTheme.type == ThemeType.dark
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
'defaultCurrencyCode': _normalizeCurrency(currency),
'refundWalletAddress': refundWalletAddress,
};
if (_apiKey.isNotEmpty) { if (paymentMethod != null) params['paymentMethod'] = paymentMethod;
params['apiKey'] = _apiKey;
final path = '$_currenciesPath/${cryptocurrency.toLowerCase()}/limits';
final url = Uri.https(_baseUrl, path, params);
try {
final response = await get(url, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
print('MoonPay does not support fiat: $fiatCurrency');
return {};
}
} catch (e) {
print('MoonPay Error fetching fiat currencies: $e');
return {};
} }
final originalUri = Uri.https(
baseSellUrl,
'',
params,
);
if (isTestEnvironment) {
return originalUri;
}
final signature = await getMoonpaySignature('?${originalUri.query}');
final query = Map<String, dynamic>.from(originalUri.queryParameters);
query['signature'] = signature;
final signedUri = originalUri.replace(queryParameters: query);
return signedUri;
} }
// BUY: Future<List<PaymentMethod>> getAvailablePaymentTypes(
static const _currenciesSuffix = '/v3/currencies'; String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
static const _quoteSuffix = '/buy_quote'; final List<PaymentMethod> paymentMethods = [];
static const _transactionsSuffix = '/v1/transactions';
static const _ipAddressSuffix = '/v4/ip_address'; if (isBuyAction) {
final fiatBuyCredentials = await fetchFiatCredentials(fiatCurrency, cryptoCurrency, null);
if (fiatBuyCredentials.isNotEmpty) {
final paymentMethod = fiatBuyCredentials['paymentMethod'] as String?;
paymentMethods.add(PaymentMethod.fromMoonPayJson(
fiatBuyCredentials, _getPaymentTypeByString(paymentMethod)));
return paymentMethods;
}
}
return paymentMethods;
}
@override
Future<List<Quote>?> fetchQuote(
{required CryptoCurrency cryptoCurrency,
required FiatCurrency fiatCurrency,
required double amount,
required bool isBuyAction,
required String walletAddress,
PaymentType? paymentType,
String? countryCode}) async {
String? paymentMethod;
if (paymentType != null && paymentType != PaymentType.all) {
paymentMethod = normalizePaymentMethod(paymentType);
if (paymentMethod == null) paymentMethod = paymentType.name;
} else {
paymentMethod = 'credit_debit_card';
}
final action = isBuyAction ? 'buy' : 'sell';
final formattedCryptoCurrency = _normalizeCurrency(cryptoCurrency);
final baseCurrencyCode =
isBuyAction ? fiatCurrency.name.toLowerCase() : cryptoCurrency.title.toLowerCase();
Future<Uri> requestBuyMoonPayUrl({
required CryptoCurrency currency,
required SettingsStore settingsStore,
required String walletAddress,
String? amount,
}) async {
final params = { final params = {
'theme': themeToMoonPayTheme(settingsStore.currentTheme), 'baseCurrencyCode': baseCurrencyCode,
'language': settingsStore.languageCode, 'baseCurrencyAmount': amount.toString(),
'colorCode': settingsStore.currentTheme.type == ThemeType.dark 'amount': amount.toString(),
'paymentMethod': paymentMethod,
'areFeesIncluded': 'false',
'apiKey': _apiKey
};
log('MoonPay: Fetching $action quote: ${isBuyAction ? formattedCryptoCurrency : fiatCurrency.name.toLowerCase()} -> ${isBuyAction ? baseCurrencyCode : formattedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod');
final quotePath = isBuyAction ? _buyQuote : _sellQuote;
final path = '$_currenciesPath/$formattedCryptoCurrency$quotePath';
final url = Uri.https(_baseUrl, path, params);
try {
final response = await get(url);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
// Check if the response is for the correct fiat currency
if (isBuyAction) {
final fiatCurrencyCode = data['baseCurrencyCode'] as String?;
if (fiatCurrencyCode == null || fiatCurrencyCode != fiatCurrency.name.toLowerCase())
return null;
} else {
final quoteCurrency = data['quoteCurrency'] as Map<String, dynamic>?;
if (quoteCurrency == null || quoteCurrency['code'] != fiatCurrency.name.toLowerCase())
return null;
}
final paymentMethods = data['paymentMethod'] as String?;
final quote =
Quote.fromMoonPayJson(data, isBuyAction, _getPaymentTypeByString(paymentMethods));
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
return [quote];
} else {
print('Moon Pay: Error fetching buy quote: ');
return null;
}
} catch (e) {
print('Moon Pay: Error fetching buy quote: $e');
return null;
}
}
@override
Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
final Map<String, String> params = {
'theme': themeToMoonPayTheme(_settingsStore.currentTheme),
'language': _settingsStore.languageCode,
'colorCode': _settingsStore.currentTheme.type == ThemeType.dark
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}' ? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}', : '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
'baseCurrencyCode': settingsStore.fiatCurrency.title, 'baseCurrencyCode': isBuyAction ? quote.fiatCurrency.name : quote.cryptoCurrency.name,
'baseCurrencyAmount': amount ?? '0', 'baseCurrencyAmount': amount.toString(),
'currencyCode': _normalizeCurrency(currency), 'walletAddress': cryptoCurrencyAddress,
'walletAddress': walletAddress,
'lockAmount': 'false', 'lockAmount': 'false',
'showAllCurrencies': 'false', 'showAllCurrencies': 'false',
'showWalletAddressForm': 'false', 'showWalletAddressForm': 'false',
'enabledPaymentMethods': if (isBuyAction)
'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment', 'enabledPaymentMethods': normalizePaymentMethod(quote.paymentType) ??
'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment',
if (!isBuyAction) 'refundWalletAddress': cryptoCurrencyAddress
}; };
if (_apiKey.isNotEmpty) { if (isBuyAction) params['currencyCode'] = quote.cryptoCurrency.name;
params['apiKey'] = _apiKey; if (!isBuyAction) params['quoteCurrencyCode'] = quote.cryptoCurrency.name;
}
final originalUri = Uri.https( try {
baseBuyUrl, {
'', final uri = await requestMoonPayUrl(
params, walletAddress: cryptoCurrencyAddress,
); settingsStore: _settingsStore,
isBuyAction: isBuyAction,
amount: amount.toString(),
params: params);
if (isTestEnvironment) { if (await canLaunchUrl(uri)) {
return originalUri; await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
throw Exception('Could not launch URL');
}
}
} catch (e) {
if (context.mounted) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
} }
}
Future<Uri> requestMoonPayUrl({
required String walletAddress,
required SettingsStore settingsStore,
required bool isBuyAction,
required Map<String, String> params,
String? amount,
}) async {
if (_apiKey.isNotEmpty) params['apiKey'] = _apiKey;
final baseUrl = isBuyAction ? baseBuyUrl : baseSellUrl;
final originalUri = Uri.https(baseUrl, '', params);
if (isTestEnvironment) return originalUri;
final signature = await getMoonpaySignature('?${originalUri.query}'); final signature = await getMoonpaySignature('?${originalUri.query}');
final query = Map<String, dynamic>.from(originalUri.queryParameters); final query = Map<String, dynamic>.from(originalUri.queryParameters);
@ -181,33 +296,6 @@ class MoonPayProvider extends BuyProvider {
return signedUri; return signedUri;
} }
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
final url = _apiUrl +
_currenciesSuffix +
'/$currencyCode' +
_quoteSuffix +
'/?apiKey=' +
_apiKey +
'&baseCurrencyAmount=' +
amount +
'&baseCurrencyCode=' +
sourceCurrency.toLowerCase();
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode != 200) {
throw BuyException(title: providerDescription, content: 'Quote is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final sourceAmount = responseJSON['totalAmount'] as double;
final destAmount = responseJSON['quoteCurrencyAmount'] as double;
final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int;
return BuyAmount(
sourceAmount: sourceAmount, destAmount: destAmount, minAmount: minSourceAmount);
}
Future<Order> findOrderById(String id) async { Future<Order> findOrderById(String id) async {
final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey; final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey;
final uri = Uri.parse(url); final uri = Uri.parse(url);
@ -235,74 +323,83 @@ class MoonPayProvider extends BuyProvider {
walletId: wallet.id); walletId: wallet.id);
} }
static Future<bool> onEnabled() async {
final url = _apiUrl + _ipAddressSuffix + '?apiKey=' + _apiKey;
var isBuyEnable = false;
final uri = Uri.parse(url);
final response = await get(uri);
try {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
isBuyEnable = responseJSON['isBuyAllowed'] as bool;
} catch (e) {
isBuyEnable = false;
print(e.toString());
}
return isBuyEnable;
}
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
try {
late final Uri uri;
if (isBuyAction ?? true) {
uri = await requestBuyMoonPayUrl(
currency: wallet.currency,
walletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
} else {
uri = await requestSellMoonPayUrl(
currency: wallet.currency,
refundWalletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
}
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
if (context.mounted) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}
}
String _normalizeCurrency(CryptoCurrency currency) { String _normalizeCurrency(CryptoCurrency currency) {
if (currency == CryptoCurrency.maticpoly) { if (currency.tag == 'POLY') {
return "POL_POLYGON"; return '${currency.title.toLowerCase()}_polygon';
} else if (currency == CryptoCurrency.matic) { }
return "POL";
if (currency.tag == 'TRX') {
return '${currency.title.toLowerCase()}_trx';
} }
return currency.toString().toLowerCase(); return currency.toString().toLowerCase();
} }
String? normalizePaymentMethod(PaymentType paymentMethod) {
switch (paymentMethod) {
case PaymentType.creditCard:
return 'credit_debit_card';
case PaymentType.debitCard:
return 'credit_debit_card';
case PaymentType.ach:
return 'ach_bank_transfer';
case PaymentType.applePay:
return 'apple_pay';
case PaymentType.googlePay:
return 'google_pay';
case PaymentType.sepa:
return 'sepa_bank_transfer';
case PaymentType.paypal:
return 'paypal';
case PaymentType.sepaOpenBankingPayment:
return 'sepa_open_banking_payment';
case PaymentType.gbpOpenBankingPayment:
return 'gbp_open_banking_payment';
case PaymentType.lowCostAch:
return 'low_cost_ach';
case PaymentType.mobileWallet:
return 'mobile_wallet';
case PaymentType.pixInstantPayment:
return 'pix_instant_payment';
case PaymentType.yellowCardBankTransfer:
return 'yellow_card_bank_transfer';
case PaymentType.fiatBalance:
return 'fiat_balance';
default:
return null;
}
}
PaymentType _getPaymentTypeByString(String? paymentMethod) {
switch (paymentMethod) {
case 'ach_bank_transfer':
return PaymentType.ach;
case 'apple_pay':
return PaymentType.applePay;
case 'credit_debit_card':
return PaymentType.creditCard;
case 'fiat_balance':
return PaymentType.fiatBalance;
case 'gbp_open_banking_payment':
return PaymentType.gbpOpenBankingPayment;
case 'google_pay':
return PaymentType.googlePay;
case 'low_cost_ach':
return PaymentType.lowCostAch;
case 'mobile_wallet':
return PaymentType.mobileWallet;
case 'paypal':
return PaymentType.paypal;
case 'pix_instant_payment':
return PaymentType.pixInstantPayment;
case 'sepa_bank_transfer':
return PaymentType.sepa;
case 'sepa_open_banking_payment':
return PaymentType.sepaOpenBankingPayment;
case 'yellow_card_bank_transfer':
return PaymentType.yellowCardBankTransfer;
default:
return PaymentType.all;
}
}
} }

View file

@ -1,13 +1,19 @@
import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class OnRamperBuyProvider extends BuyProvider { class OnRamperBuyProvider extends BuyProvider {
@ -16,9 +22,15 @@ class OnRamperBuyProvider extends BuyProvider {
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null); : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
static const _baseUrl = 'buy.onramper.com'; static const _baseUrl = 'buy.onramper.com';
static const _baseApiUrl = 'api.onramper.com';
static const quotes = '/quotes';
static const paymentTypes = '/payment-types';
static const supported = '/supported';
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
String get _apiKey => secrets.onramperApiKey;
@override @override
String get title => 'Onramper'; String get title => 'Onramper';
@ -31,74 +43,327 @@ class OnRamperBuyProvider extends BuyProvider {
@override @override
String get darkIcon => 'assets/images/onramper_dark.png'; String get darkIcon => 'assets/images/onramper_dark.png';
String get _apiKey => secrets.onramperApiKey; @override
bool get isAggregator => true;
String get _normalizeCryptoCurrency { Future<List<PaymentMethod>> getAvailablePaymentTypes(
switch (wallet.currency) { String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
case CryptoCurrency.ltc: final params = {
return "LTC_LITECOIN"; 'fiatCurrency': fiatCurrency,
case CryptoCurrency.xmr: 'type': isBuyAction ? 'buy' : 'sell',
return "XMR_MONERO"; 'isRecurringPayment': 'false'
case CryptoCurrency.bch: };
return "BCH_BITCOINCASH";
case CryptoCurrency.nano: final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params);
return "XNO_NANO";
default: try {
return wallet.currency.title; final response =
await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'});
if (response.statusCode == 200) {
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> message = data['message'] as List<dynamic>;
return message
.map((item) => PaymentMethod.fromOnramperJson(item as Map<String, dynamic>))
.toList();
} else {
print('Failed to fetch available payment types');
return [];
}
} catch (e) {
print('Failed to fetch available payment types: $e');
return [];
} }
} }
String getColorStr(Color color) { Future<Map<String, dynamic>> getOnrampMetadata() async {
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); final url = Uri.https(_baseApiUrl, '$supported/onramps/all');
try {
final response =
await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'});
if (response.statusCode == 200) {
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> onramps = data['message'] as List<dynamic>;
final Map<String, dynamic> result = {
for (var onramp in onramps)
(onramp['id'] as String): {
'displayName': onramp['displayName'] as String,
'svg': onramp['icons']['svg'] as String
}
};
return result;
} else {
print('Failed to fetch onramp metadata');
return {};
}
} catch (e) {
print('Error occurred: $e');
return {};
}
} }
Uri requestOnramperUrl(BuildContext context, bool? isBuyAction) { @override
String primaryColor, Future<List<Quote>?> fetchQuote(
secondaryColor, {required CryptoCurrency cryptoCurrency,
primaryTextColor, required FiatCurrency fiatCurrency,
secondaryTextColor, required double amount,
containerColor, required bool isBuyAction,
cardColor; required String walletAddress,
PaymentType? paymentType,
String? countryCode}) async {
String? paymentMethod;
primaryColor = getColorStr(Theme.of(context).primaryColor); if (paymentType != null && paymentType != PaymentType.all) {
secondaryColor = getColorStr(Theme.of(context).colorScheme.background); paymentMethod = normalizePaymentMethod(paymentType);
primaryTextColor = if (paymentMethod == null) paymentMethod = paymentType.name;
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor); }
secondaryTextColor = getColorStr(
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor); final actionType = isBuyAction ? 'buy' : 'sell';
containerColor = getColorStr(Theme.of(context).colorScheme.background);
cardColor = getColorStr(Theme.of(context).cardColor); final normalizedCryptoCurrency = _getNormalizeCryptoCurrency(cryptoCurrency);
final params = {
'amount': amount.toString(),
if (paymentMethod != null) 'paymentMethod': paymentMethod,
'clientName': 'CakeWallet',
'type': actionType,
'walletAddress': walletAddress,
'isRecurringPayment': 'false',
'input': 'source',
};
log('Onramper: Fetching $actionType quote: ${isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name} -> ${isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod');
final sourceCurrency = isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency;
final destinationCurrency = isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name;
final url = Uri.https(_baseApiUrl, '$quotes/${sourceCurrency}/${destinationCurrency}', params);
final headers = {'Authorization': _apiKey, 'accept': 'application/json'};
try {
final response = await http.get(url, headers: headers);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as List<dynamic>;
if (data.isEmpty) return null;
List<Quote> validQuotes = [];
final onrampMetadata = await getOnrampMetadata();
for (var item in data) {
if (item['errors'] != null) continue;
final paymentMethod = (item as Map<String, dynamic>)['paymentMethod'] as String;
final rampId = item['ramp'] as String?;
final rampMetaData = onrampMetadata[rampId] as Map<String, dynamic>?;
if (rampMetaData == null) continue;
final quote = Quote.fromOnramperJson(
item, isBuyAction, onrampMetadata, _getPaymentTypeByString(paymentMethod));
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
validQuotes.add(quote);
}
if (validQuotes.isEmpty) return null;
return validQuotes;
} else {
print('Onramper: Failed to fetch rate');
return null;
}
} catch (e) {
print('Onramper: Failed to fetch rate $e');
return null;
}
}
Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
final actionType = isBuyAction ? 'buy' : 'sell';
final prefix = actionType == 'sell' ? actionType + '_' : '';
final primaryColor = getColorStr(Theme.of(context).primaryColor);
final secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
final primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
final secondaryTextColor =
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
final containerColor = getColorStr(Theme.of(context).colorScheme.background);
var cardColor = getColorStr(Theme.of(context).cardColor);
if (_settingsStore.currentTheme.title == S.current.high_contrast_theme) { if (_settingsStore.currentTheme.title == S.current.high_contrast_theme) {
cardColor = getColorStr(Colors.white); cardColor = getColorStr(Colors.white);
} }
final networkName = final defaultCrypto = _getNormalizeCryptoCurrency(quote.cryptoCurrency);
wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
return Uri.https(_baseUrl, '', <String, dynamic>{ final paymentMethod = normalizePaymentMethod(quote.paymentType);
final uri = Uri.https(_baseUrl, '', {
'apiKey': _apiKey, 'apiKey': _apiKey,
'defaultCrypto': _normalizeCryptoCurrency, 'mode': actionType,
'sell_defaultCrypto': _normalizeCryptoCurrency, '${prefix}defaultFiat': quote.fiatCurrency.name,
'networkWallets': '${networkName}:${wallet.walletAddresses.address}', '${prefix}defaultCrypto': defaultCrypto,
'${prefix}defaultAmount': amount.toString(),
if (paymentMethod != null) '${prefix}defaultPaymentMethod': paymentMethod,
'onlyOnramps': quote.rampId,
'networkWallets': '$defaultCrypto:$cryptoCurrencyAddress',
'walletAddress': cryptoCurrencyAddress,
'supportSwap': "false", 'supportSwap': "false",
'primaryColor': primaryColor, 'primaryColor': primaryColor,
'secondaryColor': secondaryColor, 'secondaryColor': secondaryColor,
'containerColor': containerColor,
'primaryTextColor': primaryTextColor, 'primaryTextColor': primaryTextColor,
'secondaryTextColor': secondaryTextColor, 'secondaryTextColor': secondaryTextColor,
'containerColor': containerColor,
'cardColor': cardColor, 'cardColor': cardColor,
'mode': isBuyAction == true ? 'buy' : 'sell',
}); });
}
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async { if (await canLaunchUrl(uri)) {
final uri = requestOnramperUrl(context, isBuyAction); await launchUrl(uri, mode: LaunchMode.externalApplication);
if (DeviceInfo.instance.isMobile) {
Navigator.of(context)
.pushNamed(Routes.webViewPage, arguments: [title, uri]);
} else { } else {
await launchUrl(uri); throw Exception('Could not launch URL');
} }
} }
List<CryptoCurrency> mainCurrency = [
CryptoCurrency.btc,
CryptoCurrency.eth,
CryptoCurrency.sol,
];
String _tagToNetwork(String tag) {
switch (tag) {
case 'OMNI':
return tag;
case 'POL':
return 'POLYGON';
default:
return CryptoCurrency.fromString(tag).fullName ?? tag;
}
}
String _getNormalizeCryptoCurrency(Currency currency) {
if (currency is CryptoCurrency) {
if (!mainCurrency.contains(currency)) {
final network = currency.tag == null ? currency.fullName : _tagToNetwork(currency.tag!);
return '${currency.title}_${network?.replaceAll(' ', '')}'.toUpperCase();
}
return currency.title.toUpperCase();
}
return currency.name.toUpperCase();
}
String? normalizePaymentMethod(PaymentType paymentType) {
switch (paymentType) {
case PaymentType.bankTransfer:
return 'banktransfer';
case PaymentType.creditCard:
return 'creditcard';
case PaymentType.debitCard:
return 'debitcard';
case PaymentType.applePay:
return 'applepay';
case PaymentType.googlePay:
return 'googlepay';
case PaymentType.revolutPay:
return 'revolutpay';
case PaymentType.neteller:
return 'neteller';
case PaymentType.skrill:
return 'skrill';
case PaymentType.sepa:
return 'sepabanktransfer';
case PaymentType.sepaInstant:
return 'sepainstant';
case PaymentType.ach:
return 'ach';
case PaymentType.achInstant:
return 'iach';
case PaymentType.Khipu:
return 'khipu';
case PaymentType.palomaBanktTansfer:
return 'palomabanktransfer';
case PaymentType.ovo:
return 'ovo';
case PaymentType.zaloPay:
return 'zalopay';
case PaymentType.zaloBankTransfer:
return 'zalobanktransfer';
case PaymentType.gcash:
return 'gcash';
case PaymentType.imps:
return 'imps';
case PaymentType.dana:
return 'dana';
case PaymentType.ideal:
return 'ideal';
default:
return null;
}
}
PaymentType _getPaymentTypeByString(String paymentMethod) {
switch (paymentMethod.toLowerCase()) {
case 'banktransfer':
return PaymentType.bankTransfer;
case 'creditcard':
return PaymentType.creditCard;
case 'debitcard':
return PaymentType.debitCard;
case 'applepay':
return PaymentType.applePay;
case 'googlepay':
return PaymentType.googlePay;
case 'revolutpay':
return PaymentType.revolutPay;
case 'neteller':
return PaymentType.neteller;
case 'skrill':
return PaymentType.skrill;
case 'sepabanktransfer':
return PaymentType.sepa;
case 'sepainstant':
return PaymentType.sepaInstant;
case 'ach':
return PaymentType.ach;
case 'iach':
return PaymentType.achInstant;
case 'khipu':
return PaymentType.Khipu;
case 'palomabanktransfer':
return PaymentType.palomaBanktTansfer;
case 'ovo':
return PaymentType.ovo;
case 'zalopay':
return PaymentType.zaloPay;
case 'zalobanktransfer':
return PaymentType.zaloBankTransfer;
case 'gcash':
return PaymentType.gcash;
case 'imps':
return PaymentType.imps;
case 'dana':
return PaymentType.dana;
case 'ideal':
return PaymentType.ideal;
default:
return PaymentType.all;
}
}
String getColorStr(Color color) => color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
} }

287
lib/buy/payment_method.dart Normal file
View file

@ -0,0 +1,287 @@
import 'dart:ui';
import 'package:cake_wallet/core/selectable_option.dart';
enum PaymentType {
all,
bankTransfer,
creditCard,
debitCard,
applePay,
googlePay,
revolutPay,
neteller,
skrill,
sepa,
sepaInstant,
ach,
achInstant,
Khipu,
palomaBanktTansfer,
ovo,
zaloPay,
zaloBankTransfer,
gcash,
imps,
dana,
ideal,
paypal,
sepaOpenBankingPayment,
gbpOpenBankingPayment,
lowCostAch,
mobileWallet,
pixInstantPayment,
yellowCardBankTransfer,
fiatBalance,
bancontact,
}
extension PaymentTypeTitle on PaymentType {
String? get title {
switch (this) {
case PaymentType.all:
return 'All Payment Methods';
case PaymentType.bankTransfer:
return 'Bank Transfer';
case PaymentType.creditCard:
return 'Credit Card';
case PaymentType.debitCard:
return 'Debit Card';
case PaymentType.applePay:
return 'Apple Pay';
case PaymentType.googlePay:
return 'Google Pay';
case PaymentType.revolutPay:
return 'Revolut Pay';
case PaymentType.neteller:
return 'Neteller';
case PaymentType.skrill:
return 'Skrill';
case PaymentType.sepa:
return 'SEPA';
case PaymentType.sepaInstant:
return 'SEPA Instant';
case PaymentType.ach:
return 'ACH';
case PaymentType.achInstant:
return 'ACH Instant';
case PaymentType.Khipu:
return 'Khipu';
case PaymentType.palomaBanktTansfer:
return 'Paloma Bank Transfer';
case PaymentType.ovo:
return 'OVO';
case PaymentType.zaloPay:
return 'Zalo Pay';
case PaymentType.zaloBankTransfer:
return 'Zalo Bank Transfer';
case PaymentType.gcash:
return 'GCash';
case PaymentType.imps:
return 'IMPS';
case PaymentType.dana:
return 'DANA';
case PaymentType.ideal:
return 'iDEAL';
case PaymentType.paypal:
return 'PayPal';
case PaymentType.sepaOpenBankingPayment:
return 'SEPA Open Banking Payment';
case PaymentType.gbpOpenBankingPayment:
return 'GBP Open Banking Payment';
case PaymentType.lowCostAch:
return 'Low Cost ACH';
case PaymentType.mobileWallet:
return 'Mobile Wallet';
case PaymentType.pixInstantPayment:
return 'PIX Instant Payment';
case PaymentType.yellowCardBankTransfer:
return 'Yellow Card Bank Transfer';
case PaymentType.fiatBalance:
return 'Fiat Balance';
case PaymentType.bancontact:
return 'Bancontact';
default:
return null;
}
}
String? get lightIconPath {
switch (this) {
case PaymentType.all:
return 'assets/images/usd_round_light.svg';
case PaymentType.creditCard:
case PaymentType.debitCard:
case PaymentType.yellowCardBankTransfer:
return 'assets/images/card.svg';
case PaymentType.bankTransfer:
return 'assets/images/bank_light.svg';
case PaymentType.skrill:
return 'assets/images/skrill.svg';
case PaymentType.applePay:
return 'assets/images/apple_pay_round_light.svg';
default:
return null;
}
}
String? get darkIconPath {
switch (this) {
case PaymentType.all:
return 'assets/images/usd_round_dark.svg';
case PaymentType.creditCard:
case PaymentType.debitCard:
case PaymentType.yellowCardBankTransfer:
return 'assets/images/card_dark.svg';
case PaymentType.bankTransfer:
return 'assets/images/bank_dark.svg';
case PaymentType.skrill:
return 'assets/images/skrill.svg';
case PaymentType.applePay:
return 'assets/images/apple_pay_round_dark.svg';
default:
return null;
}
}
String? get description {
switch (this) {
default:
return null;
}
}
}
class PaymentMethod extends SelectableOption {
PaymentMethod({
required this.paymentMethodType,
required this.customTitle,
required this.customIconPath,
this.customDescription,
}) : super(title: paymentMethodType.title ?? customTitle);
final PaymentType paymentMethodType;
final String customTitle;
final String customIconPath;
final String? customDescription;
bool isSelected = false;
@override
String? get description => paymentMethodType.description ?? customDescription;
@override
String get lightIconPath => paymentMethodType.lightIconPath ?? customIconPath;
@override
String get darkIconPath => paymentMethodType.darkIconPath ?? customIconPath;
@override
bool get isOptionSelected => isSelected;
factory PaymentMethod.all() {
return PaymentMethod(
paymentMethodType: PaymentType.all,
customTitle: 'All Payment Methods',
customIconPath: 'assets/images/dollar_coin.svg');
}
factory PaymentMethod.fromOnramperJson(Map<String, dynamic> json) {
final type = PaymentMethod.getPaymentTypeId(json['paymentTypeId'] as String?);
return PaymentMethod(
paymentMethodType: type,
customTitle: json['name'] as String? ?? 'Unknown',
customIconPath: json['icon'] as String? ?? 'assets/images/card.png',
customDescription: json['description'] as String?);
}
factory PaymentMethod.fromDFX(String paymentMethod, PaymentType paymentType) {
return PaymentMethod(
paymentMethodType: paymentType,
customTitle: paymentMethod,
customIconPath: 'assets/images/card.png');
}
factory PaymentMethod.fromMoonPayJson(Map<String, dynamic> json, PaymentType paymentType) {
return PaymentMethod(
paymentMethodType: paymentType,
customTitle: json['paymentMethod'] as String,
customIconPath: 'assets/images/card.png');
}
factory PaymentMethod.fromMeldJson(Map<String, dynamic> json) {
final type = PaymentMethod.getPaymentTypeId(json['paymentMethod'] as String?);
final logos = json['logos'] as Map<String, dynamic>;
return PaymentMethod(
paymentMethodType: type,
customTitle: json['name'] as String? ?? 'Unknown',
customIconPath: logos['dark'] as String? ?? 'assets/images/card.png',
customDescription: json['description'] as String?);
}
static PaymentType getPaymentTypeId(String? type) {
switch (type?.toLowerCase()) {
case 'banktransfer':
case 'bank':
case 'yellow_card_bank_transfer':
return PaymentType.bankTransfer;
case 'creditcard':
case 'card':
case 'credit_debit_card':
return PaymentType.creditCard;
case 'debitcard':
return PaymentType.debitCard;
case 'applepay':
case 'apple_pay':
return PaymentType.applePay;
case 'googlepay':
case 'google_pay':
return PaymentType.googlePay;
case 'revolutpay':
return PaymentType.revolutPay;
case 'neteller':
return PaymentType.neteller;
case 'skrill':
return PaymentType.skrill;
case 'sepabanktransfer':
case 'sepa':
case 'sepa_bank_transfer':
return PaymentType.sepa;
case 'sepainstant':
case 'sepa_instant':
return PaymentType.sepaInstant;
case 'ach':
case 'ach_bank_transfer':
return PaymentType.ach;
case 'iach':
case 'instant_ach':
return PaymentType.achInstant;
case 'khipu':
return PaymentType.Khipu;
case 'palomabanktransfer':
return PaymentType.palomaBanktTansfer;
case 'ovo':
return PaymentType.ovo;
case 'zalopay':
return PaymentType.zaloPay;
case 'zalobanktransfer':
case 'za_bank_transfer':
return PaymentType.zaloBankTransfer;
case 'gcash':
return PaymentType.gcash;
case 'imps':
return PaymentType.imps;
case 'dana':
return PaymentType.dana;
case 'ideal':
return PaymentType.ideal;
case 'paypal':
return PaymentType.paypal;
case 'sepa_open_banking_payment':
return PaymentType.sepaOpenBankingPayment;
case 'bancontact':
return PaymentType.bancontact;
default:
return PaymentType.all;
}
}
}

View file

@ -1,13 +1,18 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -15,7 +20,8 @@ import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class RobinhoodBuyProvider extends BuyProvider { class RobinhoodBuyProvider extends BuyProvider {
RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM}) RobinhoodBuyProvider(
{required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM); : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM);
static const _baseUrl = 'applink.robinhood.com'; static const _baseUrl = 'applink.robinhood.com';
@ -33,6 +39,9 @@ class RobinhoodBuyProvider extends BuyProvider {
@override @override
String get darkIcon => 'assets/images/robinhood_dark.png'; String get darkIcon => 'assets/images/robinhood_dark.png';
@override
bool get isAggregator => false;
String get _applicationId => secrets.robinhoodApplicationId; String get _applicationId => secrets.robinhoodApplicationId;
String get _apiSecret => secrets.exchangeHelperApiKey; String get _apiSecret => secrets.exchangeHelperApiKey;
@ -86,7 +95,13 @@ class RobinhoodBuyProvider extends BuyProvider {
}); });
} }
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async { Future<void>? launchProvider(
{required BuildContext context,
required Quote quote,
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) { if (wallet.isHardwareWallet) {
if (!ledgerVM!.isConnected) { if (!ledgerVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices, await Navigator.of(context).pushNamed(Routes.connectDevices,
@ -116,4 +131,87 @@ class RobinhoodBuyProvider extends BuyProvider {
}); });
} }
} }
@override
Future<List<Quote>?> fetchQuote(
{required CryptoCurrency cryptoCurrency,
required FiatCurrency fiatCurrency,
required double amount,
required bool isBuyAction,
required String walletAddress,
PaymentType? paymentType,
String? countryCode}) async {
String? paymentMethod;
if (paymentType != null && paymentType != PaymentType.all) {
paymentMethod = normalizePaymentMethod(paymentType);
if (paymentMethod == null) paymentMethod = paymentType.name;
}
final action = isBuyAction ? 'buy' : 'sell';
log('Robinhood: Fetching $action quote: ${isBuyAction ? cryptoCurrency.title : fiatCurrency.name.toUpperCase()} -> ${isBuyAction ? fiatCurrency.name.toUpperCase() : cryptoCurrency.title}, amount: $amount paymentMethod: $paymentMethod');
final queryParams = {
'applicationId': _applicationId,
'fiatCode': fiatCurrency.name,
'assetCode': cryptoCurrency.title,
'fiatAmount': amount.toString(),
if (paymentMethod != null) 'paymentMethod': paymentMethod,
};
final uri =
Uri.https('api.robinhood.com', '/catpay/v1/${cryptoCurrency.title}/quote/', queryParams);
try {
final response = await http.get(uri, headers: {'accept': 'application/json'});
final responseData = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode == 200) {
final paymentType = _getPaymentTypeByString(responseData['paymentMethod'] as String?);
final quote = Quote.fromRobinhoodJson(responseData, isBuyAction, paymentType);
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
return [quote];
} else {
if (responseData.containsKey('message')) {
log('Robinhood Error: ${responseData['message']}');
} else {
print('Robinhood Failed to fetch $action quote: ${response.statusCode}');
}
return null;
}
} catch (e) {
log('Robinhood: Failed to fetch $action quote: $e');
return null;
}
// buying_power
// crypto_balance
// debit_card
// bank_transfer
}
String? normalizePaymentMethod(PaymentType paymentMethod) {
switch (paymentMethod) {
case PaymentType.creditCard:
return 'debit_card';
case PaymentType.debitCard:
return 'debit_card';
case PaymentType.bankTransfer:
return 'bank_transfer';
default:
return null;
}
}
PaymentType _getPaymentTypeByString(String? paymentMethod) {
switch (paymentMethod) {
case 'debit_card':
return PaymentType.debitCard;
case 'bank_transfer':
return PaymentType.bankTransfer;
default:
return PaymentType.all;
}
}
} }

View file

@ -0,0 +1,20 @@
abstract class PaymentMethodLoadingState {}
class InitialPaymentMethod extends PaymentMethodLoadingState {}
class PaymentMethodLoading extends PaymentMethodLoadingState {}
class PaymentMethodLoaded extends PaymentMethodLoadingState {}
class PaymentMethodFailed extends PaymentMethodLoadingState {}
abstract class BuySellQuotLoadingState {}
class InitialBuySellQuotState extends BuySellQuotLoadingState {}
class BuySellQuotLoading extends BuySellQuotLoadingState {}
class BuySellQuotLoaded extends BuySellQuotLoadingState {}
class BuySellQuotFailed extends BuySellQuotLoadingState {}

View file

@ -42,6 +42,9 @@ class WyreBuyProvider extends BuyProvider {
@override @override
String get darkIcon => 'assets/images/robinhood_dark.png'; String get darkIcon => 'assets/images/robinhood_dark.png';
@override
bool get isAggregator => false;
String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl; String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl;
String baseApiUrl; String baseApiUrl;
@ -148,10 +151,4 @@ class WyreBuyProvider extends BuyProvider {
receiveAddress: wallet.walletAddresses.address, receiveAddress: wallet.walletAddresses.address,
walletId: wallet.id); walletId: wallet.id);
} }
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) {
// TODO: implement launchProvider
throw UnimplementedError();
}
} }

View file

@ -268,9 +268,7 @@ class BackupService {
final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?; final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?;
final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?; final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?;
final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?; final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?;
final disableBuy = data[PreferencesKey.disableBuyKey] as bool?; final disableTradeOption = data[PreferencesKey.disableTradeOption] as bool?;
final disableSell = data[PreferencesKey.disableSellKey] as bool?;
final defaultBuyProvider = data[PreferencesKey.defaultBuyProvider] as int?;
final currentTransactionPriorityKeyLegacy = final currentTransactionPriorityKeyLegacy =
data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?;
final currentBitcoinElectrumSererId = final currentBitcoinElectrumSererId =
@ -323,14 +321,8 @@ class BackupService {
if (isAppSecure != null) if (isAppSecure != null)
await _sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure); await _sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure);
if (disableBuy != null) if (disableTradeOption != null)
await _sharedPreferences.setBool(PreferencesKey.disableBuyKey, disableBuy); await _sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption);
if (disableSell != null)
await _sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell);
if (defaultBuyProvider != null)
await _sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider);
if (currentTransactionPriorityKeyLegacy != null) if (currentTransactionPriorityKeyLegacy != null)
await _sharedPreferences.setInt( await _sharedPreferences.setInt(
@ -516,10 +508,7 @@ class BackupService {
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey), _sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
PreferencesKey.shouldSaveRecipientAddressKey: PreferencesKey.shouldSaveRecipientAddressKey:
_sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey), _sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
PreferencesKey.disableBuyKey: _sharedPreferences.getBool(PreferencesKey.disableBuyKey), PreferencesKey.disableTradeOption: _sharedPreferences.getBool(PreferencesKey.disableTradeOption),
PreferencesKey.disableSellKey: _sharedPreferences.getBool(PreferencesKey.disableSellKey),
PreferencesKey.defaultBuyProvider:
_sharedPreferences.getInt(PreferencesKey.defaultBuyProvider),
PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength), PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength),
PreferencesKey.currentTransactionPriorityKeyLegacy: PreferencesKey.currentTransactionPriorityKeyLegacy:
_sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), _sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),

View file

@ -0,0 +1,47 @@
abstract class SelectableItem {
SelectableItem({required this.title});
final String title;
}
class OptionTitle extends SelectableItem {
OptionTitle({required String title}) : super(title: title);
}
abstract class SelectableOption extends SelectableItem {
SelectableOption({required String title}) : super(title: title);
String get lightIconPath;
String get darkIconPath;
String? get description => null;
String? get topLeftSubTitle => null;
String? get topLeftSubTitleIconPath => null;
String? get topRightSubTitle => null;
String? get topRightSubTitleLightIconPath => null;
String? get topRightSubTitleDarkIconPath => null;
String? get bottomLeftSubTitle => null;
String? get bottomLeftSubTitleIconPath => null;
String? get bottomRightSubTitle => null;
String? get bottomRightSubTitleLightIconPath => null;
String? get bottomRightSubTitleDarkIconPath => null;
List<String> get badges => [];
bool get isOptionSelected => false;
set isOptionSelected(bool isSelected) => false;
}

View file

@ -14,7 +14,11 @@ import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class WalletLoadingService { class WalletLoadingService {
WalletLoadingService(this.sharedPreferences, this.keyService, this.walletServiceFactory); WalletLoadingService(
this.sharedPreferences,
this.keyService,
this.walletServiceFactory,
);
final SharedPreferences sharedPreferences; final SharedPreferences sharedPreferences;
final KeyService keyService; final KeyService keyService;
@ -77,7 +81,8 @@ class WalletLoadingService {
await updateMoneroWalletPassword(wallet); await updateMoneroWalletPassword(wallet);
} }
await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name); await sharedPreferences.setString(
PreferencesKey.currentWalletName, wallet.name);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentWalletType, serializeToInt(wallet.type)); PreferencesKey.currentWalletType, serializeToInt(wallet.type));
@ -129,4 +134,9 @@ class WalletLoadingService {
return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}"; return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}";
} }
bool requireHardwareWalletConnection(WalletType type, String name) {
final walletService = walletServiceFactory.call(type);
return walletService.requireHardwareWalletConnection(name);
}
} }

View file

@ -19,6 +19,7 @@ import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/core/selectable_option.dart';
import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
@ -31,10 +32,14 @@ import 'package:cake_wallet/entities/biometric_auth.dart';
import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart'; import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_manager.dart'; import 'package:cake_wallet/entities/wallet_manager.dart';
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart'; import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart'; import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
@ -61,7 +66,6 @@ import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dar
import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart'; import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/webview_page.dart'; import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
@ -125,6 +129,7 @@ import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.d
import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart';
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart'; import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart'; import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart'; import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart'; import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
@ -134,6 +139,8 @@ import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
import 'package:cake_wallet/view_model/animated_ur_model.dart';
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart'; import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
@ -179,7 +186,6 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@ -246,6 +252,8 @@ import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'buy/meld/meld_buy_provider.dart';
import 'src/screens/buy/buy_sell_page.dart';
import 'cake_pay/cake_pay_payment_credantials.dart'; import 'cake_pay/cake_pay_payment_credantials.dart';
final getIt = GetIt.instance; final getIt = GetIt.instance;
@ -579,7 +587,7 @@ Future<void> setup({
); );
} else { } else {
// wallet is already loaded: // wallet is already loaded:
if (appStore.wallet != null) { if (appStore.wallet != null || requireHardwareWalletConnection()) {
// goes to the dashboard: // goes to the dashboard:
authStore.allowed(); authStore.allowed();
// trigger any deep links: // trigger any deep links:
@ -773,10 +781,12 @@ Future<void> setup({
); );
} }
getIt.registerFactory(() => WalletListPage( getIt.registerFactoryParam<WalletListPage, Function(BuildContext)?, void>(
walletListViewModel: getIt.get<WalletListViewModel>(), (Function(BuildContext)? onWalletLoaded, _) => WalletListPage(
authService: getIt.get<AuthService>(), walletListViewModel: getIt.get<WalletListViewModel>(),
)); authService: getIt.get<AuthService>(),
onWalletLoaded: onWalletLoaded,
));
getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>( getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>(
(WalletListViewModel walletListViewModel, _) => WalletEditViewModel( (WalletListViewModel walletListViewModel, _) => WalletEditViewModel(
@ -903,6 +913,11 @@ Future<void> setup({
getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>())); getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>())); getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
getIt.registerFactoryParam<AnimatedURPage, String, void>((String urQr, _) =>
AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>( getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact)); (ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
@ -997,6 +1012,10 @@ Future<void> setup({
wallet: getIt.get<AppStore>().wallet!, wallet: getIt.get<AppStore>().wallet!,
)); ));
getIt.registerFactory<MeldBuyProvider>(() => MeldBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
));
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri)); getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider( getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
@ -1186,8 +1205,25 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel()); getIt.registerFactory(() => BuyAmountViewModel());
getIt.registerFactoryParam<BuySellOptionsPage, bool, void>( getIt.registerFactory(() => BuySellViewModel(getIt.get<AppStore>()));
(isBuyOption, _) => BuySellOptionsPage(getIt.get<DashboardViewModel>(), isBuyOption));
getIt.registerFactory(() => BuySellPage(getIt.get<BuySellViewModel>()));
getIt.registerFactoryParam<BuyOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
final items = args.first as List<SelectableItem>;
final pickAnOption = args[1] as void Function(SelectableOption option)?;
final confirmOption = args[2] as void Function(BuildContext contex)?;
return BuyOptionsPage(
items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
});
getIt.registerFactoryParam<PaymentMethodOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
final items = args.first as List<SelectableOption>;
final pickAnOption = args[1] as void Function(SelectableOption option)?;
return PaymentMethodOptionsPage(
items: items, pickAnOption: pickAnOption);
});
getIt.registerFactory(() { getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet; final wallet = getIt.get<AppStore>().wallet;

View file

@ -252,13 +252,19 @@ Future<void> defaultSettingsMigration(
await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes); await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 41: case 41:
_deselectQuantex(sharedPreferences); _deselectExchangeProvider(sharedPreferences, "Quantex");
await _addSethNode(nodes, sharedPreferences); await _addSethNode(nodes, sharedPreferences);
await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes); await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 42: case 42:
updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences); updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences);
break; break;
case 43:
_updateCakeXmrNode(nodes);
_deselectExchangeProvider(sharedPreferences, "THORChain");
_deselectExchangeProvider(sharedPreferences, "SimpleSwap");
break;
default: default:
break; break;
} }
@ -273,6 +279,15 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
} }
void _updateCakeXmrNode(Box<Node> nodes) {
final node = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletMoneroUri);
if (node != null && !node.trusted) {
node.trusted = true;
node.save();
}
}
void updateBtcElectrumNodeToUseSSL(Box<Node> nodes, SharedPreferences sharedPreferences) { void updateBtcElectrumNodeToUseSSL(Box<Node> nodes, SharedPreferences sharedPreferences) {
final btcElectrumNode = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri); final btcElectrumNode = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri);
@ -282,12 +297,12 @@ void updateBtcElectrumNodeToUseSSL(Box<Node> nodes, SharedPreferences sharedPref
} }
} }
void _deselectQuantex(SharedPreferences sharedPreferences) { void _deselectExchangeProvider(SharedPreferences sharedPreferences, String providerName) {
final Map<String, dynamic> exchangeProvidersSelection = final Map<String, dynamic> exchangeProvidersSelection =
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
as Map<String, dynamic>; as Map<String, dynamic>;
exchangeProvidersSelection['Quantex'] = false; exchangeProvidersSelection[providerName] = false;
sharedPreferences.setString( sharedPreferences.setString(
PreferencesKey.exchangeProvidersSelection, PreferencesKey.exchangeProvidersSelection,
@ -843,7 +858,7 @@ Future<void> changeDefaultMoneroNode(
} }
}); });
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero, trusted: true);
await nodeSource.add(newCakeWalletNode); await nodeSource.add(newCakeWalletNode);

View file

@ -0,0 +1,25 @@
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:shared_preferences/shared_preferences.dart';
bool requireHardwareWalletConnection() {
final name = getIt
.get<SharedPreferences>()
.getString(PreferencesKey.currentWalletName);
final typeRaw =
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
if (typeRaw == null) {
return false;
}
if (name == null) {
throw Exception('Incorrect current wallet name: $name');
}
final type = deserializeFromInt(typeRaw);
final walletLoadingService = getIt.get<WalletLoadingService>();
return walletLoadingService.requireHardwareWalletConnection(type, name);
}

View file

@ -23,31 +23,18 @@ class MainActions {
}); });
static List<MainActions> all = [ static List<MainActions> all = [
buyAction, showWalletsAction,
receiveAction, receiveAction,
exchangeAction, exchangeAction,
sendAction, sendAction,
sellAction, tradeAction,
]; ];
static MainActions buyAction = MainActions._( static MainActions showWalletsAction = MainActions._(
name: (context) => S.of(context).buy, name: (context) => S.of(context).wallets,
image: 'assets/images/buy.png', image: 'assets/images/wallet_new.png',
isEnabled: (viewModel) => viewModel.isEnabledBuyAction,
canShow: (viewModel) => viewModel.hasBuyAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async { onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledBuyAction) { Navigator.pushNamed(context, Routes.walletList);
return;
}
final defaultBuyProvider = viewModel.defaultBuyProvider;
try {
defaultBuyProvider != null
? await defaultBuyProvider.launchProvider(context, true)
: await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: true);
} catch (e) {
await _showErrorDialog(context, defaultBuyProvider.toString(), e.toString());
}
}, },
); );
@ -79,39 +66,15 @@ class MainActions {
}, },
); );
static MainActions sellAction = MainActions._(
name: (context) => S.of(context).sell,
image: 'assets/images/sell.png',
isEnabled: (viewModel) => viewModel.isEnabledSellAction,
canShow: (viewModel) => viewModel.hasSellAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledSellAction) {
return;
}
final defaultSellProvider = viewModel.defaultSellProvider; static MainActions tradeAction = MainActions._(
try { name: (context) => '${S.of(context).buy} / ${S.of(context).sell}',
defaultSellProvider != null image: 'assets/images/buy_sell.png',
? await defaultSellProvider.launchProvider(context, false) isEnabled: (viewModel) => viewModel.isEnabledTradeAction,
: await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); canShow: (viewModel) => viewModel.hasTradeAction,
} catch (e) { onTap: (BuildContext context, DashboardViewModel viewModel) async {
await _showErrorDialog(context, defaultSellProvider.toString(), e.toString()); if (!viewModel.isEnabledTradeAction) return;
} await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false);
}, },
); );
static Future<void> _showErrorDialog(
BuildContext context, String title, String errorMessage) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: title,
alertContent: errorMessage,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
} }

View file

@ -41,7 +41,8 @@ class AddressResolver {
'kresus', 'kresus',
'anime', 'anime',
'manga', 'manga',
'binanceus' 'binanceus',
'xmr',
]; ];
static String? extractAddressByType({required String raw, required CryptoCurrency type}) { static String? extractAddressByType({required String raw, required CryptoCurrency type}) {

View file

@ -21,10 +21,8 @@ class PreferencesKey {
static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const shouldSaveRecipientAddressKey = 'save_recipient_address';
static const isAppSecureKey = 'is_app_secure'; static const isAppSecureKey = 'is_app_secure';
static const disableBuyKey = 'disable_buy'; static const disableTradeOption = 'disable_buy';
static const disableSellKey = 'disable_sell';
static const disableBulletinKey = 'disable_bulletin'; static const disableBulletinKey = 'disable_bulletin';
static const defaultBuyProvider = 'default_buy_provider';
static const walletListOrder = 'wallet_list_order'; static const walletListOrder = 'wallet_list_order';
static const contactListOrder = 'contact_list_order'; static const contactListOrder = 'contact_list_order';
static const walletListAscending = 'wallet_list_ascending'; static const walletListAscending = 'wallet_list_ascending';

View file

@ -1,24 +1,18 @@
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/meld/meld_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:http/http.dart';
enum ProviderType { enum ProviderType { robinhood, dfx, onramper, moonpay, meld }
askEachTime,
robinhood,
dfx,
onramper,
moonpay,
}
extension ProviderTypeName on ProviderType { extension ProviderTypeName on ProviderType {
String get title { String get title {
switch (this) { switch (this) {
case ProviderType.askEachTime:
return 'Ask each time';
case ProviderType.robinhood: case ProviderType.robinhood:
return 'Robinhood Connect'; return 'Robinhood Connect';
case ProviderType.dfx: case ProviderType.dfx:
@ -27,13 +21,13 @@ extension ProviderTypeName on ProviderType {
return 'Onramper'; return 'Onramper';
case ProviderType.moonpay: case ProviderType.moonpay:
return 'MoonPay'; return 'MoonPay';
case ProviderType.meld:
return 'Meld';
} }
} }
String get id { String get id {
switch (this) { switch (this) {
case ProviderType.askEachTime:
return 'ask_each_time_provider';
case ProviderType.robinhood: case ProviderType.robinhood:
return 'robinhood_connect_provider'; return 'robinhood_connect_provider';
case ProviderType.dfx: case ProviderType.dfx:
@ -42,6 +36,8 @@ extension ProviderTypeName on ProviderType {
return 'onramper_provider'; return 'onramper_provider';
case ProviderType.moonpay: case ProviderType.moonpay:
return 'moonpay_provider'; return 'moonpay_provider';
case ProviderType.meld:
return 'meld_provider';
} }
} }
} }
@ -52,14 +48,13 @@ class ProvidersHelper {
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
case WalletType.wownero: case WalletType.wownero:
return [ProviderType.askEachTime, ProviderType.onramper]; return [ProviderType.onramper];
case WalletType.monero: case WalletType.monero:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.dfx]; return [ProviderType.onramper, ProviderType.dfx];
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.polygon: case WalletType.polygon:
case WalletType.ethereum: case WalletType.ethereum:
return [ return [
ProviderType.askEachTime,
ProviderType.onramper, ProviderType.onramper,
ProviderType.dfx, ProviderType.dfx,
ProviderType.robinhood, ProviderType.robinhood,
@ -68,10 +63,13 @@ class ProvidersHelper {
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
case WalletType.solana: case WalletType.solana:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay]; return [
ProviderType.onramper,
ProviderType.robinhood,
ProviderType.moonpay
];
case WalletType.tron: case WalletType.tron:
return [ return [
ProviderType.askEachTime,
ProviderType.onramper, ProviderType.onramper,
ProviderType.robinhood, ProviderType.robinhood,
ProviderType.moonpay, ProviderType.moonpay,
@ -88,28 +86,24 @@ class ProvidersHelper {
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
return [ return [
ProviderType.askEachTime,
ProviderType.onramper, ProviderType.onramper,
ProviderType.moonpay, ProviderType.moonpay,
ProviderType.dfx, ProviderType.dfx,
]; ];
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return [ProviderType.askEachTime, ProviderType.moonpay]; return [ProviderType.moonpay];
case WalletType.solana: case WalletType.solana:
return [ return [
ProviderType.askEachTime,
ProviderType.onramper, ProviderType.onramper,
ProviderType.robinhood,
ProviderType.moonpay, ProviderType.moonpay,
]; ];
case WalletType.tron: case WalletType.tron:
return [ return [
ProviderType.askEachTime,
ProviderType.robinhood,
ProviderType.moonpay, ProviderType.moonpay,
]; ];
case WalletType.monero: case WalletType.monero:
return [ProviderType.dfx];
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
case WalletType.none: case WalletType.none:
@ -129,7 +123,9 @@ class ProvidersHelper {
return getIt.get<OnRamperBuyProvider>(); return getIt.get<OnRamperBuyProvider>();
case ProviderType.moonpay: case ProviderType.moonpay:
return getIt.get<MoonPayProvider>(); return getIt.get<MoonPayProvider>();
case ProviderType.askEachTime: case ProviderType.meld:
return getIt.get<MeldBuyProvider>();
default:
return null; return null;
} }
} }

View file

@ -1,15 +1,376 @@
import 'package:barcode_scan2/barcode_scan2.dart'; import 'dart:math';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:fast_scanner/fast_scanner.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
var isQrScannerShown = false; var isQrScannerShown = false;
Future<String> presentQRScanner() async { Future<String> presentQRScanner(BuildContext context) async {
isQrScannerShown = true; isQrScannerShown = true;
try { try {
final result = await BarcodeScanner.scan(); final result = await Navigator.of(context).push<String>(
MaterialPageRoute(
builder:(context) {
return BarcodeScannerSimple();
},
),
);
isQrScannerShown = false; isQrScannerShown = false;
return result.rawContent.trim(); return result??'';
} catch (e) { } catch (e) {
isQrScannerShown = false; isQrScannerShown = false;
rethrow; rethrow;
} }
} }
// https://github.com/MrCyjaneK/fast_scanner/blob/master/example/lib/barcode_scanner_simple.dart
class BarcodeScannerSimple extends StatefulWidget {
const BarcodeScannerSimple({super.key});
@override
State<BarcodeScannerSimple> createState() => _BarcodeScannerSimpleState();
}
class _BarcodeScannerSimpleState extends State<BarcodeScannerSimple> {
Barcode? _barcode;
bool popped = false;
List<String> urCodes = [];
late var ur = URQRToURQRData(urCodes);
void _handleBarcode(BarcodeCapture barcodes) {
try {
_handleBarcodeInternal(barcodes);
} catch (e) {
showPopUp<void>(
context: context,
builder: (context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
buttonText: 'ok',
buttonAction: () {
Navigator.of(context).pop();
},
);
},
);
print(e);
}
}
void _handleBarcodeInternal(BarcodeCapture barcodes) {
for (final barcode in barcodes.barcodes) {
// don't handle unknown QR codes
if (barcode.rawValue?.trim().isEmpty??false == false) continue;
if (barcode.rawValue!.startsWith("ur:")) {
if (urCodes.contains(barcode.rawValue)) continue;
setState(() {
urCodes.add(barcode.rawValue!);
ur = URQRToURQRData(urCodes);
});
if (ur.progress == 1) {
setState(() {
popped = true;
});
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop(ur.inputs.join("\n"));
});
};
}
}
if (urCodes.isNotEmpty) return;
if (mounted) {
setState(() {
_barcode = barcodes.barcodes.firstOrNull;
});
if (_barcode != null && popped != true) {
setState(() {
popped = true;
});
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop(_barcode?.rawValue ?? "");
});
}
}
}
final MobileScannerController ctrl = MobileScannerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scan'),
actions: [
SwitchCameraButton(controller: ctrl),
ToggleFlashlightButton(controller: ctrl),
],
),
backgroundColor: Colors.black,
body: Stack(
children: [
MobileScanner(
onDetect: _handleBarcode,
controller: ctrl,
),
if (ur.inputs.length != 0)
Center(child:
Text(
"${ur.inputs.length}/${ur.count}",
style: Theme.of(context).textTheme.displayLarge?.copyWith(color: Colors.white)
),
),
SizedBox(
child: Center(
child: SizedBox(
width: 250,
height: 250,
child: CustomPaint(
painter: ProgressPainter(
urQrProgress: URQrProgress(
expectedPartCount: ur.count - 1,
processedPartsCount: ur.inputs.length,
receivedPartIndexes: _urParts(),
percentage: ur.progress,
),
),
),
),
),
),
],
),
);
}
List<int> _urParts() {
List<int> l = [];
for (var inp in ur.inputs) {
try {
l.add(int.parse(inp.split("/")[1].split("-")[0]));
} catch (e) {}
}
return l;
}
}
class ToggleFlashlightButton extends StatelessWidget {
const ToggleFlashlightButton({required this.controller, super.key});
final MobileScannerController controller;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
switch (state.torchState) {
case TorchState.auto:
return IconButton(
iconSize: 32.0,
icon: const Icon(Icons.flash_auto),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.off:
return IconButton(
iconSize: 32.0,
icon: const Icon(Icons.flash_off),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.on:
return IconButton(
iconSize: 32.0,
icon: const Icon(Icons.flash_on),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.unavailable:
return const Icon(
Icons.no_flash,
color: Colors.grey,
);
}
},
);
}
}
class SwitchCameraButton extends StatelessWidget {
const SwitchCameraButton({required this.controller, super.key});
final MobileScannerController controller;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
final int? availableCameras = state.availableCameras;
if (availableCameras != null && availableCameras < 2) {
return const SizedBox.shrink();
}
final Widget icon;
switch (state.cameraDirection) {
case CameraFacing.front:
icon = const Icon(Icons.camera_front);
case CameraFacing.back:
icon = const Icon(Icons.camera_rear);
}
return IconButton(
iconSize: 32.0,
icon: icon,
onPressed: () async {
await controller.switchCamera();
},
);
},
);
}
}
class URQRData {
URQRData(
{required this.tag,
required this.str,
required this.progress,
required this.count,
required this.error,
required this.inputs});
final String tag;
final String str;
final double progress;
final int count;
final String error;
final List<String> inputs;
Map<String, dynamic> toJson() {
return {
"tag": tag,
"str": str,
"progress": progress,
"count": count,
"error": error,
"inputs": inputs,
};
}
}
URQRData URQRToURQRData(List<String> urqr_) {
final urqr = urqr_.toSet().toList();
urqr.sort((s1, s2) {
final s1s = s1.split("/");
final s1frameStr = s1s[1].split("-");
final s1curFrame = int.parse(s1frameStr[0]);
final s2s = s2.split("/");
final s2frameStr = s2s[1].split("-");
final s2curFrame = int.parse(s2frameStr[0]);
return s1curFrame - s2curFrame;
});
String tag = '';
int count = 0;
String bw = '';
for (var elm in urqr) {
final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix
final s2 = s.split("/");
tag = s2[0];
final frameStr = s2[1].split("-");
// final curFrame = int.parse(frameStr[0]);
count = int.parse(frameStr[1]);
final byteWords = s2[2];
bw += byteWords;
}
String? error;
return URQRData(
tag: tag,
str: bw,
progress: count == 0 ? 0 : (urqr.length / count),
count: count,
error: error ?? "",
inputs: urqr,
);
}
class ProgressPainter extends CustomPainter {
final URQrProgress urQrProgress;
ProgressPainter({required this.urQrProgress});
@override
void paint(Canvas canvas, Size size) {
final c = Offset(size.width / 2.0, size.height / 2.0);
final radius = size.width * 0.9;
final rect = Rect.fromCenter(center: c, width: radius, height: radius);
const fullAngle = 360.0;
var startAngle = 0.0;
for (int i = 0; i < urQrProgress.expectedPartCount.toInt(); i++) {
var sweepAngle =
(1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0;
drawSector(canvas, urQrProgress.receivedPartIndexes.contains(i), rect,
startAngle, sweepAngle);
startAngle += sweepAngle;
}
}
void drawSector(Canvas canvas, bool isActive, Rect rect, double startAngle,
double sweepAngle) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 8
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..color = isActive ? const Color(0xffff6600) : Colors.white70;
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
}
@override
bool shouldRepaint(covariant ProgressPainter oldDelegate) {
return urQrProgress != oldDelegate.urQrProgress;
}
}
class URQrProgress {
int expectedPartCount;
int processedPartsCount;
List<int> receivedPartIndexes;
double percentage;
URQrProgress({
required this.expectedPartCount,
required this.processedPartsCount,
required this.receivedPartIndexes,
required this.percentage,
});
bool equals(URQrProgress? progress) {
if (progress == null) {
return false;
}
return processedPartsCount == progress.processedPartsCount;
}
}

View file

@ -89,13 +89,12 @@ class TrocadorExchangeProvider extends ExchangeProvider {
required CryptoCurrency to, required CryptoCurrency to,
required bool isFixedRateMode}) async { required bool isFixedRateMode}) async {
final params = { final params = {
'api_key': apiKey,
'ticker': _normalizeCurrency(from), 'ticker': _normalizeCurrency(from),
'name': from.name, 'name': from.name,
}; };
final uri = await _getUri(coinPath, params); final uri = await _getUri(coinPath, params);
final response = await get(uri); final response = await get(uri, headers: {'API-Key': apiKey});
if (response.statusCode != 200) if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}'); throw Exception('Unexpected http status: ${response.statusCode}');
@ -123,7 +122,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
if (amount == 0) return 0.0; if (amount == 0) return 0.0;
final params = <String, String>{ final params = <String, String>{
'api_key': apiKey,
'ticker_from': _normalizeCurrency(from), 'ticker_from': _normalizeCurrency(from),
'ticker_to': _normalizeCurrency(to), 'ticker_to': _normalizeCurrency(to),
'network_from': _networkFor(from), 'network_from': _networkFor(from),
@ -136,7 +134,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
}; };
final uri = await _getUri(newRatePath, params); final uri = await _getUri(newRatePath, params);
final response = await get(uri); final response = await get(uri, headers: {'API-Key': apiKey});
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromAmount = double.parse(responseJSON['amount_from'].toString()); final fromAmount = double.parse(responseJSON['amount_from'].toString());
final toAmount = double.parse(responseJSON['amount_to'].toString()); final toAmount = double.parse(responseJSON['amount_to'].toString());
@ -161,7 +160,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
required bool isSendAll, required bool isSendAll,
}) async { }) async {
final params = { final params = {
'api_key': apiKey,
'ticker_from': _normalizeCurrency(request.fromCurrency), 'ticker_from': _normalizeCurrency(request.fromCurrency),
'ticker_to': _normalizeCurrency(request.toCurrency), 'ticker_to': _normalizeCurrency(request.toCurrency),
'network_from': _networkFor(request.fromCurrency), 'network_from': _networkFor(request.fromCurrency),
@ -202,7 +200,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
params['provider'] = firstAvailableProvider; params['provider'] = firstAvailableProvider;
final uri = await _getUri(createTradePath, params); final uri = await _getUri(createTradePath, params);
final response = await get(uri); final response = await get(uri, headers: {'API-Key': apiKey});
if (response.statusCode == 400) { if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -248,8 +246,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
@override @override
Future<Trade> findTradeById({required String id}) async { Future<Trade> findTradeById({required String id}) async {
final uri = await _getUri(tradePath, {'api_key': apiKey, 'id': id}); final uri = await _getUri(tradePath, {'id': id});
return get(uri).then((response) { return get(uri, headers: {'API-Key': apiKey}).then((response) {
if (response.statusCode != 200) if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}'); throw Exception('Unexpected http status: ${response.statusCode}');

View file

@ -204,7 +204,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 42, initialMigrationVersion: 43,
); );
} }

View file

@ -225,6 +225,19 @@ class CWMonero extends Monero {
language: language, language: language,
height: height); height: height);
@override
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({
required String name,
required String password,
required int height,
required ledger.LedgerConnection ledgerConnection,
}) =>
MoneroRestoreWalletFromHardwareCredentials(
name: name,
password: password,
height: height,
ledgerConnection: ledgerConnection);
@override @override
WalletCredentials createMoneroRestoreWalletFromSeedCredentials( WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
{required String name, {required String name,
@ -248,6 +261,7 @@ class CWMonero extends Monero {
final moneroWallet = wallet as MoneroWallet; final moneroWallet = wallet as MoneroWallet;
final keys = moneroWallet.keys; final keys = moneroWallet.keys;
return <String, String>{ return <String, String>{
'primaryAddress': keys.primaryAddress,
'privateSpendKey': keys.privateSpendKey, 'privateSpendKey': keys.privateSpendKey,
'privateViewKey': keys.privateViewKey, 'privateViewKey': keys.privateViewKey,
'publicSpendKey': keys.publicSpendKey, 'publicSpendKey': keys.publicSpendKey,
@ -357,9 +371,48 @@ class CWMonero extends Monero {
Future<int> getCurrentHeight() async { Future<int> getCurrentHeight() async {
return monero_wallet_api.getCurrentHeight(); return monero_wallet_api.getCurrentHeight();
} }
@override
bool importKeyImagesUR(Object wallet, String ur) {
final moneroWallet = wallet as MoneroWallet;
return moneroWallet.importKeyImagesUR(ur);
}
@override
Future<bool> commitTransactionUR(Object wallet, String ur) {
final moneroWallet = wallet as MoneroWallet;
return moneroWallet.submitTransactionUR(ur);
}
@override
String exportOutputsUR(Object wallet, bool all) {
final moneroWallet = wallet as MoneroWallet;
return moneroWallet.exportOutputsUR(all);
}
@override @override
void monerocCheck() { void monerocCheck() {
checkIfMoneroCIsFine(); checkIfMoneroCIsFine();
} }
@override
void setLedgerConnection(Object wallet, ledger.LedgerConnection connection) {
final moneroWallet = wallet as MoneroWallet;
moneroWallet.setLedgerConnection(connection);
}
void resetLedgerConnection() {
disableLedgerExchange();
}
@override
void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
gLedger = connection;
keepAlive(connection);
}
bool isViewOnly() {
return isViewOnlyBySpendKey();
}
} }

View file

@ -1,18 +1,28 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart';
import 'package:cake_wallet/entities/load_current_wallet.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/load_current_wallet.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:rxdart/subjects.dart'; import 'package:rxdart/subjects.dart';
ReactionDisposer? _onAuthenticationStateChange; ReactionDisposer? _onAuthenticationStateChange;
dynamic loginError; dynamic loginError;
StreamController<dynamic> authenticatedErrorStreamController = BehaviorSubject<dynamic>(); StreamController<dynamic> authenticatedErrorStreamController =
BehaviorSubject<dynamic>();
void startAuthenticationStateChange( void startAuthenticationStateChange(
AuthenticationStore authenticationStore, AuthenticationStore authenticationStore,
@ -27,18 +37,49 @@ void startAuthenticationStateChange(
_onAuthenticationStateChange ??= autorun((_) async { _onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state; final state = authenticationStore.state;
if (state == AuthenticationState.installed && !SettingsStoreBase.walletPasswordDirectInput) { if (state == AuthenticationState.installed &&
!SettingsStoreBase.walletPasswordDirectInput) {
try { try {
await loadCurrentWallet(); if (!requireHardwareWalletConnection()) await loadCurrentWallet();
} catch (error, stack) { } catch (error, stack) {
loginError = error; loginError = error;
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); ExceptionHandler.onError(
FlutterErrorDetails(exception: error, stack: stack));
} }
return; return;
} }
if (state == AuthenticationState.allowed) { if (state == AuthenticationState.allowed) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); if (requireHardwareWalletConnection()) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil(
Routes.connectDevices,
(route) => false,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
onConnectDevice: (context, ledgerVM) async {
monero!.setGlobalLedgerConnection(ledgerVM.connection);
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
buttonAction: () => Navigator.of(context).pop()),
);
await loadCurrentWallet();
getIt.get<BottomSheetService>().resetCurrentSheet();
await navigatorKey.currentState!
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
},
allowChangeWallet: true,
),
);
// await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.connectDevices, (route) => false, arguments: ConnectDevicePageParams(walletType: walletType, onConnectDevice: onConnectDevice));
} else {
await navigatorKey.currentState!
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
}
if (!(await authenticatedErrorStreamController.stream.isEmpty)) { if (!(await authenticatedErrorStreamController.stream.isEmpty)) {
ExceptionHandler.showError( ExceptionHandler.showError(
(await authenticatedErrorStreamController.stream.first).toString()); (await authenticatedErrorStreamController.stream.first).toString());

View file

@ -10,9 +10,6 @@ import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/reactions/check_connection.dart'; import 'package:cake_wallet/reactions/check_connection.dart';
import 'package:cake_wallet/reactions/on_wallet_sync_status_change.dart'; import 'package:cake_wallet/reactions/on_wallet_sync_status_change.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@ -65,10 +62,6 @@ void startCurrentWalletChangeReaction(
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore); startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
startCheckConnectionReaction(wallet, settingsStore); startCheckConnectionReaction(wallet, settingsStore);
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
await getIt
.get<SharedPreferences>()
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
if (wallet.type == WalletType.monero || if (wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero || wallet.type == WalletType.wownero ||

View file

@ -17,12 +17,14 @@ import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dar
import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart'; import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart'; import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
import 'package:cake_wallet/src/screens/buy/webview_page.dart'; import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart'; import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart'; import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart'; import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@ -96,6 +98,7 @@ import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dar
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart'; import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
@ -128,7 +131,8 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'src/screens/buy/buy_sell_page.dart';
import 'src/screens/dashboard/pages/nft_import_page.dart'; import 'src/screens/dashboard/pages/nft_import_page.dart';
late RouteSettings currentRouteSettings; late RouteSettings currentRouteSettings;
@ -209,6 +213,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
final type = arguments[0] as WalletType; final type = arguments[0] as WalletType;
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type); final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
if (type == WalletType.monero)
return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM)); return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
case Routes.setupPin: case Routes.setupPin:
@ -400,8 +407,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>()); return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
case Routes.walletList: case Routes.walletList:
final onWalletLoaded = settings.arguments as Function(BuildContext)?;
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<WalletListPage>()); fullscreenDialog: true,
builder: (_) => getIt.get<WalletListPage>(param1: onWalletLoaded),
);
case Routes.walletEdit: case Routes.walletEdit:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
@ -570,7 +580,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.buySellPage: case Routes.buySellPage:
final args = settings.arguments as bool; final args = settings.arguments as bool;
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuySellOptionsPage>(param1: args)); return MaterialPageRoute<void>(builder: (_) => getIt.get<BuySellPage>(param1: args));
case Routes.buyOptionsPage:
final args = settings.arguments as List;
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>(param1: args));
case Routes.paymentMethodOptionsPage:
final args = settings.arguments as List;
return MaterialPageRoute<void>(builder: (_) => getIt.get<PaymentMethodOptionsPage>(param1: args));
case Routes.buyWebView: case Routes.buyWebView:
final args = settings.arguments as List; final args = settings.arguments as List;
@ -732,6 +750,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.setup2faInfoPage: case Routes.setup2faInfoPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>()); return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>());
case Routes.urqrAnimatedPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<AnimatedURPage>(param1: settings.arguments));
case Routes.homeSettings: case Routes.homeSettings:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments), builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments),

View file

@ -59,6 +59,8 @@ class Routes {
static const supportOtherLinks = '/support/other'; static const supportOtherLinks = '/support/other';
static const orderDetails = '/order_details'; static const orderDetails = '/order_details';
static const buySellPage = '/buy_sell_page'; static const buySellPage = '/buy_sell_page';
static const buyOptionsPage = '/buy_sell_options';
static const paymentMethodOptionsPage = '/payment_method_options';
static const buyWebView = '/buy_web_view'; static const buyWebView = '/buy_web_view';
static const unspentCoinsList = '/unspent_coins_list'; static const unspentCoinsList = '/unspent_coins_list';
static const unspentCoinsDetails = '/unspent_coins_details'; static const unspentCoinsDetails = '/unspent_coins_details';
@ -108,6 +110,7 @@ class Routes {
static const signPage = '/sign_page'; static const signPage = '/sign_page';
static const connectDevices = '/device/connect'; static const connectDevices = '/device/connect';
static const urqrAnimatedPage = '/urqr/animated_page';
static const walletGroupsDisplayPage = '/wallet_groups_display_page'; static const walletGroupsDisplayPage = '/wallet_groups_display_page';
static const walletGroupDescription = '/wallet_group_description'; static const walletGroupDescription = '/wallet_group_description';
} }

View file

@ -1,74 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/option_tile.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
class BuySellOptionsPage extends BasePage {
BuySellOptionsPage(this.dashboardViewModel, this.isBuyAction);
final DashboardViewModel dashboardViewModel;
final bool isBuyAction;
@override
String get title => isBuyAction ? S.current.buy : S.current.sell;
@override
AppBarStyle get appBarStyle => AppBarStyle.regular;
@override
Widget body(BuildContext context) {
final isLightMode = Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final availableProviders = isBuyAction
? dashboardViewModel.availableBuyProviders
: dashboardViewModel.availableSellProviders;
return ScrollableWithBottomSection(
content: Container(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 330),
child: Column(
children: [
...availableProviders.map((provider) {
final icon = Image.asset(
isLightMode ? provider.lightIcon : provider.darkIcon,
height: 40,
width: 40,
);
return Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: icon,
title: provider.toString(),
description: provider.providerDescription,
onPressed: () => provider.launchProvider(context, isBuyAction),
),
);
}).toList(),
],
),
),
),
),
bottomSection: Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(
isBuyAction
? S.of(context).select_buy_provider_notice
: S.of(context).select_sell_provider_notice,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
),
),
),
);
}
}

Some files were not shown because too many files have changed in this diff Show more