Merge branch 'main' of https://github.com/cake-tech/cake_wallet into mweb-bg-sync-2
14
.github/workflows/cache_dependencies.yml
vendored
|
@ -62,10 +62,22 @@ jobs:
|
|||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
/opt/android/cake_wallet/scripts/monero_c/release
|
||||
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
|
||||
|
||||
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||
name: Generate Externals
|
||||
run: |
|
||||
cd /opt/android/cake_wallet/scripts/android/
|
||||
source ./app_env.sh cakewallet
|
||||
./build_monero_all.sh
|
||||
|
||||
- name: 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
|
||||
|
|
48
.github/workflows/pr_test_build_android.yml
vendored
|
@ -115,6 +115,14 @@ jobs:
|
|||
cd /opt/android/cake_wallet/scripts/android/
|
||||
./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
|
||||
run: |
|
||||
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 tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
|
||||
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
|
||||
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
|
@ -201,6 +211,36 @@ jobs:
|
|||
run: |
|
||||
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
|
||||
run: |
|
||||
cd /opt/android/cake_wallet
|
||||
|
@ -231,6 +271,13 @@ jobs:
|
|||
with:
|
||||
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
|
||||
continue-on-error: true
|
||||
uses: adrey/slack-file-upload-action@1.0.5
|
||||
|
@ -241,3 +288,4 @@ jobs:
|
|||
title: "${{ env.BRANCH_NAME }}.apk"
|
||||
filename: ${{ env.BRANCH_NAME }}.apk
|
||||
initial_comment: ${{ github.event.head_commit.message }}
|
||||
|
||||
|
|
2
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -175,6 +175,8 @@ jobs:
|
|||
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
|
||||
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
|
||||
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
|
|
|
@ -92,3 +92,8 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test:runner:1.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'
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx3072M
|
||||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
|
BIN
assets/images/apple_pay_logo.png
Normal file
After Width: | Height: | Size: 55 KiB |
9
assets/images/apple_pay_round_dark.svg
Normal 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 |
9
assets/images/apple_pay_round_light.svg
Normal 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
After Width: | Height: | Size: 1.3 KiB |
8
assets/images/bank_dark.svg
Normal 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 |
8
assets/images/bank_light.svg
Normal 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
After Width: | Height: | Size: 9.7 KiB |
1
assets/images/card.svg
Normal 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 |
7
assets/images/card_dark.svg
Normal 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 |
12
assets/images/dollar_coin.svg
Normal 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 |
BIN
assets/images/google_pay_icon.png
Normal file
After Width: | Height: | Size: 12 KiB |
30
assets/images/meld_logo.svg
Normal 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
After Width: | Height: | Size: 11 KiB |
15
assets/images/skrill.svg
Normal 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 |
4
assets/images/usd_round_dark.svg
Normal 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 |
2
assets/images/usd_round_light.svg
Normal 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 |
BIN
assets/images/wallet_new.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
|
@ -1,6 +1,7 @@
|
|||
-
|
||||
uri: xmr-node.cakewallet.com:18081
|
||||
is_default: true
|
||||
trusted: true
|
||||
-
|
||||
uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
|
||||
is_default: false
|
||||
|
|
38
build-guide-win.md
Normal 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.
|
|
@ -11,7 +11,6 @@ import 'package:blockchain_utils/blockchain_utils.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_bitcoin/address_from_output.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
|
@ -620,8 +619,8 @@ abstract class ElectrumWalletBase
|
|||
|
||||
UtxoDetails _createUTXOS({
|
||||
required bool sendAll,
|
||||
required int credentialsAmount,
|
||||
required bool paysToSilentPayment,
|
||||
int credentialsAmount = 0,
|
||||
int? inputsCount,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) {
|
||||
|
@ -755,13 +754,11 @@ abstract class ElectrumWalletBase
|
|||
List<BitcoinOutput> outputs,
|
||||
int feeRate, {
|
||||
String? memo,
|
||||
int credentialsAmount = 0,
|
||||
bool hasSilentPayment = false,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) async {
|
||||
final utxoDetails = _createUTXOS(
|
||||
sendAll: true,
|
||||
credentialsAmount: credentialsAmount,
|
||||
paysToSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
@ -787,23 +784,11 @@ abstract class ElectrumWalletBase
|
|||
throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee);
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
// Attempting to send less than the dust limit
|
||||
if (_isBelowDust(amount)) {
|
||||
throw BitcoinTransactionNoDustException();
|
||||
}
|
||||
|
||||
if (credentialsAmount > 0) {
|
||||
final amountLeftForFee = amount - credentialsAmount;
|
||||
if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) {
|
||||
amount -= amountLeftForFee;
|
||||
fee += amountLeftForFee;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputs.length == 1) {
|
||||
outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount));
|
||||
}
|
||||
|
@ -833,6 +818,11 @@ abstract class ElectrumWalletBase
|
|||
bool hasSilentPayment = false,
|
||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||
}) async {
|
||||
// Attempting to send less than the dust limit
|
||||
if (_isBelowDust(credentialsAmount)) {
|
||||
throw BitcoinTransactionNoDustException();
|
||||
}
|
||||
|
||||
final utxoDetails = _createUTXOS(
|
||||
sendAll: false,
|
||||
credentialsAmount: credentialsAmount,
|
||||
|
@ -917,7 +907,43 @@ abstract class ElectrumWalletBase
|
|||
final lastOutput = updatedOutputs.last;
|
||||
final amountLeftForChange = amountLeftForChangeAndFee - fee;
|
||||
|
||||
if (!_isBelowDust(amountLeftForChange)) {
|
||||
if (_isBelowDust(amountLeftForChange)) {
|
||||
// If has change that is lower than dust, will end up with tx rejected by network rules
|
||||
// so remove the change amount
|
||||
updatedOutputs.removeLast();
|
||||
outputs.removeLast();
|
||||
|
||||
if (amountLeftForChange < 0) {
|
||||
if (!spendingAllCoins) {
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
inputsCount: utxoDetails.utxos.length + 1,
|
||||
memo: memo,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
} else {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
}
|
||||
|
||||
return EstimatedTxResult(
|
||||
utxos: utxoDetails.utxos,
|
||||
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
|
||||
publicKeys: utxoDetails.publicKeys,
|
||||
fee: fee,
|
||||
amount: amount,
|
||||
hasChange: false,
|
||||
isSendAll: spendingAllCoins,
|
||||
memo: memo,
|
||||
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
|
||||
spendsSilentPayment: utxoDetails.spendsSilentPayment,
|
||||
);
|
||||
} else {
|
||||
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
|
||||
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
|
||||
address: lastOutput.address,
|
||||
|
@ -931,88 +957,20 @@ abstract class ElectrumWalletBase
|
|||
isSilentPayment: lastOutput.isSilentPayment,
|
||||
isChange: true,
|
||||
);
|
||||
} else {
|
||||
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
|
||||
updatedOutputs.removeLast();
|
||||
outputs.removeLast();
|
||||
|
||||
// Still has inputs to spend before failing
|
||||
if (!spendingAllCoins) {
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
inputsCount: utxoDetails.utxos.length + 1,
|
||||
memo: memo,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
|
||||
final estimatedSendAll = await estimateSendAllTx(
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
return EstimatedTxResult(
|
||||
utxos: utxoDetails.utxos,
|
||||
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
|
||||
publicKeys: utxoDetails.publicKeys,
|
||||
fee: fee,
|
||||
amount: amount,
|
||||
hasChange: true,
|
||||
isSendAll: spendingAllCoins,
|
||||
memo: memo,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
||||
if (estimatedSendAll.amount == credentialsAmount) {
|
||||
return estimatedSendAll;
|
||||
}
|
||||
|
||||
// Estimate to user how much is needed to send to cover the fee
|
||||
final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1;
|
||||
throw BitcoinTransactionNoDustOnChangeException(
|
||||
bitcoinAmountToString(amount: maxAmountWithReturningChange),
|
||||
bitcoinAmountToString(amount: estimatedSendAll.amount),
|
||||
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
|
||||
spendsSilentPayment: utxoDetails.spendsSilentPayment,
|
||||
);
|
||||
}
|
||||
|
||||
// Attempting to send less than the dust limit
|
||||
if (_isBelowDust(amount)) {
|
||||
throw BitcoinTransactionNoDustException();
|
||||
}
|
||||
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if (totalAmount > (balance[currency]!.confirmed + balance[currency]!.secondConfirmed)) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
if (totalAmount > utxoDetails.allInputsAmount) {
|
||||
if (spendingAllCoins) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
} else {
|
||||
updatedOutputs.removeLast();
|
||||
outputs.removeLast();
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
updatedOutputs,
|
||||
feeRate,
|
||||
inputsCount: utxoDetails.utxos.length + 1,
|
||||
memo: memo,
|
||||
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return EstimatedTxResult(
|
||||
utxos: utxoDetails.utxos,
|
||||
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
|
||||
publicKeys: utxoDetails.publicKeys,
|
||||
fee: fee,
|
||||
amount: amount,
|
||||
hasChange: true,
|
||||
isSendAll: false,
|
||||
memo: memo,
|
||||
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
|
||||
spendsSilentPayment: utxoDetails.spendsSilentPayment,
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> calcFee({
|
||||
|
@ -1103,15 +1061,20 @@ abstract class ElectrumWalletBase
|
|||
: feeRate(transactionCredentials.priority!);
|
||||
|
||||
EstimatedTxResult estimatedTx;
|
||||
final updatedOutputs =
|
||||
outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
|
||||
final updatedOutputs = outputs
|
||||
.map((e) => BitcoinOutput(
|
||||
address: e.address,
|
||||
value: e.value,
|
||||
isSilentPayment: e.isSilentPayment,
|
||||
isChange: e.isChange,
|
||||
))
|
||||
.toList();
|
||||
|
||||
if (sendAll) {
|
||||
estimatedTx = await estimateSendAllTx(
|
||||
updatedOutputs,
|
||||
feeRateInt,
|
||||
memo: memo,
|
||||
credentialsAmount: credentialsAmount,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||
);
|
||||
|
|
|
@ -153,4 +153,9 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
|
||||
outputAddresses: outputAddresses,
|
||||
fee: fee);
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -386,10 +386,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_web_bluetooth
|
||||
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
|
||||
sha256: fcd03e2e5f82edcedcbc940f1b6a0635a50757374183254f447640886c53208e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
version: "0.2.4"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -560,7 +560,7 @@ packages:
|
|||
description:
|
||||
path: "packages/ledger-bitcoin"
|
||||
ref: HEAD
|
||||
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
|
||||
resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
|
||||
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
|
||||
source: git
|
||||
version: "0.0.3"
|
||||
|
@ -568,16 +568,16 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: ledger_flutter_plus
|
||||
sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
|
||||
sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.5"
|
||||
version: "1.4.1"
|
||||
ledger_litecoin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/ledger-litecoin"
|
||||
ref: HEAD
|
||||
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
|
||||
resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
|
||||
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
|
||||
source: git
|
||||
version: "0.0.2"
|
||||
|
|
|
@ -85,4 +85,8 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
|||
fee: fee,
|
||||
isReplaced: false,
|
||||
);
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,3 +35,34 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
|
|||
'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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ enum DeviceConnectionType {
|
|||
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
|
||||
[bool isIOS = false]) {
|
||||
switch (walletType) {
|
||||
case WalletType.monero:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.ethereum:
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
class MoneroWalletKeys {
|
||||
const MoneroWalletKeys(
|
||||
{required this.privateSpendKey,
|
||||
{required this.primaryAddress,
|
||||
required this.privateSpendKey,
|
||||
required this.privateViewKey,
|
||||
required this.publicSpendKey,
|
||||
required this.publicViewKey});
|
||||
|
||||
final String primaryAddress;
|
||||
final String publicViewKey;
|
||||
final String privateViewKey;
|
||||
final String publicSpendKey;
|
||||
|
|
|
@ -14,5 +14,8 @@ mixin PendingTransaction {
|
|||
int? get outputCount => null;
|
||||
PendingChange? change;
|
||||
|
||||
bool shouldCommitUR() => false;
|
||||
|
||||
Future<void> commit();
|
||||
Future<String?> commitUR();
|
||||
}
|
||||
|
|
|
@ -61,4 +61,8 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -47,4 +47,9 @@ class PendingEVMChainTransaction with PendingTransaction {
|
|||
|
||||
return '0x${Hex.HEX.encode(txid)}';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,9 @@ class WalletRestoreFromKeysException implements Exception {
|
|||
WalletRestoreFromKeysException({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -73,6 +73,7 @@ abstract class HavenWalletBase
|
|||
|
||||
@override
|
||||
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||
primaryAddress: haven_wallet.getAddress(accountIndex: 0, addressIndex: 0),
|
||||
privateSpendKey: haven_wallet.getSecretSpendKey(),
|
||||
privateViewKey: haven_wallet.getSecretViewKey(),
|
||||
publicSpendKey: haven_wallet.getPublicSpendKey(),
|
||||
|
|
|
@ -48,4 +48,9 @@ class PendingHavenTransaction with PendingTransaction {
|
|||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cw_monero/api/wallet.dart';
|
|||
import 'package:monero/monero.dart' as monero;
|
||||
|
||||
monero.wallet? wptr = null;
|
||||
bool get isViewOnly => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
|
||||
|
||||
int _wlptrForW = 0;
|
||||
monero.WalletListener? _wlptr = null;
|
||||
|
|
|
@ -13,7 +13,13 @@ import 'package:mutex/mutex.dart';
|
|||
|
||||
|
||||
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();
|
||||
monero.TransactionHistory? txhistory;
|
||||
|
@ -178,12 +184,13 @@ PendingTransactionDescription createTransactionMultDestSync(
|
|||
);
|
||||
}
|
||||
|
||||
void commitTransactionFromPointerAddress({required int address}) =>
|
||||
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address));
|
||||
String? commitTransactionFromPointerAddress({required int address, required bool useUR}) =>
|
||||
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
|
||||
|
||||
void commitTransaction({required monero.PendingTransaction transactionPointer}) {
|
||||
|
||||
final txCommit = monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
|
||||
String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
|
||||
final txCommit = useUR
|
||||
? monero.PendingTransaction_commitUR(transactionPointer, 120)
|
||||
: monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
|
||||
|
||||
final String? error = (() {
|
||||
final status = monero.PendingTransaction_status(transactionPointer.cast());
|
||||
|
@ -196,6 +203,11 @@ void commitTransaction({required monero.PendingTransaction transactionPointer})
|
|||
if (error != null) {
|
||||
throw CreationTransactionException(message: error);
|
||||
}
|
||||
if (useUR) {
|
||||
return txCommit as String?;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<PendingTransactionDescription> _createTransactionSync(Map args) async {
|
||||
|
|
|
@ -119,7 +119,7 @@ Future<bool> setupNodeSync(
|
|||
daemonUsername: login ?? '',
|
||||
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!);
|
||||
|
||||
|
@ -330,4 +330,4 @@ String signMessage(String message, {String address = ""}) {
|
|||
|
||||
bool verifyMessage(String message, String address, String signature) {
|
||||
return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_restore_from_keys_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/wallet.dart';
|
||||
import 'package:cw_monero/ledger.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
|
||||
class MoneroCException implements Exception {
|
||||
final String message;
|
||||
|
||||
MoneroCException(this.message);
|
||||
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
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'");
|
||||
}
|
||||
}
|
||||
|
||||
monero.WalletManager? _wmPtr;
|
||||
final monero.WalletManager wmPtr = Pointer.fromAddress((() {
|
||||
try {
|
||||
|
@ -60,6 +58,13 @@ final monero.WalletManager wmPtr = Pointer.fromAddress((() {
|
|||
return _wmPtr!.address;
|
||||
})());
|
||||
|
||||
void createWalletPointer() {
|
||||
final newWptr = monero.WalletManager_createWallet(wmPtr,
|
||||
path: "", password: "", language: "", networkType: 0);
|
||||
|
||||
wptr = newWptr;
|
||||
}
|
||||
|
||||
void createWalletSync(
|
||||
{required String path,
|
||||
required String password,
|
||||
|
@ -124,24 +129,24 @@ void restoreWalletFromKeysSync(
|
|||
int restoreHeight = 0}) {
|
||||
txhistory = null;
|
||||
var newWptr = (spendKey != "")
|
||||
? monero.WalletManager_createDeterministicWalletFromSpendKey(
|
||||
wmPtr,
|
||||
path: path,
|
||||
password: password,
|
||||
language: language,
|
||||
spendKeyString: spendKey,
|
||||
newWallet: true, // TODO(mrcyjanek): safe to remove
|
||||
restoreHeight: restoreHeight)
|
||||
: monero.WalletManager_createWalletFromKeys(
|
||||
wmPtr,
|
||||
path: path,
|
||||
password: password,
|
||||
restoreHeight: restoreHeight,
|
||||
addressString: address,
|
||||
viewKeyString: viewKey,
|
||||
spendKeyString: spendKey,
|
||||
nettype: 0,
|
||||
);
|
||||
? monero.WalletManager_createDeterministicWalletFromSpendKey(wmPtr,
|
||||
path: path,
|
||||
password: password,
|
||||
language: language,
|
||||
spendKeyString: spendKey,
|
||||
newWallet: true,
|
||||
// TODO(mrcyjanek): safe to remove
|
||||
restoreHeight: restoreHeight)
|
||||
: monero.WalletManager_createWalletFromKeys(
|
||||
wmPtr,
|
||||
path: path,
|
||||
password: password,
|
||||
restoreHeight: restoreHeight,
|
||||
addressString: address,
|
||||
viewKeyString: viewKey,
|
||||
spendKeyString: spendKey,
|
||||
nettype: 0,
|
||||
);
|
||||
|
||||
final status = monero.Wallet_status(newWptr);
|
||||
if (status != 0) {
|
||||
|
@ -156,7 +161,7 @@ void restoreWalletFromKeysSync(
|
|||
if (viewKey != viewKeyRestored && viewKey != "") {
|
||||
monero.WalletManager_closeWallet(wmPtr, newWptr, false);
|
||||
File(path).deleteSync();
|
||||
File(path+".keys").deleteSync();
|
||||
File(path + ".keys").deleteSync();
|
||||
newWptr = monero.WalletManager_createWalletFromKeys(
|
||||
wmPtr,
|
||||
path: path,
|
||||
|
@ -199,7 +204,7 @@ void restoreWalletFromSpendKeySync(
|
|||
// viewKeyString: '',
|
||||
// nettype: 0,
|
||||
// );
|
||||
|
||||
|
||||
txhistory = null;
|
||||
final newWptr = monero.WalletManager_createDeterministicWalletFromSpendKey(
|
||||
wmPtr,
|
||||
|
@ -230,41 +235,39 @@ void restoreWalletFromSpendKeySync(
|
|||
|
||||
String _lastOpenedWallet = "";
|
||||
|
||||
// void restoreMoneroWalletFromDevice(
|
||||
// {required String path,
|
||||
// required String password,
|
||||
// required String deviceName,
|
||||
// int nettype = 0,
|
||||
// int restoreHeight = 0}) {
|
||||
//
|
||||
// final pathPointer = path.toNativeUtf8();
|
||||
// final passwordPointer = password.toNativeUtf8();
|
||||
// final deviceNamePointer = deviceName.toNativeUtf8();
|
||||
// final errorMessagePointer = ''.toNativeUtf8();
|
||||
//
|
||||
// final isWalletRestored = restoreWalletFromDeviceNative(
|
||||
// pathPointer,
|
||||
// passwordPointer,
|
||||
// deviceNamePointer,
|
||||
// nettype,
|
||||
// restoreHeight,
|
||||
// errorMessagePointer) != 0;
|
||||
//
|
||||
// calloc.free(pathPointer);
|
||||
// calloc.free(passwordPointer);
|
||||
//
|
||||
// storeSync();
|
||||
//
|
||||
// if (!isWalletRestored) {
|
||||
// throw WalletRestoreFromKeysException(
|
||||
// message: convertUTF8ToString(pointer: errorMessagePointer));
|
||||
// }
|
||||
// }
|
||||
Future<void> restoreWalletFromHardwareWallet(
|
||||
{required String path,
|
||||
required String password,
|
||||
required String deviceName,
|
||||
int nettype = 0,
|
||||
int restoreHeight = 0}) async {
|
||||
txhistory = null;
|
||||
|
||||
final newWptrAddr = await Isolate.run(() {
|
||||
return monero.WalletManager_createWalletFromDevice(wmPtr,
|
||||
path: path,
|
||||
password: password,
|
||||
restoreHeight: restoreHeight,
|
||||
deviceName: deviceName)
|
||||
.address;
|
||||
});
|
||||
final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
|
||||
|
||||
final status = monero.Wallet_status(newWptr);
|
||||
|
||||
if (status != 0) {
|
||||
final error = monero.Wallet_errorString(newWptr);
|
||||
throw WalletRestoreFromSeedException(message: error);
|
||||
}
|
||||
wptr = newWptr;
|
||||
|
||||
openedWalletsByPath[path] = wptr!;
|
||||
}
|
||||
|
||||
Map<String, monero.wallet> openedWalletsByPath = {};
|
||||
|
||||
void loadWallet(
|
||||
{required String path, required String password, int nettype = 0}) {
|
||||
Future<void> loadWallet(
|
||||
{required String path, required String password, int nettype = 0}) async {
|
||||
if (openedWalletsByPath[path] != null) {
|
||||
txhistory = null;
|
||||
wptr = openedWalletsByPath[path]!;
|
||||
|
@ -278,8 +281,29 @@ void loadWallet(
|
|||
});
|
||||
}
|
||||
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;
|
||||
final status = monero.Wallet_status(newWptr);
|
||||
if (status != 0) {
|
||||
|
@ -287,6 +311,7 @@ void loadWallet(
|
|||
print(err);
|
||||
throw WalletOpeningException(message: err);
|
||||
}
|
||||
|
||||
wptr = newWptr;
|
||||
openedWalletsByPath[path] = wptr!;
|
||||
}
|
||||
|
@ -351,7 +376,7 @@ Future<void> _openWallet(Map<String, String> args) async => loadWallet(
|
|||
|
||||
bool _isWalletExist(String path) => isWalletExistSync(path: path);
|
||||
|
||||
void openWallet(
|
||||
Future<void> openWallet(
|
||||
{required String path,
|
||||
required String password,
|
||||
int nettype = 0}) async =>
|
||||
|
@ -425,3 +450,5 @@ Future<void> restoreFromSpendKey(
|
|||
});
|
||||
|
||||
bool isWalletExist({required String path}) => _isWalletExist(path);
|
||||
|
||||
bool isViewOnlyBySpendKey() => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
|
88
cw_monero/lib/ledger.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
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) {
|
||||
UniversalBle.onConnectionChange = (String deviceId, bool isConnected) {
|
||||
print("[Monero] Ledger Disconnected");
|
||||
_ledgerKeepAlive?.cancel();
|
||||
};
|
||||
_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];
|
||||
}
|
|
@ -19,6 +19,7 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/api/coins_info.dart';
|
||||
import 'package:cw_monero/api/monero_output.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/exceptions/monero_transaction_creation_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_history.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:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
|
||||
|
@ -118,6 +121,7 @@ abstract class MoneroWalletBase
|
|||
|
||||
@override
|
||||
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||
primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
|
||||
privateSpendKey: monero_wallet.getSecretSpendKey(),
|
||||
privateViewKey: monero_wallet.getSecretViewKey(),
|
||||
publicSpendKey: monero_wallet.getPublicSpendKey(),
|
||||
|
@ -223,6 +227,36 @@ abstract class MoneroWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
Future<void> stopSync() async {
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
|
@ -769,4 +803,10 @@ abstract class MoneroWalletBase
|
|||
|
||||
return monero_wallet.verifyMessage(message, address, signature);
|
||||
}
|
||||
|
||||
void setLedgerConnection(LedgerConnection connection) {
|
||||
final dummyWPtr = wptr ??
|
||||
monero.WalletManager_openWallet(wmPtr, path: '', password: '');
|
||||
enableLedgerExchange(dummyWPtr, connection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,13 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.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';
|
||||
import 'package:cw_monero/ledger.dart';
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
|
||||
|
@ -25,6 +28,15 @@ class MoneroNewWalletCredentials extends WalletCredentials {
|
|||
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 {
|
||||
MoneroRestoreWalletFromSeedCredentials(
|
||||
{required String name, required this.mnemonic, int height = 0, String? password})
|
||||
|
@ -39,14 +51,13 @@ class MoneroWalletLoadingException implements Exception {
|
|||
}
|
||||
|
||||
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
||||
MoneroRestoreWalletFromKeysCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.language,
|
||||
required this.address,
|
||||
required this.viewKey,
|
||||
required this.spendKey,
|
||||
int height = 0})
|
||||
MoneroRestoreWalletFromKeysCredentials({required String name,
|
||||
required String password,
|
||||
required this.language,
|
||||
required this.address,
|
||||
required this.viewKey,
|
||||
required this.spendKey,
|
||||
int height = 0})
|
||||
: super(name: name, password: password, height: height);
|
||||
|
||||
final String language;
|
||||
|
@ -59,7 +70,7 @@ class MoneroWalletService extends WalletService<
|
|||
MoneroNewWalletCredentials,
|
||||
MoneroRestoreWalletFromSeedCredentials,
|
||||
MoneroRestoreWalletFromKeysCredentials,
|
||||
MoneroNewWalletCredentials> {
|
||||
MoneroRestoreWalletFromHardwareCredentials> {
|
||||
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
@ -81,7 +92,7 @@ class MoneroWalletService extends WalletService<
|
|||
final lang = PolyseedLang.getByEnglishName(credentials.language);
|
||||
|
||||
final heightOverride =
|
||||
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
|
||||
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
|
||||
|
||||
return _restoreFromPolyseed(
|
||||
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
|
||||
|
@ -91,9 +102,9 @@ class MoneroWalletService extends WalletService<
|
|||
await monero_wallet_manager.createWallet(
|
||||
path: path, password: credentials.password!, language: credentials.language);
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: credentials.password!);
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: credentials.password!);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
|
@ -128,11 +139,11 @@ class MoneroWalletService extends WalletService<
|
|||
await monero_wallet_manager
|
||||
.openWalletAsync({'path': path, 'password': password});
|
||||
final walletInfo = walletInfoSource.values.firstWhere(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()));
|
||||
(info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: password);
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: password);
|
||||
final isValid = wallet.walletAddresses.validate();
|
||||
|
||||
if (!isValid) {
|
||||
|
@ -185,10 +196,9 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(
|
||||
String currentName, String password, String newName) async {
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values.firstWhere(
|
||||
(info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
(info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
final currentWallet = MoneroWallet(
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
@ -218,9 +228,9 @@ class MoneroWalletService extends WalletService<
|
|||
viewKey: credentials.viewKey,
|
||||
spendKey: credentials.spendKey);
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: credentials.password!);
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: credentials.password!);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
|
@ -232,9 +242,34 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
|
||||
throw UnimplementedError(
|
||||
"Restoring a Monero wallet from a hardware wallet is not yet supported!");
|
||||
Future<MoneroWallet> restoreFromHardwareWallet(
|
||||
MoneroRestoreWalletFromHardwareCredentials credentials) async {
|
||||
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
|
||||
|
@ -253,9 +288,9 @@ class MoneroWalletService extends WalletService<
|
|||
seed: credentials.mnemonic,
|
||||
restoreHeight: credentials.height!);
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: credentials.password!);
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: credentials.password!);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
|
@ -283,8 +318,8 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> _restoreFromPolyseed(
|
||||
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
|
||||
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
|
||||
WalletInfo walletInfo, PolyseedLang lang,
|
||||
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
|
||||
final height = overrideHeight ??
|
||||
getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
||||
|
@ -329,7 +364,9 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
dir.listSync().forEach((f) {
|
||||
final file = File(f.path);
|
||||
final name = f.path.split('/').last;
|
||||
final name = f.path
|
||||
.split('/')
|
||||
.last;
|
||||
final newPath = newWalletDirPath + '/$name';
|
||||
final newFile = File(newPath);
|
||||
|
||||
|
@ -366,4 +403,11 @@ class MoneroWalletService extends WalletService<
|
|||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool requireHardwareWalletConnection(String name) {
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
return walletInfo.isHardwareWallet;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/transaction_history.dart'
|
||||
as monero_transaction_history;
|
||||
|
@ -35,11 +36,32 @@ class PendingMoneroTransaction with PendingTransaction {
|
|||
String get feeFormatted => AmountConverter.amountIntToString(
|
||||
CryptoCurrency.xmr, pendingTransactionDescription.fee);
|
||||
|
||||
bool shouldCommitUR() => isViewOnly;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
try {
|
||||
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) {
|
||||
final message = e.toString();
|
||||
|
||||
|
|
|
@ -29,10 +29,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.3"
|
||||
version: "1.5.5"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -41,6 +41,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bluez:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bluez
|
||||
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -209,6 +217,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
encrypt:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -229,10 +245,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -267,6 +283,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -295,18 +319,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: hashlib
|
||||
sha256: d41795742c10947930630118c6836608deeb9047cd05aee32d2baeb697afd66a
|
||||
sha256: f572f2abce09fc7aee53f15927052b9732ea1053e540af8cae211111ee0b99b1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.2"
|
||||
version: "1.21.0"
|
||||
hashlib_codecs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hashlib_codecs
|
||||
sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f"
|
||||
sha256: "8cea9ccafcfeaa7324d2ae52c61c69f7ff71f4237507a018caab31b9e416e3b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.6.0"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -327,10 +351,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -403,6 +427,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -439,10 +479,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.6"
|
||||
mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -463,8 +503,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "impls/monero.dart"
|
||||
ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
|
||||
resolved-ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
|
@ -504,10 +544,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -544,10 +584,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -612,6 +660,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -737,6 +793,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -785,22 +857,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -811,4 +883,4 @@ packages:
|
|||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
flutter: ">=3.19.0"
|
||||
|
|
|
@ -25,9 +25,11 @@ dependencies:
|
|||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
|
||||
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
ledger_flutter_plus: ^1.4.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -37,4 +37,9 @@ class PendingNanoTransaction with PendingTransaction {
|
|||
await nanoClient.processBlock(block, "send");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
189
cw_shared_external/pubspec.lock
Normal 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"
|
|
@ -40,4 +40,9 @@ class PendingSolanaTransaction with PendingTransaction {
|
|||
|
||||
@override
|
||||
String get id => '';
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,9 @@ class PendingTronTransaction with PendingTransaction {
|
|||
|
||||
@override
|
||||
String get id => '';
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class ConnectionToNodeException implements Exception {
|
||||
ConnectionToNodeException({required this.message});
|
||||
|
||||
final String message;
|
||||
}
|
|
@ -3,5 +3,6 @@ class WalletRestoreFromKeysException implements Exception {
|
|||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
12
cw_wownero/lib/api/structs/account_row.dart
Normal 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;
|
||||
}
|
73
cw_wownero/lib/api/structs/coins_info_row.dart
Normal 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();
|
||||
}
|
15
cw_wownero/lib/api/structs/subaddress_row.dart
Normal 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;
|
||||
}
|
41
cw_wownero/lib/api/structs/transaction_info_row.dart
Normal 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();
|
||||
}
|
8
cw_wownero/lib/api/structs/ut8_box.dart
Normal 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();
|
||||
}
|
8
cw_wownero/lib/cw_wownero.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
import 'cw_wownero_platform_interface.dart';
|
||||
|
||||
class CwWownero {
|
||||
Future<String?> getPlatformVersion() {
|
||||
return CwWowneroPlatform.instance.getPlatformVersion();
|
||||
}
|
||||
}
|
17
cw_wownero/lib/cw_wownero_method_channel.dart
Normal 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;
|
||||
}
|
||||
}
|
29
cw_wownero/lib/cw_wownero_platform_interface.dart
Normal 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.');
|
||||
}
|
||||
}
|
1689
cw_wownero/lib/mywownero.dart
Normal file
|
@ -50,4 +50,9 @@ class PendingWowneroTransaction with PendingTransaction {
|
|||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ abstract class WowneroWalletBase
|
|||
|
||||
@override
|
||||
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||
primaryAddress: wownero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
|
||||
privateSpendKey: wownero_wallet.getSecretSpendKey(),
|
||||
privateViewKey: wownero_wallet.getSecretViewKey(),
|
||||
publicSpendKey: wownero_wallet.getPublicSpendKey(),
|
||||
|
|
|
@ -41,6 +41,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bluez:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bluez
|
||||
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -209,6 +217,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
encrypt:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -267,6 +283,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -403,6 +427,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
ledger_flutter_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ledger_flutter_plus
|
||||
sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.5"
|
||||
ledger_usb_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ledger_usb_plus
|
||||
sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -463,8 +503,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "impls/monero.dart"
|
||||
ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
|
||||
resolved-ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
|
@ -540,6 +580,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -552,10 +600,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -596,6 +644,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -721,6 +777,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -777,6 +849,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -786,5 +866,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
|
|
@ -25,7 +25,8 @@ dependencies:
|
|||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
ref: caaf1e56b1d2a254b332fdf848926fb963af4a3b
|
||||
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
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/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:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -23,14 +27,38 @@ abstract class BuyProvider {
|
|||
|
||||
String get darkIcon;
|
||||
|
||||
bool get isAggregator;
|
||||
|
||||
@override
|
||||
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<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
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,17 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
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/routes.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/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.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_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -15,10 +19,12 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
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);
|
||||
|
||||
static const _baseUrl = 'api.dfx.swiss';
|
||||
|
||||
// static const _signMessagePath = '/v1/auth/signMessage';
|
||||
static const _authPath = '/v1/auth';
|
||||
static const walletName = 'CakeWallet';
|
||||
|
@ -35,24 +41,8 @@ class DFXBuyProvider extends BuyProvider {
|
|||
@override
|
||||
String get darkIcon => 'assets/images/dfx_dark.png';
|
||||
|
||||
String get assetOut {
|
||||
switch (wallet.type) {
|
||||
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}");
|
||||
}
|
||||
}
|
||||
@override
|
||||
bool get isAggregator => false;
|
||||
|
||||
String get blockchain {
|
||||
switch (wallet.type) {
|
||||
|
@ -60,21 +50,13 @@ class DFXBuyProvider extends BuyProvider {
|
|||
case WalletType.bitcoinCash:
|
||||
case WalletType.litecoin:
|
||||
return 'Bitcoin';
|
||||
case WalletType.monero:
|
||||
return 'Monero';
|
||||
case WalletType.ethereum:
|
||||
return 'Ethereum';
|
||||
case WalletType.polygon:
|
||||
return 'Polygon';
|
||||
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";
|
||||
|
||||
// // 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 {
|
||||
final signMessage = await getSignature(await getSignMessage());
|
||||
Future<String> auth(String walletAddress) async {
|
||||
final signMessage = await getSignature(
|
||||
await getSignMessage(walletAddress), walletAddress);
|
||||
|
||||
final requestBody = jsonEncode({
|
||||
'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) {
|
||||
case WalletType.ethereum:
|
||||
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
|
||||
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 (!ledgerVM!.isConnected) {
|
||||
await Navigator.of(context).pushNamed(Routes.connectDevices,
|
||||
|
@ -152,26 +305,21 @@ class DFXBuyProvider extends BuyProvider {
|
|||
}
|
||||
|
||||
try {
|
||||
final assetOut = this.assetOut;
|
||||
final blockchain = this.blockchain;
|
||||
final actionType = isBuyAction == true ? '/buy' : '/sell';
|
||||
final actionType = isBuyAction ? '/buy' : '/sell';
|
||||
|
||||
final accessToken = await auth();
|
||||
final accessToken = await auth(cryptoCurrencyAddress);
|
||||
|
||||
final uri = Uri.https('services.dfx.swiss', actionType, {
|
||||
'session': accessToken,
|
||||
'lang': 'en',
|
||||
'asset-out': assetOut,
|
||||
'asset-out': isBuyAction ? quote.cryptoCurrency.toString() : quote.fiatCurrency.toString(),
|
||||
'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 (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [title, uri]);
|
||||
} else {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
266
lib/buy/meld/meld_buy_provider.dart
Normal file
|
@ -0,0 +1,266 @@
|
|||
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/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.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:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,20 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
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_provider.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/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.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/store/settings_store.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/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -39,6 +40,15 @@ class MoonPayProvider extends BuyProvider {
|
|||
static const _baseBuyProductUrl = 'buy.moonpay.com';
|
||||
static const _cIdBaseUrl = 'exchange-helper.cakewallet.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
|
||||
String get providerDescription =>
|
||||
|
@ -53,6 +63,17 @@ class MoonPayProvider extends BuyProvider {
|
|||
@override
|
||||
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) {
|
||||
switch (theme.type) {
|
||||
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 {
|
||||
final uri = Uri.https(_cIdBaseUrl, "/api/moonpay");
|
||||
|
||||
final response = await post(
|
||||
uri,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': _exchangeHelperApiKey,
|
||||
},
|
||||
body: json.encode({'query': query}),
|
||||
);
|
||||
final response = await post(uri,
|
||||
headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey},
|
||||
body: json.encode({'query': query}));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return (jsonDecode(response.body) as Map<String, dynamic>)['signature'] as String;
|
||||
|
@ -94,85 +99,195 @@ class MoonPayProvider extends BuyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Uri> requestSellMoonPayUrl({
|
||||
required CryptoCurrency currency,
|
||||
required String refundWalletAddress,
|
||||
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,
|
||||
};
|
||||
Future<Map<String, dynamic>> fetchFiatCredentials(
|
||||
String fiatCurrency, String cryptocurrency, String? paymentMethod) async {
|
||||
final params = {'baseCurrencyCode': fiatCurrency.toLowerCase(), 'apiKey': _apiKey};
|
||||
|
||||
if (_apiKey.isNotEmpty) {
|
||||
params['apiKey'] = _apiKey;
|
||||
if (paymentMethod != null) params['paymentMethod'] = paymentMethod;
|
||||
|
||||
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:
|
||||
static const _currenciesSuffix = '/v3/currencies';
|
||||
static const _quoteSuffix = '/buy_quote';
|
||||
static const _transactionsSuffix = '/v1/transactions';
|
||||
static const _ipAddressSuffix = '/v4/ip_address';
|
||||
Future<List<PaymentMethod>> getAvailablePaymentTypes(
|
||||
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
|
||||
final List<PaymentMethod> paymentMethods = [];
|
||||
|
||||
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 = {
|
||||
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
|
||||
'language': settingsStore.languageCode,
|
||||
'colorCode': settingsStore.currentTheme.type == ThemeType.dark
|
||||
'baseCurrencyCode': baseCurrencyCode,
|
||||
'baseCurrencyAmount': amount.toString(),
|
||||
'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.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
|
||||
'baseCurrencyCode': settingsStore.fiatCurrency.title,
|
||||
'baseCurrencyAmount': amount ?? '0',
|
||||
'currencyCode': _normalizeCurrency(currency),
|
||||
'walletAddress': walletAddress,
|
||||
'baseCurrencyCode': isBuyAction ? quote.fiatCurrency.name : quote.cryptoCurrency.name,
|
||||
'baseCurrencyAmount': amount.toString(),
|
||||
'walletAddress': cryptoCurrencyAddress,
|
||||
'lockAmount': 'false',
|
||||
'showAllCurrencies': 'false',
|
||||
'showWalletAddressForm': 'false',
|
||||
'enabledPaymentMethods':
|
||||
'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment',
|
||||
if (isBuyAction)
|
||||
'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) {
|
||||
params['apiKey'] = _apiKey;
|
||||
}
|
||||
if (isBuyAction) params['currencyCode'] = quote.cryptoCurrency.name;
|
||||
if (!isBuyAction) params['quoteCurrencyCode'] = quote.cryptoCurrency.name;
|
||||
|
||||
final originalUri = Uri.https(
|
||||
baseBuyUrl,
|
||||
'',
|
||||
params,
|
||||
);
|
||||
try {
|
||||
{
|
||||
final uri = await requestMoonPayUrl(
|
||||
walletAddress: cryptoCurrencyAddress,
|
||||
settingsStore: _settingsStore,
|
||||
isBuyAction: isBuyAction,
|
||||
amount: amount.toString(),
|
||||
params: params);
|
||||
|
||||
if (isTestEnvironment) {
|
||||
return originalUri;
|
||||
if (await canLaunchUrl(uri)) {
|
||||
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 query = Map<String, dynamic>.from(originalUri.queryParameters);
|
||||
|
@ -181,33 +296,6 @@ class MoonPayProvider extends BuyProvider {
|
|||
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 {
|
||||
final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey;
|
||||
final uri = Uri.parse(url);
|
||||
|
@ -235,74 +323,83 @@ class MoonPayProvider extends BuyProvider {
|
|||
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) {
|
||||
if (currency == CryptoCurrency.maticpoly) {
|
||||
return "POL_POLYGON";
|
||||
} else if (currency == CryptoCurrency.matic) {
|
||||
return "POL";
|
||||
if (currency.tag == 'POLY') {
|
||||
return '${currency.title.toLowerCase()}_polygon';
|
||||
}
|
||||
|
||||
if (currency.tag == 'TRX') {
|
||||
return '${currency.title.toLowerCase()}_trx';
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
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/routes.dart';
|
||||
import 'package:cake_wallet/store/settings_store.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/currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class OnRamperBuyProvider extends BuyProvider {
|
||||
|
@ -16,9 +22,15 @@ class OnRamperBuyProvider extends BuyProvider {
|
|||
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
|
||||
|
||||
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;
|
||||
|
||||
String get _apiKey => secrets.onramperApiKey;
|
||||
|
||||
@override
|
||||
String get title => 'Onramper';
|
||||
|
||||
|
@ -31,74 +43,327 @@ class OnRamperBuyProvider extends BuyProvider {
|
|||
@override
|
||||
String get darkIcon => 'assets/images/onramper_dark.png';
|
||||
|
||||
String get _apiKey => secrets.onramperApiKey;
|
||||
@override
|
||||
bool get isAggregator => true;
|
||||
|
||||
String get _normalizeCryptoCurrency {
|
||||
switch (wallet.currency) {
|
||||
case CryptoCurrency.ltc:
|
||||
return "LTC_LITECOIN";
|
||||
case CryptoCurrency.xmr:
|
||||
return "XMR_MONERO";
|
||||
case CryptoCurrency.bch:
|
||||
return "BCH_BITCOINCASH";
|
||||
case CryptoCurrency.nano:
|
||||
return "XNO_NANO";
|
||||
default:
|
||||
return wallet.currency.title;
|
||||
Future<List<PaymentMethod>> getAvailablePaymentTypes(
|
||||
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
|
||||
final params = {
|
||||
'fiatCurrency': fiatCurrency,
|
||||
'type': isBuyAction ? 'buy' : 'sell',
|
||||
'isRecurringPayment': 'false'
|
||||
};
|
||||
|
||||
final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params);
|
||||
|
||||
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> 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) {
|
||||
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
|
||||
Future<Map<String, dynamic>> getOnrampMetadata() async {
|
||||
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) {
|
||||
String primaryColor,
|
||||
secondaryColor,
|
||||
primaryTextColor,
|
||||
secondaryTextColor,
|
||||
containerColor,
|
||||
cardColor;
|
||||
@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;
|
||||
|
||||
primaryColor = getColorStr(Theme.of(context).primaryColor);
|
||||
secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
|
||||
primaryTextColor =
|
||||
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
||||
secondaryTextColor = getColorStr(
|
||||
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
|
||||
containerColor = getColorStr(Theme.of(context).colorScheme.background);
|
||||
cardColor = getColorStr(Theme.of(context).cardColor);
|
||||
if (paymentType != null && paymentType != PaymentType.all) {
|
||||
paymentMethod = normalizePaymentMethod(paymentType);
|
||||
if (paymentMethod == null) paymentMethod = paymentType.name;
|
||||
}
|
||||
|
||||
final actionType = isBuyAction ? 'buy' : 'sell';
|
||||
|
||||
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) {
|
||||
cardColor = getColorStr(Colors.white);
|
||||
}
|
||||
|
||||
final networkName =
|
||||
wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
|
||||
final defaultCrypto = _getNormalizeCryptoCurrency(quote.cryptoCurrency);
|
||||
|
||||
return Uri.https(_baseUrl, '', <String, dynamic>{
|
||||
final paymentMethod = normalizePaymentMethod(quote.paymentType);
|
||||
|
||||
final uri = Uri.https(_baseUrl, '', {
|
||||
'apiKey': _apiKey,
|
||||
'defaultCrypto': _normalizeCryptoCurrency,
|
||||
'sell_defaultCrypto': _normalizeCryptoCurrency,
|
||||
'networkWallets': '${networkName}:${wallet.walletAddresses.address}',
|
||||
'mode': actionType,
|
||||
'${prefix}defaultFiat': quote.fiatCurrency.name,
|
||||
'${prefix}defaultCrypto': defaultCrypto,
|
||||
'${prefix}defaultAmount': amount.toString(),
|
||||
if (paymentMethod != null) '${prefix}defaultPaymentMethod': paymentMethod,
|
||||
'onlyOnramps': quote.rampId,
|
||||
'networkWallets': '$defaultCrypto:$cryptoCurrencyAddress',
|
||||
'walletAddress': cryptoCurrencyAddress,
|
||||
'supportSwap': "false",
|
||||
'primaryColor': primaryColor,
|
||||
'secondaryColor': secondaryColor,
|
||||
'containerColor': containerColor,
|
||||
'primaryTextColor': primaryTextColor,
|
||||
'secondaryTextColor': secondaryTextColor,
|
||||
'containerColor': containerColor,
|
||||
'cardColor': cardColor,
|
||||
'mode': isBuyAction == true ? 'buy' : 'sell',
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||
final uri = requestOnramperUrl(context, isBuyAction);
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.webViewPage, arguments: [title, uri]);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} 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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
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/routes.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/utils/show_pop_up.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_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -15,7 +20,8 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
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);
|
||||
|
||||
static const _baseUrl = 'applink.robinhood.com';
|
||||
|
@ -33,6 +39,9 @@ class RobinhoodBuyProvider extends BuyProvider {
|
|||
@override
|
||||
String get darkIcon => 'assets/images/robinhood_dark.png';
|
||||
|
||||
@override
|
||||
bool get isAggregator => false;
|
||||
|
||||
String get _applicationId => secrets.robinhoodApplicationId;
|
||||
|
||||
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 (!ledgerVM!.isConnected) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
20
lib/buy/sell_buy_states.dart
Normal 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 {}
|
|
@ -42,6 +42,9 @@ class WyreBuyProvider extends BuyProvider {
|
|||
@override
|
||||
String get darkIcon => 'assets/images/robinhood_dark.png';
|
||||
|
||||
@override
|
||||
bool get isAggregator => false;
|
||||
|
||||
String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl;
|
||||
|
||||
String baseApiUrl;
|
||||
|
@ -148,10 +151,4 @@ class WyreBuyProvider extends BuyProvider {
|
|||
receiveAddress: wallet.walletAddresses.address,
|
||||
walletId: wallet.id);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) {
|
||||
// TODO: implement launchProvider
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
class CakePayOrder {
|
||||
final String orderId;
|
||||
final List<OrderCard> cards;
|
||||
|
|
|
@ -82,10 +82,12 @@ class CakePayService {
|
|||
}
|
||||
|
||||
/// Logout
|
||||
Future<void> logout(String email) async {
|
||||
Future<void> logout([String? email]) async {
|
||||
await secureStorage.delete(key: cakePayUsernameStorageKey);
|
||||
await secureStorage.delete(key: cakePayUserTokenKey);
|
||||
await cakePayApi.logoutUser(email: email, apiKey: cakePayApiKey);
|
||||
if (email != null) {
|
||||
await cakePayApi.logoutUser(email: email, apiKey: cakePayApiKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// Purchase Gift Card
|
||||
|
|
|
@ -268,9 +268,7 @@ class BackupService {
|
|||
final currentFiatCurrency = data[PreferencesKey.currentFiatCurrencyKey] as String?;
|
||||
final shouldSaveRecipientAddress = data[PreferencesKey.shouldSaveRecipientAddressKey] as bool?;
|
||||
final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?;
|
||||
final disableBuy = data[PreferencesKey.disableBuyKey] as bool?;
|
||||
final disableSell = data[PreferencesKey.disableSellKey] as bool?;
|
||||
final defaultBuyProvider = data[PreferencesKey.defaultBuyProvider] as int?;
|
||||
final disableTradeOption = data[PreferencesKey.disableTradeOption] as bool?;
|
||||
final currentTransactionPriorityKeyLegacy =
|
||||
data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?;
|
||||
final currentBitcoinElectrumSererId =
|
||||
|
@ -323,14 +321,8 @@ class BackupService {
|
|||
if (isAppSecure != null)
|
||||
await _sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure);
|
||||
|
||||
if (disableBuy != null)
|
||||
await _sharedPreferences.setBool(PreferencesKey.disableBuyKey, disableBuy);
|
||||
|
||||
if (disableSell != null)
|
||||
await _sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell);
|
||||
|
||||
if (defaultBuyProvider != null)
|
||||
await _sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider);
|
||||
if (disableTradeOption != null)
|
||||
await _sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption);
|
||||
|
||||
if (currentTransactionPriorityKeyLegacy != null)
|
||||
await _sharedPreferences.setInt(
|
||||
|
@ -516,10 +508,7 @@ class BackupService {
|
|||
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
|
||||
PreferencesKey.shouldSaveRecipientAddressKey:
|
||||
_sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
|
||||
PreferencesKey.disableBuyKey: _sharedPreferences.getBool(PreferencesKey.disableBuyKey),
|
||||
PreferencesKey.disableSellKey: _sharedPreferences.getBool(PreferencesKey.disableSellKey),
|
||||
PreferencesKey.defaultBuyProvider:
|
||||
_sharedPreferences.getInt(PreferencesKey.defaultBuyProvider),
|
||||
PreferencesKey.disableTradeOption: _sharedPreferences.getBool(PreferencesKey.disableTradeOption),
|
||||
PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength),
|
||||
PreferencesKey.currentTransactionPriorityKeyLegacy:
|
||||
_sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),
|
||||
|
|
47
lib/core/selectable_option.dart
Normal 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;
|
||||
}
|
||||
|
||||
|
|
@ -14,7 +14,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class WalletLoadingService {
|
||||
WalletLoadingService(this.sharedPreferences, this.keyService, this.walletServiceFactory);
|
||||
WalletLoadingService(
|
||||
this.sharedPreferences,
|
||||
this.keyService,
|
||||
this.walletServiceFactory,
|
||||
);
|
||||
|
||||
final SharedPreferences sharedPreferences;
|
||||
final KeyService keyService;
|
||||
|
@ -77,7 +81,8 @@ class WalletLoadingService {
|
|||
await updateMoneroWalletPassword(wallet);
|
||||
}
|
||||
|
||||
await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name);
|
||||
await sharedPreferences.setString(
|
||||
PreferencesKey.currentWalletName, wallet.name);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||
|
||||
|
@ -129,4 +134,9 @@ class WalletLoadingService {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
54
lib/di.dart
|
@ -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/new_wallet_type_arguments.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/wallet_connect/wallet_connect_key_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_record.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/wallet_edit_page_arguments.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/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_node_page.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/backup/backup_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/webview_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_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/ur/animated_ur_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_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/utils/payment_request.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/anon_invoice_page_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_list_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/authentication_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:mobx/mobx.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';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
@ -573,7 +581,7 @@ Future<void> setup({
|
|||
);
|
||||
} else {
|
||||
// wallet is already loaded:
|
||||
if (appStore.wallet != null) {
|
||||
if (appStore.wallet != null || requireHardwareWalletConnection()) {
|
||||
// goes to the dashboard:
|
||||
authStore.allowed();
|
||||
// trigger any deep links:
|
||||
|
@ -767,10 +775,12 @@ Future<void> setup({
|
|||
);
|
||||
}
|
||||
|
||||
getIt.registerFactory(() => WalletListPage(
|
||||
walletListViewModel: getIt.get<WalletListViewModel>(),
|
||||
authService: getIt.get<AuthService>(),
|
||||
));
|
||||
getIt.registerFactoryParam<WalletListPage, Function(BuildContext)?, void>(
|
||||
(Function(BuildContext)? onWalletLoaded, _) => WalletListPage(
|
||||
walletListViewModel: getIt.get<WalletListViewModel>(),
|
||||
authService: getIt.get<AuthService>(),
|
||||
onWalletLoaded: onWalletLoaded,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>(
|
||||
(WalletListViewModel walletListViewModel, _) => WalletEditViewModel(
|
||||
|
@ -899,6 +909,11 @@ Future<void> setup({
|
|||
getIt.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>()));
|
||||
|
||||
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>(
|
||||
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
|
||||
|
@ -993,6 +1008,10 @@ Future<void> setup({
|
|||
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.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
|
||||
|
@ -1184,8 +1203,25 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => BuyAmountViewModel());
|
||||
|
||||
getIt.registerFactoryParam<BuySellOptionsPage, bool, void>(
|
||||
(isBuyOption, _) => BuySellOptionsPage(getIt.get<DashboardViewModel>(), isBuyOption));
|
||||
getIt.registerFactory(() => BuySellViewModel(getIt.get<AppStore>()));
|
||||
|
||||
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(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
|
|
@ -259,6 +259,10 @@ Future<void> defaultSettingsMigration(
|
|||
case 42:
|
||||
updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences);
|
||||
break;
|
||||
case 43:
|
||||
_updateCakeXmrNode(nodes);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -273,6 +277,15 @@ Future<void> defaultSettingsMigration(
|
|||
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) {
|
||||
final btcElectrumNode = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri);
|
||||
|
||||
|
@ -843,7 +856,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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -23,31 +23,18 @@ class MainActions {
|
|||
});
|
||||
|
||||
static List<MainActions> all = [
|
||||
buyAction,
|
||||
showWalletsAction,
|
||||
receiveAction,
|
||||
exchangeAction,
|
||||
sendAction,
|
||||
sellAction,
|
||||
tradeAction,
|
||||
];
|
||||
|
||||
static MainActions buyAction = MainActions._(
|
||||
name: (context) => S.of(context).buy,
|
||||
image: 'assets/images/buy.png',
|
||||
isEnabled: (viewModel) => viewModel.isEnabledBuyAction,
|
||||
canShow: (viewModel) => viewModel.hasBuyAction,
|
||||
static MainActions showWalletsAction = MainActions._(
|
||||
name: (context) => S.of(context).wallets,
|
||||
image: 'assets/images/wallet_new.png',
|
||||
onTap: (BuildContext context, DashboardViewModel viewModel) async {
|
||||
if (!viewModel.isEnabledBuyAction) {
|
||||
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());
|
||||
}
|
||||
Navigator.pushNamed(context, Routes.walletList);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -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;
|
||||
try {
|
||||
defaultSellProvider != null
|
||||
? await defaultSellProvider.launchProvider(context, false)
|
||||
: await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false);
|
||||
} catch (e) {
|
||||
await _showErrorDialog(context, defaultSellProvider.toString(), e.toString());
|
||||
}
|
||||
static MainActions tradeAction = MainActions._(
|
||||
name: (context) => '${S.of(context).buy} / ${S.of(context).sell}',
|
||||
image: 'assets/images/buy_sell.png',
|
||||
isEnabled: (viewModel) => viewModel.isEnabledTradeAction,
|
||||
canShow: (viewModel) => viewModel.hasTradeAction,
|
||||
onTap: (BuildContext context, DashboardViewModel viewModel) async {
|
||||
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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -21,10 +21,8 @@ class PreferencesKey {
|
|||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
|
||||
static const isAppSecureKey = 'is_app_secure';
|
||||
static const disableBuyKey = 'disable_buy';
|
||||
static const disableSellKey = 'disable_sell';
|
||||
static const disableTradeOption = 'disable_buy';
|
||||
static const disableBulletinKey = 'disable_bulletin';
|
||||
static const defaultBuyProvider = 'default_buy_provider';
|
||||
static const walletListOrder = 'wallet_list_order';
|
||||
static const contactListOrder = 'contact_list_order';
|
||||
static const walletListAscending = 'wallet_list_ascending';
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
import 'package:cake_wallet/buy/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/onramper/onramper_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
enum ProviderType {
|
||||
askEachTime,
|
||||
robinhood,
|
||||
dfx,
|
||||
onramper,
|
||||
moonpay,
|
||||
}
|
||||
enum ProviderType { robinhood, dfx, onramper, moonpay, meld }
|
||||
|
||||
extension ProviderTypeName on ProviderType {
|
||||
String get title {
|
||||
switch (this) {
|
||||
case ProviderType.askEachTime:
|
||||
return 'Ask each time';
|
||||
case ProviderType.robinhood:
|
||||
return 'Robinhood Connect';
|
||||
case ProviderType.dfx:
|
||||
|
@ -27,13 +21,13 @@ extension ProviderTypeName on ProviderType {
|
|||
return 'Onramper';
|
||||
case ProviderType.moonpay:
|
||||
return 'MoonPay';
|
||||
case ProviderType.meld:
|
||||
return 'Meld';
|
||||
}
|
||||
}
|
||||
|
||||
String get id {
|
||||
switch (this) {
|
||||
case ProviderType.askEachTime:
|
||||
return 'ask_each_time_provider';
|
||||
case ProviderType.robinhood:
|
||||
return 'robinhood_connect_provider';
|
||||
case ProviderType.dfx:
|
||||
|
@ -42,6 +36,8 @@ extension ProviderTypeName on ProviderType {
|
|||
return 'onramper_provider';
|
||||
case ProviderType.moonpay:
|
||||
return 'moonpay_provider';
|
||||
case ProviderType.meld:
|
||||
return 'meld_provider';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,14 +48,13 @@ class ProvidersHelper {
|
|||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.wownero:
|
||||
return [ProviderType.askEachTime, ProviderType.onramper];
|
||||
return [ProviderType.onramper];
|
||||
case WalletType.monero:
|
||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.dfx];
|
||||
return [ProviderType.onramper, ProviderType.dfx];
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.polygon:
|
||||
case WalletType.ethereum:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.dfx,
|
||||
ProviderType.robinhood,
|
||||
|
@ -68,10 +63,13 @@ class ProvidersHelper {
|
|||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
case WalletType.solana:
|
||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay];
|
||||
return [
|
||||
ProviderType.onramper,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay
|
||||
];
|
||||
case WalletType.tron:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
|
@ -88,28 +86,24 @@ class ProvidersHelper {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.moonpay,
|
||||
ProviderType.dfx,
|
||||
];
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return [ProviderType.askEachTime, ProviderType.moonpay];
|
||||
return [ProviderType.moonpay];
|
||||
case WalletType.solana:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.tron:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.monero:
|
||||
return [ProviderType.dfx];
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.none:
|
||||
|
@ -129,7 +123,9 @@ class ProvidersHelper {
|
|||
return getIt.get<OnRamperBuyProvider>();
|
||||
case ProviderType.moonpay:
|
||||
return getIt.get<MoonPayProvider>();
|
||||
case ProviderType.askEachTime:
|
||||
case ProviderType.meld:
|
||||
return getIt.get<MeldBuyProvider>();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
Future<String> presentQRScanner() async {
|
||||
Future<String> presentQRScanner(BuildContext context) async {
|
||||
isQrScannerShown = true;
|
||||
try {
|
||||
final result = await BarcodeScanner.scan();
|
||||
final result = await Navigator.of(context).push<String>(
|
||||
MaterialPageRoute(
|
||||
builder:(context) {
|
||||
return BarcodeScannerSimple();
|
||||
},
|
||||
),
|
||||
);
|
||||
isQrScannerShown = false;
|
||||
return result.rawContent.trim();
|
||||
return result??'';
|
||||
} catch (e) {
|
||||
isQrScannerShown = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,7 +205,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 42,
|
||||
initialMigrationVersion: 43,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -225,6 +225,19 @@ class CWMonero extends Monero {
|
|||
language: language,
|
||||
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
|
||||
WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
|
@ -248,6 +261,7 @@ class CWMonero extends Monero {
|
|||
final moneroWallet = wallet as MoneroWallet;
|
||||
final keys = moneroWallet.keys;
|
||||
return <String, String>{
|
||||
'primaryAddress': keys.primaryAddress,
|
||||
'privateSpendKey': keys.privateSpendKey,
|
||||
'privateViewKey': keys.privateViewKey,
|
||||
'publicSpendKey': keys.publicSpendKey,
|
||||
|
@ -357,9 +371,44 @@ class CWMonero extends Monero {
|
|||
Future<int> getCurrentHeight() async {
|
||||
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
|
||||
void monerocCheck() {
|
||||
checkIfMoneroCIsFine();
|
||||
}
|
||||
|
||||
@override
|
||||
void setLedgerConnection(Object wallet, ledger.LedgerConnection connection) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
moneroWallet.setLedgerConnection(connection);
|
||||
}
|
||||
|
||||
@override
|
||||
void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
|
||||
gLedger = connection;
|
||||
keepAlive(connection);
|
||||
}
|
||||
|
||||
bool isViewOnly() {
|
||||
return isViewOnlyBySpendKey();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
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/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/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: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';
|
||||
|
||||
ReactionDisposer? _onAuthenticationStateChange;
|
||||
|
||||
dynamic loginError;
|
||||
StreamController<dynamic> authenticatedErrorStreamController = BehaviorSubject<dynamic>();
|
||||
StreamController<dynamic> authenticatedErrorStreamController =
|
||||
BehaviorSubject<dynamic>();
|
||||
|
||||
void startAuthenticationStateChange(
|
||||
AuthenticationStore authenticationStore,
|
||||
|
@ -27,18 +37,49 @@ void startAuthenticationStateChange(
|
|||
_onAuthenticationStateChange ??= autorun((_) async {
|
||||
final state = authenticationStore.state;
|
||||
|
||||
if (state == AuthenticationState.installed && !SettingsStoreBase.walletPasswordDirectInput) {
|
||||
if (state == AuthenticationState.installed &&
|
||||
!SettingsStoreBase.walletPasswordDirectInput) {
|
||||
try {
|
||||
await loadCurrentWallet();
|
||||
if (!requireHardwareWalletConnection()) await loadCurrentWallet();
|
||||
} catch (error, stack) {
|
||||
loginError = error;
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
|
||||
ExceptionHandler.onError(
|
||||
FlutterErrorDetails(exception: error, stack: stack));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
ExceptionHandler.showError(
|
||||
(await authenticatedErrorStreamController.stream.first).toString());
|
||||
|
|
|
@ -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/backup/backup_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/payment_method_options_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/cake_pay.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/contact/contact_list_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/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/ur/animated_ur_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_keys/wallet_keys_page.dart';
|
||||
|
@ -128,7 +131,8 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.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';
|
||||
|
||||
late RouteSettings currentRouteSettings;
|
||||
|
@ -209,6 +213,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
final type = arguments[0] as WalletType;
|
||||
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
|
||||
|
||||
if (type == WalletType.monero)
|
||||
return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
|
||||
|
||||
return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
|
||||
|
||||
case Routes.setupPin:
|
||||
|
@ -400,8 +407,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
|
||||
|
||||
case Routes.walletList:
|
||||
final onWalletLoaded = settings.arguments as Function(BuildContext)?;
|
||||
return MaterialPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<WalletListPage>());
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<WalletListPage>(param1: onWalletLoaded),
|
||||
);
|
||||
|
||||
case Routes.walletEdit:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -570,7 +580,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
|
||||
case Routes.buySellPage:
|
||||
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:
|
||||
final args = settings.arguments as List;
|
||||
|
@ -732,6 +750,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
case Routes.setup2faInfoPage:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>());
|
||||
|
||||
case Routes.urqrAnimatedPage:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<AnimatedURPage>(param1: settings.arguments));
|
||||
|
||||
case Routes.homeSettings:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments),
|
||||
|
|
|
@ -59,6 +59,8 @@ class Routes {
|
|||
static const supportOtherLinks = '/support/other';
|
||||
static const orderDetails = '/order_details';
|
||||
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 unspentCoinsList = '/unspent_coins_list';
|
||||
static const unspentCoinsDetails = '/unspent_coins_details';
|
||||
|
@ -108,6 +110,7 @@ class Routes {
|
|||
|
||||
static const signPage = '/sign_page';
|
||||
static const connectDevices = '/device/connect';
|
||||
static const urqrAnimatedPage = '/urqr/animated_page';
|
||||
static const walletGroupsDisplayPage = '/wallet_groups_display_page';
|
||||
static const walletGroupDescription = '/wallet_group_description';
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
48
lib/src/screens/buy/buy_sell_options_page.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'package:cake_wallet/core/selectable_option.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/select_options_page.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class BuyOptionsPage extends SelectOptionsPage {
|
||||
BuyOptionsPage({required this.items, this.pickAnOption, this.confirmOption});
|
||||
|
||||
final List<SelectableItem> items;
|
||||
final Function(SelectableOption option)? pickAnOption;
|
||||
final Function(BuildContext context)? confirmOption;
|
||||
|
||||
@override
|
||||
String get pageTitle => S.current.choose_a_provider;
|
||||
|
||||
@override
|
||||
EdgeInsets? get contentPadding => null;
|
||||
|
||||
@override
|
||||
EdgeInsets? get tilePadding => EdgeInsets.only(top: 8);
|
||||
|
||||
@override
|
||||
EdgeInsets? get innerPadding => EdgeInsets.symmetric(horizontal: 24, vertical: 8);
|
||||
|
||||
@override
|
||||
double? get imageHeight => 40;
|
||||
|
||||
@override
|
||||
double? get imageWidth => 40;
|
||||
|
||||
@override
|
||||
Color? get selectedBackgroundColor => null;
|
||||
|
||||
@override
|
||||
double? get tileBorderRadius => 30;
|
||||
|
||||
@override
|
||||
String get bottomSectionText => '';
|
||||
|
||||
@override
|
||||
void Function(SelectableOption option)? get onOptionTap => pickAnOption;
|
||||
|
||||
@override
|
||||
String get primaryButtonText => S.current.confirm;
|
||||
|
||||
@override
|
||||
void Function(BuildContext context)? get primaryButtonAction => confirmOption;
|
||||
}
|
469
lib/src/screens/buy/buy_sell_page.dart
Normal file
|
@ -0,0 +1,469 @@
|
|||
import 'package:cake_wallet/buy/sell_buy_states.dart';
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/provider_optoin_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/typography.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class BuySellPage extends BasePage {
|
||||
BuySellPage(this.buySellViewModel);
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
final cryptoCurrencyKey = GlobalKey<ExchangeCardState>();
|
||||
final fiatCurrencyKey = GlobalKey<ExchangeCardState>();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _fiatAmountFocus = FocusNode();
|
||||
final _cryptoAmountFocus = FocusNode();
|
||||
final _cryptoAddressFocus = FocusNode();
|
||||
var _isReactionsSet = false;
|
||||
|
||||
final arrowBottomPurple = Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
);
|
||||
final arrowBottomCakeGreen = Image.asset(
|
||||
'assets/images/arrow_bottom_cake_green.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
);
|
||||
|
||||
late final String? depositWalletName;
|
||||
late final String? receiveWalletName;
|
||||
|
||||
@override
|
||||
String get title => S.current.buy + '/' + S.current.sell;
|
||||
|
||||
@override
|
||||
bool get gradientBackground => true;
|
||||
|
||||
@override
|
||||
bool get gradientAll => true;
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) => TrailButton(
|
||||
caption: S.of(context).clear,
|
||||
onPressed: () {
|
||||
_formKey.currentState?.reset();
|
||||
buySellViewModel.reset();
|
||||
});
|
||||
|
||||
@override
|
||||
Widget? leading(BuildContext context) {
|
||||
final _backButton = Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: titleColor(context),
|
||||
size: 16,
|
||||
);
|
||||
final _closeButton =
|
||||
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
|
||||
|
||||
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
|
||||
|
||||
return MergeSemantics(
|
||||
child: SizedBox(
|
||||
height: isMobileView ? 37 : 45,
|
||||
width: isMobileView ? 37 : 45,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: Semantics(
|
||||
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||
),
|
||||
onPressed: () => onClose(context),
|
||||
child: !isMobileView ? _closeButton : _backButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, buySellViewModel));
|
||||
|
||||
return KeyboardActions(
|
||||
disableScroll: true,
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _fiatAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]),
|
||||
KeyboardActionsItem(
|
||||
focusNode: _cryptoAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()])
|
||||
]),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24),
|
||||
content: Observer(
|
||||
builder: (_) => Column(children: [
|
||||
_exchangeCardsSection(context),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
_buildPaymentMethodTile(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
])),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Column(children: [
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: S.current.choose_a_provider,
|
||||
onPressed: () async {
|
||||
if(!_formKey.currentState!.validate()) return;
|
||||
buySellViewModel.onTapChoseProvider(context);
|
||||
},
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isDisabled: false,
|
||||
isLoading: !buySellViewModel.isReadyToTrade)),
|
||||
]),
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildPaymentMethodTile(BuildContext context) {
|
||||
if (buySellViewModel.paymentMethodState is PaymentMethodLoading ||
|
||||
buySellViewModel.paymentMethodState is InitialPaymentMethod) {
|
||||
return OptionTilePlaceholder(
|
||||
withBadge: false,
|
||||
withSubtitle: false,
|
||||
borderRadius: 30,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
leadingIcon: Icons.arrow_forward_ios,
|
||||
isDarkTheme: buySellViewModel.isDarkTheme);
|
||||
}
|
||||
if (buySellViewModel.paymentMethodState is PaymentMethodFailed) {
|
||||
return OptionTilePlaceholder(errorText: 'No payment methods available', borderRadius: 30);
|
||||
}
|
||||
if (buySellViewModel.paymentMethodState is PaymentMethodLoaded &&
|
||||
buySellViewModel.selectedPaymentMethod != null) {
|
||||
return Observer(builder: (_) {
|
||||
final selectedPaymentMethod = buySellViewModel.selectedPaymentMethod!;
|
||||
return ProviderOptionTile(
|
||||
lightImagePath: selectedPaymentMethod.lightIconPath,
|
||||
darkImagePath: selectedPaymentMethod.darkIconPath,
|
||||
title: selectedPaymentMethod.title,
|
||||
onPressed: () => _pickPaymentMethod(context),
|
||||
leadingIcon: Icons.arrow_forward_ios,
|
||||
isLightMode: !buySellViewModel.isDarkTheme,
|
||||
borderRadius: 30,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
titleTextStyle:
|
||||
textLargeBold(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
);
|
||||
});
|
||||
}
|
||||
return OptionTilePlaceholder(errorText: 'No payment methods available', borderRadius: 30);
|
||||
}
|
||||
|
||||
void _pickPaymentMethod(BuildContext context) async {
|
||||
final currentOption = buySellViewModel.selectedPaymentMethod;
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.paymentMethodOptionsPage,
|
||||
arguments: [
|
||||
buySellViewModel.paymentMethods,
|
||||
buySellViewModel.changeOption,
|
||||
],
|
||||
);
|
||||
|
||||
buySellViewModel.selectedPaymentMethod;
|
||||
if (currentOption != null &&
|
||||
currentOption.paymentMethodType !=
|
||||
buySellViewModel.selectedPaymentMethod?.paymentMethodType) {
|
||||
await buySellViewModel.calculateBestRate();
|
||||
}
|
||||
}
|
||||
|
||||
void _setReactions(BuildContext context, BuySellViewModel buySellViewModel) {
|
||||
if (_isReactionsSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
final fiatAmountController = fiatCurrencyKey.currentState!.amountController;
|
||||
final cryptoAmountController = cryptoCurrencyKey.currentState!.amountController;
|
||||
final cryptoAddressController = cryptoCurrencyKey.currentState!.addressController;
|
||||
|
||||
_onCurrencyChange(buySellViewModel.cryptoCurrency, buySellViewModel, cryptoCurrencyKey);
|
||||
_onCurrencyChange(buySellViewModel.fiatCurrency, buySellViewModel, fiatCurrencyKey);
|
||||
|
||||
reaction(
|
||||
(_) => buySellViewModel.wallet.name,
|
||||
(String _) =>
|
||||
_onWalletNameChange(buySellViewModel, buySellViewModel.cryptoCurrency, cryptoCurrencyKey));
|
||||
|
||||
reaction(
|
||||
(_) => buySellViewModel.cryptoCurrency,
|
||||
(CryptoCurrency currency) =>
|
||||
_onCurrencyChange(currency, buySellViewModel, cryptoCurrencyKey));
|
||||
|
||||
reaction(
|
||||
(_) => buySellViewModel.fiatCurrency,
|
||||
(FiatCurrency currency) =>
|
||||
_onCurrencyChange(currency, buySellViewModel, fiatCurrencyKey));
|
||||
|
||||
reaction((_) => buySellViewModel.fiatAmount, (String amount) {
|
||||
if (fiatCurrencyKey.currentState!.amountController.text != amount) {
|
||||
fiatCurrencyKey.currentState!.amountController.text = amount;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => buySellViewModel.isCryptoCurrencyAddressEnabled, (bool isEnabled) {
|
||||
cryptoCurrencyKey.currentState!.isAddressEditable(isEditable: isEnabled);
|
||||
});
|
||||
|
||||
reaction((_) => buySellViewModel.cryptoAmount, (String amount) {
|
||||
if (cryptoCurrencyKey.currentState!.amountController.text != amount) {
|
||||
cryptoCurrencyKey.currentState!.amountController.text = amount;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => buySellViewModel.cryptoCurrencyAddress, (String address) {
|
||||
if (cryptoAddressController != address) {
|
||||
cryptoCurrencyKey.currentState!.addressController.text = address;
|
||||
}
|
||||
});
|
||||
|
||||
fiatAmountController.addListener(() {
|
||||
if (fiatAmountController.text != buySellViewModel.fiatAmount) {
|
||||
buySellViewModel.changeFiatAmount(amount: fiatAmountController.text);
|
||||
}
|
||||
});
|
||||
|
||||
cryptoAmountController.addListener(() {
|
||||
if (cryptoAmountController.text != buySellViewModel.cryptoAmount) {
|
||||
buySellViewModel.changeCryptoAmount(amount: cryptoAmountController.text);
|
||||
}
|
||||
});
|
||||
|
||||
cryptoAddressController.addListener(() {
|
||||
buySellViewModel.changeCryptoCurrencyAddress(cryptoAddressController.text);
|
||||
});
|
||||
|
||||
_cryptoAddressFocus.addListener(() async {
|
||||
if (!_cryptoAddressFocus.hasFocus && cryptoAddressController.text.isNotEmpty) {
|
||||
final domain = cryptoAddressController.text;
|
||||
buySellViewModel.cryptoCurrencyAddress =
|
||||
await fetchParsedAddress(context, domain, buySellViewModel.cryptoCurrency);
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => buySellViewModel.wallet.walletAddresses.addressForExchange, (String address) {
|
||||
if (buySellViewModel.cryptoCurrency == CryptoCurrency.xmr) {
|
||||
cryptoCurrencyKey.currentState!.changeAddress(address: address);
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => buySellViewModel.isReadyToTrade, (bool isReady) {
|
||||
if (isReady) {
|
||||
if (cryptoAmountController.text.isNotEmpty &&
|
||||
cryptoAmountController.text != S.current.fetching) {
|
||||
buySellViewModel.changeCryptoAmount(amount: cryptoAmountController.text);
|
||||
} else if (fiatAmountController.text.isNotEmpty &&
|
||||
fiatAmountController.text != S.current.fetching) {
|
||||
buySellViewModel.changeFiatAmount(amount: fiatAmountController.text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_isReactionsSet = true;
|
||||
}
|
||||
|
||||
void _onCurrencyChange(Currency currency, BuySellViewModel buySellViewModel,
|
||||
GlobalKey<ExchangeCardState> key) {
|
||||
final isCurrentTypeWallet = currency == buySellViewModel.wallet.currency;
|
||||
|
||||
key.currentState!.changeSelectedCurrency(currency);
|
||||
key.currentState!.changeWalletName(isCurrentTypeWallet ? buySellViewModel.wallet.name : '');
|
||||
|
||||
key.currentState!.changeAddress(
|
||||
address: isCurrentTypeWallet ? buySellViewModel.wallet.walletAddresses.addressForExchange : '');
|
||||
|
||||
key.currentState!.changeAmount(amount: '');
|
||||
}
|
||||
|
||||
void _onWalletNameChange(BuySellViewModel buySellViewModel, CryptoCurrency currency,
|
||||
GlobalKey<ExchangeCardState> key) {
|
||||
final isCurrentTypeWallet = currency == buySellViewModel.wallet.currency;
|
||||
|
||||
if (isCurrentTypeWallet) {
|
||||
key.currentState!.changeWalletName(buySellViewModel.wallet.name);
|
||||
key.currentState!.addressController.text = buySellViewModel.wallet.walletAddresses.addressForExchange;
|
||||
} else if (key.currentState!.addressController.text ==
|
||||
buySellViewModel.wallet.walletAddresses.addressForExchange) {
|
||||
key.currentState!.changeWalletName('');
|
||||
key.currentState!.addressController.text = '';
|
||||
}
|
||||
}
|
||||
|
||||
void disposeBestRateSync() => {};
|
||||
|
||||
Widget _exchangeCardsSection(BuildContext context) {
|
||||
final fiatExchangeCard = Observer(
|
||||
builder: (_) => ExchangeCard(
|
||||
cardInstanceName: 'fiat_currency_trade_card',
|
||||
onDispose: disposeBestRateSync,
|
||||
amountFocusNode: _fiatAmountFocus,
|
||||
key: fiatCurrencyKey,
|
||||
title: 'FIAT ${S.of(context).amount}',
|
||||
initialCurrency: buySellViewModel.fiatCurrency,
|
||||
initialWalletName: '',
|
||||
initialAddress: '',
|
||||
initialIsAmountEditable: true,
|
||||
isAmountEstimated: false,
|
||||
currencyRowPadding: EdgeInsets.zero,
|
||||
addressRowPadding: EdgeInsets.zero,
|
||||
isMoneroWallet: buySellViewModel.wallet == WalletType.monero,
|
||||
showAddressField: false,
|
||||
showLimitsField: false,
|
||||
currencies: buySellViewModel.fiatCurrencies,
|
||||
onCurrencySelected: (currency) =>
|
||||
buySellViewModel.changeFiatCurrency(currency: currency),
|
||||
imageArrow: arrowBottomPurple,
|
||||
currencyButtonColor: Colors.transparent,
|
||||
addressButtonsColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderColor:
|
||||
Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderTopPanelColor,
|
||||
onPushPasteButton: (context) async {},
|
||||
onPushAddressBookButton: (context) async {},
|
||||
));
|
||||
|
||||
final cryptoExchangeCard = Observer(
|
||||
builder: (_) => ExchangeCard(
|
||||
cardInstanceName: 'crypto_currency_trade_card',
|
||||
onDispose: disposeBestRateSync,
|
||||
amountFocusNode: _cryptoAmountFocus,
|
||||
addressFocusNode: _cryptoAddressFocus,
|
||||
key: cryptoCurrencyKey,
|
||||
title: 'Crypto ${S.of(context).amount}',
|
||||
initialCurrency: buySellViewModel.cryptoCurrency,
|
||||
initialWalletName: '',
|
||||
initialAddress: buySellViewModel.cryptoCurrency == buySellViewModel.wallet.currency
|
||||
? buySellViewModel.wallet.walletAddresses.addressForExchange
|
||||
: buySellViewModel.cryptoCurrencyAddress,
|
||||
initialIsAmountEditable: true,
|
||||
isAmountEstimated: true,
|
||||
showLimitsField: false,
|
||||
currencyRowPadding: EdgeInsets.zero,
|
||||
addressRowPadding: EdgeInsets.zero,
|
||||
isMoneroWallet: buySellViewModel.wallet == WalletType.monero,
|
||||
currencies: buySellViewModel.cryptoCurrencies,
|
||||
onCurrencySelected: (currency) =>
|
||||
buySellViewModel.changeCryptoCurrency(currency: currency),
|
||||
imageArrow: arrowBottomCakeGreen,
|
||||
currencyButtonColor: Colors.transparent,
|
||||
addressButtonsColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderColor:
|
||||
Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderBottomPanelColor,
|
||||
addressTextFieldValidator: AddressValidator(type: buySellViewModel.cryptoCurrency),
|
||||
onPushPasteButton: (context) async {},
|
||||
onPushAddressBookButton: (context) async {},
|
||||
));
|
||||
|
||||
if (responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (buySellViewModel.isBuyAction) {
|
||||
return MobileExchangeCardsSection(
|
||||
firstExchangeCard: fiatExchangeCard,
|
||||
secondExchangeCard: cryptoExchangeCard,
|
||||
onBuyTap: () => null,
|
||||
onSellTap: () =>
|
||||
buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
|
||||
isBuySellOption: true,
|
||||
);
|
||||
} else {
|
||||
return MobileExchangeCardsSection(
|
||||
firstExchangeCard: cryptoExchangeCard,
|
||||
secondExchangeCard: fiatExchangeCard,
|
||||
onBuyTap: () =>
|
||||
!buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
|
||||
onSellTap: () => null,
|
||||
isBuySellOption: true,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (buySellViewModel.isBuyAction) {
|
||||
return DesktopExchangeCardsSection(
|
||||
firstExchangeCard: fiatExchangeCard,
|
||||
secondExchangeCard: cryptoExchangeCard,
|
||||
);
|
||||
} else {
|
||||
return DesktopExchangeCardsSection(
|
||||
firstExchangeCard: cryptoExchangeCard,
|
||||
secondExchangeCard: fiatExchangeCard,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> fetchParsedAddress(
|
||||
BuildContext context, String domain, CryptoCurrency currency) async {
|
||||
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
|
||||
final address = await extractAddressFromParsed(context, parsedAddress);
|
||||
return address;
|
||||
}
|
||||
}
|
47
lib/src/screens/buy/payment_method_options_page.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
import 'package:cake_wallet/core/selectable_option.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/select_options_page.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class PaymentMethodOptionsPage extends SelectOptionsPage {
|
||||
PaymentMethodOptionsPage({required this.items, this.pickAnOption});
|
||||
|
||||
final List<SelectableItem> items;
|
||||
final Function(SelectableOption option)? pickAnOption;
|
||||
|
||||
@override
|
||||
String get pageTitle => S.current.choose_a_payment_method;
|
||||
|
||||
@override
|
||||
EdgeInsets? get contentPadding => null;
|
||||
|
||||
@override
|
||||
EdgeInsets? get tilePadding => EdgeInsets.only(top: 12);
|
||||
|
||||
@override
|
||||
EdgeInsets? get innerPadding => EdgeInsets.symmetric(horizontal: 24, vertical: 12);
|
||||
|
||||
@override
|
||||
double? get imageHeight => null;
|
||||
|
||||
@override
|
||||
double? get imageWidth => null;
|
||||
|
||||
@override
|
||||
Color? get selectedBackgroundColor => null;
|
||||
|
||||
@override
|
||||
double? get tileBorderRadius => 30;
|
||||
|
||||
@override
|
||||
String get bottomSectionText => '';
|
||||
|
||||
@override
|
||||
void Function(SelectableOption option)? get onOptionTap => pickAnOption;
|
||||
|
||||
@override
|
||||
String get primaryButtonText => S.current.confirm;
|
||||
|
||||
@override
|
||||
void Function(BuildContext context)? get primaryButtonAction => null;
|
||||
}
|
|
@ -258,7 +258,11 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
if (!isLogged) {
|
||||
Navigator.of(context).pushNamed(Routes.cakePayWelcomePage);
|
||||
} else {
|
||||
await cakePayPurchaseViewModel.createOrder();
|
||||
try {
|
||||
await cakePayPurchaseViewModel.createOrder();
|
||||
} catch (_) {
|
||||
await cakePayPurchaseViewModel.cakePayService.logout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,8 +347,8 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(popupContext).pop();
|
||||
await cakePayPurchaseViewModel.sendViewModel.commitTransaction();
|
||||
Navigator.of(context).pop();
|
||||
await cakePayPurchaseViewModel.sendViewModel.commitTransaction(context);
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop()));
|
||||
},
|
||||
|
|
|
@ -2,9 +2,12 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -17,35 +20,46 @@ typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
|
|||
class ConnectDevicePageParams {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
|
||||
ConnectDevicePageParams(
|
||||
{required this.walletType, required this.onConnectDevice});
|
||||
ConnectDevicePageParams({
|
||||
required this.walletType,
|
||||
required this.onConnectDevice,
|
||||
this.allowChangeWallet = false,
|
||||
});
|
||||
}
|
||||
|
||||
class ConnectDevicePage extends BasePage {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final LedgerViewModel ledgerVM;
|
||||
|
||||
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
|
||||
: walletType = params.walletType,
|
||||
onConnectDevice = params.onConnectDevice;
|
||||
onConnectDevice = params.onConnectDevice,
|
||||
allowChangeWallet = params.allowChangeWallet;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_title_from_hardware_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
|
||||
Widget body(BuildContext context) => ConnectDevicePageBody(
|
||||
walletType, onConnectDevice, allowChangeWallet, ledgerVM);
|
||||
}
|
||||
|
||||
class ConnectDevicePageBody extends StatefulWidget {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final LedgerViewModel ledgerVM;
|
||||
|
||||
const ConnectDevicePageBody(
|
||||
this.walletType, this.onConnectDevice, this.ledgerVM);
|
||||
this.walletType,
|
||||
this.onConnectDevice,
|
||||
this.allowChangeWallet,
|
||||
this.ledgerVM,
|
||||
);
|
||||
|
||||
@override
|
||||
ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
|
||||
|
@ -102,14 +116,16 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
|
||||
Future<void> _refreshBleDevices() async {
|
||||
try {
|
||||
_bleRefresh = widget.ledgerVM
|
||||
.scanForBleDevices()
|
||||
.listen((device) => setState(() => bleDevices.add(device)))
|
||||
..onError((e) {
|
||||
throw e.toString();
|
||||
});
|
||||
_bleRefreshTimer?.cancel();
|
||||
_bleRefreshTimer = null;
|
||||
if (widget.ledgerVM.bleIsEnabled) {
|
||||
_bleRefresh = widget.ledgerVM
|
||||
.scanForBleDevices()
|
||||
.listen((device) => setState(() => bleDevices.add(device)))
|
||||
..onError((e) {
|
||||
throw e.toString();
|
||||
});
|
||||
_bleRefreshTimer?.cancel();
|
||||
_bleRefreshTimer = null;
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
@ -227,9 +243,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -247,11 +261,27 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
),
|
||||
)
|
||||
.toList(),
|
||||
]
|
||||
],
|
||||
if (widget.allowChangeWallet) ...[
|
||||
PrimaryButton(
|
||||
text: S.of(context).wallets,
|
||||
color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor,
|
||||
textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor,
|
||||
onPressed: _onChangeWallet,
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onChangeWallet() {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.walletList,
|
||||
arguments: (BuildContext context) => Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|