diff --git a/.github/workflows/cache_dependencies.yml b/.github/workflows/cache_dependencies.yml
index c0042bf5c..ce098b7f1 100644
--- a/.github/workflows/cache_dependencies.yml
+++ b/.github/workflows/cache_dependencies.yml
@@ -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
diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index 925f4e00d..774404e3b 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -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
@@ -224,12 +264,20 @@ jobs:
cd /opt/android/cake_wallet/build/app/outputs/flutter-apk
mkdir test-apk
cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk
+ cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk
- name: Upload Artifact
uses: kittaakos/upload-artifact-as-is@v0
with:
path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/
+ # 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
@@ -240,3 +288,4 @@ jobs:
title: "${{ env.BRANCH_NAME }}.apk"
filename: ${{ env.BRANCH_NAME }}.apk
initial_comment: ${{ github.event.head_commit.message }}
+
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 5ea0cb377..f3ce1045d 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -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
diff --git a/PRIVACY.md b/PRIVACY.md
index 76cfcc4d3..a5c8eddfb 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -5,7 +5,7 @@ Last modified: January 24, 2024
Introduction
============
- Cake Labs LLC ("Cake Labs", "Company", or "We") respect your privacy and are committed to protecting it through our compliance with this policy.
+ Cake Labs LLC ("Cake Labs", "Company", or "We") respects your privacy and are committed to protecting it through our compliance with this policy.
This policy describes the types of information we may collect from you or that you may provide when you use the App (our "App") and our practices for collecting, using, maintaining, protecting, and disclosing that information.
@@ -13,7 +13,7 @@ Introduction
- On this App.
- In email, text, and other electronic messages between you and this App.
It does not apply to information collected by:
- - Us offline or through any other means, including on any other App operated by Company or any third party (including our affiliates and subsidiaries); or
+ - Us offline or through any other means, including on any other App operated by the Company or any third party (including our affiliates and subsidiaries); or
- Any third party (including our affiliates and subsidiaries), including through any application or content (including advertising) that may link to or be accessible from or on the App.
Please read this policy carefully to understand our policies and practices regarding your information and how we will treat it. If you do not agree with our policies and practices, you have the choice to not use the App. By accessing or using this App, you agree to this privacy policy. This policy may change from time to time. Your continued use of this App after we make changes is deemed to be acceptance of those changes, so please check the policy periodically for updates.
diff --git a/README.md b/README.md
index 1c28f92a2..078c4437e 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
# Cake Wallet
-Cake Wallet is an open source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
+[Cake Wallet](https://cakewallet.com) is an open-source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
Cake Wallet includes support for several cryptocurrencies, including:
* Monero (XMR)
@@ -26,7 +26,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Ethereum (ETH)
* Litecoin (LTC)
* Bitcoin Cash (BCH)
-* Polygon (MATIC)
+* Polygon (Pol)
* Solana (SOL)
* Nano (XNO)
* Haven (XHV)
@@ -44,7 +44,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Create several wallets
* Select your own custom nodes/servers
* Address book
-* Backup to external location or iCloud
+* Backup to an external location or iCloud
* Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles
* Set desired network fee level
* Store local transaction notes
@@ -161,7 +161,7 @@ The only parts to be translated, if needed, are the values m and s after the var
4. Add the language to `lib/entities/language_service.dart` under both `supportedLocales` and `localeCountryCode`. Use the name of the language in the local language and in English in parentheses after for `supportedLocales`. Use the [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) for `localeCountryCode`. You must choose one country, so choose the country with the most native speakers of this language or is otherwise best associated with this language.
-5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make transparent. Or you can use another program like Photoshop.
+5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make it transparent. Or you can use another program like Photoshop.
6. Add the new language code to `tool/utils/translation/translation_constants.dart`
diff --git a/SECURITY.md b/SECURITY.md
index a1b489b76..e7c6baa02 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,4 +9,4 @@ If you need to report a vulnerability, please either:
## Supported Versions
-As we don't maintain prevoius versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
+As we don't maintain previous versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 2f5427531..5005a8bab 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -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'
+}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index 1d0e2c93c..5b080e3ec 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -15,6 +15,7 @@
+
diff --git a/android/gradle.properties b/android/gradle.properties
index 38c8d4544..66dd09454 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,4 +1,4 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx3072M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
diff --git a/assets/bitcoin_electrum_server_list.yml b/assets/bitcoin_electrum_server_list.yml
index 305db38f5..20a28cd24 100644
--- a/assets/bitcoin_electrum_server_list.yml
+++ b/assets/bitcoin_electrum_server_list.yml
@@ -3,6 +3,7 @@
useSSL: true
-
uri: btc-electrum.cakewallet.com:50002
+ useSSL: true
isDefault: true
-
uri: electrs.cakewallet.com:50001
diff --git a/assets/images/apple_pay_logo.png b/assets/images/apple_pay_logo.png
new file mode 100644
index 000000000..346007e3b
Binary files /dev/null and b/assets/images/apple_pay_logo.png differ
diff --git a/assets/images/apple_pay_round_dark.svg b/assets/images/apple_pay_round_dark.svg
new file mode 100644
index 000000000..82443bfb4
--- /dev/null
+++ b/assets/images/apple_pay_round_dark.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/apple_pay_round_light.svg b/assets/images/apple_pay_round_light.svg
new file mode 100644
index 000000000..2beb1248f
--- /dev/null
+++ b/assets/images/apple_pay_round_light.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/bank.png b/assets/images/bank.png
new file mode 100644
index 000000000..9dc68147a
Binary files /dev/null and b/assets/images/bank.png differ
diff --git a/assets/images/bank_dark.svg b/assets/images/bank_dark.svg
new file mode 100644
index 000000000..670120796
--- /dev/null
+++ b/assets/images/bank_dark.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/bank_light.svg b/assets/images/bank_light.svg
new file mode 100644
index 000000000..804716289
--- /dev/null
+++ b/assets/images/bank_light.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/buy_sell.png b/assets/images/buy_sell.png
new file mode 100644
index 000000000..0fbffe56f
Binary files /dev/null and b/assets/images/buy_sell.png differ
diff --git a/assets/images/card.svg b/assets/images/card.svg
new file mode 100644
index 000000000..95530cdc9
--- /dev/null
+++ b/assets/images/card.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/card_dark.svg b/assets/images/card_dark.svg
new file mode 100644
index 000000000..2e5bcf986
--- /dev/null
+++ b/assets/images/card_dark.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/dollar_coin.svg b/assets/images/dollar_coin.svg
new file mode 100644
index 000000000..22218f332
--- /dev/null
+++ b/assets/images/dollar_coin.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/flags/abw.png b/assets/images/flags/abw.png
new file mode 100644
index 000000000..d049d3a43
Binary files /dev/null and b/assets/images/flags/abw.png differ
diff --git a/assets/images/flags/afg.png b/assets/images/flags/afg.png
new file mode 100644
index 000000000..f2ea25144
Binary files /dev/null and b/assets/images/flags/afg.png differ
diff --git a/assets/images/flags/ago.png b/assets/images/flags/ago.png
new file mode 100644
index 000000000..b04d7dfa6
Binary files /dev/null and b/assets/images/flags/ago.png differ
diff --git a/assets/images/flags/aia.png b/assets/images/flags/aia.png
new file mode 100644
index 000000000..193f0ff41
Binary files /dev/null and b/assets/images/flags/aia.png differ
diff --git a/assets/images/flags/and.png b/assets/images/flags/and.png
new file mode 100644
index 000000000..fc5d9a89b
Binary files /dev/null and b/assets/images/flags/and.png differ
diff --git a/assets/images/flags/asm.png b/assets/images/flags/asm.png
new file mode 100644
index 000000000..fd3818eda
Binary files /dev/null and b/assets/images/flags/asm.png differ
diff --git a/assets/images/flags/atf.png b/assets/images/flags/atf.png
new file mode 100644
index 000000000..af77e45d5
Binary files /dev/null and b/assets/images/flags/atf.png differ
diff --git a/assets/images/flags/atg.png b/assets/images/flags/atg.png
new file mode 100644
index 000000000..d9a3d9f9e
Binary files /dev/null and b/assets/images/flags/atg.png differ
diff --git a/assets/images/flags/aut.png b/assets/images/flags/aut.png
new file mode 100644
index 000000000..1be1ff483
Binary files /dev/null and b/assets/images/flags/aut.png differ
diff --git a/assets/images/flags/aze.png b/assets/images/flags/aze.png
new file mode 100644
index 000000000..834b1e696
Binary files /dev/null and b/assets/images/flags/aze.png differ
diff --git a/assets/images/flags/bel.png b/assets/images/flags/bel.png
new file mode 100644
index 000000000..1c06c5fa7
Binary files /dev/null and b/assets/images/flags/bel.png differ
diff --git a/assets/images/flags/bes.png b/assets/images/flags/bes.png
new file mode 100644
index 000000000..b00bfb1f5
Binary files /dev/null and b/assets/images/flags/bes.png differ
diff --git a/assets/images/flags/bhr.png b/assets/images/flags/bhr.png
new file mode 100644
index 000000000..135c254cb
Binary files /dev/null and b/assets/images/flags/bhr.png differ
diff --git a/assets/images/flags/blz.png b/assets/images/flags/blz.png
new file mode 100644
index 000000000..06b23f161
Binary files /dev/null and b/assets/images/flags/blz.png differ
diff --git a/assets/images/flags/bmu.png b/assets/images/flags/bmu.png
new file mode 100644
index 000000000..6e253a8e6
Binary files /dev/null and b/assets/images/flags/bmu.png differ
diff --git a/assets/images/flags/bol.png b/assets/images/flags/bol.png
new file mode 100644
index 000000000..4996ddbcc
Binary files /dev/null and b/assets/images/flags/bol.png differ
diff --git a/assets/images/flags/brn.png b/assets/images/flags/brn.png
new file mode 100644
index 000000000..bd1d1cc9a
Binary files /dev/null and b/assets/images/flags/brn.png differ
diff --git a/assets/images/flags/btn.png b/assets/images/flags/btn.png
new file mode 100644
index 000000000..962e5e5bb
Binary files /dev/null and b/assets/images/flags/btn.png differ
diff --git a/assets/images/flags/bvt.png b/assets/images/flags/bvt.png
new file mode 100644
index 000000000..4acde7fbf
Binary files /dev/null and b/assets/images/flags/bvt.png differ
diff --git a/assets/images/flags/bwa.png b/assets/images/flags/bwa.png
new file mode 100644
index 000000000..5b7eff92a
Binary files /dev/null and b/assets/images/flags/bwa.png differ
diff --git a/assets/images/flags/cck.png b/assets/images/flags/cck.png
new file mode 100644
index 000000000..d255ab91a
Binary files /dev/null and b/assets/images/flags/cck.png differ
diff --git a/assets/images/flags/cmr.png b/assets/images/flags/cmr.png
new file mode 100644
index 000000000..2bc6ad13c
Binary files /dev/null and b/assets/images/flags/cmr.png differ
diff --git a/assets/images/flags/cok.png b/assets/images/flags/cok.png
new file mode 100644
index 000000000..49386516d
Binary files /dev/null and b/assets/images/flags/cok.png differ
diff --git a/assets/images/flags/cpv.png b/assets/images/flags/cpv.png
new file mode 100644
index 000000000..0683d931f
Binary files /dev/null and b/assets/images/flags/cpv.png differ
diff --git a/assets/images/flags/cri.png b/assets/images/flags/cri.png
new file mode 100644
index 000000000..029bbfc49
Binary files /dev/null and b/assets/images/flags/cri.png differ
diff --git a/assets/images/flags/cuw.png b/assets/images/flags/cuw.png
new file mode 100644
index 000000000..92a36b728
Binary files /dev/null and b/assets/images/flags/cuw.png differ
diff --git a/assets/images/flags/cxr.png b/assets/images/flags/cxr.png
new file mode 100644
index 000000000..e644a49ea
Binary files /dev/null and b/assets/images/flags/cxr.png differ
diff --git a/assets/images/flags/cyp.png b/assets/images/flags/cyp.png
new file mode 100644
index 000000000..ba5246809
Binary files /dev/null and b/assets/images/flags/cyp.png differ
diff --git a/assets/images/flags/dji.png b/assets/images/flags/dji.png
new file mode 100644
index 000000000..185c5322b
Binary files /dev/null and b/assets/images/flags/dji.png differ
diff --git a/assets/images/flags/dma.png b/assets/images/flags/dma.png
new file mode 100644
index 000000000..7f61af95e
Binary files /dev/null and b/assets/images/flags/dma.png differ
diff --git a/assets/images/flags/dza.png b/assets/images/flags/dza.png
new file mode 100644
index 000000000..342284223
Binary files /dev/null and b/assets/images/flags/dza.png differ
diff --git a/assets/images/flags/ecu.png b/assets/images/flags/ecu.png
new file mode 100644
index 000000000..efb3acb43
Binary files /dev/null and b/assets/images/flags/ecu.png differ
diff --git a/assets/images/flags/est.png b/assets/images/flags/est.png
new file mode 100644
index 000000000..c2e417b93
Binary files /dev/null and b/assets/images/flags/est.png differ
diff --git a/assets/images/flags/eth.png b/assets/images/flags/eth.png
new file mode 100644
index 000000000..5b4970a67
Binary files /dev/null and b/assets/images/flags/eth.png differ
diff --git a/assets/images/flags/fin.png b/assets/images/flags/fin.png
new file mode 100644
index 000000000..b6bc2f9a9
Binary files /dev/null and b/assets/images/flags/fin.png differ
diff --git a/assets/images/flags/fji.png b/assets/images/flags/fji.png
new file mode 100644
index 000000000..4700ad579
Binary files /dev/null and b/assets/images/flags/fji.png differ
diff --git a/assets/images/flags/flk.png b/assets/images/flags/flk.png
new file mode 100644
index 000000000..66ff172c2
Binary files /dev/null and b/assets/images/flags/flk.png differ
diff --git a/assets/images/flags/fro.png b/assets/images/flags/fro.png
new file mode 100644
index 000000000..2c3ed5f6b
Binary files /dev/null and b/assets/images/flags/fro.png differ
diff --git a/assets/images/flags/fsm.png b/assets/images/flags/fsm.png
new file mode 100644
index 000000000..b8aedd34e
Binary files /dev/null and b/assets/images/flags/fsm.png differ
diff --git a/assets/images/flags/gab.png b/assets/images/flags/gab.png
new file mode 100644
index 000000000..c8e1cbd1f
Binary files /dev/null and b/assets/images/flags/gab.png differ
diff --git a/assets/images/flags/geo.png b/assets/images/flags/geo.png
new file mode 100644
index 000000000..46c83a589
Binary files /dev/null and b/assets/images/flags/geo.png differ
diff --git a/assets/images/flags/ggi.png b/assets/images/flags/ggi.png
new file mode 100644
index 000000000..fbc403f16
Binary files /dev/null and b/assets/images/flags/ggi.png differ
diff --git a/assets/images/flags/ggy.png b/assets/images/flags/ggy.png
new file mode 100644
index 000000000..a882b4a59
Binary files /dev/null and b/assets/images/flags/ggy.png differ
diff --git a/assets/images/flags/glp.png b/assets/images/flags/glp.png
new file mode 100644
index 000000000..8bd0a69bf
Binary files /dev/null and b/assets/images/flags/glp.png differ
diff --git a/assets/images/flags/gmb.png b/assets/images/flags/gmb.png
new file mode 100644
index 000000000..fa641ca1a
Binary files /dev/null and b/assets/images/flags/gmb.png differ
diff --git a/assets/images/flags/grc.png b/assets/images/flags/grc.png
new file mode 100644
index 000000000..d7b37b0c7
Binary files /dev/null and b/assets/images/flags/grc.png differ
diff --git a/assets/images/flags/grd.png b/assets/images/flags/grd.png
new file mode 100644
index 000000000..7138a28d7
Binary files /dev/null and b/assets/images/flags/grd.png differ
diff --git a/assets/images/flags/grl.png b/assets/images/flags/grl.png
new file mode 100644
index 000000000..53e45988b
Binary files /dev/null and b/assets/images/flags/grl.png differ
diff --git a/assets/images/flags/guf.png b/assets/images/flags/guf.png
new file mode 100644
index 000000000..07a2d5070
Binary files /dev/null and b/assets/images/flags/guf.png differ
diff --git a/assets/images/flags/gum.png b/assets/images/flags/gum.png
new file mode 100644
index 000000000..828c5f3d9
Binary files /dev/null and b/assets/images/flags/gum.png differ
diff --git a/assets/images/flags/guy.png b/assets/images/flags/guy.png
new file mode 100644
index 000000000..5845c6db9
Binary files /dev/null and b/assets/images/flags/guy.png differ
diff --git a/assets/images/flags/hmd.png b/assets/images/flags/hmd.png
new file mode 100644
index 000000000..8c2931c4e
Binary files /dev/null and b/assets/images/flags/hmd.png differ
diff --git a/assets/images/flags/iot.png b/assets/images/flags/iot.png
new file mode 100644
index 000000000..2863320f5
Binary files /dev/null and b/assets/images/flags/iot.png differ
diff --git a/assets/images/flags/irl.png b/assets/images/flags/irl.png
new file mode 100644
index 000000000..2126054d3
Binary files /dev/null and b/assets/images/flags/irl.png differ
diff --git a/assets/images/flags/jam.png b/assets/images/flags/jam.png
new file mode 100644
index 000000000..97bce2de3
Binary files /dev/null and b/assets/images/flags/jam.png differ
diff --git a/assets/images/flags/jey.png b/assets/images/flags/jey.png
new file mode 100644
index 000000000..e144d060e
Binary files /dev/null and b/assets/images/flags/jey.png differ
diff --git a/assets/images/flags/jor.png b/assets/images/flags/jor.png
new file mode 100644
index 000000000..6e5d2bbb8
Binary files /dev/null and b/assets/images/flags/jor.png differ
diff --git a/assets/images/flags/kaz.png b/assets/images/flags/kaz.png
new file mode 100644
index 000000000..db52cf078
Binary files /dev/null and b/assets/images/flags/kaz.png differ
diff --git a/assets/images/flags/ken.png b/assets/images/flags/ken.png
new file mode 100644
index 000000000..2570c185d
Binary files /dev/null and b/assets/images/flags/ken.png differ
diff --git a/assets/images/flags/kir.png b/assets/images/flags/kir.png
new file mode 100644
index 000000000..c8b69d702
Binary files /dev/null and b/assets/images/flags/kir.png differ
diff --git a/assets/images/flags/kwt.png b/assets/images/flags/kwt.png
new file mode 100644
index 000000000..f21563b5f
Binary files /dev/null and b/assets/images/flags/kwt.png differ
diff --git a/assets/images/flags/lbn.png b/assets/images/flags/lbn.png
new file mode 100644
index 000000000..2a9ae1dd0
Binary files /dev/null and b/assets/images/flags/lbn.png differ
diff --git a/assets/images/flags/lie.png b/assets/images/flags/lie.png
new file mode 100644
index 000000000..0b8c77442
Binary files /dev/null and b/assets/images/flags/lie.png differ
diff --git a/assets/images/flags/lka.png b/assets/images/flags/lka.png
new file mode 100644
index 000000000..2b6492daa
Binary files /dev/null and b/assets/images/flags/lka.png differ
diff --git a/assets/images/flags/ltu.png b/assets/images/flags/ltu.png
new file mode 100644
index 000000000..dec6babea
Binary files /dev/null and b/assets/images/flags/ltu.png differ
diff --git a/assets/images/flags/lux.png b/assets/images/flags/lux.png
new file mode 100644
index 000000000..9cc1c65b5
Binary files /dev/null and b/assets/images/flags/lux.png differ
diff --git a/assets/images/flags/lva.png b/assets/images/flags/lva.png
new file mode 100644
index 000000000..b3312700d
Binary files /dev/null and b/assets/images/flags/lva.png differ
diff --git a/assets/images/flags/mco.png b/assets/images/flags/mco.png
new file mode 100644
index 000000000..6c12bd624
Binary files /dev/null and b/assets/images/flags/mco.png differ
diff --git a/assets/images/flags/mlt.png b/assets/images/flags/mlt.png
new file mode 100644
index 000000000..12809815f
Binary files /dev/null and b/assets/images/flags/mlt.png differ
diff --git a/assets/images/flags/mnp.png b/assets/images/flags/mnp.png
new file mode 100644
index 000000000..3e6c538e4
Binary files /dev/null and b/assets/images/flags/mnp.png differ
diff --git a/assets/images/flags/mrt.png b/assets/images/flags/mrt.png
new file mode 100644
index 000000000..0fd8d757e
Binary files /dev/null and b/assets/images/flags/mrt.png differ
diff --git a/assets/images/flags/msr.png b/assets/images/flags/msr.png
new file mode 100644
index 000000000..2d2af3aef
Binary files /dev/null and b/assets/images/flags/msr.png differ
diff --git a/assets/images/flags/mtq.png b/assets/images/flags/mtq.png
new file mode 100644
index 000000000..1897c94e7
Binary files /dev/null and b/assets/images/flags/mtq.png differ
diff --git a/assets/images/flags/mwi.png b/assets/images/flags/mwi.png
new file mode 100644
index 000000000..7ddfbb17b
Binary files /dev/null and b/assets/images/flags/mwi.png differ
diff --git a/assets/images/flags/myt.png b/assets/images/flags/myt.png
new file mode 100644
index 000000000..c149a2a79
Binary files /dev/null and b/assets/images/flags/myt.png differ
diff --git a/assets/images/flags/ner.png b/assets/images/flags/ner.png
new file mode 100644
index 000000000..87bb8211f
Binary files /dev/null and b/assets/images/flags/ner.png differ
diff --git a/assets/images/flags/nfk.png b/assets/images/flags/nfk.png
new file mode 100644
index 000000000..6e68aee4f
Binary files /dev/null and b/assets/images/flags/nfk.png differ
diff --git a/assets/images/flags/niu.png b/assets/images/flags/niu.png
new file mode 100644
index 000000000..acb36780d
Binary files /dev/null and b/assets/images/flags/niu.png differ
diff --git a/assets/images/flags/omn.png b/assets/images/flags/omn.png
new file mode 100644
index 000000000..6b6cd8b3b
Binary files /dev/null and b/assets/images/flags/omn.png differ
diff --git a/assets/images/flags/per.png b/assets/images/flags/per.png
new file mode 100644
index 000000000..490a26441
Binary files /dev/null and b/assets/images/flags/per.png differ
diff --git a/assets/images/flags/plw.png b/assets/images/flags/plw.png
new file mode 100644
index 000000000..6f6ff993a
Binary files /dev/null and b/assets/images/flags/plw.png differ
diff --git a/assets/images/flags/pri.png b/assets/images/flags/pri.png
new file mode 100644
index 000000000..cb0c54cd6
Binary files /dev/null and b/assets/images/flags/pri.png differ
diff --git a/assets/images/flags/pyf.png b/assets/images/flags/pyf.png
new file mode 100644
index 000000000..66a5da6b8
Binary files /dev/null and b/assets/images/flags/pyf.png differ
diff --git a/assets/images/flags/qat.png b/assets/images/flags/qat.png
new file mode 100644
index 000000000..1e8461e91
Binary files /dev/null and b/assets/images/flags/qat.png differ
diff --git a/assets/images/flags/slb.png b/assets/images/flags/slb.png
new file mode 100644
index 000000000..d63061a0b
Binary files /dev/null and b/assets/images/flags/slb.png differ
diff --git a/assets/images/flags/slv.png b/assets/images/flags/slv.png
new file mode 100644
index 000000000..e597e45b3
Binary files /dev/null and b/assets/images/flags/slv.png differ
diff --git a/assets/images/flags/svk.png b/assets/images/flags/svk.png
new file mode 100644
index 000000000..06bed756e
Binary files /dev/null and b/assets/images/flags/svk.png differ
diff --git a/assets/images/flags/svn.png b/assets/images/flags/svn.png
new file mode 100644
index 000000000..a791163bd
Binary files /dev/null and b/assets/images/flags/svn.png differ
diff --git a/assets/images/flags/tkm.png b/assets/images/flags/tkm.png
new file mode 100644
index 000000000..0c3ff8755
Binary files /dev/null and b/assets/images/flags/tkm.png differ
diff --git a/assets/images/flags/ton.png b/assets/images/flags/ton.png
new file mode 100644
index 000000000..84cf20ef5
Binary files /dev/null and b/assets/images/flags/ton.png differ
diff --git a/assets/images/flags/tuv.png b/assets/images/flags/tuv.png
new file mode 100644
index 000000000..15478f191
Binary files /dev/null and b/assets/images/flags/tuv.png differ
diff --git a/assets/images/flags/ury.png b/assets/images/flags/ury.png
new file mode 100644
index 000000000..c41e2780a
Binary files /dev/null and b/assets/images/flags/ury.png differ
diff --git a/assets/images/flags/vat.png b/assets/images/flags/vat.png
new file mode 100644
index 000000000..d6c99cc1f
Binary files /dev/null and b/assets/images/flags/vat.png differ
diff --git a/assets/images/flags/vir.png b/assets/images/flags/vir.png
new file mode 100644
index 000000000..a57f924b2
Binary files /dev/null and b/assets/images/flags/vir.png differ
diff --git a/assets/images/flags/vut.png b/assets/images/flags/vut.png
new file mode 100644
index 000000000..3c4d6e429
Binary files /dev/null and b/assets/images/flags/vut.png differ
diff --git a/assets/images/google_pay_icon.png b/assets/images/google_pay_icon.png
new file mode 100644
index 000000000..a3ca38311
Binary files /dev/null and b/assets/images/google_pay_icon.png differ
diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png
new file mode 100644
index 000000000..fa39f241f
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_flex.png differ
diff --git a/assets/images/hardware_wallet/ledger_nano_s.png b/assets/images/hardware_wallet/ledger_nano_s.png
new file mode 100644
index 000000000..02777aeb6
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_nano_s.png differ
diff --git a/assets/images/hardware_wallet/ledger_nano_x.png b/assets/images/hardware_wallet/ledger_nano_x.png
new file mode 100644
index 000000000..e9328a5ef
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_nano_x.png differ
diff --git a/assets/images/hardware_wallet/ledger_stax.png b/assets/images/hardware_wallet/ledger_stax.png
new file mode 100644
index 000000000..06c9c848e
Binary files /dev/null and b/assets/images/hardware_wallet/ledger_stax.png differ
diff --git a/assets/images/ledger_nano.png b/assets/images/ledger_nano.png
deleted file mode 100644
index bb61ba175..000000000
Binary files a/assets/images/ledger_nano.png and /dev/null differ
diff --git a/assets/images/meld_logo.svg b/assets/images/meld_logo.svg
new file mode 100644
index 000000000..1d9211d64
--- /dev/null
+++ b/assets/images/meld_logo.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/images/revolut.png b/assets/images/revolut.png
new file mode 100644
index 000000000..bbe342592
Binary files /dev/null and b/assets/images/revolut.png differ
diff --git a/assets/images/skrill.svg b/assets/images/skrill.svg
new file mode 100644
index 000000000..b264b57eb
--- /dev/null
+++ b/assets/images/skrill.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/usd_round_dark.svg b/assets/images/usd_round_dark.svg
new file mode 100644
index 000000000..f329dd617
--- /dev/null
+++ b/assets/images/usd_round_dark.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/usd_round_light.svg b/assets/images/usd_round_light.svg
new file mode 100644
index 000000000..f5965c597
--- /dev/null
+++ b/assets/images/usd_round_light.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/assets/images/wallet_new.png b/assets/images/wallet_new.png
new file mode 100644
index 000000000..47c43bfca
Binary files /dev/null and b/assets/images/wallet_new.png differ
diff --git a/assets/node_list.yml b/assets/node_list.yml
index d04a9a2e8..6191129b3 100644
--- a/assets/node_list.yml
+++ b/assets/node_list.yml
@@ -1,6 +1,7 @@
-
uri: xmr-node.cakewallet.com:18081
is_default: true
+ trusted: true
-
uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
is_default: false
diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt
index 2f79dd5c6..46f21e172 100644
--- a/assets/text/Monerocom_Release_Notes.txt
+++ b/assets/text/Monerocom_Release_Notes.txt
@@ -1,4 +1,3 @@
-Monero enhancements for sending and address generation
-StealthEx
-LetsExchange
-Visual enhancements and bug fixes
\ No newline at end of file
+Add airgapped Monero wallet support (best used with our new offline app Cupcake)
+New Buy & Sell flow
+Bug fixes
\ No newline at end of file
diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt
index 868ae7954..6764826a7 100644
--- a/assets/text/Release_Notes.txt
+++ b/assets/text/Release_Notes.txt
@@ -1,9 +1,5 @@
-Add Litecoin MWEB
-Wallet groups (same seed, multiple wallets)
-Silent Payments enhancements
-Monero enhancements for sending and address generation
-StealthEx
-LetsExchange
-Replace-By-Fee improvements
-ERC20 tokens potential scam detection
-Visual enhancements and bug fixes
\ No newline at end of file
+Add Litecoin Ledger support
+Add airgapped Monero wallet support (best used with our new offline app Cupcake)
+MWEB fixes and enhancements
+New Buy & Sell flow
+Bug fixes
\ No newline at end of file
diff --git a/build-guide-linux.md b/build-guide-linux.md
index e0158945b..55264fbfa 100644
--- a/build-guide-linux.md
+++ b/build-guide-linux.md
@@ -15,7 +15,7 @@ These steps will help you configure and execute a build of CakeWallet from its s
### 1. Installing Package Dependencies
-CakeWallet requires some packages to be install on your build system. You may easily install them on your build system with the following command:
+CakeWallet requires some packages to be installed on your build system. You may easily install them on your build system with the following command:
`$ sudo apt install build-essential cmake pkg-config git curl autoconf libtool`
@@ -55,7 +55,7 @@ Need to install flutter. For this please check section [How to install flutter o
### 3. Verify Installations
-Verify that the Flutter have been correctly installed on your system with the following command:
+Verify that the Flutter has been correctly installed on your system with the following command:
`$ flutter doctor`
@@ -145,7 +145,7 @@ Path to executable file will be:
# Flatpak
-For package the built application into flatpak you need fistly to install `flatpak` and `flatpak-builder`:
+For package the built application into flatpak you need firstly to install `flatpak` and `flatpak-builder`:
`$ sudo apt install flatpak flatpak-builder`
@@ -163,7 +163,7 @@ And then export bundle:
`$ flatpak build-bundle export cake_wallet.flatpak com.cakewallet.CakeWallet`
-Result file: `cake_wallet.flatpak` should be generated in current directory.
+Result file: `cake_wallet.flatpak` should be generated in the current directory.
For install generated flatpak file use:
diff --git a/build-guide-win.md b/build-guide-win.md
new file mode 100644
index 000000000..6ace961af
--- /dev/null
+++ b/build-guide-win.md
@@ -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.
diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
index de339175d..a02c51c69 100644
--- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
+++ b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
@@ -5,30 +5,31 @@ import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
-import 'package:ledger_flutter/ledger_flutter.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class BitcoinHardwareWalletService {
- BitcoinHardwareWalletService(this.ledger, this.device);
+ BitcoinHardwareWalletService(this.ledgerConnection);
- final Ledger ledger;
- final LedgerDevice device;
+ final LedgerConnection ledgerConnection;
- Future> getAvailableAccounts({int index = 0, int limit = 5}) async {
- final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
+ Future> getAvailableAccounts(
+ {int index = 0, int limit = 5}) async {
+ final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
- final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
- print(masterFp);
+ final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
final accounts = [];
final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'";
- final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
+ final xpub =
+ await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
Bip32Slip10Secp256k1 hd =
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
- final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
+ final address = generateP2WPKHAddress(
+ hd: hd, index: 0, network: BitcoinNetwork.mainnet);
accounts.add(HardwareAccountData(
address: address,
diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart
index 30f04667a..908897845 100644
--- a/cw_bitcoin/lib/bitcoin_wallet.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet.dart
@@ -5,13 +5,13 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
+import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
-import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_info.dart';
@@ -19,7 +19,7 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
-import 'package:ledger_flutter/ledger_flutter.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:mobx/mobx.dart';
part 'bitcoin_wallet.g.dart';
@@ -61,8 +61,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils,
- currency:
- networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
+ currency: networkParam == BitcoinNetwork.testnet
+ ? CryptoCurrency.tbtc
+ : CryptoCurrency.btc,
alwaysScan: alwaysScan,
) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
@@ -80,11 +81,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mainHd: hd,
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: networkParam ?? network,
- masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
+ masterHd:
+ seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
+ isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
- this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
+ this.walletAddresses.isEnabledAutoGenerateSubaddress =
+ this.isEnabledAutoGenerateSubaddress;
});
}
@@ -185,8 +189,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo ??= DerivationInfo();
// set the default if not present:
- walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
- walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
+ walletInfo.derivationInfo!.derivationPath ??=
+ snp?.derivationPath ?? electrum_path;
+ walletInfo.derivationInfo!.derivationType ??=
+ snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;
final mnemonic = keysData.mnemonic;
@@ -228,15 +234,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
);
}
- Ledger? _ledger;
- LedgerDevice? _ledgerDevice;
+ LedgerConnection? _ledgerConnection;
BitcoinLedgerApp? _bitcoinLedgerApp;
- void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
- _ledger = setLedger;
- _ledgerDevice = setLedgerDevice;
- _bitcoinLedgerApp =
- BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
+ @override
+ void setLedgerConnection(LedgerConnection connection) {
+ _ledgerConnection = connection;
+ _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
+ derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
@@ -251,12 +256,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
- final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
+ final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
final psbtReadyInputs = [];
for (final utxo in utxos) {
- final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
- final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
+ final rawTx =
+ await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
+ final publicKeyAndDerivationPath =
+ publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo,
@@ -268,10 +275,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
));
}
- final psbt =
- PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
+ final psbt = PSBTTransactionBuild(
+ inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
- final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
+ final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
}
@@ -279,14 +286,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) {
final addressEntry = address != null
- ? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
+ ? walletAddresses.allAddresses
+ .firstWhere((element) => element.address == address)
: null;
final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath;
- final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
+ final derivationPath =
+ accountPath != null ? "$accountPath/$isChange/$index" : null;
- final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
+ final signature = await _bitcoinLedgerApp!.signMessage(
message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature);
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
index 697719894..04a3cae36 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
@@ -15,6 +15,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required super.mainHd,
required super.sideHd,
required super.network,
+ required super.isHardwareWallet,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index 54ccaaef5..df8e14119 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -68,8 +68,8 @@ class ElectrumClient {
try {
await socket?.close();
- socket = null;
} catch (_) {}
+ socket = null;
try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
@@ -102,7 +102,8 @@ class ElectrumClient {
return;
}
- _setConnectionStatus(ConnectionStatus.connected);
+ // use ping to determine actual connection status since we could've just not timed out yet:
+ // _setConnectionStatus(ConnectionStatus.connected);
socket!.listen(
(Uint8List event) {
@@ -123,14 +124,16 @@ class ElectrumClient {
final errorMsg = error.toString();
print(errorMsg);
unterminatedString = '';
+ socket = null;
},
onDone: () {
print("SOCKET CLOSED!!!!!");
unterminatedString = '';
try {
- if (host == socket?.address.host) {
+ if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy();
+ socket = null;
}
} catch (e) {
print("onDone: $e");
@@ -178,7 +181,7 @@ class ElectrumClient {
unterminatedString = '';
}
} catch (e) {
- print(e.toString());
+ print("parse $e");
}
}
@@ -191,7 +194,7 @@ class ElectrumClient {
try {
await callWithTimeout(method: 'server.ping');
_setConnectionStatus(ConnectionStatus.connected);
- } on RequestFailedTimeoutException catch (_) {
+ } catch (_) {
_setConnectionStatus(ConnectionStatus.disconnected);
}
}
@@ -422,7 +425,7 @@ class ElectrumClient {
BehaviorSubject? subscribe(
{required String id, required String method, List params = const []}) {
try {
- if (socket == null || !isConnected) {
+ if (socket == null) {
return null;
}
final subscription = BehaviorSubject();
@@ -431,14 +434,14 @@ class ElectrumClient {
return subscription;
} catch (e) {
- print(e.toString());
+ print("subscribe $e");
return null;
}
}
Future call(
{required String method, List params = const [], Function(int)? idCallback}) async {
- if (socket == null || !isConnected) {
+ if (socket == null) {
return null;
}
final completer = Completer();
@@ -454,7 +457,7 @@ class ElectrumClient {
Future callWithTimeout(
{required String method, List params = const [], int timeout = 5000}) async {
try {
- if (socket == null || !isConnected) {
+ if (socket == null) {
return null;
}
final completer = Completer();
@@ -470,7 +473,8 @@ class ElectrumClient {
return completer.future;
} catch (e) {
- print(e.toString());
+ print("callWithTimeout $e");
+ rethrow;
}
}
@@ -537,6 +541,12 @@ class ElectrumClient {
onConnectionStatusChange?.call(status);
_connectionStatus = status;
_isConnected = status == ConnectionStatus.connected;
+ if (!_isConnected) {
+ try {
+ socket?.destroy();
+ } catch (_) {}
+ socket = null;
+ }
}
void _handleResponse(Map response) {
diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart
index 4e37f40b1..ebd2f06ae 100644
--- a/cw_bitcoin/lib/electrum_balance.dart
+++ b/cw_bitcoin/lib/electrum_balance.dart
@@ -24,9 +24,12 @@ class ElectrumBalance extends Balance {
final decoded = json.decode(jsonSource) as Map;
return ElectrumBalance(
- confirmed: decoded['confirmed'] as int? ?? 0,
- unconfirmed: decoded['unconfirmed'] as int? ?? 0,
- frozen: decoded['frozen'] as int? ?? 0);
+ confirmed: decoded['confirmed'] as int? ?? 0,
+ unconfirmed: decoded['unconfirmed'] as int? ?? 0,
+ frozen: decoded['frozen'] as int? ?? 0,
+ secondConfirmed: decoded['secondConfirmed'] as int? ?? 0,
+ secondUnconfirmed: decoded['secondUnconfirmed'] as int? ?? 0,
+ );
}
int confirmed;
@@ -36,8 +39,7 @@ class ElectrumBalance extends Balance {
int secondUnconfirmed = 0;
@override
- String get formattedAvailableBalance =>
- bitcoinAmountToString(amount: confirmed - frozen);
+ String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
@override
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart
index 1ab7799e3..7a8b3b951 100644
--- a/cw_bitcoin/lib/electrum_transaction_info.dart
+++ b/cw_bitcoin/lib/electrum_transaction_info.dart
@@ -41,6 +41,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
String? to,
this.unspents,
this.isReceivedSilentPayment = false,
+ Map? additionalInfo,
}) {
this.id = id;
this.height = height;
@@ -54,6 +55,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.isReplaced = isReplaced;
this.confirmations = confirmations;
this.to = to;
+ this.additionalInfo = additionalInfo ?? {};
}
factory ElectrumTransactionInfo.fromElectrumVerbose(Map obj, WalletType type,
@@ -212,6 +214,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map))
.toList(),
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
+ additionalInfo: data['additionalInfo'] as Map?,
);
}
@@ -246,7 +249,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
isReplaced: isReplaced ?? false,
inputAddresses: inputAddresses,
outputAddresses: outputAddresses,
- confirmations: info.confirmations);
+ confirmations: info.confirmations,
+ additionalInfo: additionalInfo);
}
Map toJson() {
@@ -265,10 +269,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses;
m['isReceivedSilentPayment'] = isReceivedSilentPayment;
+ m['additionalInfo'] = additionalInfo;
return m;
}
String toString() {
- return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses)';
+ return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)';
}
}
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index ceaa2f088..e58fad00f 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -4,13 +4,13 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:cw_bitcoin/bitcoin_wallet.dart';
+import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart';
-import 'package:cw_core/encryption_file_utils.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
-import 'package:cw_bitcoin/bitcoin_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';
@@ -25,6 +25,8 @@ import 'package:cw_bitcoin/exceptions.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/encryption_file_utils.dart';
+import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
@@ -36,10 +38,10 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_core/wallet_type.dart';
-import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
@@ -116,7 +118,7 @@ abstract class ElectrumWalletBase
case CryptoCurrency.btc:
case CryptoCurrency.ltc:
case CryptoCurrency.tbtc:
- return Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
+ return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath(
_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1;
case CryptoCurrency.bch:
@@ -126,7 +128,7 @@ abstract class ElectrumWalletBase
}
}
- return Bip32Slip10Secp256k1.fromExtendedKey(xpub!);
+ return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
}
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
@@ -135,6 +137,15 @@ abstract class ElectrumWalletBase
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
+ static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) {
+ switch (network) {
+ case LitecoinNetwork.mainnet:
+ return Bip44Conf.litecoinMainNet.altKeyNetVer;
+ default:
+ return null;
+ }
+ }
+
bool? alwaysScan;
final Bip32Slip10Secp256k1 accountHD;
@@ -231,7 +242,7 @@ abstract class ElectrumWalletBase
}
if (tip > walletInfo.restoreHeight) {
- _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
+ _setListeners(walletInfo.restoreHeight, chainTipParam: currentChainTip);
}
} else {
alwaysScan = false;
@@ -246,23 +257,23 @@ abstract class ElectrumWalletBase
}
}
- int? _currentChainTip;
+ int? currentChainTip;
Future getCurrentChainTip() async {
- if (_currentChainTip != null) {
- return _currentChainTip!;
+ if ((currentChainTip ?? 0) > 0) {
+ return currentChainTip!;
}
- _currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
+ currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
- return _currentChainTip!;
+ return currentChainTip!;
}
Future getUpdatedChainTip() async {
final newTip = await electrumClient.getCurrentBlockChainTip();
- if (newTip != null && newTip > (_currentChainTip ?? 0)) {
- _currentChainTip = newTip;
+ if (newTip != null && newTip > (currentChainTip ?? 0)) {
+ currentChainTip = newTip;
}
- return _currentChainTip ?? 0;
+ return currentChainTip ?? 0;
}
@override
@@ -301,6 +312,7 @@ abstract class ElectrumWalletBase
@action
Future _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
+ if (this is! BitcoinWallet) return;
final chainTip = chainTipParam ?? await getUpdatedChainTip();
if (chainTip == height) {
@@ -337,7 +349,7 @@ abstract class ElectrumWalletBase
isSingleScan: doSingleScan ?? false,
));
- _receiveStream?.cancel();
+ await _receiveStream?.cancel();
_receiveStream = receivePort.listen((var message) async {
if (message is Map) {
for (final map in message.entries) {
@@ -467,7 +479,7 @@ abstract class ElectrumWalletBase
}
} catch (e, stacktrace) {
print(stacktrace);
- print(e.toString());
+ print("startSync $e");
syncStatus = FailedSyncStatus();
}
}
@@ -479,10 +491,10 @@ abstract class ElectrumWalletBase
final response =
await http.get(Uri.parse("http://mempool.cakewallet.com:8999/api/v1/fees/recommended"));
- final result = json.decode(response.body) as Map;
- final slowFee = result['economyFee']?.toInt() ?? 0;
- int mediumFee = result['hourFee']?.toInt() ?? 0;
- int fastFee = result['fastestFee']?.toInt() ?? 0;
+ final result = json.decode(response.body) as Map;
+ final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
+ int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
+ int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0;
if (slowFee == mediumFee) {
mediumFee++;
}
@@ -491,7 +503,9 @@ abstract class ElectrumWalletBase
}
_feeRates = [slowFee, mediumFee, fastFee];
return;
- } catch (_) {}
+ } catch (e) {
+ print(e);
+ }
}
final feeRates = await electrumClient.feeRates(network: network);
@@ -560,6 +574,8 @@ abstract class ElectrumWalletBase
Future connectToNode({required Node node}) async {
this.node = node;
+ if (syncStatus is ConnectingSyncStatus) return;
+
try {
syncStatus = ConnectingSyncStatus();
@@ -571,7 +587,7 @@ abstract class ElectrumWalletBase
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
} catch (e, stacktrace) {
print(stacktrace);
- print(e.toString());
+ print("connectToNode $e");
syncStatus = FailedSyncStatus();
}
}
@@ -582,8 +598,8 @@ abstract class ElectrumWalletBase
UtxoDetails _createUTXOS({
required bool sendAll,
- required int credentialsAmount,
required bool paysToSilentPayment,
+ int credentialsAmount = 0,
int? inputsCount,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) {
@@ -596,7 +612,7 @@ abstract class ElectrumWalletBase
bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount;
- final availableInputs = unspentCoins.where((utx) {
+ var availableInputs = unspentCoins.where((utx) {
if (!utx.isSending || utx.isFrozen) {
return false;
}
@@ -612,6 +628,9 @@ abstract class ElectrumWalletBase
}).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
+ // sort the unconfirmed coins so that mweb coins are first:
+ availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1);
+
for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i];
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
@@ -714,13 +733,11 @@ abstract class ElectrumWalletBase
List 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,
);
@@ -746,23 +763,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));
}
@@ -784,6 +789,7 @@ abstract class ElectrumWalletBase
Future estimateTxForAmount(
int credentialsAmount,
List outputs,
+ List updatedOutputs,
int feeRate, {
int? inputsCount,
String? memo,
@@ -791,6 +797,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,
@@ -812,6 +823,7 @@ abstract class ElectrumWalletBase
return estimateTxForAmount(
credentialsAmount,
outputs,
+ updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
@@ -824,19 +836,38 @@ abstract class ElectrumWalletBase
}
final changeAddress = await walletAddresses.getChangeAddress(
- outputs: outputs,
- utxoDetails: utxoDetails,
+ inputs: utxoDetails.availableInputs,
+ outputs: updatedOutputs,
);
- final address = RegexUtils.addressTypeFromStr(changeAddress, network);
+ final address = RegexUtils.addressTypeFromStr(changeAddress.address, network);
+ updatedOutputs.add(BitcoinOutput(
+ address: address,
+ value: BigInt.from(amountLeftForChangeAndFee),
+ isChange: true,
+ ));
outputs.add(BitcoinOutput(
address: address,
value: BigInt.from(amountLeftForChangeAndFee),
isChange: true,
));
+ // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets
+ final changeDerivationPath =
+ "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
+ "/${changeAddress.isHidden ? "1" : "0"}"
+ "/${changeAddress.index}";
+ utxoDetails.publicKeys[address.pubKeyHash()] =
+ PublicKeyWithDerivationPath('', changeDerivationPath);
+
+ // calcFee updates the silent payment outputs to calculate the tx size accounting
+ // for taproot addresses, but if more inputs are needed to make up for fees,
+ // the silent payment outputs need to be recalculated for the new inputs
+ var temp = outputs.map((output) => output).toList();
int fee = await calcFee(
utxos: utxoDetails.utxos,
- outputs: outputs,
+ // Always take only not updated bitcoin outputs here so for every estimation
+ // the SP outputs are re-generated to the proper taproot addresses
+ outputs: temp,
network: network,
memo: memo,
feeRate: feeRate,
@@ -844,101 +875,81 @@ abstract class ElectrumWalletBase
vinOutpoints: utxoDetails.vinOutpoints,
);
+ updatedOutputs.clear();
+ updatedOutputs.addAll(temp);
+
if (fee == 0) {
throw BitcoinTransactionNoFeeException();
}
int amount = credentialsAmount;
- final lastOutput = outputs.last;
+ final lastOutput = updatedOutputs.last;
final amountLeftForChange = amountLeftForChangeAndFee - fee;
- print(amountLeftForChangeAndFee);
+ 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 (!_isBelowDust(amountLeftForChange)) {
+ 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,
+ value: BigInt.from(amountLeftForChange),
+ isSilentPayment: lastOutput.isSilentPayment,
+ isChange: true,
+ );
outputs[outputs.length - 1] = BitcoinOutput(
address: lastOutput.address,
value: BigInt.from(amountLeftForChange),
isSilentPayment: lastOutput.isSilentPayment,
isChange: true,
);
- } else {
- // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
- outputs.removeLast();
- // Still has inputs to spend before failing
- if (!spendingAllCoins) {
- return estimateTxForAmount(
- credentialsAmount,
- outputs,
- feeRate,
- inputsCount: utxoDetails.utxos.length + 1,
- memo: memo,
- useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
- coinTypeToSpendFrom: coinTypeToSpendFrom,
- );
- }
-
- final estimatedSendAll = await estimateSendAllTx(
- outputs,
- 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 {
- outputs.removeLast();
- return estimateTxForAmount(
- credentialsAmount,
- outputs,
- 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 calcFee({
@@ -1029,12 +1040,20 @@ abstract class ElectrumWalletBase
: feeRate(transactionCredentials.priority!);
EstimatedTxResult estimatedTx;
+ final updatedOutputs = outputs
+ .map((e) => BitcoinOutput(
+ address: e.address,
+ value: e.value,
+ isSilentPayment: e.isSilentPayment,
+ isChange: e.isChange,
+ ))
+ .toList();
+
if (sendAll) {
estimatedTx = await estimateSendAllTx(
- outputs,
+ updatedOutputs,
feeRateInt,
memo: memo,
- credentialsAmount: credentialsAmount,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
@@ -1042,6 +1061,7 @@ abstract class ElectrumWalletBase
estimatedTx = await estimateTxForAmount(
credentialsAmount,
outputs,
+ updatedOutputs,
feeRateInt,
memo: memo,
hasSilentPayment: hasSilentPayment,
@@ -1052,7 +1072,7 @@ abstract class ElectrumWalletBase
if (walletInfo.isHardwareWallet) {
final transaction = await buildHardwareWalletTransaction(
utxos: estimatedTx.utxos,
- outputs: outputs,
+ outputs: updatedOutputs,
publicKeys: estimatedTx.publicKeys,
fee: BigInt.from(estimatedTx.fee),
network: network,
@@ -1082,7 +1102,7 @@ abstract class ElectrumWalletBase
if (network is BitcoinCashNetwork) {
txb = ForkedTransactionBuilder(
utxos: estimatedTx.utxos,
- outputs: outputs,
+ outputs: updatedOutputs,
fee: BigInt.from(estimatedTx.fee),
network: network,
memo: estimatedTx.memo,
@@ -1092,7 +1112,7 @@ abstract class ElectrumWalletBase
} else {
txb = BitcoinTransactionBuilder(
utxos: estimatedTx.utxos,
- outputs: outputs,
+ outputs: updatedOutputs,
fee: BigInt.from(estimatedTx.fee),
network: network,
memo: estimatedTx.memo,
@@ -1152,6 +1172,7 @@ abstract class ElectrumWalletBase
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
+ utxos: estimatedTx.utxos,
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
if (estimatedTx.spendsSilentPayment) {
@@ -1172,6 +1193,8 @@ abstract class ElectrumWalletBase
}
}
+ void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
+
Future buildHardwareWalletTransaction({
required List outputs,
required BigInt fee,
@@ -1341,7 +1364,7 @@ abstract class ElectrumWalletBase
});
}
- // Set the balance of all non-silent payment addresses to 0 before updating
+ // Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) {
@@ -1458,7 +1481,7 @@ abstract class ElectrumWalletBase
await unspentCoinsInfo.deleteAll(keys);
}
} catch (e) {
- print(e.toString());
+ print("refreshUnspentCoinsInfo $e");
}
}
@@ -1780,6 +1803,7 @@ abstract class ElectrumWalletBase
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES
+ .where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
}
@@ -1802,7 +1826,7 @@ abstract class ElectrumWalletBase
return historiesWithDetails;
} catch (e) {
- print(e.toString());
+ print("fetchTransactions $e");
return {};
}
}
@@ -1876,7 +1900,9 @@ abstract class ElectrumWalletBase
if (height > 0) {
storedTx.height = height;
// the tx's block itself is the first confirmation so add 1
- if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1;
+ if ((currentHeight ?? 0) > 0) {
+ storedTx.confirmations = currentHeight! - height + 1;
+ }
storedTx.isPending = storedTx.confirmations == 0;
}
@@ -1889,6 +1915,20 @@ abstract class ElectrumWalletBase
// Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster
+
+ if (this is LitecoinWallet) {
+ // if we have a peg out transaction with the same value
+ // that matches this received transaction, mark it as being from a peg out:
+ for (final tx2 in transactionHistory.transactions.values) {
+ final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs();
+ // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other
+ if (tx2.additionalInfo["isPegOut"] == true &&
+ tx2.amount == tx.amount &&
+ heightDiff <= 5) {
+ tx.additionalInfo["fromPegOut"] = true;
+ }
+ }
+ }
transactionHistory.addOne(tx);
await transactionHistory.save();
}
@@ -1915,14 +1955,28 @@ abstract class ElectrumWalletBase
if (_isTransactionUpdating) {
return;
}
- await getCurrentChainTip();
+ currentChainTip = await getUpdatedChainTip();
- transactionHistory.transactions.values.forEach((tx) async {
- if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) {
- tx.confirmations = await getCurrentChainTip() - tx.height! + 1;
+ bool updated = false;
+ transactionHistory.transactions.values.forEach((tx) {
+ if ((tx.height ?? 0) > 0 && (currentChainTip ?? 0) > 0) {
+ var confirmations = currentChainTip! - tx.height! + 1;
+ if (confirmations < 0) {
+ // if our chain tip is outdated then it could lead to negative confirmations so this is just a failsafe:
+ confirmations = 0;
+ }
+ if (confirmations != tx.confirmations) {
+ updated = true;
+ tx.confirmations = confirmations;
+ transactionHistory.addOne(tx);
+ }
}
});
+ if (updated) {
+ await transactionHistory.save();
+ }
+
_isTransactionUpdating = true;
await fetchTransactions();
walletAddresses.updateReceiveAddresses();
@@ -1944,9 +1998,17 @@ abstract class ElectrumWalletBase
await Future.wait(unsubscribedScriptHashes.map((address) async {
final sh = address.getScriptHash(network);
if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) {
- await _scripthashesUpdateSubject[sh]?.close();
+ try {
+ await _scripthashesUpdateSubject[sh]?.close();
+ } catch (e) {
+ print("failed to close: $e");
+ }
+ }
+ try {
+ _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
+ } catch (e) {
+ print("failed scripthashUpdate: $e");
}
- _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh]?.listen((event) async {
try {
await updateUnspentsForAddress(address);
@@ -1962,6 +2024,8 @@ abstract class ElectrumWalletBase
library: this.runtimeType.toString(),
));
}
+ }, onError: (e, s) {
+ print("sub_listen error: $e $s");
});
}));
}
@@ -2011,6 +2075,13 @@ abstract class ElectrumWalletBase
final balances = await Future.wait(balanceFutures);
+ if (balances.isNotEmpty && balances.first['confirmed'] == null) {
+ // if we got null balance responses from the server, set our connection status to lost and return our last known balance:
+ print("got null balance responses from the server, setting connection status to lost");
+ syncStatus = LostConnectionSyncStatus();
+ return balance[currency] ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
+ }
+
for (var i = 0; i < balances.length; i++) {
final addressRecord = addresses[i];
final balance = balances[i];
@@ -2116,10 +2187,10 @@ abstract class ElectrumWalletBase
Future _setInitialHeight() async {
if (_chainTipUpdateSubject != null) return;
- _currentChainTip = await getUpdatedChainTip();
+ currentChainTip = await getUpdatedChainTip();
- if ((_currentChainTip == null || _currentChainTip! == 0) && walletInfo.restoreHeight == 0) {
- await walletInfo.updateRestoreHeight(_currentChainTip!);
+ if ((currentChainTip == null || currentChainTip! == 0) && walletInfo.restoreHeight == 0) {
+ await walletInfo.updateRestoreHeight(currentChainTip!);
}
_chainTipUpdateSubject = electrumClient.chainTipSubscribe();
@@ -2128,7 +2199,7 @@ abstract class ElectrumWalletBase
final height = int.tryParse(event['height'].toString());
if (height != null) {
- _currentChainTip = height;
+ currentChainTip = height;
if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
_setListeners(walletInfo.restoreHeight);
@@ -2147,25 +2218,33 @@ abstract class ElectrumWalletBase
if (syncStatus is NotConnectedSyncStatus ||
syncStatus is LostConnectionSyncStatus ||
syncStatus is ConnectingSyncStatus) {
- syncStatus = AttemptingSyncStatus();
- startSync();
+ syncStatus = ConnectedSyncStatus();
}
break;
case ConnectionStatus.disconnected:
- syncStatus = NotConnectedSyncStatus();
+ if (syncStatus is! NotConnectedSyncStatus &&
+ syncStatus is! ConnectingSyncStatus &&
+ syncStatus is! SyncronizingSyncStatus) {
+ syncStatus = NotConnectedSyncStatus();
+ }
break;
case ConnectionStatus.failed:
- syncStatus = LostConnectionSyncStatus();
+ if (syncStatus is! LostConnectionSyncStatus) {
+ syncStatus = LostConnectionSyncStatus();
+ }
break;
case ConnectionStatus.connecting:
- syncStatus = ConnectingSyncStatus();
+ if (syncStatus is! ConnectingSyncStatus) {
+ syncStatus = ConnectingSyncStatus();
+ }
break;
default:
}
}
void _syncStatusReaction(SyncStatus syncStatus) async {
+ print("SYNC_STATUS_CHANGE: ${syncStatus}");
if (syncStatus is SyncingSyncStatus) {
return;
}
diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart
index ce1ff9713..c29579436 100644
--- a/cw_bitcoin/lib/electrum_wallet_addresses.dart
+++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart
@@ -3,7 +3,7 @@ import 'dart:io' show Platform;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
-import 'package:cw_bitcoin/electrum_wallet.dart';
+import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -36,6 +36,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required this.mainHd,
required this.sideHd,
required this.network,
+ required this.isHardwareWallet,
List? initialAddresses,
Map? initialRegularAddressIndex,
Map? initialChangeAddressIndex,
@@ -44,6 +45,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
List? initialMwebAddresses,
Bip32Slip10Secp256k1? masterHd,
BitcoinAddressType? initialAddressPageType,
+
}) : _addresses = ObservableList.of((initialAddresses ?? []).toSet()),
addressesByReceiveType =
ObservableList.of(([]).toSet()),
@@ -112,6 +114,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final BasedUtxoNetwork network;
final Bip32Slip10Secp256k1 mainHd;
final Bip32Slip10Secp256k1 sideHd;
+ final bool isHardwareWallet;
@observable
SilentPaymentOwner? silentAddress;
@@ -240,15 +243,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) {
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
- if (Platform.isAndroid || Platform.isIOS) {
+ if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
await _generateInitialAddresses(type: SegwitAddresType.mweb);
}
} else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses();
- await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
- await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
- await _generateInitialAddresses(type: SegwitAddresType.p2tr);
- await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
+ if (!isHardwareWallet) {
+ await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
+ await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
+ await _generateInitialAddresses(type: SegwitAddresType.p2tr);
+ await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
+ }
}
updateAddressesByMatch();
@@ -267,7 +272,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@action
- Future getChangeAddress({List? outputs, UtxoDetails? utxoDetails}) async {
+ Future getChangeAddress({List? inputs, List? outputs, bool isPegIn = false}) async {
updateChangeAddresses();
if (changeAddresses.isEmpty) {
@@ -282,7 +287,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
updateChangeAddresses();
- final address = changeAddresses[currentChangeAddressIndex].address;
+ final address = changeAddresses[currentChangeAddressIndex];
currentChangeAddressIndex += 1;
return address;
}
@@ -326,7 +331,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
);
silentAddresses.add(address);
- updateAddressesByMatch();
+ Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
@@ -343,7 +348,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
network: network,
);
_addresses.add(address);
- updateAddressesByMatch();
+ Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
@@ -478,7 +483,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await saveAddressesInBox();
} catch (e) {
- print(e.toString());
+ print("updateAddresses $e");
}
}
@@ -670,7 +675,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
+
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
+
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
!addr.isHidden && !addr.isUsed && addr.type == type;
diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart
new file mode 100644
index 000000000..62840933c
--- /dev/null
+++ b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart
@@ -0,0 +1,46 @@
+import 'dart:async';
+
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/utils.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:ledger_litecoin/ledger_litecoin.dart';
+
+class LitecoinHardwareWalletService {
+ LitecoinHardwareWalletService(this.ledgerConnection);
+
+ final LedgerConnection ledgerConnection;
+
+ Future> getAvailableAccounts(
+ {int index = 0, int limit = 5}) async {
+ final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
+
+ await litecoinLedgerApp.getVersion();
+
+ final accounts = [];
+ final indexRange = List.generate(limit, (i) => i + index);
+ final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
+
+ for (final i in indexRange) {
+ final derivationPath = "m/84'/2'/$i'";
+ final xpub = await litecoinLedgerApp.getXPubKey(
+ accountsDerivationPath: derivationPath,
+ xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
+ .childKey(Bip32KeyIndex(0));
+
+ final address = generateP2WPKHAddress(
+ hd: hd, index: 0, network: LitecoinNetwork.mainnet);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ xpub: xpub,
+ ));
+ }
+
+ return accounts;
+ }
+}
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 29a2df48a..86228fc83 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:typed_data';
+
import 'package:convert/convert.dart' as convert;
import 'dart:math';
import 'package:collection/collection.dart';
@@ -7,6 +9,7 @@ import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/mweb_utxo.dart';
+import 'package:cw_core/node.dart';
import 'package:cw_mweb/mwebd.pbgrpc.dart';
import 'package:fixnum/fixnum.dart';
import 'package:bip39/bip39.dart' as bip39;
@@ -37,12 +40,15 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:flutter/foundation.dart';
import 'package:grpc/grpc.dart';
import 'package:hive/hive.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:ledger_litecoin/ledger_litecoin.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_mweb/cw_mweb.dart';
import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
import 'package:pointycastle/ecc/api.dart';
import 'package:pointycastle/ecc/curves/secp256k1.dart';
+import 'package:shared_preferences/shared_preferences.dart';
part 'litecoin_wallet.g.dart';
@@ -50,12 +56,13 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
LitecoinWalletBase({
- required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box unspentCoinsInfo,
- required Uint8List seedBytes,
required EncryptionFileUtils encryptionFileUtils,
+ Uint8List? seedBytes,
+ String? mnemonic,
+ String? xpub,
String? passphrase,
String? addressPageType,
List? initialAddresses,
@@ -68,6 +75,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}) : super(
mnemonic: mnemonic,
password: password,
+ xpub: xpub,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
network: LitecoinNetwork.mainnet,
@@ -78,8 +86,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.ltc,
alwaysScan: alwaysScan,
) {
- mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
- mwebEnabled = alwaysScan ?? false;
+ if (seedBytes != null) {
+ mwebHd =
+ Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
+ mwebEnabled = alwaysScan ?? false;
+ } else {
+ mwebHd = null;
+ mwebEnabled = false;
+ }
walletAddresses = LitecoinWalletAddresses(
walletInfo,
initialAddresses: initialAddresses,
@@ -91,12 +105,43 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
network: network,
mwebHd: mwebHd,
mwebEnabled: mwebEnabled,
+ isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
+ reaction((_) => mwebSyncStatus, (status) async {
+ if (mwebSyncStatus is FailedSyncStatus) {
+ // we failed to connect to mweb, check if we are connected to the litecoin node:
+ late int nodeHeight;
+ try {
+ nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
+ } catch (_) {
+ nodeHeight = 0;
+ }
+
+ if (nodeHeight == 0) {
+ // we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us
+ } else {
+ // we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds:
+ await CwMweb.stop();
+ await Future.delayed(const Duration(seconds: 5));
+ startSync();
+ }
+ } else if (mwebSyncStatus is SyncingSyncStatus) {
+ syncStatus = mwebSyncStatus;
+ } else if (mwebSyncStatus is SyncronizingSyncStatus) {
+ if (syncStatus is! SyncronizingSyncStatus) {
+ syncStatus = mwebSyncStatus;
+ }
+ } else if (mwebSyncStatus is SyncedSyncStatus) {
+ if (syncStatus is! SyncedSyncStatus) {
+ syncStatus = mwebSyncStatus;
+ }
+ }
+ });
}
- late final Bip32Slip10Secp256k1 mwebHd;
+ late final Bip32Slip10Secp256k1? mwebHd;
late final Box mwebUtxosBox;
Timer? _syncTimer;
Timer? _feeRatesTimer;
@@ -105,8 +150,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
late bool mwebEnabled;
bool processingUtxos = false;
- List get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
- List get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
+ @observable
+ SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
+
+ List get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
+ List get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
static Future create(
{required String mnemonic,
@@ -216,14 +264,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
return LitecoinWallet(
- mnemonic: keysData.mnemonic!,
+ mnemonic: keysData.mnemonic,
+ xpub: keysData.xPub,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp?.addresses,
initialMwebAddresses: snp?.mwebAddresses,
initialBalance: snp?.balance,
- seedBytes: seedBytes!,
+ seedBytes: seedBytes,
passphrase: passphrase,
encryptionFileUtils: encryptionFileUtils,
initialRegularAddressIndex: snp?.regularAddressIndex,
@@ -240,17 +289,38 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020);
}
+ @action
+ @override
+ Future connectToNode({required Node node}) async {
+ await super.connectToNode(node: node);
+
+ final prefs = await SharedPreferences.getInstance();
+ final mwebNodeUri = prefs.getString("mwebNodeUri") ?? "ltc-electrum.cakewallet.com:9333";
+ await CwMweb.setNodeUriOverride(mwebNodeUri);
+ }
+
@action
@override
Future startSync() async {
print("startSync() called!");
- if (syncStatus is SyncronizingSyncStatus) {
+ print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
+ if (!mwebEnabled) {
+ try {
+ // in case we're switching from a litecoin wallet that had mweb enabled
+ CwMweb.stop();
+ } catch (_) {}
+ super.startSync();
return;
}
+
+ if (mwebSyncStatus is SyncronizingSyncStatus) {
+ return;
+ }
+
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
_syncTimer?.cancel();
try {
- syncStatus = SyncronizingSyncStatus();
+ mwebSyncStatus = SyncronizingSyncStatus();
try {
await subscribeForUpdates();
} catch (e) {
@@ -261,88 +331,105 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
_feeRatesTimer =
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
- if (!mwebEnabled) {
- try {
- // in case we're switching from a litecoin wallet that had mweb enabled
- CwMweb.stop();
- } catch (_) {}
- try {
- await updateAllUnspents();
- await updateTransactions();
- await updateBalance();
- syncStatus = SyncedSyncStatus();
- } catch (e, s) {
- print(e);
- print(s);
- syncStatus = FailedSyncStatus();
- }
- return;
- }
-
+ print("START SYNC FUNCS");
await waitForMwebAddresses();
await processMwebUtxos();
await updateTransactions();
await updateUnspent();
await updateBalance();
- } catch (e) {
- print("failed to start mweb sync: $e");
- syncStatus = FailedSyncStatus(error: "failed to start");
+ print("DONE SYNC FUNCS");
+ } catch (e, s) {
+ print("mweb sync failed: $e $s");
+ mwebSyncStatus = FailedSyncStatus(error: "mweb sync failed: $e");
return;
}
_syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async {
- if (syncStatus is FailedSyncStatus) return;
+ if (mwebSyncStatus is FailedSyncStatus) {
+ _syncTimer?.cancel();
+ return;
+ }
final nodeHeight =
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
if (nodeHeight == 0) {
// we aren't connected to the ltc node yet
- if (syncStatus is! NotConnectedSyncStatus) {
- syncStatus = FailedSyncStatus(error: "Failed to connect to Litecoin node");
+ if (mwebSyncStatus is! NotConnectedSyncStatus) {
+ mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected");
}
return;
}
+ // update the current chain tip so that confirmation calculations are accurate:
+ currentChainTip = nodeHeight;
+
final resp = await CwMweb.status(StatusRequest());
try {
if (resp.blockHeaderHeight < nodeHeight) {
int h = resp.blockHeaderHeight;
- syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
+ mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebHeaderHeight < nodeHeight) {
int h = resp.mwebHeaderHeight;
- syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
+ mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebUtxosHeight < nodeHeight) {
- syncStatus = SyncingSyncStatus(1, 0.999);
+ mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else {
+ bool confirmationsUpdated = false;
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
await checkMwebUtxosSpent();
// update the confirmations for each transaction:
- for (final transaction in transactionHistory.transactions.values) {
- if (transaction.isPending) continue;
- int txHeight = transaction.height ?? resp.mwebUtxosHeight;
- final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
- if (transaction.confirmations == confirmations) continue;
- transaction.confirmations = confirmations;
- transactionHistory.addOne(transaction);
+ for (final tx in transactionHistory.transactions.values) {
+ if (tx.height == null || tx.height == 0) {
+ // update with first confirmation on next block since it hasn't been confirmed yet:
+ tx.height = resp.mwebUtxosHeight;
+ continue;
+ }
+
+ final confirmations = (resp.mwebUtxosHeight - tx.height!) + 1;
+
+ // if the confirmations haven't changed, skip updating:
+ if (tx.confirmations == confirmations) continue;
+
+
+ // if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin):
+ if (confirmations >= 2 &&
+ tx.direction == TransactionDirection.outgoing &&
+ tx.unspents != null) {
+ for (var coin in tx.unspents!) {
+ final utxo = mwebUtxosBox.get(coin.address);
+ if (utxo != null) {
+ print("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ await mwebUtxosBox.delete(coin.address);
+ }
+ }
+ }
+
+ tx.confirmations = confirmations;
+ tx.isPending = false;
+ transactionHistory.addOne(tx);
+ confirmationsUpdated = true;
+ }
+ if (confirmationsUpdated) {
+ await transactionHistory.save();
+ await updateTransactions();
}
- await transactionHistory.save();
}
// prevent unnecessary reaction triggers:
- if (syncStatus is! SyncedSyncStatus) {
+ if (mwebSyncStatus is! SyncedSyncStatus) {
// mwebd is synced, but we could still be processing incoming utxos:
if (!processingUtxos) {
- syncStatus = SyncedSyncStatus();
+ mwebSyncStatus = SyncedSyncStatus();
}
}
return;
}
} catch (e) {
print("error syncing: $e");
- syncStatus = FailedSyncStatus(error: e.toString());
+ mwebSyncStatus = FailedSyncStatus(error: e.toString());
}
});
}
@@ -390,7 +477,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool? usingElectrs,
}) async {
_syncTimer?.cancel();
- int oldHeight = walletInfo.restoreHeight;
await walletInfo.updateRestoreHeight(height);
// go through mwebUtxos and clear any that are above the new restore height:
@@ -454,13 +540,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
outputAddresses: [utxo.outputId],
isReplaced: false,
);
- }
-
- // don't update the confirmations if the tx is updated by electrum:
- if (tx.confirmations == 0 || utxo.height != 0) {
- tx.height = utxo.height;
- tx.isPending = utxo.height == 0;
- tx.confirmations = confirmations;
+ } else {
+ if (tx.confirmations != confirmations || tx.height != utxo.height) {
+ tx.height = utxo.height;
+ tx.confirmations = confirmations;
+ tx.isPending = utxo.height == 0;
+ }
}
bool isNew = transactionHistory.transactions[tx.id] == null;
@@ -510,48 +595,88 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (responseStream == null) {
throw Exception("failed to get utxos stream!");
}
- _utxoStream = responseStream.listen((Utxo sUtxo) async {
- // we're processing utxos, so our balance could still be innacurate:
- if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) {
- syncStatus = SyncronizingSyncStatus();
- processingUtxos = true;
- _processingTimer?.cancel();
- _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
- processingUtxos = false;
- timer.cancel();
- });
- }
+ _utxoStream = responseStream.listen(
+ (Utxo sUtxo) async {
+ // we're processing utxos, so our balance could still be innacurate:
+ if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
+ mwebSyncStatus = SyncronizingSyncStatus();
+ processingUtxos = true;
+ _processingTimer?.cancel();
+ _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
+ processingUtxos = false;
+ timer.cancel();
+ });
+ }
- final utxo = MwebUtxo(
- address: sUtxo.address,
- blockTime: sUtxo.blockTime,
- height: sUtxo.height,
- outputId: sUtxo.outputId,
- value: sUtxo.value.toInt(),
- );
+ final utxo = MwebUtxo(
+ address: sUtxo.address,
+ blockTime: sUtxo.blockTime,
+ height: sUtxo.height,
+ outputId: sUtxo.outputId,
+ value: sUtxo.value.toInt(),
+ );
- // if (mwebUtxosBox.containsKey(utxo.outputId)) {
- // // we've already stored this utxo, skip it:
- // return;
- // }
+ if (mwebUtxosBox.containsKey(utxo.outputId)) {
+ // we've already stored this utxo, skip it:
+ // but do update the utxo height if it's somehow different:
+ final existingUtxo = mwebUtxosBox.get(utxo.outputId);
+ if (existingUtxo!.height != utxo.height) {
+ print(
+ "updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
+ existingUtxo.height = utxo.height;
+ await mwebUtxosBox.put(utxo.outputId, existingUtxo);
+ }
+ return;
+ }
- await updateUnspent();
- await updateBalance();
+ await updateUnspent();
+ await updateBalance();
- final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
+ final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
- // don't process utxos with addresses that are not in the mwebAddrs list:
- if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
- return;
- }
+ // don't process utxos with addresses that are not in the mwebAddrs list:
+ if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
+ return;
+ }
- await mwebUtxosBox.put(utxo.outputId, utxo);
+ await mwebUtxosBox.put(utxo.outputId, utxo);
- await handleIncoming(utxo);
- });
+ await handleIncoming(utxo);
+ },
+ onError: (error) {
+ print("error in utxo stream: $error");
+ mwebSyncStatus = FailedSyncStatus(error: error.toString());
+ },
+ cancelOnError: true,
+ );
+ }
+
+ Future deleteSpentUtxos() async {
+ print("deleteSpentUtxos() called!");
+ final chainHeight = await electrumClient.getCurrentBlockChainTip();
+ final status = await CwMweb.status(StatusRequest());
+ if (chainHeight == null || status.blockHeaderHeight != chainHeight) return;
+ if (status.mwebUtxosHeight != chainHeight) return; // we aren't synced
+
+ // delete any spent utxos with >= 2 confirmations:
+ final spentOutputIds = mwebUtxosBox.values
+ .where((utxo) => utxo.spent && (chainHeight - utxo.height) >= 2)
+ .map((utxo) => utxo.outputId)
+ .toList();
+
+ if (spentOutputIds.isEmpty) return;
+
+ final resp = await CwMweb.spent(SpentRequest(outputId: spentOutputIds));
+ final spent = resp.outputId;
+ if (spent.isEmpty) return;
+
+ for (final outputId in spent) {
+ await mwebUtxosBox.delete(outputId);
+ }
}
Future checkMwebUtxosSpent() async {
+ print("checkMwebUtxosSpent() called!");
if (!mwebEnabled) {
return;
}
@@ -565,21 +690,22 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
updatedAny = await isConfirmed(tx) || updatedAny;
}
+ await deleteSpentUtxos();
+
// get output ids of all the mweb utxos that have > 0 height:
- final outputIds =
- mwebUtxosBox.values.where((utxo) => utxo.height > 0).map((utxo) => utxo.outputId).toList();
+ final outputIds = mwebUtxosBox.values
+ .where((utxo) => utxo.height > 0 && !utxo.spent)
+ .map((utxo) => utxo.outputId)
+ .toList();
final resp = await CwMweb.spent(SpentRequest(outputId: outputIds));
final spent = resp.outputId;
- if (spent.isEmpty) {
- return;
- }
+ if (spent.isEmpty) return;
final status = await CwMweb.status(StatusRequest());
final height = await electrumClient.getCurrentBlockChainTip();
if (height == null || status.blockHeaderHeight != height) return;
if (status.mwebUtxosHeight != height) return; // we aren't synced
-
int amount = 0;
Set inputAddresses = {};
var output = convert.AccumulatorSink();
@@ -673,10 +799,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
@action
Future updateAllUnspents() async {
- // get ltc unspents:
- await super.updateAllUnspents();
-
if (!mwebEnabled) {
+ await super.updateAllUnspents();
return;
}
@@ -687,7 +811,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mwebUtxosBox.keys.forEach((dynamic oId) {
final String outputId = oId as String;
final utxo = mwebUtxosBox.get(outputId);
- if (utxo == null) {
+ if (utxo == null || utxo.spent) {
return;
}
if (utxo.address.isEmpty) {
@@ -712,6 +836,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
mwebUnspentCoins.add(unspent);
});
+
+ // copy coin control attributes to mwebCoins:
+ await updateCoins(mwebUnspentCoins);
+ // get regular ltc unspents (this resets unspentCoins):
+ await super.updateAllUnspents();
+ // add the mwebCoins:
unspentCoins.addAll(mwebUnspentCoins);
}
@@ -731,15 +861,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int unconfirmedMweb = 0;
try {
mwebUtxosBox.values.forEach((utxo) {
- if (utxo.height > 0) {
+ bool isConfirmed = utxo.height > 0;
+
+ print(
+ "utxo: ${isConfirmed ? "confirmed" : "unconfirmed"} ${utxo.spent ? "spent" : "unspent"} ${utxo.outputId} ${utxo.height} ${utxo.value}");
+
+ if (isConfirmed) {
confirmedMweb += utxo.value.toInt();
- } else {
+ }
+
+ if (isConfirmed && utxo.spent) {
+ unconfirmedMweb -= utxo.value.toInt();
+ }
+
+ if (!isConfirmed && !utxo.spent) {
unconfirmedMweb += utxo.value.toInt();
}
});
- if (unconfirmedMweb > 0) {
- unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
- }
} catch (_) {}
for (var addressRecord in walletAddresses.allAddresses) {
@@ -771,7 +909,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// update the txCount for each address using the tx history, since we can't rely on mwebd
// to have an accurate count, we should just keep it in sync with what we know from the tx history:
for (final tx in transactionHistory.transactions.values) {
- // if (tx.isPending) continue;
if (tx.inputAddresses == null || tx.outputAddresses == null) {
continue;
}
@@ -850,7 +987,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation
final preOutputSum =
outputs.fold(BigInt.zero, (acc, output) => acc + output.toOutput.amount);
- final fee = utxos.sumOfUtxosValue() - preOutputSum;
+ var fee = utxos.sumOfUtxosValue() - preOutputSum;
+
+ // determines if the fee is correct:
+ BigInt _sumOutputAmounts(List outputs) {
+ BigInt sum = BigInt.zero;
+ for (final e in outputs) {
+ sum += e.amount;
+ }
+ return sum;
+ }
+
+ final sum1 = _sumOutputAmounts(outputs.map((e) => e.toOutput).toList()) + fee;
+ final sum2 = utxos.sumOfUtxosValue();
+ if (sum1 != sum2) {
+ print("@@@@@ WE HAD TO ADJUST THE FEE! @@@@@@@@");
+ final diff = sum2 - sum1;
+ // add the difference to the fee (abs value):
+ fee += diff.abs();
+ }
+
final txb =
BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network);
final resp = await CwMweb.create(CreateRequest(
@@ -890,6 +1046,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled;
if (!mwebEnabled) {
+ tx.changeAddressOverride =
+ (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
+ .address;
return tx;
}
await waitForMwebAddresses();
@@ -907,18 +1066,41 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool hasMwebInput = false;
bool hasMwebOutput = false;
+ bool hasRegularOutput = false;
for (final output in transactionCredentials.outputs) {
- if (output.extractedAddress?.toLowerCase().contains("mweb") ?? false) {
+ final address = output.address.toLowerCase();
+ final extractedAddress = output.extractedAddress?.toLowerCase();
+
+ if (address.contains("mweb")) {
hasMwebOutput = true;
- break;
+ }
+ if (!address.contains("mweb")) {
+ hasRegularOutput = true;
+ }
+ if (extractedAddress != null && extractedAddress.isNotEmpty) {
+ if (extractedAddress.contains("mweb")) {
+ hasMwebOutput = true;
+ }
+ if (!extractedAddress.contains("mweb")) {
+ hasRegularOutput = true;
+ }
}
}
- if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) {
- hasMwebInput = true;
+ // check if mweb inputs are used:
+ for (final utxo in tx.utxos) {
+ if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
+ hasMwebInput = true;
+ }
}
+ bool isPegIn = !hasMwebInput && hasMwebOutput;
+ bool isPegOut = hasMwebInput && hasRegularOutput;
+ bool isRegular = !hasMwebInput && !hasMwebOutput;
+ tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
+ .getChangeAddress(isPegIn: isPegIn || isRegular))
+ .address;
if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false;
return tx;
@@ -971,8 +1153,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = {};
transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id);
- // await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent
+ // await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return;
+ // mark utxo as spent so we add it to the unconfirmed balance (as negative):
+ utxo.spent = true;
+ await mwebUtxosBox.put(id, utxo);
final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
if (!addresses.contains(utxo.address)) {
@@ -981,7 +1166,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
addressRecord.balance -= utxo.value.toInt();
});
transaction.inputAddresses?.addAll(addresses);
-
+ print("isPegIn: $isPegIn, isPegOut: $isPegOut");
+ transaction.additionalInfo["isPegIn"] = isPegIn;
+ transaction.additionalInfo["isPegOut"] = isPegOut;
transactionHistory.addOne(transaction);
await updateUnspent();
await updateBalance();
@@ -990,6 +1177,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
print(e);
print(s);
if (e.toString().contains("commit failed")) {
+ print(e);
throw Exception("Transaction commit failed (no peers responded), please try again.");
}
rethrow;
@@ -1157,4 +1345,62 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return false;
}
+
+ LedgerConnection? _ledgerConnection;
+ LitecoinLedgerApp? _litecoinLedgerApp;
+
+ @override
+ void setLedgerConnection(LedgerConnection connection) {
+ _ledgerConnection = connection;
+ _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
+ derivationPath: walletInfo.derivationInfo!.derivationPath!);
+ }
+
+ @override
+ Future buildHardwareWalletTransaction({
+ required List outputs,
+ required BigInt fee,
+ required BasedUtxoNetwork network,
+ required List utxos,
+ required Map publicKeys,
+ String? memo,
+ bool enableRBF = false,
+ BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
+ BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
+ }) async {
+ final readyInputs = [];
+ for (final utxo in utxos) {
+ final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
+ final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
+
+ readyInputs.add(LedgerTransaction(
+ rawTx: rawTx,
+ outputIndex: utxo.utxo.vout,
+ ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
+ ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
+ // sequence: enableRBF ? 0x1 : 0xffffffff,
+ sequence: 0xffffffff,
+ ));
+ }
+
+ String? changePath;
+ for (final output in outputs) {
+ final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
+ if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
+ }
+
+ final rawHex = await _litecoinLedgerApp!.createTransaction(
+ inputs: readyInputs,
+ outputs: outputs
+ .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
+ Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
+ .toList(),
+ changePath: changePath,
+ sigHashType: 0x01,
+ additionals: ["bech32"],
+ isSegWit: true,
+ useTrustedInputForSegwit: true);
+
+ return BtcTransaction.fromRaw(rawHex);
+ }
}
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index 0a20665bf..062c590ba 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
+import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@@ -23,6 +24,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required super.mainHd,
required super.sideHd,
required super.network,
+ required super.isHardwareWallet,
required this.mwebHd,
required this.mwebEnabled,
super.initialAddresses,
@@ -36,20 +38,19 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
print("initialized with ${mwebAddrs.length} mweb addresses");
}
- final Bip32Slip10Secp256k1 mwebHd;
+ final Bip32Slip10Secp256k1? mwebHd;
bool mwebEnabled;
int mwebTopUpIndex = 1000;
List mwebAddrs = [];
bool generating = false;
- List get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
-
+ List get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List get spendPubkey =>
- mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
+ mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@override
Future init() async {
- await initMwebAddresses();
+ if (!isHardwareWallet) await initMwebAddresses();
await super.init();
}
@@ -93,6 +94,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
print("Done generating MWEB addresses len: ${mwebAddrs.length}");
// ensure mweb addresses are up to date:
+ // This is the Case if the Litecoin Wallet is a hardware Wallet
+ if (mwebHd == null) return;
+
if (mwebAddresses.length < mwebAddrs.length) {
List addressRecords = mwebAddrs
.asMap()
@@ -142,14 +146,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@action
@override
- Future getChangeAddress({List? outputs, UtxoDetails? utxoDetails}) async {
+ Future getChangeAddress(
+ {List? inputs, List? outputs, bool isPegIn = false}) async {
// use regular change address on peg in, otherwise use mweb for change address:
- if (!mwebEnabled) {
+ if (!mwebEnabled || isPegIn) {
return super.getChangeAddress();
}
- if (outputs != null && utxoDetails != null) {
+ if (inputs != null && outputs != null) {
// check if this is a PEGIN:
bool outputsToMweb = false;
bool comesFromMweb = false;
@@ -161,14 +166,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
outputsToMweb = true;
}
}
- // TODO: this doesn't respect coin control because it doesn't know which available inputs are selected
- utxoDetails.availableInputs.forEach((element) {
+
+ inputs.forEach((element) {
+ if (!element.isSending || element.isFrozen) {
+ return;
+ }
if (element.address.contains("mweb")) {
comesFromMweb = true;
}
});
bool isPegIn = !comesFromMweb && outputsToMweb;
+
if (isPegIn && mwebEnabled) {
return super.getChangeAddress();
}
@@ -181,9 +190,22 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
if (mwebEnabled) {
await ensureMwebAddressUpToIndexExists(1);
- return mwebAddrs[0];
+ return BitcoinAddressRecord(
+ mwebAddrs[0],
+ index: 0,
+ type: SegwitAddresType.mweb,
+ network: network,
+ );
}
return super.getChangeAddress();
}
+
+ @override
+ String get addressForExchange {
+ // don't use mweb addresses for exchange refund address:
+ final addresses = receiveAddresses
+ .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed);
+ return addresses.first.address;
+ }
}
diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart
index c659dd658..d519f4d0a 100644
--- a/cw_bitcoin/lib/litecoin_wallet_service.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_service.dart
@@ -1,4 +1,5 @@
import 'dart:io';
+import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_core/encryption_file_utils.dart';
@@ -20,7 +21,7 @@ class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials,
- BitcoinNewWalletCredentials> {
+ BitcoinRestoreWalletFromHardware> {
LitecoinWalletService(
this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
@@ -111,6 +112,7 @@ class LitecoinWalletService extends WalletService<
File neturinoDb = File('$appDirPath/neutrino.db');
File blockHeaders = File('$appDirPath/block_headers.bin');
File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin');
+ File mwebdLogs = File('$appDirPath/logs/debug.log');
if (neturinoDb.existsSync()) {
neturinoDb.deleteSync();
}
@@ -120,6 +122,9 @@ class LitecoinWalletService extends WalletService<
if (regFilterHeaders.existsSync()) {
regFilterHeaders.deleteSync();
}
+ if (mwebdLogs.existsSync()) {
+ mwebdLogs.deleteSync();
+ }
}
}
@@ -147,9 +152,23 @@ class LitecoinWalletService extends WalletService<
}
@override
- Future restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
- throw UnimplementedError(
- "Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
+ Future restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
+ {bool? isTestnet}) async {
+ final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet;
+ credentials.walletInfo?.network = network.value;
+ credentials.walletInfo?.derivationInfo?.derivationPath =
+ credentials.hwAccountData.derivationPath;
+
+ final wallet = await LitecoinWallet(
+ password: credentials.password!,
+ xpub: credentials.hwAccountData.xpub,
+ walletInfo: credentials.walletInfo!,
+ unspentCoinsInfo: unspentCoinsInfoSource,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+ await wallet.save();
+ await wallet.init();
+ return wallet;
}
@override
diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
index c722dc14f..411c7de16 100644
--- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart
+++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
@@ -24,6 +24,7 @@ class PendingBitcoinTransaction with PendingTransaction {
this.isSendAll = false,
this.hasTaprootInputs = false,
this.isMweb = false,
+ this.utxos = const [],
}) : _listeners = [];
final WalletType type;
@@ -36,7 +37,9 @@ class PendingBitcoinTransaction with PendingTransaction {
final bool isSendAll;
final bool hasChange;
final bool hasTaprootInputs;
+ List utxos;
bool isMweb;
+ String? changeAddressOverride;
String? idOverride;
String? hexOverride;
List? outputAddresses;
@@ -63,6 +66,9 @@ class PendingBitcoinTransaction with PendingTransaction {
PendingChange? get change {
try {
final change = _tx.outputs.firstWhere((out) => out.isChange);
+ if (changeAddressOverride != null) {
+ return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount));
+ }
return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount));
} catch (_) {
return null;
@@ -112,8 +118,7 @@ class PendingBitcoinTransaction with PendingTransaction {
Future _ltcCommit() async {
try {
- final stub = await CwMweb.stub();
- final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
+ final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
idOverride = resp.txid;
} on GrpcError catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
@@ -148,4 +153,9 @@ class PendingBitcoinTransaction with PendingTransaction {
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
outputAddresses: outputAddresses,
fee: fee);
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_bitcoin/lib/psbt_transaction_builder.dart b/cw_bitcoin/lib/psbt_transaction_builder.dart
index d8d2c9fac..81efb792e 100644
--- a/cw_bitcoin/lib/psbt_transaction_builder.dart
+++ b/cw_bitcoin/lib/psbt_transaction_builder.dart
@@ -16,10 +16,6 @@ class PSBTTransactionBuild {
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
- print(input.utxo.isP2tr());
- print(input.utxo.isSegwit());
- print(input.utxo.isP2shSegwit());
-
psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
psbt.setInputOutputIndex(i, input.utxo.vout);
psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index 36d762ea1..c8a83f90c 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -87,8 +87,8 @@ packages:
dependency: "direct overridden"
description:
path: "."
- ref: cake-update-v8
- resolved-ref: fc045a11db3d85d806ca67f75e8b916c706745a2
+ ref: cake-update-v9
+ resolved-ref: "86969a14e337383e14965f5fb45a72a63e5009bc"
url: "https://github.com/cake-tech/bitcoin_base"
source: git
version: "4.7.0"
@@ -101,6 +101,14 @@ packages:
url: "https://github.com/cake-tech/blockchain_utils"
source: git
version: "3.3.0"
+ bluez:
+ dependency: transitive
+ description:
+ name: bluez
+ sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.2"
boolean_selector:
dependency: transitive
description:
@@ -300,6 +308,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.3"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.10"
encrypt:
dependency: transitive
description:
@@ -361,19 +377,19 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.1+1"
- flutter_reactive_ble:
- dependency: transitive
- description:
- name: flutter_reactive_ble
- sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
- url: "https://pub.dev"
- source: hosted
- version: "5.3.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
+ flutter_web_bluetooth:
+ dependency: transitive
+ description:
+ name: flutter_web_bluetooth
+ sha256: fcd03e2e5f82edcedcbc940f1b6a0635a50757374183254f447640886c53208e
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.4"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -387,14 +403,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
- functional_data:
- dependency: transitive
- description:
- name: functional_data
- sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
- url: "https://pub.dev"
- source: hosted
- version: "1.2.0"
glob:
dependency: transitive
description:
@@ -550,29 +558,37 @@ packages:
ledger_bitcoin:
dependency: "direct main"
description:
- path: "."
+ path: "packages/ledger-bitcoin"
ref: HEAD
- resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
- url: "https://github.com/cake-tech/ledger-bitcoin"
+ resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
+ url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
- version: "0.0.2"
- ledger_flutter:
+ version: "0.0.3"
+ ledger_flutter_plus:
dependency: "direct main"
description:
- path: "."
- ref: cake-v3
- resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4"
- url: "https://github.com/cake-tech/ledger-flutter.git"
- source: git
- version: "1.0.2"
- ledger_usb:
- dependency: transitive
- description:
- name: ledger_usb
- sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
+ name: ledger_flutter_plus
+ sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
url: "https://pub.dev"
source: hosted
- version: "1.0.0"
+ version: "1.4.1"
+ ledger_litecoin:
+ dependency: "direct main"
+ description:
+ path: "packages/ledger-litecoin"
+ ref: HEAD
+ resolved-ref: "07cd61ef76a2a017b6d5ef233396740163265457"
+ url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
+ source: git
+ version: "0.0.2"
+ 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:
@@ -701,6 +717,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
@@ -773,30 +797,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.2"
- reactive_ble_mobile:
- dependency: transitive
- description:
- name: reactive_ble_mobile
- sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
- url: "https://pub.dev"
- source: hosted
- version: "5.3.1"
- reactive_ble_platform_interface:
- dependency: transitive
- description:
- name: reactive_ble_platform_interface
- sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
- url: "https://pub.dev"
- source: hosted
- version: "5.3.1"
rxdart:
dependency: "direct main"
description:
name: rxdart
- sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
+ sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
- version: "0.27.7"
+ version: "0.28.0"
shared_preferences:
dependency: "direct main"
description:
@@ -987,6 +995,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:
@@ -1043,6 +1067,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 7e33d8260..bff6104ac 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -24,16 +24,12 @@ dependencies:
git:
url: https://github.com/cake-tech/bitbox-flutter.git
ref: Add-Support-For-OP-Return-data
- rxdart: ^0.27.5
+ rxdart: ^0.28.0
cryptography: ^2.0.5
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2
- ledger_flutter: ^1.0.1
- ledger_bitcoin:
- git:
- url: https://github.com/cake-tech/ledger-bitcoin
cw_mweb:
path: ../cw_mweb
grpc: ^3.2.4
@@ -44,6 +40,15 @@ dependencies:
bech32:
git:
url: https://github.com/cake-tech/bech32.git
+ ledger_flutter_plus: ^1.4.1
+ ledger_bitcoin:
+ git:
+ url: https://github.com/cake-tech/ledger-flutter-plus-plugins
+ path: packages/ledger-bitcoin
+ ledger_litecoin:
+ git:
+ url: https://github.com/cake-tech/ledger-flutter-plus-plugins
+ path: packages/ledger-litecoin
dev_dependencies:
flutter_test:
@@ -54,16 +59,12 @@ dev_dependencies:
hive_generator: ^1.1.3
dependency_overrides:
- ledger_flutter:
- git:
- url: https://github.com/cake-tech/ledger-flutter.git
- ref: cake-v3
watcher: ^1.1.0
protobuf: ^3.1.0
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base
- ref: cake-update-v8
+ ref: cake-update-v9
pointycastle: 3.7.4
ffi: 2.1.0
@@ -72,6 +73,7 @@ dependency_overrides:
# The following section is specific to Flutter.
flutter:
+ uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:
diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
index 825c80d4a..d55914dcd 100644
--- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
@@ -58,6 +58,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: network,
initialAddressPageType: addressPageType,
+ isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart
index 7342dc7f5..fe0ebc828 100644
--- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart
+++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart
@@ -15,6 +15,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required super.mainHd,
required super.sideHd,
required super.network,
+ required super.isHardwareWallet,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
index e1fa9d6e0..05483ce54 100644
--- a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
+++ b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
@@ -85,4 +85,8 @@ class PendingBitcoinCashTransaction with PendingTransaction {
fee: fee,
isReplaced: false,
);
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml
index cd1e52f51..9a5c4f14f 100644
--- a/cw_bitcoin_cash/pubspec.yaml
+++ b/cw_bitcoin_cash/pubspec.yaml
@@ -42,13 +42,14 @@ dependency_overrides:
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base
- ref: cake-update-v8
+ ref: cake-update-v9
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
+ uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:
diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart
index 630078949..af6037a3b 100644
--- a/cw_core/lib/currency_for_wallet_type.dart
+++ b/cw_core/lib/currency_for_wallet_type.dart
@@ -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;
+ }
+}
diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart
index 99fd5b1f0..466d58e2a 100644
--- a/cw_core/lib/hardware/device_connection_type.dart
+++ b/cw_core/lib/hardware/device_connection_type.dart
@@ -7,7 +7,9 @@ enum DeviceConnectionType {
static List supportedConnectionTypes(WalletType walletType,
[bool isIOS = false]) {
switch (walletType) {
+ // case WalletType.monero:
case WalletType.bitcoin:
+ case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
if (isIOS) return [DeviceConnectionType.ble];
diff --git a/cw_core/lib/monero_wallet_keys.dart b/cw_core/lib/monero_wallet_keys.dart
index 1435002a8..fe3784986 100644
--- a/cw_core/lib/monero_wallet_keys.dart
+++ b/cw_core/lib/monero_wallet_keys.dart
@@ -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;
diff --git a/cw_core/lib/mweb_utxo.dart b/cw_core/lib/mweb_utxo.dart
index f8dfab395..5eab11734 100644
--- a/cw_core/lib/mweb_utxo.dart
+++ b/cw_core/lib/mweb_utxo.dart
@@ -11,6 +11,7 @@ class MwebUtxo extends HiveObject {
required this.address,
required this.outputId,
required this.blockTime,
+ this.spent = false,
});
static const typeId = MWEB_UTXO_TYPE_ID;
@@ -30,4 +31,7 @@ class MwebUtxo extends HiveObject {
@HiveField(4)
int blockTime;
+
+ @HiveField(5, defaultValue: false)
+ bool spent;
}
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index e19d2a54b..1094b6402 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -79,6 +79,9 @@ class Node extends HiveObject with Keyable {
@HiveField(9)
bool? supportsSilentPayments;
+ @HiveField(10)
+ bool? supportsMweb;
+
bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
diff --git a/cw_core/lib/pending_transaction.dart b/cw_core/lib/pending_transaction.dart
index 0a6103a5f..78eba68a3 100644
--- a/cw_core/lib/pending_transaction.dart
+++ b/cw_core/lib/pending_transaction.dart
@@ -14,5 +14,8 @@ mixin PendingTransaction {
int? get outputCount => null;
PendingChange? change;
+ bool shouldCommitUR() => false;
+
Future commit();
+ Future commitUR();
}
diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart
index 9d0c968d8..3e34af75f 100644
--- a/cw_core/lib/transaction_info.dart
+++ b/cw_core/lib/transaction_info.dart
@@ -25,6 +25,5 @@ abstract class TransactionInfo extends Object with Keyable {
@override
dynamic get keyIndex => id;
- late Map additionalInfo;
+ Map additionalInfo = {};
}
-
diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart
index ca488cfed..714d229d9 100644
--- a/cw_core/lib/wallet_addresses.dart
+++ b/cw_core/lib/wallet_addresses.dart
@@ -23,7 +23,7 @@ abstract class WalletAddresses {
return _localAddress ?? address;
}
- String? get primaryAddress => null;
+ String get primaryAddress => address;
String? _localAddress;
diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart
index d90ae30bc..85126853e 100644
--- a/cw_core/lib/wallet_service.dart
+++ b/cw_core/lib/wallet_service.dart
@@ -61,4 +61,8 @@ abstract class WalletService false;
}
diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml
index 070779caa..6e32c2ba1 100644
--- a/cw_core/pubspec.yaml
+++ b/cw_core/pubspec.yaml
@@ -47,6 +47,7 @@ dependency_overrides:
# The following section is specific to Flutter.
flutter:
+ uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:
diff --git a/cw_evm/lib/evm_chain_hardware_wallet_service.dart b/cw_evm/lib/evm_chain_hardware_wallet_service.dart
index 6f0d11f2e..d8f67c641 100644
--- a/cw_evm/lib/evm_chain_hardware_wallet_service.dart
+++ b/cw_evm/lib/evm_chain_hardware_wallet_service.dart
@@ -2,26 +2,26 @@ import 'dart:async';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_ethereum/ledger_ethereum.dart';
-import 'package:ledger_flutter/ledger_flutter.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class EVMChainHardwareWalletService {
- EVMChainHardwareWalletService(this.ledger, this.device);
+ EVMChainHardwareWalletService(this.ledgerConnection);
- final Ledger ledger;
- final LedgerDevice device;
+ final LedgerConnection ledgerConnection;
- Future> getAvailableAccounts({int index = 0, int limit = 5}) async {
- final ethereumLedgerApp = EthereumLedgerApp(ledger);
+ Future> getAvailableAccounts(
+ {int index = 0, int limit = 5}) async {
+ final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
- final version = await ethereumLedgerApp.getVersion(device);
+ await ethereumLedgerApp.getVersion();
final accounts = [];
final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) {
final derivationPath = "m/44'/60'/$i'/0/0";
- final address =
- await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath);
+ final address = await ethereumLedgerApp.getAccounts(
+ accountsDerivationPath: derivationPath);
accounts.add(HardwareAccountData(
address: address.first,
diff --git a/cw_evm/lib/evm_chain_transaction_credentials.dart b/cw_evm/lib/evm_chain_transaction_credentials.dart
index 02927cb4d..5b5bdf170 100644
--- a/cw_evm/lib/evm_chain_transaction_credentials.dart
+++ b/cw_evm/lib/evm_chain_transaction_credentials.dart
@@ -1,7 +1,6 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
-import 'package:ledger_flutter/ledger_flutter.dart';
class EVMChainTransactionCredentials {
EVMChainTransactionCredentials(
diff --git a/cw_evm/lib/evm_chain_wallet_addresses.dart b/cw_evm/lib/evm_chain_wallet_addresses.dart
index 4615d79ed..7dd501cc5 100644
--- a/cw_evm/lib/evm_chain_wallet_addresses.dart
+++ b/cw_evm/lib/evm_chain_wallet_addresses.dart
@@ -17,6 +17,9 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
+ @override
+ String get primaryAddress => address;
+
@override
Future init() async {
address = walletInfo.address;
diff --git a/cw_evm/lib/evm_ledger_credentials.dart b/cw_evm/lib/evm_ledger_credentials.dart
index 0d8de1736..a0b7788dc 100644
--- a/cw_evm/lib/evm_ledger_credentials.dart
+++ b/cw_evm/lib/evm_ledger_credentials.dart
@@ -1,17 +1,16 @@
import 'dart:async';
import 'dart:typed_data';
-import 'package:cw_core/hardware/device_not_connected_exception.dart';
+import 'package:cw_core/hardware/device_not_connected_exception.dart'
+ as exception;
import 'package:ledger_ethereum/ledger_ethereum.dart';
-import 'package:ledger_flutter/ledger_flutter.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
class EvmLedgerCredentials extends CredentialsWithKnownAddress {
final String _address;
- Ledger? ledger;
- LedgerDevice? ledgerDevice;
EthereumLedgerApp? ethereumLedgerApp;
EvmLedgerCredentials(this._address);
@@ -19,25 +18,25 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
@override
EthereumAddress get address => EthereumAddress.fromHex(_address);
- void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) {
- ledger = setLedger;
- ledgerDevice = setLedgerDevice;
- ethereumLedgerApp =
- EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
+ void setLedgerConnection(LedgerConnection connection,
+ [String? derivationPath]) {
+ ethereumLedgerApp = EthereumLedgerApp(connection,
+ derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
}
@override
- MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
- throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
+ MsgSignature signToEcSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
@override
Future signToSignature(Uint8List payload,
{int? chainId, bool isEIP1559 = false}) async {
- if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) {
- throw DeviceNotConnectedException();
+ if (ethereumLedgerApp == null) {
+ throw exception.DeviceNotConnectedException();
}
- final sig = await ethereumLedgerApp!.signTransaction(device, payload);
+ final sig = await ethereumLedgerApp!.signTransaction(payload);
final v = sig[0].toInt();
final r = bytesToHex(sig.sublist(1, 1 + 32));
@@ -65,14 +64,16 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
}
- return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
+ return MsgSignature(
+ BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
}
@override
- Future signPersonalMessage(Uint8List payload, {int? chainId}) async {
- if (isNotConnected) throw DeviceNotConnectedException();
+ Future signPersonalMessage(Uint8List payload,
+ {int? chainId}) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
- final sig = await ethereumLedgerApp!.signMessage(device, payload);
+ final sig = await ethereumLedgerApp!.signMessage(payload);
final r = sig.sublist(1, 1 + 32);
final s = sig.sublist(1 + 32, 1 + 32 + 32);
@@ -84,20 +85,22 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
@override
Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
- throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
+ throw UnimplementedError(
+ "EvmLedgerCredentials.signPersonalMessageToUint8List");
- Future provideERC20Info(String erc20ContractAddress, int chainId) async {
- if (isNotConnected) throw DeviceNotConnectedException();
+ Future provideERC20Info(
+ String erc20ContractAddress, int chainId) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
try {
- await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device,
+ await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
erc20ContractAddress: erc20ContractAddress, chainId: chainId);
- } on LedgerException catch (e) {
- if (e.errorCode != -28672) rethrow;
+ } catch (e) {
+ print(e);
+ rethrow;
+ // if (e.errorCode != -28672) rethrow;
}
}
- bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null;
-
- LedgerDevice get device => ledgerDevice ?? ledger!.devices.first;
+ bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
}
diff --git a/cw_evm/lib/pending_evm_chain_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart
index 0b367da68..6861b41f8 100644
--- a/cw_evm/lib/pending_evm_chain_transaction.dart
+++ b/cw_evm/lib/pending_evm_chain_transaction.dart
@@ -47,4 +47,9 @@ class PendingEVMChainTransaction with PendingTransaction {
return '0x${Hex.HEX.encode(txid)}';
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml
index 3e12834b1..326ff4dc9 100644
--- a/cw_evm/pubspec.yaml
+++ b/cw_evm/pubspec.yaml
@@ -25,20 +25,17 @@ dependencies:
mobx: ^2.0.7+4
cw_core:
path: ../cw_core
- ledger_flutter: ^1.0.1
+ ledger_flutter_plus: ^1.4.1
ledger_ethereum:
git:
- url: https://github.com/cake-tech/ledger-ethereum.git
+ url: https://github.com/cake-tech/ledger-flutter-plus-plugins
+ path: packages/ledger-ethereum
dependency_overrides:
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
ref: cake
- ledger_flutter:
- git:
- url: https://github.com/cake-tech/ledger-flutter.git
- ref: cake-v3
watcher: ^1.1.0
dev_dependencies:
diff --git a/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart
index c6b6c6ef7..3ff5f2438 100644
--- a/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart
+++ b/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart
@@ -2,4 +2,9 @@ class WalletRestoreFromKeysException implements Exception {
WalletRestoreFromKeysException({required this.message});
final String message;
+
+ @override
+ String toString() {
+ return message;
+ }
}
\ No newline at end of file
diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart
index 06a838100..e2e598bbe 100644
--- a/cw_haven/lib/haven_wallet.dart
+++ b/cw_haven/lib/haven_wallet.dart
@@ -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(),
diff --git a/cw_haven/lib/haven_wallet_addresses.dart b/cw_haven/lib/haven_wallet_addresses.dart
index 06de44dff..c3d1ef46c 100644
--- a/cw_haven/lib/haven_wallet_addresses.dart
+++ b/cw_haven/lib/haven_wallet_addresses.dart
@@ -23,6 +23,8 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount address;
+
// @override
@observable
Account? account;
diff --git a/cw_haven/lib/pending_haven_transaction.dart b/cw_haven/lib/pending_haven_transaction.dart
index 1d95de08c..dbf075044 100644
--- a/cw_haven/lib/pending_haven_transaction.dart
+++ b/cw_haven/lib/pending_haven_transaction.dart
@@ -48,4 +48,9 @@ class PendingHavenTransaction with PendingTransaction {
rethrow;
}
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart
index 7cb95a507..e3bb25c97 100644
--- a/cw_monero/lib/api/account_list.dart
+++ b/cw_monero/lib/api/account_list.dart
@@ -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;
diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart
index c47263a0f..8aadec74e 100644
--- a/cw_monero/lib/api/transaction_history.dart
+++ b/cw_monero/lib/api/transaction_history.dart
@@ -14,9 +14,13 @@ import 'package:mutex/mutex.dart';
String getTxKey(String txId) {
- final ret = monero.Wallet_getTxKey(wptr!, txid: txId);
- monero.Wallet_status(wptr!);
- return ret;
+ 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;
@@ -185,12 +189,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());
@@ -203,6 +208,11 @@ void commitTransaction({required monero.PendingTransaction transactionPointer})
if (error != null) {
throw CreationTransactionException(message: error);
}
+ if (useUR) {
+ return txCommit as String?;
+ } else {
+ return null;
+ }
}
Future _createTransactionSync(Map args) async {
diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart
index 422acf126..0581ccb94 100644
--- a/cw_monero/lib/api/wallet.dart
+++ b/cw_monero/lib/api/wallet.dart
@@ -120,6 +120,7 @@ Future setupNodeSync(
daemonUsername: login ?? '',
daemonPassword: password ?? '');
});
+ // monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '');
final status = monero.Wallet_status(wptr!);
@@ -339,4 +340,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);
-}
\ No newline at end of file
+}
diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart
index dca7586fb..7f9dbd8fa 100644
--- a/cw_monero/lib/api/wallet_manager.dart
+++ b/cw_monero/lib/api/wallet_manager.dart
@@ -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 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.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 openedWalletsByPath = {};
-void loadWallet(
- {required String path, required String password, int nettype = 0}) {
+Future 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.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 _openWallet(Map args) async => loadWallet(
bool _isWalletExist(String path) => isWalletExistSync(path: path);
-void openWallet(
+Future openWallet(
{required String path,
required String password,
int nettype = 0}) async =>
@@ -425,3 +450,5 @@ Future restoreFromSpendKey(
});
bool isWalletExist({required String path}) => _isWalletExist(path);
+
+bool isViewOnlyBySpendKey() => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
\ No newline at end of file
diff --git a/cw_monero/lib/ledger.dart b/cw_monero/lib/ledger.dart
new file mode 100644
index 000000000..c947d0944
--- /dev/null
+++ b/cw_monero/lib/ledger.dart
@@ -0,0 +1,84 @@
+import 'dart:async';
+import 'dart:ffi';
+import 'dart:typed_data';
+
+import 'package:ffi/ffi.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
+import 'package:monero/monero.dart' as monero;
+// import 'package:polyseed/polyseed.dart';
+
+LedgerConnection? gLedger;
+
+Timer? _ledgerExchangeTimer;
+Timer? _ledgerKeepAlive;
+
+void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
+ _ledgerExchangeTimer?.cancel();
+ _ledgerExchangeTimer = Timer.periodic(Duration(milliseconds: 1), (_) async {
+ final ledgerRequestLength = monero.Wallet_getSendToDeviceLength(ptr);
+ final ledgerRequest = monero.Wallet_getSendToDevice(ptr)
+ .cast()
+ .asTypedList(ledgerRequestLength);
+ if (ledgerRequestLength > 0) {
+ _ledgerKeepAlive?.cancel();
+
+ final Pointer emptyPointer = malloc(0);
+ monero.Wallet_setDeviceSendData(
+ ptr, emptyPointer.cast(), 0);
+ malloc.free(emptyPointer);
+
+ // print("> ${ledgerRequest.toHexString()}");
+ final response = await exchange(connection, ledgerRequest);
+ // print("< ${response.toHexString()}");
+
+ final Pointer result = malloc(response.length);
+ for (var i = 0; i < response.length; i++) {
+ result.asTypedList(response.length)[i] = response[i];
+ }
+
+ monero.Wallet_setDeviceReceivedData(
+ ptr, result.cast(), response.length);
+ malloc.free(result);
+ keepAlive(connection);
+ }
+ });
+}
+
+void keepAlive(LedgerConnection connection) {
+ if (connection.connectionType == ConnectionType.ble) {
+ _ledgerKeepAlive = Timer.periodic(Duration(seconds: 10), (_) async {
+ try {
+ UniversalBle.setNotifiable(
+ connection.device.id,
+ connection.device.deviceInfo.serviceId,
+ connection.device.deviceInfo.notifyCharacteristicKey,
+ BleInputProperty.notification,
+ );
+ } catch (_) {}
+ });
+ }
+}
+
+void disableLedgerExchange() {
+ _ledgerExchangeTimer?.cancel();
+ _ledgerKeepAlive?.cancel();
+ gLedger?.disconnect();
+ gLedger = null;
+}
+
+Future exchange(LedgerConnection connection, Uint8List data) async =>
+ connection.sendOperation(ExchangeOperation(data));
+
+class ExchangeOperation extends LedgerRawOperation {
+ final Uint8List inputData;
+
+ ExchangeOperation(this.inputData);
+
+ @override
+ Future read(ByteDataReader reader) async =>
+ reader.read(reader.remainingLength);
+
+ @override
+ Future> write(ByteDataWriter writer) async => [inputData];
+}
diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart
index 9ae320dcd..2b302e745 100644
--- a/cw_monero/lib/monero_wallet.dart
+++ b/cw_monero/lib/monero_wallet.dart
@@ -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;
@@ -121,6 +124,7 @@ abstract class MoneroWalletBase extends WalletBase MoneroWalletKeys(
+ primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: monero_wallet.getSecretSpendKey(),
privateViewKey: monero_wallet.getSecretViewKey(),
publicSpendKey: monero_wallet.getPublicSpendKey(),
@@ -230,6 +234,36 @@ abstract class MoneroWalletBase extends WalletBase 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 createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
@@ -790,4 +824,9 @@ abstract class MoneroWalletBase extends WalletBase getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
+
@override
String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart
index 63ad17583..6f49640be 100644
--- a/cw_monero/lib/monero_wallet_service.dart
+++ b/cw_monero/lib/monero_wallet_service.dart
@@ -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 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,13 +139,18 @@ 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 (wallet.isHardwareWallet) {
+ wallet.setLedgerConnection(gLedger!);
+ gLedger = null;
+ }
+
if (!isValid) {
await restoreOrResetWalletFiles(name);
wallet.close(shouldCleanup: false);
@@ -185,10 +201,9 @@ class MoneroWalletService extends WalletService<
}
@override
- Future rename(
- String currentName, String password, String newName) async {
+ Future 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 +233,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 +247,34 @@ class MoneroWalletService extends WalletService<
}
@override
- Future restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
- throw UnimplementedError(
- "Restoring a Monero wallet from a hardware wallet is not yet supported!");
+ Future 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 +293,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 +323,8 @@ class MoneroWalletService extends WalletService<
}
}
- Future _restoreFromPolyseed(
- String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
+ Future _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 +369,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 +408,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;
+ }
}
diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart
index 2a75fc26b..eb714eeb3 100644
--- a/cw_monero/lib/pending_monero_transaction.dart
+++ b/cw_monero/lib/pending_monero_transaction.dart
@@ -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 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 commitUR() async {
+ try {
+ final ret = monero_transaction_history.commitTransactionFromPointerAddress(
+ address: pendingTransactionDescription.pointerAddress,
+ useUR: true);
+ return ret;
} catch (e) {
final message = e.toString();
diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock
index 53dc1927d..31d44ac63 100644
--- a/cw_monero/pubspec.lock
+++ b/cw_monero/pubspec.lock
@@ -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: "939040032f6e22529ccb6b5f54d9c48fc94db3d6"
- resolved-ref: "939040032f6e22529ccb6b5f54d9c48fc94db3d6"
+ ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
+ resolved-ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
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"
diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml
index 21039061e..71cbfa243 100644
--- a/cw_monero/pubspec.yaml
+++ b/cw_monero/pubspec.yaml
@@ -25,9 +25,11 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
- ref: 939040032f6e22529ccb6b5f54d9c48fc94db3d6 # monero_c hash
+ ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
+# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0
+ ledger_flutter_plus: ^1.4.1
dev_dependencies:
flutter_test:
diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart
index 39aa433cd..75cc0bcca 100644
--- a/cw_mweb/lib/cw_mweb.dart
+++ b/cw_mweb/lib/cw_mweb.dart
@@ -13,8 +13,18 @@ class CwMweb {
static RpcClient? _rpcClient;
static ClientChannel? _clientChannel;
static int? _port;
- static const TIMEOUT_DURATION = Duration(seconds: 5);
+ static const TIMEOUT_DURATION = Duration(seconds: 15);
static Timer? logTimer;
+ static String? nodeUriOverride;
+
+
+ static Future setNodeUriOverride(String uri) async {
+ nodeUriOverride = uri;
+ if (_rpcClient != null) {
+ await stop();
+ // will be re-started automatically when the next rpc call is made
+ }
+ }
static void readFileWithTimer(String filePath) {
final file = File(filePath);
@@ -40,21 +50,21 @@ class CwMweb {
}
static Future _initializeClient() async {
- print("initialize client called!");
+ print("_initializeClient() called!");
final appDir = await getApplicationSupportDirectory();
const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
String debugLogPath = "${appDir.path}/logs/debug.log";
readFileWithTimer(debugLogPath);
- _port = await CwMwebPlatform.instance.start(appDir.path, ltcNodeUri);
+ _port = await CwMwebPlatform.instance.start(appDir.path, nodeUriOverride ?? ltcNodeUri);
if (_port == null || _port == 0) {
throw Exception("Failed to start server");
}
log("Attempting to connect to server on port: $_port");
// wait for the server to finish starting up before we try to connect to it:
- await Future.delayed(const Duration(seconds: 5));
+ await Future.delayed(const Duration(seconds: 8));
_clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
_rpcClient = null;
@@ -83,10 +93,13 @@ class CwMweb {
log("Attempt $i failed: $e");
log('Caught grpc error: ${e.message}');
_rpcClient = null;
+ // necessary if the database isn't open:
+ await stop();
await Future.delayed(const Duration(seconds: 3));
} catch (e) {
log("Attempt $i failed: $e");
_rpcClient = null;
+ await stop();
await Future.delayed(const Duration(seconds: 3));
}
}
@@ -194,4 +207,18 @@ class CwMweb {
}
return null;
}
+
+ static Future broadcast(BroadcastRequest request) async {
+ log("mweb.broadcast() called");
+ try {
+ _rpcClient = await stub();
+ return await _rpcClient!.broadcast(request, options: CallOptions(timeout: TIMEOUT_DURATION));
+ } on GrpcError catch (e) {
+ log('Caught grpc error: ${e.message}');
+ throw "error from broadcast mweb: $e";
+ } catch (e) {
+ log("Error getting create: $e");
+ rethrow;
+ }
+ }
}
diff --git a/cw_nano/lib/nano_wallet_addresses.dart b/cw_nano/lib/nano_wallet_addresses.dart
index cc532d2c7..e8eae7737 100644
--- a/cw_nano/lib/nano_wallet_addresses.dart
+++ b/cw_nano/lib/nano_wallet_addresses.dart
@@ -18,6 +18,9 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
+ @override
+ String get primaryAddress => address;
+
@observable
NanoAccount? account;
diff --git a/cw_nano/lib/pending_nano_transaction.dart b/cw_nano/lib/pending_nano_transaction.dart
index a027100fd..51a4ef6c1 100644
--- a/cw_nano/lib/pending_nano_transaction.dart
+++ b/cw_nano/lib/pending_nano_transaction.dart
@@ -37,4 +37,9 @@ class PendingNanoTransaction with PendingTransaction {
await nanoClient.processBlock(block, "send");
}
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_shared_external/pubspec.lock b/cw_shared_external/pubspec.lock
new file mode 100644
index 000000000..203ecd5c3
--- /dev/null
+++ b/cw_shared_external/pubspec.lock
@@ -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"
diff --git a/cw_solana/lib/pending_solana_transaction.dart b/cw_solana/lib/pending_solana_transaction.dart
index 38347ed13..e01446000 100644
--- a/cw_solana/lib/pending_solana_transaction.dart
+++ b/cw_solana/lib/pending_solana_transaction.dart
@@ -40,4 +40,9 @@ class PendingSolanaTransaction with PendingTransaction {
@override
String get id => '';
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_solana/lib/solana_wallet_addresses.dart b/cw_solana/lib/solana_wallet_addresses.dart
index 97a76fb99..19eb91fa1 100644
--- a/cw_solana/lib/solana_wallet_addresses.dart
+++ b/cw_solana/lib/solana_wallet_addresses.dart
@@ -14,6 +14,9 @@ abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
@override
String address;
+ @override
+ String get primaryAddress => address;
+
@override
Future init() async {
address = walletInfo.address;
diff --git a/cw_tron/lib/pending_tron_transaction.dart b/cw_tron/lib/pending_tron_transaction.dart
index b6d064b31..2420f083b 100644
--- a/cw_tron/lib/pending_tron_transaction.dart
+++ b/cw_tron/lib/pending_tron_transaction.dart
@@ -30,4 +30,9 @@ class PendingTronTransaction with PendingTransaction {
@override
String get id => '';
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_tron/lib/tron_wallet_addresses.dart b/cw_tron/lib/tron_wallet_addresses.dart
index 35939de26..095f97fa9 100644
--- a/cw_tron/lib/tron_wallet_addresses.dart
+++ b/cw_tron/lib/tron_wallet_addresses.dart
@@ -17,6 +17,9 @@ abstract class TronWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
+ @override
+ String get primaryAddress => address;
+
@override
Future init() async {
address = walletInfo.address;
diff --git a/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart b/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart
new file mode 100644
index 000000000..483b0a174
--- /dev/null
+++ b/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart
@@ -0,0 +1,5 @@
+class ConnectionToNodeException implements Exception {
+ ConnectionToNodeException({required this.message});
+
+ final String message;
+}
\ No newline at end of file
diff --git a/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart
index ad576faa2..6c461ee4c 100644
--- a/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart
+++ b/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart
@@ -3,5 +3,6 @@ class WalletRestoreFromKeysException implements Exception {
final String message;
+ @override
String toString() => message;
}
\ No newline at end of file
diff --git a/cw_wownero/lib/api/structs/account_row.dart b/cw_wownero/lib/api/structs/account_row.dart
new file mode 100644
index 000000000..aa492ee0f
--- /dev/null
+++ b/cw_wownero/lib/api/structs/account_row.dart
@@ -0,0 +1,12 @@
+import 'dart:ffi';
+import 'package:ffi/ffi.dart';
+
+class AccountRow extends Struct {
+ @Int64()
+ external int id;
+
+ external Pointer label;
+
+ String getLabel() => label.toDartString();
+ int getId() => id;
+}
diff --git a/cw_wownero/lib/api/structs/coins_info_row.dart b/cw_wownero/lib/api/structs/coins_info_row.dart
new file mode 100644
index 000000000..ff6f6ce73
--- /dev/null
+++ b/cw_wownero/lib/api/structs/coins_info_row.dart
@@ -0,0 +1,73 @@
+import 'dart:ffi';
+import 'package:ffi/ffi.dart';
+
+class CoinsInfoRow extends Struct {
+ @Int64()
+ external int blockHeight;
+
+ external Pointer 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 address;
+
+ external Pointer addressLabel;
+
+ external Pointer keyImage;
+
+ @Uint64()
+ external int unlockTime;
+
+ @Int8()
+ external int unlocked;
+
+ external Pointer pubKey;
+
+ @Int8()
+ external int coinbase;
+
+ external Pointer 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();
+}
diff --git a/cw_wownero/lib/api/structs/subaddress_row.dart b/cw_wownero/lib/api/structs/subaddress_row.dart
new file mode 100644
index 000000000..d593a793d
--- /dev/null
+++ b/cw_wownero/lib/api/structs/subaddress_row.dart
@@ -0,0 +1,15 @@
+import 'dart:ffi';
+import 'package:ffi/ffi.dart';
+
+class SubaddressRow extends Struct {
+ @Int64()
+ external int id;
+
+ external Pointer address;
+
+ external Pointer label;
+
+ String getLabel() => label.toDartString();
+ String getAddress() => address.toDartString();
+ int getId() => id;
+}
\ No newline at end of file
diff --git a/cw_wownero/lib/api/structs/transaction_info_row.dart b/cw_wownero/lib/api/structs/transaction_info_row.dart
new file mode 100644
index 000000000..bdcc64d3f
--- /dev/null
+++ b/cw_wownero/lib/api/structs/transaction_info_row.dart
@@ -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 hash;
+
+ external Pointer 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();
+}
diff --git a/cw_wownero/lib/api/structs/ut8_box.dart b/cw_wownero/lib/api/structs/ut8_box.dart
new file mode 100644
index 000000000..53e678c88
--- /dev/null
+++ b/cw_wownero/lib/api/structs/ut8_box.dart
@@ -0,0 +1,8 @@
+import 'dart:ffi';
+import 'package:ffi/ffi.dart';
+
+class Utf8Box extends Struct {
+ external Pointer value;
+
+ String getValue() => value.toDartString();
+}
diff --git a/cw_wownero/lib/cw_wownero.dart b/cw_wownero/lib/cw_wownero.dart
new file mode 100644
index 000000000..33a55e305
--- /dev/null
+++ b/cw_wownero/lib/cw_wownero.dart
@@ -0,0 +1,8 @@
+
+import 'cw_wownero_platform_interface.dart';
+
+class CwWownero {
+ Future getPlatformVersion() {
+ return CwWowneroPlatform.instance.getPlatformVersion();
+ }
+}
diff --git a/cw_wownero/lib/cw_wownero_method_channel.dart b/cw_wownero/lib/cw_wownero_method_channel.dart
new file mode 100644
index 000000000..d797f5f81
--- /dev/null
+++ b/cw_wownero/lib/cw_wownero_method_channel.dart
@@ -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 getPlatformVersion() async {
+ final version = await methodChannel.invokeMethod('getPlatformVersion');
+ return version;
+ }
+}
diff --git a/cw_wownero/lib/cw_wownero_platform_interface.dart b/cw_wownero/lib/cw_wownero_platform_interface.dart
new file mode 100644
index 000000000..78b21592c
--- /dev/null
+++ b/cw_wownero/lib/cw_wownero_platform_interface.dart
@@ -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 getPlatformVersion() {
+ throw UnimplementedError('platformVersion() has not been implemented.');
+ }
+}
diff --git a/cw_wownero/lib/mywownero.dart b/cw_wownero/lib/mywownero.dart
new file mode 100644
index 000000000..d50e48b64
--- /dev/null
+++ b/cw_wownero/lib/mywownero.dart
@@ -0,0 +1,1689 @@
+const prefixLength = 3;
+
+String swapEndianBytes(String original) {
+ if (original.length != 8) {
+ return '';
+ }
+
+ return original[6] +
+ original[7] +
+ original[4] +
+ original[5] +
+ original[2] +
+ original[3] +
+ original[0] +
+ original[1];
+}
+
+List tructWords(List wordSet) {
+ final start = 0;
+ final end = prefixLength;
+
+ return wordSet.map((word) => word.substring(start, end)).toList();
+}
+
+String mnemonicDecode(String seed) {
+ final n = englistWordSet.length;
+ var out = '';
+ var wlist = seed.split(' ');
+ wlist.removeLast();
+
+ for (var i = 0; i < wlist.length; i += 3) {
+ final w1 =
+ tructWords(englistWordSet).indexOf(wlist[i].substring(0, prefixLength));
+ final w2 = tructWords(englistWordSet)
+ .indexOf(wlist[i + 1].substring(0, prefixLength));
+ final w3 = tructWords(englistWordSet)
+ .indexOf(wlist[i + 2].substring(0, prefixLength));
+
+ if (w1 == -1 || w2 == -1 || w3 == -1) {
+ print("invalid word in mnemonic");
+ return '';
+ }
+
+ final x = w1 + n * (((n - w1) + w2) % n) + n * n * (((n - w2) + w3) % n);
+
+ if (x % n != w1) {
+ print("Something went wrong when decoding your private key, please try again");
+ return '';
+ }
+
+ final _res = '0000000' + x.toRadixString(16);
+ final start = _res.length - 8;
+ final end = _res.length;
+ final res = _res.substring(start, end);
+
+ out += swapEndianBytes(res);
+ }
+
+ return out;
+}
+
+final englistWordSet = [
+ "abbey",
+ "abducts",
+ "ability",
+ "ablaze",
+ "abnormal",
+ "abort",
+ "abrasive",
+ "absorb",
+ "abyss",
+ "academy",
+ "aces",
+ "aching",
+ "acidic",
+ "acoustic",
+ "acquire",
+ "across",
+ "actress",
+ "acumen",
+ "adapt",
+ "addicted",
+ "adept",
+ "adhesive",
+ "adjust",
+ "adopt",
+ "adrenalin",
+ "adult",
+ "adventure",
+ "aerial",
+ "afar",
+ "affair",
+ "afield",
+ "afloat",
+ "afoot",
+ "afraid",
+ "after",
+ "against",
+ "agenda",
+ "aggravate",
+ "agile",
+ "aglow",
+ "agnostic",
+ "agony",
+ "agreed",
+ "ahead",
+ "aided",
+ "ailments",
+ "aimless",
+ "airport",
+ "aisle",
+ "ajar",
+ "akin",
+ "alarms",
+ "album",
+ "alchemy",
+ "alerts",
+ "algebra",
+ "alkaline",
+ "alley",
+ "almost",
+ "aloof",
+ "alpine",
+ "already",
+ "also",
+ "altitude",
+ "alumni",
+ "always",
+ "amaze",
+ "ambush",
+ "amended",
+ "amidst",
+ "ammo",
+ "amnesty",
+ "among",
+ "amply",
+ "amused",
+ "anchor",
+ "android",
+ "anecdote",
+ "angled",
+ "ankle",
+ "annoyed",
+ "answers",
+ "antics",
+ "anvil",
+ "anxiety",
+ "anybody",
+ "apart",
+ "apex",
+ "aphid",
+ "aplomb",
+ "apology",
+ "apply",
+ "apricot",
+ "aptitude",
+ "aquarium",
+ "arbitrary",
+ "archer",
+ "ardent",
+ "arena",
+ "argue",
+ "arises",
+ "army",
+ "around",
+ "arrow",
+ "arsenic",
+ "artistic",
+ "ascend",
+ "ashtray",
+ "aside",
+ "asked",
+ "asleep",
+ "aspire",
+ "assorted",
+ "asylum",
+ "athlete",
+ "atlas",
+ "atom",
+ "atrium",
+ "attire",
+ "auburn",
+ "auctions",
+ "audio",
+ "august",
+ "aunt",
+ "austere",
+ "autumn",
+ "avatar",
+ "avidly",
+ "avoid",
+ "awakened",
+ "awesome",
+ "awful",
+ "awkward",
+ "awning",
+ "awoken",
+ "axes",
+ "axis",
+ "axle",
+ "aztec",
+ "azure",
+ "baby",
+ "bacon",
+ "badge",
+ "baffles",
+ "bagpipe",
+ "bailed",
+ "bakery",
+ "balding",
+ "bamboo",
+ "banjo",
+ "baptism",
+ "basin",
+ "batch",
+ "bawled",
+ "bays",
+ "because",
+ "beer",
+ "befit",
+ "begun",
+ "behind",
+ "being",
+ "below",
+ "bemused",
+ "benches",
+ "berries",
+ "bested",
+ "betting",
+ "bevel",
+ "beware",
+ "beyond",
+ "bias",
+ "bicycle",
+ "bids",
+ "bifocals",
+ "biggest",
+ "bikini",
+ "bimonthly",
+ "binocular",
+ "biology",
+ "biplane",
+ "birth",
+ "biscuit",
+ "bite",
+ "biweekly",
+ "blender",
+ "blip",
+ "bluntly",
+ "boat",
+ "bobsled",
+ "bodies",
+ "bogeys",
+ "boil",
+ "boldly",
+ "bomb",
+ "border",
+ "boss",
+ "both",
+ "bounced",
+ "bovine",
+ "bowling",
+ "boxes",
+ "boyfriend",
+ "broken",
+ "brunt",
+ "bubble",
+ "buckets",
+ "budget",
+ "buffet",
+ "bugs",
+ "building",
+ "bulb",
+ "bumper",
+ "bunch",
+ "business",
+ "butter",
+ "buying",
+ "buzzer",
+ "bygones",
+ "byline",
+ "bypass",
+ "cabin",
+ "cactus",
+ "cadets",
+ "cafe",
+ "cage",
+ "cajun",
+ "cake",
+ "calamity",
+ "camp",
+ "candy",
+ "casket",
+ "catch",
+ "cause",
+ "cavernous",
+ "cease",
+ "cedar",
+ "ceiling",
+ "cell",
+ "cement",
+ "cent",
+ "certain",
+ "chlorine",
+ "chrome",
+ "cider",
+ "cigar",
+ "cinema",
+ "circle",
+ "cistern",
+ "citadel",
+ "civilian",
+ "claim",
+ "click",
+ "clue",
+ "coal",
+ "cobra",
+ "cocoa",
+ "code",
+ "coexist",
+ "coffee",
+ "cogs",
+ "cohesive",
+ "coils",
+ "colony",
+ "comb",
+ "cool",
+ "copy",
+ "corrode",
+ "costume",
+ "cottage",
+ "cousin",
+ "cowl",
+ "criminal",
+ "cube",
+ "cucumber",
+ "cuddled",
+ "cuffs",
+ "cuisine",
+ "cunning",
+ "cupcake",
+ "custom",
+ "cycling",
+ "cylinder",
+ "cynical",
+ "dabbing",
+ "dads",
+ "daft",
+ "dagger",
+ "daily",
+ "damp",
+ "dangerous",
+ "dapper",
+ "darted",
+ "dash",
+ "dating",
+ "dauntless",
+ "dawn",
+ "daytime",
+ "dazed",
+ "debut",
+ "decay",
+ "dedicated",
+ "deepest",
+ "deftly",
+ "degrees",
+ "dehydrate",
+ "deity",
+ "dejected",
+ "delayed",
+ "demonstrate",
+ "dented",
+ "deodorant",
+ "depth",
+ "desk",
+ "devoid",
+ "dewdrop",
+ "dexterity",
+ "dialect",
+ "dice",
+ "diet",
+ "different",
+ "digit",
+ "dilute",
+ "dime",
+ "dinner",
+ "diode",
+ "diplomat",
+ "directed",
+ "distance",
+ "ditch",
+ "divers",
+ "dizzy",
+ "doctor",
+ "dodge",
+ "does",
+ "dogs",
+ "doing",
+ "dolphin",
+ "domestic",
+ "donuts",
+ "doorway",
+ "dormant",
+ "dosage",
+ "dotted",
+ "double",
+ "dove",
+ "down",
+ "dozen",
+ "dreams",
+ "drinks",
+ "drowning",
+ "drunk",
+ "drying",
+ "dual",
+ "dubbed",
+ "duckling",
+ "dude",
+ "duets",
+ "duke",
+ "dullness",
+ "dummy",
+ "dunes",
+ "duplex",
+ "duration",
+ "dusted",
+ "duties",
+ "dwarf",
+ "dwelt",
+ "dwindling",
+ "dying",
+ "dynamite",
+ "dyslexic",
+ "each",
+ "eagle",
+ "earth",
+ "easy",
+ "eating",
+ "eavesdrop",
+ "eccentric",
+ "echo",
+ "eclipse",
+ "economics",
+ "ecstatic",
+ "eden",
+ "edgy",
+ "edited",
+ "educated",
+ "eels",
+ "efficient",
+ "eggs",
+ "egotistic",
+ "eight",
+ "either",
+ "eject",
+ "elapse",
+ "elbow",
+ "eldest",
+ "eleven",
+ "elite",
+ "elope",
+ "else",
+ "eluded",
+ "emails",
+ "ember",
+ "emerge",
+ "emit",
+ "emotion",
+ "empty",
+ "emulate",
+ "energy",
+ "enforce",
+ "enhanced",
+ "enigma",
+ "enjoy",
+ "enlist",
+ "enmity",
+ "enough",
+ "enraged",
+ "ensign",
+ "entrance",
+ "envy",
+ "epoxy",
+ "equip",
+ "erase",
+ "erected",
+ "erosion",
+ "error",
+ "eskimos",
+ "espionage",
+ "essential",
+ "estate",
+ "etched",
+ "eternal",
+ "ethics",
+ "etiquette",
+ "evaluate",
+ "evenings",
+ "evicted",
+ "evolved",
+ "examine",
+ "excess",
+ "exhale",
+ "exit",
+ "exotic",
+ "exquisite",
+ "extra",
+ "exult",
+ "fabrics",
+ "factual",
+ "fading",
+ "fainted",
+ "faked",
+ "fall",
+ "family",
+ "fancy",
+ "farming",
+ "fatal",
+ "faulty",
+ "fawns",
+ "faxed",
+ "fazed",
+ "feast",
+ "february",
+ "federal",
+ "feel",
+ "feline",
+ "females",
+ "fences",
+ "ferry",
+ "festival",
+ "fetches",
+ "fever",
+ "fewest",
+ "fiat",
+ "fibula",
+ "fictional",
+ "fidget",
+ "fierce",
+ "fifteen",
+ "fight",
+ "films",
+ "firm",
+ "fishing",
+ "fitting",
+ "five",
+ "fixate",
+ "fizzle",
+ "fleet",
+ "flippant",
+ "flying",
+ "foamy",
+ "focus",
+ "foes",
+ "foggy",
+ "foiled",
+ "folding",
+ "fonts",
+ "foolish",
+ "fossil",
+ "fountain",
+ "fowls",
+ "foxes",
+ "foyer",
+ "framed",
+ "friendly",
+ "frown",
+ "fruit",
+ "frying",
+ "fudge",
+ "fuel",
+ "fugitive",
+ "fully",
+ "fuming",
+ "fungal",
+ "furnished",
+ "fuselage",
+ "future",
+ "fuzzy",
+ "gables",
+ "gadget",
+ "gags",
+ "gained",
+ "galaxy",
+ "gambit",
+ "gang",
+ "gasp",
+ "gather",
+ "gauze",
+ "gave",
+ "gawk",
+ "gaze",
+ "gearbox",
+ "gecko",
+ "geek",
+ "gels",
+ "gemstone",
+ "general",
+ "geometry",
+ "germs",
+ "gesture",
+ "getting",
+ "geyser",
+ "ghetto",
+ "ghost",
+ "giant",
+ "giddy",
+ "gifts",
+ "gigantic",
+ "gills",
+ "gimmick",
+ "ginger",
+ "girth",
+ "giving",
+ "glass",
+ "gleeful",
+ "glide",
+ "gnaw",
+ "gnome",
+ "goat",
+ "goblet",
+ "godfather",
+ "goes",
+ "goggles",
+ "going",
+ "goldfish",
+ "gone",
+ "goodbye",
+ "gopher",
+ "gorilla",
+ "gossip",
+ "gotten",
+ "gourmet",
+ "governing",
+ "gown",
+ "greater",
+ "grunt",
+ "guarded",
+ "guest",
+ "guide",
+ "gulp",
+ "gumball",
+ "guru",
+ "gusts",
+ "gutter",
+ "guys",
+ "gymnast",
+ "gypsy",
+ "gyrate",
+ "habitat",
+ "hacksaw",
+ "haggled",
+ "hairy",
+ "hamburger",
+ "happens",
+ "hashing",
+ "hatchet",
+ "haunted",
+ "having",
+ "hawk",
+ "haystack",
+ "hazard",
+ "hectare",
+ "hedgehog",
+ "heels",
+ "hefty",
+ "height",
+ "hemlock",
+ "hence",
+ "heron",
+ "hesitate",
+ "hexagon",
+ "hickory",
+ "hiding",
+ "highway",
+ "hijack",
+ "hiker",
+ "hills",
+ "himself",
+ "hinder",
+ "hippo",
+ "hire",
+ "history",
+ "hitched",
+ "hive",
+ "hoax",
+ "hobby",
+ "hockey",
+ "hoisting",
+ "hold",
+ "honked",
+ "hookup",
+ "hope",
+ "hornet",
+ "hospital",
+ "hotel",
+ "hounded",
+ "hover",
+ "howls",
+ "hubcaps",
+ "huddle",
+ "huge",
+ "hull",
+ "humid",
+ "hunter",
+ "hurried",
+ "husband",
+ "huts",
+ "hybrid",
+ "hydrogen",
+ "hyper",
+ "iceberg",
+ "icing",
+ "icon",
+ "identity",
+ "idiom",
+ "idled",
+ "idols",
+ "igloo",
+ "ignore",
+ "iguana",
+ "illness",
+ "imagine",
+ "imbalance",
+ "imitate",
+ "impel",
+ "inactive",
+ "inbound",
+ "incur",
+ "industrial",
+ "inexact",
+ "inflamed",
+ "ingested",
+ "initiate",
+ "injury",
+ "inkling",
+ "inline",
+ "inmate",
+ "innocent",
+ "inorganic",
+ "input",
+ "inquest",
+ "inroads",
+ "insult",
+ "intended",
+ "inundate",
+ "invoke",
+ "inwardly",
+ "ionic",
+ "irate",
+ "iris",
+ "irony",
+ "irritate",
+ "island",
+ "isolated",
+ "issued",
+ "italics",
+ "itches",
+ "items",
+ "itinerary",
+ "itself",
+ "ivory",
+ "jabbed",
+ "jackets",
+ "jaded",
+ "jagged",
+ "jailed",
+ "jamming",
+ "january",
+ "jargon",
+ "jaunt",
+ "javelin",
+ "jaws",
+ "jazz",
+ "jeans",
+ "jeers",
+ "jellyfish",
+ "jeopardy",
+ "jerseys",
+ "jester",
+ "jetting",
+ "jewels",
+ "jigsaw",
+ "jingle",
+ "jittery",
+ "jive",
+ "jobs",
+ "jockey",
+ "jogger",
+ "joining",
+ "joking",
+ "jolted",
+ "jostle",
+ "journal",
+ "joyous",
+ "jubilee",
+ "judge",
+ "juggled",
+ "juicy",
+ "jukebox",
+ "july",
+ "jump",
+ "junk",
+ "jury",
+ "justice",
+ "juvenile",
+ "kangaroo",
+ "karate",
+ "keep",
+ "kennel",
+ "kept",
+ "kernels",
+ "kettle",
+ "keyboard",
+ "kickoff",
+ "kidneys",
+ "king",
+ "kiosk",
+ "kisses",
+ "kitchens",
+ "kiwi",
+ "knapsack",
+ "knee",
+ "knife",
+ "knowledge",
+ "knuckle",
+ "koala",
+ "laboratory",
+ "ladder",
+ "lagoon",
+ "lair",
+ "lakes",
+ "lamb",
+ "language",
+ "laptop",
+ "large",
+ "last",
+ "later",
+ "launching",
+ "lava",
+ "lawsuit",
+ "layout",
+ "lazy",
+ "lectures",
+ "ledge",
+ "leech",
+ "left",
+ "legion",
+ "leisure",
+ "lemon",
+ "lending",
+ "leopard",
+ "lesson",
+ "lettuce",
+ "lexicon",
+ "liar",
+ "library",
+ "licks",
+ "lids",
+ "lied",
+ "lifestyle",
+ "light",
+ "likewise",
+ "lilac",
+ "limits",
+ "linen",
+ "lion",
+ "lipstick",
+ "liquid",
+ "listen",
+ "lively",
+ "loaded",
+ "lobster",
+ "locker",
+ "lodge",
+ "lofty",
+ "logic",
+ "loincloth",
+ "long",
+ "looking",
+ "lopped",
+ "lordship",
+ "losing",
+ "lottery",
+ "loudly",
+ "love",
+ "lower",
+ "loyal",
+ "lucky",
+ "luggage",
+ "lukewarm",
+ "lullaby",
+ "lumber",
+ "lunar",
+ "lurk",
+ "lush",
+ "luxury",
+ "lymph",
+ "lynx",
+ "lyrics",
+ "macro",
+ "madness",
+ "magically",
+ "mailed",
+ "major",
+ "makeup",
+ "malady",
+ "mammal",
+ "maps",
+ "masterful",
+ "match",
+ "maul",
+ "maverick",
+ "maximum",
+ "mayor",
+ "maze",
+ "meant",
+ "mechanic",
+ "medicate",
+ "meeting",
+ "megabyte",
+ "melting",
+ "memoir",
+ "menu",
+ "merger",
+ "mesh",
+ "metro",
+ "mews",
+ "mice",
+ "midst",
+ "mighty",
+ "mime",
+ "mirror",
+ "misery",
+ "mittens",
+ "mixture",
+ "moat",
+ "mobile",
+ "mocked",
+ "mohawk",
+ "moisture",
+ "molten",
+ "moment",
+ "money",
+ "moon",
+ "mops",
+ "morsel",
+ "mostly",
+ "motherly",
+ "mouth",
+ "movement",
+ "mowing",
+ "much",
+ "muddy",
+ "muffin",
+ "mugged",
+ "mullet",
+ "mumble",
+ "mundane",
+ "muppet",
+ "mural",
+ "musical",
+ "muzzle",
+ "myriad",
+ "mystery",
+ "myth",
+ "nabbing",
+ "nagged",
+ "nail",
+ "names",
+ "nanny",
+ "napkin",
+ "narrate",
+ "nasty",
+ "natural",
+ "nautical",
+ "navy",
+ "nearby",
+ "necklace",
+ "needed",
+ "negative",
+ "neither",
+ "neon",
+ "nephew",
+ "nerves",
+ "nestle",
+ "network",
+ "neutral",
+ "never",
+ "newt",
+ "nexus",
+ "nibs",
+ "niche",
+ "niece",
+ "nifty",
+ "nightly",
+ "nimbly",
+ "nineteen",
+ "nirvana",
+ "nitrogen",
+ "nobody",
+ "nocturnal",
+ "nodes",
+ "noises",
+ "nomad",
+ "noodles",
+ "northern",
+ "nostril",
+ "noted",
+ "nouns",
+ "novelty",
+ "nowhere",
+ "nozzle",
+ "nuance",
+ "nucleus",
+ "nudged",
+ "nugget",
+ "nuisance",
+ "null",
+ "number",
+ "nuns",
+ "nurse",
+ "nutshell",
+ "nylon",
+ "oaks",
+ "oars",
+ "oasis",
+ "oatmeal",
+ "obedient",
+ "object",
+ "obliged",
+ "obnoxious",
+ "observant",
+ "obtains",
+ "obvious",
+ "occur",
+ "ocean",
+ "october",
+ "odds",
+ "odometer",
+ "offend",
+ "often",
+ "oilfield",
+ "ointment",
+ "okay",
+ "older",
+ "olive",
+ "olympics",
+ "omega",
+ "omission",
+ "omnibus",
+ "onboard",
+ "oncoming",
+ "oneself",
+ "ongoing",
+ "onion",
+ "online",
+ "onslaught",
+ "onto",
+ "onward",
+ "oozed",
+ "opacity",
+ "opened",
+ "opposite",
+ "optical",
+ "opus",
+ "orange",
+ "orbit",
+ "orchid",
+ "orders",
+ "organs",
+ "origin",
+ "ornament",
+ "orphans",
+ "oscar",
+ "ostrich",
+ "otherwise",
+ "otter",
+ "ouch",
+ "ought",
+ "ounce",
+ "ourselves",
+ "oust",
+ "outbreak",
+ "oval",
+ "oven",
+ "owed",
+ "owls",
+ "owner",
+ "oxidant",
+ "oxygen",
+ "oyster",
+ "ozone",
+ "pact",
+ "paddles",
+ "pager",
+ "pairing",
+ "palace",
+ "pamphlet",
+ "pancakes",
+ "paper",
+ "paradise",
+ "pastry",
+ "patio",
+ "pause",
+ "pavements",
+ "pawnshop",
+ "payment",
+ "peaches",
+ "pebbles",
+ "peculiar",
+ "pedantic",
+ "peeled",
+ "pegs",
+ "pelican",
+ "pencil",
+ "people",
+ "pepper",
+ "perfect",
+ "pests",
+ "petals",
+ "phase",
+ "pheasants",
+ "phone",
+ "phrases",
+ "physics",
+ "piano",
+ "picked",
+ "pierce",
+ "pigment",
+ "piloted",
+ "pimple",
+ "pinched",
+ "pioneer",
+ "pipeline",
+ "pirate",
+ "pistons",
+ "pitched",
+ "pivot",
+ "pixels",
+ "pizza",
+ "playful",
+ "pledge",
+ "pliers",
+ "plotting",
+ "plus",
+ "plywood",
+ "poaching",
+ "pockets",
+ "podcast",
+ "poetry",
+ "point",
+ "poker",
+ "polar",
+ "ponies",
+ "pool",
+ "popular",
+ "portents",
+ "possible",
+ "potato",
+ "pouch",
+ "poverty",
+ "powder",
+ "pram",
+ "present",
+ "pride",
+ "problems",
+ "pruned",
+ "prying",
+ "psychic",
+ "public",
+ "puck",
+ "puddle",
+ "puffin",
+ "pulp",
+ "pumpkins",
+ "punch",
+ "puppy",
+ "purged",
+ "push",
+ "putty",
+ "puzzled",
+ "pylons",
+ "pyramid",
+ "python",
+ "queen",
+ "quick",
+ "quote",
+ "rabbits",
+ "racetrack",
+ "radar",
+ "rafts",
+ "rage",
+ "railway",
+ "raking",
+ "rally",
+ "ramped",
+ "randomly",
+ "rapid",
+ "rarest",
+ "rash",
+ "rated",
+ "ravine",
+ "rays",
+ "razor",
+ "react",
+ "rebel",
+ "recipe",
+ "reduce",
+ "reef",
+ "refer",
+ "regular",
+ "reheat",
+ "reinvest",
+ "rejoices",
+ "rekindle",
+ "relic",
+ "remedy",
+ "renting",
+ "reorder",
+ "repent",
+ "request",
+ "reruns",
+ "rest",
+ "return",
+ "reunion",
+ "revamp",
+ "rewind",
+ "rhino",
+ "rhythm",
+ "ribbon",
+ "richly",
+ "ridges",
+ "rift",
+ "rigid",
+ "rims",
+ "ringing",
+ "riots",
+ "ripped",
+ "rising",
+ "ritual",
+ "river",
+ "roared",
+ "robot",
+ "rockets",
+ "rodent",
+ "rogue",
+ "roles",
+ "romance",
+ "roomy",
+ "roped",
+ "roster",
+ "rotate",
+ "rounded",
+ "rover",
+ "rowboat",
+ "royal",
+ "ruby",
+ "rudely",
+ "ruffled",
+ "rugged",
+ "ruined",
+ "ruling",
+ "rumble",
+ "runway",
+ "rural",
+ "rustled",
+ "ruthless",
+ "sabotage",
+ "sack",
+ "sadness",
+ "safety",
+ "saga",
+ "sailor",
+ "sake",
+ "salads",
+ "sample",
+ "sanity",
+ "sapling",
+ "sarcasm",
+ "sash",
+ "satin",
+ "saucepan",
+ "saved",
+ "sawmill",
+ "saxophone",
+ "sayings",
+ "scamper",
+ "scenic",
+ "school",
+ "science",
+ "scoop",
+ "scrub",
+ "scuba",
+ "seasons",
+ "second",
+ "sedan",
+ "seeded",
+ "segments",
+ "seismic",
+ "selfish",
+ "semifinal",
+ "sensible",
+ "september",
+ "sequence",
+ "serving",
+ "session",
+ "setup",
+ "seventh",
+ "sewage",
+ "shackles",
+ "shelter",
+ "shipped",
+ "shocking",
+ "shrugged",
+ "shuffled",
+ "shyness",
+ "siblings",
+ "sickness",
+ "sidekick",
+ "sieve",
+ "sifting",
+ "sighting",
+ "silk",
+ "simplest",
+ "sincerely",
+ "sipped",
+ "siren",
+ "situated",
+ "sixteen",
+ "sizes",
+ "skater",
+ "skew",
+ "skirting",
+ "skulls",
+ "skydive",
+ "slackens",
+ "sleepless",
+ "slid",
+ "slower",
+ "slug",
+ "smash",
+ "smelting",
+ "smidgen",
+ "smog",
+ "smuggled",
+ "snake",
+ "sneeze",
+ "sniff",
+ "snout",
+ "snug",
+ "soapy",
+ "sober",
+ "soccer",
+ "soda",
+ "software",
+ "soggy",
+ "soil",
+ "solved",
+ "somewhere",
+ "sonic",
+ "soothe",
+ "soprano",
+ "sorry",
+ "southern",
+ "sovereign",
+ "sowed",
+ "soya",
+ "space",
+ "speedy",
+ "sphere",
+ "spiders",
+ "splendid",
+ "spout",
+ "sprig",
+ "spud",
+ "spying",
+ "square",
+ "stacking",
+ "stellar",
+ "stick",
+ "stockpile",
+ "strained",
+ "stunning",
+ "stylishly",
+ "subtly",
+ "succeed",
+ "suddenly",
+ "suede",
+ "suffice",
+ "sugar",
+ "suitcase",
+ "sulking",
+ "summon",
+ "sunken",
+ "superior",
+ "surfer",
+ "sushi",
+ "suture",
+ "swagger",
+ "swept",
+ "swiftly",
+ "sword",
+ "swung",
+ "syllabus",
+ "symptoms",
+ "syndrome",
+ "syringe",
+ "system",
+ "taboo",
+ "tacit",
+ "tadpoles",
+ "tagged",
+ "tail",
+ "taken",
+ "talent",
+ "tamper",
+ "tanks",
+ "tapestry",
+ "tarnished",
+ "tasked",
+ "tattoo",
+ "taunts",
+ "tavern",
+ "tawny",
+ "taxi",
+ "teardrop",
+ "technical",
+ "tedious",
+ "teeming",
+ "tell",
+ "template",
+ "tender",
+ "tepid",
+ "tequila",
+ "terminal",
+ "testing",
+ "tether",
+ "textbook",
+ "thaw",
+ "theatrics",
+ "thirsty",
+ "thorn",
+ "threaten",
+ "thumbs",
+ "thwart",
+ "ticket",
+ "tidy",
+ "tiers",
+ "tiger",
+ "tilt",
+ "timber",
+ "tinted",
+ "tipsy",
+ "tirade",
+ "tissue",
+ "titans",
+ "toaster",
+ "tobacco",
+ "today",
+ "toenail",
+ "toffee",
+ "together",
+ "toilet",
+ "token",
+ "tolerant",
+ "tomorrow",
+ "tonic",
+ "toolbox",
+ "topic",
+ "torch",
+ "tossed",
+ "total",
+ "touchy",
+ "towel",
+ "toxic",
+ "toyed",
+ "trash",
+ "trendy",
+ "tribal",
+ "trolling",
+ "truth",
+ "trying",
+ "tsunami",
+ "tubes",
+ "tucks",
+ "tudor",
+ "tuesday",
+ "tufts",
+ "tugs",
+ "tuition",
+ "tulips",
+ "tumbling",
+ "tunnel",
+ "turnip",
+ "tusks",
+ "tutor",
+ "tuxedo",
+ "twang",
+ "tweezers",
+ "twice",
+ "twofold",
+ "tycoon",
+ "typist",
+ "tyrant",
+ "ugly",
+ "ulcers",
+ "ultimate",
+ "umbrella",
+ "umpire",
+ "unafraid",
+ "unbending",
+ "uncle",
+ "under",
+ "uneven",
+ "unfit",
+ "ungainly",
+ "unhappy",
+ "union",
+ "unjustly",
+ "unknown",
+ "unlikely",
+ "unmask",
+ "unnoticed",
+ "unopened",
+ "unplugs",
+ "unquoted",
+ "unrest",
+ "unsafe",
+ "until",
+ "unusual",
+ "unveil",
+ "unwind",
+ "unzip",
+ "upbeat",
+ "upcoming",
+ "update",
+ "upgrade",
+ "uphill",
+ "upkeep",
+ "upload",
+ "upon",
+ "upper",
+ "upright",
+ "upstairs",
+ "uptight",
+ "upwards",
+ "urban",
+ "urchins",
+ "urgent",
+ "usage",
+ "useful",
+ "usher",
+ "using",
+ "usual",
+ "utensils",
+ "utility",
+ "utmost",
+ "utopia",
+ "uttered",
+ "vacation",
+ "vague",
+ "vain",
+ "value",
+ "vampire",
+ "vane",
+ "vapidly",
+ "vary",
+ "vastness",
+ "vats",
+ "vaults",
+ "vector",
+ "veered",
+ "vegan",
+ "vehicle",
+ "vein",
+ "velvet",
+ "venomous",
+ "verification",
+ "vessel",
+ "veteran",
+ "vexed",
+ "vials",
+ "vibrate",
+ "victim",
+ "video",
+ "viewpoint",
+ "vigilant",
+ "viking",
+ "village",
+ "vinegar",
+ "violin",
+ "vipers",
+ "virtual",
+ "visited",
+ "vitals",
+ "vivid",
+ "vixen",
+ "vocal",
+ "vogue",
+ "voice",
+ "volcano",
+ "vortex",
+ "voted",
+ "voucher",
+ "vowels",
+ "voyage",
+ "vulture",
+ "wade",
+ "waffle",
+ "wagtail",
+ "waist",
+ "waking",
+ "wallets",
+ "wanted",
+ "warped",
+ "washing",
+ "water",
+ "waveform",
+ "waxing",
+ "wayside",
+ "weavers",
+ "website",
+ "wedge",
+ "weekday",
+ "weird",
+ "welders",
+ "went",
+ "wept",
+ "were",
+ "western",
+ "wetsuit",
+ "whale",
+ "when",
+ "whipped",
+ "whole",
+ "wickets",
+ "width",
+ "wield",
+ "wife",
+ "wiggle",
+ "wildly",
+ "winter",
+ "wipeout",
+ "wiring",
+ "wise",
+ "withdrawn",
+ "wives",
+ "wizard",
+ "wobbly",
+ "woes",
+ "woken",
+ "wolf",
+ "womanly",
+ "wonders",
+ "woozy",
+ "worry",
+ "wounded",
+ "woven",
+ "wrap",
+ "wrist",
+ "wrong",
+ "yacht",
+ "yahoo",
+ "yanks",
+ "yard",
+ "yawning",
+ "yearbook",
+ "yellow",
+ "yesterday",
+ "yeti",
+ "yields",
+ "yodel",
+ "yoga",
+ "younger",
+ "yoyo",
+ "zapped",
+ "zeal",
+ "zebra",
+ "zero",
+ "zesty",
+ "zigzags",
+ "zinger",
+ "zippers",
+ "zodiac",
+ "zombie",
+ "zones",
+ "zoom"
+];
diff --git a/cw_wownero/lib/pending_wownero_transaction.dart b/cw_wownero/lib/pending_wownero_transaction.dart
index 1fc1805eb..967f63756 100644
--- a/cw_wownero/lib/pending_wownero_transaction.dart
+++ b/cw_wownero/lib/pending_wownero_transaction.dart
@@ -50,4 +50,9 @@ class PendingWowneroTransaction with PendingTransaction {
rethrow;
}
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart
index 331957d67..5927d6434 100644
--- a/cw_wownero/lib/wownero_wallet.dart
+++ b/cw_wownero/lib/wownero_wallet.dart
@@ -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(),
diff --git a/cw_wownero/lib/wownero_wallet_addresses.dart b/cw_wownero/lib/wownero_wallet_addresses.dart
index b2f9ec67a..eed81eb45 100644
--- a/cw_wownero/lib/wownero_wallet_addresses.dart
+++ b/cw_wownero/lib/wownero_wallet_addresses.dart
@@ -29,6 +29,9 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
+ @override
+ String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
+
@override
String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock
index 7b7342f8c..a861c6bac 100644
--- a/cw_wownero/pubspec.lock
+++ b/cw_wownero/pubspec.lock
@@ -463,8 +463,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: "939040032f6e22529ccb6b5f54d9c48fc94db3d6"
- resolved-ref: "939040032f6e22529ccb6b5f54d9c48fc94db3d6"
+ ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
+ resolved-ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"
@@ -552,10 +552,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:
diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml
index 0dbde724f..28ece82ef 100644
--- a/cw_wownero/pubspec.yaml
+++ b/cw_wownero/pubspec.yaml
@@ -25,7 +25,8 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
- ref: 939040032f6e22529ccb6b5f54d9c48fc94db3d6 # monero_c hash
+ ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
+# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0
diff --git a/how_to_add_new_wallet_type.md b/how_to_add_new_wallet_type.md
index 917e87cf4..d71e181e0 100644
--- a/how_to_add_new_wallet_type.md
+++ b/how_to_add_new_wallet_type.md
@@ -5,7 +5,7 @@
**N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
**Core Folder/Files Setup**
-- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
+- Identify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
- Fill out the necessary information in the various functions in the files, concerning the wallet name, the native currency type, symbol etc.
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
@@ -144,7 +144,7 @@ You can add as many node entries as desired.
}
}
-- Next, we’ll write the function to change walletX current node to default. An handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, we’ll use it to identify the current node id.
+- Next, we’ll write the function to change walletX current node to default. A handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, we’ll use it to identify the current node id.
Future changeWalletXCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box nodes}) async {
@@ -228,7 +228,7 @@ Now you can run the codebase and successfully create a wallet for type walletX s
**Balance Screen**
- Go to `lib/view_model/dashboard/balance_view_model.dart`
-- Modify the function to adjust the way the balance is being display on the app: `isHomeScreenSettingsEnabled`
+- Modify the function to adjust the way the balance is being displayed on the app: `isHomeScreenSettingsEnabled`
- Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed)
- Same for `additionalBalanceLabel`
- Next, go to `lib/reactions/fiat_rate_update.dart`
diff --git a/howto-build-ios.md b/howto-build-ios.md
index 418fbc96b..544d4359e 100644
--- a/howto-build-ios.md
+++ b/howto-build-ios.md
@@ -57,7 +57,7 @@ Proceed into the source code before proceeding with the next steps:
### 7. Execute Build & Setup Commands for Cake Wallet
-We need to generate project settings like app name, app icon, package name, etc. For this need to setup environment variables and configure project files.
+We need to generate project settings like app name, app icon, package name, etc. For this, we need to setup environment variables and configure project files.
Please pick what app you want to build: cakewallet or monero.com.
@@ -92,7 +92,7 @@ Then we need to generate localization files and mobx models.
`$ flutter build ios --release`
-Then you can open `ios/Runner.xcworkspace` with Xcode and you can to archive the application.
+Then you can open `ios/Runner.xcworkspace` with Xcode and you can archive the application.
Or if you want to run to connected device:
diff --git a/howto-build-windows.md b/howto-build-windows.md
index 796cb3cc8..504f8f785 100644
--- a/howto-build-windows.md
+++ b/howto-build-windows.md
@@ -22,7 +22,7 @@ Then install `Desktop development with C++` packages via Visual Studio 2022, or
- `C++ 2022 Redistributable Update`
- `C++ core desktop features`
- `MVC v143 - VS 2022 C++ x64/x86 build tools`
-- `C++ CMake tools for Windwos`
+- `C++ CMake tools for Windows`
- `Testing tools core features - Build Tools`
- `C++ AddressSanitizer`.
@@ -38,7 +38,7 @@ For building monero dependencies, it is required to install Windows WSL (https:/
### 5. Pull Cake Wallet source code
-You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git:
+You can download CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git:
`$ 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)
@@ -52,6 +52,6 @@ For that you need to run the shell (bash - typically same named utility should b
To configure the application, open the directory where you have downloaded or unarchived Cake Wallet 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.
+After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contain `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.
Copyright (c) 2024 Cake Labs LLC.
diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart
index 2e2991804..83bbb0449 100644
--- a/integration_test/components/common_test_cases.dart
+++ b/integration_test/components/common_test_cases.dart
@@ -10,10 +10,16 @@ class CommonTestCases {
hasType();
}
- Future tapItemByKey(String key, {bool shouldPumpAndSettle = true}) async {
+ Future tapItemByKey(
+ String key, {
+ bool shouldPumpAndSettle = true,
+ int pumpDuration = 100,
+ }) async {
final widget = find.byKey(ValueKey(key));
await tester.tap(widget);
- shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump();
+ shouldPumpAndSettle
+ ? await tester.pumpAndSettle(Duration(milliseconds: pumpDuration))
+ : await tester.pump();
}
Future tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async {
@@ -31,6 +37,11 @@ class CommonTestCases {
expect(typeWidget, findsOneWidget);
}
+ bool isKeyPresent(String key) {
+ final typeWidget = find.byKey(ValueKey(key));
+ return typeWidget.tryEvaluate();
+ }
+
void hasValueKey(String key) {
final typeWidget = find.byKey(ValueKey(key));
expect(typeWidget, findsOneWidget);
@@ -53,33 +64,86 @@ class CommonTestCases {
await tester.pumpAndSettle();
}
- Future scrollUntilVisible(String childKey, String parentScrollableKey,
- {double delta = 300}) async {
- final scrollableWidget = find.descendant(
- of: find.byKey(Key(parentScrollableKey)),
+ Future dragUntilVisible(String childKey, String parentKey) async {
+ await tester.pumpAndSettle();
+
+ final itemFinder = find.byKey(ValueKey(childKey));
+ final listFinder = find.byKey(ValueKey(parentKey));
+
+ // Check if the widget is already in the widget tree
+ if (tester.any(itemFinder)) {
+ // Widget is already built and in the tree
+ tester.printToConsole('Child is already present');
+ return;
+ }
+
+ // We can adjust this as needed
+ final maxScrolls = 200;
+
+ int scrolls = 0;
+ bool found = false;
+
+ // We start by scrolling down
+ bool scrollDown = true;
+
+ // Flag to check if we've already reversed direction
+ bool reversedDirection = false;
+
+ // Find the Scrollable associated with the Parent Ad
+ final scrollableFinder = find.descendant(
+ of: listFinder,
matching: find.byType(Scrollable),
);
- final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey)));
-
- if (isAlreadyVisibile) return;
-
- await tester.scrollUntilVisible(
- find.byKey(ValueKey(childKey)),
- delta,
- scrollable: scrollableWidget,
+ // Ensure that the Scrollable is found
+ expect(
+ scrollableFinder,
+ findsOneWidget,
+ reason: 'Scrollable descendant of the Parent Widget not found.',
);
- }
- bool isWidgetVisible(Finder finder) {
- try {
- final Element element = finder.evaluate().single;
- final RenderBox renderBox = element.renderObject as RenderBox;
- return renderBox.paintBounds
- .shift(renderBox.localToGlobal(Offset.zero))
- .overlaps(tester.binding.renderViews.first.paintBounds);
- } catch (e) {
- return false;
+ // Get the initial scroll position
+ final scrollableState = tester.state(scrollableFinder);
+ double previousScrollPosition = scrollableState.position.pixels;
+
+ while (!found && scrolls < maxScrolls) {
+ tester.printToConsole('Scrolling ${scrollDown ? 'down' : 'up'}, attempt $scrolls');
+
+ // Perform the drag in the current direction
+ await tester.drag(
+ scrollableFinder,
+ scrollDown ? const Offset(0, -100) : const Offset(0, 100),
+ );
+ await tester.pumpAndSettle();
+ scrolls++;
+
+ // Update the scroll position after the drag
+ final currentScrollPosition = scrollableState.position.pixels;
+
+ if (currentScrollPosition == previousScrollPosition) {
+ // Cannot scroll further in this direction
+ if (reversedDirection) {
+ // We've already tried both directions
+ tester.printToConsole('Cannot scroll further in both directions. Widget not found.');
+ break;
+ } else {
+ // Reverse the scroll direction
+ scrollDown = !scrollDown;
+ reversedDirection = true;
+ tester.printToConsole('Reached the end, reversing direction');
+ }
+ } else {
+ // Continue scrolling in the current direction
+ previousScrollPosition = currentScrollPosition;
+ }
+
+ // Check if the widget is now in the widget tree
+ found = tester.any(itemFinder);
+ }
+
+ if (!found) {
+ tester.printToConsole('Widget not found after scrolling in both directions.');
+ return;
}
}
@@ -91,6 +155,15 @@ class CommonTestCases {
await tester.pumpAndSettle();
}
+ void findWidgetViaDescendant({
+ required FinderBase of,
+ required FinderBase matching,
+ }) {
+ final textWidget = find.descendant(of: of, matching: matching);
+
+ expect(textWidget, findsOneWidget);
+ }
+
Future defaultSleepTime({int seconds = 2}) async =>
await Future.delayed(Duration(seconds: seconds));
}
diff --git a/integration_test/components/common_test_constants.dart b/integration_test/components/common_test_constants.dart
index d8381973e..302d52189 100644
--- a/integration_test/components/common_test_constants.dart
+++ b/integration_test/components/common_test_constants.dart
@@ -9,5 +9,5 @@ class CommonTestConstants {
static final String testWalletName = 'Integrated Testing Wallet';
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol;
static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol;
- static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L';
+ static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm';
}
diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart
index 807509de9..82f714da0 100644
--- a/integration_test/components/common_test_flows.dart
+++ b/integration_test/components/common_test_flows.dart
@@ -1,41 +1,65 @@
-import 'package:flutter/foundation.dart';
+import 'package:cake_wallet/entities/seed_type.dart';
+import 'package:cake_wallet/reactions/bip39_wallet_utils.dart';
+import 'package:cake_wallet/wallet_types.g.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/main.dart' as app;
+import '../robots/dashboard_page_robot.dart';
import '../robots/disclaimer_page_robot.dart';
+import '../robots/new_wallet_page_robot.dart';
import '../robots/new_wallet_type_page_robot.dart';
+import '../robots/pre_seed_page_robot.dart';
import '../robots/restore_from_seed_or_key_robot.dart';
import '../robots/restore_options_page_robot.dart';
import '../robots/setup_pin_code_robot.dart';
+import '../robots/wallet_group_description_page_robot.dart';
+import '../robots/wallet_list_page_robot.dart';
+import '../robots/wallet_seed_page_robot.dart';
import '../robots/welcome_page_robot.dart';
import 'common_test_cases.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
+
import 'common_test_constants.dart';
class CommonTestFlows {
CommonTestFlows(this._tester)
: _commonTestCases = CommonTestCases(_tester),
_welcomePageRobot = WelcomePageRobot(_tester),
+ _preSeedPageRobot = PreSeedPageRobot(_tester),
_setupPinCodeRobot = SetupPinCodeRobot(_tester),
+ _dashboardPageRobot = DashboardPageRobot(_tester),
+ _newWalletPageRobot = NewWalletPageRobot(_tester),
_disclaimerPageRobot = DisclaimerPageRobot(_tester),
+ _walletSeedPageRobot = WalletSeedPageRobot(_tester),
+ _walletListPageRobot = WalletListPageRobot(_tester),
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
- _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester);
+ _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester),
+ _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester);
final WidgetTester _tester;
final CommonTestCases _commonTestCases;
final WelcomePageRobot _welcomePageRobot;
+ final PreSeedPageRobot _preSeedPageRobot;
final SetupPinCodeRobot _setupPinCodeRobot;
+ final NewWalletPageRobot _newWalletPageRobot;
+ final DashboardPageRobot _dashboardPageRobot;
final DisclaimerPageRobot _disclaimerPageRobot;
+ final WalletSeedPageRobot _walletSeedPageRobot;
+ final WalletListPageRobot _walletListPageRobot;
final NewWalletTypePageRobot _newWalletTypePageRobot;
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
+ final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
+ //* ========== Handles flow to start the app afresh and accept disclaimer =============
Future startAppFlow(Key key) async {
await app.main(topLevelKey: ValueKey('send_flow_test_app_key'));
-
+
await _tester.pumpAndSettle();
// --------- Disclaimer Page ------------
@@ -46,56 +70,275 @@ class CommonTestFlows {
await _disclaimerPageRobot.tapAcceptButton();
}
- Future restoreWalletThroughSeedsFlow() async {
- await _welcomeToRestoreFromSeedsPath();
- await _restoreFromSeeds();
+ //* ========== Handles flow from welcome to creating a new wallet ===============
+ Future welcomePageToCreateNewWalletFlow(
+ WalletType walletTypeToCreate,
+ List walletPin,
+ ) async {
+ await _welcomeToCreateWalletPath(walletTypeToCreate, walletPin);
+
+ await _generateNewWalletDetails();
+
+ await _confirmPreSeedInfo();
+
+ await _confirmWalletDetails();
}
- Future restoreWalletThroughKeysFlow() async {
- await _welcomeToRestoreFromSeedsPath();
+ //* ========== Handles flow from welcome to restoring wallet from seeds ===============
+ Future welcomePageToRestoreWalletThroughSeedsFlow(
+ WalletType walletTypeToRestore,
+ String walletSeed,
+ List walletPin,
+ ) async {
+ await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
+ await _restoreFromSeeds(walletTypeToRestore, walletSeed);
+ }
+
+ //* ========== Handles flow from welcome to restoring wallet from keys ===============
+ Future welcomePageToRestoreWalletThroughKeysFlow(
+ WalletType walletTypeToRestore,
+ List walletPin,
+ ) async {
+ await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
await _restoreFromKeys();
}
- Future _welcomeToRestoreFromSeedsPath() async {
- // --------- Welcome Page ---------------
- await _welcomePageRobot.navigateToRestoreWalletPage();
+ //* ========== Handles switching to wallet list or menu from dashboard ===============
+ Future switchToWalletMenuFromDashboardPage() async {
+ _tester.printToConsole('Switching to Wallet Menu');
+ await _dashboardPageRobot.openDrawerMenu();
- // ----------- Restore Options Page -----------
- // Route to restore from seeds page to continue flow
- await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage();
+ await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu();
+ }
+ void confirmAllAvailableWalletTypeIconsDisplayCorrectly() {
+ for (var walletType in availableWalletTypes) {
+ final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath;
+
+ final walletIconFinder = find.image(
+ Image.asset(
+ imageUrl!,
+ width: 32,
+ height: 32,
+ ).image,
+ );
+
+ expect(walletIconFinder, findsAny);
+ }
+ }
+
+ //* ========== Handles creating new wallet flow from wallet list/menu ===============
+ Future createNewWalletFromWalletMenu(WalletType walletTypeToCreate) async {
+ _tester.printToConsole('Creating ${walletTypeToCreate.name} Wallet');
+ await _walletListPageRobot.navigateToCreateNewWalletPage();
+ await _commonTestCases.defaultSleepTime();
+
+ await _selectWalletTypeForWallet(walletTypeToCreate);
+ await _commonTestCases.defaultSleepTime();
+
+ // ---- Wallet Group/New Seed Implementation Comes here
+ await _walletGroupDescriptionPageFlow(true, walletTypeToCreate);
+
+ await _generateNewWalletDetails();
+
+ await _confirmPreSeedInfo();
+
+ await _confirmWalletDetails();
+ await _commonTestCases.defaultSleepTime();
+ }
+
+ Future _walletGroupDescriptionPageFlow(bool isNewSeed, WalletType walletType) async {
+ if (!isBIP39Wallet(walletType)) return;
+
+ await _walletGroupDescriptionPageRobot.isWalletGroupDescriptionPage();
+
+ if (isNewSeed) {
+ await _walletGroupDescriptionPageRobot.navigateToCreateNewSeedPage();
+ } else {
+ await _walletGroupDescriptionPageRobot.navigateToChooseWalletGroup();
+ }
+ }
+
+ //* ========== Handles restore wallet flow from wallet list/menu ===============
+ Future restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async {
+ _tester.printToConsole('Restoring ${walletType.name} Wallet');
+ await _walletListPageRobot.navigateToRestoreWalletOptionsPage();
+ await _commonTestCases.defaultSleepTime();
+
+ await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
+ await _commonTestCases.defaultSleepTime();
+
+ await _selectWalletTypeForWallet(walletType);
+ await _commonTestCases.defaultSleepTime();
+
+ await _restoreFromSeeds(walletType, walletSeed);
+ await _commonTestCases.defaultSleepTime();
+ }
+
+ //* ========== Handles setting up pin code for wallet on first install ===============
+ Future setupPinCodeForWallet(List pin) async {
// ----------- SetupPinCode Page -------------
// Confirm initial defaults - Widgets to be displayed etc
await _setupPinCodeRobot.isSetupPinCodePage();
- await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true);
- await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false);
+ await _setupPinCodeRobot.enterPinCode(pin);
+ await _setupPinCodeRobot.enterPinCode(pin);
await _setupPinCodeRobot.tapSuccessButton();
+ }
+ Future _welcomeToCreateWalletPath(
+ WalletType walletTypeToCreate,
+ List pin,
+ ) async {
+ await _welcomePageRobot.navigateToCreateNewWalletPage();
+
+ await setupPinCodeForWallet(pin);
+
+ await _selectWalletTypeForWallet(walletTypeToCreate);
+ }
+
+ Future _welcomeToRestoreFromSeedsOrKeysPath(
+ WalletType walletTypeToRestore,
+ List pin,
+ ) async {
+ await _welcomePageRobot.navigateToRestoreWalletPage();
+
+ await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
+
+ await setupPinCodeForWallet(pin);
+
+ await _selectWalletTypeForWallet(walletTypeToRestore);
+ }
+
+ //* ============ Handles New Wallet Type Page ==================
+ Future _selectWalletTypeForWallet(WalletType type) async {
// ----------- NewWalletType Page -------------
// Confirm scroll behaviour works properly
- await _newWalletTypePageRobot
- .findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType);
+ await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
// Select a wallet and route to next page
- await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType);
+ await _newWalletTypePageRobot.selectWalletType(type);
await _newWalletTypePageRobot.onNextButtonPressed();
}
- Future _restoreFromSeeds() async {
+ //* ============ Handles New Wallet Page ==================
+ Future _generateNewWalletDetails() async {
+ await _newWalletPageRobot.isNewWalletPage();
+
+ await _newWalletPageRobot.generateWalletName();
+
+ await _newWalletPageRobot.onNextButtonPressed();
+ }
+
+ //* ============ Handles Pre Seed Page =====================
+ Future _confirmPreSeedInfo() async {
+ await _preSeedPageRobot.isPreSeedPage();
+
+ await _preSeedPageRobot.onConfirmButtonPressed();
+ }
+
+ //* ============ Handles Wallet Seed Page ==================
+ Future _confirmWalletDetails() async {
+ await _walletSeedPageRobot.isWalletSeedPage();
+
+ _walletSeedPageRobot.confirmWalletDetailsDisplayCorrectly();
+
+ _walletSeedPageRobot.confirmWalletSeedReminderDisplays();
+
+ await _walletSeedPageRobot.onCopySeedsButtonPressed();
+
+ await _walletSeedPageRobot.onNextButtonPressed();
+
+ await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed();
+ }
+
+ //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action
+ Future _restoreFromSeeds(WalletType type, String walletSeed) async {
// ----------- RestoreFromSeedOrKeys Page -------------
- await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
- await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.solanaTestWalletSeeds);
+
+ await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions();
+ await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(walletSeed);
+
+ final numberOfWords = walletSeed.split(' ').length;
+
+ if (numberOfWords == 25 && (type == WalletType.monero)) {
+ await _restoreFromSeedOrKeysPageRobot
+ .chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy);
+
+ // Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet
+ await _restoreFromSeedOrKeysPageRobot
+ .enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight);
+ }
+
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
}
+ //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Keys Action
Future _restoreFromKeys() async {
await _commonTestCases.swipePage();
await _commonTestCases.defaultSleepTime();
- await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
+ await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions(
+ isSeedFormEntry: false,
+ );
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore('');
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
}
+
+ //* ====== Utility Function to get test wallet seeds for each wallet type ========
+ String getWalletSeedsByWalletType(WalletType walletType) {
+ switch (walletType) {
+ case WalletType.monero:
+ return secrets.moneroTestWalletSeeds;
+ case WalletType.bitcoin:
+ return secrets.bitcoinTestWalletSeeds;
+ case WalletType.ethereum:
+ return secrets.ethereumTestWalletSeeds;
+ case WalletType.litecoin:
+ return secrets.litecoinTestWalletSeeds;
+ case WalletType.bitcoinCash:
+ return secrets.bitcoinCashTestWalletSeeds;
+ case WalletType.polygon:
+ return secrets.polygonTestWalletSeeds;
+ case WalletType.solana:
+ return secrets.solanaTestWalletSeeds;
+ case WalletType.tron:
+ return secrets.tronTestWalletSeeds;
+ case WalletType.nano:
+ return secrets.nanoTestWalletSeeds;
+ case WalletType.wownero:
+ return secrets.wowneroTestWalletSeeds;
+ default:
+ return '';
+ }
+ }
+
+ //* ====== Utility Function to get test receive address for each wallet type ========
+ String getReceiveAddressByWalletType(WalletType walletType) {
+ switch (walletType) {
+ case WalletType.monero:
+ return secrets.moneroTestWalletReceiveAddress;
+ case WalletType.bitcoin:
+ return secrets.bitcoinTestWalletReceiveAddress;
+ case WalletType.ethereum:
+ return secrets.ethereumTestWalletReceiveAddress;
+ case WalletType.litecoin:
+ return secrets.litecoinTestWalletReceiveAddress;
+ case WalletType.bitcoinCash:
+ return secrets.bitcoinCashTestWalletReceiveAddress;
+ case WalletType.polygon:
+ return secrets.polygonTestWalletReceiveAddress;
+ case WalletType.solana:
+ return secrets.solanaTestWalletReceiveAddress;
+ case WalletType.tron:
+ return secrets.tronTestWalletReceiveAddress;
+ case WalletType.nano:
+ return secrets.nanoTestWalletReceiveAddress;
+ case WalletType.wownero:
+ return secrets.wowneroTestWalletReceiveAddress;
+ default:
+ return '';
+ }
+ }
}
diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart
index 9d97d47f8..db24fbc0b 100644
--- a/integration_test/funds_related_tests.dart
+++ b/integration_test/funds_related_tests.dart
@@ -9,6 +9,7 @@ import 'robots/dashboard_page_robot.dart';
import 'robots/exchange_confirm_page_robot.dart';
import 'robots/exchange_page_robot.dart';
import 'robots/exchange_trade_page_robot.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -32,7 +33,11 @@ void main() {
await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key'));
- await commonTestFlows.restoreWalletThroughSeedsFlow();
+ await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
+ CommonTestConstants.testWalletType,
+ secrets.solanaTestWalletSeeds,
+ CommonTestConstants.pin,
+ );
// ----------- RestoreFromSeedOrKeys Page -------------
await dashboardPageRobot.navigateToExchangePage();
@@ -59,7 +64,7 @@ void main() {
final onAuthPage = authPageRobot.onAuthPage();
if (onAuthPage) {
- await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
+ await authPageRobot.enterPinCode(CommonTestConstants.pin);
}
// ----------- Exchange Confirm Page -------------
diff --git a/integration_test/robots/dashboard_menu_widget_robot.dart b/integration_test/robots/dashboard_menu_widget_robot.dart
new file mode 100644
index 000000000..f48033dda
--- /dev/null
+++ b/integration_test/robots/dashboard_menu_widget_robot.dart
@@ -0,0 +1,39 @@
+import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class DashboardMenuWidgetRobot {
+ DashboardMenuWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future hasMenuWidget() async {
+ commonTestCases.hasType();
+ }
+
+ void displaysTheCorrectWalletNameAndSubName() {
+ final menuWidgetState = tester.state(find.byType(MenuWidget));
+
+ final walletName = menuWidgetState.widget.dashboardViewModel.name;
+ commonTestCases.hasText(walletName);
+
+ final walletSubName = menuWidgetState.widget.dashboardViewModel.subname;
+ if (walletSubName.isNotEmpty) {
+ commonTestCases.hasText(walletSubName);
+ }
+ }
+
+ Future navigateToWalletMenu() async {
+ await commonTestCases.tapItemByKey('dashboard_page_menu_widget_wallet_menu_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future navigateToSecurityAndBackupPage() async {
+ await commonTestCases.tapItemByKey(
+ 'dashboard_page_menu_widget_security_and_backup_button_key',
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart
index fc917c3b2..bc5f411ad 100644
--- a/integration_test/robots/dashboard_page_robot.dart
+++ b/integration_test/robots/dashboard_page_robot.dart
@@ -1,20 +1,44 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
+import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_test/flutter_test.dart';
import '../components/common_test_cases.dart';
+import 'dashboard_menu_widget_robot.dart';
class DashboardPageRobot {
- DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+ DashboardPageRobot(this.tester)
+ : commonTestCases = CommonTestCases(tester),
+ dashboardMenuWidgetRobot = DashboardMenuWidgetRobot(tester);
final WidgetTester tester;
+ final DashboardMenuWidgetRobot dashboardMenuWidgetRobot;
late CommonTestCases commonTestCases;
Future isDashboardPage() async {
await commonTestCases.isSpecificPage();
}
+ Future confirmWalletTypeIsDisplayedCorrectly(
+ WalletType type, {
+ bool isHaven = false,
+ }) async {
+ final cryptoBalanceWidget =
+ tester.widget(find.byType(CryptoBalanceWidget));
+ final hasAccounts = cryptoBalanceWidget.dashboardViewModel.balanceViewModel.hasAccounts;
+
+ if (hasAccounts) {
+ final walletName = cryptoBalanceWidget.dashboardViewModel.name;
+ commonTestCases.hasText(walletName);
+ } else {
+ final walletName = walletTypeToString(type);
+ final assetName = isHaven ? '$walletName Assets' : walletName;
+ commonTestCases.hasText(assetName);
+ }
+ await commonTestCases.defaultSleepTime(seconds: 5);
+ }
+
void confirmServiceUpdateButtonDisplays() {
commonTestCases.hasValueKey('dashboard_page_services_update_button_key');
}
@@ -27,30 +51,40 @@ class DashboardPageRobot {
commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key');
}
- Future confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type,
- {bool isHaven = false}) async {
+ Future confirmRightCryptoAssetTitleDisplaysPerPageView(
+ WalletType type, {
+ bool isHaven = false,
+ }) async {
//Balance Page
- final walletName = walletTypeToString(type);
- final assetName = isHaven ? '$walletName Assets' : walletName;
- commonTestCases.hasText(assetName);
+ await confirmWalletTypeIsDisplayedCorrectly(type, isHaven: isHaven);
// Swipe to Cake features Page
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
- await commonTestCases.defaultSleepTime();
+ await swipeDashboardTab(false);
commonTestCases.hasText('Cake ${S.current.features}');
// Swipe back to balance
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
- await commonTestCases.defaultSleepTime();
+ await swipeDashboardTab(true);
// Swipe to Transactions Page
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
- await commonTestCases.defaultSleepTime();
+ await swipeDashboardTab(true);
commonTestCases.hasText(S.current.transactions);
// Swipe back to balance
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
- await commonTestCases.defaultSleepTime(seconds: 5);
+ await swipeDashboardTab(false);
+ await commonTestCases.defaultSleepTime(seconds: 3);
+ }
+
+ Future swipeDashboardTab(bool swipeRight) async {
+ await commonTestCases.swipeByPageKey(
+ key: 'dashboard_page_view_key',
+ swipeRight: swipeRight,
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future openDrawerMenu() async {
+ await commonTestCases.tapItemByKey('dashboard_page_wallet_menu_button_key');
+ await commonTestCases.defaultSleepTime();
}
Future navigateToBuyPage() async {
diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart
index b439e4791..e01b2df9c 100644
--- a/integration_test/robots/exchange_page_robot.dart
+++ b/integration_test/robots/exchange_page_robot.dart
@@ -123,7 +123,7 @@ class ExchangePageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${depositCurrency.name}_button_key',
'picker_scrollbar_key',
);
@@ -149,7 +149,7 @@ class ExchangePageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
diff --git a/integration_test/robots/new_wallet_page_robot.dart b/integration_test/robots/new_wallet_page_robot.dart
new file mode 100644
index 000000000..f8deb00ae
--- /dev/null
+++ b/integration_test/robots/new_wallet_page_robot.dart
@@ -0,0 +1,35 @@
+import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class NewWalletPageRobot {
+ NewWalletPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isNewWalletPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future enterWalletName(String walletName) async {
+ await commonTestCases.enterText(
+ walletName,
+ 'new_wallet_page_wallet_name_textformfield_key',
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future generateWalletName() async {
+ await commonTestCases.tapItemByKey(
+ 'new_wallet_page_wallet_name_textformfield_generate_name_button_key',
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future onNextButtonPressed() async {
+ await commonTestCases.tapItemByKey('new_wallet_page_confirm_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart
index b6805e9e0..18dc5fba4 100644
--- a/integration_test/robots/pin_code_widget_robot.dart
+++ b/integration_test/robots/pin_code_widget_robot.dart
@@ -24,13 +24,12 @@ class PinCodeWidgetRobot {
commonTestCases.hasValueKey('pin_code_button_0_key');
}
- Future pushPinButton(int index) async {
- await commonTestCases.tapItemByKey('pin_code_button_${index}_key');
- }
-
- Future enterPinCode(List pinCode, bool isFirstEntry) async {
+ Future enterPinCode(List pinCode, {int pumpDuration = 100}) async {
for (int pin in pinCode) {
- await pushPinButton(pin);
+ await commonTestCases.tapItemByKey(
+ 'pin_code_button_${pin}_key',
+ pumpDuration: pumpDuration,
+ );
}
await commonTestCases.defaultSleepTime();
diff --git a/integration_test/robots/pre_seed_page_robot.dart b/integration_test/robots/pre_seed_page_robot.dart
new file mode 100644
index 000000000..01be1249c
--- /dev/null
+++ b/integration_test/robots/pre_seed_page_robot.dart
@@ -0,0 +1,20 @@
+import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class PreSeedPageRobot {
+ PreSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isPreSeedPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future onConfirmButtonPressed() async {
+ await commonTestCases.tapItemByKey('pre_seed_page_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart
index 43a65095d..9d973061b 100644
--- a/integration_test/robots/restore_from_seed_or_key_robot.dart
+++ b/integration_test/robots/restore_from_seed_or_key_robot.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
@@ -70,6 +71,22 @@ class RestoreFromSeedOrKeysPageRobot {
await tester.pumpAndSettle();
}
+ Future enterBlockHeightForWalletRestore(String blockHeight) async {
+ await commonTestCases.enterText(
+ blockHeight,
+ 'wallet_restore_from_seed_blockheight_textfield_key',
+ );
+ await tester.pumpAndSettle();
+ }
+
+ Future chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType selectedType) async {
+ await commonTestCases.tapItemByKey('wallet_restore_from_seed_seedtype_picker_button_key');
+
+ await commonTestCases.defaultSleepTime();
+
+ await commonTestCases.tapItemByKey('picker_items_index_${selectedType.title}_button_key');
+ }
+
Future onPasteSeedPhraseButtonPressed() async {
await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
}
diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart
index b3cefc90c..cd1919609 100644
--- a/integration_test/robots/restore_options_page_robot.dart
+++ b/integration_test/robots/restore_options_page_robot.dart
@@ -14,14 +14,14 @@ class RestoreOptionsPageRobot {
}
void hasRestoreOptionsButton() {
- commonTestCases.hasValueKey('restore_options_from_seeds_button_key');
+ commonTestCases.hasValueKey('restore_options_from_seeds_or_keys_button_key');
commonTestCases.hasValueKey('restore_options_from_backup_button_key');
commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key');
commonTestCases.hasValueKey('restore_options_from_qr_button_key');
}
- Future navigateToRestoreFromSeedsPage() async {
- await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key');
+ Future navigateToRestoreFromSeedsOrKeysPage() async {
+ await commonTestCases.tapItemByKey('restore_options_from_seeds_or_keys_button_key');
await commonTestCases.defaultSleepTime();
}
diff --git a/integration_test/robots/security_and_backup_page_robot.dart b/integration_test/robots/security_and_backup_page_robot.dart
new file mode 100644
index 000000000..eb7c1bc87
--- /dev/null
+++ b/integration_test/robots/security_and_backup_page_robot.dart
@@ -0,0 +1,24 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class SecurityAndBackupPageRobot {
+ SecurityAndBackupPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ final CommonTestCases commonTestCases;
+
+ Future isSecurityAndBackupPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ void hasTitle() {
+ commonTestCases.hasText(S.current.security_and_backup);
+ }
+
+ Future navigateToShowKeysPage() async {
+ await commonTestCases.tapItemByKey('security_backup_page_show_keys_button_key');
+ }
+}
diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart
index 971556620..20cef948d 100644
--- a/integration_test/robots/send_page_robot.dart
+++ b/integration_test/robots/send_page_robot.dart
@@ -84,7 +84,7 @@ class SendPageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
@@ -117,7 +117,7 @@ class SendPageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${priority.title}_button_key',
'picker_scrollbar_key',
);
@@ -198,7 +198,7 @@ class SendPageRobot {
tester.printToConsole('Starting inner _handleAuth loop checks');
try {
- await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
+ await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500);
tester.printToConsole('Auth done');
await tester.pump();
@@ -213,6 +213,7 @@ class SendPageRobot {
}
Future handleSendResult() async {
+ await tester.pump();
tester.printToConsole('Inside handle function');
bool hasError = false;
@@ -287,6 +288,8 @@ class SendPageRobot {
// Loop to wait for the operation to commit transaction
await _waitForCommitTransactionCompletion();
+ await tester.pump();
+
await commonTestCases.defaultSleepTime(seconds: 4);
} else {
await commonTestCases.defaultSleepTime();
diff --git a/integration_test/robots/transactions_page_robot.dart b/integration_test/robots/transactions_page_robot.dart
new file mode 100644
index 000000000..40a49928f
--- /dev/null
+++ b/integration_test/robots/transactions_page_robot.dart
@@ -0,0 +1,286 @@
+import 'dart:async';
+
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
+import 'package:cake_wallet/utils/date_formatter.dart';
+import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
+import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
+import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/sync_status.dart';
+import 'package:cw_core/transaction_direction.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:intl/intl.dart';
+
+import '../components/common_test_cases.dart';
+
+class TransactionsPageRobot {
+ TransactionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isTransactionsPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future confirmTransactionsPageConstantsDisplayProperly() async {
+ await commonTestCases.defaultSleepTime();
+
+ final transactionsPage = tester.widget(find.byType(TransactionsPage));
+ final dashboardViewModel = transactionsPage.dashboardViewModel;
+ if (dashboardViewModel.status is SyncingSyncStatus) {
+ commonTestCases.hasValueKey('transactions_page_syncing_alert_card_key');
+ commonTestCases.hasText(S.current.syncing_wallet_alert_title);
+ commonTestCases.hasText(S.current.syncing_wallet_alert_content);
+ }
+
+ commonTestCases.hasValueKey('transactions_page_header_row_key');
+ commonTestCases.hasText(S.current.transactions);
+ commonTestCases.hasValueKey('transactions_page_header_row_transaction_filter_button_key');
+ }
+
+ Future confirmTransactionHistoryListDisplaysCorrectly(bool hasTxHistoryWhileSyncing) async {
+ // Retrieve the TransactionsPage widget and its DashboardViewModel
+ final transactionsPage = tester.widget(find.byType(TransactionsPage));
+ final dashboardViewModel = transactionsPage.dashboardViewModel;
+
+ // Define a timeout to prevent infinite loops
+ // Putting at one hour for cases like monero that takes time to sync
+ final timeout = Duration(hours: 1);
+ final pollingInterval = Duration(seconds: 2);
+ final endTime = DateTime.now().add(timeout);
+
+ while (DateTime.now().isBefore(endTime)) {
+ final isSynced = dashboardViewModel.status is SyncedSyncStatus;
+ final itemsLoaded = dashboardViewModel.items.isNotEmpty;
+
+ // Perform item checks if items are loaded
+ if (itemsLoaded) {
+ await _performItemChecks(dashboardViewModel);
+ } else {
+ // Verify placeholder when items are not loaded
+ _verifyPlaceholder();
+ }
+
+ // Determine if we should exit the loop
+ if (_shouldExitLoop(hasTxHistoryWhileSyncing, isSynced, itemsLoaded)) {
+ break;
+ }
+
+ // Pump the UI and wait for the next polling interval
+ await tester.pump(pollingInterval);
+ }
+
+ // After the loop, verify that both status is synced and items are loaded
+ if (!_isFinalStateValid(dashboardViewModel)) {
+ throw TimeoutException('Dashboard did not sync and load items within the allotted time.');
+ }
+ }
+
+ bool _shouldExitLoop(bool hasTxHistoryWhileSyncing, bool isSynced, bool itemsLoaded) {
+ if (hasTxHistoryWhileSyncing) {
+ // When hasTxHistoryWhileSyncing is true, exit when status is synced
+ return isSynced;
+ } else {
+ // When hasTxHistoryWhileSyncing is false, exit when status is synced and items are loaded
+ return isSynced && itemsLoaded;
+ }
+ }
+
+ void _verifyPlaceholder() {
+ commonTestCases.hasValueKey('transactions_page_placeholder_transactions_text_key');
+ commonTestCases.hasText(S.current.placeholder_transactions);
+ }
+
+ bool _isFinalStateValid(DashboardViewModel dashboardViewModel) {
+ final isSynced = dashboardViewModel.status is SyncedSyncStatus;
+ final itemsLoaded = dashboardViewModel.items.isNotEmpty;
+ return isSynced && itemsLoaded;
+ }
+
+ Future