CW-1135-add-support-for-bitbox-hww (#2476)
What changed, and why it matters
This is a large feature commit adding support for BitBox hardware wallets to Cake Wallet, alongside a broader refactor of hardware wallet handling. It also includes a small fix to prevent crashes when BIP353 payment-proof fields are missing. There is no clear evidence of a deliberate security vulnerability, but the size and nature of the changes introduce normal implementation risks.
Review the new bitbox_flutter package and its FFI bindings for supply-chain and memory-safety risks; audit the refactored hardware wallet signing flow for correct derivation paths, PSBT finalization, and error handling; verify that the dependency downgrades in cw_monero do not reintroduce known issues; and ensure UnimplementedError stubs cannot be reached in production flows.
Security signals we found
New native/FFI dependency introduced (bitbox_flutter) with limited visibility into its security properties
Hardware wallet signing paths refactored; transaction signing now routes through a shared abstraction
BIP353 extra-map fields now guarded against missing keys, fixing a potential null-dereference/crash
Some hardware wallet operations left as UnimplementedError, which could lead to runtime failures rather than silent misuse
Dependency version rollbacks in cw_monero lockfile (blockchain_utils v4→v3, on_chain v6→v3) without explicit security rationale in commit message
Evidence from the diff
The commit refactors Ledger-specific hardware wallet code into a generic abstraction (HardwareWalletService, HardwareWalletViewModel), adds BitBox02 integration via a new bitbox_flutter FFI package, and extends the hardware wallet type enum. It touches wallet creation, transaction signing (BTC/ETH), key derivation, and UI flows. A notable fix is in cw_bitcoin/lib/psbt/transaction_builder.dart, where BIP353 extra fields are now validated before access to avoid null exceptions. Several methods remain UnimplementedError (e.g., BitBox message signing). The commit also downgrades transitive blockchain_utils and on_chain references from v4/v6 to v2/v3 in cw_monero/pubspec.lock, which may have functional implications.
Changed components
cw_bitcoin hardware wallet services (Bitcoin/Litecoin)cw_evm hardware wallet credentials and services (Ethereum/Polygon)cw_core hardware wallet abstraction and wallet infoPSBT transaction builder (BIP353 handling)CI build scripts for BitBox FlutterUI screens for device connection and wallet restorationInspect captured patch +2226 / −901
diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index b7a645e..4a6d49d 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -251,6 +251,14 @@ jobs:
./build_monero_all.sh
popd
+ - name: Build Bitbox Flutter
+ run: |
+ set -x -e
+ pushd scripts
+ gomobile init;
+ ./build_bitbox_flutter.sh
+ popd
+
- name: Install Flutter dependencies
run: |
flutter pub get
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 1391600..dea8824 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -228,6 +228,13 @@ jobs:
./build_monero_all.sh
popd
+ - name: Build Bitbox Flutter
+ run: |
+ set -x -e
+ pushd scripts
+ ./build_bitbox_flutter.sh --dont-install
+ popd
+
- name: Install Flutter dependencies
run: |
flutter pub get
diff --git a/assets/images/hardware_wallet/bitbox_man.svg b/assets/images/hardware_wallet/bitbox_man.svg
new file mode 100644
index 0000000..b1123f7
--- /dev/null
+++ b/assets/images/hardware_wallet/bitbox_man.svg
@@ -0,0 +1,8 @@
+<svg width="90" height="25" viewBox="0 0 90 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<mask id="mask0_7843_12605" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="1" width="90" height="23">
+<path d="M89.7959 1.5H0V23.5H89.7959V1.5Z" fill="white"/>
+</mask>
+<g mask="url(#mask0_7843_12605)">
+<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"/>
+</g>
+</svg>
diff --git a/assets/images/hardware_wallet/coldcard_man.svg b/assets/images/hardware_wallet/coldcard_man.svg
new file mode 100644
index 0000000..73a0658
--- /dev/null
+++ b/assets/images/hardware_wallet/coldcard_man.svg
@@ -0,0 +1,19 @@
+<svg width="84" height="25" viewBox="0 0 84 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_7540_12880)">
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+</g>
+<defs>
+<clipPath id="clip0_7540_12880">
+<rect width="84" height="12" fill="white" transform="translate(0 6.5)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/assets/images/hardware_wallet/cupcake_man.svg b/assets/images/hardware_wallet/cupcake_man.svg
new file mode 100644
index 0000000..841a0c3
--- /dev/null
+++ b/assets/images/hardware_wallet/cupcake_man.svg
@@ -0,0 +1,4 @@
+<svg width="90" height="25" viewBox="0 0 90 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+<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"/>
+</svg>
diff --git a/assets/images/hardware_wallet/device_bitbox.svg b/assets/images/hardware_wallet/device_bitbox.svg
new file mode 100644
index 0000000..52368aa
--- /dev/null
+++ b/assets/images/hardware_wallet/device_bitbox.svg
@@ -0,0 +1,3 @@
+<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/assets/images/hardware_wallet/device_ledger_nano_x.svg b/assets/images/hardware_wallet/device_ledger_nano_x.svg
new file mode 100644
index 0000000..c605249
--- /dev/null
+++ b/assets/images/hardware_wallet/device_ledger_nano_x.svg
@@ -0,0 +1,3 @@
+<svg width="97" height="97" viewBox="0 0 97 97" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/assets/images/hardware_wallet/device_qr.svg b/assets/images/hardware_wallet/device_qr.svg
new file mode 100644
index 0000000..06aece5
--- /dev/null
+++ b/assets/images/hardware_wallet/device_qr.svg
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/assets/images/hardware_wallet/foundation_man.svg b/assets/images/hardware_wallet/foundation_man.svg
new file mode 100644
index 0000000..5946c59
--- /dev/null
+++ b/assets/images/hardware_wallet/foundation_man.svg
@@ -0,0 +1,48 @@
+<svg width="153" height="25" viewBox="0 0 153 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_7559_11731)">
+<g clip-path="url(#clip1_7559_11731)">
+<g opacity="0.5">
+<path opacity="0.5" d="M10.9595 17.6824H2.06372L10.9595 1.86987V17.6824Z" fill="#D7E2F7"/>
+</g>
+<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)"/>
+<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)"/>
+<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)"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+</g>
+</g>
+<defs>
+<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)">
+<stop offset="0.5" stop-color="#BB6E5A"/>
+<stop offset="0.75" stop-color="#DE9173"/>
+<stop offset="1" stop-color="#FAC2A9"/>
+</radialGradient>
+<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)">
+<stop offset="0.5" stop-color="#FAC2A9"/>
+<stop offset="0.75" stop-color="#DE9173"/>
+<stop offset="1" stop-color="#BB6E5A"/>
+</radialGradient>
+<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)">
+<stop offset="0.5" stop-color="#FAC2A9"/>
+<stop offset="0.75" stop-color="#DE9173"/>
+<stop offset="1" stop-color="#BB6E5A"/>
+</radialGradient>
+<clipPath id="clip0_7559_11731">
+<rect width="153" height="22" fill="white" transform="translate(0 1.5)"/>
+</clipPath>
+<clipPath id="clip1_7559_11731">
+<rect width="153.312" height="22" fill="white" transform="translate(0 1.5)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/assets/images/hardware_wallet/keystone_man.svg b/assets/images/hardware_wallet/keystone_man.svg
new file mode 100644
index 0000000..e9d7b84
--- /dev/null
+++ b/assets/images/hardware_wallet/keystone_man.svg
@@ -0,0 +1,13 @@
+<svg width="111" height="25" viewBox="0 0 111 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.6875 3.90625H14.0625L7.8125 17.1875H3.125L4.6875 3.90625Z" fill="#D7E2F7"/>
+<path d="M20.3125 21.0938H10.9375L17.1875 7.8125H21.875L20.3125 21.0938Z" fill="#3D71FF"/>
+<path d="M20.3125 21.0938H10.9375L17.1875 7.8125H21.875L20.3125 21.0938Z" fill="#D7E2F7"/>
+<path d="M89.4531 7.8125H92.805L95.7031 13.6585V7.8125H97.6562V17.1875H95.7031V15.2344H94.3044L91.4062 9.38844V17.1875H89.4531V7.8125Z" fill="#D7E2F7"/>
+<path d="M76.5625 7.8125H68.75V9.375H71.6797V17.1875H73.6328V9.375H76.5625V7.8125Z" fill="#D7E2F7"/>
+<path d="M107.031 7.8125H101.953V9.375H100V17.1875H107.031V15.625H101.953V13.2812H106.25V11.7188H101.953V9.375H107.031V7.8125Z" fill="#D7E2F7"/>
+<path d="M48.4375 7.8125H43.3594V9.375H41.4062V17.1875H48.4375V15.625H43.3594V13.2812H47.6562V11.7188H43.3594V9.375H48.4375V7.8125Z" fill="#D7E2F7"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+</svg>
diff --git a/assets/images/hardware_wallet/ledger_man.svg b/assets/images/hardware_wallet/ledger_man.svg
new file mode 100644
index 0000000..399a6a3
--- /dev/null
+++ b/assets/images/hardware_wallet/ledger_man.svg
@@ -0,0 +1,3 @@
+<svg width="75" height="25" viewBox="0 0 75 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/assets/images/hardware_wallet/seedsigner_man.svg b/assets/images/hardware_wallet/seedsigner_man.svg
new file mode 100644
index 0000000..ec0c750
--- /dev/null
+++ b/assets/images/hardware_wallet/seedsigner_man.svg
@@ -0,0 +1,20 @@
+<svg width="78" height="25" viewBox="0 0 78 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_7559_11744)">
+<g clip-path="url(#clip1_7559_11744)">
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+<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"/>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_7559_11744">
+<rect width="77.8091" height="25" fill="white"/>
+</clipPath>
+<clipPath id="clip1_7559_11744">
+<rect width="77.8091" height="25" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/assets/images/hardware_wallet/trezor_man.svg b/assets/images/hardware_wallet/trezor_man.svg
new file mode 100644
index 0000000..253e18b
--- /dev/null
+++ b/assets/images/hardware_wallet/trezor_man.svg
@@ -0,0 +1,11 @@
+<svg width="80" height="25" viewBox="0 0 80 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_7559_11628)">
+<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"/>
+<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"/>
+</g>
+<defs>
+<clipPath id="clip0_7559_11628">
+<rect width="79.3544" height="20" fill="white" transform="translate(0 2.5)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
deleted file mode 100644
index c8715b2..0000000
--- a/cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-import 'dart:async';
-
-import 'package:bitcoin_base/bitcoin_base.dart';
-import 'package:blockchain_utils/blockchain_utils.dart';
-import 'package:cw_bitcoin/utils.dart';
-import 'package:cw_core/hardware/hardware_account_data.dart';
-import 'package:ledger_bitcoin/ledger_bitcoin.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-
-class BitcoinHardwareWalletService {
- BitcoinHardwareWalletService(this.ledgerConnection);
-
- final LedgerConnection ledgerConnection;
-
- Future<List<HardwareAccountData>> getAvailableAccounts(
- {int index = 0, int limit = 5}) async {
- final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
-
- final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
-
- final accounts = <HardwareAccountData>[];
- final indexRange = List.generate(limit, (i) => i + index);
-
- for (final i in indexRange) {
- final derivationPath = "m/84'/0'/$i'";
- final xpub =
- await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
- Bip32Slip10Secp256k1 hd =
- Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
-
- final address = generateP2WPKHAddress(
- hd: hd, index: 0, network: BitcoinNetwork.mainnet);
-
- accounts.add(HardwareAccountData(
- address: address,
- accountIndex: i,
- derivationPath: derivationPath,
- masterFingerprint: masterFp,
- xpub: xpub,
- ));
- }
-
- return accounts;
- }
-}
diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart
index a5c209e..241ef34 100644
--- a/cw_bitcoin/lib/bitcoin_wallet.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet.dart
@@ -12,6 +12,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
+import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
import 'package:cw_bitcoin/payjoin/manager.dart';
import 'package:cw_bitcoin/payjoin/storage.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
@@ -25,7 +26,7 @@ import 'package:cw_core/output_info.dart';
import 'package:cw_core/payjoin_session.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/unspent_coins_info.dart';
-import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/utils/zpub.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart';
import 'package:flutter/foundation.dart';
@@ -241,7 +242,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
return BitcoinWallet(
mnemonic: mnemonic,
- xpub: keysData.xPub,
+ xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null,
password: password,
passphrase: passphrase,
walletInfo: walletInfo,
@@ -260,16 +261,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
payjoinBox: payjoinBox);
}
- LedgerConnection? _ledgerConnection;
- BitcoinLedgerApp? _bitcoinLedgerApp;
-
- @override
- void setLedgerConnection(LedgerConnection connection) {
- _ledgerConnection = connection;
- _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
- derivationPath: walletInfo.derivationInfo!.derivationPath!);
- }
-
@override
Future<void> close({bool shouldCleanup = false}) async {
payjoinManager.cleanupSessions();
@@ -331,7 +322,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
- final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
+ final masterFingerprint =
+ await (hardwareWalletService as BitcoinHardwareWalletService).getMasterFingerprint();
final psbt = await buildPsbt(
outputs: outputs,
@@ -347,7 +339,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
outputOrdering: outputOrdering,
);
- final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt);
+ final psbtStr = base64Encode(psbt.serialize());
+ final rawHex = await hardwareWalletService!.signTransaction(transaction: psbtStr);
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
}
@@ -495,8 +488,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final derivationPath =
accountPath != null ? "$accountPath/$isChange/$index" : null;
- final signature = await _bitcoinLedgerApp!.signMessage(
- message: ascii.encode(message), signDerivationPath: derivationPath);
+ final signature = await hardwareWalletService!
+ .signMessage(message: ascii.encode(message), derivationPath: derivationPath);
return base64Encode(signature);
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
index 3d71a0c..9be8df3 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
@@ -60,6 +60,7 @@ class BitcoinWalletFromKeysCredentials extends WalletCredentials {
required String password,
required this.xpub,
WalletInfo? walletInfo,
+ super.hardwareWalletType
}) : super(name: name, password: password, walletInfo: walletInfo);
final String xpub;
diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart
index 8d41298..e5edfec 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_service.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart
@@ -7,6 +7,7 @@ import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/payjoin_session.dart';
import 'package:cw_core/unspent_coins_info.dart';
+import 'package:cw_core/utils/zpub.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
@@ -150,9 +151,12 @@ class BitcoinWalletService extends WalletService<
credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath =
credentials.hwAccountData.derivationPath;
+
+ final xpub = convertZpubToXpub(credentials.hwAccountData.xpub!);
+
final wallet = await BitcoinWallet(
password: credentials.password!,
- xpub: credentials.hwAccountData.xpub,
+ xpub: xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
networkParam: network,
@@ -166,13 +170,15 @@ class BitcoinWalletService extends WalletService<
@override
Future<BitcoinWallet> restoreFromKeys(BitcoinWalletFromKeysCredentials credentials,
- {bool? isTestnet}) async {
+ {bool? isTestnet}) async {
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
+ final xpub = convertZpubToXpub(credentials.xpub);
+
final wallet = await BitcoinWallet(
password: credentials.password!,
- xpub: credentials.xpub,
+ xpub: xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
networkParam: network,
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 75f59bb..38b7659 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -4,6 +4,7 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -75,8 +76,9 @@ abstract class ElectrumWalletBase
ElectrumBalance? initialBalance,
CryptoCurrency? currency,
bool? alwaysScan,
- }) : accountHD =
- getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
+ })
+ : accountHD = getAccountHDWallet(currency, network, seedBytes, xpub,
+ walletInfo.derivationInfo, walletInfo.hardwareWalletType),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = <int>[],
@@ -113,8 +115,13 @@ abstract class ElectrumWalletBase
sharedPrefs.complete(SharedPreferences.getInstance());
}
- static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
- Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
+ static Bip32Slip10Secp256k1 getAccountHDWallet(
+ CryptoCurrency? currency,
+ BasedUtxoNetwork network,
+ Uint8List? seedBytes,
+ String? xpub,
+ DerivationInfo? derivationInfo,
+ HardwareWalletType? hardwareWalletType) {
if (seedBytes == null && xpub == null) {
throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen");
@@ -125,8 +132,10 @@ abstract class ElectrumWalletBase
case CryptoCurrency.btc:
case CryptoCurrency.ltc:
case CryptoCurrency.tbtc:
- return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath(
- _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
+ return Bip32Slip10Secp256k1.fromSeed(
+ seedBytes, getKeyNetVersion(network, hardwareWalletType))
+ .derivePath(
+ _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1;
case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes);
@@ -137,7 +146,8 @@ abstract class ElectrumWalletBase
}
}
- return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
+ return Bip32Slip10Secp256k1.fromExtendedKey(
+ xpub!, getKeyNetVersion(network, hardwareWalletType));
}
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
@@ -149,10 +159,13 @@ abstract class ElectrumWalletBase
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
- static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) {
+ static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network,
+ [HardwareWalletType? hardwareWalletType]) {
switch (network) {
case LitecoinNetwork.mainnet:
- return Bip44Conf.litecoinMainNet.altKeyNetVer;
+ if (hardwareWalletType == HardwareWalletType.ledger)
+ return Bip44Conf.litecoinMainNet.altKeyNetVer;
+ return null;
default:
return null;
}
@@ -1408,6 +1421,8 @@ abstract class ElectrumWalletBase
}
}
+ HardwareWalletService? hardwareWalletService;
+
void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
Future<BtcTransaction> buildHardwareWalletTransaction({
diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart
new file mode 100644
index 0000000..99bbf76
--- /dev/null
+++ b/cw_bitcoin/lib/hardware/bitbox_service.dart
@@ -0,0 +1,93 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:developer';
+import 'dart:typed_data';
+
+import 'package:bitbox_flutter/bitbox_manager.dart';
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
+import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
+import 'package:cw_bitcoin/psbt/v0_finalizer.dart';
+import 'package:cw_bitcoin/utils.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:ledger_bitcoin/psbt.dart';
+
+class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService {
+ BitcoinBitboxService(this.manager);
+
+ final int bitboxCoinType = 0; // https://github.com/BitBoxSwiss/bitbox02-api-go/blob/ae070b1d41bf1cf00588fa6f498b4734b5ecd6fc/api/firmware/messages/btc.pb.go#L40
+ final BitboxManager manager;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final accounts = <HardwareAccountData>[];
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ for (final i in indexRange) {
+ final derivationPath = "m/84'/0'/$i'";
+ final xPub = await manager.getBTCXPub(bitboxCoinType, derivationPath, 1);
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ xpub: xPub,
+ ));
+ }
+
+ return accounts;
+ }
+
+
+ @override
+ Future<Uint8List> signTransaction({required String transaction}) async {
+ final psbt = PsbtV2()..deserialize(base64Decode(transaction));
+ log(base64Encode(psbt.asPsbtV0()), name: 'PSBT');
+ final signedPsbt = await manager.signBTCPsbt(bitboxCoinType, base64Encode(psbt.asPsbtV0()));
+ log(signedPsbt, name: 'signed PSBT');
+ final transactionRes = PsbtV2()
+ ..deserializeV0(base64Decode(signedPsbt))
+ ..finalizeV0();
+ return transactionRes.extract();
+ }
+
+ @override
+ Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) async {
+ throw UnimplementedError(); // ToDo (Konsti)
+ }
+
+ @override
+ Future<Uint8List> getMasterFingerprint() => manager.getMasterFingerprint();
+}
+
+class LitecoinBitboxService extends HardwareWalletService {
+ LitecoinBitboxService(this.manager);
+
+ final BitboxManager manager;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final accounts = <HardwareAccountData>[];
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ for (final i in indexRange) {
+ final derivationPath = "m/84'/2'/$i'";
+ final xPub = await manager.getBTCXPub(0, derivationPath, 1);
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ xpub: xPub,
+ ));
+ }
+
+ return accounts;
+ }
+}
diff --git a/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart
new file mode 100644
index 0000000..cc4e209
--- /dev/null
+++ b/cw_bitcoin/lib/hardware/bitcoin_hardware_wallet_service.dart
@@ -0,0 +1,5 @@
+import 'dart:typed_data';
+
+mixin BitcoinHardwareWalletService {
+ Future<Uint8List> getMasterFingerprint();
+}
diff --git a/cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart b/cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart
new file mode 100644
index 0000000..65ab764
--- /dev/null
+++ b/cw_bitcoin/lib/hardware/bitcoin_ledger_service.dart
@@ -0,0 +1,57 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
+import 'package:cw_bitcoin/utils.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:ledger_bitcoin/ledger_bitcoin.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+
+class BitcoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService {
+ BitcoinLedgerService(this.ledgerConnection)
+ : bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
+
+ final LedgerConnection ledgerConnection;
+ final BitcoinLedgerApp bitcoinLedgerApp;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
+
+ final accounts = <HardwareAccountData>[];
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ for (final i in indexRange) {
+ final derivationPath = "m/84'/0'/$i'";
+ final xPub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(xPub).childKey(Bip32KeyIndex(0));
+
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ masterFingerprint: masterFp,
+ xpub: xPub,
+ ));
+ }
+
+ return accounts;
+ }
+
+ @override
+ Future<Uint8List> signTransaction({required String transaction}) =>
+ bitcoinLedgerApp.signPsbt(psbt: PsbtV2()..deserialize(base64Decode(transaction)));
+
+ @override
+ Future<Uint8List> getMasterFingerprint() => bitcoinLedgerApp.getMasterFingerprint();
+
+ @override
+ Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
+ bitcoinLedgerApp.signMessage(message: message, signDerivationPath: derivationPath);
+}
diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
new file mode 100644
index 0000000..8334eb8
--- /dev/null
+++ b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart
@@ -0,0 +1,45 @@
+import 'dart:async';
+
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/utils.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:ledger_litecoin/ledger_litecoin.dart';
+
+class LitecoinLedgerService extends HardwareWalletService {
+ LitecoinLedgerService(this.ledgerConnection);
+
+ final LedgerConnection ledgerConnection;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
+ final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
+
+ await litecoinLedgerApp.getVersion();
+
+ final accounts = <HardwareAccountData>[];
+ final indexRange = List.generate(limit, (i) => i + index);
+ final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
+
+ for (final i in indexRange) {
+ final derivationPath = "m/84'/2'/$i'";
+ final xpub = await litecoinLedgerApp.getXPubKey(
+ accountsDerivationPath: derivationPath,
+ xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
+ final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0));
+
+ final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ xpub: xpub,
+ ));
+ }
+
+ return accounts;
+ }
+}
diff --git a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart b/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart
deleted file mode 100644
index 6284093..0000000
--- a/cw_bitcoin/lib/litecoin_hardware_wallet_service.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-import 'dart:async';
-
-import 'package:bitcoin_base/bitcoin_base.dart';
-import 'package:blockchain_utils/blockchain_utils.dart';
-import 'package:cw_bitcoin/utils.dart';
-import 'package:cw_core/hardware/hardware_account_data.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-import 'package:ledger_litecoin/ledger_litecoin.dart';
-
-class LitecoinHardwareWalletService {
- LitecoinHardwareWalletService(this.ledgerConnection);
-
- final LedgerConnection ledgerConnection;
-
- Future<List<HardwareAccountData>> getAvailableAccounts(
- {int index = 0, int limit = 5}) async {
- final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
-
- await litecoinLedgerApp.getVersion();
-
- final accounts = <HardwareAccountData>[];
- final indexRange = List.generate(limit, (i) => i + index);
- final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
-
- for (final i in indexRange) {
- final derivationPath = "m/84'/2'/$i'";
- final xpub = await litecoinLedgerApp.getXPubKey(
- accountsDerivationPath: derivationPath,
- xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
- final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
- .childKey(Bip32KeyIndex(0));
-
- final address = generateP2WPKHAddress(
- hd: hd, index: 0, network: LitecoinNetwork.mainnet);
-
- accounts.add(HardwareAccountData(
- address: address,
- accountIndex: i,
- derivationPath: derivationPath,
- xpub: xpub,
- ));
- }
-
- return accounts;
- }
-}
diff --git a/cw_bitcoin/lib/psbt/transaction_builder.dart b/cw_bitcoin/lib/psbt/transaction_builder.dart
index 4692e09..7d2bade 100644
--- a/cw_bitcoin/lib/psbt/transaction_builder.dart
+++ b/cw_bitcoin/lib/psbt/transaction_builder.dart
@@ -52,7 +52,9 @@ class PSBTTransactionBuild {
.map((e) => e?.toLowerCase())
.contains(output.address.toAddress().toLowerCase()))
.firstOrNull;
- if (cwOutput != null) {
+ if (cwOutput != null &&
+ cwOutput.extra.containsKey('bip353_name') &&
+ cwOutput.extra.containsKey('bip353_proof')) {
final bip353Name = utf8.encode(cwOutput.extra['bip353_name'] as String);
final bip353Proof = base64.decode(cwOutput.extra['bip353_proof'] as String);
diff --git a/cw_bitcoin/lib/utils.dart b/cw_bitcoin/lib/utils.dart
index a7435be..1eadc04 100644
--- a/cw_bitcoin/lib/utils.dart
+++ b/cw_bitcoin/lib/utils.dart
@@ -1,3 +1,4 @@
+import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
@@ -51,4 +52,4 @@ String generateP2TRAddress({
}) =>
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
.toTaprootAddress()
- .toAddress(network);
+ .toAddress(network);
\ No newline at end of file
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index 53db8c9..6f0580b 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -89,6 +89,13 @@ packages:
url: "https://github.com/cake-tech/bitbox-flutter.git"
source: git
version: "1.0.1"
+ bitbox_flutter:
+ dependency: "direct main"
+ description:
+ path: "../scripts/bitbox_flutter"
+ relative: true
+ source: path
+ version: "0.0.1"
bitcoin_base:
dependency: "direct overridden"
description:
@@ -597,8 +604,8 @@ packages:
dependency: "direct main"
description:
path: "packages/ledger-bitcoin"
- ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6
- resolved-ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6
+ ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
+ resolved-ref: "6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3"
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.3"
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 0c53067..5bb0b17 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -49,11 +49,13 @@ dependencies:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-bitcoin
- ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6
+ ref: 6e6ce17c2ba5cfe3d16a60d53f930db8e827b8f3
ledger_litecoin:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin
+ bitbox_flutter:
+ path: ../scripts/bitbox_flutter
socks_socket:
git:
url: https://github.com/sneurlax/socks_socket
diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart
index 76a501a..92c8096 100644
--- a/cw_core/lib/hardware/device_connection_type.dart
+++ b/cw_core/lib/hardware/device_connection_type.dart
@@ -1,22 +1,39 @@
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
enum DeviceConnectionType {
usb,
ble;
- static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
+ static List<DeviceConnectionType> supportedConnectionTypes(
+ WalletType walletType, HardwareWalletType hardwareType,
[bool isIOS = false]) {
- switch (walletType) {
- case WalletType.monero:
- case WalletType.bitcoin:
- case WalletType.litecoin:
- case WalletType.ethereum:
- case WalletType.polygon:
- if (isIOS) return [DeviceConnectionType.ble];
- return [DeviceConnectionType.ble, DeviceConnectionType.usb];
- default:
- return [];
+ bool isSupported = false;
+ switch (hardwareType) {
+ case HardwareWalletType.bitbox:
+ isSupported = [
+ WalletType.bitcoin,
+ // WalletType.litecoin,
+ WalletType.ethereum,
+ WalletType.polygon
+ ].contains(walletType);
+ break;
+ case HardwareWalletType.ledger:
+ isSupported = [
+ WalletType.monero,
+ WalletType.bitcoin,
+ WalletType.litecoin,
+ WalletType.ethereum,
+ WalletType.polygon
+ ].contains(walletType);
+ break;
}
+
+ return isSupported
+ ? (isIOS
+ ? [DeviceConnectionType.ble]
+ : [DeviceConnectionType.ble, DeviceConnectionType.usb])
+ : [];
}
String get iconString {
diff --git a/cw_core/lib/hardware/hardware_wallet_service.dart b/cw_core/lib/hardware/hardware_wallet_service.dart
new file mode 100644
index 0000000..19125ba
--- /dev/null
+++ b/cw_core/lib/hardware/hardware_wallet_service.dart
@@ -0,0 +1,13 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:cw_core/hardware/hardware_account_data.dart';
+
+abstract class HardwareWalletService {
+ Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5});
+
+ Future<Uint8List> signTransaction({required String transaction}) => throw UnimplementedError();
+
+ Future<Uint8List> signMessage({required Uint8List message, String? derivationPath}) =>
+ throw UnimplementedError();
+}
diff --git a/cw_core/lib/utils/zpub.dart b/cw_core/lib/utils/zpub.dart
new file mode 100644
index 0000000..90f0389
--- /dev/null
+++ b/cw_core/lib/utils/zpub.dart
@@ -0,0 +1,46 @@
+
+import 'dart:typed_data';
+
+import 'package:blockchain_utils/blockchain_utils.dart';
+
+String convertZpubToXpub(String zpub) {
+ try {
+ final decoded = Base58Decoder.checkDecode(zpub);
+
+ if (decoded.length < 4) {
+ throw ArgumentError('Invalid extended public key length');
+ }
+
+ final versionBytes = decoded.sublist(0, 4);
+ final zpubVersionBytes = [0x04, 0xb2, 0x47, 0x46]; // zpub mainnet version
+ final zpubTestnetVersionBytes = [0x04, 0x5f, 0x1c, 0xf6]; // vpub testnet version
+
+ bool isZpub = listEquals(versionBytes, zpubVersionBytes);
+ bool isVpub = listEquals(versionBytes, zpubTestnetVersionBytes);
+
+ if (!isZpub && !isVpub) {
+ return zpub;
+ }
+
+ final xpubVersionBytes = isZpub ?
+ [0x04, 0x88, 0xb2, 0x1e] : // xpub mainnet
+ [0x04, 0x35, 0x87, 0xcf]; // tpub testnet
+
+ final newExtendedKey = Uint8List.fromList([
+ ...xpubVersionBytes,
+ ...decoded.sublist(4),
+ ]);
+
+ return Base58Encoder.checkEncode(newExtendedKey);
+ } catch (e) {
+ throw ArgumentError('Failed to convert zpub to xpub: $e');
+ }
+}
+
+bool listEquals<T>(List<T> a, List<T> b) {
+ if (a.length != b.length) return false;
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) return false;
+ }
+ return true;
+}
diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart
index 7d0fc4f..b492fb0 100644
--- a/cw_core/lib/wallet_base.dart
+++ b/cw_core/lib/wallet_base.dart
@@ -62,6 +62,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
bool get isHardwareWallet => walletInfo.isHardwareWallet;
+ HardwareWalletType? get hardwareWalletType => walletInfo.hardwareWalletType;
+
bool get hasRescan => false;
Future<void> connectToNode({required Node node});
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index d62e619..15a9438 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -25,6 +25,16 @@ enum DerivationType {
enum HardwareWalletType {
@HiveField(0)
ledger,
+ @HiveField(1)
+ bitbox,
+ @HiveField(2)
+ cupcake,
+ @HiveField(3)
+ coldcard,
+ @HiveField(4)
+ seedsigner,
+ @HiveField(5)
+ keystone,
}
@HiveType(typeId: DerivationInfo.typeId)
@@ -224,7 +234,8 @@ class WalletInfo extends HiveObject {
return showIntroCakePayCard!;
}
- bool get isHardwareWallet => hardwareWalletType != null;
+ bool get isHardwareWallet =>
+ [HardwareWalletType.ledger, HardwareWalletType.bitbox].contains(hardwareWalletType);
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock
index fcf747d..2cfa935 100644
--- a/cw_core/pubspec.lock
+++ b/cw_core/pubspec.lock
@@ -47,7 +47,7 @@ packages:
source: hosted
version: "2.13.0"
blockchain_utils:
- dependency: transitive
+ dependency: "direct main"
description:
path: "."
ref: cake-update-v2
diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml
index ed7a6e4..ed1f90c 100644
--- a/cw_core/pubspec.yaml
+++ b/cw_core/pubspec.yaml
@@ -40,6 +40,10 @@ dependencies:
ref: e6232c53c1595469931ababa878759a067c02e94
torch_dart:
path: ../scripts/torch_dart
+ blockchain_utils:
+ git:
+ url: https://github.com/cake-tech/blockchain_utils
+ ref: cake-update-v2
dev_dependencies:
flutter_test:
diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart
index 29a88dc..215e8bc 100644
--- a/cw_evm/lib/evm_chain_client.dart
+++ b/cw_evm/lib/evm_chain_client.dart
@@ -9,6 +9,7 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
+import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:flutter/foundation.dart';
diff --git a/cw_evm/lib/evm_chain_hardware_wallet_service.dart b/cw_evm/lib/evm_chain_hardware_wallet_service.dart
deleted file mode 100644
index d8f67c6..0000000
--- a/cw_evm/lib/evm_chain_hardware_wallet_service.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-import 'dart:async';
-
-import 'package:cw_core/hardware/hardware_account_data.dart';
-import 'package:ledger_ethereum/ledger_ethereum.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-
-class EVMChainHardwareWalletService {
- EVMChainHardwareWalletService(this.ledgerConnection);
-
- final LedgerConnection ledgerConnection;
-
- Future<List<HardwareAccountData>> getAvailableAccounts(
- {int index = 0, int limit = 5}) async {
- final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
-
- await ethereumLedgerApp.getVersion();
-
- final accounts = <HardwareAccountData>[];
- final indexRange = List.generate(limit, (i) => i + index);
-
- for (final i in indexRange) {
- final derivationPath = "m/44'/60'/$i'/0/0";
- final address = await ethereumLedgerApp.getAccounts(
- accountsDerivationPath: derivationPath);
-
- accounts.add(HardwareAccountData(
- address: address.first,
- accountIndex: i,
- derivationPath: derivationPath,
- ));
- }
-
- return accounts;
- }
-}
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index f7fb244..db35030 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -29,7 +29,8 @@ import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
-import 'package:cw_evm/evm_ledger_credentials.dart';
+import 'package:cw_evm/hardware/evm_bitbox_credentials.dart';
+import 'package:cw_evm/hardware/evm_ledger_credentials.dart';
import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@@ -207,9 +208,12 @@ abstract class EVMChainWalletBase
// check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯
await _checkForExistingScamTokens();
- if (walletInfo.isHardwareWallet) {
+ if (walletInfo.hardwareWalletType == HardwareWalletType.ledger) {
_evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
+ } else if (walletInfo.hardwareWalletType == HardwareWalletType.bitbox) {
+ _evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address);
+ walletAddresses.address = walletInfo.address;
} else {
_evmChainPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
@@ -547,7 +551,8 @@ abstract class EVMChainWalletBase
}
}
- if (transactionCurrency is Erc20Token && isHardwareWallet) {
+ if (transactionCurrency is Erc20Token &&
+ walletInfo.hardwareWalletType == HardwareWalletType.ledger) {
await (_evmChainPrivateKey as EvmLedgerCredentials)
.provideERC20Info(transactionCurrency.contractAddress, _client.chainId);
}
diff --git a/cw_evm/lib/evm_ledger_credentials.dart b/cw_evm/lib/evm_ledger_credentials.dart
deleted file mode 100644
index b579b91..0000000
--- a/cw_evm/lib/evm_ledger_credentials.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-import 'dart:async';
-import 'dart:typed_data';
-
-import 'package:cw_core/hardware/device_not_connected_exception.dart'
- as exception;
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:ledger_ethereum/ledger_ethereum.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-import 'package:web3dart/crypto.dart';
-import 'package:web3dart/web3dart.dart';
-
-class EvmLedgerCredentials extends CredentialsWithKnownAddress {
- final String _address;
-
- EthereumLedgerApp? ethereumLedgerApp;
-
- EvmLedgerCredentials(this._address);
-
- @override
- EthereumAddress get address => EthereumAddress.fromHex(_address);
-
- void setLedgerConnection(LedgerConnection connection,
- [String? derivationPath]) {
- ethereumLedgerApp = EthereumLedgerApp(connection,
- derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
- }
-
- @override
- MsgSignature signToEcSignature(Uint8List payload,
- {int? chainId, bool isEIP1559 = false}) =>
- throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
-
- @override
- Future<MsgSignature> signToSignature(Uint8List payload,
- {int? chainId, bool isEIP1559 = false}) async {
- if (ethereumLedgerApp == null) {
- throw exception.DeviceNotConnectedException();
- }
-
- final sig = await ethereumLedgerApp!.signTransaction(payload);
-
- final v = sig[0].toInt();
- final r = bytesToHex(sig.sublist(1, 1 + 32));
- final s = bytesToHex(sig.sublist(1 + 32, 1 + 32 + 32));
-
- var truncChainId = chainId ?? 1;
- while (truncChainId.bitLength > 32) {
- truncChainId >>= 8;
- }
-
- final truncTarget = truncChainId * 2 + 35;
-
- int parity = v;
- if (truncTarget & 0xff == v) {
- parity = 0;
- } else if ((truncTarget + 1) & 0xff == v) {
- parity = 1;
- }
-
- // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
- int chainIdV;
- if (isEIP1559) {
- chainIdV = v;
- } else {
- chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
- }
-
- return MsgSignature(
- BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
- }
-
- @override
- Future<Uint8List> signPersonalMessage(Uint8List payload,
- {int? chainId}) async {
- if (isNotConnected) throw exception.DeviceNotConnectedException();
-
- final sig = await ethereumLedgerApp!.signMessage(payload);
-
- final r = sig.sublist(1, 1 + 32);
- final s = sig.sublist(1 + 32, 1 + 32 + 32);
- final v = [sig[0]];
-
- // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
- return Uint8List.fromList(r + s + v);
- }
-
- @override
- Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
- throw UnimplementedError(
- "EvmLedgerCredentials.signPersonalMessageToUint8List");
-
- Future<void> provideERC20Info(
- String erc20ContractAddress, int chainId) async {
- if (isNotConnected) throw exception.DeviceNotConnectedException();
-
- try {
- await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
- erc20ContractAddress: erc20ContractAddress, chainId: chainId);
- } catch (e) {
- printV(e);
- rethrow;
- // if (e.errorCode != -28672) rethrow;
- }
- }
-
- bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
-}
diff --git a/cw_evm/lib/hardware/evm_bitbox_credentials.dart b/cw_evm/lib/hardware/evm_bitbox_credentials.dart
new file mode 100644
index 0000000..d8dca4e
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_bitbox_credentials.dart
@@ -0,0 +1,79 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:bitbox_flutter/bitbox_manager.dart';
+import 'package:cw_core/hardware/device_not_connected_exception.dart' as exception;
+import 'package:web3dart/crypto.dart';
+import 'package:web3dart/web3dart.dart';
+
+class EvmBitboxCredentials extends CredentialsWithKnownAddress {
+ final String _address;
+
+ BitboxManager? bitboxManager;
+ String? derivationPath;
+
+ EvmBitboxCredentials(this._address);
+
+ @override
+ EthereumAddress get address => EthereumAddress.fromHex(_address);
+
+ void setBitbox(BitboxManager connection, [String? derivationPath_]) {
+ bitboxManager = connection;
+ derivationPath = derivationPath_ ?? "m/44'/60'/0'/0/0";
+ }
+
+ @override
+ MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
+
+ @override
+ Future<MsgSignature> signToSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) async {
+ if (bitboxManager == null) {
+ throw exception.DeviceNotConnectedException();
+ }
+
+ if (isEIP1559) payload = payload.sublist(1);
+ final sig = await bitboxManager!
+ .signETHRLPTransaction(chainId ?? 1, derivationPath!, bytesToHex(payload), isEIP1559);
+
+ final r = bytesToHex(sig.sublist(0, 32));
+ final s = bytesToHex(sig.sublist(32, 32 + 32));
+ final v = sig.last.toInt();
+
+ if (isEIP1559) {
+ return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), v);
+ }
+
+ var truncChainId = chainId ?? 1;
+ while (truncChainId.bitLength > 32) {
+ truncChainId >>= 8;
+ }
+
+ final truncTarget = truncChainId * 2 + 35;
+
+ int parity = v;
+ if (truncTarget & 0xff == v) {
+ parity = 0;
+ } else if ((truncTarget + 1) & 0xff == v) {
+ parity = 1;
+ }
+
+ // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
+ final chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
+
+ return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
+ }
+
+ @override
+ Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+ return await bitboxManager!.signETHMessage(chainId ?? 1, derivationPath!, payload);
+ }
+
+ @override
+ Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
+
+ bool get isNotConnected => bitboxManager == null;
+}
diff --git a/cw_evm/lib/hardware/evm_chain_bitbox_service.dart b/cw_evm/lib/hardware/evm_chain_bitbox_service.dart
new file mode 100644
index 0000000..ef1918b
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_chain_bitbox_service.dart
@@ -0,0 +1,32 @@
+import 'dart:async';
+
+import 'package:bitbox_flutter/bitbox_manager.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+
+class EVMChainBitboxService extends HardwareWalletService {
+ EVMChainBitboxService(this.manager, {this.chainId = 1});
+
+ final BitboxManager manager;
+ final int chainId;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts(
+ {int index = 0, int limit = 5}) async {
+ final accounts = <HardwareAccountData>[];
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ for (final i in indexRange) {
+ final derivationPath = "m/44'/60'/0'/0/$i";
+ final address = await manager.getETHAddress(chainId, derivationPath);
+
+ accounts.add(HardwareAccountData(
+ address: address,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ ));
+ }
+
+ return accounts;
+ }
+}
diff --git a/cw_evm/lib/hardware/evm_chain_ledger_service.dart b/cw_evm/lib/hardware/evm_chain_ledger_service.dart
new file mode 100644
index 0000000..d1c883f
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_chain_ledger_service.dart
@@ -0,0 +1,37 @@
+import 'dart:async';
+
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:ledger_ethereum/ledger_ethereum.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+
+class EVMChainLedgerService extends HardwareWalletService {
+ EVMChainLedgerService(this.ledgerConnection);
+
+ final LedgerConnection ledgerConnection;
+
+ @override
+ Future<List<HardwareAccountData>> getAvailableAccounts(
+ {int index = 0, int limit = 5}) async {
+ final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
+
+ await ethereumLedgerApp.getVersion();
+
+ final accounts = <HardwareAccountData>[];
+ final indexRange = List.generate(limit, (i) => i + index);
+
+ for (final i in indexRange) {
+ final derivationPath = "m/44'/60'/$i'/0/0";
+ final address = await ethereumLedgerApp.getAccounts(
+ accountsDerivationPath: derivationPath);
+
+ accounts.add(HardwareAccountData(
+ address: address.first,
+ accountIndex: i,
+ derivationPath: derivationPath,
+ ));
+ }
+
+ return accounts;
+ }
+}
diff --git a/cw_evm/lib/hardware/evm_ledger_credentials.dart b/cw_evm/lib/hardware/evm_ledger_credentials.dart
new file mode 100644
index 0000000..13a93cf
--- /dev/null
+++ b/cw_evm/lib/hardware/evm_ledger_credentials.dart
@@ -0,0 +1,109 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:cw_core/hardware/device_not_connected_exception.dart'
+ as exception;
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:ledger_ethereum/ledger_ethereum.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:web3dart/crypto.dart';
+import 'package:web3dart/web3dart.dart';
+
+class EvmLedgerCredentials extends CredentialsWithKnownAddress {
+ final String _address;
+
+ EthereumLedgerApp? ethereumLedgerApp;
+
+ EvmLedgerCredentials(this._address);
+
+ @override
+ EthereumAddress get address => EthereumAddress.fromHex(_address);
+
+ void setLedgerConnection(LedgerConnection connection,
+ [String? derivationPath]) {
+ ethereumLedgerApp = EthereumLedgerApp(connection,
+ derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
+ }
+
+ @override
+ MsgSignature signToEcSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) =>
+ throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
+
+ @override
+ Future<MsgSignature> signToSignature(Uint8List payload,
+ {int? chainId, bool isEIP1559 = false}) async {
+ if (ethereumLedgerApp == null) {
+ throw exception.DeviceNotConnectedException();
+ }
+
+ final sig = await ethereumLedgerApp!.signTransaction(payload);
+
+ final v = sig[0].toInt();
+ final r = bytesToHex(sig.sublist(1, 1 + 32));
+ final s = bytesToHex(sig.sublist(1 + 32, 1 + 32 + 32));
+
+ var truncChainId = chainId ?? 1;
+ while (truncChainId.bitLength > 32) {
+ truncChainId >>= 8;
+ }
+
+ final truncTarget = truncChainId * 2 + 35;
+
+ int parity = v;
+ if (truncTarget & 0xff == v) {
+ parity = 0;
+ } else if ((truncTarget + 1) & 0xff == v) {
+ parity = 1;
+ }
+
+ // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
+ int chainIdV;
+ if (isEIP1559) {
+ chainIdV = v;
+ } else {
+ chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
+ }
+
+ return MsgSignature(
+ BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
+ }
+
+ @override
+ Future<Uint8List> signPersonalMessage(Uint8List payload,
+ {int? chainId}) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+
+ final sig = await ethereumLedgerApp!.signMessage(payload);
+
+ if (sig.isEmpty) throw Exception("No Signature received from device!");
+
+ final r = sig.sublist(1, 1 + 32);
+ final s = sig.sublist(1 + 32, 1 + 32 + 32);
+ final v = [sig[0]];
+
+ // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
+ return Uint8List.fromList(r + s + v);
+ }
+
+ @override
+ Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
+ throw UnimplementedError(
+ "EvmLedgerCredentials.signPersonalMessageToUint8List");
+
+ Future<void> provideERC20Info(
+ String erc20ContractAddress, int chainId) async {
+ if (isNotConnected) throw exception.DeviceNotConnectedException();
+
+ try {
+ await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
+ erc20ContractAddress: erc20ContractAddress, chainId: chainId);
+ } catch (e) {
+ printV(e);
+ rethrow;
+ // if (e.errorCode != -28672) rethrow;
+ }
+ }
+
+ bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
+}
diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml
index 80788ca..60e214d 100644
--- a/cw_evm/pubspec.yaml
+++ b/cw_evm/pubspec.yaml
@@ -31,6 +31,8 @@ dependencies:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-ethereum
ref: f4761cd5171d4c1e2e42fd3298261650539fb2db
+ bitbox_flutter:
+ path: ../scripts/bitbox_flutter
dependency_overrides:
web3dart:
diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock
index 914d52f..e04f73f 100644
--- a/cw_monero/pubspec.lock
+++ b/cw_monero/pubspec.lock
@@ -66,11 +66,11 @@ packages:
dependency: transitive
description:
path: "."
- ref: cake-update-v4
- resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a"
+ ref: cake-update-v2
+ resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils"
source: git
- version: "4.3.0"
+ version: "3.3.0"
bluez:
dependency: transitive
description:
@@ -599,10 +599,10 @@ packages:
description:
path: "."
ref: cake-update-v2
- resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e"
+ resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
url: "https://github.com/cake-tech/on_chain.git"
source: git
- version: "6.2.0"
+ version: "3.7.0"
package_config:
dependency: transitive
description:
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 12493fe..91877fc 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -252,6 +252,13 @@ SPEC CHECKSUMS:
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
+ permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
+ reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
+ SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
+ sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b
+ share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
+ shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
+ sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart
index e8c2559..953c88c 100644
--- a/lib/bitcoin/cw_bitcoin.dart
+++ b/lib/bitcoin/cw_bitcoin.dart
@@ -23,8 +23,10 @@ class CWBitcoin extends Bitcoin {
required String name,
required String password,
required String xpub,
+ HardwareWalletType? hardwareWalletType,
}) =>
- BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub);
+ BitcoinWalletFromKeysCredentials(
+ name: name, password: password, xpub: xpub, hardwareWalletType: hardwareWalletType);
@override
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
@@ -315,8 +317,12 @@ class CWBitcoin extends Bitcoin {
}
@override
- List<ReceivePageOption> getLitecoinReceivePageOptions() {
- if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
+ List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet) {
+ final litecoinWallet = wallet as ElectrumWallet;
+ if (Platform.isLinux ||
+ Platform.isMacOS ||
+ Platform.isWindows ||
+ litecoinWallet.isHardwareWallet) {
return BitcoinReceivePageOption.allLitecoin
.where((element) => element != BitcoinReceivePageOption.mweb)
.toList();
@@ -536,27 +542,22 @@ class CWBitcoin extends Bitcoin {
}
@override
- Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
- {int index = 0, int limit = 5}) async {
- final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
- try {
- return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
- } catch (err) {
- printV(err);
- throw err;
- }
+ void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
+ (wallet as ElectrumWallet).hardwareWalletService = service;
}
@override
- Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
- {int index = 0, int limit = 5}) async {
- final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
- try {
- return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
- } catch (err) {
- printV(err);
- throw err;
- }
+ HardwareWalletService getLedgerHardwareWalletService(
+ ledger.LedgerConnection connection, bool isBitcoin) {
+ if (isBitcoin) return BitcoinLedgerService(connection);
+ return LitecoinLedgerService(connection);
+ }
+
+ @override
+ HardwareWalletService getBitboxHardwareWalletService(
+ bitbox.BitboxManager manager, bool isBitcoin) {
+ if (isBitcoin) return BitcoinBitboxService(manager);
+ return LitecoinBitboxService(manager);
}
@override
diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart
index 689c86f..7cc92eb 100644
--- a/lib/buy/buy_provider.dart
+++ b/lib/buy/buy_provider.dart
@@ -4,7 +4,7 @@ import 'package:cake_wallet/order/order.dart';
import 'package:cake_wallet/buy/pairs_utils.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
@@ -13,14 +13,14 @@ abstract class BuyProvider {
BuyProvider({
required this.wallet,
required this.isTestEnvironment,
- required this.ledgerVM,
+ required this.hardwareWalletVM,
required this.supportedCryptoList,
required this.supportedFiatList
});
final WalletBase wallet;
final bool isTestEnvironment;
- final LedgerViewModel? ledgerVM;
+ final HardwareWalletViewModel? hardwareWalletVM;
final List<TradePair<dynamic, dynamic>> supportedCryptoList;
final List<TradePair<dynamic, dynamic>> supportedFiatList;
diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart
index 3ab5d42..c88ea37 100644
--- a/lib/buy/dfx/dfx_buy_provider.dart
+++ b/lib/buy/dfx/dfx_buy_provider.dart
@@ -10,8 +10,8 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
@@ -24,16 +24,16 @@ class DFXBuyProvider extends BuyProvider {
DFXBuyProvider({
required WalletBase wallet,
bool isTestEnvironment = false,
- LedgerViewModel? ledgerVM,
+ HardwareWalletViewModel? hardwareWalletVM,
}) : super(
- wallet: wallet,
- isTestEnvironment: isTestEnvironment,
- ledgerVM: ledgerVM,
- supportedCryptoList: supportedCryptoToFiatPairs(
- notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
- supportedFiatList: supportedFiatToCryptoPairs(
- notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto)
- );
+ wallet: wallet,
+ isTestEnvironment: isTestEnvironment,
+ hardwareWalletVM: hardwareWalletVM,
+ supportedCryptoList: supportedCryptoToFiatPairs(
+ notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
+ supportedFiatList: supportedFiatToCryptoPairs(
+ notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
+ );
static const _baseUrl = 'api.dfx.swiss';
@@ -104,8 +104,7 @@ class DFXBuyProvider extends BuyProvider {
// }
Future<String> auth(String walletAddress) async {
- final signMessage = await getSignature(
- await getSignMessage(walletAddress), walletAddress);
+ final signMessage = await getSignature(await getSignMessage(walletAddress), walletAddress);
final requestBody = jsonEncode({
'wallet': walletName,
@@ -119,7 +118,6 @@ class DFXBuyProvider extends BuyProvider {
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
-
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
@@ -139,14 +137,13 @@ class DFXBuyProvider extends BuyProvider {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
- final r = await wallet.signMessage(message);
- return r;
+ return wallet.signMessage(message);
case WalletType.monero:
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.zano:
- return await wallet.signMessage(message, address: walletAddress);
+ return wallet.signMessage(message, address: walletAddress);
default:
throw Exception("WalletType is not available for DFX ${wallet.type}");
}
@@ -336,16 +333,17 @@ class DFXBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
- if (!ledgerVM!.isConnected) {
+ if (!hardwareWalletVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
- onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
- ledgerVM.setLedger(wallet);
+ hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
+ onConnectDevice: (context, hwwVM) {
+ hwwVM.initWallet(wallet);
Navigator.of(context).pop();
}));
} else {
- ledgerVM!.setLedger(wallet);
+ hardwareWalletVM!.initWallet(wallet);
}
}
@@ -370,14 +368,13 @@ class DFXBuyProvider extends BuyProvider {
}
} catch (e) {
await showPopUp<void>(
- context: context,
- builder: (BuildContext context) {
- return AlertWithOneAction(
- alertTitle: "DFX.swiss",
- alertContent: S.of(context).buy_provider_unavailable + ': $e',
- buttonText: S.of(context).ok,
- buttonAction: () => Navigator.of(context).pop());
- });
+ context: context,
+ builder: (context) => AlertWithOneAction(
+ alertTitle: "DFX.swiss",
+ alertContent: '${S.of(context).buy_provider_unavailable}: $e',
+ buttonText: S.of(context).ok,
+ buttonAction: () => Navigator.of(context).pop()),
+ );
}
}
diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart
index 1d2d10f..d796381 100644
--- a/lib/buy/kryptonim/kryptonim.dart
+++ b/lib/buy/kryptonim/kryptonim.dart
@@ -20,7 +20,7 @@ class KryptonimBuyProvider extends BuyProvider {
: super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
- ledgerVM: null,
+ hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
diff --git a/lib/buy/meld/meld_buy_provider.dart b/lib/buy/meld/meld_buy_provider.dart
index 002ab04..632d08e 100644
--- a/lib/buy/meld/meld_buy_provider.dart
+++ b/lib/buy/meld/meld_buy_provider.dart
@@ -22,7 +22,7 @@ class MeldBuyProvider extends BuyProvider {
: super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
- ledgerVM: null,
+ hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart
index 92f9e50..8e33044 100644
--- a/lib/buy/moonpay/moonpay_provider.dart
+++ b/lib/buy/moonpay/moonpay_provider.dart
@@ -36,7 +36,7 @@ class MoonPayProvider extends BuyProvider {
super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
- ledgerVM: null,
+ hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart
index 80c9a63..1d1d029 100644
--- a/lib/buy/onramper/onramper_buy_provider.dart
+++ b/lib/buy/onramper/onramper_buy_provider.dart
@@ -9,7 +9,6 @@ import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/core/theme_store.dart';
-import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -22,7 +21,7 @@ class OnRamperBuyProvider extends BuyProvider {
{required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet,
isTestEnvironment: isTestEnvironment,
- ledgerVM: null,
+ hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart
index 4b9c1aa..50f7f86 100644
--- a/lib/buy/robinhood/robinhood_buy_provider.dart
+++ b/lib/buy/robinhood/robinhood_buy_provider.dart
@@ -11,9 +11,9 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
@@ -22,16 +22,19 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class RobinhoodBuyProvider extends BuyProvider {
- RobinhoodBuyProvider(
- {required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
- : super(
- wallet: wallet,
- isTestEnvironment: isTestEnvironment,
- ledgerVM: ledgerVM,
- supportedCryptoList: supportedCryptoToFiatPairs(
- notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
- supportedFiatList: supportedFiatToCryptoPairs(
- notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto));
+ RobinhoodBuyProvider({
+ required WalletBase wallet,
+ bool isTestEnvironment = false,
+ HardwareWalletViewModel? hardwareWalletVM,
+ }) : super(
+ wallet: wallet,
+ isTestEnvironment: isTestEnvironment,
+ hardwareWalletVM: hardwareWalletVM,
+ supportedCryptoList: supportedCryptoToFiatPairs(
+ notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
+ supportedFiatList: supportedFiatToCryptoPairs(
+ notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
+ );
static const _baseUrl = 'applink.robinhood.com';
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
@@ -69,8 +72,8 @@ class RobinhoodBuyProvider extends BuyProvider {
final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId});
try {
- final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
-
+ final response =
+ await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body) as Map<String, dynamic>;
@@ -104,11 +107,11 @@ class RobinhoodBuyProvider extends BuyProvider {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
- return await wallet.signMessage(message);
+ return wallet.signMessage(message);
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
- return await wallet.signMessage(message, address: wallet.walletAddresses.address);
+ return wallet.signMessage(message, address: wallet.walletAddresses.address);
default:
throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood");
}
@@ -123,14 +126,14 @@ class RobinhoodBuyProvider extends BuyProvider {
final uri = Uri.https(_cIdBaseUrl, "/api/robinhood");
- var response = await ProxyWrapper().post(
+ final response = await ProxyWrapper().post(
clearnetUri: uri,
headers: {'Content-Type': 'application/json'},
- body: json.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
+ body: json
+ .encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
);
if (response.statusCode == 200) {
-
return (jsonDecode(response.body) as Map<String, dynamic>)['connectId'] as String;
} else {
throw Exception('Provider currently unavailable. Status: ${response.statusCode}');
@@ -158,16 +161,17 @@ class RobinhoodBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
- if (!ledgerVM!.isConnected) {
+ if (!hardwareWalletVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
- onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
- ledgerVM.setLedger(wallet);
+ hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
+ onConnectDevice: (context, hwwVM) {
+ hwwVM.initWallet(wallet);
Navigator.of(context).pop();
}));
} else {
- ledgerVM!.setLedger(wallet);
+ hardwareWalletVM!.initWallet(wallet);
}
}
@@ -176,14 +180,13 @@ class RobinhoodBuyProvider extends BuyProvider {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e) {
await showPopUp<void>(
- context: context,
- builder: (BuildContext context) {
- return AlertWithOneAction(
- alertTitle: "Robinhood Connect",
- alertContent: e.toString(),
- buttonText: S.of(context).ok,
- buttonAction: () => Navigator.of(context).pop());
- });
+ context: context,
+ builder: (context) => AlertWithOneAction(
+ alertTitle: "Robinhood Connect",
+ alertContent: '${S.of(context).buy_provider_unavailable}: $e',
+ buttonText: S.of(context).ok,
+ buttonAction: () => Navigator.of(context).pop()),
+ );
}
}
diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart
index f85c044..a0cf1ed 100644
--- a/lib/buy/wyre/wyre_buy_provider.dart
+++ b/lib/buy/wyre/wyre_buy_provider.dart
@@ -20,7 +20,7 @@ class WyreBuyProvider extends BuyProvider {
super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
- ledgerVM: null,
+ hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
diff --git a/lib/core/new_wallet_type_arguments.dart b/lib/core/new_wallet_type_arguments.dart
index b436178..a2b5102 100644
--- a/lib/core/new_wallet_type_arguments.dart
+++ b/lib/core/new_wallet_type_arguments.dart
@@ -1,14 +1,15 @@
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class NewWalletTypeArguments {
final void Function(BuildContext, WalletType)? onTypeSelected;
final bool isCreate;
- final bool isHardwareWallet;
+ final HardwareWalletType? hardwareWalletType;
NewWalletTypeArguments({
required this.onTypeSelected,
required this.isCreate,
- required this.isHardwareWallet,
+ this.hardwareWalletType,
});
}
diff --git a/lib/di.dart b/lib/di.dart
index e00bc5c..61d478a 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
+import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
@@ -53,6 +54,8 @@ import 'package:cake_wallet/themes/core/theme_store.dart';
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
@@ -393,8 +396,22 @@ Future<void> setup({
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
+ getIt.registerFactoryParam<HardwareWalletViewModel, HardwareWalletType, void>((type, _) {
+ switch(type) {
+ case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
+ case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
+ case HardwareWalletType.cupcake:
+ case HardwareWalletType.coldcard:
+ case HardwareWalletType.seedsigner:
+ case HardwareWalletType.keystone:
+ throw Exception("This should not have happened, because airgapped Wallets don't need View Models");
+ }
+ });
+
getIt.registerLazySingleton(() => LedgerViewModel());
+ getIt.registerLazySingleton(() => BitboxViewModel());
+
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore);
@@ -505,9 +522,9 @@ Future<void> setup({
walletType: args.walletType ?? currentWalletType);
});
- getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, void>((type, _) =>
- WalletHardwareRestoreViewModel(
- getIt.get<LedgerViewModel>(),
+ getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, HardwareWalletViewModel>(
+ (type, hardwareWalletVM) => WalletHardwareRestoreViewModel(
+ hardwareWalletVM,
getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type),
_walletInfoSource,
@@ -816,7 +833,10 @@ Future<void> setup({
getIt.get<BalanceViewModel>(),
getIt.get<ContactListViewModel>(),
_transactionDescriptionBox,
- getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
+ getIt.get<AppStore>().wallet!.isHardwareWallet
+ ? getIt<HardwareWalletViewModel>(
+ param1: getIt.get<AppStore>().wallet!.hardwareWalletType!)
+ : null,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb,
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
getIt.get<FeesViewModel>(),
@@ -1077,14 +1097,18 @@ Future<void> setup({
isSelected: isSelected));
getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
- wallet: getIt.get<AppStore>().wallet!,
- ledgerVM:
- getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
+ wallet: getIt.get<AppStore>().wallet!,
+ hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
+ ? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
+ : null,
+ ));
getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
- wallet: getIt.get<AppStore>().wallet!,
- ledgerVM:
- getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
+ wallet: getIt<AppStore>().wallet!,
+ hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
+ ? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
+ : null,
+ ));
getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
appStore: getIt.get<AppStore>(),
@@ -1235,19 +1259,25 @@ Future<void> setup({
getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
- getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, RestoredWallet?>((WalletType type,
- restoredWallet) =>
- WalletRestoreViewModel(getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type),
- _walletInfoSource, getIt.get<SeedSettingsViewModel>(),
- type: type, restoredWallet: restoredWallet));
-
- getIt.registerFactoryParam<WalletRestorePage, WalletType, RestoredWallet?>((WalletType type,
- restoredWallet) {
- return WalletRestorePage(
- getIt.get<WalletRestoreViewModel>(param1: type, param2: restoredWallet),
- getIt.get<SeedSettingsViewModel>());
+ getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
+ (type, additionalParams) {
+ final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
+ final hardwareWalletType = additionalParams?['hardwareWalletType'] as HardwareWalletType?;
+
+ return WalletRestoreViewModel(
+ getIt.get<AppStore>(),
+ getIt.get<WalletCreationService>(param1: type),
+ _walletInfoSource,
+ getIt.get<SeedSettingsViewModel>(),
+ type: type,
+ restoredWallet: restoredWallet,
+ hardwareWalletType: hardwareWalletType);
});
+ getIt.registerFactoryParam<WalletRestorePage, WalletType, Map<String, dynamic>?>((type, additionalParams) =>
+ WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type, param2: additionalParams),
+ getIt.get<SeedSettingsViewModel>()));
+
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
@@ -1565,9 +1595,12 @@ Future<void> setup({
getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
getIt.registerFactory(() => SocketHealthLogsViewModel());
+
getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get<SocketHealthLogsViewModel>()));
getIt.registerFactory(() => DevNetworkRequests());
+
+ getIt.registerFactory(() => DevQRToolsPage());
getIt.registerFactory(() => ExchangeProviderLogsViewModel());
getIt.registerFactory(() => DevExchangeProviderLogsPage(getIt.get<ExchangeProviderLogsViewModel>()));
diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart
index d3acc5d..5bfbf96 100644
--- a/lib/entities/hardware_wallet/hardware_wallet_device.dart
+++ b/lib/entities/hardware_wallet/hardware_wallet_device.dart
@@ -1,22 +1,44 @@
+import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
-class HardwareWalletDevice {
- final String name;
- final HardwareWalletDeviceType type;
- final HardwareWalletConnectionType connectionType;
-
- const HardwareWalletDevice({
- required this.name,
- required this.type,
- required this.connectionType,
- });
-
- factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
- HardwareWalletDevice(
- name: device.name,
- type: device.deviceInfo.toGeneric(),
- connectionType: device.connectionType.toGeneric(),
- );
+abstract class HardwareWalletDevice {
+ String get name;
+
+ HardwareWalletDeviceType get type;
+
+ HardwareWalletConnectionType get connectionType;
+}
+
+class LedgerHardwareWalletDevice extends HardwareWalletDevice {
+ final ledger.LedgerDevice device;
+
+ LedgerHardwareWalletDevice(this.device);
+
+ @override
+ String get name => device.name;
+
+ @override
+ HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
+
+ @override
+ HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
+}
+
+class BitboxHardwareWalletDevice extends HardwareWalletDevice {
+ final bitbox.BitboxDevice device;
+
+ BitboxHardwareWalletDevice(this.device);
+
+ @override
+ String get name => device.productName;
+
+ @override
+ HardwareWalletDeviceType get type => name.contains("BitBox02 Nova")
+ ? HardwareWalletDeviceType.BitBox02Nova
+ : HardwareWalletDeviceType.BitBox02;
+
+ @override
+ HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
}
enum HardwareWalletDeviceType {
@@ -25,13 +47,16 @@ enum HardwareWalletDeviceType {
ledgerNanoX,
ledgerNanoSPlus,
ledgerStax,
- ledgerFlex;
+ ledgerFlex,
+ BitBox02,
+ BitBox02Nova;
}
enum HardwareWalletConnectionType {
usb,
ble,
- nfc;
+ nfc,
+ qr;
}
extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart
index 9f19944..04dadaa 100644
--- a/lib/ethereum/cw_ethereum.dart
+++ b/lib/ethereum/cw_ethereum.dart
@@ -181,22 +181,22 @@ class CWEthereum extends Ethereum {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
- void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
- ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
- .setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath);
+ void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
+ if (service is EVMChainLedgerService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
+ service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
+ } else if (service is EVMChainBitboxService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
+ .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
+ }
}
+ @override
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
+ EVMChainLedgerService(connection);
@override
- Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
- {int index = 0, int limit = 5}) async {
- final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
- try {
- return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
- } catch (err) {
- printV(err);
- throw err;
- }
- }
+ HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
+ EVMChainBitboxService(manager);
@override
List<String> getDefaultTokenContractAddresses() {
diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart
index dabda4e..0758af1 100644
--- a/lib/polygon/cw_polygon.dart
+++ b/lib/polygon/cw_polygon.dart
@@ -199,27 +199,23 @@ class CWPolygon extends Polygon {
);
@override
- void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
- ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
- connection,
- wallet.walletInfo.derivationInfo?.derivationPath,
- );
+ void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
+ if (service is EVMChainLedgerService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
+ service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
+ } else if (service is EVMChainBitboxService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
+ .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
+ }
}
@override
- Future<List<HardwareAccountData>> getHardwareWalletAccounts(
- LedgerViewModel ledgerVM, {
- int index = 0,
- int limit = 5,
- }) async {
- final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
- try {
- return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
- } catch (err) {
- printV(err);
- throw err;
- }
- }
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
+ EVMChainLedgerService(connection);
+
+ @override
+ HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
+ EVMChainBitboxService(manager, chainId: 137);
@override
List<String> getDefaultTokenContractAddresses() =>
diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart
index 16e8421..b95bb7e 100644
--- a/lib/reactions/on_authentication_state_change.dart
+++ b/lib/reactions/on_authentication_state_change.dart
@@ -14,6 +14,8 @@ import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'package:mobx/mobx.dart';
@@ -60,8 +62,10 @@ void startAuthenticationStateChange(
(route) => false,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
+ hardwareWalletType: HardwareWalletType.ledger,
onConnectDevice: (context, ledgerVM) async {
- monero!.setGlobalLedgerConnection(ledgerVM.connection);
+ if (ledgerVM is LedgerViewModel)
+ monero!.setGlobalLedgerConnection(ledgerVM.connection);
showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
diff --git a/lib/router.dart b/lib/router.dart
index ad9c1fa..08ff50b 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -23,6 +23,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
+import 'package:cake_wallet/src/screens/connect_device/select_device_manufacturer_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@@ -39,6 +40,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
+import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
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
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@@ -213,7 +216,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
arguments: NewWalletArguments(type: type),
),
isCreate: true,
- isHardwareWallet: false,
),
),
);
@@ -245,12 +247,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.chooseHardwareWalletAccount:
final arguments = settings.arguments as List<dynamic>;
final type = arguments[0] as WalletType;
- final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
+ final hardwareWallet = arguments [1] as HardwareWalletType;
+
+ final walletVM = getIt.get<WalletHardwareRestoreViewModel>(
+ param1: type, param2: getIt<HardwareWalletViewModel>(param1: hardwareWallet));
if (type == WalletType.monero)
- return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
+ return handleRouteWithPlatformAwareness((_) => MoneroHardwareWalletOptionsPage(walletVM));
- return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
+ return handleRouteWithPlatformAwareness((_) => SelectHardwareWalletAccountPage(walletVM));
case Routes.setupPin:
Function(PinCodeState<PinCodeWidget>, String)? callback;
@@ -272,7 +277,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
},
isCreate: false,
- isHardwareWallet: false,
),
),
);
@@ -301,41 +305,59 @@ Route<dynamic> createRoute(RouteSettings settings) {
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
},
isCreate: false,
- isHardwareWallet: false,
),
),
);
case Routes.restoreWalletFromHardwareWallet:
+ final arguments = settings.arguments as Map<String, dynamic>?;
+ final showUnavailable = (arguments?['showUnavailable'] as bool?) ?? true;
+ final onSelect = arguments?['onSelect'] as void Function(BuildContext, HardwareWalletType)?;
+ final availableHardwareWalletTypes =
+ arguments?['availableHardwareWalletTypes'] as List<HardwareWalletType>?;
+
+ return handleRouteWithPlatformAwareness((_) => SelectDeviceManufacturerPage(
+ showUnavailable: showUnavailable,
+ onSelect: onSelect,
+ availableHardwareWalletTypes: availableHardwareWalletTypes,
+ ));
+
+ case Routes.connectHardwareWallet:
+ final arguments = settings.arguments as List<dynamic>;
+ final hardwareWalletType = (arguments[0] as HardwareWalletType?) ?? HardwareWalletType.ledger;
+
if (isSingleCoin) {
- return MaterialPageRoute<void>(
- builder: (_) => ConnectDevicePage(
+ return handleRouteWithPlatformAwareness(
+ (_) => ConnectDevicePage(
ConnectDevicePageParams(
walletType: availableWalletTypes.first,
+ hardwareWalletType: hardwareWalletType,
onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
Routes.chooseHardwareWalletAccount,
- arguments: [availableWalletTypes.first]),
+ arguments: [availableWalletTypes.first, hardwareWalletType]),
isReconnect: false,
),
getIt.get<LedgerViewModel>(),
),
);
}
- return CupertinoPageRoute<void>(
- builder: (_) => getIt.get<NewWalletTypePage>(
+ return handleRouteWithPlatformAwareness(
+ (_) => getIt.get<NewWalletTypePage>(
param1: NewWalletTypeArguments(
onTypeSelected: (BuildContext context, WalletType type) {
final arguments = ConnectDevicePageParams(
walletType: type,
- onConnectDevice: (BuildContext context, _) => Navigator.of(context)
- .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]),
+ hardwareWalletType: hardwareWalletType,
+ onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
+ Routes.chooseHardwareWalletAccount,
+ arguments: [type, hardwareWalletType]),
isReconnect: false,
);
Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
},
isCreate: false,
- isHardwareWallet: true,
+ hardwareWalletType: hardwareWalletType,
),
),
);
@@ -347,7 +369,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
onTypeSelected: (BuildContext context, WalletType type) =>
Navigator.of(context).pop(type),
isCreate: false,
- isHardwareWallet: false,
),
),
);
@@ -360,9 +381,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWallet:
final args = settings.arguments as Map<String, dynamic>?;
final walletType = args?['walletType'] as WalletType;
- final restoredWallet = args?['restoredWallet'] as RestoredWallet?;
return MaterialPageRoute<void>(
- builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: restoredWallet));
+ builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: args));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
@@ -873,8 +893,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.connectDevices:
final params = settings.arguments as ConnectDevicePageParams;
+
return MaterialPageRoute<void>(
- builder: (_) => ConnectDevicePage(params, getIt.get<LedgerViewModel>()));
+ builder: (_) => ConnectDevicePage(
+ params, getIt.get<HardwareWalletViewModel>(param1: params.hardwareWalletType)));
case Routes.walletGroupDescription:
final walletType = settings.arguments as WalletType;
@@ -920,6 +942,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<DevSocketHealthLogsPage>(),
);
+ case Routes.devQRTools:
+ return MaterialPageRoute<void>(
+ builder: (_) => getIt.get<DevQRToolsPage>(),
+ );
+
case Routes.devNetworkRequests:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<DevNetworkRequests>(),
diff --git a/lib/routes.dart b/lib/routes.dart
index 4894956..3e18c36 100644
--- a/lib/routes.dart
+++ b/lib/routes.dart
@@ -11,6 +11,7 @@ class Routes {
static const restoreWalletFromHardwareWallet = '/restore/hardware_wallet';
static const restoreWalletTypeFromQR = '/restore_wallet_from_qr_code';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
+ static const connectHardwareWallet = '/restore/hardware_wallet/devices';
static const chooseHardwareWalletAccount = '/restore/hardware_wallet/accounts';
static const dashboard = '/dashboard';
static const send = '/send';
@@ -123,6 +124,7 @@ class Routes {
static const devBackgroundSyncLogs = '/dev/background_sync_logs';
static const devSocketHealthLogs = '/dev/socket_health_logs';
static const devNetworkRequests = '/dev/network_requests';
+ static const devQRTools = '/dev/qr_tools';
static const devExchangeProviderLogs = '/dev/exchange_provider_logs';
static const signPage = '/sign_page';
diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart
index ffa9e54..204e0eb 100644
--- a/lib/src/screens/connect_device/connect_device_page.dart
+++ b/lib/src/screens/connect_device/connect_device_page.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
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
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
-typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
+typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel);
class ConnectDevicePageParams {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
+ final HardwareWalletType hardwareWalletType;
ConnectDevicePageParams({
required this.walletType,
+ required this.hardwareWalletType,
required this.onConnectDevice,
this.allowChangeWallet = false,
this.isReconnect = true,
@@ -37,9 +40,9 @@ class ConnectDevicePage extends BasePage {
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
- final LedgerViewModel ledgerVM;
+ final HardwareWalletViewModel hardwareWalletVM;
- ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
+ ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
: walletType = params.walletType,
onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet,
@@ -51,8 +54,7 @@ class ConnectDevicePage extends BasePage {
: S.current.restore_title_from_hardware_wallet;
@override
- Widget? leading(BuildContext context) =>
- !isReconnect ? super.leading(context) : null;
+ Widget? leading(BuildContext context) => !isReconnect ? super.leading(context) : null;
@override
Widget body(BuildContext context) => PopScope(
@@ -61,7 +63,7 @@ class ConnectDevicePage extends BasePage {
walletType,
onConnectDevice,
allowChangeWallet,
- ledgerVM,
+ hardwareWalletVM,
currentTheme,
));
}
@@ -70,14 +72,14 @@ class ConnectDevicePageBody extends StatefulWidget {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
- final LedgerViewModel ledgerVM;
+ final HardwareWalletViewModel hardwareWalletVM;
final MaterialThemeBase currentTheme;
const ConnectDevicePageBody(
this.walletType,
this.onConnectDevice,
this.allowChangeWallet,
- this.ledgerVM,
+ this.hardwareWalletVM,
this.currentTheme,
);
@@ -86,13 +88,13 @@ class ConnectDevicePageBody extends StatefulWidget {
}
class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
- var bleDevices = <LedgerDevice>[];
- var usbDevices = <LedgerDevice>[];
+ var bleDevices = <HardwareWalletDevice>[];
+ var usbDevices = <HardwareWalletDevice>[];
late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
- late StreamSubscription<LedgerDevice>? _bleRefresh = null;
+ late StreamSubscription<HardwareWalletDevice>? _bleRefresh = null;
bool longWait = false;
Timer? _longWaitTimer;
@@ -101,21 +103,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
- _bleStateTimer = Timer.periodic(
- Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
+ _bleStateTimer =
+ Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState());
- _bleRefreshTimer =
- Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
+ _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
if (Platform.isAndroid) {
- _usbRefreshTimer =
- Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
+ _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
}
- _longWaitTimer = Timer(Duration(seconds: 10), () {
- if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
- setState(() => longWait = true);
- });
+ if (widget.hardwareWalletVM.hasBluetooth) {
+ _longWaitTimer = Timer(Duration(seconds: 10), () {
+ if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty)
+ setState(() => longWait = true);
+ });
+ }
});
}
@@ -127,35 +129,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_bleRefresh?.cancel();
_longWaitTimer?.cancel();
- widget.ledgerVM.stopScanning();
+ widget.hardwareWalletVM.stopScanning();
super.dispose();
}
Future<void> _refreshUsbDevices() async {
- final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
+ final dev = await widget.hardwareWalletVM.getAllUsbDevices();
if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
- // _usbRefresh = widget.ledgerVM
- // .scanForUsbDevices()
- // .listen((device) => setState(() => usbDevices.add(device)))
- // ..onError((e) {
- // throw e.toString();
- // });
- // Keep polling until the lfp lib gets updated
- // _usbRefreshTimer?.cancel();
- // _usbRefreshTimer = null;
}
Future<void> _refreshBleDevices() async {
try {
- if (widget.ledgerVM.bleIsEnabled) {
- _bleRefresh =
- widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
- bleDevices.add(device);
- if (longWait) longWait = false;
- }))
- ..onError((e) {
- throw e.toString();
- });
+ if (widget.hardwareWalletVM.isBleEnabled) {
+ _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() {
+ bleDevices.add(device);
+ if (longWait) longWait = false;
+ }))
+ ..onError((e) {
+ throw e.toString();
+ });
_bleRefreshTimer?.cancel();
_bleRefreshTimer = null;
}
@@ -164,25 +156,35 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
}
}
- Future<void> _connectToDevice(LedgerDevice device) async {
- final isConnected =
- await widget.ledgerVM.connectLedger(device, widget.walletType);
- if (isConnected) widget.onConnectDevice(context, widget.ledgerVM);
+ Future<void> _connectToDevice(HardwareWalletDevice device) async {
+ final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
+ if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
}
- String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
- switch (deviceInfo) {
- case LedgerDeviceType.nanoX:
+ String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
+ switch (deviceType) {
+ case HardwareWalletDeviceType.ledgerNanoX:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
- case LedgerDeviceType.stax:
+ case HardwareWalletDeviceType.ledgerStax:
return 'assets/images/hardware_wallet/ledger_stax.png';
- case LedgerDeviceType.flex:
+ case HardwareWalletDeviceType.ledgerFlex:
return 'assets/images/hardware_wallet/ledger_flex.png';
+ case HardwareWalletDeviceType.BitBox02:
+ return 'assets/images/hardware_wallet/device_bitbox.svg';
+
default:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
}
}
+ String get description {
+ if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble;
+
+ if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet;
+
+ return S.of(context).connect_your_hardware_wallet_usb;
+ }
+
@override
Widget build(BuildContext context) {
return Container(
@@ -197,11 +199,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
- Platform.isAndroid
- ? S.of(context).connect_your_hardware_wallet
- : S.of(context).connect_your_hardware_wallet_ios,
- style: Theme.of(context)
- .textTheme.titleMedium,
+ description,
+ style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
@@ -211,22 +210,20 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).if_you_dont_see_your_device,
- style: Theme.of(context)
- .textTheme.titleMedium,
+ style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
),
Observer(
builder: (_) => Offstage(
- offstage: widget.ledgerVM.bleIsEnabled,
+ offstage: widget.hardwareWalletVM.isBleEnabled ||
+ !widget.hardwareWalletVM.hasBluetooth,
child: Padding(
- padding:
- EdgeInsets.only(left: 20, right: 20, bottom: 20),
+ padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
- style: Theme.of(context)
- .textTheme.titleMedium,
+ style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
@@ -239,8 +236,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
width: double.infinity,
child: Text(
S.of(context).bluetooth,
- style: Theme.of(context)
- .textTheme.bodyMedium,
+ style: Theme.of(context).textTheme.bodyMedium,
),
),
),
@@ -251,7 +247,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
- leading: _getDeviceTileLeading(device.deviceInfo),
+ leading: _getDeviceTileLeading(device.type),
connectionType: device.connectionType,
),
),
@@ -265,8 +261,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
width: double.infinity,
child: Text(
S.of(context).usb,
- style: Theme.of(context)
- .textTheme.bodyMedium,
+ style: Theme.of(context).textTheme.bodyMedium,
),
),
),
@@ -277,7 +272,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
- leading: _getDeviceTileLeading(device.deviceInfo),
+ leading: _getDeviceTileLeading(device.type),
connectionType: device.connectionType,
),
),
@@ -287,10 +282,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
- color: Theme.of(context)
- .colorScheme.primary,
- textColor: Theme.of(context)
- .colorScheme.onPrimary,
+ color: Theme.of(context).colorScheme.primary,
+ textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: _onChangeWallet,
)
],
@@ -312,8 +305,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void _onChangeWallet() {
Navigator.of(context).pushNamed(
Routes.walletList,
- arguments: (BuildContext context) => Navigator.of(context)
- .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
+ arguments: (BuildContext context) =>
+ Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
);
}
diff --git a/lib/src/screens/connect_device/select_device_manufacturer_page.dart b/lib/src/screens/connect_device/select_device_manufacturer_page.dart
new file mode 100644
index 0000000..b1192ac
--- /dev/null
+++ b/lib/src/screens/connect_device/select_device_manufacturer_page.dart
@@ -0,0 +1,183 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/routes.dart';
+import 'package:cake_wallet/src/screens/base_page.dart';
+import 'package:cake_wallet/src/screens/connect_device/widgets/dotted_divider.dart';
+import 'package:cake_wallet/src/screens/connect_device/widgets/manufacturer_option_tile.dart';
+import 'package:cake_wallet/utils/permission_handler.dart';
+import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class SelectDeviceManufacturerPage extends BasePage {
+ SelectDeviceManufacturerPage({
+ this.showUnavailable = true,
+ this.onSelect,
+ this.availableHardwareWalletTypes,
+ });
+
+ final bool showUnavailable;
+ final void Function(BuildContext, HardwareWalletType)? onSelect;
+ final List<HardwareWalletType>? availableHardwareWalletTypes;
+
+ @override
+ String get title => S.current.select_manufacturer_title;
+
+ @override
+ AppBarStyle get appBarStyle => AppBarStyle.regular;
+
+ List<_DeviceManufacturer> get availableManufacturers => [
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
+ hardwareWalletType: HardwareWalletType.ledger,
+ ),
+ // _DeviceManufacturer(
+ // image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
+ // hardwareWalletType: HardwareWalletType.bitbox,
+ // tag: S.current.new_tag,
+ // ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
+ hardwareWalletType: HardwareWalletType.cupcake,
+ tag: S.current.new_tag,
+ ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
+ hardwareWalletType: HardwareWalletType.coldcard,
+ tag: S.current.new_tag,
+ ),
+ // _DeviceManufacturer(
+ // image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
+ // hardwareWalletType: HardwareWalletType.seedsigner,
+ // tag: S.current.new_tag,
+ // ),
+ ]
+ .where((e) => availableHardwareWalletTypes == null
+ ? ![HardwareWalletType.cupcake].contains(e.hardwareWalletType)
+ : availableHardwareWalletTypes!.contains(e.hardwareWalletType))
+ .toList();
+
+ List<_DeviceManufacturer> get comingManufacturers => [
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
+ tag: S.current.coming_soon_tag,
+ ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
+ hardwareWalletType: HardwareWalletType.bitbox,
+ tag: S.current.coming_soon_tag,
+ ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
+ hardwareWalletType: HardwareWalletType.seedsigner,
+ tag: S.current.coming_soon_tag,
+ ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
+ tag: S.current.coming_soon_tag,
+ ),
+ _DeviceManufacturer(
+ image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
+ tag: S.current.coming_soon_tag,
+ ),
+ ];
+
+ @override
+ Widget body(BuildContext context) => Container(
+ child: Center(
+ child: Container(
+ padding: EdgeInsets.only(left: 24, right: 24),
+ child: Column(
+ children: [
+ ...availableManufacturers.map(
+ (manufacturer) => Padding(
+ padding: EdgeInsets.only(top: 10),
+ child: ManufacturerOptionTile(
+ image: manufacturer.image,
+ tag: manufacturer.tag,
+ onPressed: () {
+ if (onSelect != null)
+ return onSelect!.call(context, manufacturer.hardwareWalletType!);
+
+ if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
+ _onScanQRCode(context, manufacturer.hardwareWalletType!);
+ } else if (manufacturer.hardwareWalletType != null) {
+ Navigator.pushNamed(context, Routes.connectHardwareWallet,
+ arguments: [manufacturer.hardwareWalletType]);
+ }
+ },
+ isDarkTheme: currentTheme.isDark,
+ ),
+ ),
+ ),
+ if (showUnavailable) ...[
+ Padding(
+ padding: EdgeInsets.only(top: 20, bottom: 10),
+ child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
+ ),
+ ...comingManufacturers.map(
+ (manufacturer) => Padding(
+ padding: EdgeInsets.only(top: 10),
+ child: ManufacturerOptionTile(
+ image: manufacturer.image,
+ tag: manufacturer.tag,
+ onPressed: () =>
+ Fluttertoast.showToast(msg: 'One more tap and it might work'),
+ // Ester egg
+ isDarkTheme: currentTheme.isDark,
+ isUnavailable: true,
+ ),
+ ),
+ ),
+ ]
+ ],
+ ),
+ ),
+ ),
+ );
+
+ bool isAirgappedWallet(HardwareWalletType? type) => [
+ HardwareWalletType.cupcake,
+ HardwareWalletType.coldcard,
+ HardwareWalletType.seedsigner,
+ HardwareWalletType.keystone,
+ ].contains(type);
+
+ bool isRestoring = false;
+
+ Future<void> _onScanQRCode(BuildContext context, HardwareWalletType type) async {
+ final isCameraPermissionGranted =
+ await PermissionHandler.checkPermission(Permission.camera, context);
+
+ if (!isCameraPermissionGranted) return;
+ try {
+ if (isRestoring) return;
+
+ isRestoring = true;
+
+ final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
+
+ final params = {
+ 'walletType': restoredWallet.type,
+ 'restoredWallet': restoredWallet,
+ 'hardwareWalletType': type,
+ };
+
+ Navigator.pushNamed(context, Routes.restoreWallet, arguments: params)
+ .then((_) => isRestoring = false);
+ } catch (e) {
+ printV(e.toString());
+ }
+ }
+}
+
+class _DeviceManufacturer {
+ final SvgPicture image;
+ final HardwareWalletType? hardwareWalletType;
+ final String? tag;
+
+ _DeviceManufacturer({required this.image, this.hardwareWalletType, this.tag});
+}
diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart
index 7808570..803a648 100644
--- a/lib/src/screens/connect_device/widgets/device_tile.dart
+++ b/lib/src/screens/connect_device/widgets/device_tile.dart
@@ -1,5 +1,6 @@
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:flutter/material.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
+import 'package:flutter_svg/flutter_svg.dart';
class DeviceTile extends StatelessWidget {
const DeviceTile({
@@ -12,15 +13,15 @@ class DeviceTile extends StatelessWidget {
final VoidCallback onPressed;
final String title;
final String? leading;
- final ConnectionType? connectionType;
+ final HardwareWalletConnectionType? connectionType;
String? get connectionTypeIcon {
switch (connectionType) {
- case ConnectionType.ble:
+ case HardwareWalletConnectionType.ble:
return 'assets/images/bluetooth.png';
- case ConnectionType.usb:
+ case HardwareWalletConnectionType.usb:
return 'assets/images/usb.png';
- case null:
+ default:
return null;
}
}
@@ -42,12 +43,14 @@ class DeviceTile extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
- if (leading != null)
+ if (leading != null && !leading!.endsWith(".svg"))
Image.asset(
leading!,
height: 30,
color: Theme.of(context).colorScheme.onSurface,
),
+ if (leading != null && leading!.endsWith(".svg"))
+ SvgPicture.asset(leading!, height: 30),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
diff --git a/lib/src/screens/connect_device/widgets/dotted_divider.dart b/lib/src/screens/connect_device/widgets/dotted_divider.dart
new file mode 100644
index 0000000..aa783cb
--- /dev/null
+++ b/lib/src/screens/connect_device/widgets/dotted_divider.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/material.dart';
+
+class DottedDivider extends StatelessWidget {
+ final Color color;
+
+ const DottedDivider({super.key, required this.color});
+
+ @override
+ Widget build(BuildContext context) => SizedBox(
+ width: double.infinity,
+ child: CustomPaint(
+ painter: _DashedLinePainter(color: color),
+ ),
+ );
+}
+
+class _DashedLinePainter extends CustomPainter {
+ final Color color;
+
+ const _DashedLinePainter({required this.color});
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ double dashWidth = 9, dashSpace = 5, startX = 0;
+ final paint = Paint()
+ ..color = color
+ ..strokeWidth = 1;
+ while (startX < size.width) {
+ canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
+ startX += dashWidth + dashSpace;
+ }
+ }
+
+ @override
+ bool shouldRepaint(CustomPainter oldDelegate) => false;
+}
diff --git a/lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart b/lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart
new file mode 100644
index 0000000..6e49ae9
--- /dev/null
+++ b/lib/src/screens/connect_device/widgets/manufacturer_option_tile.dart
@@ -0,0 +1,80 @@
+import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
+import 'package:flutter/material.dart';
+
+class ManufacturerOptionTile extends StatelessWidget {
+ const ManufacturerOptionTile({
+ required this.onPressed,
+ required this.image,
+ required this.isDarkTheme,
+ this.tag,
+ this.isUnavailable = false,
+ });
+
+ final VoidCallback onPressed;
+ final Widget image;
+ final String? tag;
+ final bool isUnavailable;
+ final bool isDarkTheme;
+
+ @override
+ Widget build(BuildContext context) => GestureDetector(
+ onTap: onPressed,
+ child: Opacity(
+ opacity: isUnavailable ? 0.5 : 1,
+ child: Container(
+ width: double.infinity,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ gradient: LinearGradient(
+ colors: [
+ isDarkTheme
+ ? CustomThemeColors.cardGradientColorPrimaryDark
+ : CustomThemeColors.cardGradientColorPrimaryLight,
+ isDarkTheme
+ ? CustomThemeColors.cardGradientColorSecondaryDark
+ : CustomThemeColors.cardGradientColorSecondaryLight,
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ padding: EdgeInsets.all(24),
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ Expanded(
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: <Widget>[
+ Row(
+ children: [
+ Container(height: 25, child: image),
+ if (tag != null)
+ Container(
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.transparent),
+ borderRadius: BorderRadius.circular(20),
+ color: Theme.of(context).colorScheme.surfaceContainerLow),
+ padding: EdgeInsets.symmetric(horizontal: 5),
+ margin: EdgeInsets.only(left: 5),
+ child: Text(tag!))
+ ],
+ ),
+ ],
+ ),
+ ),
+ Icon(
+ Icons.arrow_forward_ios,
+ size: 16,
+ color: Theme.of(context).colorScheme.onSurface,
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+}
diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
index ee37765..fc526aa 100644
--- a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
@@ -12,6 +12,7 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -27,6 +28,23 @@ class CryptoBalanceWidget extends StatelessWidget {
final btcLockLight = 'assets/images/btc_lock_light.png';
final btcLockDark = 'assets/images/btc_lock_dark.png';
+ String? get hardwareWalletIcon {
+ switch (dashboardViewModel.wallet.hardwareWalletType) {
+ case null:
+ return null;
+ case HardwareWalletType.ledger:
+ return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
+ case HardwareWalletType.bitbox:
+ return "assets/images/hardware_wallet/device_bitbox.svg";
+ case HardwareWalletType.cupcake:
+ return "assets/images/cupcake.svg";
+ case HardwareWalletType.coldcard:
+ case HardwareWalletType.seedsigner:
+ case HardwareWalletType.keystone:
+ return "assets/images/hardware_wallet/device_qr.svg";
+ }
+ }
+
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
@@ -86,10 +104,10 @@ class CryptoBalanceWidget extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.center,
),
- if (dashboardViewModel.wallet.isHardwareWallet)
+ if (hardwareWalletIcon != null)
Container(
- child: Image.asset(
- 'assets/images/hardware_wallet/ledger_nano_x.png',
+ child: SvgPicture.asset(
+ hardwareWalletIcon!,
width: 24,
color: Theme.of(context).colorScheme.onSurface,
),
diff --git a/lib/src/screens/dev/qr_tools_page.dart b/lib/src/screens/dev/qr_tools_page.dart
new file mode 100644
index 0000000..67ba724
--- /dev/null
+++ b/lib/src/screens/dev/qr_tools_page.dart
@@ -0,0 +1,39 @@
+import 'package:cake_wallet/src/screens/base_page.dart';
+import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
+import 'package:cake_wallet/view_model/dev/qr_tools_view_model.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_mobx/flutter_mobx.dart';
+
+class DevQRToolsPage extends BasePage {
+ @override
+ String? get title => "[dev] *QR tools";
+
+ final QRToolsViewModel viewModel = QRToolsViewModel();
+
+ late final textCtrl = TextEditingController(text: viewModel.input);
+
+ @override
+ Widget body(BuildContext context) {
+ return Container(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ TextField(
+ controller: textCtrl,
+ maxLines: 8,
+ onChanged: (value) {
+ viewModel.input = value;
+ }
+ ),
+ Observer(
+ builder: (_) {
+ return Expanded(
+ child: JsonExplorer(data: viewModel.data, title: "result"),
+ );
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart
index dbdcd91..2e58032 100644
--- a/lib/src/screens/new_wallet/new_wallet_type_page.dart
+++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart
@@ -1,7 +1,7 @@
-import 'package:cake_wallet/core/new_wallet_arguments.dart';
-import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
import 'dart:io';
+import 'package:cake_wallet/core/new_wallet_arguments.dart';
+import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/routes.dart';
@@ -11,12 +11,12 @@ import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_aler
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
-import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -51,7 +51,7 @@ class NewWalletTypePage extends BasePage {
isCreate: newWalletTypeArguments.isCreate,
newWalletTypeViewModel: newWalletTypeViewModel,
onTypeSelected: newWalletTypeArguments.onTypeSelected,
- isHardwareWallet: newWalletTypeArguments.isHardwareWallet,
+ hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
);
}
@@ -61,14 +61,16 @@ class WalletTypeForm extends StatefulWidget {
required this.isCreate,
required this.newWalletTypeViewModel,
this.onTypeSelected,
- required this.isHardwareWallet,
+ this.hardwareWalletType,
});
final bool isCreate;
final Image walletImage;
final NewWalletTypeViewModel newWalletTypeViewModel;
final void Function(BuildContext, WalletType)? onTypeSelected;
- final bool isHardwareWallet;
+ final HardwareWalletType? hardwareWalletType;
+
+ bool get isHardwareWallet => hardwareWalletType != null;
@override
WalletTypeFormState createState() => WalletTypeFormState();
@@ -90,7 +92,9 @@ class WalletTypeFormState extends State<WalletTypeForm> {
types = filteredTypes = availableWalletTypes
.where((element) =>
!widget.isHardwareWallet ||
- DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty)
+ DeviceConnectionType.supportedConnectionTypes(
+ element, widget.hardwareWalletType!, Platform.isIOS)
+ .isNotEmpty)
.toList();
super.initState();
@@ -148,7 +152,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
isSelected: selected == type,
onTap: () => setState(() => selected = type),
deviceConnectionTypes: widget.isHardwareWallet
- ? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS)
+ ? DeviceConnectionType.supportedConnectionTypes(
+ type, widget.hardwareWalletType!, Platform.isIOS)
: [],
),
),
diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart
index fcb34df..b44f30a 100644
--- a/lib/src/screens/receive/widgets/qr_widget.dart
+++ b/lib/src/screens/receive/widgets/qr_widget.dart
@@ -111,7 +111,7 @@ class QRWidget extends StatelessWidget {
),
if (addressListViewModel.isPayjoinUnavailable &&
!addressListViewModel.isSilentPayments &&
- !addressListViewModel.isCupcake) ...[
+ !addressListViewModel.isBitcoinViewOnly) ...[
GestureDetector(
onTap: () => _onPayjoinInactivePressed(context),
child: Row(
diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart
index ea1bff6..63a9c60 100644
--- a/lib/src/screens/restore/restore_options_page.dart
+++ b/lib/src/screens/restore/restore_options_page.dart
@@ -1,10 +1,9 @@
-import 'dart:io';
-
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/option_tile.dart';
+import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
@@ -12,11 +11,9 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
-import 'package:cw_core/hardware/device_connection_type.dart';
-import 'package:cw_core/wallet_type.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
-import 'package:cake_wallet/themes/core/material_base_theme.dart';
class RestoreOptionsPage extends BasePage {
RestoreOptionsPage({required this.isNewInstall});
@@ -27,9 +24,8 @@ class RestoreOptionsPage extends BasePage {
final bool isNewInstall;
@override
- Widget body(BuildContext context) {
- return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
- }
+ Widget body(BuildContext context) =>
+ _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
}
class _RestoreOptionsBody extends StatefulWidget {
@@ -45,41 +41,25 @@ class _RestoreOptionsBody extends StatefulWidget {
class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
bool isRestoring = false;
- bool get _doesSupportHardwareWallets {
- if (isMoneroOnly) {
- return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
- .isNotEmpty;
- }
-
- return true;
- }
-
- String get imageRestoreHWPath =>
- widget.themeType == ThemeType.dark
- ? 'assets/images/restore_hw_dark.png'
- : 'assets/images/restore_hw.png';
+ String get imageRestoreHWPath => widget.themeType == ThemeType.dark
+ ? 'assets/images/restore_hw_dark.png'
+ : 'assets/images/restore_hw.png';
- String get imageRestoreCupcakePath =>
- widget.themeType == ThemeType.dark
- ? 'assets/images/restore_cupcake_dark.png'
- : 'assets/images/restore_cupcake.png';
+ String get imageRestoreCupcakePath => widget.themeType == ThemeType.dark
+ ? 'assets/images/restore_cupcake_dark.png'
+ : 'assets/images/restore_cupcake.png';
- String get imageRestoreQRPath =>
- widget.themeType == ThemeType.dark
- ? 'assets/images/restore_qr_dark.png'
- : 'assets/images/restore_qr.png';
+ String get imageRestoreQRPath => widget.themeType == ThemeType.dark
+ ? 'assets/images/restore_qr_dark.png'
+ : 'assets/images/restore_qr.png';
+ String get imageRestoreHotWalletPath => widget.themeType == ThemeType.dark
+ ? 'assets/images/restore_hot_wallet_dark.png'
+ : 'assets/images/restore_hot_wallet.png';
- String get imageRestoreHotWalletPath =>
- widget.themeType == ThemeType.dark
- ? 'assets/images/restore_hot_wallet_dark.png'
- : 'assets/images/restore_hot_wallet.png';
-
-
- String get imageRestoreBackupPath =>
- widget.themeType == ThemeType.dark
- ? 'assets/images/restore_backup_dark.png'
- : 'assets/images/restore_backup.png';
+ String get imageRestoreBackupPath => widget.themeType == ThemeType.dark
+ ? 'assets/images/restore_backup_dark.png'
+ : 'assets/images/restore_backup.png';
@override
Widget build(BuildContext context) {
@@ -116,18 +96,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
tag: S.of(context).new_tag,
),
),
- if (_doesSupportHardwareWallets)
- Padding(
- padding: EdgeInsets.only(top: 24),
- child: OptionTile(
- key: ValueKey('restore_options_from_hardware_wallet_button_key'),
- onPressed: () =>
- Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet),
- image: imageRestoreHW,
- title: S.of(context).restore_title_from_hardware_wallet,
- description: S.of(context).restore_description_from_hardware_wallet,
- ),
+ Padding(
+ padding: EdgeInsets.only(top: 24),
+ child: OptionTile(
+ key: ValueKey('restore_options_from_hardware_wallet_button_key'),
+ onPressed: () => Navigator.pushNamed(
+ context,
+ isSingleCoin
+ ? Routes.connectHardwareWallet
+ : Routes.restoreWalletFromHardwareWallet),
+ image: imageRestoreHW,
+ title: S.of(context).restore_title_from_hardware_wallet,
+ description: S.of(context).restore_description_from_hardware_wallet,
),
+ ),
if (widget.isNewInstall)
Padding(
padding: EdgeInsets.only(top: 24),
@@ -143,11 +125,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
- key: ValueKey('restore_options_from_qr_button_key'),
- onPressed: () => _onScanQRCode(context),
- image: imageRestoreQR,
- title: S.of(context).scan_qr_code,
- description: S.of(context).cold_or_recover_wallet,
+ key: ValueKey('restore_options_from_qr_button_key'),
+ onPressed: () => _onScanQRCode(context),
+ image: imageRestoreQR,
+ title: S.of(context).scan_qr_code,
+ description: S.of(context).cold_or_recover_wallet,
),
),
],
@@ -184,7 +166,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
- final params = {'walletType': restoredWallet.type, 'restoredWallet': restoredWallet};
+ final params = {
+ 'walletType': restoredWallet.type,
+ 'restoredWallet': restoredWallet,
+ 'hardwareWalletType': HardwareWalletType.cupcake,
+ };
Navigator.pushNamed(context, Routes.restoreWallet, arguments: params).then((_) {
if (mounted) setState(() => isRestoring = false);
diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart
index ef4d626..2915415 100644
--- a/lib/src/screens/send/send_page.dart
+++ b/lib/src/screens/send/send_page.dart
@@ -440,18 +440,21 @@ class SendPage extends BasePage {
}
if (sendViewModel.wallet.isHardwareWallet) {
- if (!sendViewModel.ledgerViewModel!.isConnected) {
+ if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
+ hardwareWalletType:
+ sendViewModel.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (BuildContext context, _) {
- sendViewModel.ledgerViewModel!
- .setLedger(sendViewModel.wallet);
+ sendViewModel.hardwareWalletViewModel!
+ .initWallet(sendViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
- sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
+ sendViewModel.hardwareWalletViewModel!
+ .initWallet(sendViewModel.wallet);
}
}
diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart
index 9c943d0..0cb3227 100644
--- a/lib/src/screens/settings/other_settings_page.dart
+++ b/lib/src/screens/settings/other_settings_page.dart
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -26,16 +27,14 @@ class OtherSettingsPage extends BasePage {
final OtherSettingsViewModel _otherSettingsViewModel;
@override
- Widget body(BuildContext context) {
- return Observer(
- builder: (_) {
- return Container(
+ Widget body(BuildContext context) => Observer(
+ builder: (_) => Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
if (_otherSettingsViewModel.displayTransactionPriority)
- _otherSettingsViewModel.walletType == WalletType.bitcoin ?
- SettingsPriorityPickerCell(
+ _otherSettingsViewModel.walletType == WalletType.bitcoin
+ ? SettingsPriorityPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
@@ -44,8 +43,8 @@ class OtherSettingsPage extends BasePage {
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
- ) :
- SettingsPickerCell(
+ )
+ : SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
@@ -55,75 +54,96 @@ class OtherSettingsPage extends BasePage {
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
- handler: (BuildContext context) =>
- Navigator.of(context).pushNamed(Routes.changeRep),
+ handler: (context) => Navigator.of(context).pushNamed(Routes.changeRep),
+ ),
+ if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled)
+ SettingsCellWithArrow(
+ title: "Hardware wallet manufacturer",
+ handler: (context) => Navigator.of(context)
+ .pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: {
+ "showUnavailable": false,
+ "availableHardwareWalletTypes": [
+ HardwareWalletType.cupcake,
+ HardwareWalletType.coldcard,
+ HardwareWalletType.seedsigner,
+ ],
+ "onSelect": (BuildContext context, HardwareWalletType hwType) async {
+ await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType);
+ Navigator.pop(context);
+ },
+ }),
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
- handler: (BuildContext context) =>
- Navigator.of(context).pushNamed(Routes.readDisclaimer),
+ handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
- if (FeatureFlag.hasDevOptions && _otherSettingsViewModel.walletType == WalletType.monero)
+ if (FeatureFlag.hasDevOptions &&
+ _otherSettingsViewModel.walletType == WalletType.monero)
SettingsCellWithArrow(
title: '[dev] monero background sync',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync),
),
- if (FeatureFlag.hasDevOptions && [WalletType.monero, WalletType.wownero, WalletType.zano].contains(_otherSettingsViewModel.walletType))
+ if (FeatureFlag.hasDevOptions &&
+ [WalletType.monero, WalletType.wownero, WalletType.zano]
+ .contains(_otherSettingsViewModel.walletType))
SettingsCellWithArrow(
title: '[dev] xmr call profiler',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler),
),
- if (FeatureFlag.hasDevOptions && [WalletType.monero].contains(_otherSettingsViewModel.walletType))
+ if (FeatureFlag.hasDevOptions &&
+ [WalletType.monero].contains(_otherSettingsViewModel.walletType))
SettingsCellWithArrow(
title: '[dev] xmr wallet cache debug',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] shared preferences',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devSharedPreferences),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] secure storage preferences',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devSecurePreferences),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] background sync logs',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] socket health logs',
- handler: (BuildContext context) =>
- Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
+ handler: (context) => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] network requests logs',
- handler: (BuildContext context) =>
- Navigator.of(context).pushNamed(Routes.devNetworkRequests),
+ handler: (context) => Navigator.of(context).pushNamed(Routes.devNetworkRequests),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] exchange provider logs',
- handler: (BuildContext context) =>
+ handler: (context) =>
Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
),
+ if (FeatureFlag.hasDevOptions)
+ SettingsCellWithArrow(
+ title: '[dev] *QR tools',
+ handler: (context) => Navigator.of(context).pushNamed(Routes.devQRTools),
+ ),
Spacer(),
SettingsVersionCell(
- title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
+ title: S.of(context).version(_otherSettingsViewModel.currentVersion),
+ ),
],
),
- );
- },
- );
- }
+ ),
+ );
}
diff --git a/lib/src/screens/ur/animated_ur_page.dart b/lib/src/screens/ur/animated_ur_page.dart
index 0ee5e02..ea23834 100644
--- a/lib/src/screens/ur/animated_ur_page.dart
+++ b/lib/src/screens/ur/animated_ur_page.dart
@@ -61,6 +61,7 @@ class AnimatedURPage extends BasePage {
child: URQR(
urqr: urQr,
walletType: animatedURmodel.wallet.type,
+ hardwareWalletType: animatedURmodel.wallet.hardwareWalletType,
),
),
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header]
@@ -125,4 +126,4 @@ class AnimatedURPage extends BasePage {
});
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/src/screens/ur/widgets/urqr.dart b/lib/src/screens/ur/widgets/urqr.dart
index b268152..7649933 100644
--- a/lib/src/screens/ur/widgets/urqr.dart
+++ b/lib/src/screens/ur/widgets/urqr.dart
@@ -1,4 +1,3 @@
-
import 'dart:async';
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';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
class URQR extends StatefulWidget {
- URQR({super.key, required this.urqr, required this.walletType});
+ const URQR({super.key, required this.urqr, required this.walletType, this.hardwareWalletType});
final Map<String, String> urqr;
final WalletType walletType;
+ final HardwareWalletType? hardwareWalletType;
+
@override
- // ignore: library_private_types_in_public_api
_URQRState createState() => _URQRState();
}
const urFrameTime = 1000 ~/ 5;
class _URQRState extends State<URQR> {
- Timer? t;
+ Timer? _timer;
int frame = 0;
+ int selectedInt = 0;
+
@override
void initState() {
- super.initState();
- setState(() {
- t = Timer.periodic(const Duration(milliseconds: urFrameTime), (timer) {
- _nextFrame();
- });
- });
- }
+ final keys = widget.urqr.keys.toList();
+ if (widget.hardwareWalletType == HardwareWalletType.coldcard) {
+ selectedInt = 1;
+ selected = keys[(selectedInt) % keys.length];
+ } else if (widget.hardwareWalletType == HardwareWalletType.seedsigner) {
+ selectedInt = 2;
+ selected = keys[(selectedInt) % keys.length];
+ }
- void _nextFrame() {
- setState(() {
- frame++;
- });
+ _timer = Timer.periodic(const Duration(milliseconds: urFrameTime), (_) => _nextFrame());
+ super.initState();
}
@override
void dispose() {
- t?.cancel();
+ _timer?.cancel();
super.dispose();
}
- late String selected =
- (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
- int selectedInt = 0;
+ String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
+ void next() => setState(() {
+ final keys = widget.urqr.keys.toList();
- List<String> get frames {
- return widget.urqr[selected]?.split("\n") ?? [];
- }
+ selectedInt++;
+ selected = keys[(selectedInt) % keys.length];
+ });
- late String nextLabel =
- widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
+ late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
+
+
+ List<String> get frames => widget.urqr[selected]?.split("\n") ?? [];
+ void _nextFrame() => setState(() => frame++);
- void next() {
- final keys = widget.urqr.keys.toList();
- setState(() {
- selectedInt++;
- nextLabel = keys[(selectedInt + 1) % keys.length];
- selected = keys[(selectedInt) % keys.length];
- });
- }
@override
Widget build(BuildContext context) {
@@ -107,45 +104,44 @@ All frames:
);
}
- Widget _legacySwitch(BuildContext context) {
- return SizedBox(
- width: double.maxFinite,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16.0),
- child: SizedBox(
- width: double.maxFinite,
- child: PrimaryButton(
- onPressed: next,
- text: nextLabel,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
+ Widget _legacySwitch(BuildContext context) => SizedBox(
+ width: double.maxFinite,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: SizedBox(
+ width: double.maxFinite,
+ child: PrimaryButton(
+ onPressed: next,
+ text: nextLabel,
+ color: Theme.of(context).colorScheme.primary,
+ textColor: Theme.of(context).colorScheme.onPrimary,
+ ),
),
),
- ),
- );
- }
+ );
Widget _newSwitch(BuildContext context) {
return Column(
children: [
- SizedBox(height: 32),
+ const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- Icon(Icons.qr_code,
- color: Theme.of(context).colorScheme.primary, size: 24),
- SizedBox(width: 4),
- Text("QR Code Format",
- style: TextStyle(
- fontWeight: FontWeight.bold,
- fontSize: 16,
- color: Theme.of(context).colorScheme.onSurface,
- )),
+ Icon(Icons.qr_code, color: Theme.of(context).colorScheme.primary, size: 24),
+ const SizedBox(width: 4),
+ Text(
+ "QR Code Format",
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ ),
],
),
- SizedBox(height: 16),
+ const SizedBox(height: 16),
Container(
- margin: EdgeInsets.symmetric(horizontal: 16),
+ margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(12),
@@ -176,7 +172,7 @@ All frames:
),
),
),
- SizedBox(width: 16),
+ const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -193,10 +189,7 @@ All frames:
_getCurrentFormatSubtitle(),
style: TextStyle(
fontSize: 14,
- color: Theme.of(context)
- .colorScheme
- .onSurface
- .withOpacity(0.6),
+ color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],
@@ -204,10 +197,7 @@ All frames:
),
Icon(
Icons.chevron_right,
- color: Theme.of(context)
- .colorScheme
- .onSurface
- .withOpacity(0.4),
+ color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
size: 24,
),
],
@@ -216,7 +206,7 @@ All frames:
),
),
),
- SizedBox(height: 16),
+ const SizedBox(height: 16),
GestureDetector(
onTap: () => _showInfoDialog(context),
child: Row(
@@ -226,35 +216,32 @@ All frames:
"What's this for?",
style: TextStyle(
fontSize: 14,
- color:
- Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
+ color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
- SizedBox(width: 4),
+ const SizedBox(width: 4),
Icon(
Icons.info_outline,
size: 16,
- color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
+ color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
],
),
),
- SizedBox(height: 32),
+ const SizedBox(height: 32),
],
);
}
String _getCurrentFormatName() {
if (widget.urqr.isEmpty) return "Unknown";
- final currentKey =
- widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
+ final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
return currentKey.split(' ')[0];
}
String _getCurrentFormatSubtitle() {
if (widget.urqr.isEmpty) return "";
- final currentKey =
- widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
+ final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
return currentKey.substring(currentKey.indexOf(' ') + 1);
}
@@ -264,30 +251,25 @@ All frames:
final keys = widget.urqr.keys.toList();
await showPopUp<void>(
- context: context,
- builder: (BuildContext context) {
- return QRFormatSelectionDialog(
- formats: keys,
- currentIndex: selectedInt,
- onFormatSelected: (index) {
- Navigator.pop(context);
- setState(() {
- selectedInt = index;
- selected = keys[index];
- nextLabel = keys[(index + 1) % keys.length];
- });
- },
- );
- });
+ context: context,
+ builder: (context) => QRFormatSelectionDialog(
+ formats: keys,
+ currentIndex: selectedInt,
+ onFormatSelected: (index) {
+ Navigator.pop(context);
+ setState(() {
+ selectedInt = index;
+ selected = keys[index];
+ });
+ },
+ ),
+ );
}
- Future<void> _showInfoDialog(BuildContext context) async {
- await showModalBottomSheet(
+ Future<void> _showInfoDialog(BuildContext context) => showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
- builder: (BuildContext context) {
- return QRFormatInfoBottomSheet();
- });
- }
+ builder: (_) => QRFormatInfoBottomSheet(),
+ );
}
diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart
index b2f3bc6..d8f20d2 100644
--- a/lib/src/screens/wallet_list/wallet_list_page.dart
+++ b/lib/src/screens/wallet_list/wallet_list_page.dart
@@ -21,9 +21,11 @@ import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -477,8 +479,9 @@ class WalletListBodyState extends State<WalletListBody> {
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
+ hardwareWalletType: HardwareWalletType.ledger,
onConnectDevice: (context, ledgerVM) async {
- monero!.setGlobalLedgerConnection(ledgerVM.connection);
+ if (ledgerVM is LedgerViewModel) monero!.setGlobalLedgerConnection(ledgerVM.connection);
didConnect = true;
Navigator.of(context).pop();
},
diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart
index 1ae6851..8aab273 100644
--- a/lib/view_model/dashboard/receive_option_view_model.dart
+++ b/lib/view_model/dashboard/receive_option_view_model.dart
@@ -28,7 +28,7 @@ abstract class ReceiveOptionViewModelBase with Store {
break;
case WalletType.litecoin:
_options = [
- ...bitcoin!.getLitecoinReceivePageOptions(),
+ ...bitcoin!.getLitecoinReceivePageOptions(_wallet),
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
];
break;
diff --git a/lib/view_model/dev/qr_tools_view_model.dart b/lib/view_model/dev/qr_tools_view_model.dart
new file mode 100644
index 0000000..76cd285
--- /dev/null
+++ b/lib/view_model/dev/qr_tools_view_model.dart
@@ -0,0 +1,81 @@
+import 'dart:convert';
+
+import 'package:flutter/foundation.dart';
+import 'package:ur/cbor_lite.dart';
+import 'package:ur/ur.dart';
+import 'package:mobx/mobx.dart';
+import 'package:ur/ur_decoder.dart';
+
+part 'qr_tools_view_model.g.dart';
+
+class QRToolsViewModel = QRToolsViewModelBase with _$QRToolsViewModel;
+
+enum DevQrType {
+ zpub,
+ xpub,
+ bbqr,
+ bcur,
+ unknown,
+}
+
+abstract class QRToolsViewModelBase with Store {
+ @observable
+ String input = '';
+
+
+
+ @computed
+ Map<String, dynamic> get data => _getResult(input);
+
+ Uint8List _decodeCBOR(Uint8List cbor) {
+ final cborDecoder = CBORDecoder(cbor);
+ final out = cborDecoder.decodeBytes();
+ return out.$1;
+ }
+
+
+ Map<String, dynamic> _getResult(String input) {
+ try {
+ final decoder = URDecoder();
+ for (var part in input.split('\n')) {
+ part = part.toLowerCase();
+ part = part.trim();
+ if (part.startsWith('-')) {
+ part = part.substring(1).trim();
+ }
+ if (!part.startsWith('ur:')) {
+ continue;
+ }
+ decoder.receivePart(part);
+ }
+ return {
+ "result": (decoder.result != null) ? switch (decoder.result.runtimeType) {
+ UR => {
+ "cbor": (decoder.result as UR).cbor,
+ "_cbor.decode.base64": base64.encode(_decodeCBOR((decoder.result as UR).cbor)),
+ "type": (decoder.result as UR).type,
+ "toString": (decoder.result as UR).toString(),
+ },
+ _ => "unknown type: ${decoder.result.runtimeType}"
+ } : null,
+ "expectedType": decoder.expectedType,
+ "estimatedPercentComplete": decoder.estimatedPercentComplete(),
+ "processedPartsCount": decoder.processedPartsCount(),
+ "expectedPartCount": decoder.expectedPartCount(),
+ "isComplete": decoder.isComplete(),
+ "isFailure": decoder.isFailure(),
+ "isSuccess": decoder.isSuccess(),
+ "lastPartIndexes": decoder.lastPartIndexes(),
+ "receivedPartIndexes": decoder.receivedPartIndexes(),
+ "resultError": decoder.resultError(),
+ "resultMessage": decoder.resultMessage(),
+ "toString": decoder.toString(),
+
+ };
+ } catch (e) {
+ return {
+ "error": e.toString(),
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/view_model/hardware_wallet/bitbox_view_model.dart b/lib/view_model/hardware_wallet/bitbox_view_model.dart
new file mode 100644
index 0000000..20370b5
--- /dev/null
+++ b/lib/view_model/hardware_wallet/bitbox_view_model.dart
@@ -0,0 +1,110 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
+import 'package:cake_wallet/ethereum/ethereum.dart';
+import 'package:cake_wallet/polygon/polygon.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
+import 'package:cake_wallet/wallet_type_utils.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:cw_core/root_dir.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
+import 'package:mobx/mobx.dart';
+
+part 'bitbox_view_model.g.dart';
+
+class BitboxViewModel = BitboxViewModelBase with _$BitboxViewModel;
+
+abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
+ late final sdk.BitboxManager bitboxManager;
+
+ BitboxViewModelBase() {
+ if (!Platform.isIOS && !isMoneroOnly) {
+ bitboxManager = sdk.BitboxManager();
+ }
+ }
+
+ @override
+ @observable
+ bool isBleEnabled = false;
+
+ @override
+ bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth
+
+ @override
+ Future<void> updateBleState() async {}
+
+ @override
+ Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
+
+ @override
+ Future<List<HardwareWalletDevice>> getAllUsbDevices() => bitboxManager.devices
+ .then((devices) => devices.map((d) => BitboxHardwareWalletDevice(d)).toList());
+
+ @override
+ Future<void> stopScanning() async {}
+
+ @override
+ Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
+ if (!(device is BitboxHardwareWalletDevice)) return false;
+ if (_isConnecting) return false;
+ _isConnecting = true;
+
+ try {
+ final appDocDir = await getAppDir();
+
+ await bitboxManager.connect(device.device);
+ printV("Got Connection", file: '${appDocDir.path}/error.txt');
+ await bitboxManager.initBitBox();
+ printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt');
+ await bitboxManager.channelHashVerify();
+ printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt');
+ _isConnecting = false;
+ return true;
+ } catch (e) {
+ printV(e);
+ }
+ _isConnecting = false;
+ return false;
+ }
+
+ bool _isConnecting = false;
+
+ @override
+ bool get isConnected => false;
+
+ @override
+ HardwareWalletService getHardwareWalletService(WalletType type) {
+ switch (type) {
+ case WalletType.bitcoin:
+ return bitcoin!.getBitboxHardwareWalletService(bitboxManager, true);
+ case WalletType.litecoin:
+ return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false);
+ case WalletType.ethereum:
+ return ethereum!.getBitboxHardwareWalletService(bitboxManager);
+ case WalletType.polygon:
+ return polygon!.getBitboxHardwareWalletService(bitboxManager);
+ default:
+ throw UnimplementedError();
+ }
+ }
+
+ @override
+ void initWallet(WalletBase wallet) {
+ switch (wallet.type) {
+ case WalletType.bitcoin:
+ case WalletType.litecoin:
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ case WalletType.ethereum:
+ return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ case WalletType.polygon:
+ return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
+ default:
+ throw Exception('Unexpected wallet type: ${wallet.type}');
+ }
+ }
+}
diff --git a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
new file mode 100644
index 0000000..825090e
--- /dev/null
+++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
@@ -0,0 +1,27 @@
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_type.dart';
+
+abstract class HardwareWalletViewModel {
+ bool get isConnected;
+ bool get isBleEnabled;
+ bool get hasBluetooth;
+
+ Future<void> updateBleState();
+
+ Stream<HardwareWalletDevice> scanForBleDevices();
+
+ Future<List<HardwareWalletDevice>> getAllUsbDevices();
+
+ Future<void> stopScanning();
+
+ Future<bool> connectDevice(HardwareWalletDevice device, WalletType type);
+
+ HardwareWalletService getHardwareWalletService(WalletType type);
+
+ void initWallet(WalletBase wallet);
+
+ String? interpretErrorCode(String error) => null;
+}
+
diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart
index be3982d..7fa0134 100644
--- a/lib/view_model/hardware_wallet/ledger_view_model.dart
+++ b/lib/view_model/hardware_wallet/ledger_view_model.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
@@ -9,10 +10,13 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
@@ -23,15 +27,14 @@ part 'ledger_view_model.g.dart';
class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
-abstract class LedgerViewModelBase with Store {
- // late final Ledger ledger;
+abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
late final sdk.LedgerInterface ledgerPlusBLE;
late final sdk.LedgerInterface ledgerPlusUSB;
bool get _doesSupportHardwareWallets {
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(
- WalletType.monero, Platform.isIOS)
+ WalletType.monero, HardwareWalletType.ledger, Platform.isIOS)
.isNotEmpty;
}
@@ -40,8 +43,8 @@ abstract class LedgerViewModelBase with Store {
LedgerViewModelBase() {
if (_doesSupportHardwareWallets) {
- reaction((_) => bleIsEnabled, (_) {
- if (bleIsEnabled) _initBLE();
+ reaction((_) => isBleEnabled, (_) {
+ if (isBleEnabled) _initBLE();
});
updateBleState();
@@ -51,13 +54,17 @@ abstract class LedgerViewModelBase with Store {
}
}
+ @override
@observable
- bool bleIsEnabled = false;
+ bool isBleEnabled = false;
+
+ @override
+ bool get hasBluetooth => true;
bool _bleIsInitialized = false;
Future<void> _initBLE() async {
- if (bleIsEnabled && !_bleIsInitialized) {
+ if (isBleEnabled && !_bleIsInitialized) {
ledgerPlusBLE = sdk.LedgerInterface.ble(
onPermissionRequest: (_) async {
if (Platform.isMacOS) return true;
@@ -76,24 +83,32 @@ abstract class LedgerViewModelBase with Store {
}
}
+ @override
Future<void> updateBleState() async {
final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
final newState = bleState == sdk.AvailabilityState.poweredOn;
- if (newState != bleIsEnabled) bleIsEnabled = newState;
+ if (newState != isBleEnabled) isBleEnabled = newState;
}
- Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
+ @override
+ Stream<HardwareWalletDevice> scanForBleDevices() =>
+ ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d));
- Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
+ @override
+ Future<List<HardwareWalletDevice>> getAllUsbDevices() => ledgerPlusUSB.devices
+ .then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList());
+ @override
Future<void> stopScanning() async {
if (_bleIsInitialized) await ledgerPlusBLE.stopScanning();
if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
}
- Future<bool> connectLedger(sdk.LedgerDevice device, WalletType type) async {
+ @override
+ Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
+ if (!(device is LedgerHardwareWalletDevice)) return false;
if (_isConnecting) return false;
_isConnecting = true;
_connectingWalletType = type;
@@ -103,18 +118,18 @@ abstract class LedgerViewModelBase with Store {
} catch (_) {}
}
- final ledger = device.connectionType == sdk.ConnectionType.ble
+ final ledger = device.connectionType == HardwareWalletConnectionType.ble
? ledgerPlusBLE
: ledgerPlusUSB;
if (_connectionChangeSubscription == null) {
_connectionChangeSubscription = ledger
- .deviceStateChanges(device.id)
+ .deviceStateChanges(device.device.id)
.listen(_connectionChangeListener);
}
try {
- _connection = await ledger.connect(device);
+ _connection = await ledger.connect(device.device);
_isConnecting = false;
return true;
} catch (e) {
@@ -140,6 +155,7 @@ abstract class LedgerViewModelBase with Store {
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
+ hardwareWalletType: HardwareWalletType.ledger,
allowChangeWallet: true,
isReconnect: true,
onConnectDevice: (context, ledgerVM) async {
@@ -153,26 +169,47 @@ abstract class LedgerViewModelBase with Store {
}
}
+ @override
bool get isConnected => _connection != null && !(_connection!.isDisconnected);
sdk.LedgerConnection get connection => _connection!;
- void setLedger(WalletBase wallet) {
+ @override
+ void initWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.monero:
return monero!.setLedgerConnection(wallet, connection);
case WalletType.bitcoin:
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.litecoin:
- return bitcoin!.setLedgerConnection(wallet, connection);
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.ethereum:
- return ethereum!.setLedgerConnection(wallet, connection);
+ return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.polygon:
- return polygon!.setLedgerConnection(wallet, connection);
+ return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
}
+ @override
+ HardwareWalletService getHardwareWalletService(WalletType type) {
+ switch (type) {
+ case WalletType.bitcoin:
+ return bitcoin!.getLedgerHardwareWalletService(connection, true);
+ case WalletType.litecoin:
+ return bitcoin!.getLedgerHardwareWalletService(connection, false);
+ case WalletType.ethereum:
+ return ethereum!.getLedgerHardwareWalletService(connection);
+ case WalletType.polygon:
+ return polygon!.getLedgerHardwareWalletService(connection);
+ default:
+ throw UnimplementedError();
+ }
+ }
+
+
+ @override
String? interpretErrorCode(String error) {
if (error.contains("Make sure no other program is communicating with the Ledger")) {
return error;
diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart
index 153a996..1ca8dd0 100644
--- a/lib/view_model/restore/restore_wallet.dart
+++ b/lib/view_model/restore/restore_wallet.dart
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
+import 'package:cw_core/utils/zpub.dart';
import 'package:cw_core/wallet_type.dart';
class RestoredWallet {
@@ -44,6 +45,9 @@ class RestoredWallet {
} catch (e) {
// fine, we don't care, it is only for monero anyway
}
+ if (json['zpub'] != null) {
+ json['xpub'] = convertZpubToXpub(json['zpub'] as String);
+ }
json['view_key'] ??= json['xpub'];
final height = json['height'] as String?;
return RestoredWallet(
diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart
index 94b1072..5fe46da 100644
--- a/lib/view_model/restore/wallet_restore_from_qr_code.dart
+++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart
@@ -58,6 +58,7 @@ class WalletRestoreFromQRCode {
final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
if (code.startsWith("xpub")) return WalletType.bitcoin;
+ if (code.startsWith("zpub")) return WalletType.bitcoin;
if (extracted == null) {
// Special case for view-only monero wallet
@@ -109,9 +110,11 @@ class WalletRestoreFromQRCode {
if (code == null) throw Exception("Unexpected scan QR code value: aborted");
if (code.isEmpty) throw Exception('Unexpected scan QR code value: value is empty');
+ if (code.startsWith("[")) code = code.substring(code.indexOf("]") + 1);
+
String formattedUri = '';
WalletType? walletType = _extractWalletType(code);
-
+ final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????';
if (walletType == null) {
await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually");
walletType =
@@ -122,14 +125,14 @@ class WalletRestoreFromQRCode {
formattedUri = seedPhrase != null
? '$walletType:?seed=$seedPhrase'
- : code.startsWith('xpub')
- ? '$walletType:?xpub=$code'
+ : code.startsWith(prefix)
+ ? '$walletType:?$prefix=$code'
: throw Exception('Failed to determine valid seed phrase');
} else {
final index = code.indexOf(':');
final query = code.substring(index + 1).replaceAll('?', '&');
- formattedUri = code.startsWith('xpub')
- ? '$walletType:?xpub=$code'
+ formattedUri = code.startsWith(prefix)
+ ? '$walletType:?$prefix=$code'
:'$walletType:?$query';
}
@@ -167,7 +170,8 @@ class WalletRestoreFromQRCode {
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
}
- if (credentials.containsKey("xpub")) {
+ if (credentials.containsKey("xpub") ||
+ credentials.containsKey("zpub")) {
return WalletRestoreMode.keys;
}
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index 2a956a5..5e4bdae 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -36,6 +36,7 @@ import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
@@ -93,7 +94,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
this.balanceViewModel,
this.contactListViewModel,
this.transactionDescriptionBox,
- this.ledgerViewModel,
+ this.hardwareWalletViewModel,
this.unspentCoinsListViewModel,
this.feesViewModel,
this.walletInfoSource, {
@@ -368,7 +369,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
final SendTemplateViewModel sendTemplateViewModel;
final BalanceViewModel balanceViewModel;
final ContactListViewModel contactListViewModel;
- final LedgerViewModel? ledgerViewModel;
+ final HardwareWalletViewModel? hardwareWalletViewModel;
final FeesViewModel feesViewModel;
final FiatConversionStore _fiatConversationStore;
final Box<TransactionDescription> transactionDescriptionBox;
diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart
index 64265a0..407c039 100644
--- a/lib/view_model/settings/other_settings_view_model.dart
+++ b/lib/view_model/settings/other_settings_view_model.dart
@@ -1,17 +1,15 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
-import 'package:cake_wallet/entities/provider_types.dart';
-import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
-// import 'package:package_info/package_info.dart';
import 'package:collection/collection.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@@ -58,20 +56,28 @@ abstract class OtherSettingsViewModelBase with Store {
@computed
bool get changeRepresentativeEnabled =>
- _wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
+ [WalletType.nano, WalletType.banano].contains(_wallet.type);
@computed
- bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
- _wallet.type == WalletType.solana ||
- _wallet.type == WalletType.tron);
+ bool get changeHardwareWalletTypeEnabled => [
+ HardwareWalletType.cupcake,
+ HardwareWalletType.coldcard,
+ HardwareWalletType.seedsigner,
+ ].contains(_wallet.hardwareWalletType);
+
+ @computed
+ bool get displayTransactionPriority =>
+ !(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type));
String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority;
- if (_wallet.type == WalletType.bitcoin ||
- _wallet.type == WalletType.litecoin ||
- _wallet.type == WalletType.bitcoinCash ||
- _wallet.type == WalletType.dogecoin) {
+ if ([
+ WalletType.bitcoin,
+ WalletType.litecoin,
+ WalletType.bitcoinCash,
+ WalletType.dogecoin,
+ ].contains(_wallet.type)) {
final rate = bitcoin!.getFeeRate(_wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
}
@@ -82,10 +88,12 @@ abstract class OtherSettingsViewModelBase with Store {
String getDisplayBitcoinPriority(dynamic priority, int customValue) {
final _priority = priority as TransactionPriority;
- if (_wallet.type == WalletType.bitcoin ||
- _wallet.type == WalletType.litecoin ||
- _wallet.type == WalletType.bitcoinCash ||
- _wallet.type == WalletType.dogecoin) {
+ if ([
+ WalletType.bitcoin,
+ WalletType.litecoin,
+ WalletType.bitcoinCash,
+ WalletType.dogecoin,
+ ].contains(_wallet.type)) {
final rate = bitcoin!.getFeeRate(_wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate,
customRate: customValue);
@@ -105,6 +113,12 @@ abstract class OtherSettingsViewModelBase with Store {
_settingsStore.priority[_wallet.type] = priority;
}
+ @action
+ Future<void> onHardwareWalletTypeChanged(HardwareWalletType hwType) async {
+ _wallet.walletInfo.hardwareWalletType = hwType;
+ return _wallet.walletInfo.save();
+ }
+
@computed
double get customBitcoinFeeRate =>
_settingsStore.customBitcoinFeeRate.toDouble();
diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
index 5e0525c..49ee117 100644
--- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
@@ -473,7 +473,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
@computed
- bool get isCupcake =>
+ bool get isBitcoinViewOnly =>
wallet.type == WalletType.bitcoin &&
(bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty;
diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart
index 0cfc18c..8e2e2f7 100644
--- a/lib/view_model/wallet_hardware_restore_view_model.dart
+++ b/lib/view_model/wallet_hardware_restore_view_model.dart
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
@@ -24,12 +25,12 @@ class WalletHardwareRestoreViewModel = WalletHardwareRestoreViewModelBase
with _$WalletHardwareRestoreViewModel;
abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with Store {
- final LedgerViewModel ledgerViewModel;
+ final HardwareWalletViewModel hardwareWalletVM;
int _nextIndex = 0;
WalletHardwareRestoreViewModelBase(
- this.ledgerViewModel,
+ this.hardwareWalletVM,
AppStore appStore,
WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource,
@@ -56,33 +57,16 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
@action
Future<void> getNextAvailableAccounts(int limit) async {
try {
- List<HardwareAccountData> accounts;
- switch (type) {
- case WalletType.bitcoin:
- accounts = await bitcoin!
- .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
- break;
- case WalletType.litecoin:
- accounts = await bitcoin!
- .getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
- break;
- case WalletType.ethereum:
- accounts = await ethereum!
- .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
- break;
- case WalletType.polygon:
- accounts = await polygon!
- .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
- break;
- default:
- return;
- }
+ List<HardwareAccountData> accounts = await hardwareWalletVM
+ .getHardwareWalletService(type)
+ .getAvailableAccounts(index: _nextIndex, limit: limit);
availableAccounts.addAll(accounts);
_nextIndex += limit;
} catch (e) {
printV(e);
- error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
+ error =
+ hardwareWalletVM.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
}
isLoadingMoreAccounts = false;
@@ -110,7 +94,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
name: name,
- ledgerConnection: ledgerViewModel.connection,
+ ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
password: password,
height: _options['height'] as int? ?? 0,
);
@@ -118,7 +102,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
throw Exception('Unexpected type: ${type.toString()}');
}
- credentials.hardwareWalletType = HardwareWalletType.ledger;
+ if (hardwareWalletVM is LedgerViewModel)
+ credentials.hardwareWalletType = HardwareWalletType.ledger;
+ else
+ credentials.hardwareWalletType = HardwareWalletType.bitbox;
return credentials;
}
diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart
index 7e73cb8..cd10d14 100644
--- a/lib/view_model/wallet_restore_view_model.dart
+++ b/lib/view_model/wallet_restore_view_model.dart
@@ -33,7 +33,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreVi
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
- {required WalletType type, this.restoredWallet})
+ {required WalletType type, this.restoredWallet, this.hardwareWalletType})
: isButtonEnabled = restoredWallet != null,
hasPassphrase = false,
mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
@@ -102,6 +102,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
].contains(type);
final RestoredWallet? restoredWallet;
+ final HardwareWalletType? hardwareWalletType;
@observable
WalletRestoreMode mode;
@@ -226,6 +227,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
name: name,
password: password,
xpub: viewKey!,
+ hardwareWalletType: hardwareWalletType,
);
case WalletType.monero:
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index 20d6aab..5a6072b 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -116,6 +116,8 @@ dependencies:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
ref: 60817d4b20144f9da9029f5034790272795b9d38
+ bitbox_flutter:
+ path: ./scripts/bitbox_flutter
hashlib: 1.19.2
on_chain:
git:
@@ -186,6 +188,10 @@ dependency_overrides:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
ref: 60817d4b20144f9da9029f5034790272795b9d38
+ ledger_usb_plus:
+ git:
+ url: https://github.com/konstantinullrich/ledger-usb-plus
+ ref: dad482c847c0d0eedceabbb40d3478e64b65e630
socks5_proxy:
git:
url: https://github.com/LacticWhale/socks_dart.git
diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb
index 03c7425..bf05dc5 100644
--- a/res/values/strings_ar.arb
+++ b/res/values/strings_ar.arb
@@ -171,6 +171,7 @@
"collection_description": "وصف المجموعة",
"collection_name": "اسم المجموعة",
"color_theme": "سمة اللون",
+ "coming_soon_tag": "قريباً",
"commit_transaction_amount_fee": "تنفيذ الصفقة\nالمبلغ: ${amount}\nالرسوم: ${fee}",
"confirm": "تأكيد",
"confirm_delete_template": "سيؤدي هذا الإجراء إلى حذف هذا القالب. هل ترغب في الاستمرار؟",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "حدد جهازك في محفظة كعكة ومتابعة الإعداد.",
"connect_yats": "توصيل Yats",
"connect_your_hardware_wallet": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth أو USB",
- "connect_your_hardware_wallet_ios": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth",
+ "connect_your_hardware_wallet_ble": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth",
+ "connect_your_hardware_wallet_usb": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام USB",
"connected": "متصل",
"connection_sync": "الاتصال والمزامنة",
"connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ",
@@ -784,6 +786,7 @@
"select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.",
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
"select_hw_account_below": "الرجاء تحديد حساب الاستعادة أدناه:",
+ "select_manufacturer_title": "حدد الشركة المصنعة",
"select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ",
"select_your_country": "الرجاء تحديد بلدك",
"selected_trocador_provider": "مزود تروكادور المختار",
@@ -1146,4 +1149,4 @@
"youCanGoBackToYourDapp": "يمكنك العودة إلى DAPP الخاص بك الآن",
"your": "لك",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb
index cf54121..b42f18a 100644
--- a/res/values/strings_bg.arb
+++ b/res/values/strings_bg.arb
@@ -171,6 +171,7 @@
"collection_description": "Описание на колекцията",
"collection_name": "Име на колекцията",
"color_theme": "Цвят",
+ "coming_soon_tag": "Очаквайте скоро",
"commit_transaction_amount_fee": "Изпълняване на транзакция\nСума: ${amount}\nТакса: ${fee}",
"confirm": "Потвърждаване",
"confirm_delete_template": "Този шаблон ще бъде изтрит. Искате ли да продължите?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Изберете вашето устройство в портфейла за торта и продължете с настройката.",
"connect_yats": "Добавете Yats",
"connect_your_hardware_wallet": "Свържете хардуерния си портфейл с помощта на Bluetooth или USB",
- "connect_your_hardware_wallet_ios": "Свържете хардуерния си портфейл с помощта на Bluetooth",
+ "connect_your_hardware_wallet_ble": "Свържете хардуерния си портфейл с помощта на Bluetooth",
+ "connect_your_hardware_wallet_usb": "Свържете хардуерния си портфейл с помощта на USB",
"connected": "Свързани",
"connection_sync": "Свързване и синхронизиране",
"connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции",
@@ -784,6 +786,7 @@
"select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.",
"select_destination": "Моля, изберете дестинация за архивния файл.",
"select_hw_account_below": "Моля, изберете кой акаунт да възстановите по -долу:",
+ "select_manufacturer_title": "Изберете производител",
"select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.",
"select_your_country": "Моля, изберете вашата страна",
"selected_trocador_provider": "Избран доставчик на трокадор",
@@ -1146,4 +1149,4 @@
"youCanGoBackToYourDapp": "Можете да се върнете при вашия Dapp сега",
"your": "Вашия",
"yy": "гг"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb
index 54e58dd..996d23c 100644
--- a/res/values/strings_cs.arb
+++ b/res/values/strings_cs.arb
@@ -171,6 +171,7 @@
"collection_description": "Popis sbírky",
"collection_name": "Název sbírky",
"color_theme": "Barevný motiv",
+ "coming_soon_tag": "Přijde brzy",
"commit_transaction_amount_fee": "Odeslat transakci\nČástka: ${amount}\nPoplatek: ${fee}",
"confirm": "Potvrdit",
"confirm_delete_template": "Tato akce smaže tuto šablonu. Přejete si pokračovat?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Vyberte zařízení v peněžence dortu a pokračujte v nastavení.",
"connect_yats": "Připojit Yaty",
"connect_your_hardware_wallet": "Připojte hardwarovou peněženku pomocí Bluetooth nebo USB",
- "connect_your_hardware_wallet_ios": "Připojte hardwarovou peněženku pomocí Bluetooth",
+ "connect_your_hardware_wallet_ble": "Připojte hardwarovou peněženku pomocí Bluetooth",
+ "connect_your_hardware_wallet_usb": "Připojte hardwarovou peněženku pomocí USB",
"connected": "Připojeno",
"connection_sync": "Připojení a synch.",
"connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce",
@@ -784,6 +786,7 @@
"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.",
"select_destination": "Vyberte cíl pro záložní soubor.",
"select_hw_account_below": "Níže vyberte, který účet chcete obnovit:",
+ "select_manufacturer_title": "Vyberte výrobce",
"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.",
"select_your_country": "Vyberte prosím svou zemi",
"selected_trocador_provider": "Vybraný poskytovatel Trocador",
@@ -1146,4 +1149,4 @@
"youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu",
"your": "Vaše",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb
index 415507b..f7f3b63 100644
--- a/res/values/strings_de.arb
+++ b/res/values/strings_de.arb
@@ -158,7 +158,7 @@
"choose_from_available_options": "Wähle aus verfügbaren Optionen:",
"choose_one": "Wähle ein",
"choose_qr_code_format": "Wählen Sie das QR-Code-Format",
- "choose_qr_code_format_note": "Wählen Sie das richtige Format für Ihre Hardware-Wallet:",
+ "choose_qr_code_format_note": "Wählen Sie das richtige Format für Ihr Hardware-Wallet:",
"choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
"choose_wallet_currency": "Bitte wählen Sie die Währung der Wallet:",
"choose_wallet_group": "Wählen Sie Walletgruppe",
@@ -171,6 +171,7 @@
"collection_description": "Sammlung Beschreibung",
"collection_name": "Sammlungsname",
"color_theme": "Farbthema",
+ "coming_soon_tag": "Kommt bald",
"commit_transaction_amount_fee": "Transaktion absenden\nBetrag: ${amount}\nGebühr: ${fee}",
"confirm": "Bestätigen",
"confirm_delete_template": "Diese Aktion löscht diese Vorlage. Möchten Sie fortfahren?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Wählen Sie Ihr Gerät in Cake Wallet aus und fahren Sie mit dem Setup fort.",
"connect_yats": "Yats verbinden",
"connect_your_hardware_wallet": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth oder USB",
- "connect_your_hardware_wallet_ios": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth",
+ "connect_your_hardware_wallet_ble": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth",
+ "connect_your_hardware_wallet_usb": "Schließen Sie Ihre Hardware-Wallet mit USB an",
"connected": "Verbunden",
"connection_sync": "Verbindung und Synchronisierung",
"connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen",
@@ -354,7 +356,7 @@
"error_while_processing": "Ein Fehler beim Proceessing trat ein Fehler auf",
"errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen",
"errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten",
- "establishing_tor_connection": "Einstellung der TOR-Verbindung",
+ "establishing_tor_connection": "Einstellung der Tor-Verbindung",
"estimated": "Geschätzt",
"estimated_new_fee": "Geschätzte neue Gebühr",
"estimated_receive_amount": "Geschätzter Empfangsbetrag",
@@ -646,8 +648,8 @@
"purchase_gift_card": "Geschenkkarte kaufen",
"purple_dark_theme": "Lila dunkle Thema",
"qr_code_format": "QR-Codeformat",
- "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.",
- "qr_code_format_note": "Unterschiedliche QR-Codeformate werden von verschiedenen Hardware-Wallet im Bitcoin-Bereich verwendet",
+ "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.",
+ "qr_code_format_note": "Unterschiedliche QR-Codeformate werden von verschiedenen Hardware-Wallets im Bitcoin-Bereich verwendet",
"qr_fullscreen": "Tippen Sie hier, um den QR-Code im Vollbildmodus zu öffnen",
"qr_instruction": "Verwenden Sie diese Adresse, um ein Token oder ein Sammlerstück zu erhalten",
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
@@ -785,6 +787,7 @@
"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.",
"select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.",
"select_hw_account_below": "Bitte wählen Sie unten, welches Konto unten wiederhergestellt werden soll:",
+ "select_manufacturer_title": "Hersteller auswählen",
"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.",
"select_your_country": "Bitte wählen Sie Ihr Land aus",
"selected_trocador_provider": "Ausgewählter Trocador-Anbieter",
@@ -894,7 +897,7 @@
"sort_by": "Sortiere nach",
"spend_key_private": "Spend Key (geheim)",
"spend_key_public": "Spend Key (öffentlich)",
- "starting_tor_proxy": "TOR-Proxy beginnen",
+ "starting_tor_proxy": "Tor-Proxy beginnen",
"status": "Status: ",
"step": "Schritt",
"string_default": "Standard",
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index e7d1775..bcac148 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -171,6 +171,7 @@
"collection_description": "Collection Description",
"collection_name": "Collection Name",
"color_theme": "Color theme",
+ "coming_soon_tag": "Coming Soon",
"commit_transaction_amount_fee": "Commit transaction\nAmount: ${amount}\nFee: ${fee}",
"confirm": "Confirm",
"confirm_delete_template": "This action will delete this template. Do you wish to continue?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Select your device in Cake Wallet and continue with the setup.",
"connect_yats": "Connect Yats",
"connect_your_hardware_wallet": "Connect your hardware wallet using Bluetooth or USB",
- "connect_your_hardware_wallet_ios": "Connect your hardware wallet using Bluetooth",
+ "connect_your_hardware_wallet_ble": "Connect your hardware wallet using Bluetooth",
+ "connect_your_hardware_wallet_usb": "Connect your hardware wallet using USB",
"connected": "Connected",
"connection_sync": "Connection and sync",
"connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions",
@@ -785,6 +787,7 @@
"select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.",
"select_destination": "Please select destination for the backup file.",
"select_hw_account_below": "Please select which account to restore below:",
+ "select_manufacturer_title": "Select Manufacturer",
"select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.",
"select_your_country": "Please select your country",
"selected_trocador_provider": "selected Trocador provider",
@@ -1147,4 +1150,4 @@
"youCanGoBackToYourDapp": "You can go back to your dApp now",
"your": "Your",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb
index 6635599..31cd806 100644
--- a/res/values/strings_es.arb
+++ b/res/values/strings_es.arb
@@ -171,6 +171,7 @@
"collection_description": "Descripción de la coleccWhy this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.