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

* chore: add ffi and ffigen dependencies to pubspec.yaml

* refactor: replace method channel with FFI implementation for MWeb module

* fix: correct `cd` command in build_mwebd.sh script

* chore: add go.sum file with dependency checksums

* fix: ensure ANDROID_NDK_VERSION is set before building and dynamically update NDK_BIN path

* fix: update NDK_BIN path to dynamically detect platform in build_mwebd.sh script

* refactor: remove unused mwebd dependencies and clean up CwMwebPlugin code

* Update scripts/android/build_mwebd.sh

Co-authored-by: cyan <cyjan@mrcyjanek.net>

* refactor: replace iOS MoneroWallet framework with Mwebd.xcframework and clean up plugin implementation [skip-ci]

* refactor: extract hardware wallet service abstraction

* refactor: extend BitcoinHardwareWalletService from HardwareWalletService and override account retrieval method

* feat: add BitBox hardware wallet support and implement manufacturer selection page with custom UI components

* refactor: update manufacturer selection UI and integrate custom gradient styles for ManufacturerOptionTile widget

* fix: mwebd ffi ios

* feat: add BitBox hardware wallet support, update derivation paths, and refactor hardware wallet credential implementations

* feat: add bitbox support and refactor the old ledger implementation

* feat: add BitBox hardware wallet support and refactor hardware wallet service implementations

* feat: refactor hardware wallet support to introduce abstraction for BitBox and Ledger; update device scanning, initialization, and UI integration

* fix: BitBox hardware wallet ethereum transaction signing

* chore: update BitBox Flutter path references and include build steps in CI workflows

* chore: update CI workflows to include `--dont-install` flag for BitBox Flutter builds

* refactor: replace if-else logic with switch-case for HardwareWalletViewModel factory

* feat: refactor hardware wallet services to introduce shared interface, improve ledger and bitbox implementations, and support additional transaction signing operations

* feat: add SVG assets for hardware wallet manufacturers (Foundation, Keystone, SeedSigner)

* feat: redesign hardware wallet restoration flow with improved routing, UI updates, and manufacturer-specific device selection handling

* fix: validate required BIP353 fields in transaction builder to prevent potential null value errors

* feat: improve BitBox transaction signing logs with detailed PSBT information

* refactor: update MWebFfi class to use non-nullable `lib` and improve address debug logging

* feat: add BitBox coin type constant and improve hardware wallet service extensibility

* chore: remove unused iOS/Android build scripts for mwebd

* feat: add BitBox SVG asset and update localization for hardware wallet connection instructions

* feat: update manufacturer tags and add Toast Easter egg for device selection interactions

* refactor: replace LedgerViewModel with HardwareWalletViewModel across providers and improve hardware wallet handling

* refactor: replace mweb.so with libmweb.so, use sync methods in MWebFfi, and update iOS/Android frameworks/scripts accordingly

* feat: add Ledger Nano X SVG asset to hardware wallet resources

* chore: unify mwebd build process in workflows, remove unused gomobile init command

* refactor: remove redundant print statement and unnecessary whitespace

* chore: add `gomobile init` to Android PR test build workflow

* chore: update Android PR test build workflow and add file existence check in `build_bitbox_flutter.sh`

* chore: override `ledger_usb_plus` dependency in pubspec_base.yaml

* feat: add verbose logging for BitBox connection and initialization process

* Update lib/view_model/hardware_wallet/bitbox_view_model.dart [skip ci]

