CW-1135-add-support-for-bitbox-hww (#2476)

* chore: add ffi and ffigen dependencies to pubspec.yaml * refactor: replace method channel with FFI implementation for MWeb module * fix: correct `cd` command in build_mwebd.sh script * chore: add go.sum file with dependency checksums * fix: ensure ANDROID_NDK_VERSION is set before building and dynamically update NDK_BIN path * fix: update NDK_BIN path to dynamically detect platform in build_mwebd.sh script * refactor: remove unused mwebd dependencies and clean up CwMwebPlugin code * Update scripts/android/build_mwebd.sh Co-authored-by: cyan <cyjan@mrcyjanek.net> * refactor: replace iOS MoneroWallet framework with Mwebd.xcframework and clean up plugin implementation [skip-ci] * refactor: extract hardware wallet service abstraction * refactor: extend BitcoinHardwareWalletService from HardwareWalletService and override account retrieval method * feat: add BitBox hardware wallet support and implement manufacturer selection page with custom UI components * refactor: update manufacturer selection UI and integrate custom gradient styles for ManufacturerOptionTile widget * fix: mwebd ffi ios * feat: add BitBox hardware wallet support, update derivation paths, and refactor hardware wallet credential implementations * feat: add bitbox support and refactor the old ledger implementation * feat: add BitBox hardware wallet support and refactor hardware wallet service implementations * feat: refactor hardware wallet support to introduce abstraction for BitBox and Ledger; update device scanning, initialization, and UI integration * fix: BitBox hardware wallet ethereum transaction signing * chore: update BitBox Flutter path references and include build steps in CI workflows * chore: update CI workflows to include `--dont-install` flag for BitBox Flutter builds * refactor: replace if-else logic with switch-case for HardwareWalletViewModel factory * feat: refactor hardware wallet services to introduce shared interface, improve ledger and bitbox implementations, and support additional transaction signing operations * feat: add SVG assets for hardware wallet manufacturers (Foundation, Keystone, SeedSigner) * feat: redesign hardware wallet restoration flow with improved routing, UI updates, and manufacturer-specific device selection handling * fix: validate required BIP353 fields in transaction builder to prevent potential null value errors * feat: improve BitBox transaction signing logs with detailed PSBT information * refactor: update MWebFfi class to use non-nullable `lib` and improve address debug logging * feat: add BitBox coin type constant and improve hardware wallet service extensibility * chore: remove unused iOS/Android build scripts for mwebd * feat: add BitBox SVG asset and update localization for hardware wallet connection instructions * feat: update manufacturer tags and add Toast Easter egg for device selection interactions * refactor: replace LedgerViewModel with HardwareWalletViewModel across providers and improve hardware wallet handling * refactor: replace mweb.so with libmweb.so, use sync methods in MWebFfi, and update iOS/Android frameworks/scripts accordingly * feat: add Ledger Nano X SVG asset to hardware wallet resources * chore: unify mwebd build process in workflows, remove unused gomobile init command * refactor: remove redundant print statement and unnecessary whitespace * chore: add `gomobile init` to Android PR test build workflow * chore: update Android PR test build workflow and add file existence check in `build_bitbox_flutter.sh` * chore: override `ledger_usb_plus` dependency in pubspec_base.yaml * feat: add verbose logging for BitBox connection and initialization process * Update lib/view_model/hardware_wallet/bitbox_view_model.dart [skip ci] * CW-1206-refactor-hardware-wallets-code (#2507) * feat: add support for new hardware wallet types (Cupcake, Coldcard, SeedSigner) and improve QR Code handling - Introduced new hardware wallet types (Cupcake, Coldcard, SeedSigner) with corresponding assets and localization updates. - Updated QR Code format selection for hardware wallets with new formats (BCUR, BBQR) specific to devices. - Improved hardware wallet restoration flow to include airgapped wallet handling via QR Code scanning. - Refactored routing, view models, and dependency injection to accommodate new hardware wallet types. - Enhanced UI for device manufacturer selection and QR Code format dialogs. * feat: update hardware wallet assets and restoration flow * feat: add changing airgapped wallet type * feat: add Cupcake Man SVG asset to hardware wallet resources * refactor: update Litecoin receive options to require wallet parameter and handle hardware wallets * fix: debugging keystone integration feat: support for zpub view only restore * fix: seed signer restore * fix: disable bitbox restoring (for now) [skip-ci] * refactor: rename `isCupcake` to `isBitcoinViewOnly` for clarity [skip ci] --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * refactor: standardize hardware wallet connection handling across wallet types --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Konstantin Ullrich committed Oct 1, 2025 at 17:20 UTC e4672c75b79ecca6ddbbc4983d0dcc1de5a4163c
117 files changed +2048 -723
.github/workflows/pr_test_build_android.yml
+8
@@ -251,6 +251,14 @@ jobs:
251 ./build_monero_all.sh
252 popd
253
254 + - name: Build Bitbox Flutter
255 + run: |
256 + set -x -e
257 + pushd scripts
258 + gomobile init;
259 + ./build_bitbox_flutter.sh
260 + popd
261 +
262 - name: Install Flutter dependencies
263 run: |
264 flutter pub get
.github/workflows/pr_test_build_linux.yml
+7
@@ -228,6 +228,13 @@ jobs:
228 ./build_monero_all.sh
229 popd
230
231 + - name: Build Bitbox Flutter
232 + run: |
233 + set -x -e
234 + pushd scripts
235 + ./build_bitbox_flutter.sh --dont-install
236 + popd
237 +
238 - name: Install Flutter dependencies
239 run: |
240 flutter pub get
assets/images/hardware_wallet/bitbox_man.svg new
+8
@@ -0,0 +1,8 @@
1 +<svg width="90" height="25" viewBox="0 0 90 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<mask id="mask0_7843_12605" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="1" width="90" height="23">
3 +<path d="M89.7959 1.5H0V23.5H89.7959V1.5Z" fill="white"/>
4 +</mask>
5 +<g mask="url(#mask0_7843_12605)">
6 +<path fill-rule="evenodd" clip-rule="evenodd" d="M38.989 15.6797C38.989 18.2977 37.1944 19.9022 34.3442 19.9022H26.8069V4.91211H33.9429C36.582 4.91211 38.4611 6.31966 38.4611 8.7758C38.5227 10.2635 37.5659 11.603 36.1386 12.0272C37.9755 12.5972 38.989 13.8006 38.989 15.6797ZM33.2885 17.6221C35.083 17.6221 36.0331 16.8198 36.0331 15.4474C36.0331 14.0752 35.0619 13.2517 33.2885 13.2517H29.7697V17.6221H33.2885ZM29.7697 10.9504H33.2251C34.7453 10.9504 35.5898 10.2467 35.5898 9.00802C35.5898 7.76933 34.8086 7.19239 33.2463 7.19239H29.7627L29.7697 10.9504ZM43.6478 4.91211V7.65677H40.9032V4.91211H43.6478ZM43.6478 9.11357V19.9022H40.9032V9.11357H43.6478ZM52.3111 19.7545C51.5297 20.0226 50.7091 20.1581 49.8831 20.1556C47.7718 20.1556 46.8217 19.1632 46.8217 17.1154V11.2037H44.9005V9.13463H46.8217V5.77774H49.5664V9.13463H52.1422V11.2459H49.5664V16.8197C49.5664 17.5586 49.883 17.812 50.6431 17.812C51.101 17.7875 51.5545 17.7096 51.9943 17.5798L52.3111 19.7545ZM66.2173 15.6797C66.2173 18.2977 64.4228 19.9022 61.5725 19.9022H54.0071V4.91211H61.1432C63.7823 4.91211 65.6612 6.31966 65.6612 8.7758C65.723 10.2635 64.7662 11.603 63.3389 12.0272C65.1758 12.5972 66.2173 13.8006 66.2173 15.6797ZM56.9699 10.9504H60.4324C61.9525 10.9504 62.7971 10.2467 62.7971 9.00802C62.7971 7.76933 61.9947 7.19239 60.4465 7.19239H56.9629L56.9699 10.9504ZM60.4887 17.6221C62.2833 17.6221 63.2333 16.8198 63.2333 15.4474C63.2333 14.0752 62.2622 13.2517 60.4887 13.2517H56.9699V17.6221H60.4887ZM78.716 14.4974C78.716 17.9177 76.3726 20.24 73.086 20.24C69.7994 20.24 67.4559 17.9177 67.4559 14.4974C67.4559 11.0771 69.7994 8.7547 73.086 8.7547C76.3726 8.7547 78.716 11.0771 78.716 14.4974ZM70.271 14.4974C70.271 16.6087 71.3899 17.9177 73.086 17.9177C74.7821 17.9177 75.9011 16.5876 75.9011 14.4974C75.9011 12.4072 74.7821 11.0771 73.086 11.0771C71.3899 11.0771 70.271 12.4846 70.271 14.4974ZM86.5489 19.9022L84.1209 16.2075L81.6719 19.9022H78.716L82.6008 14.244L79.075 9.11357H82.0519L84.1631 12.2804L86.2322 9.11357H89.181L85.7044 14.1384L89.561 19.9022H86.5489ZM0 18.1359V13.0218L4.13093 11.2452V3.28523L8.22737 1.5V14.5569L0 18.1359ZM18.5159 18.179L10.2799 14.6V1.54315L14.3849 3.33696V11.2883L18.5159 13.0735V18.179ZM9.27084 16.3507L17.4983 19.921L9.26223 23.5L1.06071 19.9383L9.27084 16.3507Z" fill="#D7E2F7"/>
7 +</g>
8 +</svg>
assets/images/hardware_wallet/coldcard_man.svg new
+19
@@ -0,0 +1,19 @@
1 +<svg width="84" height="25" viewBox="0 0 84 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<g clip-path="url(#clip0_7540_12880)">
3 +<path d="M29.2774 18.4306H22.9156C22.0991 18.4306 21.4355 17.7666 21.4355 16.9516V7.65171C21.4355 7.02755 21.9425 6.521 22.5671 6.521C23.1917 6.521 23.6986 7.02755 23.6986 7.65171V16.1691H29.2774C29.902 16.1691 30.4089 16.6757 30.4089 17.2998C30.4089 17.924 29.902 18.4306 29.2774 18.4306Z" fill="#D7E2F7"/>
4 +<path d="M16.7993 18.5002H13.6066C13.3061 18.5002 13.0191 18.3808 12.8064 18.1692L11.0503 16.4143C10.8384 16.2026 10.719 15.915 10.719 15.6147V9.38582C10.719 9.0855 10.8384 8.79785 11.0503 8.58618L12.8064 6.83132C13.0191 6.61965 13.307 6.50024 13.6066 6.50024H16.7993C17.0998 6.50024 17.3868 6.61965 17.5995 6.83132L19.3556 8.58618C19.5674 8.79785 19.6869 9.0855 19.6869 9.38582V15.6147C19.6869 15.915 19.5674 16.2026 19.3556 16.4143L17.5995 18.1692C17.3877 18.3808 17.0998 18.5002 16.7993 18.5002ZM14.0755 16.2388H16.3304L17.4239 15.1461V9.85438L16.3304 8.76167H14.0755L12.982 9.85438V15.1461L14.0755 16.2388Z" fill="#D7E2F7"/>
5 +<path d="M33.2873 18.5002C32.6627 18.5002 32.1558 17.9937 32.1558 17.3695V7.63095C32.1567 7.0068 32.6636 6.50024 33.2882 6.50024H38.237C38.5375 6.50024 38.8245 6.61965 39.0372 6.83132L40.7933 8.58618C41.0051 8.79785 41.1246 9.0855 41.1246 9.38582V15.6147C41.1246 15.915 41.0051 16.2026 40.7933 16.4143L39.0372 18.1692C38.8254 18.3808 38.5375 18.5002 38.237 18.5002H33.2873ZM34.4188 16.2388H37.7672L38.8607 15.1461V9.85438L37.7672 8.76167H34.4188V16.2388Z" fill="#D7E2F7"/>
6 +<path d="M81.1125 18.5002H76.1636C75.5391 18.5002 75.0323 17.9937 75.0323 17.3695V7.63095C75.0323 7.0068 75.5391 6.50024 76.1636 6.50024H81.1125C81.4131 6.50024 81.7 6.61965 81.9127 6.83132L83.6691 8.58618C83.8807 8.79785 84.0002 9.0855 84.0002 9.38582V15.6147C84.0002 15.915 83.8807 16.2026 83.6691 16.4143L81.9127 18.1692C81.7011 18.3808 81.4131 18.5002 81.1125 18.5002ZM77.2954 16.2388H80.6434L81.7371 15.1461V9.85438L80.6434 8.76167H77.2954V16.2388Z" fill="#D7E2F7"/>
7 +<path d="M6.08029 18.5002H2.88763C2.588 18.5002 2.30014 18.3808 2.08742 18.1692L0.331307 16.4143C0.119488 16.2026 0 15.915 0 15.6147V9.38582C0 9.0855 0.119488 8.79785 0.331307 8.58618L2.08742 6.83132C2.30014 6.61965 2.588 6.50024 2.88763 6.50024H6.07942C6.37991 6.50024 6.6678 6.61965 6.8796 6.83132L8.63569 8.58618C9.07745 9.02761 9.07745 9.74402 8.63569 10.1855C8.19398 10.6269 7.47704 10.6269 7.03533 10.1855L5.61049 8.76167H3.35562L2.26213 9.85438V15.1461L3.35562 16.2388H5.61049L7.03533 14.815C7.47704 14.3736 8.19398 14.3736 8.63569 14.815C9.07745 15.2565 9.07745 15.9729 8.63569 16.4143L6.8796 18.1692C6.6678 18.3808 6.37991 18.5002 6.07942 18.5002H6.08029Z" fill="#D7E2F7"/>
8 +<path d="M48.9556 18.5002H45.7639C45.4633 18.5002 45.1764 18.3808 44.9637 18.1692L43.2075 16.4143C42.9957 16.2026 42.8762 15.915 42.8762 15.6147V9.38582C42.8762 9.0855 42.9957 8.79785 43.2075 8.58618L44.9637 6.83132C45.1755 6.61965 45.4633 6.50024 45.763 6.50024H48.9548C49.2553 6.50024 49.5431 6.61965 49.7549 6.83132L51.511 8.58618C51.9528 9.02761 51.9528 9.74402 51.511 10.1855C51.0693 10.6269 50.3524 10.6269 49.9106 10.1855L48.4858 8.76167H46.231L45.1375 9.85438V15.1461L46.231 16.2388H48.4858L49.9106 14.815C50.3524 14.3736 51.0693 14.3736 51.511 14.815C51.9528 15.2565 51.9528 15.9729 51.511 16.4143L49.7549 18.1692C49.5431 18.3808 49.2553 18.5002 48.9548 18.5002H48.9556Z" fill="#D7E2F7"/>
9 +<path d="M62.2305 8.58618L60.4747 6.83132C60.263 6.61965 59.975 6.50024 59.6745 6.50024H56.4825C56.182 6.50024 55.894 6.61965 55.6823 6.83132L53.9263 8.58618C53.7145 8.79785 53.595 9.0855 53.595 9.38582V17.3695C53.595 17.9937 54.1019 18.5002 54.7267 18.5002C55.3512 18.5002 55.858 17.9937 55.858 17.3695V14.9544H60.3001V17.3695C60.3001 17.9937 60.8069 18.5002 61.4314 18.5002C62.056 18.5002 62.5627 17.9937 62.5627 17.3695V9.38582C62.5627 9.0855 62.4432 8.79785 62.2316 8.58618H62.2305ZM55.858 12.692V9.85348L56.9516 8.76076H59.2065L60.3001 9.85348V12.692H55.858Z" fill="#D7E2F7"/>
10 +<path d="M72.6886 13.2085C73.071 12.8268 73.281 12.3193 73.281 11.7802V9.75397C73.281 9.21395 73.071 8.70739 72.6886 8.32566L71.4543 7.09183C71.0719 6.7101 70.5641 6.50024 70.0246 6.50024H65.445C64.8205 6.50024 64.3137 7.0068 64.3137 7.63095V17.3695C64.3137 17.9937 64.8205 18.5002 65.445 18.5002C66.0701 18.5002 66.5768 17.9937 66.5768 17.3695V15.0339H68.5879L71.3021 18.1022C71.5257 18.3546 71.8372 18.4839 72.1503 18.4839C72.4164 18.4839 72.6843 18.3908 72.8986 18.2008C73.3666 17.7874 73.4114 17.0728 72.9974 16.6052L71.2443 14.6233C71.3174 14.5681 71.3877 14.5093 71.4543 14.4433L72.6886 13.2094V13.2085ZM66.5757 8.76167H69.9243L71.0179 9.85438V11.6798L69.9243 12.7725H66.5757V8.76167Z" fill="#D7E2F7"/>
11 +<path d="M82.997 17.5253L83.2081 17.7361L83.1421 17.8021L83.0216 17.6818H82.967L82.8749 17.7741L83.1536 18.0527H83.2081L83.3085 17.9523L83.3718 18.0156L83.0434 18.344L82.9801 18.2806L83.0805 18.1802V18.1259L82.8018 17.8474L82.709 17.9396V17.9939L82.8296 18.1142L82.7636 18.1802L82.5525 17.9695L82.9981 17.5244L82.997 17.5253Z" fill="#D7E2F7"/>
12 +<path d="M83.5998 17.5771L83.2725 17.3898L83.2523 17.4097L83.5256 17.6829H83.5801L83.6227 17.6403L83.6859 17.7037L83.4934 17.8964L83.4296 17.833L83.4618 17.8014V17.7471L83.2098 17.4956H83.1558L83.1241 17.5273L83.0603 17.464L83.2507 17.274L83.5594 17.4567L83.3767 17.1483L83.5654 16.9592L83.6292 17.0226L83.597 17.0542V17.1085L83.849 17.3599H83.903L83.9352 17.3283L83.9985 17.3916L83.8054 17.5843L83.7421 17.5209L83.7836 17.4794V17.4251L83.5103 17.1519L83.4912 17.1709L83.6778 17.4992L83.5998 17.5771Z" fill="#D7E2F7"/>
13 +</g>
14 +<defs>
15 +<clipPath id="clip0_7540_12880">
16 +<rect width="84" height="12" fill="white" transform="translate(0 6.5)"/>
17 +</clipPath>
18 +</defs>
19 +</svg>
assets/images/hardware_wallet/cupcake_man.svg new
+4
@@ -0,0 +1,4 @@
1 +<svg width="90" height="25" viewBox="0 0 90 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M51.504 11.0566C52.2326 11.0566 52.8799 11.2416 53.4454 11.6113C54.0109 11.9811 54.4628 12.492 54.7999 13.1445C55.1369 13.7969 55.3048 14.5418 55.3048 15.3789C55.3048 16.2163 55.1313 16.9618 54.7833 17.6143C54.4462 18.2558 53.9838 18.7611 53.3966 19.1309C52.8203 19.5005 52.1681 19.6855 51.4396 19.6855C50.7654 19.6855 50.1781 19.5387 49.6779 19.2451C49.2817 18.9986 48.9615 18.6836 48.715 18.3018V22.5889H47.0841V11.3174H48.6984V12.6855C48.7529 12.5849 48.8123 12.4871 48.8781 12.3936C49.1716 11.9804 49.5416 11.6542 49.9874 11.415C50.444 11.1759 50.9496 11.0567 51.504 11.0566ZM33.0304 7.5C33.933 7.5 34.7548 7.69067 35.4943 8.07129C36.2444 8.45187 36.8749 8.99549 37.3859 9.70215L36.2443 10.8438C35.8637 10.2458 35.3963 9.78896 34.8419 9.47363C34.2982 9.1583 33.6889 9.00102 33.0148 9.00098C32.2536 9.00098 31.5738 9.20217 30.9757 9.60449C30.3885 9.99599 29.9261 10.5346 29.589 11.2197C29.252 11.9047 29.0832 12.6982 29.0831 13.6006C29.0831 14.4814 29.2519 15.2699 29.589 15.9658C29.9261 16.6509 30.3885 17.1953 30.9757 17.5977C31.5738 17.989 32.2537 18.1846 33.0148 18.1846C33.6888 18.1845 34.3031 18.0271 34.8575 17.7119C35.4121 17.3857 35.8745 16.9289 36.2443 16.3418L37.3859 17.4834C36.8965 18.1684 36.2548 18.7072 35.4611 19.0986C34.6782 19.49 33.8571 19.6855 32.9982 19.6855C32.1825 19.6855 31.4266 19.533 30.7306 19.2285C30.0455 18.924 29.4477 18.4998 28.9366 17.9561C28.4255 17.4014 28.028 16.7541 27.7452 16.0146C27.4626 15.2753 27.3214 14.4704 27.3214 13.6006C27.3214 12.7198 27.4625 11.9093 27.7452 11.1699C28.0389 10.4305 28.4412 9.7888 28.9523 9.24512C29.4634 8.69049 30.0678 8.26153 30.7638 7.95703C31.4596 7.65266 32.215 7.50005 33.0304 7.5ZM39.9425 16.1621C39.9425 16.8581 40.095 17.391 40.3995 17.7607C40.7148 18.1195 41.1441 18.2988 41.6876 18.2988C42.0898 18.2988 42.4543 18.2014 42.7804 18.0059C43.1066 17.7993 43.3793 17.5218 43.5968 17.1738C43.7141 16.9951 43.8056 16.8012 43.8741 16.5938V11.3174H45.505V19.4248H43.8898V18.2393C43.647 18.6634 43.3209 19.0045 42.9113 19.2617C42.4437 19.5444 41.8945 19.6855 41.2638 19.6855C40.3721 19.6855 39.6542 19.3976 39.1105 18.8213C38.5776 18.2449 38.3116 17.4127 38.3116 16.3252V11.3174H39.9425V16.1621ZM60.1154 11.0566C61.5071 11.0568 62.6056 11.5839 63.4103 12.6387L62.3175 13.6826C62.0783 13.2911 61.7732 12.9918 61.4034 12.7852C61.0338 12.5678 60.6151 12.459 60.1476 12.459C59.419 12.459 58.8203 12.7259 58.3527 13.2588C57.8962 13.7807 57.6681 14.4875 57.6681 15.3789C57.6681 16.2489 57.9016 16.9563 58.3693 17.5C58.8368 18.0328 59.4405 18.2988 60.1798 18.2988C60.6365 18.2988 61.0446 18.1958 61.4034 17.9893C61.7622 17.7718 62.0722 17.4618 62.3331 17.0596L63.3937 18.1035C63.0131 18.6037 62.5402 18.9956 61.9747 19.2783C61.4093 19.5502 60.7838 19.6855 60.0988 19.6855C59.294 19.6855 58.5762 19.5006 57.9454 19.1309C57.3257 18.7612 56.8365 18.2557 56.4777 17.6143C56.1297 16.9618 55.9552 16.2163 55.9552 15.3789C55.9552 14.5309 56.1345 13.786 56.4933 13.1445C56.8522 12.492 57.3422 11.9811 57.962 11.6113C58.5927 11.2416 59.3107 11.0566 60.1154 11.0566ZM67.2765 11.0566C67.994 11.0567 68.614 11.192 69.1359 11.4639C69.6686 11.7357 70.0815 12.122 70.3751 12.6221C70.6687 13.1114 70.8156 13.6939 70.8156 14.3682V19.4248H69.3312V18.3633C69.0701 18.73 68.7391 19.0302 68.3361 19.2617C67.8359 19.5444 67.2542 19.6855 66.5909 19.6855C66.0146 19.6855 65.5036 19.5768 65.0577 19.3594C64.6227 19.131 64.2795 18.8212 64.0294 18.4297C63.7794 18.0382 63.6544 17.592 63.6544 17.0918C63.6545 16.2545 63.9757 15.6024 64.6173 15.1348C65.2698 14.6563 66.145 14.417 67.2433 14.417H69.2823C69.2823 13.7862 69.0917 13.2915 68.7111 12.9326C68.3305 12.5629 67.8089 12.3779 67.1456 12.3779C66.2756 12.3779 65.514 12.7364 64.8615 13.4541L63.9161 12.4434C64.8623 11.519 65.9824 11.0566 67.2765 11.0566ZM83.3322 11.0566C84.0716 11.0566 84.7237 11.2416 85.2892 11.6113C85.8545 11.9701 86.2951 12.4699 86.6105 13.1113C86.9367 13.7528 87.1007 14.4872 87.1007 15.3135V15.9014H81.0333C81.0785 16.2732 81.1698 16.6101 81.3097 16.9121C81.5271 17.347 81.8314 17.6951 82.2228 17.9561C82.6251 18.2062 83.1037 18.332 83.6583 18.332C84.1476 18.332 84.5882 18.2233 84.9796 18.0059C85.3711 17.7884 85.7191 17.4671 86.0236 17.043L86.9865 18.0703C86.5623 18.6141 86.0561 19.0221 85.4689 19.2939C84.8926 19.5549 84.2622 19.6855 83.5773 19.6855C82.729 19.6855 81.9835 19.5006 81.3419 19.1309C80.7112 18.7611 80.2164 18.2501 79.8575 17.5977C79.4987 16.9452 79.3195 16.2054 79.3195 15.3789C79.3195 14.5527 79.4932 13.8134 79.8409 13.1611C80.1998 12.5086 80.6784 11.9967 81.2765 11.627C81.8855 11.2463 82.5709 11.0566 83.3322 11.0566ZM73.9122 14.9502L77.5011 11.3174H79.5402L75.588 15.2207L79.9962 19.4248H77.8107L73.9122 15.6289V19.4248H72.2804V7.67969H73.9122V14.9502ZM67.3898 15.5752C66.7375 15.5753 66.2262 15.7059 65.8566 15.9668C65.498 16.2168 65.3186 16.57 65.3185 17.0264C65.3185 17.4395 65.4597 17.7771 65.7423 18.0381C66.0359 18.2882 66.4278 18.4131 66.9171 18.4131C67.3846 18.4131 67.8033 18.31 68.173 18.1035C68.5427 17.8969 68.8421 17.6138 69.0704 17.2549C69.1544 17.1229 69.2249 16.9836 69.2823 16.8369V15.5752H67.3898ZM51.1945 12.4434C50.716 12.4434 50.2917 12.573 49.922 12.834C49.5522 13.095 49.2586 13.4601 49.0411 13.9277C48.8238 14.3844 48.715 14.9175 48.715 15.5264V16.5576C48.7877 16.7788 48.8848 16.9847 49.0089 17.1738C49.2373 17.5217 49.531 17.7993 49.8898 18.0059C50.2594 18.2015 50.6619 18.2988 51.0968 18.2988C51.597 18.2988 52.0318 18.1739 52.4015 17.9238C52.782 17.6738 53.0757 17.3314 53.2823 16.8965C53.4998 16.4506 53.6085 15.9444 53.6085 15.3789C53.6085 14.8027 53.5055 14.2972 53.299 13.8623C53.1032 13.4165 52.8257 13.0685 52.4669 12.8184C52.108 12.5682 51.6838 12.4434 51.1945 12.4434ZM83.3156 12.4102C82.8589 12.4102 82.4565 12.5303 82.1085 12.7695C81.7607 13.0088 81.4888 13.3511 81.2931 13.7969C81.1694 14.0785 81.0857 14.3885 81.0402 14.7266H85.4572C85.3924 14.0979 85.2081 13.581 84.8986 13.1768C84.5179 12.6656 83.9898 12.4102 83.3156 12.4102Z" fill="#D7E2F7"/>
3 +<path d="M5.61206 15.0771C5.97989 15.0772 6.29433 15.3419 6.35815 15.7041L7.83472 24.1113C7.91629 24.5752 7.5586 25 7.08765 25H5.02124C4.67819 24.9999 4.37729 24.7694 4.28882 24.4375L2.04663 16.0312C1.91824 15.5495 2.28065 15.0772 2.77905 15.0771H5.61206ZM13.2664 15.0771C13.7375 15.0771 14.0948 15.5028 14.0134 15.9668L12.5369 24.373C12.4731 24.7358 12.1586 24.9998 11.7908 25H9.52612C9.15817 25 8.84279 24.7354 8.77905 24.373L7.30347 15.9668C7.2216 15.5029 7.57862 15.0774 8.04956 15.0771H13.2664ZM18.5378 15.0771C19.0357 15.0771 19.3987 15.55 19.2703 16.0312L17.0271 24.4375C16.9386 24.769 16.6384 25 16.2947 25H14.2283C13.7574 24.9998 13.4009 24.5752 13.4822 24.1113L14.9578 15.7051C15.0215 15.3421 15.3368 15.0772 15.7048 15.0771H18.5378ZM20.3054 9.94629C20.8637 9.94629 21.316 10.3988 21.3162 10.957V12.9541C21.316 13.5123 20.8637 13.9648 20.3054 13.9648H1.0105C0.452352 13.9647 -0.000130405 13.5123 -0.000244141 12.9541V10.957C-0.000127057 10.3989 0.452354 9.94643 1.0105 9.94629H20.3054ZM18.5779 5.01855C18.7189 5.19495 18.8005 5.41639 18.8005 5.65039V8.02539C18.8005 8.58391 18.3483 9.03705 17.7898 9.03711H3.52612C2.96757 9.03711 2.51538 8.58395 2.51538 8.02539V6.0293C2.51538 5.47074 2.96757 5.01855 3.52612 5.01855H18.5779ZM18.2332 4.74121C18.3696 4.80793 18.4874 4.90286 18.5789 5.01758H9.63354L7.65894 4.05469C7.15716 3.80997 6.94851 3.20499 7.19312 2.70312L8.51147 0L18.2332 4.74121Z" fill="#D7E2F7"/>
4 +</svg>
assets/images/hardware_wallet/device_bitbox.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M67.0791 26C72.5581 26 77 30.4419 77 35.9209V39H90V58H77V61.0791C77 66.5581 72.5581 71 67.0791 71H15.9209L15.4102 70.9873C10.1684 70.7216 6 66.3869 6 61.0791V35.9209C6 30.6131 10.1684 26.2784 15.4102 26.0127L15.9209 26H67.0791ZM15.9209 33C14.3582 33 13.082 34.2273 13.0039 35.7705L13 35.9209V61.0791C13 62.6921 14.3079 64 15.9209 64H67.0791C68.6921 64 70 62.6921 70 61.0791V35.9209C70 34.3079 68.6921 33 67.0791 33H15.9209Z" fill="#D7E2F7"/>
3 +</svg>
assets/images/hardware_wallet/device_ledger_nano_x.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M7.76001 86.33H72.556C81.674 86.33 89.24 78.764 89.24 69.549C89.24 65.281 87.688 61.207 84.875 58.297L37.345 10.67L14.162 33.8531L33.271 52.865H7.76001V86.33ZM15.035 79.055V60.14H72.556C77.697 60.14 81.965 64.408 81.965 69.549C81.965 74.787 77.697 79.055 72.556 79.055H15.035ZM24.444 33.8531L37.345 20.952L69.161 52.865H66.251H43.553L24.444 33.8531ZM32.592 34.1441C32.592 36.8601 34.823 39.2851 37.733 39.2851C40.546 39.2851 42.777 36.8601 42.777 34.1441C42.777 31.3311 40.546 29.1001 37.733 29.1001C34.823 29.1001 32.592 31.3311 32.592 34.1441ZM66.93 69.549C66.93 72.265 69.161 74.69 72.071 74.69C74.884 74.69 77.115 72.265 77.115 69.549C77.115 66.736 74.884 64.505 72.071 64.505C69.161 64.505 66.93 66.736 66.93 69.549Z" fill="#D7E2F7"/>
3 +</svg>
assets/images/hardware_wallet/device_qr.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M2 7.33333V2H7.33333V7.33333H2ZM3.33333 6H6V3.33333H3.33333V6ZM2 14V8.66667H7.33333V14H2ZM3.33333 12.6667H6V10H3.33333V12.6667ZM8.66667 7.33333V2H14V7.33333H8.66667ZM10 6H12.6667V3.33333H10V6ZM12.6667 14V12.6667H14V14H12.6667ZM8.66667 10V8.66667H10V10H8.66667ZM10 11.3333V10H11.3333V11.3333H10ZM8.66667 12.6667V11.3333H10V12.6667H8.66667ZM10 14V12.6667H11.3333V14H10ZM11.3333 12.6667V11.3333H12.6667V12.6667H11.3333ZM11.3333 10V8.66667H12.6667V10H11.3333ZM12.6667 11.3333V10H14V11.3333H12.6667Z" fill="#91B0FF"/>
3 +</svg>
assets/images/hardware_wallet/foundation_man.svg new
+48
@@ -0,0 +1,48 @@
1 +<svg width="153" height="25" viewBox="0 0 153 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<g clip-path="url(#clip0_7559_11731)">
3 +<g clip-path="url(#clip1_7559_11731)">
4 +<g opacity="0.5">
5 +<path opacity="0.5" d="M10.9595 17.6824H2.06372L10.9595 1.86987V17.6824Z" fill="#D7E2F7"/>
6 +</g>
7 +<path d="M2.88303 18.2907C2.0483 18.2824 1.87099 18.6798 2.38383 19.3156C4.4161 21.8481 7.50134 23.5007 10.9903 23.5007C14.4793 23.5007 17.5645 21.8481 19.5967 19.3156C20.1096 18.6798 19.9295 18.2852 19.0975 18.2907C15.0412 18.3098 2.88303 18.2907 2.88303 18.2907Z" fill="url(#paint0_radial_7559_11731)"/>
8 +<path d="M1.9009 16.5796C1.48626 17.3059 1.0607 17.2648 0.766095 16.5029C-0.401439 13.469 -0.28414 9.95817 1.4617 6.9215C3.20755 3.88483 6.17275 2.0294 9.37255 1.52512C10.1773 1.39904 10.431 1.75259 10.0054 2.47339C10.0054 2.47339 3.90862 13.0442 1.9009 16.5796Z" fill="url(#paint1_radial_7559_11731)"/>
9 +<path d="M11.9725 2.47351C11.5524 1.74998 11.8007 1.39917 12.6054 1.52524C15.8052 2.02679 18.7704 3.88497 20.5163 6.92163C22.2621 9.95831 22.3767 13.4691 21.2119 16.503C20.9172 17.2677 20.489 17.3088 20.0771 16.5798C20.0771 16.5798 14.013 5.9898 11.9725 2.47351Z" fill="url(#paint2_radial_7559_11731)"/>
10 +<path d="M2.90647 18.2907C2.07174 18.2824 1.89442 18.6798 2.40726 19.3156C4.43953 21.8481 7.52479 23.5007 11.0137 23.5007C14.5027 23.5007 17.5879 21.8481 19.6202 19.3156C20.1331 18.6798 19.953 18.2852 19.121 18.2907C15.0646 18.3098 2.90647 18.2907 2.90647 18.2907Z" fill="#D7E2F7"/>
11 +<path d="M1.92433 16.5796C1.5097 17.3059 1.08414 17.2648 0.789532 16.5029C-0.378001 13.469 -0.260702 9.95817 1.48514 6.9215C3.23098 3.88483 6.19619 2.0294 9.396 1.52512C10.2007 1.39904 10.4544 1.75259 10.0288 2.47339C10.0288 2.47339 3.93205 13.0442 1.92433 16.5796Z" fill="#D7E2F7"/>
12 +<path d="M11.996 2.47351C11.5759 1.74998 11.8241 1.39917 12.6289 1.52524C15.8286 2.02679 18.7938 3.88497 20.5397 6.92163C22.2856 9.95831 22.4001 13.4691 21.2353 16.503C20.9407 17.2677 20.5124 17.3088 20.1005 16.5798C20.1005 16.5798 14.0365 5.9898 11.996 2.47351Z" fill="#D7E2F7"/>
13 +<path d="M36.4474 8.56001C36.4474 8.44238 36.5461 8.33459 36.6745 8.33459H41.5533C41.6817 8.33459 41.7804 8.44238 41.7804 8.56001V9.54004C41.7804 9.65767 41.6817 9.76547 41.5533 9.76547H37.9979V12H40.9607C41.0792 12 41.1878 12.1078 41.1878 12.2253V13.2055C41.1878 13.323 41.0792 13.4308 40.9607 13.4308H37.9979V16.4396C37.9979 16.5572 37.8893 16.6649 37.7708 16.6649H36.6646C36.5363 16.6649 36.4375 16.5572 36.4375 16.4396V8.56001H36.4474Z" fill="#D7E2F7"/>
14 +<path d="M51.3117 8.21692C53.7115 8.21692 55.6275 10.1378 55.6275 12.5193C55.6275 14.9009 53.7115 16.7923 51.3117 16.7923C48.9118 16.7923 47.0057 14.9009 47.0057 12.5193C47.0057 10.1378 48.9118 8.21692 51.3117 8.21692ZM51.3117 15.234C52.8227 15.234 54.0671 14.009 54.0671 12.5095C54.0671 11.01 52.8227 9.75561 51.3117 9.75561C49.8006 9.75561 48.5662 11.01 48.5662 12.5095C48.5563 14.009 49.7908 15.234 51.3117 15.234Z" fill="#D7E2F7"/>
15 +<path d="M61.4539 8.56001C61.4539 8.44238 61.5626 8.33459 61.6811 8.33459H62.8168C62.9453 8.33459 63.044 8.44238 63.044 8.56001V13.4308C63.044 14.4403 63.7353 15.2439 64.7822 15.2439C65.8291 15.2439 66.5302 14.4501 66.5302 13.4504V8.56977C66.5302 8.45221 66.629 8.34439 66.7574 8.34439H67.8931C68.0117 8.34439 68.1203 8.45221 68.1203 8.56977V13.519C68.1203 15.3321 66.6685 16.7924 64.7723 16.7924C62.886 16.7924 61.4342 15.3321 61.4342 13.519V8.56001H61.4539Z" fill="#D7E2F7"/>
16 +<path d="M74.5891 8.42302C74.5891 8.30539 74.6977 8.2074 74.816 8.2074H75.1123L80.1001 13.4702H80.1097V8.55042C80.1097 8.43279 80.2087 8.32499 80.3366 8.32499H81.4428C81.5617 8.32499 81.6703 8.43279 81.6703 8.55042V16.5672C81.6703 16.6848 81.5617 16.7828 81.4428 16.7828H81.1568L76.1394 11.3142H76.1298V16.43C76.1298 16.5476 76.0308 16.6554 75.9022 16.6554H74.8063C74.6881 16.6554 74.5795 16.5476 74.5795 16.43V8.42302H74.5891Z" fill="#D7E2F7"/>
17 +<path d="M88.2374 8.56001C88.2374 8.44238 88.3357 8.33459 88.4547 8.33459H91.2989C93.6096 8.33459 95.5057 10.2065 95.5057 12.49C95.5057 14.8029 93.6096 16.6649 91.2989 16.6649H88.4547C88.3357 16.6649 88.2374 16.5572 88.2374 16.4396V8.56001ZM91.1504 15.2145C92.711 15.2145 93.8467 14.0483 93.8467 12.49C93.8467 10.9415 92.711 9.77524 91.1504 9.77524H89.7781V15.2145H91.1504Z" fill="#D7E2F7"/>
18 +<path d="M101.905 16.43C101.856 16.5476 101.747 16.6652 101.56 16.6652H100.513C100.335 16.6652 100.236 16.5084 100.306 16.3516L104.039 8.3348C104.078 8.26619 104.138 8.2074 104.246 8.2074H104.365C104.483 8.2074 104.532 8.26619 104.572 8.3348L108.266 16.3516C108.335 16.5084 108.246 16.6652 108.059 16.6652H107.012C106.833 16.6652 106.745 16.5966 106.666 16.43L104.286 11.2063H104.246L101.905 16.43Z" fill="#D7E2F7"/>
19 +<path d="M114.419 9.76547H112.582C112.454 9.76547 112.356 9.65767 112.356 9.54004V8.56001C112.356 8.44238 112.454 8.33459 112.582 8.33459H117.827C117.955 8.33459 118.054 8.44238 118.054 8.56001V9.54004C118.054 9.65767 117.955 9.76547 117.827 9.76547H115.99V16.4493C115.99 16.567 115.881 16.6748 115.763 16.6748H114.647C114.528 16.6748 114.419 16.567 114.419 16.4493V9.76547Z" fill="#D7E2F7"/>
20 +<path d="M123.683 8.56001C123.683 8.44238 123.792 8.33459 123.911 8.33459H125.026C125.145 8.33459 125.253 8.44238 125.253 8.56001V16.4396C125.253 16.5572 125.145 16.6649 125.026 16.6649H123.911C123.792 16.6649 123.683 16.5572 123.683 16.4396V8.56001Z" fill="#D7E2F7"/>
21 +<path d="M135.524 8.21692C137.925 8.21692 139.841 10.1378 139.841 12.5193C139.841 14.9009 137.925 16.7923 135.524 16.7923C133.124 16.7923 131.219 14.9009 131.219 12.5193C131.219 10.1378 133.124 8.21692 135.524 8.21692ZM135.524 15.234C137.036 15.234 138.28 14.009 138.28 12.5095C138.28 11.01 137.036 9.75561 135.524 9.75561C134.013 9.75561 132.779 11.01 132.779 12.5095C132.779 14.009 134.013 15.234 135.524 15.234Z" fill="#D7E2F7"/>
22 +<path d="M145.795 8.42302C145.795 8.30539 145.904 8.2074 146.023 8.2074H146.319L151.306 13.4702H151.316V8.55042C151.316 8.43279 151.415 8.32499 151.544 8.32499H152.65C152.768 8.32499 152.877 8.43279 152.877 8.55042V16.5672C152.877 16.6848 152.768 16.7828 152.65 16.7828H152.363L147.346 11.3142H147.336V16.43C147.336 16.5476 147.237 16.6554 147.109 16.6554H146.013C145.894 16.6554 145.786 16.5476 145.786 16.43V8.42302H145.795Z" fill="#D7E2F7"/>
23 +</g>
24 +</g>
25 +<defs>
26 +<radialGradient id="paint0_radial_7559_11731" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.0209 12.4279) rotate(180) scale(11.0059 11.0575)">
27 +<stop offset="0.5" stop-color="#BB6E5A"/>
28 +<stop offset="0.75" stop-color="#DE9173"/>
29 +<stop offset="1" stop-color="#FAC2A9"/>
30 +</radialGradient>
31 +<radialGradient id="paint1_radial_7559_11731" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.003 12.4451) rotate(180) scale(11.0036 11.0552)">
32 +<stop offset="0.5" stop-color="#FAC2A9"/>
33 +<stop offset="0.75" stop-color="#DE9173"/>
34 +<stop offset="1" stop-color="#BB6E5A"/>
35 +</radialGradient>
36 +<radialGradient id="paint2_radial_7559_11731" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.9943 12.4407) rotate(180) scale(11.0194 11.0711)">
37 +<stop offset="0.5" stop-color="#FAC2A9"/>
38 +<stop offset="0.75" stop-color="#DE9173"/>
39 +<stop offset="1" stop-color="#BB6E5A"/>
40 +</radialGradient>
41 +<clipPath id="clip0_7559_11731">
42 +<rect width="153" height="22" fill="white" transform="translate(0 1.5)"/>
43 +</clipPath>
44 +<clipPath id="clip1_7559_11731">
45 +<rect width="153.312" height="22" fill="white" transform="translate(0 1.5)"/>
46 +</clipPath>
47 +</defs>
48 +</svg>
assets/images/hardware_wallet/keystone_man.svg new
+13
@@ -0,0 +1,13 @@
1 +<svg width="111" height="25" viewBox="0 0 111 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M4.6875 3.90625H14.0625L7.8125 17.1875H3.125L4.6875 3.90625Z" fill="#D7E2F7"/>
3 +<path d="M20.3125 21.0938H10.9375L17.1875 7.8125H21.875L20.3125 21.0938Z" fill="#3D71FF"/>
4 +<path d="M20.3125 21.0938H10.9375L17.1875 7.8125H21.875L20.3125 21.0938Z" fill="#D7E2F7"/>
5 +<path d="M89.4531 7.8125H92.805L95.7031 13.6585V7.8125H97.6562V17.1875H95.7031V15.2344H94.3044L91.4062 9.38844V17.1875H89.4531V7.8125Z" fill="#D7E2F7"/>
6 +<path d="M76.5625 7.8125H68.75V9.375H71.6797V17.1875H73.6328V9.375H76.5625V7.8125Z" fill="#D7E2F7"/>
7 +<path d="M107.031 7.8125H101.953V9.375H100V17.1875H107.031V15.625H101.953V13.2812H106.25V11.7188H101.953V9.375H107.031V7.8125Z" fill="#D7E2F7"/>
8 +<path d="M48.4375 7.8125H43.3594V9.375H41.4062V17.1875H48.4375V15.625H43.3594V13.2812H47.6562V11.7188H43.3594V9.375H48.4375V7.8125Z" fill="#D7E2F7"/>
9 +<path d="M33.2031 12.1095L36.8822 7.8125H39.4534L35.4704 12.4645L39.8439 17.1875H37.182L33.2031 12.8906V17.1875H31.25V7.8125H33.2031V12.1095Z" fill="#D7E2F7"/>
10 +<path d="M49.9995 7.8125L53.3203 13.0858V17.1875H55.2734V13.0859L58.5942 7.8125H56.2861L54.2969 10.9714L52.3076 7.8125H49.9995Z" fill="#D7E2F7"/>
11 +<path opacity="0.9" d="M66.9922 8.10547C66.9922 8.10547 65.625 7.61719 63.6719 7.61719C60.9375 7.61719 59.9609 8.69141 59.9609 10.3516C59.9609 11.8164 60.7422 12.793 62.8906 13.2812C64.6484 13.6808 65.2344 13.7695 65.2344 14.5508C65.2344 15.332 64.5508 15.5273 63.2812 15.5273C61.8164 15.5273 59.9609 14.7461 59.9609 14.7461V16.6992C60.5144 16.9271 61.9141 17.3828 63.6719 17.3828C65.7227 17.3828 67.3828 16.7969 67.3828 14.6484C67.3828 12.5977 66.2043 12.0428 63.8672 11.5234C62.1094 11.1328 61.9141 10.6445 61.9141 10.3516C61.9141 9.68386 62.4023 9.27734 63.6719 9.27734C64.4531 9.27734 65.5637 9.51581 66.9922 9.86328V8.10547Z" fill="#D7E2F7"/>
12 +<path fill-rule="evenodd" clip-rule="evenodd" d="M82.6172 17.3828C85.6172 17.3828 87.3047 15.2344 87.3047 12.5C87.3047 9.76562 85.6172 7.61719 82.6172 7.61719C79.6172 7.61719 77.9297 9.76562 77.9297 12.5C77.9297 15.2344 79.6172 17.3828 82.6172 17.3828ZM82.6172 15.625C84.3672 15.625 85.3516 14.25 85.3516 12.5C85.3516 10.75 84.3672 9.375 82.6172 9.375C80.8672 9.375 79.8828 10.75 79.8828 12.5C79.8828 14.25 80.8672 15.625 82.6172 15.625Z" fill="#D7E2F7"/>
13 +</svg>
assets/images/hardware_wallet/ledger_man.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="75" height="25" viewBox="0 0 75 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M63.9184 23.4258V24.9996H74.7207V17.9013H73.1467V23.4258H63.9184ZM63.9184 0V1.57391H73.1467V7.09867H74.7207V0H63.9184ZM58.3477 12.176V8.51871H60.8168C62.0207 8.51871 62.4527 8.91984 62.4527 10.0157V10.6637C62.4527 11.7902 62.0359 12.176 60.8168 12.176H58.3477ZM62.2672 12.824C63.3936 12.5308 64.1805 11.4813 64.1805 10.2316C64.1805 9.44461 63.8721 8.73459 63.2857 8.16352C62.5449 7.4535 61.557 7.09867 60.2764 7.09867H56.8043V17.901H58.3477V13.5956H60.6625C61.8506 13.5956 62.3291 14.0894 62.3291 15.3242V17.9013H63.9031V15.5711C63.9031 13.8735 63.502 13.2255 62.2672 13.0403V12.824ZM49.2738 13.1788H54.027V11.7592H49.2738V8.51834H54.4898V7.09867H47.6996V17.901H54.7213V16.4813H49.2738V13.1788ZM44.1039 13.7499V14.4905C44.1039 16.0492 43.5328 16.5586 42.0979 16.5586H41.7584C40.323 16.5586 39.6287 16.0955 39.6287 13.9505V11.0492C39.6287 8.88885 40.3541 8.44104 41.7891 8.44104H42.0975C43.502 8.44104 43.9494 8.96578 43.9646 10.4164H45.6623C45.508 8.28678 44.0883 6.94443 41.9586 6.94443C40.9248 6.94443 40.0605 7.26861 39.4125 7.88564C38.4402 8.79623 37.9002 10.3395 37.9002 12.4998C37.9002 14.5832 38.3633 16.1265 39.3199 17.083C39.968 17.7157 40.8631 18.0552 41.7428 18.0552C42.6686 18.0552 43.5176 17.6847 43.9494 16.8824H44.1652V17.901H45.585V12.3303H41.4029V13.7499H44.1039ZM30.4934 8.51834H32.1756C33.7652 8.51834 34.6295 8.91947 34.6295 11.0802V13.9195C34.6295 16.0798 33.7652 16.4813 32.1756 16.4813H30.4934V8.51834ZM32.3143 17.9013C35.2619 17.9013 36.3572 15.6637 36.3572 12.5002C36.3572 9.29035 35.1846 7.09904 32.2832 7.09904H28.9498V17.9013H32.3143ZM21.4967 13.1788H26.2498V11.7592H21.4967V8.51834H26.7125V7.09867H19.9223V17.901H26.9441V16.4813H21.4967V13.1788ZM12.3919 7.09867H10.8179V17.901H17.9166V16.4813H12.3919V7.09867ZM0 17.9013V25H10.8023V23.4258H1.57391V17.9013H0ZM0 0V7.09867H1.57391V1.57391H10.8023V0H0Z" fill="#D7E2F7"/>
3 +</svg>
assets/images/hardware_wallet/seedsigner_man.svg new
+20
@@ -0,0 +1,20 @@
1 +<svg width="78" height="25" viewBox="0 0 78 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<g clip-path="url(#clip0_7559_11744)">
3 +<g clip-path="url(#clip1_7559_11744)">
4 +<path d="M0.0103835 11.9826C0.240345 5.35887 5.62559 0.296015 12.0963 0.000244141H66.2297C68.0073 0.0926724 70.2751 0.706396 71.7569 1.62698C75.3261 3.84378 77.6413 7.75091 77.798 11.9826C77.8128 12.3893 77.8121 12.833 77.798 13.2397C77.5836 19.3148 72.8771 24.5189 66.6733 25.0002H11.0611C4.9076 24.4738 0.212247 19.337 0.0103835 13.2397C-0.00292619 12.8285 -0.00366562 12.3938 0.0103835 11.9826ZM33.8761 1.85251L12.171 1.84955C6.64378 2.02775 1.81532 6.53307 1.56688 12.0159C1.31251 17.6326 5.42003 22.6252 11.0618 23.1509L33.8761 23.148V1.85251ZM40.9509 9.6394C40.3498 8.89258 38.6254 8.78685 37.7951 9.13511C36.4449 9.70152 36.124 11.8362 37.3743 12.6622C38.0169 13.0866 38.5988 13.077 39.2539 13.2966C40.4001 13.6818 39.5882 14.8642 38.5426 14.3776C37.9289 14.0915 37.6161 13.0526 36.7865 13.8571C36.0175 14.6039 36.9403 15.4506 37.6553 15.7818C39.6643 16.7128 42.202 15.3175 41.3798 12.9446C40.9384 11.6721 39.5608 11.9664 38.6624 11.5212C37.835 11.1116 38.5537 10.1592 39.4425 10.5933C39.7316 10.7345 39.79 11.0569 40.2736 11.0266C40.7572 10.9963 41.345 10.1259 40.9517 9.63866L40.9509 9.6394ZM49.6266 13.2389C49.6813 14.127 49.7131 14.4316 48.7134 14.3947C48.0006 14.368 47.3174 13.8253 47.0564 13.1842C46.473 11.7519 47.6272 10.4143 49.1282 10.6687C49.8839 10.7966 50.1302 11.6861 50.8836 10.6879C51.4183 9.9788 51.0227 9.64976 50.3424 9.3074C48.5323 8.39643 46.1402 9.22976 45.4296 11.1501C44.3286 14.1255 47.137 16.942 50.1427 15.8632C50.5014 15.7345 51.2511 15.2968 51.3236 14.8997C51.4042 14.4597 51.3754 12.8766 51.3303 12.3856C51.3096 12.1594 51.3066 12.0115 51.0855 11.891C50.7365 11.7009 48.7829 11.6765 48.4768 11.9043C48.2587 12.0669 48.2557 12.569 48.3119 12.8167C48.4421 13.3942 49.1933 13.2182 49.6274 13.2389H49.6266ZM61.087 11.6869C61.1269 11.4776 60.979 10.8107 61.2083 10.736C62.1599 10.6147 63.3822 10.8646 64.2991 10.7212C64.9852 10.614 64.9571 9.23568 64.4351 9.11441C63.3555 9.08335 62.2745 9.09962 61.1949 9.10184C60.8119 9.10258 60.3609 9.02494 59.9742 9.05748C59.5091 9.09593 59.4188 9.29631 59.3833 9.72444C59.234 11.5264 59.5031 13.5266 59.3833 15.3507C59.39 15.7781 59.6044 15.9423 60.0119 15.9792C61.3983 16.1049 63.011 15.906 64.4188 15.9423C64.8876 15.8417 64.8862 14.7503 64.6717 14.4989C64.637 14.4582 64.3257 14.2749 64.3042 14.2749H61.0877V13.3876H63.269C63.2868 13.3876 63.5633 13.2375 63.5811 13.2197C63.8746 12.9247 63.8236 11.6869 63.343 11.6869H61.0877H61.087ZM42.8764 9.11441C42.551 9.18909 42.483 9.5869 42.4549 9.87602C42.3174 11.2624 42.4231 13.3513 42.5274 14.7562C42.5873 15.56 42.3595 16.0902 43.5264 15.9755C44.1379 15.9157 44.1807 15.594 44.2281 15.0505C44.3427 13.7343 44.336 11.346 44.2281 10.0246C44.2096 9.79542 44.1526 9.30149 43.9626 9.18096C43.7726 9.06043 43.0975 9.06487 42.8771 9.11515L42.8764 9.11441ZM53.9885 15.6428V12.2045L54.2088 12.3169L56.8338 15.8291C57.1636 16.0887 58.3156 16.0857 58.3555 15.4986L58.3925 9.68673C58.5182 8.85044 56.7251 8.96874 56.7251 9.43163V12.9439C56.223 12.5002 55.8578 11.9013 55.454 11.3645C55.3949 11.2861 55.2958 11.2528 55.2455 11.1863C54.9579 10.801 54.061 9.24677 53.6972 9.13216C53.3984 9.03825 52.5096 9.00424 52.395 9.39022L52.378 15.6273C52.5333 16.1574 53.8776 16.0487 53.9892 15.6428H53.9885ZM67.4461 15.5689C67.5104 15.2635 67.4461 14.53 67.4461 14.164C67.4461 13.9968 68.7829 14.0124 68.95 14.1203C69.1985 14.2815 69.427 15.9186 70.0148 16.0362C70.284 16.0902 71.0685 15.7722 71.1787 15.5304C71.4316 14.9736 70.5021 13.8837 70.4762 13.4171C70.4666 13.2471 70.8955 12.8078 70.9983 12.5394C71.4168 11.4429 71.0249 10.0579 70.0111 9.45381C69.2473 8.99906 67.2065 8.98945 66.2985 9.09666C66.1203 9.11811 65.9554 9.15878 65.874 9.33772C65.8171 9.46194 65.7579 10.1259 65.7461 10.3189C65.6663 11.6137 65.6145 13.934 65.755 15.1888C65.7712 15.333 65.8156 15.6051 65.8888 15.7204C66.1402 16.1153 67.3381 16.0798 67.4461 15.5681V15.5689Z" fill="#D7E2F7"/>
5 +<path d="M31.2396 11.3689C32.0559 13.3505 30.7701 15.8342 28.5141 16.0479C27.9003 16.1064 26.5265 16.1204 25.9261 16.0509C25.5423 16.0058 25.4329 15.7773 25.3708 15.4231C25.4994 13.6115 25.2059 11.5825 25.3693 9.79831C25.4092 9.36205 25.5556 9.20973 25.9978 9.1698C28.2131 8.96942 30.3168 9.12987 31.2396 11.3696V11.3689ZM27.1476 14.3487C27.1838 14.4597 27.9196 14.4426 28.0808 14.4279C28.2242 14.4153 28.2656 14.3643 28.3307 14.3487C29.601 14.0478 30.2207 12.5068 29.3237 11.4702C29.1833 11.3082 28.6302 10.8734 28.4416 10.8734H27.1476V14.3487Z" fill="#D7E2F7"/>
6 +<path d="M20.7882 13.5357C20.8318 13.7967 20.7572 14.148 20.7882 14.423H24.0047C24.526 14.423 24.6724 15.9122 24.0631 16.0342L19.4913 16.0535C19.1156 15.9817 19.0409 15.8583 19.0106 15.4989C18.8524 13.6296 19.1378 11.5437 19.0099 9.65077C19.0247 9.37127 19.3108 9.18789 19.5667 9.17236L24.0639 9.18937C24.5327 9.40528 24.5585 10.2164 24.3293 10.6076C24.197 10.8346 24.0831 10.7473 23.913 10.8235C23.8768 10.8398 23.9005 10.9485 23.8938 10.9485H20.7882V11.8358H22.8956C22.9362 11.8358 23.0146 11.7071 23.1529 11.7944C23.4819 12.0021 23.496 13.2252 23.1514 13.4226C23.1085 13.4477 22.7787 13.5283 22.7107 13.5365C22.0837 13.3671 21.3635 13.5069 20.7143 13.4633C20.695 13.5224 20.7867 13.5298 20.7882 13.5365V13.5357Z" fill="#D7E2F7"/>
7 +<path d="M14.3554 13.5348C14.1912 13.7005 14.3206 14.1766 14.2815 14.4221H17.5719C18.1945 14.4221 18.1997 15.8943 17.7634 16.0171L12.8344 15.9801C12.6961 15.8507 12.5793 15.6947 12.5786 15.4965C12.7287 13.6879 12.3833 11.5724 12.58 9.80146C12.6355 9.30234 12.807 9.22101 13.2832 9.17368C14.2275 9.07978 16.4154 9.06943 17.3493 9.17221C18.0244 9.24689 18.1287 9.60921 18.0533 10.2466C17.9867 10.81 17.7013 10.8367 17.2044 10.8758C16.3237 10.9446 15.3566 10.8189 14.4656 10.8729C14.172 10.8832 14.2245 11.7661 14.402 11.8238C14.6438 11.9029 15.9496 11.843 16.3171 11.8378C16.5515 11.8349 16.7393 11.6744 16.8961 11.9879C17.0698 12.3347 17.1083 13.2257 16.7009 13.4047C16.53 13.4794 16.4199 13.4372 16.2764 13.4609C15.9237 13.52 14.538 13.35 14.3539 13.5348H14.3554Z" fill="#D7E2F7"/>
8 +<path d="M9.97874 9.11811C9.99057 9.12329 9.99649 9.22311 10.0482 9.24086C10.3854 9.35547 11.2624 9.66899 11.2927 10.0942C11.3164 10.4321 10.7514 11.1404 10.3995 11.1715C9.70737 11.2329 9.84269 10.7774 9.42048 10.6694C9.24893 10.6251 8.8977 10.6265 8.73059 10.6827C8.48732 10.7641 8.31429 11.0835 8.41042 11.3194C8.60785 11.8008 9.72512 11.7268 10.1569 11.9102C10.2545 11.9516 10.2501 12.0699 10.3255 12.1298C10.5133 11.7031 10.4934 12.1668 10.5185 12.1912C10.5525 12.2244 10.7529 12.2052 10.8601 12.2592C11.6047 12.6356 11.7792 13.7018 11.6121 14.4516C11.5789 14.601 11.3289 15.2287 11.2491 15.3271C11.1818 15.4099 11.0775 15.3921 10.9977 15.4609C10.5363 15.8617 10.452 15.8432 9.90184 16.0547C9.83677 16.0798 9.84269 16.1959 9.80202 16.1974C9.70959 16.2003 9.58611 16.0953 9.44931 16.1197C9.39903 16.1286 9.4079 16.2558 9.35097 16.2698C9.25706 16.2928 8.87774 16.2928 8.78531 16.2698C8.72763 16.2558 8.73798 16.1234 8.68992 16.119C8.30838 16.0828 8.22926 16.1604 7.77451 15.9386C7.32568 15.7197 6.37995 14.9729 6.5478 14.4198C6.56555 14.3614 6.96262 13.8682 7.01586 13.829C7.7368 13.3003 8.17972 14.3111 8.7158 14.5522C9.31104 14.8206 10.0786 14.4405 9.83234 13.7314C9.73621 13.4556 9.02488 13.4223 8.80675 13.3528C7.8381 13.0452 6.84875 12.6755 6.6846 11.556C6.49752 10.2798 7.25691 9.19797 8.54795 9.09593C8.71136 9.08262 9.91293 9.08928 9.97874 9.11811Z" fill="#D7E2F7"/>
9 +<path d="M68.8882 10.7252C68.9266 10.7341 69.2571 10.9545 69.2963 11.0025C69.706 11.5024 69.3488 12.4259 68.7403 12.4259H67.4463V10.7252C67.8611 10.7866 68.5081 10.6409 68.8882 10.7252Z" fill="#D7E2F7"/>
10 +</g>
11 +</g>
12 +<defs>
13 +<clipPath id="clip0_7559_11744">
14 +<rect width="77.8091" height="25" fill="white"/>
15 +</clipPath>
16 +<clipPath id="clip1_7559_11744">
17 +<rect width="77.8091" height="25" fill="white"/>
18 +</clipPath>
19 +</defs>
20 +</svg>
assets/images/hardware_wallet/trezor_man.svg new
+11
@@ -0,0 +1,11 @@
1 +<svg width="80" height="25" viewBox="0 0 80 25" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<g clip-path="url(#clip0_7559_11628)">
3 +<path d="M11.9232 7.14104C11.9232 4.60444 9.69366 2.5 6.97947 2.5C4.2648 2.5 2.03478 4.60542 2.03478 7.14055V8.62445H0V19.2938L6.978 22.5L13.9599 19.2948V8.67154H11.9251L11.9237 7.14055L11.9232 7.14104ZM4.5552 7.14104C4.5552 5.9451 5.62213 4.98804 6.97947 4.98804C8.33681 4.98804 9.40276 5.9451 9.40276 7.14104V8.62445H4.55569L4.5552 7.14104ZM11.1481 17.572L6.97751 19.4871L2.80837 17.5735V11.1606H11.1476L11.1481 17.572Z" fill="#D7E2F7"/>
4 +<path d="M19.6313 8.62444H28.4052V11.1606H25.3997V19.3419H22.5884V11.1606H19.6313V8.62444ZM38.4884 12.2128C38.4884 10.0593 36.9853 8.67203 34.7558 8.67203H29.6654V19.3414H32.4772V15.7535H33.5437L35.5309 19.3414H38.7817L36.4065 15.3709C37.4219 14.9883 38.4884 13.9827 38.4884 12.2128ZM34.4188 13.2655H32.4772V11.112H34.4164C35.1434 11.112 35.628 11.5432 35.628 12.1652C35.628 12.8348 35.1434 13.2655 34.4164 13.2655H34.4188ZM39.7967 8.62444H47.6498V11.1125H42.608V12.6916H47.5046V15.1315H42.608V16.8533H47.6498V19.3419H39.7967V8.62444ZM63.4041 8.48022C60.1082 8.48022 57.7825 10.825 57.7825 13.9832C57.7825 17.1408 60.1562 19.4871 63.4041 19.4871C66.652 19.4871 69.0758 17.1428 69.0758 13.9846C69.0758 10.8265 66.7006 8.48072 63.4041 8.48072V8.48022ZM63.4041 16.9976C61.7564 16.9976 60.6409 15.7535 60.6409 13.9856C60.6409 12.1672 61.7564 10.9727 63.4041 10.9727C65.0524 10.9727 66.2159 12.2172 66.2159 13.9851C66.2159 15.7535 65.0524 16.9971 63.4041 16.9971V16.9976ZM76.9775 15.3709C77.9949 14.9883 79.0623 13.9827 79.0623 12.2128C79.0623 10.0593 77.5593 8.67203 75.3293 8.67203H70.2399V19.3414H73.0512V15.7535H74.1196L76.1068 19.3414H79.3547L76.9775 15.3709ZM74.9898 13.2655H73.0512V11.112H74.9898C75.7178 11.112 76.202 11.5432 76.202 12.1652C76.202 12.8348 75.7178 13.2655 74.9898 13.2655ZM49.0071 8.62444H57.2964V10.7774L52.7882 16.8062H57.2964V19.3419H49.0071V17.1884L53.5152 11.1606H49.0071V8.62444Z" fill="#D7E2F7"/>
5 +</g>
6 +<defs>
7 +<clipPath id="clip0_7559_11628">
8 +<rect width="79.3544" height="20" fill="white" transform="translate(0 2.5)"/>
9 +</clipPath>
10 +</defs>
11 +</svg>
cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart deleted
-46
@@ -1,46 +0,0 @@
1 -import 'dart:async';
2 -
3 -import 'package:bitcoin_base/bitcoin_base.dart';
4 -import 'package:blockchain_utils/blockchain_utils.dart';
5 -import 'package:cw_bitcoin/utils.dart';
6 -import 'package:cw_core/hardware/hardware_account_data.dart';
7 -import 'package:ledger_bitcoin/ledger_bitcoin.dart';
8 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
9 -import 'package:cw_core/utils/print_verbose.dart';
10 -
11 -class BitcoinHardwareWalletService {
12 - BitcoinHardwareWalletService(this.ledgerConnection);
13 -
14 - final LedgerConnection ledgerConnection;
15 -
16 - Future<List<HardwareAccountData>> getAvailableAccounts(
17 - {int index = 0, int limit = 5}) async {
18 - final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
19 -
20 - final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
21 -
22 - final accounts = <HardwareAccountData>[];
23 - final indexRange = List.generate(limit, (i) => i + index);
24 -
25 - for (final i in indexRange) {
26 - final derivationPath = "m/84'/0'/$i'";
27 - final xpub =
28 - await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
29 - Bip32Slip10Secp256k1 hd =
30 - Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
31 -
32 - final address = generateP2WPKHAddress(
33 - hd: hd, index: 0, network: BitcoinNetwork.mainnet);
34 -
35 - accounts.add(HardwareAccountData(
36 - address: address,
37 - accountIndex: i,
38 - derivationPath: derivationPath,
39 - masterFingerprint: masterFp,
40 - xpub: xpub,
41 - ));
42 - }
43 -
44 - return accounts;
45 - }
46 -}
cw_bitcoin/lib/bitcoin_wallet.dart
+9 -16
@@ -12,6 +12,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
12 import 'package:cw_bitcoin/electrum_derivations.dart';
13 import 'package:cw_bitcoin/electrum_wallet.dart';
14 import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
15 +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
16 import 'package:cw_bitcoin/payjoin/manager.dart';
17 import 'package:cw_bitcoin/payjoin/storage.dart';
18 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
@@ -25,7 +26,7 @@ import 'package:cw_core/output_info.dart';
26 import 'package:cw_core/payjoin_session.dart';
27 import 'package:cw_core/pending_transaction.dart';
28 import 'package:cw_core/unspent_coins_info.dart';
28 -import 'package:cw_core/utils/print_verbose.dart';
29 +import 'package:cw_core/utils/zpub.dart';
30 import 'package:cw_core/wallet_info.dart';
31 import 'package:cw_core/wallet_keys_file.dart';
32 import 'package:flutter/foundation.dart';
@@ -241,7 +242,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
242
243 return BitcoinWallet(
244 mnemonic: mnemonic,
244 - xpub: keysData.xPub,
245 + xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null,
246 password: password,
247 passphrase: passphrase,
248 walletInfo: walletInfo,
@@ -260,16 +261,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
261 payjoinBox: payjoinBox);
262 }
263
263 - LedgerConnection? _ledgerConnection;
264 - BitcoinLedgerApp? _bitcoinLedgerApp;
265 -
266 - @override
267 - void setLedgerConnection(LedgerConnection connection) {
268 - _ledgerConnection = connection;
269 - _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
270 - derivationPath: walletInfo.derivationInfo!.derivationPath!);
271 - }
272 -
264 @override
265 Future<void> close({bool shouldCleanup = false}) async {
266 payjoinManager.cleanupSessions();
@@ -331,7 +322,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
322 BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
323 BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
324 }) async {
334 - final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
325 + final masterFingerprint =
326 + await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
327
328 final psbt = await buildPsbt(
329 outputs: outputs,
@@ -347,7 +339,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
339 outputOrdering: outputOrdering,
340 );
341
350 - final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt);
342 + final psbtStr = base64Encode(psbt.serialize());
343 + final rawHex = await hardwareWalletService!.signTransaction(transaction: psbtStr);
344 return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
345 }
346
@@ -495,8 +488,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
488 final derivationPath =
489 accountPath != null ? "$accountPath/$isChange/$index" : null;
490
498 - final signature = await _bitcoinLedgerApp!.signMessage(
499 - message: ascii.encode(message), signDerivationPath: derivationPath);
491 + final signature = await hardwareWalletService!
492 + .signMessage(message: ascii.encode(message), derivationPath: derivationPath);
493 return base64Encode(signature);
494 }
495
cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
+1
@@ -60,6 +60,7 @@ class BitcoinWalletFromKeysCredentials extends WalletCredentials {
60 required String password,
61 required this.xpub,
62 WalletInfo? walletInfo,
63 + super.hardwareWalletType
64 }) : super(name: name, password: password, walletInfo: walletInfo);
65
66 final String xpub;
cw_bitcoin/lib/bitcoin_wallet_service.dart
+9 -3
@@ -7,6 +7,7 @@ import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
7 import 'package:cw_core/encryption_file_utils.dart';
8 import 'package:cw_core/payjoin_session.dart';
9 import 'package:cw_core/unspent_coins_info.dart';
10 +import 'package:cw_core/utils/zpub.dart';
11 import 'package:cw_core/wallet_base.dart';
12 import 'package:cw_core/wallet_service.dart';
13 import 'package:cw_bitcoin/bitcoin_wallet.dart';
@@ -150,9 +151,12 @@ class BitcoinWalletService extends WalletService<
151 credentials.walletInfo?.network = network.value;
152 credentials.walletInfo?.derivationInfo?.derivationPath =
153 credentials.hwAccountData.derivationPath;
154 +
155 + final xpub = convertZpubToXpub(credentials.hwAccountData.xpub!);
156 +
157 final wallet = await BitcoinWallet(
158 password: credentials.password!,
155 - xpub: credentials.hwAccountData.xpub,
159 + xpub: xpub,
160 walletInfo: credentials.walletInfo!,
161 unspentCoinsInfo: unspentCoinsInfoSource,
162 networkParam: network,
@@ -166,13 +170,15 @@ class BitcoinWalletService extends WalletService<
170
171 @override
172 Future<BitcoinWallet> restoreFromKeys(BitcoinWalletFromKeysCredentials credentials,
169 - {bool? isTestnet}) async {
173 + {bool? isTestnet}) async {
174 final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
175 credentials.walletInfo?.network = network.value;
176
177 + final xpub = convertZpubToXpub(credentials.xpub);
178 +
179 final wallet = await BitcoinWallet(
180 password: credentials.password!,
175 - xpub: credentials.xpub,
181 + xpub: xpub,
182 walletInfo: credentials.walletInfo!,
183 unspentCoinsInfo: unspentCoinsInfoSource,
184 networkParam: network,
cw_bitcoin/lib/electrum_wallet.dart
+24 -9
@@ -4,6 +4,7 @@ import 'dart:io';
4 import 'dart:isolate';
5
6 import 'package:bitcoin_base/bitcoin_base.dart';
7 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
8 import 'package:cw_core/utils/proxy_wrapper.dart';
9 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
10 import 'package:cw_core/utils/print_verbose.dart';
@@ -75,8 +76,9 @@ abstract class ElectrumWalletBase
76 ElectrumBalance? initialBalance,
77 CryptoCurrency? currency,
78 bool? alwaysScan,
78 - }) : accountHD =
79 - getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
79 + })
80 + : accountHD = getAccountHDWallet(currency, network, seedBytes, xpub,
81 + walletInfo.derivationInfo, walletInfo.hardwareWalletType),
82 syncStatus = NotConnectedSyncStatus(),
83 _password = password,
84 _feeRates = <int>[],
@@ -113,8 +115,13 @@ abstract class ElectrumWalletBase
115 sharedPrefs.complete(SharedPreferences.getInstance());
116 }
117
116 - static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
117 - Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
118 + static Bip32Slip10Secp256k1 getAccountHDWallet(
119 + CryptoCurrency? currency,
120 + BasedUtxoNetwork network,
121 + Uint8List? seedBytes,
122 + String? xpub,
123 + DerivationInfo? derivationInfo,
124 + HardwareWalletType? hardwareWalletType) {
125 if (seedBytes == null && xpub == null) {
126 throw Exception(
127 "To create a Wallet you need either a seed or an xpub. This should not happen");
@@ -125,8 +132,10 @@ abstract class ElectrumWalletBase
132 case CryptoCurrency.btc:
133 case CryptoCurrency.ltc:
134 case CryptoCurrency.tbtc:
128 - return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath(
129 - _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
135 + return Bip32Slip10Secp256k1.fromSeed(
136 + seedBytes, getKeyNetVersion(network, hardwareWalletType))
137 + .derivePath(
138 + _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
139 as Bip32Slip10Secp256k1;
140 case CryptoCurrency.bch:
141 return bitcoinCashHDWallet(seedBytes);
@@ -137,7 +146,8 @@ abstract class ElectrumWalletBase
146 }
147 }
148
140 - return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
149 + return Bip32Slip10Secp256k1.fromExtendedKey(
150 + xpub!, getKeyNetVersion(network, hardwareWalletType));
151 }
152
153 static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
@@ -149,10 +159,13 @@ abstract class ElectrumWalletBase
159 static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
160 inputsCount * 68 + outputsCounts * 34 + 10;
161
152 - static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) {
162 + static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network,
163 + [HardwareWalletType? hardwareWalletType]) {
164 switch (network) {
165 case LitecoinNetwork.mainnet:
155 - return Bip44Conf.litecoinMainNet.altKeyNetVer;
166 + if (hardwareWalletType == HardwareWalletType.ledger)
167 + return Bip44Conf.litecoinMainNet.altKeyNetVer;
168 + return null;
169 default:
170 return null;
171 }
@@ -1408,6 +1421,8 @@ abstract class ElectrumWalletBase
1421 }
1422 }
1423
1424 + HardwareWalletService? hardwareWalletService;
1425 +
1426 void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
1427
1428 Future<BtcTransaction> buildHardwareWalletTransaction({
cw_bitcoin/lib/hardware/bitbox_service.dart new
+93
@@ -0,0 +1,93 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:developer';
4 +import 'dart:typed_data';
5 +
6 +import 'package:bitbox_flutter/bitbox_manager.dart';
7 +import 'package:bitcoin_base/bitcoin_base.dart';
8 +import 'package:blockchain_utils/blockchain_utils.dart';
9 +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
10 +import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
11 +import 'package:cw_bitcoin/psbt/v0_finalizer.dart';
12 +import 'package:cw_bitcoin/utils.dart';
13 +import 'package:cw_core/hardware/hardware_account_data.dart';
14 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
15 +import 'package:ledger_bitcoin/psbt.dart';
16 +
17 +class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService {
18 + BitcoinBitboxService(this.manager);
19 +
20 + final int bitboxCoinType = 0; // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
21 + final BitboxManager manager;
22 +
23 + @override
24 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
25 + final accounts = <HardwareAccountData>[];
26 + final indexRange = List.generate(limit, (i) => i + index);
27 +
28 + for (final i in indexRange) {
29 + final derivationPath = "m/84'/0'/$i'";
30 + final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, 1);
31 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
32 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
33 +
34 + accounts.add(HardwareAccountData(
35 + address: address,
36 + accountIndex: i,
37 + derivationPath: derivationPath,
38 + xpub: xPub,
39 + ));
40 + }
41 +
42 + return accounts;
43 + }
44 +
45 +
46 + @override
47 + Future<Uint8List> signTransaction({required String transaction}) async {
48 + final psbt = PsbtV2()..deserialize(base64Decode(transaction));
49 + log(base64Encode(psbt.asPsbtV0()), name: 'PSBT');
50 + final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0()));
51 + log(signedPsbt, name: 'signed PSBT');
52 + final transactionRes = PsbtV2()
53 + ..deserializeV0(base64Decode(signedPsbt))
54 + ..finalizeV0();
55 + return transactionRes.extract();
56 + }
57 +
58 + @override
59 + Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
60 + throw UnimplementedError(); // ToDo (Konsti)
61 + }
62 +
63 + @override
64 + Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
65 +}
66 +
67 +class LitecoinBitboxService extends HardwareWalletService {
68 + LitecoinBitboxService(this.manager);
69 +
70 + final BitboxManager manager;
71 +
72 + @override
73 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
74 + final accounts = <HardwareAccountData>[];
75 + final indexRange = List.generate(limit, (i) => i + index);
76 +
77 + for (final i in indexRange) {
78 + final derivationPath = "m/84'/2'/$i'";
79 + final xPub = await manager.getBTCXPub(0, derivationPath, 1);
80 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
81 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
82 +
83 + accounts.add(HardwareAccountData(
84 + address: address,
85 + accountIndex: i,
86 + derivationPath: derivationPath,
87 + xpub: xPub,
88 + ));
89 + }
90 +
91 + return accounts;
92 + }
93 +}
cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart new
+5
@@ -0,0 +1,5 @@
1 +import 'dart:typed_data';
2 +
3 +mixin BitcoinHardwareWalletService {
4 + Future<Uint8List> getMasterFingerprint();
5 +}
cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart new
+57
@@ -0,0 +1,57 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:typed_data';
4 +
5 +import 'package:bitcoin_base/bitcoin_base.dart';
6 +import 'package:blockchain_utils/blockchain_utils.dart';
7 +import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
8 +import 'package:cw_bitcoin/utils.dart';
9 +import 'package:cw_core/hardware/hardware_account_data.dart';
10 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
11 +import 'package:ledger_bitcoin/ledger_bitcoin.dart';
12 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
13 +
14 +class BitcoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService {
15 + BitcoinLedgerService(this.ledgerConnection)
16 + : bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
17 +
18 + final LedgerConnection ledgerConnection;
19 + final BitcoinLedgerApp bitcoinLedgerApp;
20 +
21 + @override
22 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
23 + final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
24 +
25 + final accounts = <HardwareAccountData>[];
26 + final indexRange = List.generate(limit, (i) => i + index);
27 +
28 + for (final i in indexRange) {
29 + final derivationPath = "m/84'/0'/$i'";
30 + final xPub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
31 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
32 +
33 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
34 +
35 + accounts.add(HardwareAccountData(
36 + address: address,
37 + accountIndex: i,
38 + derivationPath: derivationPath,
39 + masterFingerprint: masterFp,
40 + xpub: xPub,
41 + ));
42 + }
43 +
44 + return accounts;
45 + }
46 +
47 + @override
48 + Future<Uint8List> signTransaction({required String transaction}) =>
49 + bitcoinLedgerApp.signPsbt(psbt: PsbtV2()..deserialize(base64Decode(transaction)));
50 +
51 + @override
52 + Future<Uint8List> getMasterFingerprint() => bitcoinLedgerApp.getMasterFingerprint();
53 +
54 + @override
55 + Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
56 + bitcoinLedgerApp.signMessage(message: message, signDerivationPath: derivationPath);
57 +}
cw_bitcoin/lib/hardware/litecoin_ledger_service.dart renamed
+7 -8
@@ -4,16 +4,17 @@ import 'package:bitcoin_base/bitcoin_base.dart';
4 import 'package:blockchain_utils/blockchain_utils.dart';
5 import 'package:cw_bitcoin/utils.dart';
6 import 'package:cw_core/hardware/hardware_account_data.dart';
7 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
8 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
9 import 'package:ledger_litecoin/ledger_litecoin.dart';
10
10 -class LitecoinHardwareWalletService {
11 - LitecoinHardwareWalletService(this.ledgerConnection);
11 +class LitecoinLedgerService extends HardwareWalletService {
12 + LitecoinLedgerService(this.ledgerConnection);
13
14 final LedgerConnection ledgerConnection;
15
15 - Future<List<HardwareAccountData>> getAvailableAccounts(
16 - {int index = 0, int limit = 5}) async {
16 + @override
17 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
18 final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
19
20 await litecoinLedgerApp.getVersion();
@@ -27,11 +28,9 @@ class LitecoinHardwareWalletService {
28 final xpub = await litecoinLedgerApp.getXPubKey(
29 accountsDerivationPath: derivationPath,
30 xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
30 - final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
31 - .childKey(Bip32KeyIndex(0));
31 + final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0));
32
33 - final address = generateP2WPKHAddress(
34 - hd: hd, index: 0, network: LitecoinNetwork.mainnet);
33 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
34
35 accounts.add(HardwareAccountData(
36 address: address,
cw_bitcoin/lib/psbt/transaction_builder.dart
+3 -1
@@ -52,7 +52,9 @@ class PSBTTransactionBuild {
52 .map((e) => e?.toLowerCase())
53 .contains(output.address.toAddress().toLowerCase()))
54 .firstOrNull;
55 - if (cwOutput != null) {
55 + if (cwOutput != null &&
56 + cwOutput.extra.containsKey('bip353_name') &&
57 + cwOutput.extra.containsKey('bip353_proof')) {
58 final bip353Name = utf8.encode(cwOutput.extra['bip353_name'] as String);
59 final bip353Proof = base64.decode(cwOutput.extra['bip353_proof'] as String);
60
cw_bitcoin/lib/utils.dart
+2 -1
@@ -1,3 +1,4 @@
1 +import 'dart:typed_data';
2 import 'package:bitcoin_base/bitcoin_base.dart';
3 import 'package:blockchain_utils/blockchain_utils.dart';
4
@@ -51,4 +52,4 @@ String generateP2TRAddress({
52 }) =>
53 ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
54 .toTaprootAddress()
54 - .toAddress(network);
55 + .toAddress(network);
\ No newline at end of file
cw_bitcoin/pubspec.lock
+9 -2
@@ -89,6 +89,13 @@ packages:
89 url: "https://github.com/cake-tech/bitbox-flutter.git"
90 source: git
91 version: "1.0.1"
92 + bitbox_flutter:
93 + dependency: "direct main"
94 + description:
95 + path: "../scripts/bitbox_flutter"
96 + relative: true
97 + source: path
98 + version: "0.0.1"
99 bitcoin_base:
100 dependency: "direct overridden"
101 description:
@@ -597,8 +604,8 @@ packages:
604 dependency: "direct main"
605 description:
606 path: "packages/ledger-bitcoin"
600 - ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6
601 - resolved-ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6
607 + ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
608 + resolved-ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
609 url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
610 source: git
611 version: "0.0.3"
cw_bitcoin/pubspec.yaml
+3 -1
@@ -49,11 +49,13 @@ dependencies:
49 git:
50 url: https://github.com/cake-tech/ledger-flutter-plus-plugins
51 path: packages/ledger-bitcoin
52 - ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6
52 + ref: 6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3
53 ledger_litecoin:
54 git:
55 url: https://github.com/cake-tech/ledger-flutter-plus-plugins
56 path: packages/ledger-litecoin
57 + bitbox_flutter:
58 + path: ../scripts/bitbox_flutter
59 socks_socket:
60 git:
61 url: https://github.com/sneurlax/socks_socket
cw_core/lib/hardware/device_connection_type.dart
+28 -11
@@ -1,22 +1,39 @@
1 +import 'package:cw_core/wallet_info.dart';
2 import 'package:cw_core/wallet_type.dart';
3
4 enum DeviceConnectionType {
5 usb,
6 ble;
7
7 - static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
8 + static List<DeviceConnectionType> supportedConnectionTypes(
9 + WalletType walletType, HardwareWalletType hardwareType,
10 [bool isIOS = false]) {
9 - switch (walletType) {
10 - case WalletType.monero:
11 - case WalletType.bitcoin:
12 - case WalletType.litecoin:
13 - case WalletType.ethereum:
14 - case WalletType.polygon:
15 - if (isIOS) return [DeviceConnectionType.ble];
16 - return [DeviceConnectionType.ble, DeviceConnectionType.usb];
17 - default:
18 - return [];
11 + bool isSupported = false;
12 + switch (hardwareType) {
13 + case HardwareWalletType.bitbox:
14 + isSupported = [
15 + WalletType.bitcoin,
16 + // WalletType.litecoin,
17 + WalletType.ethereum,
18 + WalletType.polygon
19 + ].contains(walletType);
20 + break;
21 + case HardwareWalletType.ledger:
22 + isSupported = [
23 + WalletType.monero,
24 + WalletType.bitcoin,
25 + WalletType.litecoin,
26 + WalletType.ethereum,
27 + WalletType.polygon
28 + ].contains(walletType);
29 + break;
30 }
31 +
32 + return isSupported
33 + ? (isIOS
34 + ? [DeviceConnectionType.ble]
35 + : [DeviceConnectionType.ble, DeviceConnectionType.usb])
36 + : [];
37 }
38
39 String get iconString {
cw_core/lib/hardware/hardware_wallet_service.dart new
+13
@@ -0,0 +1,13 @@
1 +import 'dart:async';
2 +import 'dart:typed_data';
3 +
4 +import 'package:cw_core/hardware/hardware_account_data.dart';
5 +
6 +abstract class HardwareWalletService {
7 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5});
8 +
9 + Future<Uint8List> signTransaction({required String transaction}) => throw UnimplementedError();
10 +
11 + Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
12 + throw UnimplementedError();
13 +}
cw_core/lib/utils/zpub.dart new
+46
@@ -0,0 +1,46 @@
1 +
2 +import 'dart:typed_data';
3 +
4 +import 'package:blockchain_utils/blockchain_utils.dart';
5 +
6 +String convertZpubToXpub(String zpub) {
7 + try {
8 + final decoded = Base58Decoder.checkDecode(zpub);
9 +
10 + if (decoded.length < 4) {
11 + throw ArgumentError('Invalid extended public key length');
12 + }
13 +
14 + final versionBytes = decoded.sublist(0, 4);
15 + final zpubVersionBytes = [0x04, 0xb2, 0x47, 0x46]; // zpub mainnet version
16 + final zpubTestnetVersionBytes = [0x04, 0x5f, 0x1c, 0xf6]; // vpub testnet version
17 +
18 + bool isZpub = listEquals(versionBytes, zpubVersionBytes);
19 + bool isVpub = listEquals(versionBytes, zpubTestnetVersionBytes);
20 +
21 + if (!isZpub && !isVpub) {
22 + return zpub;
23 + }
24 +
25 + final xpubVersionBytes = isZpub ?
26 + [0x04, 0x88, 0xb2, 0x1e] : // xpub mainnet
27 + [0x04, 0x35, 0x87, 0xcf]; // tpub testnet
28 +
29 + final newExtendedKey = Uint8List.fromList([
30 + ...xpubVersionBytes,
31 + ...decoded.sublist(4),
32 + ]);
33 +
34 + return Base58Encoder.checkEncode(newExtendedKey);
35 + } catch (e) {
36 + throw ArgumentError('Failed to convert zpub to xpub: $e');
37 + }
38 +}
39 +
40 +bool listEquals<T>(List<T> a, List<T> b) {
41 + if (a.length != b.length) return false;
42 + for (int i = 0; i < a.length; i++) {
43 + if (a[i] != b[i]) return false;
44 + }
45 + return true;
46 +}
cw_core/lib/wallet_base.dart
+2
@@ -62,6 +62,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
62
63 bool get isHardwareWallet => walletInfo.isHardwareWallet;
64
65 + HardwareWalletType? get hardwareWalletType => walletInfo.hardwareWalletType;
66 +
67 bool get hasRescan => false;
68
69 Future<void> connectToNode({required Node node});
cw_core/lib/wallet_info.dart
+12 -1
@@ -25,6 +25,16 @@ enum DerivationType {
25 enum HardwareWalletType {
26 @HiveField(0)
27 ledger,
28 + @HiveField(1)
29 + bitbox,
30 + @HiveField(2)
31 + cupcake,
32 + @HiveField(3)
33 + coldcard,
34 + @HiveField(4)
35 + seedsigner,
36 + @HiveField(5)
37 + keystone,
38 }
39
40 @HiveType(typeId: DerivationInfo.typeId)
@@ -224,7 +234,8 @@ class WalletInfo extends HiveObject {
234 return showIntroCakePayCard!;
235 }
236
227 - bool get isHardwareWallet => hardwareWalletType != null;
237 + bool get isHardwareWallet =>
238 + [HardwareWalletType.ledger, HardwareWalletType.bitbox].contains(hardwareWalletType);
239
240 DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
241
cw_core/pubspec.lock
+1 -1
@@ -47,7 +47,7 @@ packages:
47 source: hosted
48 version: "2.13.0"
49 blockchain_utils:
50 - dependency: transitive
50 + dependency: "direct main"
51 description:
52 path: "."
53 ref: cake-update-v2
cw_core/pubspec.yaml
+4
@@ -40,6 +40,10 @@ dependencies:
40 ref: e6232c53c1595469931ababa878759a067c02e94
41 torch_dart:
42 path: ../scripts/torch_dart
43 + blockchain_utils:
44 + git:
45 + url: https://github.com/cake-tech/blockchain_utils
46 + ref: cake-update-v2
47
48 dev_dependencies:
49 flutter_test:
cw_evm/lib/evm_chain_client.dart
+1
@@ -9,6 +9,7 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
9 import 'package:cw_evm/evm_chain_transaction_model.dart';
10 import 'package:cw_evm/evm_chain_transaction_priority.dart';
11 import 'package:cw_evm/evm_erc20_balance.dart';
12 +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
13 import 'package:cw_evm/pending_evm_chain_transaction.dart';
14 import 'package:cw_evm/.secrets.g.dart' as secrets;
15 import 'package:flutter/foundation.dart';
cw_evm/lib/evm_chain_wallet.dart
+8 -3
@@ -29,7 +29,8 @@ import 'package:cw_evm/evm_chain_transaction_history.dart';
29 import 'package:cw_evm/evm_chain_transaction_model.dart';
30 import 'package:cw_evm/evm_chain_transaction_priority.dart';
31 import 'package:cw_evm/evm_chain_wallet_addresses.dart';
32 -import 'package:cw_evm/evm_ledger_credentials.dart';
32 +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
33 +import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
34 import 'package:hex/hex.dart';
35 import 'package:hive/hive.dart';
36 import 'package:mobx/mobx.dart';
@@ -207,9 +208,12 @@ abstract class EVMChainWalletBase
208 // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯
209 await _checkForExistingScamTokens();
210
210 - if (walletInfo.isHardwareWallet) {
211 + if (walletInfo.hardwareWalletType == HardwareWalletType.ledger) {
212 _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
213 walletAddresses.address = walletInfo.address;
214 + } else if (walletInfo.hardwareWalletType == HardwareWalletType.bitbox) {
215 + _evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address);
216 + walletAddresses.address = walletInfo.address;
217 } else {
218 _evmChainPrivateKey = await getPrivateKey(
219 mnemonic: _mnemonic,
@@ -547,7 +551,8 @@ abstract class EVMChainWalletBase
551 }
552 }
553
550 - if (transactionCurrency is Erc20Token && isHardwareWallet) {
554 + if (transactionCurrency is Erc20Token &&
555 + walletInfo.hardwareWalletType == HardwareWalletType.ledger) {
556 await (_evmChainPrivateKey as EvmLedgerCredentials)
557 .provideERC20Info(transactionCurrency.contractAddress, _client.chainId);
558 }
cw_evm/lib/hardware/evm_bitbox_credentials.dart new
+79
@@ -0,0 +1,79 @@
1 +import 'dart:async';
2 +import 'dart:typed_data';
3 +
4 +import 'package:bitbox_flutter/bitbox_manager.dart';
5 +import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
6 +import 'package:web3dart/crypto.dart';
7 +import 'package:web3dart/web3dart.dart';
8 +
9 +class EvmBitboxCredentials extends CredentialsWithKnownAddress {
10 + final String _address;
11 +
12 + BitboxManager? bitboxManager;
13 + String? derivationPath;
14 +
15 + EvmBitboxCredentials(this._address);
16 +
17 + @override
18 + EthereumAddress get address => EthereumAddress.fromHex(_address);
19 +
20 + void setBitbox(BitboxManager connection, [String? derivationPath_]) {
21 + bitboxManager = connection;
22 + derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
23 + }
24 +
25 + @override
26 + MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
27 + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
28 +
29 + @override
30 + Future<MsgSignature> signToSignature(Uint8List payload,
31 + {int? chainId, bool isEIP1559 = false}) async {
32 + if (bitboxManager == null) {
33 + throw exception.DeviceNotConnectedException();
34 + }
35 +
36 + if (isEIP1559) payload = payload.sublist(1);
37 + final sig = await bitboxManager!
38 + .signETHRLPTransaction(chainId ?? 1, derivationPath!, bytesToHex(payload), isEIP1559);
39 +
40 + final r = bytesToHex(sig.sublist(0, 32));
41 + final s = bytesToHex(sig.sublist(32, 32 + 32));
42 + final v = sig.last.toInt();
43 +
44 + if (isEIP1559) {
45 + return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), v);
46 + }
47 +
48 + var truncChainId = chainId ?? 1;
49 + while (truncChainId.bitLength > 32) {
50 + truncChainId >>= 8;
51 + }
52 +
53 + final truncTarget = truncChainId * 2 + 35;
54 +
55 + int parity = v;
56 + if (truncTarget & 0xff == v) {
57 + parity = 0;
58 + } else if ((truncTarget + 1) & 0xff == v) {
59 + parity = 1;
60 + }
61 +
62 + // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
63 + final chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
64 +
65 + return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
66 + }
67 +
68 + @override
69 + Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
70 + if (isNotConnected) throw exception.DeviceNotConnectedException();
71 + return await bitboxManager!.signETHMessage(chainId ?? 1, derivationPath!, payload);
72 + }
73 +
74 + @override
75 + Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
76 + throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
77 +
78 + bool get isNotConnected => bitboxManager == null;
79 +}
cw_evm/lib/hardware/evm_chain_bitbox_service.dart new
+32
@@ -0,0 +1,32 @@
1 +import 'dart:async';
2 +
3 +import 'package:bitbox_flutter/bitbox_manager.dart';
4 +import 'package:cw_core/hardware/hardware_account_data.dart';
5 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
6 +
7 +class EVMChainBitboxService extends HardwareWalletService {
8 + EVMChainBitboxService(this.manager, {this.chainId = 1});
9 +
10 + final BitboxManager manager;
11 + final int chainId;
12 +
13 + @override
14 + Future<List<HardwareAccountData>> getAvailableAccounts(
15 + {int index = 0, int limit = 5}) async {
16 + final accounts = <HardwareAccountData>[];
17 + final indexRange = List.generate(limit, (i) => i + index);
18 +
19 + for (final i in indexRange) {
20 + final derivationPath = "m/44'/60'/0'/0/$i";
21 + final address = await manager.getETHAddress(chainId, derivationPath);
22 +
23 + accounts.add(HardwareAccountData(
24 + address: address,
25 + accountIndex: i,
26 + derivationPath: derivationPath,
27 + ));
28 + }
29 +
30 + return accounts;
31 + }
32 +}
cw_evm/lib/hardware/evm_chain_ledger_service.dart renamed
+4 -2
@@ -1,14 +1,16 @@
1 import 'dart:async';
2
3 import 'package:cw_core/hardware/hardware_account_data.dart';
4 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
5 import 'package:ledger_ethereum/ledger_ethereum.dart';
6 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
7
7 -class EVMChainHardwareWalletService {
8 - EVMChainHardwareWalletService(this.ledgerConnection);
8 +class EVMChainLedgerService extends HardwareWalletService {
9 + EVMChainLedgerService(this.ledgerConnection);
10
11 final LedgerConnection ledgerConnection;
12
13 + @override
14 Future<List<HardwareAccountData>> getAvailableAccounts(
15 {int index = 0, int limit = 5}) async {
16 final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
cw_evm/lib/hardware/evm_ledger_credentials.dart renamed
+2
@@ -76,6 +76,8 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
76
77 final sig = await ethereumLedgerApp!.signMessage(payload);
78
79 + if (sig.isEmpty) throw Exception("No Signature received from device!");
80 +
81 final r = sig.sublist(1, 1 + 32);
82 final s = sig.sublist(1 + 32, 1 + 32 + 32);
83 final v = [sig[0]];
cw_evm/pubspec.yaml
+2
@@ -31,6 +31,8 @@ dependencies:
31 url: https://github.com/cake-tech/ledger-flutter-plus-plugins
32 path: packages/ledger-ethereum
33 ref: f4761cd5171d4c1e2e42fd3298261650539fb2db
34 + bitbox_flutter:
35 + path: ../scripts/bitbox_flutter
36
37 dependency_overrides:
38 web3dart:
cw_monero/pubspec.lock
+5 -5
@@ -66,11 +66,11 @@ packages:
66 dependency: transitive
67 description:
68 path: "."
69 - ref: cake-update-v4
70 - resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a"
69 + ref: cake-update-v2
70 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
71 url: "https://github.com/cake-tech/blockchain_utils"
72 source: git
73 - version: "4.3.0"
73 + version: "3.3.0"
74 bluez:
75 dependency: transitive
76 description:
@@ -599,10 +599,10 @@ packages:
599 description:
600 path: "."
601 ref: cake-update-v2
602 - resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e"
602 + resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
603 url: "https://github.com/cake-tech/on_chain.git"
604 source: git
605 - version: "6.2.0"
605 + version: "3.7.0"
606 package_config:
607 dependency: transitive
608 description:
ios/Podfile.lock
+7
@@ -252,6 +252,13 @@ SPEC CHECKSUMS:
252 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
253 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
254 payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
255 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
256 + reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
257 + SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
258 + sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b
259 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
260 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
261 + sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5
262 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
263 reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089
264 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
lib/bitcoin/cw_bitcoin.dart
+22 -21
@@ -23,8 +23,10 @@ class CWBitcoin extends Bitcoin {
23 required String name,
24 required String password,
25 required String xpub,
26 + HardwareWalletType? hardwareWalletType,
27 }) =>
27 - BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub);
28 + BitcoinWalletFromKeysCredentials(
29 + name: name, password: password, xpub: xpub, hardwareWalletType: hardwareWalletType);
30
31 @override
32 WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
@@ -315,8 +317,12 @@ class CWBitcoin extends Bitcoin {
317 }
318
319 @override
318 - List<ReceivePageOption> getLitecoinReceivePageOptions() {
319 - if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
320 + List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet) {
321 + final litecoinWallet = wallet as ElectrumWallet;
322 + if (Platform.isLinux ||
323 + Platform.isMacOS ||
324 + Platform.isWindows ||
325 + litecoinWallet.isHardwareWallet) {
326 return BitcoinReceivePageOption.allLitecoin
327 .where((element) => element != BitcoinReceivePageOption.mweb)
328 .toList();
@@ -536,27 +542,22 @@ class CWBitcoin extends Bitcoin {
542 }
543
544 @override
539 - Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
540 - {int index = 0, int limit = 5}) async {
541 - final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
542 - try {
543 - return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
544 - } catch (err) {
545 - printV(err);
546 - throw err;
547 - }
545 + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
546 + (wallet as ElectrumWallet).hardwareWalletService = service;
547 }
548
549 @override
551 - Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
552 - {int index = 0, int limit = 5}) async {
553 - final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
554 - try {
555 - return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
556 - } catch (err) {
557 - printV(err);
558 - throw err;
559 - }
550 + HardwareWalletService getLedgerHardwareWalletService(
551 + ledger.LedgerConnection connection, bool isBitcoin) {
552 + if (isBitcoin) return BitcoinLedgerService(connection);
553 + return LitecoinLedgerService(connection);
554 + }
555 +
556 + @override
557 + HardwareWalletService getBitboxHardwareWalletService(
558 + bitbox.BitboxManager manager, bool isBitcoin) {
559 + if (isBitcoin) return BitcoinBitboxService(manager);
560 + return LitecoinBitboxService(manager);
561 }
562
563 @override
lib/buy/buy_provider.dart
+3 -3
@@ -4,7 +4,7 @@ import 'package:cake_wallet/order/order.dart';
4 import 'package:cake_wallet/buy/pairs_utils.dart';
5 import 'package:cake_wallet/buy/payment_method.dart';
6 import 'package:cake_wallet/entities/fiat_currency.dart';
7 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
7 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
8 import 'package:cw_core/crypto_currency.dart';
9 import 'package:cw_core/wallet_base.dart';
10 import 'package:flutter/material.dart';
@@ -13,14 +13,14 @@ abstract class BuyProvider {
13 BuyProvider({
14 required this.wallet,
15 required this.isTestEnvironment,
16 - required this.ledgerVM,
16 + required this.hardwareWalletVM,
17 required this.supportedCryptoList,
18 required this.supportedFiatList
19 });
20
21 final WalletBase wallet;
22 final bool isTestEnvironment;
23 - final LedgerViewModel? ledgerVM;
23 + final HardwareWalletViewModel? hardwareWalletVM;
24 final List<TradePair<dynamic, dynamic>> supportedCryptoList;
25 final List<TradePair<dynamic, dynamic>> supportedFiatList;
26
lib/buy/dfx/dfx_buy_provider.dart
+25 -28
@@ -10,8 +10,8 @@ import 'package:cake_wallet/generated/i18n.dart';
10 import 'package:cake_wallet/routes.dart';
11 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
12 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
13 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
14 import 'package:cake_wallet/utils/show_pop_up.dart';
14 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
15 import 'package:cw_core/crypto_currency.dart';
16 import 'package:cw_core/utils/print_verbose.dart';
17 import 'package:cw_core/utils/proxy_wrapper.dart';
@@ -24,16 +24,16 @@ class DFXBuyProvider extends BuyProvider {
24 DFXBuyProvider({
25 required WalletBase wallet,
26 bool isTestEnvironment = false,
27 - LedgerViewModel? ledgerVM,
27 + HardwareWalletViewModel? hardwareWalletVM,
28 }) : super(
29 - wallet: wallet,
30 - isTestEnvironment: isTestEnvironment,
31 - ledgerVM: ledgerVM,
32 - supportedCryptoList: supportedCryptoToFiatPairs(
33 - notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
34 - supportedFiatList: supportedFiatToCryptoPairs(
35 - notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto)
36 - );
29 + wallet: wallet,
30 + isTestEnvironment: isTestEnvironment,
31 + hardwareWalletVM: hardwareWalletVM,
32 + supportedCryptoList: supportedCryptoToFiatPairs(
33 + notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
34 + supportedFiatList: supportedFiatToCryptoPairs(
35 + notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
36 + );
37
38 static const _baseUrl = 'api.dfx.swiss';
39
@@ -104,8 +104,7 @@ class DFXBuyProvider extends BuyProvider {
104 // }
105
106 Future<String> auth(String walletAddress) async {
107 - final signMessage = await getSignature(
108 - await getSignMessage(walletAddress), walletAddress);
107 + final signMessage = await getSignature(await getSignMessage(walletAddress), walletAddress);
108
109 final requestBody = jsonEncode({
110 'wallet': walletName,
@@ -119,7 +118,6 @@ class DFXBuyProvider extends BuyProvider {
118 headers: {'Content-Type': 'application/json'},
119 body: requestBody,
120 );
122 -
121
122 if (response.statusCode == 201) {
123 final responseBody = jsonDecode(response.body);
@@ -139,14 +137,13 @@ class DFXBuyProvider extends BuyProvider {
137 case WalletType.polygon:
138 case WalletType.solana:
139 case WalletType.tron:
142 - final r = await wallet.signMessage(message);
143 - return r;
140 + return wallet.signMessage(message);
141 case WalletType.monero:
142 case WalletType.litecoin:
143 case WalletType.bitcoin:
144 case WalletType.bitcoinCash:
145 case WalletType.zano:
149 - return await wallet.signMessage(message, address: walletAddress);
146 + return wallet.signMessage(message, address: walletAddress);
147 default:
148 throw Exception("WalletType is not available for DFX ${wallet.type}");
149 }
@@ -336,16 +333,17 @@ class DFXBuyProvider extends BuyProvider {
333 required String cryptoCurrencyAddress,
334 String? countryCode}) async {
335 if (wallet.isHardwareWallet) {
339 - if (!ledgerVM!.isConnected) {
336 + if (!hardwareWalletVM!.isConnected) {
337 await Navigator.of(context).pushNamed(Routes.connectDevices,
338 arguments: ConnectDevicePageParams(
339 walletType: wallet.walletInfo.type,
343 - onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
344 - ledgerVM.setLedger(wallet);
340 + hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
341 + onConnectDevice: (context, hwwVM) {
342 + hwwVM.initWallet(wallet);
343 Navigator.of(context).pop();
344 }));
345 } else {
348 - ledgerVM!.setLedger(wallet);
346 + hardwareWalletVM!.initWallet(wallet);
347 }
348 }
349
@@ -370,14 +368,13 @@ class DFXBuyProvider extends BuyProvider {
368 }
369 } catch (e) {
370 await showPopUp<void>(
373 - context: context,
374 - builder: (BuildContext context) {
375 - return AlertWithOneAction(
376 - alertTitle: "DFX.swiss",
377 - alertContent: S.of(context).buy_provider_unavailable + ': $e',
378 - buttonText: S.of(context).ok,
379 - buttonAction: () => Navigator.of(context).pop());
380 - });
371 + context: context,
372 + builder: (context) => AlertWithOneAction(
373 + alertTitle: "DFX.swiss",
374 + alertContent: '${S.of(context).buy_provider_unavailable}: $e',
375 + buttonText: S.of(context).ok,
376 + buttonAction: () => Navigator.of(context).pop()),
377 + );
378 }
379 }
380
lib/buy/kryptonim/kryptonim.dart
+1 -1
@@ -20,7 +20,7 @@ class KryptonimBuyProvider extends BuyProvider {
20 : super(
21 wallet: wallet,
22 isTestEnvironment: isTestEnvironment,
23 - ledgerVM: null,
23 + hardwareWalletVM: null,
24 supportedCryptoList: supportedCryptoToFiatPairs(
25 notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
26 supportedFiatList: supportedFiatToCryptoPairs(
lib/buy/meld/meld_buy_provider.dart
+1 -1
@@ -22,7 +22,7 @@ class MeldBuyProvider extends BuyProvider {
22 : super(
23 wallet: wallet,
24 isTestEnvironment: isTestEnvironment,
25 - ledgerVM: null,
25 + hardwareWalletVM: null,
26 supportedCryptoList: supportedCryptoToFiatPairs(
27 notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
28 supportedFiatList: supportedFiatToCryptoPairs(
lib/buy/moonpay/moonpay_provider.dart
+1 -1
@@ -36,7 +36,7 @@ class MoonPayProvider extends BuyProvider {
36 super(
37 wallet: wallet,
38 isTestEnvironment: isTestEnvironment,
39 - ledgerVM: null,
39 + hardwareWalletVM: null,
40 supportedCryptoList: supportedCryptoToFiatPairs(
41 notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
42 supportedFiatList: supportedFiatToCryptoPairs(
lib/buy/onramper/onramper_buy_provider.dart
+1 -2
@@ -9,7 +9,6 @@ import 'package:cake_wallet/buy/payment_method.dart';
9 import 'package:cake_wallet/entities/fiat_currency.dart';
10 import 'package:cake_wallet/generated/i18n.dart';
11 import 'package:cake_wallet/themes/core/theme_store.dart';
12 -import 'package:cake_wallet/store/settings_store.dart';
12 import 'package:cw_core/utils/proxy_wrapper.dart';
13 import 'package:cw_core/crypto_currency.dart';
14 import 'package:cw_core/utils/print_verbose.dart';
@@ -22,7 +21,7 @@ class OnRamperBuyProvider extends BuyProvider {
21 {required WalletBase wallet, bool isTestEnvironment = false})
22 : super(wallet: wallet,
23 isTestEnvironment: isTestEnvironment,
25 - ledgerVM: null,
24 + hardwareWalletVM: null,
25 supportedCryptoList: supportedCryptoToFiatPairs(
26 notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
27 supportedFiatList: supportedFiatToCryptoPairs(
lib/buy/robinhood/robinhood_buy_provider.dart
+33 -30
@@ -11,9 +11,9 @@ import 'package:cake_wallet/generated/i18n.dart';
11 import 'package:cake_wallet/routes.dart';
12 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
13 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
14 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
15 import 'package:cw_core/utils/proxy_wrapper.dart';
16 import 'package:cake_wallet/utils/show_pop_up.dart';
16 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
17 import 'package:cw_core/crypto_currency.dart';
18 import 'package:cw_core/utils/print_verbose.dart';
19 import 'package:cw_core/wallet_base.dart';
@@ -22,16 +22,19 @@ import 'package:flutter/material.dart';
22 import 'package:url_launcher/url_launcher.dart';
23
24 class RobinhoodBuyProvider extends BuyProvider {
25 - RobinhoodBuyProvider(
26 - {required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
27 - : super(
28 - wallet: wallet,
29 - isTestEnvironment: isTestEnvironment,
30 - ledgerVM: ledgerVM,
31 - supportedCryptoList: supportedCryptoToFiatPairs(
32 - notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
33 - supportedFiatList: supportedFiatToCryptoPairs(
34 - notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto));
25 + RobinhoodBuyProvider({
26 + required WalletBase wallet,
27 + bool isTestEnvironment = false,
28 + HardwareWalletViewModel? hardwareWalletVM,
29 + }) : super(
30 + wallet: wallet,
31 + isTestEnvironment: isTestEnvironment,
32 + hardwareWalletVM: hardwareWalletVM,
33 + supportedCryptoList: supportedCryptoToFiatPairs(
34 + notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
35 + supportedFiatList: supportedFiatToCryptoPairs(
36 + notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
37 + );
38
39 static const _baseUrl = 'applink.robinhood.com';
40 static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
@@ -69,8 +72,8 @@ class RobinhoodBuyProvider extends BuyProvider {
72 final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId});
73
74 try {
72 - final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
73 -
75 + final response =
76 + await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
77
78 if (response.statusCode == 200) {
79 final responseData = jsonDecode(response.body) as Map<String, dynamic>;
@@ -104,11 +107,11 @@ class RobinhoodBuyProvider extends BuyProvider {
107 case WalletType.polygon:
108 case WalletType.solana:
109 case WalletType.tron:
107 - return await wallet.signMessage(message);
110 + return wallet.signMessage(message);
111 case WalletType.litecoin:
112 case WalletType.bitcoin:
113 case WalletType.bitcoinCash:
111 - return await wallet.signMessage(message, address: wallet.walletAddresses.address);
114 + return wallet.signMessage(message, address: wallet.walletAddresses.address);
115 default:
116 throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood");
117 }
@@ -123,14 +126,14 @@ class RobinhoodBuyProvider extends BuyProvider {
126
127 final uri = Uri.https(_cIdBaseUrl, "/api/robinhood");
128
126 - var response = await ProxyWrapper().post(
129 + final response = await ProxyWrapper().post(
130 clearnetUri: uri,
131 headers: {'Content-Type': 'application/json'},
129 - body: json.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
132 + body: json
133 + .encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
134 );
135
136 if (response.statusCode == 200) {
133 -
137 return (jsonDecode(response.body) as Map<String, dynamic>)['connectId'] as String;
138 } else {
139 throw Exception('Provider currently unavailable. Status: ${response.statusCode}');
@@ -158,16 +161,17 @@ class RobinhoodBuyProvider extends BuyProvider {
161 required String cryptoCurrencyAddress,
162 String? countryCode}) async {
163 if (wallet.isHardwareWallet) {
161 - if (!ledgerVM!.isConnected) {
164 + if (!hardwareWalletVM!.isConnected) {
165 await Navigator.of(context).pushNamed(Routes.connectDevices,
166 arguments: ConnectDevicePageParams(
167 walletType: wallet.walletInfo.type,
165 - onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
166 - ledgerVM.setLedger(wallet);
168 + hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
169 + onConnectDevice: (context, hwwVM) {
170 + hwwVM.initWallet(wallet);
171 Navigator.of(context).pop();
172 }));
173 } else {
170 - ledgerVM!.setLedger(wallet);
174 + hardwareWalletVM!.initWallet(wallet);
175 }
176 }
177
@@ -176,14 +180,13 @@ class RobinhoodBuyProvider extends BuyProvider {
180 await launchUrl(uri, mode: LaunchMode.externalApplication);
181 } catch (e) {
182 await showPopUp<void>(
179 - context: context,
180 - builder: (BuildContext context) {
181 - return AlertWithOneAction(
182 - alertTitle: "Robinhood Connect",
183 - alertContent: e.toString(),
184 - buttonText: S.of(context).ok,
185 - buttonAction: () => Navigator.of(context).pop());
186 - });
183 + context: context,
184 + builder: (context) => AlertWithOneAction(
185 + alertTitle: "Robinhood Connect",
186 + alertContent: '${S.of(context).buy_provider_unavailable}: $e',
187 + buttonText: S.of(context).ok,
188 + buttonAction: () => Navigator.of(context).pop()),
189 + );
190 }
191 }
192
lib/buy/wyre/wyre_buy_provider.dart
+1 -1
@@ -20,7 +20,7 @@ class WyreBuyProvider extends BuyProvider {
20 super(
21 wallet: wallet,
22 isTestEnvironment: isTestEnvironment,
23 - ledgerVM: null,
23 + hardwareWalletVM: null,
24 supportedCryptoList: supportedCryptoToFiatPairs(
25 notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
26 supportedFiatList: supportedFiatToCryptoPairs(
lib/core/new_wallet_type_arguments.dart
+3 -2
@@ -1,14 +1,15 @@
1 +import 'package:cw_core/wallet_info.dart';
2 import 'package:cw_core/wallet_type.dart';
3 import 'package:flutter/material.dart';
4
5 class NewWalletTypeArguments {
6 final void Function(BuildContext, WalletType)? onTypeSelected;
7 final bool isCreate;
7 - final bool isHardwareWallet;
8 + final HardwareWalletType? hardwareWalletType;
9
10 NewWalletTypeArguments({
11 required this.onTypeSelected,
12 required this.isCreate,
12 - required this.isHardwareWallet,
13 + this.hardwareWalletType,
14 });
15 }
lib/di.dart
+54 -21
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
36 import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
37 import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
38 import 'package:cake_wallet/src/screens/dev/network_requests.dart';
39 +import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
40 import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
41 import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
42 import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
@@ -53,6 +54,8 @@ import 'package:cake_wallet/themes/core/theme_store.dart';
54 import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
55 import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
56 import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
57 +import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
58 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
59 import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
60 import 'package:cake_wallet/view_model/link_view_model.dart';
61 import 'package:cake_wallet/tron/tron.dart';
@@ -393,8 +396,22 @@ Future<void> setup({
396 AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
397 getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
398
399 + getIt.registerFactoryParam<HardwareWalletViewModel, HardwareWalletType, void>((type, _) {
400 + switch(type) {
401 + case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
402 + case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
403 + case HardwareWalletType.cupcake:
404 + case HardwareWalletType.coldcard:
405 + case HardwareWalletType.seedsigner:
406 + case HardwareWalletType.keystone:
407 + throw Exception("This should not have happened, because airgapped Wallets don't need View Models");
408 + }
409 + });
410 +
411 getIt.registerLazySingleton(() => LedgerViewModel());
412
413 + getIt.registerLazySingleton(() => BitboxViewModel());
414 +
415 final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
416
417 getIt.registerSingleton<SecretStore>(secretStore);
@@ -505,9 +522,9 @@ Future<void> setup({
522 walletType: args.walletType ?? currentWalletType);
523 });
524
508 - getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, void>((type, _) =>
509 - WalletHardwareRestoreViewModel(
510 - getIt.get<LedgerViewModel>(),
525 + getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, HardwareWalletViewModel>(
526 + (type, hardwareWalletVM) => WalletHardwareRestoreViewModel(
527 + hardwareWalletVM,
528 getIt.get<AppStore>(),
529 getIt.get<WalletCreationService>(param1: type),
530 _walletInfoSource,
@@ -816,7 +833,10 @@ Future<void> setup({
833 getIt.get<BalanceViewModel>(),
834 getIt.get<ContactListViewModel>(),
835 _transactionDescriptionBox,
819 - getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
836 + getIt.get<AppStore>().wallet!.isHardwareWallet
837 + ? getIt<HardwareWalletViewModel>(
838 + param1: getIt.get<AppStore>().wallet!.hardwareWalletType!)
839 + : null,
840 coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb,
841 getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
842 getIt.get<FeesViewModel>(),
@@ -1077,14 +1097,18 @@ Future<void> setup({
1097 isSelected: isSelected));
1098
1099 getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
1080 - wallet: getIt.get<AppStore>().wallet!,
1081 - ledgerVM:
1082 - getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
1100 + wallet: getIt.get<AppStore>().wallet!,
1101 + hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
1102 + ? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
1103 + : null,
1104 + ));
1105
1106 getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
1085 - wallet: getIt.get<AppStore>().wallet!,
1086 - ledgerVM:
1087 - getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
1107 + wallet: getIt<AppStore>().wallet!,
1108 + hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
1109 + ? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
1110 + : null,
1111 + ));
1112
1113 getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
1114 appStore: getIt.get<AppStore>(),
@@ -1235,19 +1259,25 @@ Future<void> setup({
1259
1260 getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
1261
1238 - getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, RestoredWallet?>((WalletType type,
1239 - restoredWallet) =>
1240 - WalletRestoreViewModel(getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type),
1241 - _walletInfoSource, getIt.get<SeedSettingsViewModel>(),
1242 - type: type, restoredWallet: restoredWallet));
1243 -
1244 - getIt.registerFactoryParam<WalletRestorePage, WalletType, RestoredWallet?>((WalletType type,
1245 - restoredWallet) {
1246 - return WalletRestorePage(
1247 - getIt.get<WalletRestoreViewModel>(param1: type, param2: restoredWallet),
1248 - getIt.get<SeedSettingsViewModel>());
1262 + getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
1263 + (type, additionalParams) {
1264 + final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
1265 + final hardwareWalletType = additionalParams?['hardwareWalletType'] as HardwareWalletType?;
1266 +
1267 + return WalletRestoreViewModel(
1268 + getIt.get<AppStore>(),
1269 + getIt.get<WalletCreationService>(param1: type),
1270 + _walletInfoSource,
1271 + getIt.get<SeedSettingsViewModel>(),
1272 + type: type,
1273 + restoredWallet: restoredWallet,
1274 + hardwareWalletType: hardwareWalletType);
1275 });
1276
1277 + getIt.registerFactoryParam<WalletRestorePage, WalletType, Map<String, dynamic>?>((type, additionalParams) =>
1278 + WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type, param2: additionalParams),
1279 + getIt.get<SeedSettingsViewModel>()));
1280 +
1281 getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
1282 (derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
1283
@@ -1565,9 +1595,12 @@ Future<void> setup({
1595 getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
1596
1597 getIt.registerFactory(() => SocketHealthLogsViewModel());
1598 +
1599 getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get<SocketHealthLogsViewModel>()));
1600
1601 getIt.registerFactory(() => DevNetworkRequests());
1602 +
1603 + getIt.registerFactory(() => DevQRToolsPage());
1604
1605 getIt.registerFactory(() => ExchangeProviderLogsViewModel());
1606 getIt.registerFactory(() => DevExchangeProviderLogsPage(getIt.get<ExchangeProviderLogsViewModel>()));
lib/entities/hardware_wallet/hardware_wallet_device.dart
+44 -19
@@ -1,22 +1,44 @@
1 +import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
2 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
3
3 -class HardwareWalletDevice {
4 - final String name;
5 - final HardwareWalletDeviceType type;
6 - final HardwareWalletConnectionType connectionType;
7 -
8 - const HardwareWalletDevice({
9 - required this.name,
10 - required this.type,
11 - required this.connectionType,
12 - });
13 -
14 - factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
15 - HardwareWalletDevice(
16 - name: device.name,
17 - type: device.deviceInfo.toGeneric(),
18 - connectionType: device.connectionType.toGeneric(),
19 - );
4 +abstract class HardwareWalletDevice {
5 + String get name;
6 +
7 + HardwareWalletDeviceType get type;
8 +
9 + HardwareWalletConnectionType get connectionType;
10 +}
11 +
12 +class LedgerHardwareWalletDevice extends HardwareWalletDevice {
13 + final ledger.LedgerDevice device;
14 +
15 + LedgerHardwareWalletDevice(this.device);
16 +
17 + @override
18 + String get name => device.name;
19 +
20 + @override
21 + HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
22 +
23 + @override
24 + HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
25 +}
26 +
27 +class BitboxHardwareWalletDevice extends HardwareWalletDevice {
28 + final bitbox.BitboxDevice device;
29 +
30 + BitboxHardwareWalletDevice(this.device);
31 +
32 + @override
33 + String get name => device.productName;
34 +
35 + @override
36 + HardwareWalletDeviceType get type => name.contains("BitBox02 Nova")
37 + ? HardwareWalletDeviceType.BitBox02Nova
38 + : HardwareWalletDeviceType.BitBox02;
39 +
40 + @override
41 + HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
42 }
43
44 enum HardwareWalletDeviceType {
@@ -25,13 +47,16 @@ enum HardwareWalletDeviceType {
47 ledgerNanoX,
48 ledgerNanoSPlus,
49 ledgerStax,
28 - ledgerFlex;
50 + ledgerFlex,
51 + BitBox02,
52 + BitBox02Nova;
53 }
54
55 enum HardwareWalletConnectionType {
56 usb,
57 ble,
34 - nfc;
58 + nfc,
59 + qr;
60 }
61
62 extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
lib/ethereum/cw_ethereum.dart
+13 -13
@@ -181,22 +181,22 @@ class CWEthereum extends Ethereum {
181 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
182
183 @override
184 - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
185 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
186 - .setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath);
184 + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
185 + if (service is EVMChainLedgerService) {
186 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
187 + service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
188 + } else if (service is EVMChainBitboxService) {
189 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
190 + .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
191 + }
192 }
193 + @override
194 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
195 + EVMChainLedgerService(connection);
196
197 @override
190 - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
191 - {int index = 0, int limit = 5}) async {
192 - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
193 - try {
194 - return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
195 - } catch (err) {
196 - printV(err);
197 - throw err;
198 - }
199 - }
198 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
199 + EVMChainBitboxService(manager);
200
201 @override
202 List<String> getDefaultTokenContractAddresses() {
lib/polygon/cw_polygon.dart
+14 -18
@@ -199,27 +199,23 @@ class CWPolygon extends Polygon {
199 );
200
201 @override
202 - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
203 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
204 - connection,
205 - wallet.walletInfo.derivationInfo?.derivationPath,
206 - );
202 + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
203 + if (service is EVMChainLedgerService) {
204 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
205 + service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
206 + } else if (service is EVMChainBitboxService) {
207 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
208 + .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
209 + }
210 }
211
212 @override
210 - Future<List<HardwareAccountData>> getHardwareWalletAccounts(
211 - LedgerViewModel ledgerVM, {
212 - int index = 0,
213 - int limit = 5,
214 - }) async {
215 - final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
216 - try {
217 - return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
218 - } catch (err) {
219 - printV(err);
220 - throw err;
221 - }
222 - }
213 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
214 + EVMChainLedgerService(connection);
215 +
216 + @override
217 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
218 + EVMChainBitboxService(manager, chainId: 137);
219
220 @override
221 List<String> getDefaultTokenContractAddresses() =>
lib/reactions/on_authentication_state_change.dart
+5 -1
@@ -14,6 +14,8 @@ import 'package:cake_wallet/store/authentication_store.dart';
14 import 'package:cake_wallet/store/settings_store.dart';
15 import 'package:cake_wallet/utils/exception_handler.dart';
16 import 'package:cake_wallet/utils/show_pop_up.dart';
17 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
18 +import 'package:cw_core/wallet_info.dart';
19 import 'package:cw_core/wallet_type.dart';
20 import 'package:flutter/widgets.dart';
21 import 'package:mobx/mobx.dart';
@@ -60,8 +62,10 @@ void startAuthenticationStateChange(
62 (route) => false,
63 arguments: ConnectDevicePageParams(
64 walletType: WalletType.monero,
65 + hardwareWalletType: HardwareWalletType.ledger,
66 onConnectDevice: (context, ledgerVM) async {
64 - monero!.setGlobalLedgerConnection(ledgerVM.connection);
67 + if (ledgerVM is LedgerViewModel)
68 + monero!.setGlobalLedgerConnection(ledgerVM.connection);
69 showPopUp<void>(
70 context: context,
71 builder: (context) => AlertWithOneAction(
lib/router.dart
+45 -18
@@ -23,6 +23,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
23 import 'package:cake_wallet/cake_pay/cake_pay.dart';
24 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
25 import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
26 +import 'package:cake_wallet/src/screens/connect_device/select_device_manufacturer_page.dart';
27 import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
28 import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
29 import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@@ -39,6 +40,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
40 import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
41 import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
42 import 'package:cake_wallet/src/screens/dev/network_requests.dart';
43 +import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
44 import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
45 import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
46 import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
@@ -130,6 +132,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
132 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
133 import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
134 import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
135 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
136 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
137 import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
138 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@@ -213,7 +216,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
216 arguments: NewWalletArguments(type: type),
217 ),
218 isCreate: true,
216 - isHardwareWallet: false,
219 ),
220 ),
221 );
@@ -245,12 +247,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
247 case Routes.chooseHardwareWalletAccount:
248 final arguments = settings.arguments as List<dynamic>;
249 final type = arguments[0] as WalletType;
248 - final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
250 + final hardwareWallet = arguments [1] as HardwareWalletType;
251 +
252 + final walletVM = getIt.get<WalletHardwareRestoreViewModel>(
253 + param1: type, param2: getIt<HardwareWalletViewModel>(param1: hardwareWallet));
254
255 if (type == WalletType.monero)
251 - return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
256 + return handleRouteWithPlatformAwareness((_) => MoneroHardwareWalletOptionsPage(walletVM));
257
253 - return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
258 + return handleRouteWithPlatformAwareness((_) => SelectHardwareWalletAccountPage(walletVM));
259
260 case Routes.setupPin:
261 Function(PinCodeState<PinCodeWidget>, String)? callback;
@@ -272,7 +277,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
277 Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
278 },
279 isCreate: false,
275 - isHardwareWallet: false,
280 ),
281 ),
282 );
@@ -301,41 +305,59 @@ Route<dynamic> createRoute(RouteSettings settings) {
305 Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
306 },
307 isCreate: false,
304 - isHardwareWallet: false,
308 ),
309 ),
310 );
311
312 case Routes.restoreWalletFromHardwareWallet:
313 + final arguments = settings.arguments as Map<String, dynamic>?;
314 + final showUnavailable = (arguments?['showUnavailable'] as bool?) ?? true;
315 + final onSelect = arguments?['onSelect'] as void Function(BuildContext, HardwareWalletType)?;
316 + final availableHardwareWalletTypes =
317 + arguments?['availableHardwareWalletTypes'] as List<HardwareWalletType>?;
318 +
319 + return handleRouteWithPlatformAwareness((_) => SelectDeviceManufacturerPage(
320 + showUnavailable: showUnavailable,
321 + onSelect: onSelect,
322 + availableHardwareWalletTypes: availableHardwareWalletTypes,
323 + ));
324 +
325 + case Routes.connectHardwareWallet:
326 + final arguments = settings.arguments as List<dynamic>;
327 + final hardwareWalletType = (arguments[0] as HardwareWalletType?) ?? HardwareWalletType.ledger;
328 +
329 if (isSingleCoin) {
311 - return MaterialPageRoute<void>(
312 - builder: (_) => ConnectDevicePage(
330 + return handleRouteWithPlatformAwareness(
331 + (_) => ConnectDevicePage(
332 ConnectDevicePageParams(
333 walletType: availableWalletTypes.first,
334 + hardwareWalletType: hardwareWalletType,
335 onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
336 Routes.chooseHardwareWalletAccount,
317 - arguments: [availableWalletTypes.first]),
337 + arguments: [availableWalletTypes.first, hardwareWalletType]),
338 isReconnect: false,
339 ),
340 getIt.get<LedgerViewModel>(),
341 ),
342 );
343 }
324 - return CupertinoPageRoute<void>(
325 - builder: (_) => getIt.get<NewWalletTypePage>(
344 + return handleRouteWithPlatformAwareness(
345 + (_) => getIt.get<NewWalletTypePage>(
346 param1: NewWalletTypeArguments(
347 onTypeSelected: (BuildContext context, WalletType type) {
348 final arguments = ConnectDevicePageParams(
349 walletType: type,
330 - onConnectDevice: (BuildContext context, _) => Navigator.of(context)
331 - .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]),
350 + hardwareWalletType: hardwareWalletType,
351 + onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
352 + Routes.chooseHardwareWalletAccount,
353 + arguments: [type, hardwareWalletType]),
354 isReconnect: false,
355 );
356
357 Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
358 },
359 isCreate: false,
338 - isHardwareWallet: true,
360 + hardwareWalletType: hardwareWalletType,
361 ),
362 ),
363 );
@@ -347,7 +369,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
369 onTypeSelected: (BuildContext context, WalletType type) =>
370 Navigator.of(context).pop(type),
371 isCreate: false,
350 - isHardwareWallet: false,
372 ),
373 ),
374 );
@@ -360,9 +381,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
381 case Routes.restoreWallet:
382 final args = settings.arguments as Map<String, dynamic>?;
383 final walletType = args?['walletType'] as WalletType;
363 - final restoredWallet = args?['restoredWallet'] as RestoredWallet?;
384 return MaterialPageRoute<void>(
365 - builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: restoredWallet));
385 + builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: args));
386
387 case Routes.restoreWalletChooseDerivation:
388 return MaterialPageRoute<void>(
@@ -873,8 +893,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
893
894 case Routes.connectDevices:
895 final params = settings.arguments as ConnectDevicePageParams;
896 +
897 return MaterialPageRoute<void>(
877 - builder: (_) => ConnectDevicePage(params, getIt.get<LedgerViewModel>()));
898 + builder: (_) => ConnectDevicePage(
899 + params, getIt.get<HardwareWalletViewModel>(param1: params.hardwareWalletType)));
900
901 case Routes.walletGroupDescription:
902 final walletType = settings.arguments as WalletType;
@@ -920,6 +942,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
942 builder: (_) => getIt.get<DevSocketHealthLogsPage>(),
943 );
944
945 + case Routes.devQRTools:
946 + return MaterialPageRoute<void>(
947 + builder: (_) => getIt.get<DevQRToolsPage>(),
948 + );
949 +
950 case Routes.devNetworkRequests:
951 return MaterialPageRoute<void>(
952 builder: (_) => getIt.get<DevNetworkRequests>(),
lib/routes.dart
+2
@@ -11,6 +11,7 @@ class Routes {
11 static const restoreWalletFromHardwareWallet = '/restore/hardware_wallet';
12 static const restoreWalletTypeFromQR = '/restore_wallet_from_qr_code';
13 static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
14 + static const connectHardwareWallet = '/restore/hardware_wallet/devices';
15 static const chooseHardwareWalletAccount = '/restore/hardware_wallet/accounts';
16 static const dashboard = '/dashboard';
17 static const send = '/send';
@@ -123,6 +124,7 @@ class Routes {
124 static const devBackgroundSyncLogs = '/dev/background_sync_logs';
125 static const devSocketHealthLogs = '/dev/socket_health_logs';
126 static const devNetworkRequests = '/dev/network_requests';
127 + static const devQRTools = '/dev/qr_tools';
128 static const devExchangeProviderLogs = '/dev/exchange_provider_logs';
129
130 static const signPage = '/sign_page';
lib/src/screens/connect_device/connect_device_page.dart
+69 -76
@@ -1,6 +1,7 @@
1 import 'dart:async';
2 import 'dart:io';
3
4 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/routes.dart';
7 import 'package:cake_wallet/src/screens/base_page.dart';
@@ -9,23 +10,25 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_wid
10 import 'package:cake_wallet/src/widgets/primary_button.dart';
11 import 'package:cake_wallet/themes/core/material_base_theme.dart';
12 import 'package:cake_wallet/utils/responsive_layout_util.dart';
12 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
13 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
14 import 'package:cw_core/utils/print_verbose.dart';
15 +import 'package:cw_core/wallet_info.dart';
16 import 'package:cw_core/wallet_type.dart';
17 import 'package:flutter/material.dart';
18 import 'package:flutter_mobx/flutter_mobx.dart';
17 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
19
19 -typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
20 +typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel);
21
22 class ConnectDevicePageParams {
23 final WalletType walletType;
24 final OnConnectDevice onConnectDevice;
25 final bool allowChangeWallet;
26 final bool isReconnect;
27 + final HardwareWalletType hardwareWalletType;
28
29 ConnectDevicePageParams({
30 required this.walletType,
31 + required this.hardwareWalletType,
32 required this.onConnectDevice,
33 this.allowChangeWallet = false,
34 this.isReconnect = true,
@@ -37,9 +40,9 @@ class ConnectDevicePage extends BasePage {
40 final OnConnectDevice onConnectDevice;
41 final bool allowChangeWallet;
42 final bool isReconnect;
40 - final LedgerViewModel ledgerVM;
43 + final HardwareWalletViewModel hardwareWalletVM;
44
42 - ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
45 + ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
46 : walletType = params.walletType,
47 onConnectDevice = params.onConnectDevice,
48 allowChangeWallet = params.allowChangeWallet,
@@ -51,8 +54,7 @@ class ConnectDevicePage extends BasePage {
54 : S.current.restore_title_from_hardware_wallet;
55
56 @override
54 - Widget? leading(BuildContext context) =>
55 - !isReconnect ? super.leading(context) : null;
57 + Widget? leading(BuildContext context) => !isReconnect ? super.leading(context) : null;
58
59 @override
60 Widget body(BuildContext context) => PopScope(
@@ -61,7 +63,7 @@ class ConnectDevicePage extends BasePage {
63 walletType,
64 onConnectDevice,
65 allowChangeWallet,
64 - ledgerVM,
66 + hardwareWalletVM,
67 currentTheme,
68 ));
69 }
@@ -70,14 +72,14 @@ class ConnectDevicePageBody extends StatefulWidget {
72 final WalletType walletType;
73 final OnConnectDevice onConnectDevice;
74 final bool allowChangeWallet;
73 - final LedgerViewModel ledgerVM;
75 + final HardwareWalletViewModel hardwareWalletVM;
76 final MaterialThemeBase currentTheme;
77
78 const ConnectDevicePageBody(
79 this.walletType,
80 this.onConnectDevice,
81 this.allowChangeWallet,
80 - this.ledgerVM,
82 + this.hardwareWalletVM,
83 this.currentTheme,
84 );
85
@@ -86,13 +88,13 @@ class ConnectDevicePageBody extends StatefulWidget {
88 }
89
90 class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
89 - var bleDevices = <LedgerDevice>[];
90 - var usbDevices = <LedgerDevice>[];
91 + var bleDevices = <HardwareWalletDevice>[];
92 + var usbDevices = <HardwareWalletDevice>[];
93
94 late Timer? _usbRefreshTimer = null;
95 late Timer? _bleRefreshTimer = null;
96 late Timer? _bleStateTimer = null;
95 - late StreamSubscription<LedgerDevice>? _bleRefresh = null;
97 + late StreamSubscription<HardwareWalletDevice>? _bleRefresh = null;
98
99 bool longWait = false;
100 Timer? _longWaitTimer;
@@ -101,21 +103,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
103 void initState() {
104 super.initState();
105 WidgetsBinding.instance.addPostFrameCallback((_) {
104 - _bleStateTimer = Timer.periodic(
105 - Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
106 + _bleStateTimer =
107 + Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState());
108
107 - _bleRefreshTimer =
108 - Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
109 + _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
110
111 if (Platform.isAndroid) {
111 - _usbRefreshTimer =
112 - Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
112 + _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
113 }
114
115 - _longWaitTimer = Timer(Duration(seconds: 10), () {
116 - if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
117 - setState(() => longWait = true);
118 - });
115 + if (widget.hardwareWalletVM.hasBluetooth) {
116 + _longWaitTimer = Timer(Duration(seconds: 10), () {
117 + if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty)
118 + setState(() => longWait = true);
119 + });
120 + }
121 });
122 }
123
@@ -127,35 +129,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
129 _bleRefresh?.cancel();
130 _longWaitTimer?.cancel();
131
130 - widget.ledgerVM.stopScanning();
132 + widget.hardwareWalletVM.stopScanning();
133 super.dispose();
134 }
135
136 Future<void> _refreshUsbDevices() async {
135 - final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
137 + final dev = await widget.hardwareWalletVM.getAllUsbDevices();
138 if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
137 - // _usbRefresh = widget.ledgerVM
138 - // .scanForUsbDevices()
139 - // .listen((device) => setState(() => usbDevices.add(device)))
140 - // ..onError((e) {
141 - // throw e.toString();
142 - // });
143 - // Keep polling until the lfp lib gets updated
144 - // _usbRefreshTimer?.cancel();
145 - // _usbRefreshTimer = null;
139 }
140
141 Future<void> _refreshBleDevices() async {
142 try {
150 - if (widget.ledgerVM.bleIsEnabled) {
151 - _bleRefresh =
152 - widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
153 - bleDevices.add(device);
154 - if (longWait) longWait = false;
155 - }))
156 - ..onError((e) {
157 - throw e.toString();
158 - });
143 + if (widget.hardwareWalletVM.isBleEnabled) {
144 + _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() {
145 + bleDevices.add(device);
146 + if (longWait) longWait = false;
147 + }))
148 + ..onError((e) {
149 + throw e.toString();
150 + });
151 _bleRefreshTimer?.cancel();
152 _bleRefreshTimer = null;
153 }
@@ -164,25 +156,35 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
156 }
157 }
158
167 - Future<void> _connectToDevice(LedgerDevice device) async {
168 - final isConnected =
169 - await widget.ledgerVM.connectLedger(device, widget.walletType);
170 - if (isConnected) widget.onConnectDevice(context, widget.ledgerVM);
159 + Future<void> _connectToDevice(HardwareWalletDevice device) async {
160 + final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
161 + if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
162 }
163
173 - String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
174 - switch (deviceInfo) {
175 - case LedgerDeviceType.nanoX:
164 + String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
165 + switch (deviceType) {
166 + case HardwareWalletDeviceType.ledgerNanoX:
167 return 'assets/images/hardware_wallet/ledger_nano_x.png';
177 - case LedgerDeviceType.stax:
168 + case HardwareWalletDeviceType.ledgerStax:
169 return 'assets/images/hardware_wallet/ledger_stax.png';
179 - case LedgerDeviceType.flex:
170 + case HardwareWalletDeviceType.ledgerFlex:
171 return 'assets/images/hardware_wallet/ledger_flex.png';
172 + case HardwareWalletDeviceType.BitBox02:
173 + return 'assets/images/hardware_wallet/device_bitbox.svg';
174 +
175 default:
176 return 'assets/images/hardware_wallet/ledger_nano_x.png';
177 }
178 }
179
180 + String get description {
181 + if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble;
182 +
183 + if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet;
184 +
185 + return S.of(context).connect_your_hardware_wallet_usb;
186 + }
187 +
188 @override
189 Widget build(BuildContext context) {
190 return Container(
@@ -197,11 +199,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
199 Padding(
200 padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
201 child: Text(
200 - Platform.isAndroid
201 - ? S.of(context).connect_your_hardware_wallet
202 - : S.of(context).connect_your_hardware_wallet_ios,
203 - style: Theme.of(context)
204 - .textTheme.titleMedium,
202 + description,
203 + style: Theme.of(context).textTheme.titleMedium,
204 textAlign: TextAlign.center,
205 ),
206 ),
@@ -211,22 +210,20 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
210 padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
211 child: Text(
212 S.of(context).if_you_dont_see_your_device,
214 - style: Theme.of(context)
215 - .textTheme.titleMedium,
213 + style: Theme.of(context).textTheme.titleMedium,
214 textAlign: TextAlign.center,
215 ),
216 ),
217 ),
218 Observer(
219 builder: (_) => Offstage(
222 - offstage: widget.ledgerVM.bleIsEnabled,
220 + offstage: widget.hardwareWalletVM.isBleEnabled ||
221 + !widget.hardwareWalletVM.hasBluetooth,
222 child: Padding(
224 - padding:
225 - EdgeInsets.only(left: 20, right: 20, bottom: 20),
223 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
224 child: Text(
225 S.of(context).ledger_please_enable_bluetooth,
228 - style: Theme.of(context)
229 - .textTheme.titleMedium,
226 + style: Theme.of(context).textTheme.titleMedium,
227 textAlign: TextAlign.center,
228 ),
229 ),
@@ -239,8 +236,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
236 width: double.infinity,
237 child: Text(
238 S.of(context).bluetooth,
242 - style: Theme.of(context)
243 - .textTheme.bodyMedium,
239 + style: Theme.of(context).textTheme.bodyMedium,
240 ),
241 ),
242 ),
@@ -251,7 +247,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
247 child: DeviceTile(
248 onPressed: () => _connectToDevice(device),
249 title: device.name,
254 - leading: _getDeviceTileLeading(device.deviceInfo),
250 + leading: _getDeviceTileLeading(device.type),
251 connectionType: device.connectionType,
252 ),
253 ),
@@ -265,8 +261,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
261 width: double.infinity,
262 child: Text(
263 S.of(context).usb,
268 - style: Theme.of(context)
269 - .textTheme.bodyMedium,
264 + style: Theme.of(context).textTheme.bodyMedium,
265 ),
266 ),
267 ),
@@ -277,7 +272,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
272 child: DeviceTile(
273 onPressed: () => _connectToDevice(device),
274 title: device.name,
280 - leading: _getDeviceTileLeading(device.deviceInfo),
275 + leading: _getDeviceTileLeading(device.type),
276 connectionType: device.connectionType,
277 ),
278 ),
@@ -287,10 +282,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
282 if (widget.allowChangeWallet) ...[
283 PrimaryButton(
284 text: S.of(context).wallets,
290 - color: Theme.of(context)
291 - .colorScheme.primary,
292 - textColor: Theme.of(context)
293 - .colorScheme.onPrimary,
285 + color: Theme.of(context).colorScheme.primary,
286 + textColor: Theme.of(context).colorScheme.onPrimary,
287 onPressed: _onChangeWallet,
288 )
289 ],
@@ -312,8 +305,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
305 void _onChangeWallet() {
306 Navigator.of(context).pushNamed(
307 Routes.walletList,
315 - arguments: (BuildContext context) => Navigator.of(context)
316 - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
308 + arguments: (BuildContext context) =>
309 + Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
310 );
311 }
312
lib/src/screens/connect_device/select_device_manufacturer_page.dart new
+183
@@ -0,0 +1,183 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/routes.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/screens/connect_device/widgets/dotted_divider.dart';
5 +import 'package:cake_wallet/src/screens/connect_device/widgets/manufacturer_option_tile.dart';
6 +import 'package:cake_wallet/utils/permission_handler.dart';
7 +import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
8 +import 'package:cw_core/utils/print_verbose.dart';
9 +import 'package:cw_core/wallet_info.dart';
10 +import 'package:flutter/material.dart';
11 +import 'package:flutter_svg/flutter_svg.dart';
12 +import 'package:fluttertoast/fluttertoast.dart';
13 +import 'package:permission_handler/permission_handler.dart';
14 +
15 +class SelectDeviceManufacturerPage extends BasePage {
16 + SelectDeviceManufacturerPage({
17 + this.showUnavailable = true,
18 + this.onSelect,
19 + this.availableHardwareWalletTypes,
20 + });
21 +
22 + final bool showUnavailable;
23 + final void Function(BuildContext, HardwareWalletType)? onSelect;
24 + final List<HardwareWalletType>? availableHardwareWalletTypes;
25 +
26 + @override
27 + String get title => S.current.select_manufacturer_title;
28 +
29 + @override
30 + AppBarStyle get appBarStyle => AppBarStyle.regular;
31 +
32 + List<_DeviceManufacturer> get availableManufacturers => [
33 + _DeviceManufacturer(
34 + image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
35 + hardwareWalletType: HardwareWalletType.ledger,
36 + ),
37 + // _DeviceManufacturer(
38 + // image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
39 + // hardwareWalletType: HardwareWalletType.bitbox,
40 + // tag: S.current.new_tag,
41 + // ),
42 + _DeviceManufacturer(
43 + image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
44 + hardwareWalletType: HardwareWalletType.cupcake,
45 + tag: S.current.new_tag,
46 + ),
47 + _DeviceManufacturer(
48 + image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
49 + hardwareWalletType: HardwareWalletType.coldcard,
50 + tag: S.current.new_tag,
51 + ),
52 + // _DeviceManufacturer(
53 + // image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
54 + // hardwareWalletType: HardwareWalletType.seedsigner,
55 + // tag: S.current.new_tag,
56 + // ),
57 + ]
58 + .where((e) => availableHardwareWalletTypes == null
59 + ? ![HardwareWalletType.cupcake].contains(e.hardwareWalletType)
60 + : availableHardwareWalletTypes!.contains(e.hardwareWalletType))
61 + .toList();
62 +
63 + List<_DeviceManufacturer> get comingManufacturers => [
64 + _DeviceManufacturer(
65 + image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
66 + tag: S.current.coming_soon_tag,
67 + ),
68 + _DeviceManufacturer(
69 + image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
70 + hardwareWalletType: HardwareWalletType.bitbox,
71 + tag: S.current.coming_soon_tag,
72 + ),
73 + _DeviceManufacturer(
74 + image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
75 + hardwareWalletType: HardwareWalletType.seedsigner,
76 + tag: S.current.coming_soon_tag,
77 + ),
78 + _DeviceManufacturer(
79 + image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
80 + tag: S.current.coming_soon_tag,
81 + ),
82 + _DeviceManufacturer(
83 + image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
84 + tag: S.current.coming_soon_tag,
85 + ),
86 + ];
87 +
88 + @override
89 + Widget body(BuildContext context) => Container(
90 + child: Center(
91 + child: Container(
92 + padding: EdgeInsets.only(left: 24, right: 24),
93 + child: Column(
94 + children: [
95 + ...availableManufacturers.map(
96 + (manufacturer) => Padding(
97 + padding: EdgeInsets.only(top: 10),
98 + child: ManufacturerOptionTile(
99 + image: manufacturer.image,
100 + tag: manufacturer.tag,
101 + onPressed: () {
102 + if (onSelect != null)
103 + return onSelect!.call(context, manufacturer.hardwareWalletType!);
104 +
105 + if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
106 + _onScanQRCode(context, manufacturer.hardwareWalletType!);
107 + } else if (manufacturer.hardwareWalletType != null) {
108 + Navigator.pushNamed(context, Routes.connectHardwareWallet,
109 + arguments: [manufacturer.hardwareWalletType]);
110 + }
111 + },
112 + isDarkTheme: currentTheme.isDark,
113 + ),
114 + ),
115 + ),
116 + if (showUnavailable) ...[
117 + Padding(
118 + padding: EdgeInsets.only(top: 20, bottom: 10),
119 + child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
120 + ),
121 + ...comingManufacturers.map(
122 + (manufacturer) => Padding(
123 + padding: EdgeInsets.only(top: 10),
124 + child: ManufacturerOptionTile(
125 + image: manufacturer.image,
126 + tag: manufacturer.tag,
127 + onPressed: () =>
128 + Fluttertoast.showToast(msg: 'One more tap and it might work'),
129 + // Ester egg
130 + isDarkTheme: currentTheme.isDark,
131 + isUnavailable: true,
132 + ),
133 + ),
134 + ),
135 + ]
136 + ],
137 + ),
138 + ),
139 + ),
140 + );
141 +
142 + bool isAirgappedWallet(HardwareWalletType? type) => [
143 + HardwareWalletType.cupcake,
144 + HardwareWalletType.coldcard,
145 + HardwareWalletType.seedsigner,
146 + HardwareWalletType.keystone,
147 + ].contains(type);
148 +
149 + bool isRestoring = false;
150 +
151 + Future<void> _onScanQRCode(BuildContext context, HardwareWalletType type) async {
152 + final isCameraPermissionGranted =
153 + await PermissionHandler.checkPermission(Permission.camera, context);
154 +
155 + if (!isCameraPermissionGranted) return;
156 + try {
157 + if (isRestoring) return;
158 +
159 + isRestoring = true;
160 +
161 + final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
162 +
163 + final params = {
164 + 'walletType': restoredWallet.type,
165 + 'restoredWallet': restoredWallet,
166 + 'hardwareWalletType': type,
167 + };
168 +
169 + Navigator.pushNamed(context, Routes.restoreWallet, arguments: params)
170 + .then((_) => isRestoring = false);
171 + } catch (e) {
172 + printV(e.toString());
173 + }
174 + }
175 +}
176 +
177 +class _DeviceManufacturer {
178 + final SvgPicture image;
179 + final HardwareWalletType? hardwareWalletType;
180 + final String? tag;
181 +
182 + _DeviceManufacturer({required this.image, this.hardwareWalletType, this.tag});
183 +}
lib/src/screens/connect_device/widgets/device_tile.dart
+9 -6
@@ -1,5 +1,6 @@
1 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
2 import 'package:flutter/material.dart';
2 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
3 +import 'package:flutter_svg/flutter_svg.dart';
4
5 class DeviceTile extends StatelessWidget {
6 const DeviceTile({
@@ -12,15 +13,15 @@ class DeviceTile extends StatelessWidget {
13 final VoidCallback onPressed;
14 final String title;
15 final String? leading;
15 - final ConnectionType? connectionType;
16 + final HardwareWalletConnectionType? connectionType;
17
18 String? get connectionTypeIcon {
19 switch (connectionType) {
19 - case ConnectionType.ble:
20 + case HardwareWalletConnectionType.ble:
21 return 'assets/images/bluetooth.png';
21 - case ConnectionType.usb:
22 + case HardwareWalletConnectionType.usb:
23 return 'assets/images/usb.png';
23 - case null:
24 + default:
25 return null;
26 }
27 }
@@ -42,12 +43,14 @@ class DeviceTile extends StatelessWidget {
43 mainAxisAlignment: MainAxisAlignment.center,
44 crossAxisAlignment: CrossAxisAlignment.center,
45 children: <Widget>[
45 - if (leading != null)
46 + if (leading != null && !leading!.endsWith(".svg"))
47 Image.asset(
48 leading!,
49 height: 30,
50 color: Theme.of(context).colorScheme.onSurface,
51 ),
52 + if (leading != null && leading!.endsWith(".svg"))
53 + SvgPicture.asset(leading!, height: 30),
54 Expanded(
55 child: Padding(
56 padding: EdgeInsets.only(left: 16),
lib/src/screens/connect_device/widgets/dotted_divider.dart new
+36
@@ -0,0 +1,36 @@
1 +import 'package:flutter/material.dart';
2 +
3 +class DottedDivider extends StatelessWidget {
4 + final Color color;
5 +
6 + const DottedDivider({super.key, required this.color});
7 +
8 + @override
9 + Widget build(BuildContext context) => SizedBox(
10 + width: double.infinity,
11 + child: CustomPaint(
12 + painter: _DashedLinePainter(color: color),
13 + ),
14 + );
15 +}
16 +
17 +class _DashedLinePainter extends CustomPainter {
18 + final Color color;
19 +
20 + const _DashedLinePainter({required this.color});
21 +
22 + @override
23 + void paint(Canvas canvas, Size size) {
24 + double dashWidth = 9, dashSpace = 5, startX = 0;
25 + final paint = Paint()
26 + ..color = color
27 + ..strokeWidth = 1;
28 + while (startX < size.width) {
29 + canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
30 + startX += dashWidth + dashSpace;
31 + }
32 + }
33 +
34 + @override
35 + bool shouldRepaint(CustomPainter oldDelegate) => false;
36 +}
lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart new
+80
@@ -0,0 +1,80 @@
1 +import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class ManufacturerOptionTile extends StatelessWidget {
5 + const ManufacturerOptionTile({
6 + required this.onPressed,
7 + required this.image,
8 + required this.isDarkTheme,
9 + this.tag,
10 + this.isUnavailable = false,
11 + });
12 +
13 + final VoidCallback onPressed;
14 + final Widget image;
15 + final String? tag;
16 + final bool isUnavailable;
17 + final bool isDarkTheme;
18 +
19 + @override
20 + Widget build(BuildContext context) => GestureDetector(
21 + onTap: onPressed,
22 + child: Opacity(
23 + opacity: isUnavailable ? 0.5 : 1,
24 + child: Container(
25 + width: double.infinity,
26 + alignment: Alignment.center,
27 + decoration: BoxDecoration(
28 + borderRadius: BorderRadius.circular(12),
29 + gradient: LinearGradient(
30 + colors: [
31 + isDarkTheme
32 + ? CustomThemeColors.cardGradientColorPrimaryDark
33 + : CustomThemeColors.cardGradientColorPrimaryLight,
34 + isDarkTheme
35 + ? CustomThemeColors.cardGradientColorSecondaryDark
36 + : CustomThemeColors.cardGradientColorSecondaryLight,
37 + ],
38 + begin: Alignment.topCenter,
39 + end: Alignment.bottomCenter,
40 + ),
41 + ),
42 + padding: EdgeInsets.all(24),
43 + child: Row(
44 + mainAxisSize: MainAxisSize.max,
45 + mainAxisAlignment: MainAxisAlignment.center,
46 + children: <Widget>[
47 + Expanded(
48 + child: Column(
49 + mainAxisSize: MainAxisSize.max,
50 + mainAxisAlignment: MainAxisAlignment.start,
51 + crossAxisAlignment: CrossAxisAlignment.start,
52 + children: <Widget>[
53 + Row(
54 + children: [
55 + Container(height: 25, child: image),
56 + if (tag != null)
57 + Container(
58 + decoration: BoxDecoration(
59 + border: Border.all(color: Colors.transparent),
60 + borderRadius: BorderRadius.circular(20),
61 + color: Theme.of(context).colorScheme.surfaceContainerLow),
62 + padding: EdgeInsets.symmetric(horizontal: 5),
63 + margin: EdgeInsets.only(left: 5),
64 + child: Text(tag!))
65 + ],
66 + ),
67 + ],
68 + ),
69 + ),
70 + Icon(
71 + Icons.arrow_forward_ios,
72 + size: 16,
73 + color: Theme.of(context).colorScheme.onSurface,
74 + )
75 + ],
76 + ),
77 + ),
78 + ),
79 + );
80 +}
lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+21 -3
@@ -12,6 +12,7 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart';
12 import 'package:cake_wallet/utils/feature_flag.dart';
13 import 'package:cake_wallet/utils/show_pop_up.dart';
14 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
15 +import 'package:cw_core/wallet_info.dart';
16 import 'package:flutter/material.dart';
17 import 'package:flutter_mobx/flutter_mobx.dart';
18 import 'package:flutter_svg/flutter_svg.dart';
@@ -27,6 +28,23 @@ class CryptoBalanceWidget extends StatelessWidget {
28 final btcLockLight = 'assets/images/btc_lock_light.png';
29 final btcLockDark = 'assets/images/btc_lock_dark.png';
30
31 + String? get hardwareWalletIcon {
32 + switch (dashboardViewModel.wallet.hardwareWalletType) {
33 + case null:
34 + return null;
35 + case HardwareWalletType.ledger:
36 + return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
37 + case HardwareWalletType.bitbox:
38 + return "assets/images/hardware_wallet/device_bitbox.svg";
39 + case HardwareWalletType.cupcake:
40 + return "assets/images/cupcake.svg";
41 + case HardwareWalletType.coldcard:
42 + case HardwareWalletType.seedsigner:
43 + case HardwareWalletType.keystone:
44 + return "assets/images/hardware_wallet/device_qr.svg";
45 + }
46 + }
47 +
48 @override
49 Widget build(BuildContext context) {
50 return SingleChildScrollView(
@@ -86,10 +104,10 @@ class CryptoBalanceWidget extends StatelessWidget {
104 maxLines: 1,
105 textAlign: TextAlign.center,
106 ),
89 - if (dashboardViewModel.wallet.isHardwareWallet)
107 + if (hardwareWalletIcon != null)
108 Container(
91 - child: Image.asset(
92 - 'assets/images/hardware_wallet/ledger_nano_x.png',
109 + child: SvgPicture.asset(
110 + hardwareWalletIcon!,
111 width: 24,
112 color: Theme.of(context).colorScheme.onSurface,
113 ),
lib/src/screens/dev/qr_tools_page.dart new
+39
@@ -0,0 +1,39 @@
1 +import 'package:cake_wallet/src/screens/base_page.dart';
2 +import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
3 +import 'package:cake_wallet/view_model/dev/qr_tools_view_model.dart';
4 +import 'package:flutter/material.dart';
5 +import 'package:flutter_mobx/flutter_mobx.dart';
6 +
7 +class DevQRToolsPage extends BasePage {
8 + @override
9 + String? get title => "[dev] *QR tools";
10 +
11 + final QRToolsViewModel viewModel = QRToolsViewModel();
12 +
13 + late final textCtrl = TextEditingController(text: viewModel.input);
14 +
15 + @override
16 + Widget body(BuildContext context) {
17 + return Container(
18 + child: Column(
19 + mainAxisSize: MainAxisSize.min,
20 + children: [
21 + TextField(
22 + controller: textCtrl,
23 + maxLines: 8,
24 + onChanged: (value) {
25 + viewModel.input = value;
26 + }
27 + ),
28 + Observer(
29 + builder: (_) {
30 + return Expanded(
31 + child: JsonExplorer(data: viewModel.data, title: "result"),
32 + );
33 + },
34 + ),
35 + ],
36 + ),
37 + );
38 + }
39 +}
\ No newline at end of file
lib/src/screens/new_wallet/new_wallet_type_page.dart
+13 -8
@@ -1,7 +1,7 @@
1 -import 'package:cake_wallet/core/new_wallet_arguments.dart';
2 -import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
1 import 'dart:io';
2
3 +import 'package:cake_wallet/core/new_wallet_arguments.dart';
4 +import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/reactions/wallet_utils.dart';
7 import 'package:cake_wallet/routes.dart';
@@ -11,12 +11,12 @@ import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_aler
11 import 'package:cake_wallet/src/widgets/primary_button.dart';
12 import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
13 import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
14 -import 'package:cake_wallet/themes/core/material_base_theme.dart';
14 import 'package:cake_wallet/utils/responsive_layout_util.dart';
15 import 'package:cake_wallet/utils/show_pop_up.dart';
16 import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
17 import 'package:cake_wallet/wallet_types.g.dart';
18 import 'package:cw_core/hardware/device_connection_type.dart';
19 +import 'package:cw_core/wallet_info.dart';
20 import 'package:cw_core/wallet_type.dart';
21 import 'package:flutter/material.dart';
22
@@ -51,7 +51,7 @@ class NewWalletTypePage extends BasePage {
51 isCreate: newWalletTypeArguments.isCreate,
52 newWalletTypeViewModel: newWalletTypeViewModel,
53 onTypeSelected: newWalletTypeArguments.onTypeSelected,
54 - isHardwareWallet: newWalletTypeArguments.isHardwareWallet,
54 + hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
55 );
56 }
57
@@ -61,14 +61,16 @@ class WalletTypeForm extends StatefulWidget {
61 required this.isCreate,
62 required this.newWalletTypeViewModel,
63 this.onTypeSelected,
64 - required this.isHardwareWallet,
64 + this.hardwareWalletType,
65 });
66
67 final bool isCreate;
68 final Image walletImage;
69 final NewWalletTypeViewModel newWalletTypeViewModel;
70 final void Function(BuildContext, WalletType)? onTypeSelected;
71 - final bool isHardwareWallet;
71 + final HardwareWalletType? hardwareWalletType;
72 +
73 + bool get isHardwareWallet => hardwareWalletType != null;
74
75 @override
76 WalletTypeFormState createState() => WalletTypeFormState();
@@ -90,7 +92,9 @@ class WalletTypeFormState extends State<WalletTypeForm> {
92 types = filteredTypes = availableWalletTypes
93 .where((element) =>
94 !widget.isHardwareWallet ||
93 - DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty)
95 + DeviceConnectionType.supportedConnectionTypes(
96 + element, widget.hardwareWalletType!, Platform.isIOS)
97 + .isNotEmpty)
98 .toList();
99 super.initState();
100
@@ -148,7 +152,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
152 isSelected: selected == type,
153 onTap: () => setState(() => selected = type),
154 deviceConnectionTypes: widget.isHardwareWallet
151 - ? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS)
155 + ? DeviceConnectionType.supportedConnectionTypes(
156 + type, widget.hardwareWalletType!, Platform.isIOS)
157 : [],
158 ),
159 ),
lib/src/screens/receive/widgets/qr_widget.dart
+1 -1
@@ -111,7 +111,7 @@ class QRWidget extends StatelessWidget {
111 ),
112 if (addressListViewModel.isPayjoinUnavailable &&
113 !addressListViewModel.isSilentPayments &&
114 - !addressListViewModel.isCupcake) ...[
114 + !addressListViewModel.isBitcoinViewOnly) ...[
115 GestureDetector(
116 onTap: () => _onPayjoinInactivePressed(context),
117 child: Row(
lib/src/screens/restore/restore_options_page.dart
+42 -56
@@ -1,10 +1,9 @@
1 -import 'dart:io';
2 -
1 import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/routes.dart';
3 import 'package:cake_wallet/src/screens/base_page.dart';
4 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
5 import 'package:cake_wallet/src/widgets/option_tile.dart';
6 +import 'package:cake_wallet/themes/core/material_base_theme.dart';
7 import 'package:cake_wallet/utils/device_info.dart';
8 import 'package:cake_wallet/utils/feature_flag.dart';
9 import 'package:cake_wallet/utils/permission_handler.dart';
@@ -12,11 +11,9 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
11 import 'package:cake_wallet/utils/show_pop_up.dart';
12 import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
13 import 'package:cake_wallet/wallet_type_utils.dart';
15 -import 'package:cw_core/hardware/device_connection_type.dart';
16 -import 'package:cw_core/wallet_type.dart';
14 +import 'package:cw_core/wallet_info.dart';
15 import 'package:flutter/material.dart';
16 import 'package:permission_handler/permission_handler.dart';
19 -import 'package:cake_wallet/themes/core/material_base_theme.dart';
17
18 class RestoreOptionsPage extends BasePage {
19 RestoreOptionsPage({required this.isNewInstall});
@@ -27,9 +24,8 @@ class RestoreOptionsPage extends BasePage {
24 final bool isNewInstall;
25
26 @override
30 - Widget body(BuildContext context) {
31 - return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
32 - }
27 + Widget body(BuildContext context) =>
28 + _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
29 }
30
31 class _RestoreOptionsBody extends StatefulWidget {
@@ -45,41 +41,25 @@ class _RestoreOptionsBody extends StatefulWidget {
41 class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
42 bool isRestoring = false;
43
48 - bool get _doesSupportHardwareWallets {
49 - if (isMoneroOnly) {
50 - return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
51 - .isNotEmpty;
52 - }
53 -
54 - return true;
55 - }
56 -
57 - String get imageRestoreHWPath =>
58 - widget.themeType == ThemeType.dark
59 - ? 'assets/images/restore_hw_dark.png'
60 - : 'assets/images/restore_hw.png';
44 + String get imageRestoreHWPath => widget.themeType == ThemeType.dark
45 + ? 'assets/images/restore_hw_dark.png'
46 + : 'assets/images/restore_hw.png';
47
62 - String get imageRestoreCupcakePath =>
63 - widget.themeType == ThemeType.dark
64 - ? 'assets/images/restore_cupcake_dark.png'
65 - : 'assets/images/restore_cupcake.png';
48 + String get imageRestoreCupcakePath => widget.themeType == ThemeType.dark
49 + ? 'assets/images/restore_cupcake_dark.png'
50 + : 'assets/images/restore_cupcake.png';
51
67 - String get imageRestoreQRPath =>
68 - widget.themeType == ThemeType.dark
69 - ? 'assets/images/restore_qr_dark.png'
70 - : 'assets/images/restore_qr.png';
52 + String get imageRestoreQRPath => widget.themeType == ThemeType.dark
53 + ? 'assets/images/restore_qr_dark.png'
54 + : 'assets/images/restore_qr.png';
55
56 + String get imageRestoreHotWalletPath => widget.themeType == ThemeType.dark
57 + ? 'assets/images/restore_hot_wallet_dark.png'
58 + : 'assets/images/restore_hot_wallet.png';
59
73 - String get imageRestoreHotWalletPath =>
74 - widget.themeType == ThemeType.dark
75 - ? 'assets/images/restore_hot_wallet_dark.png'
76 - : 'assets/images/restore_hot_wallet.png';
77 -
78 -
79 - String get imageRestoreBackupPath =>
80 - widget.themeType == ThemeType.dark
81 - ? 'assets/images/restore_backup_dark.png'
82 - : 'assets/images/restore_backup.png';
60 + String get imageRestoreBackupPath => widget.themeType == ThemeType.dark
61 + ? 'assets/images/restore_backup_dark.png'
62 + : 'assets/images/restore_backup.png';
63
64 @override
65 Widget build(BuildContext context) {
@@ -116,18 +96,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
96 tag: S.of(context).new_tag,
97 ),
98 ),
119 - if (_doesSupportHardwareWallets)
120 - Padding(
121 - padding: EdgeInsets.only(top: 24),
122 - child: OptionTile(
123 - key: ValueKey('restore_options_from_hardware_wallet_button_key'),
124 - onPressed: () =>
125 - Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet),
126 - image: imageRestoreHW,
127 - title: S.of(context).restore_title_from_hardware_wallet,
128 - description: S.of(context).restore_description_from_hardware_wallet,
129 - ),
99 + Padding(
100 + padding: EdgeInsets.only(top: 24),
101 + child: OptionTile(
102 + key: ValueKey('restore_options_from_hardware_wallet_button_key'),
103 + onPressed: () => Navigator.pushNamed(
104 + context,
105 + isSingleCoin
106 + ? Routes.connectHardwareWallet
107 + : Routes.restoreWalletFromHardwareWallet),
108 + image: imageRestoreHW,
109 + title: S.of(context).restore_title_from_hardware_wallet,
110 + description: S.of(context).restore_description_from_hardware_wallet,
111 ),
112 + ),
113 if (widget.isNewInstall)
114 Padding(
115 padding: EdgeInsets.only(top: 24),
@@ -143,11 +125,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
125 Padding(
126 padding: EdgeInsets.only(top: 24),
127 child: OptionTile(
146 - key: ValueKey('restore_options_from_qr_button_key'),
147 - onPressed: () => _onScanQRCode(context),
148 - image: imageRestoreQR,
149 - title: S.of(context).scan_qr_code,
150 - description: S.of(context).cold_or_recover_wallet,
128 + key: ValueKey('restore_options_from_qr_button_key'),
129 + onPressed: () => _onScanQRCode(context),
130 + image: imageRestoreQR,
131 + title: S.of(context).scan_qr_code,
132 + description: S.of(context).cold_or_recover_wallet,
133 ),
134 ),
135 ],
@@ -184,7 +166,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
166
167 final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
168
187 - final params = {'walletType': restoredWallet.type, 'restoredWallet': restoredWallet};
169 + final params = {
170 + 'walletType': restoredWallet.type,
171 + 'restoredWallet': restoredWallet,
172 + 'hardwareWalletType': HardwareWalletType.cupcake,
173 + };
174
175 Navigator.pushNamed(context, Routes.restoreWallet, arguments: params).then((_) {
176 if (mounted) setState(() => isRestoring = false);
lib/src/screens/send/send_page.dart
+7 -4
@@ -440,18 +440,21 @@ class SendPage extends BasePage {
440 }
441
442 if (sendViewModel.wallet.isHardwareWallet) {
443 - if (!sendViewModel.ledgerViewModel!.isConnected) {
443 + if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
444 await Navigator.of(context).pushNamed(Routes.connectDevices,
445 arguments: ConnectDevicePageParams(
446 walletType: sendViewModel.walletType,
447 + hardwareWalletType:
448 + sendViewModel.wallet.walletInfo.hardwareWalletType!,
449 onConnectDevice: (BuildContext context, _) {
448 - sendViewModel.ledgerViewModel!
449 - .setLedger(sendViewModel.wallet);
450 + sendViewModel.hardwareWalletViewModel!
451 + .initWallet(sendViewModel.wallet);
452 Navigator.of(context).pop();
453 },
454 ));
455 } else {
454 - sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
456 + sendViewModel.hardwareWalletViewModel!
457 + .initWallet(sendViewModel.wallet);
458 }
459 }
460
lib/src/screens/settings/other_settings_page.dart
+51 -31
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
9 import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
10 import 'package:cake_wallet/utils/feature_flag.dart';
11 import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
12 +import 'package:cw_core/wallet_info.dart';
13 import 'package:cw_core/wallet_type.dart';
14 import 'package:flutter/material.dart';
15 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -26,16 +27,14 @@ class OtherSettingsPage extends BasePage {
27 final OtherSettingsViewModel _otherSettingsViewModel;
28
29 @override
29 - Widget body(BuildContext context) {
30 - return Observer(
31 - builder: (_) {
32 - return Container(
30 + Widget body(BuildContext context) => Observer(
31 + builder: (_) => Container(
32 padding: EdgeInsets.only(top: 10),
33 child: Column(
34 children: [
35 if (_otherSettingsViewModel.displayTransactionPriority)
37 - _otherSettingsViewModel.walletType == WalletType.bitcoin ?
38 - SettingsPriorityPickerCell(
36 + _otherSettingsViewModel.walletType == WalletType.bitcoin
37 + ? SettingsPriorityPickerCell(
38 title: S.current.settings_fee_priority,
39 items: priorityForWalletType(_otherSettingsViewModel.walletType),
40 displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
@@ -44,8 +43,8 @@ class OtherSettingsPage extends BasePage {
43 onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
44 customValue: _otherSettingsViewModel.customBitcoinFeeRate,
45 maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
47 - ) :
48 - SettingsPickerCell(
46 + )
47 + : SettingsPickerCell(
48 title: S.current.settings_fee_priority,
49 items: priorityForWalletType(_otherSettingsViewModel.walletType),
50 displayItem: _otherSettingsViewModel.getDisplayPriority,
@@ -55,75 +54,96 @@ class OtherSettingsPage extends BasePage {
54 if (_otherSettingsViewModel.changeRepresentativeEnabled)
55 SettingsCellWithArrow(
56 title: S.current.change_rep,
58 - handler: (BuildContext context) =>
59 - Navigator.of(context).pushNamed(Routes.changeRep),
57 + handler: (context) => Navigator.of(context).pushNamed(Routes.changeRep),
58 + ),
59 + if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled)
60 + SettingsCellWithArrow(
61 + title: "Hardware wallet manufacturer",
62 + handler: (context) => Navigator.of(context)
63 + .pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: {
64 + "showUnavailable": false,
65 + "availableHardwareWalletTypes": [
66 + HardwareWalletType.cupcake,
67 + HardwareWalletType.coldcard,
68 + HardwareWalletType.seedsigner,
69 + ],
70 + "onSelect": (BuildContext context, HardwareWalletType hwType) async {
71 + await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType);
72 + Navigator.pop(context);
73 + },
74 + }),
75 ),
76 SettingsCellWithArrow(
77 title: S.current.settings_terms_and_conditions,
63 - handler: (BuildContext context) =>
64 - Navigator.of(context).pushNamed(Routes.readDisclaimer),
78 + handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
79 ),
66 - if (FeatureFlag.hasDevOptions && _otherSettingsViewModel.walletType == WalletType.monero)
80 + if (FeatureFlag.hasDevOptions &&
81 + _otherSettingsViewModel.walletType == WalletType.monero)
82 SettingsCellWithArrow(
83 title: '[dev] monero background sync',
69 - handler: (BuildContext context) =>
84 + handler: (context) =>
85 Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync),
86 ),
72 - if (FeatureFlag.hasDevOptions && [WalletType.monero, WalletType.wownero, WalletType.zano].contains(_otherSettingsViewModel.walletType))
87 + if (FeatureFlag.hasDevOptions &&
88 + [WalletType.monero, WalletType.wownero, WalletType.zano]
89 + .contains(_otherSettingsViewModel.walletType))
90 SettingsCellWithArrow(
91 title: '[dev] xmr call profiler',
75 - handler: (BuildContext context) =>
92 + handler: (context) =>
93 Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler),
94 ),
78 - if (FeatureFlag.hasDevOptions && [WalletType.monero].contains(_otherSettingsViewModel.walletType))
95 + if (FeatureFlag.hasDevOptions &&
96 + [WalletType.monero].contains(_otherSettingsViewModel.walletType))
97 SettingsCellWithArrow(
98 title: '[dev] xmr wallet cache debug',
81 - handler: (BuildContext context) =>
99 + handler: (context) =>
100 Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug),
101 ),
102 if (FeatureFlag.hasDevOptions)
103 SettingsCellWithArrow(
104 title: '[dev] shared preferences',
87 - handler: (BuildContext context) =>
105 + handler: (context) =>
106 Navigator.of(context).pushNamed(Routes.devSharedPreferences),
107 ),
108 if (FeatureFlag.hasDevOptions)
109 SettingsCellWithArrow(
110 title: '[dev] secure storage preferences',
93 - handler: (BuildContext context) =>
111 + handler: (context) =>
112 Navigator.of(context).pushNamed(Routes.devSecurePreferences),
113 ),
114 if (FeatureFlag.hasDevOptions)
115 SettingsCellWithArrow(
116 title: '[dev] background sync logs',
99 - handler: (BuildContext context) =>
117 + handler: (context) =>
118 Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs),
119 ),
120 if (FeatureFlag.hasDevOptions)
121 SettingsCellWithArrow(
122 title: '[dev] socket health logs',
105 - handler: (BuildContext context) =>
106 - Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
123 + handler: (context) => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
124 ),
125 if (FeatureFlag.hasDevOptions)
126 SettingsCellWithArrow(
127 title: '[dev] network requests logs',
111 - handler: (BuildContext context) =>
112 - Navigator.of(context).pushNamed(Routes.devNetworkRequests),
128 + handler: (context) => Navigator.of(context).pushNamed(Routes.devNetworkRequests),
129 ),
130 if (FeatureFlag.hasDevOptions)
131 SettingsCellWithArrow(
132 title: '[dev] exchange provider logs',
117 - handler: (BuildContext context) =>
133 + handler: (context) =>
134 Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
135 ),
136 + if (FeatureFlag.hasDevOptions)
137 + SettingsCellWithArrow(
138 + title: '[dev] *QR tools',
139 + handler: (context) => Navigator.of(context).pushNamed(Routes.devQRTools),
140 + ),
141 Spacer(),
142 SettingsVersionCell(
122 - title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
143 + title: S.of(context).version(_otherSettingsViewModel.currentVersion),
144 + ),
145 ],
146 ),
125 - );
126 - },
127 - );
128 - }
147 + ),
148 + );
149 }
lib/src/screens/ur/animated_ur_page.dart
+2 -1
@@ -61,6 +61,7 @@ class AnimatedURPage extends BasePage {
61 child: URQR(
62 urqr: urQr,
63 walletType: animatedURmodel.wallet.type,
64 + hardwareWalletType: animatedURmodel.wallet.hardwareWalletType,
65 ),
66 ),
67 if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header]
@@ -125,4 +126,4 @@ class AnimatedURPage extends BasePage {
126 });
127 }
128 }
128 -}
\ No newline at end of file
129 +}
lib/src/screens/ur/widgets/urqr.dart
+81 -99
@@ -1,4 +1,3 @@
1 -
1 import 'dart:async';
2
3 import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
@@ -7,67 +6,65 @@ import 'package:cake_wallet/src/screens/ur/widgets/qr_selection_dialog.dart';
6 import 'package:cake_wallet/src/widgets/primary_button.dart';
7 import 'package:cake_wallet/utils/feature_flag.dart';
8 import 'package:cake_wallet/utils/show_pop_up.dart';
9 +import 'package:cw_core/wallet_info.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:flutter/material.dart';
12 import 'package:flutter/services.dart';
13 import 'package:flutter_svg/svg.dart';
14
15 class URQR extends StatefulWidget {
16 - URQR({super.key, required this.urqr, required this.walletType});
16 + const URQR({super.key, required this.urqr, required this.walletType, this.hardwareWalletType});
17
18 final Map<String, String> urqr;
19 final WalletType walletType;
20 + final HardwareWalletType? hardwareWalletType;
21 +
22 @override
21 - // ignore: library_private_types_in_public_api
23 _URQRState createState() => _URQRState();
24 }
25
26 const urFrameTime = 1000 ~/ 5;
27
28 class _URQRState extends State<URQR> {
28 - Timer? t;
29 + Timer? _timer;
30 int frame = 0;
31 + int selectedInt = 0;
32 +
33 @override
34 void initState() {
32 - super.initState();
33 - setState(() {
34 - t = Timer.periodic(const Duration(milliseconds: urFrameTime), (timer) {
35 - _nextFrame();
36 - });
37 - });
38 - }
35 + final keys = widget.urqr.keys.toList();
36 + if (widget.hardwareWalletType == HardwareWalletType.coldcard) {
37 + selectedInt = 1;
38 + selected = keys[(selectedInt) % keys.length];
39 + } else if (widget.hardwareWalletType == HardwareWalletType.seedsigner) {
40 + selectedInt = 2;
41 + selected = keys[(selectedInt) % keys.length];
42 + }
43
40 - void _nextFrame() {
41 - setState(() {
42 - frame++;
43 - });
44 + _timer = Timer.periodic(const Duration(milliseconds: urFrameTime), (_) => _nextFrame());
45 + super.initState();
46 }
47
48 @override
49 void dispose() {
48 - t?.cancel();
50 + _timer?.cancel();
51 super.dispose();
52 }
53
52 - late String selected =
53 - (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
54 - int selectedInt = 0;
54 + String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
55 + void next() => setState(() {
56 + final keys = widget.urqr.keys.toList();
57
56 - List<String> get frames {
57 - return widget.urqr[selected]?.split("\n") ?? [];
58 - }
58 + selectedInt++;
59 + selected = keys[(selectedInt) % keys.length];
60 + });
61
60 - late String nextLabel =
61 - widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
62 + late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
63 +
64 +
65 + List<String> get frames => widget.urqr[selected]?.split("\n") ?? [];
66 + void _nextFrame() => setState(() => frame++);
67
63 - void next() {
64 - final keys = widget.urqr.keys.toList();
65 - setState(() {
66 - selectedInt++;
67 - nextLabel = keys[(selectedInt + 1) % keys.length];
68 - selected = keys[(selectedInt) % keys.length];
69 - });
70 - }
68
69 @override
70 Widget build(BuildContext context) {
@@ -107,45 +104,44 @@ All frames:
104 );
105 }
106
110 - Widget _legacySwitch(BuildContext context) {
111 - return SizedBox(
112 - width: double.maxFinite,
113 - child: Padding(
114 - padding: const EdgeInsets.symmetric(horizontal: 16.0),
115 - child: SizedBox(
116 - width: double.maxFinite,
117 - child: PrimaryButton(
118 - onPressed: next,
119 - text: nextLabel,
120 - color: Theme.of(context).colorScheme.primary,
121 - textColor: Theme.of(context).colorScheme.onPrimary,
107 + Widget _legacySwitch(BuildContext context) => SizedBox(
108 + width: double.maxFinite,
109 + child: Padding(
110 + padding: const EdgeInsets.symmetric(horizontal: 16.0),
111 + child: SizedBox(
112 + width: double.maxFinite,
113 + child: PrimaryButton(
114 + onPressed: next,
115 + text: nextLabel,
116 + color: Theme.of(context).colorScheme.primary,
117 + textColor: Theme.of(context).colorScheme.onPrimary,
118 + ),
119 ),
120 ),
124 - ),
125 - );
126 - }
121 + );
122
123 Widget _newSwitch(BuildContext context) {
124 return Column(
125 children: [
131 - SizedBox(height: 32),
126 + const SizedBox(height: 32),
127 Row(
128 mainAxisAlignment: MainAxisAlignment.center,
129 children: [
135 - Icon(Icons.qr_code,
136 - color: Theme.of(context).colorScheme.primary, size: 24),
137 - SizedBox(width: 4),
138 - Text("QR Code Format",
139 - style: TextStyle(
140 - fontWeight: FontWeight.bold,
141 - fontSize: 16,
142 - color: Theme.of(context).colorScheme.onSurface,
143 - )),
130 + Icon(Icons.qr_code, color: Theme.of(context).colorScheme.primary, size: 24),
131 + const SizedBox(width: 4),
132 + Text(
133 + "QR Code Format",
134 + style: TextStyle(
135 + fontWeight: FontWeight.bold,
136 + fontSize: 16,
137 + color: Theme.of(context).colorScheme.onSurface,
138 + ),
139 + ),
140 ],
141 ),
146 - SizedBox(height: 16),
142 + const SizedBox(height: 16),
143 Container(
148 - margin: EdgeInsets.symmetric(horizontal: 16),
144 + margin: const EdgeInsets.symmetric(horizontal: 16),
145 decoration: BoxDecoration(
146 color: Theme.of(context).colorScheme.surfaceContainer,
147 borderRadius: BorderRadius.circular(12),
@@ -176,7 +172,7 @@ All frames:
172 ),
173 ),
174 ),
179 - SizedBox(width: 16),
175 + const SizedBox(width: 16),
176 Expanded(
177 child: Column(
178 crossAxisAlignment: CrossAxisAlignment.start,
@@ -193,10 +189,7 @@ All frames:
189 _getCurrentFormatSubtitle(),
190 style: TextStyle(
191 fontSize: 14,
196 - color: Theme.of(context)
197 - .colorScheme
198 - .onSurface
199 - .withOpacity(0.6),
192 + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
193 ),
194 ),
195 ],
@@ -204,10 +197,7 @@ All frames:
197 ),
198 Icon(
199 Icons.chevron_right,
207 - color: Theme.of(context)
208 - .colorScheme
209 - .onSurface
210 - .withOpacity(0.4),
200 + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
201 size: 24,
202 ),
203 ],
@@ -216,7 +206,7 @@ All frames:
206 ),
207 ),
208 ),
219 - SizedBox(height: 16),
209 + const SizedBox(height: 16),
210 GestureDetector(
211 onTap: () => _showInfoDialog(context),
212 child: Row(
@@ -226,35 +216,32 @@ All frames:
216 "What's this for?",
217 style: TextStyle(
218 fontSize: 14,
229 - color:
230 - Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
219 + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
220 ),
221 ),
233 - SizedBox(width: 4),
222 + const SizedBox(width: 4),
223 Icon(
224 Icons.info_outline,
225 size: 16,
237 - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
226 + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
227 ),
228 ],
229 ),
230 ),
242 - SizedBox(height: 32),
231 + const SizedBox(height: 32),
232 ],
233 );
234 }
235
236 String _getCurrentFormatName() {
237 if (widget.urqr.isEmpty) return "Unknown";
249 - final currentKey =
250 - widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
238 + final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
239 return currentKey.split(' ')[0];
240 }
241
242 String _getCurrentFormatSubtitle() {
243 if (widget.urqr.isEmpty) return "";
256 - final currentKey =
257 - widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
244 + final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
245 return currentKey.substring(currentKey.indexOf(' ') + 1);
246 }
247
@@ -264,30 +251,25 @@ All frames:
251 final keys = widget.urqr.keys.toList();
252
253 await showPopUp<void>(
267 - context: context,
268 - builder: (BuildContext context) {
269 - return QRFormatSelectionDialog(
270 - formats: keys,
271 - currentIndex: selectedInt,
272 - onFormatSelected: (index) {
273 - Navigator.pop(context);
274 - setState(() {
275 - selectedInt = index;
276 - selected = keys[index];
277 - nextLabel = keys[(index + 1) % keys.length];
278 - });
279 - },
280 - );
281 - });
254 + context: context,
255 + builder: (context) => QRFormatSelectionDialog(
256 + formats: keys,
257 + currentIndex: selectedInt,
258 + onFormatSelected: (index) {
259 + Navigator.pop(context);
260 + setState(() {
261 + selectedInt = index;
262 + selected = keys[index];
263 + });
264 + },
265 + ),
266 + );
267 }
268
284 - Future<void> _showInfoDialog(BuildContext context) async {
285 - await showModalBottomSheet(
269 + Future<void> _showInfoDialog(BuildContext context) => showModalBottomSheet(
270 context: context,
271 isScrollControlled: true,
272 backgroundColor: Colors.transparent,
289 - builder: (BuildContext context) {
290 - return QRFormatInfoBottomSheet();
291 - });
292 - }
273 + builder: (_) => QRFormatInfoBottomSheet(),
274 + );
275 }
lib/src/screens/wallet_list/wallet_list_page.dart
+4 -1
@@ -21,9 +21,11 @@ import 'package:cake_wallet/utils/exception_handler.dart';
21 import 'package:cake_wallet/utils/responsive_layout_util.dart';
22 import 'package:cake_wallet/utils/show_bar.dart';
23 import 'package:cake_wallet/utils/show_pop_up.dart';
24 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
25 import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
26 import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
27 import 'package:cake_wallet/wallet_type_utils.dart';
28 +import 'package:cw_core/wallet_info.dart';
29 import 'package:cw_core/wallet_type.dart';
30 import 'package:flutter/material.dart';
31 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -477,8 +479,9 @@ class WalletListBodyState extends State<WalletListBody> {
479 Routes.connectDevices,
480 arguments: ConnectDevicePageParams(
481 walletType: WalletType.monero,
482 + hardwareWalletType: HardwareWalletType.ledger,
483 onConnectDevice: (context, ledgerVM) async {
481 - monero!.setGlobalLedgerConnection(ledgerVM.connection);
484 + if (ledgerVM is LedgerViewModel) monero!.setGlobalLedgerConnection(ledgerVM.connection);
485 didConnect = true;
486 Navigator.of(context).pop();
487 },
lib/view_model/dashboard/receive_option_view_model.dart
+1 -1
@@ -28,7 +28,7 @@ abstract class ReceiveOptionViewModelBase with Store {
28 break;
29 case WalletType.litecoin:
30 _options = [
31 - ...bitcoin!.getLitecoinReceivePageOptions(),
31 + ...bitcoin!.getLitecoinReceivePageOptions(_wallet),
32 ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
33 ];
34 break;
lib/view_model/dev/qr_tools_view_model.dart new
+81
@@ -0,0 +1,81 @@
1 +import 'dart:convert';
2 +
3 +import 'package:flutter/foundation.dart';
4 +import 'package:ur/cbor_lite.dart';
5 +import 'package:ur/ur.dart';
6 +import 'package:mobx/mobx.dart';
7 +import 'package:ur/ur_decoder.dart';
8 +
9 +part 'qr_tools_view_model.g.dart';
10 +
11 +class QRToolsViewModel = QRToolsViewModelBase with _$QRToolsViewModel;
12 +
13 +enum DevQrType {
14 + zpub,
15 + xpub,
16 + bbqr,
17 + bcur,
18 + unknown,
19 +}
20 +
21 +abstract class QRToolsViewModelBase with Store {
22 + @observable
23 + String input = '';
24 +
25 +
26 +
27 + @computed
28 + Map<String, dynamic> get data => _getResult(input);
29 +
30 + Uint8List _decodeCBOR(Uint8List cbor) {
31 + final cborDecoder = CBORDecoder(cbor);
32 + final out = cborDecoder.decodeBytes();
33 + return out.$1;
34 + }
35 +
36 +
37 + Map<String, dynamic> _getResult(String input) {
38 + try {
39 + final decoder = URDecoder();
40 + for (var part in input.split('\n')) {
41 + part = part.toLowerCase();
42 + part = part.trim();
43 + if (part.startsWith('-')) {
44 + part = part.substring(1).trim();
45 + }
46 + if (!part.startsWith('ur:')) {
47 + continue;
48 + }
49 + decoder.receivePart(part);
50 + }
51 + return {
52 + "result": (decoder.result != null) ? switch (decoder.result.runtimeType) {
53 + UR => {
54 + "cbor": (decoder.result as UR).cbor,
55 + "_cbor.decode.base64": base64.encode(_decodeCBOR((decoder.result as UR).cbor)),
56 + "type": (decoder.result as UR).type,
57 + "toString": (decoder.result as UR).toString(),
58 + },
59 + _ => "unknown type: ${decoder.result.runtimeType}"
60 + } : null,
61 + "expectedType": decoder.expectedType,
62 + "estimatedPercentComplete": decoder.estimatedPercentComplete(),
63 + "processedPartsCount": decoder.processedPartsCount(),
64 + "expectedPartCount": decoder.expectedPartCount(),
65 + "isComplete": decoder.isComplete(),
66 + "isFailure": decoder.isFailure(),
67 + "isSuccess": decoder.isSuccess(),
68 + "lastPartIndexes": decoder.lastPartIndexes(),
69 + "receivedPartIndexes": decoder.receivedPartIndexes(),
70 + "resultError": decoder.resultError(),
71 + "resultMessage": decoder.resultMessage(),
72 + "toString": decoder.toString(),
73 +
74 + };
75 + } catch (e) {
76 + return {
77 + "error": e.toString(),
78 + };
79 + }
80 + }
81 +}
\ No newline at end of file
lib/view_model/hardware_wallet/bitbox_view_model.dart new
+110
@@ -0,0 +1,110 @@
1 +import 'dart:async';
2 +import 'dart:io';
3 +
4 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
6 +import 'package:cake_wallet/ethereum/ethereum.dart';
7 +import 'package:cake_wallet/polygon/polygon.dart';
8 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
9 +import 'package:cake_wallet/wallet_type_utils.dart';
10 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
11 +import 'package:cw_core/root_dir.dart';
12 +import 'package:cw_core/utils/print_verbose.dart';
13 +import 'package:cw_core/wallet_base.dart';
14 +import 'package:cw_core/wallet_type.dart';
15 +import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
16 +import 'package:mobx/mobx.dart';
17 +
18 +part 'bitbox_view_model.g.dart';
19 +
20 +class BitboxViewModel = BitboxViewModelBase with _$BitboxViewModel;
21 +
22 +abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
23 + late final sdk.BitboxManager bitboxManager;
24 +
25 + BitboxViewModelBase() {
26 + if (!Platform.isIOS && !isMoneroOnly) {
27 + bitboxManager = sdk.BitboxManager();
28 + }
29 + }
30 +
31 + @override
32 + @observable
33 + bool isBleEnabled = false;
34 +
35 + @override
36 + bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth
37 +
38 + @override
39 + Future<void> updateBleState() async {}
40 +
41 + @override
42 + Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
43 +
44 + @override
45 + Future<List<HardwareWalletDevice>> getAllUsbDevices() => bitboxManager.devices
46 + .then((devices) => devices.map((d) => BitboxHardwareWalletDevice(d)).toList());
47 +
48 + @override
49 + Future<void> stopScanning() async {}
50 +
51 + @override
52 + Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
53 + if (!(device is BitboxHardwareWalletDevice)) return false;
54 + if (_isConnecting) return false;
55 + _isConnecting = true;
56 +
57 + try {
58 + final appDocDir = await getAppDir();
59 +
60 + await bitboxManager.connect(device.device);
61 + printV("Got Connection", file: '${appDocDir.path}/error.txt');
62 + await bitboxManager.initBitBox();
63 + printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt');
64 + await bitboxManager.channelHashVerify();
65 + printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt');
66 + _isConnecting = false;
67 + return true;
68 + } catch (e) {
69 + printV(e);
70 + }
71 + _isConnecting = false;
72 + return false;
73 + }
74 +
75 + bool _isConnecting = false;
76 +
77 + @override
78 + bool get isConnected => false;
79 +
80 + @override
81 + HardwareWalletService getHardwareWalletService(WalletType type) {
82 + switch (type) {
83 + case WalletType.bitcoin:
84 + return bitcoin!.getBitboxHardwareWalletService(bitboxManager, true);
85 + case WalletType.litecoin:
86 + return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false);
87 + case WalletType.ethereum:
88 + return ethereum!.getBitboxHardwareWalletService(bitboxManager);
89 + case WalletType.polygon:
90 + return polygon!.getBitboxHardwareWalletService(bitboxManager);
91 + default:
92 + throw UnimplementedError();
93 + }
94 + }
95 +
96 + @override
97 + void initWallet(WalletBase wallet) {
98 + switch (wallet.type) {
99 + case WalletType.bitcoin:
100 + case WalletType.litecoin:
101 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
102 + case WalletType.ethereum:
103 + return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
104 + case WalletType.polygon:
105 + return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
106 + default:
107 + throw Exception('Unexpected wallet type: ${wallet.type}');
108 + }
109 + }
110 +}
lib/view_model/hardware_wallet/hardware_wallet_view_model.dart new
+27
@@ -0,0 +1,27 @@
1 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
2 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
3 +import 'package:cw_core/wallet_base.dart';
4 +import 'package:cw_core/wallet_type.dart';
5 +
6 +abstract class HardwareWalletViewModel {
7 + bool get isConnected;
8 + bool get isBleEnabled;
9 + bool get hasBluetooth;
10 +
11 + Future<void> updateBleState();
12 +
13 + Stream<HardwareWalletDevice> scanForBleDevices();
14 +
15 + Future<List<HardwareWalletDevice>> getAllUsbDevices();
16 +
17 + Future<void> stopScanning();
18 +
19 + Future<bool> connectDevice(HardwareWalletDevice device, WalletType type);
20 +
21 + HardwareWalletService getHardwareWalletService(WalletType type);
22 +
23 + void initWallet(WalletBase wallet);
24 +
25 + String? interpretErrorCode(String error) => null;
26 +}
27 +
lib/view_model/hardware_wallet/ledger_view_model.dart
+55 -18
@@ -2,6 +2,7 @@ import 'dart:async';
2 import 'dart:io';
3
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 +import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
6 import 'package:cake_wallet/ethereum/ethereum.dart';
7 import 'package:cake_wallet/generated/i18n.dart';
8 import 'package:cake_wallet/main.dart';
@@ -9,10 +10,13 @@ import 'package:cake_wallet/monero/monero.dart';
10 import 'package:cake_wallet/polygon/polygon.dart';
11 import 'package:cake_wallet/routes.dart';
12 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
13 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
14 import 'package:cake_wallet/wallet_type_utils.dart';
15 import 'package:cw_core/hardware/device_connection_type.dart';
16 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
17 import 'package:cw_core/utils/print_verbose.dart';
18 import 'package:cw_core/wallet_base.dart';
19 +import 'package:cw_core/wallet_info.dart';
20 import 'package:cw_core/wallet_type.dart';
21 import 'package:flutter/widgets.dart';
22 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
@@ -23,15 +27,14 @@ part 'ledger_view_model.g.dart';
27
28 class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
29
26 -abstract class LedgerViewModelBase with Store {
27 - // late final Ledger ledger;
30 +abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
31 late final sdk.LedgerInterface ledgerPlusBLE;
32 late final sdk.LedgerInterface ledgerPlusUSB;
33
34 bool get _doesSupportHardwareWallets {
35 if (isMoneroOnly) {
36 return DeviceConnectionType.supportedConnectionTypes(
34 - WalletType.monero, Platform.isIOS)
37 + WalletType.monero, HardwareWalletType.ledger, Platform.isIOS)
38 .isNotEmpty;
39 }
40
@@ -40,8 +43,8 @@ abstract class LedgerViewModelBase with Store {
43
44 LedgerViewModelBase() {
45 if (_doesSupportHardwareWallets) {
43 - reaction((_) => bleIsEnabled, (_) {
44 - if (bleIsEnabled) _initBLE();
46 + reaction((_) => isBleEnabled, (_) {
47 + if (isBleEnabled) _initBLE();
48 });
49 updateBleState();
50
@@ -51,13 +54,17 @@ abstract class LedgerViewModelBase with Store {
54 }
55 }
56
57 + @override
58 @observable
55 - bool bleIsEnabled = false;
59 + bool isBleEnabled = false;
60 +
61 + @override
62 + bool get hasBluetooth => true;
63
64 bool _bleIsInitialized = false;
65
66 Future<void> _initBLE() async {
60 - if (bleIsEnabled && !_bleIsInitialized) {
67 + if (isBleEnabled && !_bleIsInitialized) {
68 ledgerPlusBLE = sdk.LedgerInterface.ble(
69 onPermissionRequest: (_) async {
70 if (Platform.isMacOS) return true;
@@ -76,24 +83,32 @@ abstract class LedgerViewModelBase with Store {
83 }
84 }
85
86 + @override
87 Future<void> updateBleState() async {
88 final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
89
90 final newState = bleState == sdk.AvailabilityState.poweredOn;
91
84 - if (newState != bleIsEnabled) bleIsEnabled = newState;
92 + if (newState != isBleEnabled) isBleEnabled = newState;
93 }
94
87 - Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
95 + @override
96 + Stream<HardwareWalletDevice> scanForBleDevices() =>
97 + ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d));
98
89 - Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
99 + @override
100 + Future<List<HardwareWalletDevice>> getAllUsbDevices() => ledgerPlusUSB.devices
101 + .then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList());
102
103 + @override
104 Future<void> stopScanning() async {
105 if (_bleIsInitialized) await ledgerPlusBLE.stopScanning();
106 if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
107 }
108
96 - Future<bool> connectLedger(sdk.LedgerDevice device, WalletType type) async {
109 + @override
110 + Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
111 + if (!(device is LedgerHardwareWalletDevice)) return false;
112 if (_isConnecting) return false;
113 _isConnecting = true;
114 _connectingWalletType = type;
@@ -103,18 +118,18 @@ abstract class LedgerViewModelBase with Store {
118 } catch (_) {}
119 }
120
106 - final ledger = device.connectionType == sdk.ConnectionType.ble
121 + final ledger = device.connectionType == HardwareWalletConnectionType.ble
122 ? ledgerPlusBLE
123 : ledgerPlusUSB;
124
125 if (_connectionChangeSubscription == null) {
126 _connectionChangeSubscription = ledger
112 - .deviceStateChanges(device.id)
127 + .deviceStateChanges(device.device.id)
128 .listen(_connectionChangeListener);
129 }
130
131 try {
117 - _connection = await ledger.connect(device);
132 + _connection = await ledger.connect(device.device);
133 _isConnecting = false;
134 return true;
135 } catch (e) {
@@ -140,6 +155,7 @@ abstract class LedgerViewModelBase with Store {
155 Routes.connectDevices,
156 arguments: ConnectDevicePageParams(
157 walletType: WalletType.monero,
158 + hardwareWalletType: HardwareWalletType.ledger,
159 allowChangeWallet: true,
160 isReconnect: true,
161 onConnectDevice: (context, ledgerVM) async {
@@ -153,26 +169,47 @@ abstract class LedgerViewModelBase with Store {
169 }
170 }
171
172 + @override
173 bool get isConnected => _connection != null && !(_connection!.isDisconnected);
174
175 sdk.LedgerConnection get connection => _connection!;
176
160 - void setLedger(WalletBase wallet) {
177 + @override
178 + void initWallet(WalletBase wallet) {
179 switch (wallet.type) {
180 case WalletType.monero:
181 return monero!.setLedgerConnection(wallet, connection);
182 case WalletType.bitcoin:
183 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
184 case WalletType.litecoin:
166 - return bitcoin!.setLedgerConnection(wallet, connection);
185 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
186 case WalletType.ethereum:
168 - return ethereum!.setLedgerConnection(wallet, connection);
187 + return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
188 case WalletType.polygon:
170 - return polygon!.setLedgerConnection(wallet, connection);
189 + return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
190 default:
191 throw Exception('Unexpected wallet type: ${wallet.type}');
192 }
193 }
194
195 + @override
196 + HardwareWalletService getHardwareWalletService(WalletType type) {
197 + switch (type) {
198 + case WalletType.bitcoin:
199 + return bitcoin!.getLedgerHardwareWalletService(connection, true);
200 + case WalletType.litecoin:
201 + return bitcoin!.getLedgerHardwareWalletService(connection, false);
202 + case WalletType.ethereum:
203 + return ethereum!.getLedgerHardwareWalletService(connection);
204 + case WalletType.polygon:
205 + return polygon!.getLedgerHardwareWalletService(connection);
206 + default:
207 + throw UnimplementedError();
208 + }
209 + }
210 +
211 +
212 + @override
213 String? interpretErrorCode(String error) {
214 if (error.contains("Make sure no other program is communicating with the Ledger")) {
215 return error;
lib/view_model/restore/restore_wallet.dart
+4
@@ -1,6 +1,7 @@
1 import 'dart:convert';
2
3 import 'package:cake_wallet/view_model/restore/restore_mode.dart';
4 +import 'package:cw_core/utils/zpub.dart';
5 import 'package:cw_core/wallet_type.dart';
6
7 class RestoredWallet {
@@ -44,6 +45,9 @@ class RestoredWallet {
45 } catch (e) {
46 // fine, we don't care, it is only for monero anyway
47 }
48 + if (json['zpub'] != null) {
49 + json['xpub'] = convertZpubToXpub(json['zpub'] as String);
50 + }
51 json['view_key'] ??= json['xpub'];
52 final height = json['height'] as String?;
53 return RestoredWallet(
lib/view_model/restore/wallet_restore_from_qr_code.dart
+10 -6
@@ -58,6 +58,7 @@ class WalletRestoreFromQRCode {
58 final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
59
60 if (code.startsWith("xpub")) return WalletType.bitcoin;
61 + if (code.startsWith("zpub")) return WalletType.bitcoin;
62
63 if (extracted == null) {
64 // Special case for view-only monero wallet
@@ -109,9 +110,11 @@ class WalletRestoreFromQRCode {
110 if (code == null) throw Exception("Unexpected scan QR code value: aborted");
111 if (code.isEmpty) throw Exception('Unexpected scan QR code value: value is empty');
112
113 + if (code.startsWith("[")) code = code.substring(code.indexOf("]") + 1);
114 +
115 String formattedUri = '';
116 WalletType? walletType = _extractWalletType(code);
114 -
117 + final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????';
118 if (walletType == null) {
119 await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually");
120 walletType =
@@ -122,14 +125,14 @@ class WalletRestoreFromQRCode {
125
126 formattedUri = seedPhrase != null
127 ? '$walletType:?seed=$seedPhrase'
125 - : code.startsWith('xpub')
126 - ? '$walletType:?xpub=$code'
128 + : code.startsWith(prefix)
129 + ? '$walletType:?$prefix=$code'
130 : throw Exception('Failed to determine valid seed phrase');
131 } else {
132 final index = code.indexOf(':');
133 final query = code.substring(index + 1).replaceAll('?', '&');
131 - formattedUri = code.startsWith('xpub')
132 - ? '$walletType:?xpub=$code'
134 + formattedUri = code.startsWith(prefix)
135 + ? '$walletType:?$prefix=$code'
136 :'$walletType:?$query';
137 }
138
@@ -167,7 +170,8 @@ class WalletRestoreFromQRCode {
170 throw Exception('Unexpected restore mode: tx_payment_id is invalid');
171 }
172
170 - if (credentials.containsKey("xpub")) {
173 + if (credentials.containsKey("xpub") ||
174 + credentials.containsKey("zpub")) {
175 return WalletRestoreMode.keys;
176 }
177
lib/view_model/send/send_view_model.dart
+3 -2
@@ -36,6 +36,7 @@ import 'package:cake_wallet/tron/tron.dart';
36 import 'package:cake_wallet/utils/payment_request.dart';
37 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
38 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
39 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
40 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
41 import 'package:cake_wallet/view_model/send/fees_view_model.dart';
42 import 'package:cake_wallet/view_model/send/output.dart';
@@ -93,7 +94,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
94 this.balanceViewModel,
95 this.contactListViewModel,
96 this.transactionDescriptionBox,
96 - this.ledgerViewModel,
97 + this.hardwareWalletViewModel,
98 this.unspentCoinsListViewModel,
99 this.feesViewModel,
100 this.walletInfoSource, {
@@ -368,7 +369,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
369 final SendTemplateViewModel sendTemplateViewModel;
370 final BalanceViewModel balanceViewModel;
371 final ContactListViewModel contactListViewModel;
371 - final LedgerViewModel? ledgerViewModel;
372 + final HardwareWalletViewModel? hardwareWalletViewModel;
373 final FeesViewModel feesViewModel;
374 final FiatConversionStore _fiatConversationStore;
375 final Box<TransactionDescription> transactionDescriptionBox;
lib/view_model/settings/other_settings_view_model.dart
+29 -15
@@ -1,17 +1,15 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
3 -import 'package:cake_wallet/entities/provider_types.dart';
4 -import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/store/settings_store.dart';
4 import 'package:cake_wallet/utils/package_info.dart';
5 import 'package:cake_wallet/view_model/send/send_view_model.dart';
8 -// import 'package:package_info/package_info.dart';
6 import 'package:collection/collection.dart';
7 import 'package:cw_core/balance.dart';
8 import 'package:cw_core/transaction_history.dart';
9 import 'package:cw_core/transaction_info.dart';
10 import 'package:cw_core/transaction_priority.dart';
11 import 'package:cw_core/wallet_base.dart';
12 +import 'package:cw_core/wallet_info.dart';
13 import 'package:cw_core/wallet_type.dart';
14 import 'package:mobx/mobx.dart';
15
@@ -58,20 +56,28 @@ abstract class OtherSettingsViewModelBase with Store {
56
57 @computed
58 bool get changeRepresentativeEnabled =>
61 - _wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
59 + [WalletType.nano, WalletType.banano].contains(_wallet.type);
60
61 @computed
64 - bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
65 - _wallet.type == WalletType.solana ||
66 - _wallet.type == WalletType.tron);
62 + bool get changeHardwareWalletTypeEnabled => [
63 + HardwareWalletType.cupcake,
64 + HardwareWalletType.coldcard,
65 + HardwareWalletType.seedsigner,
66 + ].contains(_wallet.hardwareWalletType);
67 +
68 + @computed
69 + bool get displayTransactionPriority =>
70 + !(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type));
71
72 String getDisplayPriority(dynamic priority) {
73 final _priority = priority as TransactionPriority;
74
71 - if (_wallet.type == WalletType.bitcoin ||
72 - _wallet.type == WalletType.litecoin ||
73 - _wallet.type == WalletType.bitcoinCash ||
74 - _wallet.type == WalletType.dogecoin) {
75 + if ([
76 + WalletType.bitcoin,
77 + WalletType.litecoin,
78 + WalletType.bitcoinCash,
79 + WalletType.dogecoin,
80 + ].contains(_wallet.type)) {
81 final rate = bitcoin!.getFeeRate(_wallet, _priority);
82 return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
83 }
@@ -82,10 +88,12 @@ abstract class OtherSettingsViewModelBase with Store {
88 String getDisplayBitcoinPriority(dynamic priority, int customValue) {
89 final _priority = priority as TransactionPriority;
90
85 - if (_wallet.type == WalletType.bitcoin ||
86 - _wallet.type == WalletType.litecoin ||
87 - _wallet.type == WalletType.bitcoinCash ||
88 - _wallet.type == WalletType.dogecoin) {
91 + if ([
92 + WalletType.bitcoin,
93 + WalletType.litecoin,
94 + WalletType.bitcoinCash,
95 + WalletType.dogecoin,
96 + ].contains(_wallet.type)) {
97 final rate = bitcoin!.getFeeRate(_wallet, _priority);
98 return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate,
99 customRate: customValue);
@@ -105,6 +113,12 @@ abstract class OtherSettingsViewModelBase with Store {
113 _settingsStore.priority[_wallet.type] = priority;
114 }
115
116 + @action
117 + Future<void> onHardwareWalletTypeChanged(HardwareWalletType hwType) async {
118 + _wallet.walletInfo.hardwareWalletType = hwType;
119 + return _wallet.walletInfo.save();
120 + }
121 +
122 @computed
123 double get customBitcoinFeeRate =>
124 _settingsStore.customBitcoinFeeRate.toDouble();
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+1 -1
@@ -473,7 +473,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
473 wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
474
475 @computed
476 - bool get isCupcake =>
476 + bool get isBitcoinViewOnly =>
477 wallet.type == WalletType.bitcoin &&
478 (bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty;
479
lib/view_model/wallet_hardware_restore_view_model.dart
+13 -26
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/monero/monero.dart';
7 import 'package:cake_wallet/polygon/polygon.dart';
8 import 'package:cake_wallet/store/app_store.dart';
9 +import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
10 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
11 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
12 import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
@@ -24,12 +25,12 @@ class WalletHardwareRestoreViewModel = WalletHardwareRestoreViewModelBase
25 with _$WalletHardwareRestoreViewModel;
26
27 abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with Store {
27 - final LedgerViewModel ledgerViewModel;
28 + final HardwareWalletViewModel hardwareWalletVM;
29
30 int _nextIndex = 0;
31
32 WalletHardwareRestoreViewModelBase(
32 - this.ledgerViewModel,
33 + this.hardwareWalletVM,
34 AppStore appStore,
35 WalletCreationService walletCreationService,
36 Box<WalletInfo> walletInfoSource,
@@ -56,33 +57,16 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
57 @action
58 Future<void> getNextAvailableAccounts(int limit) async {
59 try {
59 - List<HardwareAccountData> accounts;
60 - switch (type) {
61 - case WalletType.bitcoin:
62 - accounts = await bitcoin!
63 - .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
64 - break;
65 - case WalletType.litecoin:
66 - accounts = await bitcoin!
67 - .getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
68 - break;
69 - case WalletType.ethereum:
70 - accounts = await ethereum!
71 - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
72 - break;
73 - case WalletType.polygon:
74 - accounts = await polygon!
75 - .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
76 - break;
77 - default:
78 - return;
79 - }
60 + List<HardwareAccountData> accounts = await hardwareWalletVM
61 + .getHardwareWalletService(type)
62 + .getAvailableAccounts(index: _nextIndex, limit: limit);
63
64 availableAccounts.addAll(accounts);
65 _nextIndex += limit;
66 } catch (e) {
67 printV(e);
85 - error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
68 + error =
69 + hardwareWalletVM.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
70 }
71
72 isLoadingMoreAccounts = false;
@@ -110,7 +94,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
94
95 credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
96 name: name,
113 - ledgerConnection: ledgerViewModel.connection,
97 + ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
98 password: password,
99 height: _options['height'] as int? ?? 0,
100 );
@@ -118,7 +102,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
102 throw Exception('Unexpected type: ${type.toString()}');
103 }
104
121 - credentials.hardwareWalletType = HardwareWalletType.ledger;
105 + if (hardwareWalletVM is LedgerViewModel)
106 + credentials.hardwareWalletType = HardwareWalletType.ledger;
107 + else
108 + credentials.hardwareWalletType = HardwareWalletType.bitbox;
109
110 return credentials;
111 }
lib/view_model/wallet_restore_view_model.dart
+3 -1
@@ -33,7 +33,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreVi
33 abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
34 WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
35 Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
36 - {required WalletType type, this.restoredWallet})
36 + {required WalletType type, this.restoredWallet, this.hardwareWalletType})
37 : isButtonEnabled = restoredWallet != null,
38 hasPassphrase = false,
39 mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
@@ -102,6 +102,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
102 ].contains(type);
103
104 final RestoredWallet? restoredWallet;
105 + final HardwareWalletType? hardwareWalletType;
106
107 @observable
108 WalletRestoreMode mode;
@@ -226,6 +227,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
227 name: name,
228 password: password,
229 xpub: viewKey!,
230 + hardwareWalletType: hardwareWalletType,
231 );
232
233 case WalletType.monero:
pubspec_base.yaml
+6
@@ -116,6 +116,8 @@ dependencies:
116 git:
117 url: https://github.com/vespr-wallet/ledger-flutter-plus
118 ref: 60817d4b20144f9da9029f5034790272795b9d38
119 + bitbox_flutter:
120 + path: ./scripts/bitbox_flutter
121 hashlib: 1.19.2
122 on_chain:
123 git:
@@ -186,6 +188,10 @@ dependency_overrides:
188 git:
189 url: https://github.com/vespr-wallet/ledger-flutter-plus
190 ref: 60817d4b20144f9da9029f5034790272795b9d38
191 + ledger_usb_plus:
192 + git:
193 + url: https://github.com/konstantinullrich/ledger-usb-plus
194 + ref: dad482c847c0d0eedceabbb40d3478e64b65e630
195 socks5_proxy:
196 git:
197 url: https://github.com/LacticWhale/socks_dart.git
res/values/strings_ar.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "وصف المجموعة",
172 "collection_name": "اسم المجموعة",
173 "color_theme": "سمة اللون",
174 + "coming_soon_tag": "قريباً",
175 "commit_transaction_amount_fee": "تنفيذ الصفقة\nالمبلغ: ${amount}\nالرسوم: ${fee}",
176 "confirm": "تأكيد",
177 "confirm_delete_template": "سيؤدي هذا الإجراء إلى حذف هذا القالب. هل ترغب في الاستمرار؟",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "حدد جهازك في محفظة كعكة ومتابعة الإعداد.",
194 "connect_yats": "توصيل Yats",
195 "connect_your_hardware_wallet": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth أو USB",
195 - "connect_your_hardware_wallet_ios": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth",
196 + "connect_your_hardware_wallet_ble": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth",
197 + "connect_your_hardware_wallet_usb": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام USB",
198 "connected": "متصل",
199 "connection_sync": "الاتصال والمزامنة",
200 "connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.",
787 "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
788 "select_hw_account_below": "الرجاء تحديد حساب الاستعادة أدناه:",
789 + "select_manufacturer_title": "حدد الشركة المصنعة",
790 "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ",
791 "select_your_country": "الرجاء تحديد بلدك",
792 "selected_trocador_provider": "مزود تروكادور المختار",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "يمكنك العودة إلى DAPP الخاص بك الآن",
1150 "your": "لك",
1151 "yy": "YY"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_bg.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Описание на колекцията",
172 "collection_name": "Име на колекцията",
173 "color_theme": "Цвят",
174 + "coming_soon_tag": "Очаквайте скоро",
175 "commit_transaction_amount_fee": "Изпълняване на транзакция\nСума: ${amount}\nТакса: ${fee}",
176 "confirm": "Потвърждаване",
177 "confirm_delete_template": "Този шаблон ще бъде изтрит. Искате ли да продължите?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Изберете вашето устройство в портфейла за торта и продължете с настройката.",
194 "connect_yats": "Добавете Yats",
195 "connect_your_hardware_wallet": "Свържете хардуерния си портфейл с помощта на Bluetooth или USB",
195 - "connect_your_hardware_wallet_ios": "Свържете хардуерния си портфейл с помощта на Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Свържете хардуерния си портфейл с помощта на Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Свържете хардуерния си портфейл с помощта на USB",
198 "connected": "Свързани",
199 "connection_sync": "Свързване и синхронизиране",
200 "connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.",
787 "select_destination": "Моля, изберете дестинация за архивния файл.",
788 "select_hw_account_below": "Моля, изберете кой акаунт да възстановите по -долу:",
789 + "select_manufacturer_title": "Изберете производител",
790 "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.",
791 "select_your_country": "Моля, изберете вашата страна",
792 "selected_trocador_provider": "Избран доставчик на трокадор",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Можете да се върнете при вашия Dapp сега",
1150 "your": "Вашия",
1151 "yy": "гг"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_cs.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Popis sbírky",
172 "collection_name": "Název sbírky",
173 "color_theme": "Barevný motiv",
174 + "coming_soon_tag": "Přijde brzy",
175 "commit_transaction_amount_fee": "Odeslat transakci\nČástka: ${amount}\nPoplatek: ${fee}",
176 "confirm": "Potvrdit",
177 "confirm_delete_template": "Tato akce smaže tuto šablonu. Přejete si pokračovat?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Vyberte zařízení v peněžence dortu a pokračujte v nastavení.",
194 "connect_yats": "Připojit Yaty",
195 "connect_your_hardware_wallet": "Připojte hardwarovou peněženku pomocí Bluetooth nebo USB",
195 - "connect_your_hardware_wallet_ios": "Připojte hardwarovou peněženku pomocí Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Připojte hardwarovou peněženku pomocí Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Připojte hardwarovou peněženku pomocí USB",
198 "connected": "Připojeno",
199 "connection_sync": "Připojení a synch.",
200 "connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.",
787 "select_destination": "Vyberte cíl pro záložní soubor.",
788 "select_hw_account_below": "Níže vyberte, který účet chcete obnovit:",
789 + "select_manufacturer_title": "Vyberte výrobce",
790 "select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.",
791 "select_your_country": "Vyberte prosím svou zemi",
792 "selected_trocador_provider": "Vybraný poskytovatel Trocador",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu",
1150 "your": "Vaše",
1151 "yy": "YY"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_de.arb
+9 -6
@@ -158,7 +158,7 @@
158 "choose_from_available_options": "Wähle aus verfügbaren Optionen:",
159 "choose_one": "Wähle ein",
160 "choose_qr_code_format": "Wählen Sie das QR-Code-Format",
161 - "choose_qr_code_format_note": "Wählen Sie das richtige Format für Ihre Hardware-Wallet:",
161 + "choose_qr_code_format_note": "Wählen Sie das richtige Format für Ihr Hardware-Wallet:",
162 "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
163 "choose_wallet_currency": "Bitte wählen Sie die Währung der Wallet:",
164 "choose_wallet_group": "Wählen Sie Walletgruppe",
@@ -171,6 +171,7 @@
171 "collection_description": "Sammlung Beschreibung",
172 "collection_name": "Sammlungsname",
173 "color_theme": "Farbthema",
174 + "coming_soon_tag": "Kommt bald",
175 "commit_transaction_amount_fee": "Transaktion absenden\nBetrag: ${amount}\nGebühr: ${fee}",
176 "confirm": "Bestätigen",
177 "confirm_delete_template": "Diese Aktion löscht diese Vorlage. Möchten Sie fortfahren?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Wählen Sie Ihr Gerät in Cake Wallet aus und fahren Sie mit dem Setup fort.",
194 "connect_yats": "Yats verbinden",
195 "connect_your_hardware_wallet": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth oder USB",
195 - "connect_your_hardware_wallet_ios": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Schließen Sie Ihre Hardware-Wallet mit USB an",
198 "connected": "Verbunden",
199 "connection_sync": "Verbindung und Synchronisierung",
200 "connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen",
@@ -354,7 +356,7 @@
356 "error_while_processing": "Ein Fehler beim Proceessing trat ein Fehler auf",
357 "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen",
358 "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten",
357 - "establishing_tor_connection": "Einstellung der TOR-Verbindung",
359 + "establishing_tor_connection": "Einstellung der Tor-Verbindung",
360 "estimated": "Geschätzt",
361 "estimated_new_fee": "Geschätzte neue Gebühr",
362 "estimated_receive_amount": "Geschätzter Empfangsbetrag",
@@ -646,8 +648,8 @@
648 "purchase_gift_card": "Geschenkkarte kaufen",
649 "purple_dark_theme": "Lila dunkle Thema",
650 "qr_code_format": "QR-Codeformat",
649 - "qr_code_format_body": "BCUR (Blockchain Commons Uniform Resources) ist der Branchenstandard und am weitesten verbreitet mit Bitcoin-Hardware-Wallet und sollte von den meisten Benutzern ausgewählt werden. BBQR (Better Bitcoin QR) ist ein einzigartiges Format, das nur von Coldcard-Hardware-Wallet verwendet wird, beginnend mit ihrem Modell Q.",
650 - "qr_code_format_note": "Unterschiedliche QR-Codeformate werden von verschiedenen Hardware-Wallet im Bitcoin-Bereich verwendet",
651 + "qr_code_format_body": "BCUR (Blockchain Commons Uniform Resources) ist der Branchenstandard und am weitesten verbreitet mit Bitcoin-Hardware-Wallets und sollte von den meisten Benutzern ausgewählt werden. BBQR (Better Bitcoin QR) ist ein einzigartiges Format, das nur von Coldcard-Hardware-Wallets verwendet wird, beginnend mit ihrem Modell Q.",
652 + "qr_code_format_note": "Unterschiedliche QR-Codeformate werden von verschiedenen Hardware-Wallets im Bitcoin-Bereich verwendet",
653 "qr_fullscreen": "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen",
654 "qr_instruction": "Verwenden Sie diese Adresse, um ein Token oder ein Sammlerstück zu erhalten",
655 "qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.",
788 "select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.",
789 "select_hw_account_below": "Bitte wählen Sie unten, welches Konto unten wiederhergestellt werden soll:",
790 + "select_manufacturer_title": "Hersteller auswählen",
791 "select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.",
792 "select_your_country": "Bitte wählen Sie Ihr Land aus",
793 "selected_trocador_provider": "Ausgewählter Trocador-Anbieter",
@@ -894,7 +897,7 @@
897 "sort_by": "Sortiere nach",
898 "spend_key_private": "Spend Key (geheim)",
899 "spend_key_public": "Spend Key (öffentlich)",
897 - "starting_tor_proxy": "TOR-Proxy beginnen",
900 + "starting_tor_proxy": "Tor-Proxy beginnen",
901 "status": "Status: ",
902 "step": "Schritt",
903 "string_default": "Standard",
res/values/strings_en.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Collection Description",
172 "collection_name": "Collection Name",
173 "color_theme": "Color theme",
174 + "coming_soon_tag": "Coming Soon",
175 "commit_transaction_amount_fee": "Commit transaction\nAmount: ${amount}\nFee: ${fee}",
176 "confirm": "Confirm",
177 "confirm_delete_template": "This action will delete this template. Do you wish to continue?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Select your device in Cake Wallet and continue with the setup.",
194 "connect_yats": "Connect Yats",
195 "connect_your_hardware_wallet": "Connect your hardware wallet using Bluetooth or USB",
195 - "connect_your_hardware_wallet_ios": "Connect your hardware wallet using Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Connect your hardware wallet using Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Connect your hardware wallet using USB",
198 "connected": "Connected",
199 "connection_sync": "Connection and sync",
200 "connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.",
788 "select_destination": "Please select destination for the backup file.",
789 "select_hw_account_below": "Please select which account to restore below:",
790 + "select_manufacturer_title": "Select Manufacturer",
791 "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.",
792 "select_your_country": "Please select your country",
793 "selected_trocador_provider": "selected Trocador provider",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "You can go back to your dApp now",
1151 "your": "Your",
1152 "yy": "YY"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_es.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Descripción de la colección",
172 "collection_name": "Nombre de colección",
173 "color_theme": "Tema de color",
174 + "coming_soon_tag": "Muy pronto",
175 "commit_transaction_amount_fee": "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}",
176 "confirm": "Confirmar",
177 "confirm_delete_template": "Esta acción eliminará esta plantilla. ¿Deseas continuar?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Seleccione su dispositivo en Cake Wallet y continúe con la configuración.",
194 "connect_yats": "Conectar Yats",
195 "connect_your_hardware_wallet": "Conecta tu billetera de hardware con Bluetooth o USB",
195 - "connect_your_hardware_wallet_ios": "Conecta tu billetera de hardware con Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Conecta tu billetera de hardware con Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Conecte su billetera de hardware con USB",
198 "connected": "Conectado",
199 "connection_sync": "Conexión y sincronización",
200 "connectWalletPrompt": "Conecte tu billetera con WalletConnect para realizar transacciones",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
788 "select_destination": "Selecciona el destino del archivo de copia de seguridad.",
789 "select_hw_account_below": "Seleccione qué cuenta restaurar a continuación:",
790 + "select_manufacturer_title": "Seleccionar fabricante",
791 "select_sell_provider_notice": "Selecciona un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.",
792 "select_your_country": "Seleccione su país",
793 "selected_trocador_provider": "Proveedor de Trocador seleccionado",
@@ -1148,4 +1151,4 @@
1151 "youCanGoBackToYourDapp": "Puedes volver a tu dapp ahora",
1152 "your": "Su",
1153 "yy": "YY"
1151 -}
\ No newline at end of file
1154 +}
res/values/strings_fr.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Description de la collection",
172 "collection_name": "Nom de collection",
173 "color_theme": "Couleur du thème",
174 + "coming_soon_tag": "À venir",
175 "commit_transaction_amount_fee": "Valider la transaction\nMontant : ${amount}\nFrais : ${fee}",
176 "confirm": "Confirmer",
177 "confirm_delete_template": "Cette action va supprimer ce modèle. Souhaitez-vous continuer ?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Sélectionnez votre appareil dans Cake Wallet et poursuivez la configuration.",
194 "connect_yats": "Connecter Yats",
195 "connect_your_hardware_wallet": "Connectez votre portefeuille matériel à l'aide du Bluetooth ou de l'USB",
195 - "connect_your_hardware_wallet_ios": "Connectez votre portefeuille matériel à l'aide du Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Connectez votre portefeuille matériel à l'aide du Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Connectez votre portefeuille matériel à l'aide USB",
198 "connected": "Connecté",
199 "connection_sync": "Connexion et synchronisation",
200 "connectWalletPrompt": "Connectez votre portefeuille (wallet) avec WalletConnect pour effectuer des transactions",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.",
787 "select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.",
788 "select_hw_account_below": "Veuillez sélectionner le compte à restaurer ci-dessous:",
789 + "select_manufacturer_title": "Sélectionner le fabricant",
790 "select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.",
791 "select_your_country": "Veuillez sélectionner votre pays",
792 "selected_trocador_provider": "fournisseur de trocador sélectionné",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Vous pouvez retourner à votre Dapp maintenant",
1150 "your": "Ton",
1151 "yy": "AA"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_ha.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Bayanin tarin",
172 "collection_name": "Sunan tattara",
173 "color_theme": "Jigon launi",
174 + "coming_soon_tag": "Zuwan nan da sannu",
175 "commit_transaction_amount_fee": "Aikata ciniki\nAdadi: ${amount}\nKuda: ${fee}",
176 "confirm": "Tabbatar",
177 "confirm_delete_template": "Wannan aikin zai share wannan samfuri. Kuna so ku ci gaba?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Zaɓi na'urarka a cikin walat walat kuma ci gaba da saiti.",
194 "connect_yats": "Haɗa Yats",
195 "connect_your_hardware_wallet": "Haɗa Wallake Wallware ɗinku ta Bluetooth ko USB",
195 - "connect_your_hardware_wallet_ios": "Haɗa kayan aikinku ta Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Haɗa kayan aikinku ta Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Haɗa kayan aikinku ta amfani da USB",
198 "connected": "Wanda aka haɗa",
199 "connection_sync": "Haɗi da daidaitawa",
200 "connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala",
@@ -786,6 +788,7 @@
788 "select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.",
789 "select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.",
790 "select_hw_account_below": "Da fatan za a zabi wanda asusun zai gyara a ƙasa:",
791 + "select_manufacturer_title": "Select Manaser",
792 "select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.",
793 "select_your_country": "Da fatan za a zabi ƙasarku",
794 "selected_trocador_provider": "Zabi mai bada TORACAD",
@@ -1149,4 +1152,4 @@
1152 "youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu",
1153 "your": "Naku",
1154 "yy": "YY"
1152 -}
\ No newline at end of file
1155 +}
res/values/strings_hi.arb
+6 -3
@@ -171,6 +171,7 @@
171 "collection_description": "संग्रह विवरण",
172 "collection_name": "संग्रह नाम",
173 "color_theme": "रंग विषय",
174 + "coming_soon_tag": "जल्द आ रहा है",
175 "commit_transaction_amount_fee": "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}",
176 "confirm": "की पुष्टि करें",
177 "confirm_delete_template": "यह क्रिया इस टेम्पलेट को हटा देगी। क्या आप जारी रखना चाहते हैं?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "केक वॉलेट में अपने डिवाइस का चयन करें और सेटअप के साथ जारी रखें।",
194 "connect_yats": "कनेक्ट Yats",
195 "connect_your_hardware_wallet": "ब्लूटूथ या यूएसबी का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
195 - "connect_your_hardware_wallet_ios": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
196 + "connect_your_hardware_wallet_ble": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
197 + "connect_your_hardware_wallet_usb": "USB का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
198 "connected": "जुड़े हुए",
199 "connection_sync": "कनेक्शन और सिंक",
200 "connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें",
@@ -604,8 +606,8 @@
606 "payjoin_unavailable_sheet_title": "Payjoin अनुपलब्ध क्यों है?",
607 "payment_id": "भुगतान ID: ",
608 "payment_made_easy": "भुगतान आसान किया गया",
607 - "payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
609 "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
610 + "payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
611 "payments": "भुगतान",
612 "pending": " (अपूर्ण)",
613 "percentageOf": "${amount} का",
@@ -786,6 +788,7 @@
788 "select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।",
789 "select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।",
790 "select_hw_account_below": "कृपया नीचे पुनर्स्थापित करने के लिए कौन सा खाता चुनें:",
791 + "select_manufacturer_title": "निर्माता का चयन करें",
792 "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।",
793 "select_your_country": "कृपया अपने देश का चयन करें",
794 "selected_trocador_provider": "चयनित ट्रोकैडर प्रदाता",
@@ -1148,4 +1151,4 @@
1151 "youCanGoBackToYourDapp": "अब आप अपने DAPP पर वापस जा सकते हैं",
1152 "your": "आपका",
1153 "yy": "वाईवाई"
1151 -}
\ No newline at end of file
1154 +}
res/values/strings_hr.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Zbirka Opis opisa",
172 "collection_name": "Naziv kolekcije",
173 "color_theme": "Shema boja",
174 + "coming_soon_tag": "Dolazi uskoro",
175 "commit_transaction_amount_fee": "Izvrši transakciju \nAmount: ${amount}\nFee: ${fee}",
176 "confirm": "Potvrdi",
177 "confirm_delete_template": "Ovom ćete radnjom izbrisati ovaj predložak. Želite li nastaviti?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Odaberite svoj uređaj u novčanici za torte i nastavite s postavljanjem.",
194 "connect_yats": "Povežite Yats",
195 "connect_your_hardware_wallet": "Spojite svoj hardverski novčanik pomoću Bluetooth -a ili USB -a",
195 - "connect_your_hardware_wallet_ios": "Spojite svoj hardverski novčanik pomoću Bluetooth -a",
196 + "connect_your_hardware_wallet_ble": "Spojite svoj hardverski novčanik pomoću Bluetooth -a",
197 + "connect_your_hardware_wallet_usb": "Spojite svoj hardverski novčanik pomoću USB -a",
198 "connected": "Povezan",
199 "connection_sync": "Povezivanje i sinkronizacija",
200 "connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.",
787 "select_destination": "Odaberite odredište za datoteku sigurnosne kopije.",
788 "select_hw_account_below": "Molimo odaberite koji će se račun vratiti u nastavku:",
789 + "select_manufacturer_title": "Odaberite proizvođača",
790 "select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.",
791 "select_your_country": "Odaberite svoju zemlju",
792 "selected_trocador_provider": "Odabrani pružatelj usluga trokadora",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp",
1150 "your": "Vaš",
1151 "yy": "GG"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_hy.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Հավաքածուի նկարագրությունը",
172 "collection_name": "Հավաքածուի անուն",
173 "color_theme": "Գույների տեսք",
174 + "coming_soon_tag": "Շուտով",
175 "commit_transaction_amount_fee": "Հաստատել գործարքը\nՍկզբնական գումար. ${amount}\nՄիջնորդավճար. ${fee}",
176 "confirm": "Հաստատել",
177 "confirm_delete_template": "Այս գործողությունը կջնջի այս ձևանմուշը: Ցանկանու՞մ եք շարունակել։",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Ընտրեք ձեր սարքը տորթի դրամապանակում եւ շարունակեք կարգաբերումը:",
194 "connect_yats": "Միացրեք Yat-ները",
195 "connect_your_hardware_wallet": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի կամ USB-ի միջոցով",
195 - "connect_your_hardware_wallet_ios": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի միջոցով",
196 + "connect_your_hardware_wallet_ble": "Միացրեք ձեր ապարատային դրամապանակը Bluetooth-ի միջոցով",
197 + "connect_your_hardware_wallet_usb": "Միացրեք ձեր ապարատային դրամապանակը `օգտագործելով USB",
198 "connected": "Միացված",
199 "connection_sync": "Կապ և սինխրոնիզացիա",
200 "connectWalletPrompt": "Միացրեք ձեր դրամապանակը WalletConnect-ի միջոցով գործարքներ կատարելու համար",
@@ -783,6 +785,7 @@
785 "select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով",
786 "select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը",
787 "select_hw_account_below": "Խնդրում ենք ընտրել, թե որ հաշիվն է վերականգնել հետեւյալը.",
788 + "select_manufacturer_title": "Ընտրեք արտադրող",
789 "select_sell_provider_notice": "Ընտրեք վաճառքի մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն վաճառքի մատակարարը հավելվածի կարգավորումներում սահմանելով",
790 "select_your_country": "Խնդրում ենք ընտրել ձեր երկիրը",
791 "sell": "Ծախել",
@@ -1144,4 +1147,4 @@
1147 "youCanGoBackToYourDapp": "Այժմ կարող եք վերադառնալ ձեր DAPP- ին",
1148 "your": "Քո",
1149 "yy": "ՏՏ"
1147 -}
\ No newline at end of file
1150 +}
res/values/strings_id.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Deskripsi Koleksi",
172 "collection_name": "Nama Koleksi",
173 "color_theme": "Tema warna",
174 + "coming_soon_tag": "Segera hadir",
175 "commit_transaction_amount_fee": "Lakukan transaksi\nJumlah: ${amount}\nBiaya: ${fee}",
176 "confirm": "Konfirmasi",
177 "confirm_delete_template": "Tindakan ini akan menghapus template ini. Apakah Anda ingin melanjutkan?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Pilih perangkat Anda di dompet kue dan lanjutkan dengan pengaturan.",
194 "connect_yats": "Hubungkan Yats",
195 "connect_your_hardware_wallet": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth atau USB",
195 - "connect_your_hardware_wallet_ios": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Hubungkan dompet perangkat keras Anda menggunakan USB",
198 "connected": "Terhubung",
199 "connection_sync": "Koneksi dan sinkronisasi",
200 "connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi",
@@ -787,6 +789,7 @@
789 "select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.",
790 "select_destination": "Silakan pilih tujuan untuk file cadangan.",
791 "select_hw_account_below": "Pilih akun mana yang akan dikembalikan di bawah ini:",
792 + "select_manufacturer_title": "Pilih produsen",
793 "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.",
794 "select_your_country": "Pilih negara Anda",
795 "selected_trocador_provider": "Penyedia Trocador Terpilih",
@@ -1149,4 +1152,4 @@
1152 "youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang",
1153 "your": "Milikmu",
1154 "yy": "YY"
1152 -}
\ No newline at end of file
1155 +}
res/values/strings_it.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Descrizione della raccolta",
172 "collection_name": "Nome della raccolta",
173 "color_theme": "Colore tema",
174 + "coming_soon_tag": "Prossimamente",
175 "commit_transaction_amount_fee": "Invia transazione\nAmmontare: ${amount}\nCommissione: ${fee}",
176 "confirm": "Conferma",
177 "confirm_delete_template": "Questa azione cancellerà questo modello. Desideri continuare?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Seleziona il tuo dispositivo nel portafoglio Cake e continua con l'installazione.",
194 "connect_yats": "Connetti Yats",
195 "connect_your_hardware_wallet": "Collega il tuo portafoglio hardware tramite Bluetooth o USB",
195 - "connect_your_hardware_wallet_ios": "Collega il tuo portafoglio hardware tramite Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Collega il tuo portafoglio hardware tramite Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Collega il tuo portafoglio hardware usando USB",
198 "connected": "Collegato",
199 "connection_sync": "Connessione e sincronizzazione",
200 "connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.",
788 "select_destination": "Seleziona la destinazione per il file di backup.",
789 "select_hw_account_below": "Seleziona quale account ripristinare:",
790 + "select_manufacturer_title": "Seleziona il produttore",
791 "select_sell_provider_notice": "Seleziona un provider di vendita sopra. Puoi saltare questa schermata impostando il tuo provider di vendita predefinito nelle impostazioni dell'app.",
792 "select_your_country": "Seleziona il tuo paese",
793 "selected_trocador_provider": "Provider di Trocador selezionato",
@@ -1149,4 +1152,4 @@
1152 "youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora",
1153 "your": "Tuo",
1154 "yy": "YY"
1152 -}
\ No newline at end of file
1155 +}
res/values/strings_ja.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "コレクションの説明",
172 "collection_name": "コレクション名",
173 "color_theme": "カラーテーマ",
174 + "coming_soon_tag": "近日公開",
175 "commit_transaction_amount_fee": "トランザクションをコミット\n量: ${amount}\n費用: ${fee}",
176 "confirm": "確認する",
177 "confirm_delete_template": "この操作により、このテンプレートが削除されます。 続行しますか?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "ケーキウォレットでデバイスを選択し、セットアップを続行します。",
194 "connect_yats": "Yatsを接続します",
195 "connect_your_hardware_wallet": "BluetoothまたはUSBを使用して、ハードウェアウォレットを接続します",
195 - "connect_your_hardware_wallet_ios": "Bluetoothを使用してハードウェアウォレットを接続します",
196 + "connect_your_hardware_wallet_ble": "Bluetoothを使用してハードウェアウォレットを接続します",
197 + "connect_your_hardware_wallet_usb": "USBを使用してハードウェアウォレットを接続します",
198 "connected": "接続",
199 "connection_sync": "接続と同期",
200 "connectWalletPrompt": "ウォレットを WalletConnect に接続して取引を行う",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。",
788 "select_destination": "バックアップファイルの保存先を選択してください。",
789 "select_hw_account_below": "以下に復元するアカウントを選択してください。",
790 + "select_manufacturer_title": "メーカーを選択します",
791 "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。",
792 "select_your_country": "あなたの国を選択してください",
793 "selected_trocador_provider": "選択したTrocadorプロバイダー",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "あなたは今あなたのダップに戻ることができます",
1151 "your": "あなたの",
1152 "yy": "YY"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_ko.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "컬렉션 설명",
172 "collection_name": "컬렉션 이름",
173 "color_theme": "색상 테마",
174 + "coming_soon_tag": "곧 올 것입니다",
175 "commit_transaction_amount_fee": "트랜잭션 커밋\n금액: ${amount}\n수수료: ${fee}",
176 "confirm": "확인",
177 "confirm_delete_template": "이 작업은 이 템플릿을 삭제합니다. 계속하시겠습니까?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "케이크 지갑에서 장치를 선택하고 설정을 계속하십시오.",
194 "connect_yats": "Yat 연결",
195 "connect_your_hardware_wallet": "블루투스 또는 USB를 사용하여 하드웨어 지갑 연결",
195 - "connect_your_hardware_wallet_ios": "블루투스를 사용하여 하드웨어 지갑 연결",
196 + "connect_your_hardware_wallet_ble": "블루투스를 사용하여 하드웨어 지갑 연결",
197 + "connect_your_hardware_wallet_usb": "USB를 사용하여 하드웨어 지갑을 연결하십시오",
198 "connected": "연결됨",
199 "connection_sync": "연결 및 동기화",
200 "connectWalletPrompt": "거래를 하려면 WalletConnect로 지갑을 연결하세요",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "위에서 구매 제공 업체를 선택하세요. 앱 설정에서 기본 구매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.",
788 "select_destination": "백업 파일의 대상을 선택하세요.",
789 "select_hw_account_below": "아래에서 복구할 계정을 선택하세요:",
790 + "select_manufacturer_title": "제조업체를 선택하십시오",
791 "select_sell_provider_notice": "위에서 판매 제공 업체를 선택하세요. 앱 설정에서 기본 판매 제공 업체를 설정하여 이 화면을 건너뛸 수 있습니다.",
792 "select_your_country": "국가를 선택하세요",
793 "selected_trocador_provider": "선택된 Trocador 제공 업체",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "이제 dApp으로 돌아갈 수 있습니다",
1151 "your": "당신의",
1152 "yy": "YY"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_my.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "စုဆောင်းခြင်းဖော်ပြချက်",
172 "collection_name": "စုဆောင်းခြင်းအမည်",
173 "color_theme": "အရောင်အပြင်အဆင်",
174 + "coming_soon_tag": "မကြာမီလာမည်",
175 "commit_transaction_amount_fee": "ငွေလွှဲခြင်း\nပမာဏ- ${amount}\nအခကြေးငွေ- ${fee}",
176 "confirm": "အတည်ပြုပါ။",
177 "confirm_delete_template": "ဤလုပ်ဆောင်ချက်သည် ဤပုံစံပြားကို ဖျက်လိုက်ပါမည်။ ဆက်လုပ်လိုပါသလား။",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Cake Wallet တွင်သင်၏ device ကိုရွေးချယ်ပြီး setup ကိုဆက်လုပ်ပါ။",
194 "connect_yats": "Yats ကိုချိတ်ဆက်ပါ။",
195 "connect_your_hardware_wallet": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth သို့မဟုတ် USB ကို သုံး. ချိတ်ဆက်ပါ",
195 - "connect_your_hardware_wallet_ios": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth ကို အသုံးပြု. ချိတ်ဆက်ပါ",
196 + "connect_your_hardware_wallet_ble": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth ကို အသုံးပြု. ချိတ်ဆက်ပါ",
197 + "connect_your_hardware_wallet_usb": "USB သုံး. သင်၏ hardware ပိုက်ဆံအိတ်ကိုချိတ်ဆက်ပါ",
198 "connected": "ချိတ်ဆက်ထားသော",
199 "connection_sync": "ချိတ်ဆက်မှုနှင့် ထပ်တူပြုခြင်း။",
200 "connectWalletPrompt": "အရောင်းအဝယ်ပြုလုပ်ရန် သင့်ပိုက်ဆံအိတ်ကို WalletConnect နှင့် ချိတ်ဆက်ပါ။",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။",
787 "select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",
788 "select_hw_account_below": "အောက်ဖော်ပြပါမည်သည့်အကောင့်ကိုရွေးပါ။",
789 + "select_manufacturer_title": "ထုတ်လုပ်သူကိုရွေးချယ်ပါ",
790 "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။",
791 "select_your_country": "ကျေးဇူးပြု. သင့်နိုင်ငံကိုရွေးချယ်ပါ",
792 "selected_trocador_provider": "ရွေးချယ်ထား Tracador ပံ့ပိုးပေး",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "သငျသညျယခုသင်၏ dapp ကိုပြန်သွားနိုင်ပါတယ်",
1150 "your": "ခင်ဗျားတို့၏",
1151 "yy": "YY"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_nl.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Verzamelingsbeschrijving",
172 "collection_name": "Verzamelnaam",
173 "color_theme": "Kleur thema",
174 + "coming_soon_tag": "Binnenkort",
175 "commit_transaction_amount_fee": "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}",
176 "confirm": "Bevestigen",
177 "confirm_delete_template": "Met deze actie wordt deze sjabloon verwijderd. Wilt u doorgaan?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Selecteer uw apparaat in cake -portemonnee en ga verder met de opstelling.",
194 "connect_yats": "Verbind Yats",
195 "connect_your_hardware_wallet": "Sluit uw hardware -portemonnee aan met Bluetooth of USB",
195 - "connect_your_hardware_wallet_ios": "Sluit uw hardware -portemonnee aan met Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Sluit uw hardware -portemonnee aan met Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Sluit uw hardware -portemonnee aan met USB",
198 "connected": "Aangesloten",
199 "connection_sync": "Verbinding en synchronisatie",
200 "connectWalletPrompt": "Verbind uw portemonnee met WalletConnect om transacties uit te voeren",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.",
787 "select_destination": "Selecteer de bestemming voor het back-upbestand.",
788 "select_hw_account_below": "Selecteer welk account u hieronder moet herstellen:",
789 + "select_manufacturer_title": "Selecteer fabrikant",
790 "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.",
791 "select_your_country": "Selecteer uw land",
792 "selected_trocador_provider": "Geselecteerde Trocador -provider",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "U kunt nu terug naar uw DApp gaan",
1151 "your": "Jouw",
1152 "yy": "JJ"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_pl.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Opis kolekcji",
172 "collection_name": "Nazwa kolekcji",
173 "color_theme": "Motyw kolorystyczny",
174 + "coming_soon_tag": "Już wkrótce",
175 "commit_transaction_amount_fee": "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}",
176 "confirm": "Potwierdź",
177 "confirm_delete_template": "Ta czynność usunie ten szablon. Czy chcesz kontynuować?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Wybierz urządzenie w portfelu ciasta i kontynuuj konfigurację.",
194 "connect_yats": "Połącz Yats",
195 "connect_your_hardware_wallet": "Podłącz portfel sprzętowy za pomocą Bluetooth lub USB",
195 - "connect_your_hardware_wallet_ios": "Podłącz portfel sprzętowy za pomocą Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Podłącz portfel sprzętowy za pomocą Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Podłącz portfel sprzętowy za pomocą USB",
198 "connected": "Połączony",
199 "connection_sync": "Połączenie i synchronizacja",
200 "connectWalletPrompt": "Połącz swój portfel z WalletConnect, aby dokonywać transakcji",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.",
787 "select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.",
788 "select_hw_account_below": "Wybierz, które konto przywrócić poniżej:",
789 + "select_manufacturer_title": "Wybierz producent",
790 "select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.",
791 "select_your_country": "Wybierz swój kraj",
792 "selected_trocador_provider": "Wybrany dostawca Trocador",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Możesz teraz wrócić do swojego dapp",
1150 "your": "Twój",
1151 "yy": "RR"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_pt.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Descrição da coleção",
172 "collection_name": "Nome da coleção",
173 "color_theme": "Tema de cor",
174 + "coming_soon_tag": "Em breve",
175 "commit_transaction_amount_fee": "Confirmar transação\nQuantia: ${amount}\nTaxa: ${fee}",
176 "confirm": "Confirmar",
177 "confirm_delete_template": "Esta ação excluirá este modelo. Você deseja continuar?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Selecione seu dispositivo na Cake Wallet e continue com a configuração.",
194 "connect_yats": "Connect Yats",
195 "connect_your_hardware_wallet": "Conecte sua carteira de hardware usando Bluetooth ou USB",
195 - "connect_your_hardware_wallet_ios": "Conecte sua carteira de hardware usando o Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Conecte sua carteira de hardware usando o Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Conecte sua carteira de hardware usando USB",
198 "connected": "Conectado",
199 "connection_sync": "Conexão e sincronização",
200 "connectWalletPrompt": "Conecte sua carteira ao WalletConnect para fazer transações",
@@ -786,6 +788,7 @@
788 "select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.",
789 "select_destination": "Selecione o destino para o arquivo de backup.",
790 "select_hw_account_below": "Selecione qual conta para restaurar abaixo:",
791 + "select_manufacturer_title": "Selecione o fabricante",
792 "select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.",
793 "select_your_country": "Selecione seu país",
794 "selected_trocador_provider": "Provedor de Trocador selecionado",
@@ -1149,4 +1152,4 @@
1152 "youCanGoBackToYourDapp": "Você pode voltar para o seu dapp agora",
1153 "your": "Seu",
1154 "yy": "aa"
1152 -}
\ No newline at end of file
1155 +}
res/values/strings_ru.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Описание коллекции",
172 "collection_name": "Название коллекции",
173 "color_theme": "Цветовая тема",
174 + "coming_soon_tag": "Вскоре",
175 "commit_transaction_amount_fee": "Подтвердить транзакцию \nСумма: ${amount}\nКомиссия: ${fee}",
176 "confirm": "Подтвердить",
177 "confirm_delete_template": "Это действие удалит шаблон. Вы хотите продолжить?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Выберите свое устройство в кошельке для торта и продолжайте с настройкой.",
194 "connect_yats": "Подключить Yats",
195 "connect_your_hardware_wallet": "Подключите свой аппаратный кошелек с помощью Bluetooth или USB",
195 - "connect_your_hardware_wallet_ios": "Подключите свой аппаратный кошелек с помощью Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Подключите свой аппаратный кошелек с помощью Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Подключите свой аппаратный кошелек с помощью USB",
198 "connected": "Подключенный",
199 "connection_sync": "Подключение и синхронизация",
200 "connectWalletPrompt": "Подключите свой кошелек к WalletConnect для совершения транзакций.",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.",
788 "select_destination": "Пожалуйста, выберите место для файла резервной копии.",
789 "select_hw_account_below": "Пожалуйста, выберите, какую учетную запись восстановить ниже:",
790 + "select_manufacturer_title": "Выберите производителя",
791 "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.",
792 "select_your_country": "Пожалуйста, выберите свою страну",
793 "selected_trocador_provider": "Выбранный провайдер Трокадора",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "Вы можете вернуться к своему даппу сейчас",
1151 "your": "Твой",
1152 "yy": "ГГ"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_th.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "คำอธิบายการรวบรวม",
172 "collection_name": "ชื่อคอลเลกชัน",
173 "color_theme": "ธีมสี",
174 + "coming_soon_tag": "เร็วๆ นี้",
175 "commit_transaction_amount_fee": "ยืนยันธุรกรรม\nจำนวน: ${amount}\nค่าธรรมเนียม: ${fee}",
176 "confirm": "ยืนยัน",
177 "confirm_delete_template": "การดำเนินการนี้จะลบแบบฟอร์มนี้ คุณต้องการดำเนินการต่อหรือไม่?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "เลือกอุปกรณ์ของคุณในกระเป๋าเงินเค้กและดำเนินการติดตั้งต่อไป",
194 "connect_yats": "เชื่อมต่อ Yats",
195 "connect_your_hardware_wallet": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ หรือ USB",
195 - "connect_your_hardware_wallet_ios": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ",
196 + "connect_your_hardware_wallet_ble": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ",
197 + "connect_your_hardware_wallet_usb": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้ USB",
198 "connected": "ซึ่งเชื่อมต่อกัน",
199 "connection_sync": "การเชื่อมต่อและการซิงค์",
200 "connectWalletPrompt": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect เพื่อทำธุรกรรม",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป",
787 "select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",
788 "select_hw_account_below": "กรุณาเลือกบัญชีที่จะกู้คืนด้านล่าง:",
789 + "select_manufacturer_title": "เลือกผู้ผลิต",
790 "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป",
791 "select_your_country": "กรุณาเลือกประเทศของคุณ",
792 "selected_trocador_provider": "ผู้ให้บริการ Trocador ที่เลือก",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "คุณสามารถกลับไปที่ dapp ของคุณได้ทันที",
1150 "your": "ของคุณ",
1151 "yy": "ปี"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_tl.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Paglalarawan ng Koleksyon",
172 "collection_name": "Pangalan ng Koleksyon",
173 "color_theme": "Color theme",
174 + "coming_soon_tag": "Malapit na",
175 "commit_transaction_amount_fee": "Gumawa ng transaksyon\nHalaga: ${amount}\nFee: ${fee}",
176 "confirm": "Kumpirmahin",
177 "confirm_delete_template": "Tatanggalin ng pagkilos na ito ang template na ito. Gusto mo bang magpatuloy?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Piliin ang iyong aparato sa cake wallet at magpatuloy sa pag -setup.",
194 "connect_yats": "Ikonekta sa Yats",
195 "connect_your_hardware_wallet": "Ikonekta ang iyong hardware wallet gamit ang Bluetooth o USB",
195 - "connect_your_hardware_wallet_ios": "Ikonekta ang iyong wallet gamit ang Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Ikonekta ang iyong wallet gamit ang Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Ikonekta ang iyong wallet ng hardware gamit ang USB",
198 "connected": "Konektado",
199 "connection_sync": "Koneksyon at pag-sync",
200 "connectWalletPrompt": "Ikonekta ang iyong wallet sa WalletConnect upang gumawa ng mga transaksyon",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.",
787 "select_destination": "Mangyaring piliin ang patutunguhan para sa backup na file.",
788 "select_hw_account_below": "Mangyaring piliin kung aling account ang ibabalik sa ibaba:",
789 + "select_manufacturer_title": "Piliin ang Tagagawa",
790 "select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.",
791 "select_your_country": "Mangyaring piliin ang iyong bansa",
792 "selected_trocador_provider": "Napiling Trocador Provider",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Maaari kang bumalik sa iyong dapp ngayon",
1150 "your": "IYONG",
1151 "yy": "YY"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_tr.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Koleksiyon Açıklama",
172 "collection_name": "Toplama adı",
173 "color_theme": "Renk teması",
174 + "coming_soon_tag": "Yakında gelecek",
175 "commit_transaction_amount_fee": "Transferi gerçekleştir\nMiktar: ${amount}\nKomisyon: ${fee}",
176 "confirm": "Onayla",
177 "confirm_delete_template": "Bu eylem, bu şablonu silecek. Devam etmek istiyor musun?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Cihazınızı kek cüzdanında seçin ve kuruluma devam edin.",
194 "connect_yats": "Yat'lara bağlan",
195 "connect_your_hardware_wallet": "Bluetooth veya USB kullanarak donanım cüzdanınızı bağlayın",
195 - "connect_your_hardware_wallet_ios": "Bluetooth kullanarak donanım cüzdanınızı bağlayın",
196 + "connect_your_hardware_wallet_ble": "Bluetooth kullanarak donanım cüzdanınızı bağlayın",
197 + "connect_your_hardware_wallet_usb": "USB kullanarak donanım cüzdanınızı bağlayın",
198 "connected": "Bağlı",
199 "connection_sync": "Bağlantı ve senkronizasyon",
200 "connectWalletPrompt": "İşlem yapmak için cüzdanınızı WalletConnect'e bağlayın",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.",
787 "select_destination": "Lütfen yedekleme dosyası için hedef seçin.",
788 "select_hw_account_below": "Lütfen aşağıda hangi hesabı geri yükleyeceğinizi seçin:",
789 + "select_manufacturer_title": "Üretici seçin",
790 "select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.",
791 "select_your_country": "Lütfen ülkenizi seçin",
792 "selected_trocador_provider": "Seçilmiş Trocador Sağlayıcı",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "Şimdi Dapp'ınıza geri dönebilirsin",
1150 "your": "Senin",
1151 "yy": "YY"
1149 -}
\ No newline at end of file
1152 +}
res/values/strings_uk.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Опис колекції",
172 "collection_name": "Назва колекції",
173 "color_theme": "Кольорова тема",
174 + "coming_soon_tag": "Незабаром",
175 "commit_transaction_amount_fee": "Підтвердити транзакцію \nСума: ${amount}\nКомісія: ${fee}",
176 "confirm": "Підтвердити",
177 "confirm_delete_template": "Ця дія видалить шаблон. Ви хочете продовжити?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Виберіть свій пристрій у гаманці тортів і продовжуйте налаштування.",
194 "connect_yats": "Підключіть Yats",
195 "connect_your_hardware_wallet": "Підключіть апаратний гаманець за допомогою Bluetooth або USB",
195 - "connect_your_hardware_wallet_ios": "Підключіть апаратний гаманець за допомогою Bluetooth",
196 + "connect_your_hardware_wallet_ble": "Підключіть апаратний гаманець за допомогою Bluetooth",
197 + "connect_your_hardware_wallet_usb": "Підключіть свій апаратний гаманець за допомогою USB",
198 "connected": "З'єднаний",
199 "connection_sync": "Підключення та синхронізація",
200 "connectWalletPrompt": "Підключіть свій гаманець до WalletConnect, щоб здійснювати транзакції",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.",
788 "select_destination": "Виберіть місце призначення для файлу резервної копії.",
789 "select_hw_account_below": "Виберіть, який рахунок відновити нижче:",
790 + "select_manufacturer_title": "Виберіть виробника",
791 "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.",
792 "select_your_country": "Будь ласка, виберіть свою країну",
793 "selected_trocador_provider": "Вибраний постачальник Trocador",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "Ви можете повернутися до свого DAPP зараз",
1151 "your": "Ваш",
1152 "yy": "YY"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_ur.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "جمع کرنے کی تفصیل",
172 "collection_name": "جمع کرنے کا نام",
173 "color_theme": "رنگین تھیم",
174 + "coming_soon_tag": "جلد آرہا ہے",
175 "commit_transaction_amount_fee": "لین دین کا ارتکاب کریں\\nرقم: ${amount}\\nفیس: ${fee}",
176 "confirm": "تصدیق کریں۔",
177 "confirm_delete_template": "یہ عمل اس ٹیمپلیٹ کو حذف کر دے گا۔ کیا آپ جاری رکھنا چاہتے ہیں؟",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "اپنے آلے کو کیک پرس میں منتخب کریں اور سیٹ اپ کے ساتھ جاری رکھیں۔",
194 "connect_yats": "Yats کو جوڑیں۔",
195 "connect_your_hardware_wallet": "بلوٹوتھ یا USB کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو مربوط کریں",
195 - "connect_your_hardware_wallet_ios": "بلوٹوتھ کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو جوڑیں",
196 + "connect_your_hardware_wallet_ble": "بلوٹوتھ کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو جوڑیں",
197 + "connect_your_hardware_wallet_usb": "USB کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو مربوط کریں",
198 "connected": "منسلک",
199 "connection_sync": "کنکشن اور مطابقت پذیری",
200 "connectWalletPrompt": "۔ﮟﯾﮌﻮﺟ ﮫﺗﺎﺳ ﮯﮐ WalletConnect ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻦﯾﺩ ﻦﯿﻟ",
@@ -786,6 +788,7 @@
788 "select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔",
789 "select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",
790 "select_hw_account_below": "براہ کرم ذیل میں کون سا اکاؤنٹ بحال کرنا ہے منتخب کریں:",
791 + "select_manufacturer_title": "کارخانہ دار کو منتخب کریں",
792 "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ",
793 "select_your_country": "براہ کرم اپنے ملک کو منتخب کریں",
794 "selected_trocador_provider": "منتخب کردہ ٹروکاڈور فراہم کنندہ",
@@ -1148,4 +1151,4 @@
1151 "youCanGoBackToYourDapp": "اب آپ اپنے ڈی اے پی پی پر واپس جاسکتے ہیں",
1152 "your": "آپ کا",
1153 "yy": "YY"
1151 -}
\ No newline at end of file
1154 +}
res/values/strings_vi.arb
+5 -2
@@ -169,6 +169,7 @@
169 "collection_description": "Mô tả bộ sưu tập",
170 "collection_name": "Tên bộ sưu tập",
171 "color_theme": "Chủ đề màu sắc",
172 + "coming_soon_tag": "Sắp ra mắt",
173 "commit_transaction_amount_fee": "Cam kết giao dịch\nSố tiền: ${amount}\nPhí: ${fee}",
174 "confirm": "Xác nhận",
175 "confirm_delete_template": "Thao tác này sẽ xóa mẫu này. Bạn có muốn tiếp tục không?",
@@ -191,7 +192,8 @@
192 "connect_hw_info_step_4": "Chọn thiết bị của bạn trong ví bánh và tiếp tục với thiết lập.",
193 "connect_yats": "Kết nối Yats",
194 "connect_your_hardware_wallet": "Kết nối ví phần cứng của bạn bằng Bluetooth hoặc USB",
194 - "connect_your_hardware_wallet_ios": "Kết nối ví phần cứng của bạn bằng Bluetooth",
195 + "connect_your_hardware_wallet_ble": "Kết nối ví phần cứng của bạn bằng Bluetooth",
196 + "connect_your_hardware_wallet_usb": "Kết nối ví phần cứng của bạn bằng USB",
197 "connected": "Kết nối",
198 "connection_sync": "Kết nối và đồng bộ hóa",
199 "connectWalletPrompt": "Kết nối ví của bạn với WalletConnect để thực hiện giao dịch",
@@ -782,6 +784,7 @@
784 "select_buy_provider_notice": "Chọn nhà cung cấp mua ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp mua mặc định trong cài đặt ứng dụng.",
785 "select_destination": "Vui lòng chọn đích cho tệp sao lưu.",
786 "select_hw_account_below": "Vui lòng chọn tài khoản nào để khôi phục bên dưới:",
787 + "select_manufacturer_title": "Chọn nhà sản xuất",
788 "select_sell_provider_notice": "Chọn nhà cung cấp bán ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp bán mặc định trong cài đặt ứng dụng.",
789 "select_your_country": "Vui lòng chọn quốc gia của bạn",
790 "sell": "Bán",
@@ -1143,4 +1146,4 @@
1146 "youCanGoBackToYourDapp": "Bạn có thể quay lại DAPP của mình ngay bây giờ",
1147 "your": "Của bạn",
1148 "yy": "YY"
1146 -}
\ No newline at end of file
1149 +}
res/values/strings_yo.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "Ijuwe akopọ",
172 "collection_name": "Orukọ ikojọpọ",
173 "color_theme": "Àwọn ààtò àwọ̀",
174 + "coming_soon_tag": "Nbọ laipẹ",
175 "commit_transaction_amount_fee": "Jẹ́rìí sí àránṣẹ́\nOwó: ${amount}\nIye àfikún: ${fee}",
176 "confirm": "Jẹ́rìísí",
177 "confirm_delete_template": "Ìṣe yìí máa yọ àwòṣe yìí kúrò. Ṣé ẹ fẹ́ tẹ̀síwájú?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "Yan ẹrọ rẹ ni apamọwọ akara oyinbo ki o tẹsiwaju pẹlu iṣeto.",
194 "connect_yats": "So àwọn Yat",
195 "connect_your_hardware_wallet": "So apamọwọ irinṣẹ rẹ nipa lilo Bluetooth tabi USB",
195 - "connect_your_hardware_wallet_ios": "So apamọwọ ẹrọ rẹ ni lilo Bluetooth",
196 + "connect_your_hardware_wallet_ble": "So apamọwọ ẹrọ rẹ ni lilo Bluetooth",
197 + "connect_your_hardware_wallet_usb": "So apamọwọ ohun elo rẹ ni lilo USB",
198 "connected": "Sopọ",
199 "connection_sync": "Ìkànpọ̀ àti ìbádọ́gba",
200 "connectWalletPrompt": "So apamọwọ rẹ pọ pẹlu WalletConnect lati ṣe awọn iṣowo",
@@ -785,6 +787,7 @@
787 "select_buy_provider_notice": "Yan olupese Ra loke. O le skii iboju yii nipa ṣiṣeto olupese rẹ ni awọn eto App.",
788 "select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.",
789 "select_hw_account_below": "Jọwọ yan iru iroyin lati mu pada ni isalẹ:",
790 + "select_manufacturer_title": "Yan olupese",
791 "select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.",
792 "select_your_country": "Jọwọ yan orilẹ-ede rẹ",
793 "selected_trocador_provider": "olupese trocador ti a yan",
@@ -1147,4 +1150,4 @@
1150 "youCanGoBackToYourDapp": "O le pada si tapla rẹ bayi",
1151 "your": "Rẹ",
1152 "yy": "Ọd"
1150 -}
\ No newline at end of file
1153 +}
res/values/strings_zh.arb
+5 -2
@@ -171,6 +171,7 @@
171 "collection_description": "集合描述",
172 "collection_name": "收集名称",
173 "color_theme": "主题",
174 + "coming_soon_tag": "即将推出",
175 "commit_transaction_amount_fee": "提交交易\n金额: ${amount}\n手续费: ${fee}",
176 "confirm": "确认",
177 "confirm_delete_template": "此操作将刪除此模板。确定吗?",
@@ -192,7 +193,8 @@
193 "connect_hw_info_step_4": "在蛋糕钱包中选择您的设备,然后继续设置。",
194 "connect_yats": "连接 Yats",
195 "connect_your_hardware_wallet": "使用蓝牙或USB连接硬件钱包",
195 - "connect_your_hardware_wallet_ios": "使用蓝牙连接硬件钱包",
196 + "connect_your_hardware_wallet_ble": "使用蓝牙连接硬件钱包",
197 + "connect_your_hardware_wallet_usb": "使用USB连接硬件钱包",
198 "connected": "连接",
199 "connection_sync": "连接和同步",
200 "connectWalletPrompt": "将您的钱包与 WalletConnect 连接以进行交易",
@@ -784,6 +786,7 @@
786 "select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。",
787 "select_destination": "请选择备份文件的目的地。",
788 "select_hw_account_below": "请在下面选择要还原的帐户:",
789 + "select_manufacturer_title": "选择制造商",
790 "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。",
791 "select_your_country": "请选择你的国家",
792 "selected_trocador_provider": "选定的trocador提供商",
@@ -1146,4 +1149,4 @@
1149 "youCanGoBackToYourDapp": "您现在可以回到DAPP",
1150 "your": "你的",
1151 "yy": "YY"
1149 -}
\ No newline at end of file
1152 +}
scripts/build_bitbox_flutter.sh new
+8
@@ -0,0 +1,8 @@
1 +git clone https://github.com/konstantinullrich/bitbox_flutter
2 +cd bitbox_flutter
3 +./build_bindings.sh --dont-install
4 +
5 +FILE=api.aar
6 +if [ -f "$FILE" ]; then
7 + echo "$FILE exists."
8 +fi
tool/configure.dart
+28 -16
@@ -91,6 +91,7 @@ import 'package:bitcoin_base/bitcoin_base.dart';
91 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
92 import 'package:cake_wallet/view_model/send/output.dart';
93 import 'package:cw_core/hardware/hardware_account_data.dart';
94 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
95 import 'package:cw_core/node.dart';
96 import 'package:cw_core/payjoin_session.dart';
97 import 'package:cw_core/output_info.dart';
@@ -110,6 +111,7 @@ import 'package:cw_core/utils/print_verbose.dart';
111 import 'package:cw_core/get_height_by_date.dart';
112 import 'package:hive/hive.dart';
113 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
114 +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
115 import 'package:blockchain_utils/blockchain_utils.dart';
116 import 'package:bip39/bip39.dart' as bip39;
117 """;
@@ -133,8 +135,9 @@ import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
135 import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
136 import 'package:cw_bitcoin/litecoin_wallet_service.dart';
137 import 'package:cw_bitcoin/litecoin_wallet.dart';
136 -import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart';
137 -import 'package:cw_bitcoin/litecoin_hardware_wallet_service.dart';
138 +import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart';
139 +import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart';
140 +import 'package:cw_bitcoin/hardware/bitbox_service.dart';
141 import 'package:mobx/mobx.dart';
142 """;
143 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@@ -168,7 +171,7 @@ abstract class Bitcoin {
171 String? passphrase,
172 });
173 WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo});
171 - WalletCredentials createBitcoinWalletFromKeys({required String name, required String password, required String xpub});
174 + WalletCredentials createBitcoinWalletFromKeys({required String name, required String password, required String xpub, HardwareWalletType? hardwareWalletType});
175 WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic});
176 WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo});
177 List<String> getWordList();
@@ -213,7 +216,7 @@ abstract class Bitcoin {
216 Future<void> setAddressType(Object wallet, dynamic option);
217 ReceivePageOption getSelectedAddressType(Object wallet);
218 List<ReceivePageOption> getBitcoinReceivePageOptions(Object wallet);
216 - List<ReceivePageOption> getLitecoinReceivePageOptions();
219 + List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet);
220 BitcoinAddressType getBitcoinAddressType(ReceivePageOption option);
221 bool isPayjoinAvailable(Object wallet);
222 bool hasSelectedSilentPayments(Object wallet);
@@ -243,8 +246,9 @@ abstract class Bitcoin {
246 Future<void> updateFeeRates(Object wallet);
247 int getMaxCustomFeeRate(Object wallet);
248 void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
246 - Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
247 - Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
249 + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
250 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin);
251 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin);
252 List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
253 bool txIsReceivedSilentPayment(TransactionInfo txInfo);
254 bool txIsMweb(TransactionInfo txInfo);
@@ -676,11 +680,11 @@ abstract class WowneroAccountList {
680 Future<void> generateEthereum(bool hasImplementation) async {
681 final outputFile = File(ethereumOutputPath);
682 const ethereumCommonHeaders = """
679 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
683 import 'package:cake_wallet/view_model/send/output.dart';
684 import 'package:cw_core/crypto_currency.dart';
685 import 'package:cw_core/erc20_token.dart';
686 import 'package:cw_core/hardware/hardware_account_data.dart';
687 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
688 import 'package:cw_core/output_info.dart';
689 import 'package:cw_core/pending_transaction.dart';
690 import 'package:cw_core/transaction_info.dart';
@@ -692,6 +696,7 @@ import 'package:cw_core/wallet_service.dart';
696 import 'package:cw_core/utils/print_verbose.dart';
697 import 'package:hive/hive.dart';
698 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
699 +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
700 import 'package:web3dart/web3dart.dart';
701
702 """;
@@ -702,9 +707,11 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
707 import 'package:cw_evm/evm_chain_transaction_info.dart';
708 import 'package:cw_evm/evm_chain_transaction_priority.dart';
709 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
705 -import 'package:cw_evm/evm_chain_hardware_wallet_service.dart';
706 -import 'package:cw_evm/evm_ledger_credentials.dart';
710 +import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
711 +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
712 import 'package:cw_evm/evm_chain_wallet.dart';
713 +import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
714 +import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
715
716 import 'package:cw_ethereum/ethereum_client.dart';
717 import 'package:cw_ethereum/ethereum_wallet.dart';
@@ -770,8 +777,9 @@ abstract class Ethereum {
777 Future<PendingTransaction> reinvestDEuroInterest(WalletBase wallet, TransactionPriority priority);
778 Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority);
779
773 - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
774 - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
780 + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
781 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
782 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
783 List<String> getDefaultTokenContractAddresses();
784 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
785 }
@@ -797,11 +805,11 @@ abstract class Ethereum {
805 Future<void> generatePolygon(bool hasImplementation) async {
806 final outputFile = File(polygonOutputPath);
807 const polygonCommonHeaders = """
800 -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
808 import 'package:cake_wallet/view_model/send/output.dart';
809 import 'package:cw_core/crypto_currency.dart';
810 import 'package:cw_core/erc20_token.dart';
811 import 'package:cw_core/hardware/hardware_account_data.dart';
812 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
813 import 'package:cw_core/output_info.dart';
814 import 'package:cw_core/pending_transaction.dart';
815 import 'package:cw_core/transaction_info.dart';
@@ -813,6 +821,7 @@ import 'package:cw_core/wallet_service.dart';
821 import 'package:cw_core/utils/print_verbose.dart';
822 import 'package:hive/hive.dart';
823 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
824 +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
825 import 'package:web3dart/web3dart.dart';
826
827 """;
@@ -823,9 +832,11 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
832 import 'package:cw_evm/evm_chain_transaction_info.dart';
833 import 'package:cw_evm/evm_chain_transaction_priority.dart';
834 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
826 -import 'package:cw_evm/evm_chain_hardware_wallet_service.dart';
827 -import 'package:cw_evm/evm_ledger_credentials.dart';
835 +import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
836 +import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
837 import 'package:cw_evm/evm_chain_wallet.dart';
838 +import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
839 +import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
840
841 import 'package:cw_polygon/polygon_client.dart';
842 import 'package:cw_polygon/polygon_wallet.dart';
@@ -881,8 +892,9 @@ abstract class Polygon {
892 Web3Client? getWeb3Client(WalletBase wallet);
893 String getTokenAddress(CryptoCurrency asset);
894
884 - void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
885 - Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
895 + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
896 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
897 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
898 List<String> getDefaultTokenContractAddresses();
899 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
900 }