* CW-1206-refactor-hardware-wallets-code (#2507)

* feat: add support for new hardware wallet types (Cupcake, Coldcard, SeedSigner) and improve QR Code handling

- Introduced new hardware wallet types (Cupcake, Coldcard, SeedSigner) with corresponding assets and localization updates.
- Updated QR Code format selection for hardware wallets with new formats (BCUR, BBQR) specific to devices.
- Improved hardware wallet restoration flow to include airgapped wallet handling via QR Code scanning.
- Refactored routing, view models, and dependency injection to accommodate new hardware wallet types.
- Enhanced UI for device manufacturer selection and QR Code format dialogs.

* feat: update hardware wallet assets and restoration flow

* feat: add changing airgapped wallet type

* feat: add Cupcake Man SVG asset to hardware wallet resources

* refactor: update Litecoin receive options to require wallet parameter and handle hardware wallets

* fix: debugging keystone integration
feat: support for zpub view only restore

* fix: seed signer restore

* fix: disable bitbox restoring (for now) [skip-ci]

* refactor: rename `isCupcake` to `isBitcoinViewOnly` for clarity [skip ci]

---------

Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* refactor: standardize hardware wallet connection handling across wallet types

---------

Co-authored-by: cyan <cyjan@mrcyjanek.net>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Konstantin Ullrich
2025-10-01 18:20:40 +03:00
committed by GitHub
co-authored by Czarek Nakamoto Omar Hatem
parent e1bb0e4a69
commit e4672c75b7
117 changed files with 2048 additions and 723 deletions
@@ -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
@@ -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
@@ -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>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -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>

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

@@ -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>

After

Width:  |  Height:  |  Size: 554 B

@@ -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>

After

Width:  |  Height:  |  Size: 852 B

@@ -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>

After

Width:  |  Height:  |  Size: 625 B

@@ -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>

After

Width:  |  Height:  |  Size: 7.3 KiB

@@ -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>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -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>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -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>

After

Width:  |  Height:  |  Size: 7.7 KiB

@@ -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>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -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;
}
}
+9 -16
View File
@@ -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);
}
@@ -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;
+9 -3
View File
@@ -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,
+24 -9
View File
@@ -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({
@@ -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;
}
}
@@ -0,0 +1,5 @@
import 'dart:typed_data';
mixin BitcoinHardwareWalletService {
Future<Uint8List> getMasterFingerprint();
}
@@ -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);
}
@@ -4,16 +4,17 @@ 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 LitecoinHardwareWalletService {
LitecoinHardwareWalletService(this.ledgerConnection);
class LitecoinLedgerService extends HardwareWalletService {
LitecoinLedgerService(this.ledgerConnection);
final LedgerConnection ledgerConnection;
Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
@override
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
await litecoinLedgerApp.getVersion();
@@ -27,11 +28,9 @@ class LitecoinHardwareWalletService {
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 hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(
hd: hd, index: 0, network: LitecoinNetwork.mainnet);
final address = generateP2WPKHAddress(hd: hd, index: 0, network: LitecoinNetwork.mainnet);
accounts.add(HardwareAccountData(
address: address,
+3 -1
View File
@@ -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);
+2 -1
View File
@@ -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);
+9 -2
View File
@@ -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"
+3 -1
View File
@@ -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
@@ -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 {
@@ -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();
}
+46
View File
@@ -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;
}
+2
View File
@@ -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});
+12 -1
View File
@@ -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);
+1 -1
View File
@@ -47,7 +47,7 @@ packages:
source: hosted
version: "2.13.0"
blockchain_utils:
dependency: transitive
dependency: "direct main"
description:
path: "."
ref: cake-update-v2
+4
View File
@@ -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:
+1
View File
@@ -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';
+8 -3
View File
@@ -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);
}
@@ -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;
}
@@ -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;
}
}
@@ -1,14 +1,16 @@
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 EVMChainHardwareWalletService {
EVMChainHardwareWalletService(this.ledgerConnection);
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);
@@ -76,6 +76,8 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
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]];
+2
View File
@@ -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:
+5 -5
View File
@@ -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:
+7
View File
@@ -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
+22 -21
View File
@@ -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
+3 -3
View File
@@ -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;
+25 -28
View File
@@ -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()),
);
}
}
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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(
+1 -2
View File
@@ -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(
+33 -30
View File
@@ -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()),
);
}
}
+1 -1
View File
@@ -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(
+3 -2
View File
@@ -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,
});
}
+53 -20
View File
@@ -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<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
(type, additionalParams) {
final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
final hardwareWalletType = additionalParams?['hardwareWalletType'] as HardwareWalletType?;
getIt.registerFactoryParam<WalletRestorePage, WalletType, RestoredWallet?>((WalletType type,
restoredWallet) {
return WalletRestorePage(
getIt.get<WalletRestoreViewModel>(param1: type, param2: restoredWallet),
getIt.get<SeedSettingsViewModel>());
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>()));
@@ -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;
abstract class HardwareWalletDevice {
String get name;
const HardwareWalletDevice({
required this.name,
required this.type,
required this.connectionType,
});
HardwareWalletDeviceType get type;
factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
HardwareWalletDevice(
name: device.name,
type: device.deviceInfo.toGeneric(),
connectionType: device.connectionType.toGeneric(),
);
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 {
+14 -14
View File
@@ -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);
}
@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;
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
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager);
@override
List<String> getDefaultTokenContractAddresses() {
+14 -18
View File
@@ -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() =>
@@ -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(
+45 -18
View File
@@ -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>(),
+2
View File
@@ -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';
@@ -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),
);
}
@@ -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});
}
@@ -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),
@@ -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;
}
@@ -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,
)
],
),
),
),
);
}
@@ -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,
),
+39
View File
@@ -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"),
);
},
),
],
),
);
}
}
@@ -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)
: [],
),
),
@@ -111,7 +111,7 @@ class QRWidget extends StatelessWidget {
),
if (addressListViewModel.isPayjoinUnavailable &&
!addressListViewModel.isSilentPayments &&
!addressListViewModel.isCupcake) ...[
!addressListViewModel.isBitcoinViewOnly) ...[
GestureDetector(
onTap: () => _onPayjoinInactivePressed(context),
child: Row(
@@ -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;
}
String get imageRestoreHWPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_hw_dark.png'
: 'assets/images/restore_hw.png';
return true;
}
String get imageRestoreCupcakePath => widget.themeType == ThemeType.dark
? 'assets/images/restore_cupcake_dark.png'
: 'assets/images/restore_cupcake.png';
String get imageRestoreHWPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_hw_dark.png'
: 'assets/images/restore_hw.png';
String get imageRestoreQRPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_qr_dark.png'
: 'assets/images/restore_qr.png';
String get imageRestoreCupcakePath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_cupcake_dark.png'
: 'assets/images/restore_cupcake.png';
String get imageRestoreHotWalletPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_hot_wallet_dark.png'
: 'assets/images/restore_hot_wallet.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 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);
+7 -4
View File
@@ -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);
}
}
@@ -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),
),
],
),
);
},
);
}
),
);
}
+2 -1
View File
@@ -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 {
});
}
}
}
}
+83 -101
View File
@@ -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;
List<String> get frames {
return widget.urqr[selected]?.split("\n") ?? [];
}
late String nextLabel =
widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
void next() {
String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
void next() => setState(() {
final keys = widget.urqr.keys.toList();
setState(() {
selectedInt++;
nextLabel = keys[(selectedInt + 1) % keys.length];
selected = keys[(selectedInt) % keys.length];
});
}
selectedInt++;
selected = keys[(selectedInt) % keys.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++);
@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(),
);
}
@@ -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();
},
@@ -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;
@@ -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(),
};
}
}
}
@@ -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}');
}
}
}
@@ -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;
}
@@ -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;
@@ -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(
@@ -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;
}
+3 -2
View File
@@ -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;
@@ -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();
@@ -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;
@@ -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;
}
@@ -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:
+6
View File
@@ -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
+5 -2
View File
@@ -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"
}
}
+5 -2
View File
@@ -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": "гг"
}
}
+5 -2
View File
@@ -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"
}
}
+9 -6
View File
@@ -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",
+5 -2
View File
@@ -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"
}
}
+5 -2
View File
@@ -171,6 +171,7 @@
"collection_description": "Descripción de la colección",
"collection_name": "Nombre de colección",
"color_theme": "Tema de color",
"coming_soon_tag": "Muy pronto",
"commit_transaction_amount_fee": "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}",
"confirm": "Confirmar",
"confirm_delete_template": "Esta acción eliminará esta plantilla. ¿Deseas continuar?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Seleccione su dispositivo en Cake Wallet y continúe con la configuración.",
"connect_yats": "Conectar Yats",
"connect_your_hardware_wallet": "Conecta tu billetera de hardware con Bluetooth o USB",
"connect_your_hardware_wallet_ios": "Conecta tu billetera de hardware con Bluetooth",
"connect_your_hardware_wallet_ble": "Conecta tu billetera de hardware con Bluetooth",
"connect_your_hardware_wallet_usb": "Conecte su billetera de hardware con USB",
"connected": "Conectado",
"connection_sync": "Conexión y sincronización",
"connectWalletPrompt": "Conecte tu billetera con WalletConnect para realizar transacciones",
@@ -785,6 +787,7 @@
"select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
"select_destination": "Selecciona el destino del archivo de copia de seguridad.",
"select_hw_account_below": "Seleccione qué cuenta restaurar a continuación:",
"select_manufacturer_title": "Seleccionar fabricante",
"select_sell_provider_notice": "Selecciona un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.",
"select_your_country": "Seleccione su país",
"selected_trocador_provider": "Proveedor de Trocador seleccionado",
@@ -1148,4 +1151,4 @@
"youCanGoBackToYourDapp": "Puedes volver a tu dapp ahora",
"your": "Su",
"yy": "YY"
}
}
+5 -2
View File
@@ -171,6 +171,7 @@
"collection_description": "Description de la collection",
"collection_name": "Nom de collection",
"color_theme": "Couleur du thème",
"coming_soon_tag": "À venir",
"commit_transaction_amount_fee": "Valider la transaction\nMontant : ${amount}\nFrais : ${fee}",
"confirm": "Confirmer",
"confirm_delete_template": "Cette action va supprimer ce modèle. Souhaitez-vous continuer ?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Sélectionnez votre appareil dans Cake Wallet et poursuivez la configuration.",
"connect_yats": "Connecter Yats",
"connect_your_hardware_wallet": "Connectez votre portefeuille matériel à l'aide du Bluetooth ou de l'USB",
"connect_your_hardware_wallet_ios": "Connectez votre portefeuille matériel à l'aide du Bluetooth",
"connect_your_hardware_wallet_ble": "Connectez votre portefeuille matériel à l'aide du Bluetooth",
"connect_your_hardware_wallet_usb": "Connectez votre portefeuille matériel à l'aide USB",
"connected": "Connecté",
"connection_sync": "Connexion et synchronisation",
"connectWalletPrompt": "Connectez votre portefeuille (wallet) avec WalletConnect pour effectuer des transactions",
@@ -784,6 +786,7 @@
"select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.",
"select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.",
"select_hw_account_below": "Veuillez sélectionner le compte à restaurer ci-dessous:",
"select_manufacturer_title": "Sélectionner le fabricant",
"select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.",
"select_your_country": "Veuillez sélectionner votre pays",
"selected_trocador_provider": "fournisseur de trocador sélectionné",
@@ -1146,4 +1149,4 @@
"youCanGoBackToYourDapp": "Vous pouvez retourner à votre Dapp maintenant",
"your": "Ton",
"yy": "AA"
}
}
+5 -2
View File
@@ -171,6 +171,7 @@
"collection_description": "Bayanin tarin",
"collection_name": "Sunan tattara",
"color_theme": "Jigon launi",
"coming_soon_tag": "Zuwan nan da sannu",
"commit_transaction_amount_fee": "Aikata ciniki\nAdadi: ${amount}\nKuda: ${fee}",
"confirm": "Tabbatar",
"confirm_delete_template": "Wannan aikin zai share wannan samfuri. Kuna so ku ci gaba?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Zaɓi na'urarka a cikin walat walat kuma ci gaba da saiti.",
"connect_yats": "Haɗa Yats",
"connect_your_hardware_wallet": "Haɗa Wallake Wallware ɗinku ta Bluetooth ko USB",
"connect_your_hardware_wallet_ios": "Haɗa kayan aikinku ta Bluetooth",
"connect_your_hardware_wallet_ble": "Haɗa kayan aikinku ta Bluetooth",
"connect_your_hardware_wallet_usb": "Haɗa kayan aikinku ta amfani da USB",
"connected": "Wanda aka haɗa",
"connection_sync": "Haɗi da daidaitawa",
"connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala",
@@ -786,6 +788,7 @@
"select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.",
"select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.",
"select_hw_account_below": "Da fatan za a zabi wanda asusun zai gyara a ƙasa:",
"select_manufacturer_title": "Select Manaser",
"select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.",
"select_your_country": "Da fatan za a zabi ƙasarku",
"selected_trocador_provider": "Zabi mai bada TORACAD",
@@ -1149,4 +1152,4 @@
"youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu",
"your": "Naku",
"yy": "YY"
}
}
+6 -3
View File
@@ -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": "ब्लूटूथ या यूएसबी का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
"connect_your_hardware_wallet_ios": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
"connect_your_hardware_wallet_ble": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
"connect_your_hardware_wallet_usb": "USB का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
"connected": "जुड़े हुए",
"connection_sync": "कनेक्शन और सिंक",
"connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें",
@@ -604,8 +606,8 @@
"payjoin_unavailable_sheet_title": "Payjoin अनुपलब्ध क्यों है?",
"payment_id": "भुगतान ID: ",
"payment_made_easy": "भुगतान आसान किया गया",
"payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
"Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
"payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
"payments": "भुगतान",
"pending": " (अपूर्ण)",
"percentageOf": "${amount} का",
@@ -786,6 +788,7 @@
"select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।",
"select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।",
"select_hw_account_below": "कृपया नीचे पुनर्स्थापित करने के लिए कौन सा खाता चुनें:",
"select_manufacturer_title": "निर्माता का चयन करें",
"select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।",
"select_your_country": "कृपया अपने देश का चयन करें",
"selected_trocador_provider": "चयनित ट्रोकैडर प्रदाता",
@@ -1148,4 +1151,4 @@
"youCanGoBackToYourDapp": "अब आप अपने DAPP पर वापस जा सकते हैं",
"your": "आपका",
"yy": "वाईवाई"
}
}
+5 -2
View File
@@ -171,6 +171,7 @@
"collection_description": "Zbirka Opis opisa",
"collection_name": "Naziv kolekcije",
"color_theme": "Shema boja",
"coming_soon_tag": "Dolazi uskoro",
"commit_transaction_amount_fee": "Izvrši transakciju \nAmount: ${amount}\nFee: ${fee}",
"confirm": "Potvrdi",
"confirm_delete_template": "Ovom ćete radnjom izbrisati ovaj predložak. Želite li nastaviti?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Odaberite svoj uređaj u novčanici za torte i nastavite s postavljanjem.",
"connect_yats": "Povežite Yats",
"connect_your_hardware_wallet": "Spojite svoj hardverski novčanik pomoću Bluetooth -a ili USB -a",
"connect_your_hardware_wallet_ios": "Spojite svoj hardverski novčanik pomoću Bluetooth -a",
"connect_your_hardware_wallet_ble": "Spojite svoj hardverski novčanik pomoću Bluetooth -a",
"connect_your_hardware_wallet_usb": "Spojite svoj hardverski novčanik pomoću USB -a",
"connected": "Povezan",
"connection_sync": "Povezivanje i sinkronizacija",
"connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija",
@@ -784,6 +786,7 @@
"select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.",
"select_destination": "Odaberite odredište za datoteku sigurnosne kopije.",
"select_hw_account_below": "Molimo odaberite koji će se račun vratiti u nastavku:",
"select_manufacturer_title": "Odaberite proizvođača",
"select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.",
"select_your_country": "Odaberite svoju zemlju",
"selected_trocador_provider": "Odabrani pružatelj usluga trokadora",
@@ -1146,4 +1149,4 @@
"youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp",
"your": "Vaš",
"yy": "GG"
}
}
+5 -2
View File
@@ -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": "Միացրեք Yat-ները",
"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-ի միջոցով գործարքներ կատարելու համար",
@@ -783,6 +785,7 @@
"select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով",
"select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը",
"select_hw_account_below": "Խնդրում ենք ընտրել, թե որ հաշիվն է վերականգնել հետեւյալը.",
"select_manufacturer_title": "Ընտրեք արտադրող",
"select_sell_provider_notice": "Ընտրեք վաճառքի մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն վաճառքի մատակարարը հավելվածի կարգավորումներում սահմանելով",
"select_your_country": "Խնդրում ենք ընտրել ձեր երկիրը",
"sell": "Ծախել",
@@ -1144,4 +1147,4 @@
"youCanGoBackToYourDapp": "Այժմ կարող եք վերադառնալ ձեր DAPP- ին",
"your": "Քո",
"yy": "ՏՏ"
}
}
+5 -2
View File
@@ -171,6 +171,7 @@
"collection_description": "Deskripsi Koleksi",
"collection_name": "Nama Koleksi",
"color_theme": "Tema warna",
"coming_soon_tag": "Segera hadir",
"commit_transaction_amount_fee": "Lakukan transaksi\nJumlah: ${amount}\nBiaya: ${fee}",
"confirm": "Konfirmasi",
"confirm_delete_template": "Tindakan ini akan menghapus template ini. Apakah Anda ingin melanjutkan?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Pilih perangkat Anda di dompet kue dan lanjutkan dengan pengaturan.",
"connect_yats": "Hubungkan Yats",
"connect_your_hardware_wallet": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth atau USB",
"connect_your_hardware_wallet_ios": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth",
"connect_your_hardware_wallet_ble": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth",
"connect_your_hardware_wallet_usb": "Hubungkan dompet perangkat keras Anda menggunakan USB",
"connected": "Terhubung",
"connection_sync": "Koneksi dan sinkronisasi",
"connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi",
@@ -787,6 +789,7 @@
"select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.",
"select_destination": "Silakan pilih tujuan untuk file cadangan.",
"select_hw_account_below": "Pilih akun mana yang akan dikembalikan di bawah ini:",
"select_manufacturer_title": "Pilih produsen",
"select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.",
"select_your_country": "Pilih negara Anda",
"selected_trocador_provider": "Penyedia Trocador Terpilih",
@@ -1149,4 +1152,4 @@
"youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang",
"your": "Milikmu",
"yy": "YY"
}
}
+5 -2
View File
@@ -171,6 +171,7 @@
"collection_description": "Descrizione della raccolta",
"collection_name": "Nome della raccolta",
"color_theme": "Colore tema",
"coming_soon_tag": "Prossimamente",
"commit_transaction_amount_fee": "Invia transazione\nAmmontare: ${amount}\nCommissione: ${fee}",
"confirm": "Conferma",
"confirm_delete_template": "Questa azione cancellerà questo modello. Desideri continuare?",
@@ -192,7 +193,8 @@
"connect_hw_info_step_4": "Seleziona il tuo dispositivo nel portafoglio Cake e continua con l'installazione.",
"connect_yats": "Connetti Yats",
"connect_your_hardware_wallet": "Collega il tuo portafoglio hardware tramite Bluetooth o USB",
"connect_your_hardware_wallet_ios": "Collega il tuo portafoglio hardware tramite Bluetooth",
"connect_your_hardware_wallet_ble": "Collega il tuo portafoglio hardware tramite Bluetooth",
"connect_your_hardware_wallet_usb": "Collega il tuo portafoglio hardware usando USB",
"connected": "Collegato",
"connection_sync": "Connessione e sincronizzazione",
"connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni",
@@ -785,6 +787,7 @@
"select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.",
"select_destination": "Seleziona la destinazione per il file di backup.",
"select_hw_account_below": "Seleziona quale account ripristinare:",
"select_manufacturer_title": "Seleziona il produttore",
"select_sell_provider_notice": "Seleziona un provider di vendita sopra. Puoi saltare questa schermata impostando il tuo provider di vendita predefinito nelle impostazioni dell'app.",
"select_your_country": "Seleziona il tuo paese",
"selected_trocador_provider": "Provider di Trocador selezionato",
@@ -1149,4 +1152,4 @@
"youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora",
"your": "Tuo",
"yy": "YY"
}
}

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