Cw 604 integrate bitcoin ledger (#1407)

* CW-503 Prepare Ledger integration * CW-503 Revert Tor ignore * CW-503 Add Connect Device Page * CW-503 Add createWalletFromDevice for monero * CW-503 Add Connect Device Page * CW-503 Add Connect Device Page * CW-503 Add Debug Options * CW-503 Add proper hardware wallet selection screen * CW-503 Minor design changes on connect_device_page * CW-503 Add Create Wallet from Ledger * CW-503 Spent Ledger ETH Funds * CW-503 Minor fixes * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix minor conflicts * CW-503 Improve Ledger BLE Communication * CW-503 Improve Ledger BLE Communication * CW-503 Rollback Monero Test code * CW-503 Fix Execution failed for task :app:checkReleaseDuplicateClasses * CW-503 Better Error-Exceptions * CW-503 Add SetPinScreen before restore from hardware-wallet * CW-503 override web3dart to use cake's git hosted version * CW-503 Implement ledger sign messages * CW-503 Implement ledger sign messages and send erc20 tokens * CW-503 Fix merge conflicts * CW-503 Fix merge conflicts * CW-503 Use dep override for ledger_flutter * CW-503 Ledger ERC20 finalisation * CW-503 More graceful error handling * CW-503 Even more graceful error handling & remove debug code * CW-503 Minor Changes for Vik * CW-503 Fix USB connection * CW-503 Maybe this overrides web3dart finally * Indicate Loading on the first 5 Wallet Accounts * Prepare Ledger Bitcoin * Fix conflicts with main * Add Bluetooth permission to iOS [skip ci] * add privacyinfo for ios [skip ci] * update PrivacyInfo.xcprivacy [scip ci] * ios shit [skip ci] * Improve bitcoin xpub * Resolve open Todos regarding iOS Support * Minor debug * Remove erc20 dependency to have more granular control over the tx UX * Create Bitcoin Wallets using xpub * Create Bitcoin Wallets using xpub * Better error handling * Improve Ledger account handling * Add Bitcoin Support for Ledger * Add Bitcoin Support for Ledger * Add Bitcoin Support for Ledger * Implement requested Changes * Implement requested Changes * Minor fix * Implement requested Changes * Implement requested Changes * Add comment to remind me * Enable RBF for Bitcoin Ledger * Fix merge conflicts * Update wallet_info.dart * Cw 503 ledger support (#1310) * CW-503 Prepare Ledger integration * CW-503 Revert Tor ignore * CW-503 Add Connect Device Page * CW-503 Add createWalletFromDevice for monero * CW-503 Add Connect Device Page * CW-503 Add Connect Device Page * CW-503 Add Debug Options * CW-503 Add proper hardware wallet selection screen * CW-503 Minor design changes on connect_device_page * CW-503 Add Create Wallet from Ledger * CW-503 Spent Ledger ETH Funds * CW-503 Minor fixes * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix minor conflicts * CW-503 Improve Ledger BLE Communication * CW-503 Improve Ledger BLE Communication * CW-503 Rollback Monero Test code * CW-503 Fix Execution failed for task :app:checkReleaseDuplicateClasses * CW-503 Better Error-Exceptions * CW-503 Add SetPinScreen before restore from hardware-wallet * CW-503 override web3dart to use cake's git hosted version * CW-503 Implement ledger sign messages * CW-503 Implement ledger sign messages and send erc20 tokens * CW-503 Fix merge conflicts * CW-503 Fix merge conflicts * CW-503 Use dep override for ledger_flutter * CW-503 Ledger ERC20 finalisation * CW-503 More graceful error handling * CW-503 Even more graceful error handling & remove debug code * CW-503 Minor Changes for Vik * CW-503 Fix USB connection * CW-503 Maybe this overrides web3dart finally * Indicate Loading on the first 5 Wallet Accounts * Fix conflicts with main * Add Bluetooth permission to iOS [skip ci] * add privacyinfo for ios [skip ci] * update PrivacyInfo.xcprivacy [scip ci] * ios shit [skip ci] * Resolve open Todos regarding iOS Support * Remove erc20 dependency to have more granular control over the tx UX * Better error handling * Improve Ledger account handling * Implement requested Changes * Implement requested Changes * Implement requested Changes * Implement requested Changes * Fix merge conflicts * Update wallet_info.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Fix merge conflicts * Fix merge conflicts * Minor Fix to derivations * Update cw_bitcoin/lib/bitcoin_wallet.dart [skip ci] * Update cw_bitcoin/lib/bitcoin_wallet.dart [skip ci] * Update cw_bitcoin/lib/electrum_wallet.dart [skip ci] * Fix backward compatibility issues * Merge Tron * Fix Tron with HW changes * fix ble on iOS fix tron address validation --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Konstantin Ullrich committed May 4, 2024 at 20:44 UTC 5eabdcdca1973fc892aef540d1fb3efb91eed89a
118 files changed +3358 -574
android/app/src/main/AndroidManifestBase.xml
+20
@@ -9,6 +9,26 @@
9 <uses-permission android:name="android.permission.WAKE_LOCK" />
10 <uses-permission android:name="android.permission.CAMERA" />
11 <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
12 + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
13 +
14 + <!--bibo01 : hardware option-->
15 + <uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
16 + <uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
17 +
18 + <!-- required for API 18 - 30 -->
19 + <uses-permission
20 + android:name="android.permission.BLUETOOTH"
21 + android:maxSdkVersion="30" />
22 + <uses-permission
23 + android:name="android.permission.BLUETOOTH_ADMIN"
24 + android:maxSdkVersion="30" />
25 +
26 + <!-- API 31+ -->
27 + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
28 + <uses-permission
29 + android:name="android.permission.BLUETOOTH_SCAN"
30 + android:usesPermissionFlags="neverForLocation" />
31 + <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
32
33 <application
34 android:name=".Application"
android/build.gradle
+1 -1
@@ -1,5 +1,5 @@
1 buildscript {
2 - ext.kotlin_version = '1.7.10'
2 + ext.kotlin_version = '1.8.21'
3 repositories {
4 google()
5 jcenter()
assets/images/bluetooth.png
Binary files /dev/null and b/assets/images/bluetooth.png differ
assets/images/ledger_nano.png
Binary files /dev/null and b/assets/images/ledger_nano.png differ
assets/images/usb.png
Binary files /dev/null and b/assets/images/usb.png differ
cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart new
+43
@@ -0,0 +1,43 @@
1 +import 'dart:async';
2 +
3 +import 'package:bitcoin_base/bitcoin_base.dart';
4 +import 'package:bitcoin_flutter/bitcoin_flutter.dart';
5 +import 'package:cw_bitcoin/utils.dart';
6 +import 'package:cw_core/hardware/hardware_account_data.dart';
7 +import 'package:ledger_bitcoin/ledger_bitcoin.dart';
8 +import 'package:ledger_flutter/ledger_flutter.dart';
9 +
10 +class BitcoinHardwareWalletService {
11 + BitcoinHardwareWalletService(this.ledger, this.device);
12 +
13 + final Ledger ledger;
14 + final LedgerDevice device;
15 +
16 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
17 + final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
18 +
19 + final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
20 + print(masterFp);
21 +
22 + final accounts = <HardwareAccountData>[];
23 + final indexRange = List.generate(limit, (i) => i + index);
24 +
25 + for (final i in indexRange) {
26 + final derivationPath = "m/84'/0'/$i'";
27 + final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
28 + HDWallet hd = HDWallet.fromBase58(xpub).derive(0);
29 +
30 + final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
31 +
32 + accounts.add(HardwareAccountData(
33 + address: address,
34 + accountIndex: i,
35 + derivationPath: derivationPath,
36 + masterFingerprint: masterFp,
37 + xpub: xpub,
38 + ));
39 + }
40 +
41 + return accounts;
42 + }
43 +}
cw_bitcoin/lib/bitcoin_wallet.dart
+87 -31
@@ -1,8 +1,13 @@
1 import 'package:bitcoin_base/bitcoin_base.dart';
2 +import 'package:convert/convert.dart';
3 +
4 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
5 +import 'package:cw_bitcoin/psbt_transaction_builder.dart';
6 import 'package:cw_core/crypto_currency.dart';
7 import 'package:cw_core/unspent_coins_info.dart';
8 import 'package:hive/hive.dart';
9 +import 'package:ledger_bitcoin/ledger_bitcoin.dart';
10 +import 'package:ledger_flutter/ledger_flutter.dart';
11 import 'package:mobx/mobx.dart';
12 import 'package:flutter/foundation.dart';
13 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
@@ -20,11 +25,12 @@ class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
25
26 abstract class BitcoinWalletBase extends ElectrumWallet with Store {
27 BitcoinWalletBase({
23 - required String mnemonic,
28 required String password,
29 required WalletInfo walletInfo,
30 required Box<UnspentCoinsInfo> unspentCoinsInfo,
27 - required Uint8List seedBytes,
31 + Uint8List? seedBytes,
32 + String? mnemonic,
33 + String? xpub,
34 String? addressPageType,
35 BasedUtxoNetwork? networkParam,
36 List<BitcoinAddressRecord>? initialAddresses,
@@ -33,24 +39,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
39 Map<String, int>? initialChangeAddressIndex,
40 String? passphrase,
41 }) : super(
36 - mnemonic: mnemonic,
37 - passphrase: passphrase,
38 - password: password,
39 - walletInfo: walletInfo,
40 - unspentCoinsInfo: unspentCoinsInfo,
41 - networkType: networkParam == null
42 - ? bitcoin.bitcoin
43 - : networkParam == BitcoinNetwork.mainnet
44 - ? bitcoin.bitcoin
45 - : bitcoin.testnet,
46 - initialAddresses: initialAddresses,
47 - initialBalance: initialBalance,
48 - seedBytes: seedBytes,
49 - currency: CryptoCurrency.btc) {
42 + mnemonic: mnemonic,
43 + passphrase: passphrase,
44 + xpub: xpub,
45 + password: password,
46 + walletInfo: walletInfo,
47 + unspentCoinsInfo: unspentCoinsInfo,
48 + networkType: networkParam == null
49 + ? bitcoin.bitcoin
50 + : networkParam == BitcoinNetwork.mainnet
51 + ? bitcoin.bitcoin
52 + : bitcoin.testnet,
53 + initialAddresses: initialAddresses,
54 + initialBalance: initialBalance,
55 + seedBytes: seedBytes,
56 + currency: CryptoCurrency.btc) {
57 // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
58 // the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
52 - String derivationPath = walletInfo.derivationInfo!.derivationPath!;
53 - String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
59 + // String derivationPath = walletInfo.derivationInfo!.derivationPath!;
60 + // String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
61 + // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
62 walletAddresses = BitcoinWalletAddresses(
63 walletInfo,
64 electrumClient: electrumClient,
@@ -58,7 +66,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
66 initialRegularAddressIndex: initialRegularAddressIndex,
67 initialChangeAddressIndex: initialChangeAddressIndex,
68 mainHd: hd,
61 - sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath(sideDerivationPath),
69 + sideHd: accountHD.derive(1),
70 network: networkParam ?? network,
71 );
72 autorun((_) {
@@ -129,23 +137,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
137 walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0";
138 walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
139
132 - late Uint8List seedBytes;
140 + Uint8List? seedBytes = null;
141
134 - switch (walletInfo.derivationInfo!.derivationType) {
135 - case DerivationType.electrum:
136 - seedBytes = await mnemonicToSeedBytes(snp.mnemonic);
137 - break;
138 - case DerivationType.bip39:
139 - default:
140 - seedBytes = await bip39.mnemonicToSeed(
141 - snp.mnemonic,
142 - passphrase: snp.passphrase ?? '',
143 - );
144 - break;
142 + if (snp.mnemonic != null) {
143 + switch (walletInfo.derivationInfo!.derivationType) {
144 + case DerivationType.electrum:
145 + seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
146 + break;
147 + case DerivationType.bip39:
148 + default:
149 + seedBytes = await bip39.mnemonicToSeed(
150 + snp.mnemonic!,
151 + passphrase: snp.passphrase ?? '',
152 + );
153 + break;
154 + }
155 }
156
157 return BitcoinWallet(
158 mnemonic: snp.mnemonic,
159 + xpub: snp.xpub,
160 password: password,
161 passphrase: snp.passphrase,
162 walletInfo: walletInfo,
@@ -159,4 +170,49 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
170 networkParam: network,
171 );
172 }
173 +
174 + Ledger? _ledger;
175 + LedgerDevice? _ledgerDevice;
176 + BitcoinLedgerApp? _bitcoinLedgerApp;
177 +
178 + void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
179 + _ledger = setLedger;
180 + _ledgerDevice = setLedgerDevice;
181 + _bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
182 + }
183 +
184 + @override
185 + Future<BtcTransaction> buildHardwareWalletTransaction({
186 + required List<BitcoinBaseOutput> outputs,
187 + required BigInt fee,
188 + required BasedUtxoNetwork network,
189 + required List<UtxoWithAddress> utxos,
190 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
191 + String? memo,
192 + bool enableRBF = false,
193 + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
194 + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
195 + }) async {
196 + final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
197 +
198 + final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
199 + for (final utxo in utxos) {
200 + final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
201 + final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
202 +
203 + psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
204 + utxo: utxo.utxo,
205 + rawTx: rawTx,
206 + ownerDetails: utxo.ownerDetails,
207 + ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
208 + ownerMasterFingerprint: masterFingerprint,
209 + ownerPublicKey: publicKeyAndDerivationPath.publicKey,
210 + ));
211 + }
212 +
213 + final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
214 +
215 + final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
216 + return BtcTransaction.fromRaw(hex.encode(rawHex));
217 + }
218 }
cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
+17 -3
@@ -1,3 +1,4 @@
1 +import 'package:cw_core/hardware/hardware_account_data.dart';
2 import 'package:cw_core/wallet_credentials.dart';
3 import 'package:cw_core/wallet_info.dart';
4
@@ -36,9 +37,22 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
37 }
38
39 class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
39 - BitcoinRestoreWalletFromWIFCredentials(
40 - {required String name, required String password, required this.wif, WalletInfo? walletInfo})
41 - : super(name: name, password: password, walletInfo: walletInfo);
40 + BitcoinRestoreWalletFromWIFCredentials({
41 + required String name,
42 + required String password,
43 + required this.wif,
44 + WalletInfo? walletInfo,
45 + }) : super(name: name, password: password, walletInfo: walletInfo);
46
47 final String wif;
48 }
49 +
50 +class BitcoinRestoreWalletFromHardware extends WalletCredentials {
51 + BitcoinRestoreWalletFromHardware({
52 + required String name,
53 + required this.hwAccountData,
54 + WalletInfo? walletInfo,
55 + }) : super(name: name, walletInfo: walletInfo);
56 +
57 + final HardwareAccountData hwAccountData;
58 +}
cw_bitcoin/lib/bitcoin_wallet_service.dart
+25 -3
@@ -14,8 +14,11 @@ import 'package:hive/hive.dart';
14 import 'package:collection/collection.dart';
15 import 'package:bip39/bip39.dart' as bip39;
16
17 -class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
18 - BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
17 +class BitcoinWalletService extends WalletService<
18 + BitcoinNewWalletCredentials,
19 + BitcoinRestoreWalletFromSeedCredentials,
20 + BitcoinRestoreWalletFromWIFCredentials,
21 + BitcoinRestoreWalletFromHardware> {
22 BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
23
24 final Box<WalletInfo> walletInfoSource;
@@ -99,9 +102,28 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
102 await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
103 }
104
105 + @override
106 + Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
107 + {bool? isTestnet}) async {
108 +
109 + final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
110 + credentials.walletInfo?.network = network.value;
111 + credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath;
112 +
113 + final wallet = await BitcoinWallet(password: credentials.password!,
114 + xpub: credentials.hwAccountData.xpub,
115 + walletInfo: credentials.walletInfo!,
116 + unspentCoinsInfo: unspentCoinsInfoSource,
117 + networkParam: network,
118 + );
119 + await wallet.save();
120 + await wallet.init();
121 + return wallet;
122 + }
123 +
124 @override
125 Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
104 - {bool? isTestnet}) async =>
126 + {bool? isTestnet}) async =>
127 throw UnimplementedError();
128
129 @override
cw_bitcoin/lib/electrum_derivations.dart
+1 -1
@@ -4,7 +4,7 @@ Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
4 DerivationType.electrum: [
5 DerivationInfo(
6 derivationType: DerivationType.electrum,
7 - derivationPath: "m/0'/0",
7 + derivationPath: "m/0'",
8 description: "Electrum",
9 scriptType: "p2wpkh",
10 ),
cw_bitcoin/lib/electrum_wallet.dart
+134 -29
@@ -4,8 +4,8 @@ import 'dart:io';
4 import 'dart:math';
5
6 import 'package:bitcoin_base/bitcoin_base.dart';
7 -import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
7 import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
8 +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
9 import 'package:collection/collection.dart';
10 import 'package:cw_bitcoin/address_from_output.dart';
11 import 'package:cw_bitcoin/bitcoin_address_record.dart';
@@ -37,9 +37,9 @@ import 'package:cw_core/wallet_base.dart';
37 import 'package:cw_core/wallet_info.dart';
38 import 'package:flutter/foundation.dart';
39 import 'package:hive/hive.dart';
40 +import 'package:http/http.dart' as http;
41 import 'package:mobx/mobx.dart';
42 import 'package:rxdart/subjects.dart';
42 -import 'package:http/http.dart' as http;
43
44 part 'electrum_wallet.g.dart';
45
@@ -53,17 +53,16 @@ abstract class ElectrumWalletBase
53 required WalletInfo walletInfo,
54 required Box<UnspentCoinsInfo> unspentCoinsInfo,
55 required this.networkType,
56 - required this.mnemonic,
57 - required Uint8List seedBytes,
56 + String? xpub,
57 + String? mnemonic,
58 + Uint8List? seedBytes,
59 this.passphrase,
60 List<BitcoinAddressRecord>? initialAddresses,
61 ElectrumClient? electrumClient,
62 ElectrumBalance? initialBalance,
63 CryptoCurrency? currency})
63 - : hd = currency == CryptoCurrency.bch
64 - ? bitcoinCashHDWallet(seedBytes)
65 - : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
66 - .derivePath(walletInfo.derivationInfo?.derivationPath ?? "m/0'/0"),
64 + : accountHD =
65 + getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
66 syncStatus = NotConnectedSyncStatus(),
67 _password = password,
68 _feeRates = <int>[],
@@ -80,20 +79,44 @@ abstract class ElectrumWalletBase
79 this.unspentCoinsInfo = unspentCoinsInfo,
80 this.network = _getNetwork(networkType, currency),
81 this.isTestnet = networkType == bitcoin.testnet,
82 + this._mnemonic = mnemonic,
83 super(walletInfo) {
84 this.electrumClient = electrumClient ?? ElectrumClient();
85 this.walletInfo = walletInfo;
86 transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
87 }
88
89 + static bitcoin.HDWallet getAccountHDWallet(
90 + CryptoCurrency? currency,
91 + bitcoin.NetworkType networkType,
92 + Uint8List? seedBytes,
93 + String? xpub,
94 + DerivationInfo? derivationInfo) {
95 + if (seedBytes == null && xpub == null) {
96 + throw Exception(
97 + "To create a Wallet you need either a seed or an xpub. This should not happen");
98 + }
99 +
100 + if (seedBytes != null) {
101 + return currency == CryptoCurrency.bch
102 + ? bitcoinCashHDWallet(seedBytes)
103 + : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
104 + .derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'"));
105 + }
106 +
107 + return bitcoin.HDWallet.fromBase58(xpub!);
108 + }
109 +
110 static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
90 - bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0");
111 + bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'");
112
113 static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
114 inputsCount * 68 + outputsCounts * 34 + 10;
115
95 - final bitcoin.HDWallet hd;
96 - final String mnemonic;
116 + final bitcoin.HDWallet accountHD;
117 + final String? _mnemonic;
118 +
119 + bitcoin.HDWallet get hd => accountHD.derive(0);
120 final String? passphrase;
121
122 @override
@@ -123,10 +146,10 @@ abstract class ElectrumWalletBase
146 .map((addr) => scriptHash(addr.address, network: network))
147 .toList();
148
126 - String get xpub => hd.base58!;
149 + String get xpub => accountHD.base58!;
150
151 @override
129 - String get seed => mnemonic;
152 + String? get seed => _mnemonic;
153
154 bitcoin.NetworkType networkType;
155 BasedUtxoNetwork network;
@@ -203,7 +226,9 @@ abstract class ElectrumWalletBase
226 int credentialsAmount = 0,
227 }) async {
228 final utxos = <UtxoWithAddress>[];
206 - List<ECPrivate> privateKeys = [];
229 + final privateKeys = <ECPrivate>[];
230 + final publicKeys = <String, PublicKeyWithDerivationPath>{};
231 +
232 int allInputsAmount = 0;
233
234 bool spendsUnconfirmedTX = false;
@@ -217,12 +242,22 @@ abstract class ElectrumWalletBase
242 allInputsAmount += utx.value;
243
244 final address = addressTypeFromStr(utx.address, network);
220 - final privkey = generateECPrivate(
221 - hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
222 - index: utx.bitcoinAddressRecord.index,
223 - network: network);
245 + final hd =
246 + utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
247 + final derivationPath =
248 + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
249 + "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
250 + "/${utx.bitcoinAddressRecord.index}";
251 + final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
252
225 - privateKeys.add(privkey);
253 + publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
254 +
255 + if (!walletInfo.isHardwareWallet) {
256 + final privkey =
257 + generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
258 +
259 + privateKeys.add(privkey);
260 + }
261
262 utxos.add(
263 UtxoWithAddress(
@@ -233,7 +268,7 @@ abstract class ElectrumWalletBase
268 scriptType: _getScriptType(address),
269 ),
270 ownerDetails: UtxoAddressDetails(
236 - publicKey: privkey.getPublic().toHex(),
271 + publicKey: pubKeyHex,
272 address: address,
273 ),
274 ),
@@ -294,6 +329,7 @@ abstract class ElectrumWalletBase
329 return EstimatedTxResult(
330 utxos: utxos,
331 privateKeys: privateKeys,
332 + publicKeys: publicKeys,
333 fee: fee,
334 amount: amount,
335 isSendAll: true,
@@ -312,7 +348,9 @@ abstract class ElectrumWalletBase
348 bool? useUnconfirmed,
349 }) async {
350 final utxos = <UtxoWithAddress>[];
315 - List<ECPrivate> privateKeys = [];
351 + final privateKeys = <ECPrivate>[];
352 + final publicKeys = <String, PublicKeyWithDerivationPath>{};
353 +
354 int allInputsAmount = 0;
355 bool spendsUnconfirmedTX = false;
356
@@ -332,12 +370,23 @@ abstract class ElectrumWalletBase
370 leftAmount = leftAmount - utx.value;
371
372 final address = addressTypeFromStr(utx.address, network);
335 - final privkey = generateECPrivate(
336 - hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
337 - index: utx.bitcoinAddressRecord.index,
338 - network: network);
373
340 - privateKeys.add(privkey);
374 + final hd =
375 + utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
376 + final derivationPath =
377 + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
378 + "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
379 + "/${utx.bitcoinAddressRecord.index}";
380 + final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
381 +
382 + publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
383 +
384 + if (!walletInfo.isHardwareWallet) {
385 + final privkey =
386 + generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
387 +
388 + privateKeys.add(privkey);
389 + }
390
391 utxos.add(
392 UtxoWithAddress(
@@ -348,7 +397,7 @@ abstract class ElectrumWalletBase
397 scriptType: _getScriptType(address),
398 ),
399 ownerDetails: UtxoAddressDetails(
351 - publicKey: privkey.getPublic().toHex(),
400 + publicKey: pubKeyHex,
401 address: address,
402 ),
403 ),
@@ -490,6 +539,7 @@ abstract class ElectrumWalletBase
539 return EstimatedTxResult(
540 utxos: utxos,
541 privateKeys: privateKeys,
542 + publicKeys: publicKeys,
543 fee: fee,
544 amount: amount,
545 hasChange: true,
@@ -557,6 +607,35 @@ abstract class ElectrumWalletBase
607 );
608 }
609
610 + if (walletInfo.isHardwareWallet) {
611 + final transaction = await buildHardwareWalletTransaction(
612 + utxos: estimatedTx.utxos,
613 + outputs: outputs,
614 + publicKeys: estimatedTx.publicKeys,
615 + fee: BigInt.from(estimatedTx.fee),
616 + network: network,
617 + memo: estimatedTx.memo,
618 + outputOrdering: BitcoinOrdering.none,
619 + enableRBF: true,
620 + );
621 +
622 + return PendingBitcoinTransaction(
623 + transaction,
624 + type,
625 + electrumClient: electrumClient,
626 + amount: estimatedTx.amount,
627 + fee: estimatedTx.fee,
628 + feeRate: feeRateInt.toString(),
629 + network: network,
630 + hasChange: estimatedTx.hasChange,
631 + isSendAll: estimatedTx.isSendAll,
632 + hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot
633 + )..addListener((transaction) async {
634 + transactionHistory.addOne(transaction);
635 + await updateBalance();
636 + });
637 + }
638 +
639 BasedBitcoinTransacationBuilder txb;
640 if (network is BitcoinCashNetwork) {
641 txb = ForkedTransactionBuilder(
@@ -618,8 +697,22 @@ abstract class ElectrumWalletBase
697 }
698 }
699
700 + Future<BtcTransaction> buildHardwareWalletTransaction({
701 + required List<BitcoinBaseOutput> outputs,
702 + required BigInt fee,
703 + required BasedUtxoNetwork network,
704 + required List<UtxoWithAddress> utxos,
705 + required Map<String, PublicKeyWithDerivationPath> publicKeys,
706 + String? memo,
707 + bool enableRBF = false,
708 + BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
709 + BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
710 + }) async =>
711 + throw UnimplementedError();
712 +
713 String toJSON() => json.encode({
622 - 'mnemonic': mnemonic,
714 + 'mnemonic': _mnemonic,
715 + 'xpub': xpub,
716 'passphrase': passphrase ?? '',
717 'account_index': walletAddresses.currentReceiveAddressIndexByType,
718 'change_address_index': walletAddresses.currentChangeAddressIndexByType,
@@ -1263,7 +1356,7 @@ abstract class ElectrumWalletBase
1356 void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
1357
1358 @override
1266 - String signMessage(String message, {String? address = null}) {
1359 + Future<String> signMessage(String message, {String? address = null}) async {
1360 final index = address != null
1361 ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
1362 : null;
@@ -1286,6 +1379,9 @@ abstract class ElectrumWalletBase
1379
1380 return BitcoinNetwork.mainnet;
1381 }
1382 +
1383 + static String _hardenedDerivationPath(String derivationPath) =>
1384 + derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
1385 }
1386
1387 class EstimateTxParams {
@@ -1307,6 +1403,7 @@ class EstimatedTxResult {
1403 EstimatedTxResult({
1404 required this.utxos,
1405 required this.privateKeys,
1406 + required this.publicKeys,
1407 required this.fee,
1408 required this.amount,
1409 required this.hasChange,
@@ -1317,6 +1414,7 @@ class EstimatedTxResult {
1414
1415 final List<UtxoWithAddress> utxos;
1416 final List<ECPrivate> privateKeys;
1417 + final Map<String, PublicKeyWithDerivationPath> publicKeys; // PubKey to derivationPath
1418 final int fee;
1419 final int amount;
1420 final bool hasChange;
@@ -1325,6 +1423,13 @@ class EstimatedTxResult {
1423 final bool spendsUnconfirmedTX;
1424 }
1425
1426 +class PublicKeyWithDerivationPath {
1427 + const PublicKeyWithDerivationPath(this.publicKey, this.derivationPath);
1428 +
1429 + final String derivationPath;
1430 + final String publicKey;
1431 +}
1432 +
1433 BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
1434 if (network is BitcoinCashNetwork) {
1435 if (!address.startsWith("bitcoincash:") &&
cw_bitcoin/lib/electrum_wallet_snapshot.dart
+6 -2
@@ -13,6 +13,7 @@ class ElectrumWalletSnapshot {
13 required this.type,
14 required this.password,
15 required this.mnemonic,
16 + required this.xpub,
17 required this.addresses,
18 required this.balance,
19 required this.regularAddressIndex,
@@ -28,7 +29,8 @@ class ElectrumWalletSnapshot {
29 final WalletType type;
30 final String? addressPageType;
31
31 - String mnemonic;
32 + String? mnemonic;
33 + String? xpub;
34 List<BitcoinAddressRecord> addresses;
35 ElectrumBalance balance;
36 Map<String, int> regularAddressIndex;
@@ -43,7 +45,8 @@ class ElectrumWalletSnapshot {
45 final jsonSource = await read(path: path, password: password);
46 final data = json.decode(jsonSource) as Map;
47 final addressesTmp = data['addresses'] as List? ?? <Object>[];
46 - final mnemonic = data['mnemonic'] as String;
48 + final mnemonic = data['mnemonic'] as String?;
49 + final xpub = data['xpub'] as String?;
50 final passphrase = data['passphrase'] as String? ?? '';
51 final addresses = addressesTmp
52 .whereType<String>()
@@ -79,6 +82,7 @@ class ElectrumWalletSnapshot {
82 password: password,
83 passphrase: passphrase,
84 mnemonic: mnemonic,
85 + xpub: xpub,
86 addresses: addresses,
87 balance: balance,
88 regularAddressIndex: regularAddressIndexByType,
cw_bitcoin/lib/litecoin_wallet.dart
+3 -3
@@ -50,7 +50,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
50 initialRegularAddressIndex: initialRegularAddressIndex,
51 initialChangeAddressIndex: initialChangeAddressIndex,
52 mainHd: hd,
53 - sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
53 + sideHd: accountHD.derive(1),
54 network: network,
55 );
56 autorun((_) {
@@ -106,13 +106,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
106 final snp =
107 await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
108 return LitecoinWallet(
109 - mnemonic: snp.mnemonic,
109 + mnemonic: snp.mnemonic!,
110 password: password,
111 walletInfo: walletInfo,
112 unspentCoinsInfo: unspentCoinsInfo,
113 initialAddresses: snp.addresses,
114 initialBalance: snp.balance,
115 - seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
115 + seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
116 initialRegularAddressIndex: snp.regularAddressIndex,
117 initialChangeAddressIndex: snp.changeAddressIndex,
118 addressPageType: snp.addressPageType,
cw_bitcoin/lib/litecoin_wallet_service.dart
+6 -1
@@ -16,7 +16,7 @@ import 'package:bip39/bip39.dart' as bip39;
16 class LitecoinWalletService extends WalletService<
17 BitcoinNewWalletCredentials,
18 BitcoinRestoreWalletFromSeedCredentials,
19 - BitcoinRestoreWalletFromWIFCredentials> {
19 + BitcoinRestoreWalletFromWIFCredentials,BitcoinNewWalletCredentials> {
20 LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
21
22 final Box<WalletInfo> walletInfoSource;
@@ -94,6 +94,11 @@ class LitecoinWalletService extends WalletService<
94 await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
95 }
96
97 + @override
98 + Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
99 + throw UnimplementedError("Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
100 + }
101 +
102 @override
103 Future<LitecoinWallet> restoreFromKeys(
104 BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
cw_bitcoin/lib/psbt_transaction_builder.dart new
+96
@@ -0,0 +1,96 @@
1 +import 'dart:typed_data';
2 +
3 +import 'package:bitcoin_base/bitcoin_base.dart';
4 +import 'package:convert/convert.dart';
5 +import 'package:ledger_bitcoin/psbt.dart';
6 +
7 +class PSBTTransactionBuild {
8 + final PsbtV2 psbt = PsbtV2();
9 +
10 + PSBTTransactionBuild(
11 + {required List<PSBTReadyUtxoWithAddress> inputs, required List<BitcoinBaseOutput> outputs, bool enableRBF = true}) {
12 + psbt.setGlobalTxVersion(2);
13 + psbt.setGlobalInputCount(inputs.length);
14 + psbt.setGlobalOutputCount(outputs.length);
15 +
16 + for (var i = 0; i < inputs.length; i++) {
17 + final input = inputs[i];
18 +
19 + print(input.utxo.isP2tr());
20 + print(input.utxo.isSegwit());
21 + print(input.utxo.isP2shSegwit());
22 +
23 + psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
24 + psbt.setInputOutputIndex(i, input.utxo.vout);
25 + psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);
26 +
27 +
28 + if (input.utxo.isSegwit()) {
29 + setInputSegwit(i, input);
30 + } else if (input.utxo.isP2shSegwit()) {
31 + setInputP2shSegwit(i, input);
32 + } else if (input.utxo.isP2tr()) {
33 + // ToDo: (Konsti) Handle Taproot Inputs
34 + } else {
35 + setInputP2pkh(i, input);
36 + }
37 + }
38 +
39 + for (var i = 0; i < outputs.length; i++) {
40 + final output = outputs[i];
41 +
42 + if (output is BitcoinOutput) {
43 + psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes()));
44 + psbt.setOutputAmount(i, output.value.toInt());
45 + }
46 + }
47 + }
48 +
49 + void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) {
50 + psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
51 + psbt.setInputBip32Derivation(
52 + i,
53 + Uint8List.fromList(hex.decode(input.ownerPublicKey)),
54 + input.ownerMasterFingerprint,
55 + BIPPath.fromString(input.ownerDerivationPath).toPathArray());
56 + }
57 +
58 + void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) {
59 + psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
60 + psbt.setInputBip32Derivation(
61 + i,
62 + Uint8List.fromList(hex.decode(input.ownerPublicKey)),
63 + input.ownerMasterFingerprint,
64 + BIPPath.fromString(input.ownerDerivationPath).toPathArray());
65 +
66 + psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
67 + Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
68 + }
69 +
70 + void setInputP2shSegwit(int i, PSBTReadyUtxoWithAddress input) {
71 + psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
72 + psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)),
73 + input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray());
74 +
75 + psbt.setInputRedeemScript(
76 + i, Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
77 + psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
78 + Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
79 + }
80 +}
81 +
82 +class PSBTReadyUtxoWithAddress extends UtxoWithAddress {
83 + final String rawTx;
84 + final String ownerDerivationPath;
85 + final Uint8List ownerMasterFingerprint;
86 + final String ownerPublicKey;
87 +
88 + PSBTReadyUtxoWithAddress({
89 + required super.utxo,
90 + required this.rawTx,
91 + required super.ownerDetails,
92 + required this.ownerDerivationPath,
93 + required this.ownerMasterFingerprint,
94 + required this.ownerPublicKey,
95 + });
96 +}
cw_bitcoin/pubspec.lock
+74 -1
@@ -260,6 +260,14 @@ packages:
260 url: "https://pub.dev"
261 source: hosted
262 version: "2.2.4"
263 + dart_varuint_bitcoin:
264 + dependency: transitive
265 + description:
266 + name: dart_varuint_bitcoin
267 + sha256: "4f0ccc9733fb54148b9d3688eea822b7aaabf5cc00025998f8c09a1d45b31b4b"
268 + url: "https://pub.dev"
269 + source: hosted
270 + version: "1.0.3"
271 encrypt:
272 dependency: transitive
273 description:
@@ -313,6 +321,14 @@ packages:
321 url: "https://pub.dev"
322 source: hosted
323 version: "2.2.1+1"
324 + flutter_reactive_ble:
325 + dependency: transitive
326 + description:
327 + name: flutter_reactive_ble
328 + sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
329 + url: "https://pub.dev"
330 + source: hosted
331 + version: "5.3.1"
332 flutter_test:
333 dependency: "direct dev"
334 description: flutter
@@ -326,6 +342,14 @@ packages:
342 url: "https://pub.dev"
343 source: hosted
344 version: "4.0.0"
345 + functional_data:
346 + dependency: transitive
347 + description:
348 + name: functional_data
349 + sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
350 + url: "https://pub.dev"
351 + source: hosted
352 + version: "1.1.1"
353 glob:
354 dependency: transitive
355 description:
@@ -422,6 +446,31 @@ packages:
446 url: "https://pub.dev"
447 source: hosted
448 version: "4.8.1"
449 + ledger_bitcoin:
450 + dependency: "direct main"
451 + description:
452 + path: "."
453 + ref: HEAD
454 + resolved-ref: b6ed573cbeb57d5f0d39dfe4254bf9d15b620ab6
455 + url: "https://github.com/cake-tech/ledger-bitcoin.git"
456 + source: git
457 + version: "0.0.1"
458 + ledger_flutter:
459 + dependency: "direct main"
460 + description:
461 + name: ledger_flutter
462 + sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8
463 + url: "https://pub.dev"
464 + source: hosted
465 + version: "1.0.1"
466 + ledger_usb:
467 + dependency: transitive
468 + description:
469 + name: ledger_usb
470 + sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
471 + url: "https://pub.dev"
472 + source: hosted
473 + version: "1.0.0"
474 logging:
475 dependency: transitive
476 description:
@@ -582,6 +631,14 @@ packages:
631 url: "https://pub.dev"
632 source: hosted
633 version: "1.5.1"
634 + protobuf:
635 + dependency: transitive
636 + description:
637 + name: protobuf
638 + sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08"
639 + url: "https://pub.dev"
640 + source: hosted
641 + version: "2.1.0"
642 provider:
643 dependency: transitive
644 description:
@@ -606,6 +663,22 @@ packages:
663 url: "https://pub.dev"
664 source: hosted
665 version: "1.2.3"
666 + reactive_ble_mobile:
667 + dependency: transitive
668 + description:
669 + name: reactive_ble_mobile
670 + sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
671 + url: "https://pub.dev"
672 + source: hosted
673 + version: "5.3.1"
674 + reactive_ble_platform_interface:
675 + dependency: transitive
676 + description:
677 + name: reactive_ble_platform_interface
678 + sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
679 + url: "https://pub.dev"
680 + source: hosted
681 + version: "5.3.1"
682 rxdart:
683 dependency: "direct main"
684 description:
@@ -788,5 +861,5 @@ packages:
861 source: hosted
862 version: "3.1.2"
863 sdks:
791 - dart: ">=3.0.0 <4.0.0"
864 + dart: ">=3.0.6 <4.0.0"
865 flutter: ">=3.10.0"
cw_bitcoin/pubspec.yaml
+4
@@ -35,6 +35,10 @@ dependencies:
35 url: https://github.com/cake-tech/bitcoin_base.git
36 ref: cake-update-v2
37 blockchain_utils: ^2.1.1
38 + ledger_flutter: ^1.0.1
39 + ledger_bitcoin:
40 + git:
41 + url: https://github.com/cake-tech/ledger-bitcoin.git
42
43 dev_dependencies:
44 flutter_test:
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+4 -4
@@ -51,7 +51,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
51 initialRegularAddressIndex: initialRegularAddressIndex,
52 initialChangeAddressIndex: initialChangeAddressIndex,
53 mainHd: hd,
54 - sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
54 + sideHd: accountHD.derive(1),
55 network: network,
56 initialAddressPageType: addressPageType,
57 );
@@ -93,7 +93,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
93 final snp = await ElectrumWalletSnapshot.load(
94 name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
95 return BitcoinCashWallet(
96 - mnemonic: snp.mnemonic,
96 + mnemonic: snp.mnemonic!,
97 password: password,
98 walletInfo: walletInfo,
99 unspentCoinsInfo: unspentCoinsInfo,
@@ -118,7 +118,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
118 }
119 }).toList(),
120 initialBalance: snp.balance,
121 - seedBytes: await Mnemonic.toSeed(snp.mnemonic),
121 + seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
122 initialRegularAddressIndex: snp.regularAddressIndex,
123 initialChangeAddressIndex: snp.changeAddressIndex,
124 addressPageType: P2pkhAddressType.p2pkh,
@@ -166,7 +166,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
166 }
167
168 @override
169 - String signMessage(String message, {String? address = null}) {
169 + Future<String> signMessage(String message, {String? address = null}) async {
170 final index = address != null
171 ? walletAddresses.allAddresses
172 .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart
+6 -1
@@ -12,7 +12,7 @@ import 'package:collection/collection.dart';
12 import 'package:hive/hive.dart';
13
14 class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
15 - BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
15 + BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> {
16 BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
17
18 final Box<WalletInfo> walletInfoSource;
@@ -93,6 +93,11 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
93 await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
94 }
95
96 + @override
97 + Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) {
98 + throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
99 + }
100 +
101 @override
102 Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
103 // TODO: implement restoreFromKeys
cw_core/lib/hardware/device_connection_type.dart new
+28
@@ -0,0 +1,28 @@
1 +import 'package:cw_core/wallet_type.dart';
2 +
3 +enum DeviceConnectionType {
4 + usb,
5 + ble;
6 +
7 + static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
8 + [bool isIOS = false]) {
9 + switch (walletType) {
10 + case WalletType.bitcoin:
11 + case WalletType.ethereum:
12 + case WalletType.polygon:
13 + if (isIOS) return [DeviceConnectionType.ble];
14 + return [DeviceConnectionType.ble, DeviceConnectionType.usb];
15 + default:
16 + return [];
17 + }
18 + }
19 +
20 + String get iconString {
21 + switch (this) {
22 + case ble:
23 + return 'assets/images/bluetooth.png';
24 + case usb:
25 + return 'assets/images/usb.png';
26 + }
27 + }
28 +}
cw_core/lib/hardware/device_not_connected_exception.dart new
+7
@@ -0,0 +1,7 @@
1 +class DeviceNotConnectedException implements Exception {
2 + final String message;
3 +
4 + DeviceNotConnectedException({
5 + this.message = '',
6 + });
7 +}
cw_core/lib/hardware/hardware_account_data.dart new
+19
@@ -0,0 +1,19 @@
1 +import 'dart:typed_data';
2 +
3 +class HardwareAccountData {
4 + HardwareAccountData({
5 + required this.address,
6 + required this.accountIndex,
7 + required this.derivationPath,
8 + this.xpub,
9 + this.masterFingerprint,
10 + });
11 +
12 + final String address;
13 + final int accountIndex;
14 + final String derivationPath;
15 +
16 + // Bitcoin Specific
17 + final Uint8List? masterFingerprint;
18 + final String? xpub;
19 +}
cw_core/lib/hive_type_ids.dart
+1
@@ -17,3 +17,4 @@ const DERIVATION_TYPE_TYPE_ID = 15;
17 const SPL_TOKEN_TYPE_ID = 16;
18 const DERIVATION_INFO_TYPE_ID = 17;
19 const TRON_TOKEN_TYPE_ID = 18;
20 +const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
cw_core/lib/wallet_base.dart
+3 -1
@@ -56,6 +56,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
56
57 bool get isEnabledAutoGenerateSubaddress => false;
58
59 + bool get isHardwareWallet => walletInfo.isHardwareWallet;
60 +
61 Future<void> connectToNode({required Node node});
62
63 // there is a default definition here because only coins with a pow node (nano based) need to override this
@@ -88,7 +90,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
90
91 Future<void> renameWalletFiles(String newWalletName);
92
91 - String signMessage(String message, {String? address = null}) => throw UnimplementedError();
93 + Future<String> signMessage(String message, {String? address = null}) => throw UnimplementedError();
94
95 bool? isTestnet;
96 }
cw_core/lib/wallet_credentials.dart
+2
@@ -9,6 +9,7 @@ abstract class WalletCredentials {
9 this.password,
10 this.passphrase,
11 this.derivationInfo,
12 + this.hardwareWalletType,
13 }) {
14 if (this.walletInfo != null && derivationInfo != null) {
15 this.walletInfo!.derivationInfo = derivationInfo;
@@ -22,4 +23,5 @@ abstract class WalletCredentials {
23 String? passphrase;
24 WalletInfo? walletInfo;
25 DerivationInfo? derivationInfo;
26 + HardwareWalletType? hardwareWalletType;
27 }
cw_core/lib/wallet_info.dart
+18 -3
@@ -1,4 +1,5 @@
1 import 'dart:async';
2 +
3 import 'package:cw_core/address_info.dart';
4 import 'package:cw_core/hive_type_ids.dart';
5 import 'package:cw_core/wallet_type.dart';
@@ -20,6 +21,12 @@ enum DerivationType {
21 electrum,
22 }
23
24 +@HiveType(typeId: HARDWARE_WALLET_TYPE_TYPE_ID)
25 +enum HardwareWalletType {
26 + @HiveField(0)
27 + ledger,
28 +}
29 +
30 @HiveType(typeId: DerivationInfo.typeId)
31 class DerivationInfo extends HiveObject {
32 DerivationInfo({
@@ -40,7 +47,7 @@ class DerivationInfo extends HiveObject {
47 @HiveField(1, defaultValue: '')
48 String balance;
49
43 - @HiveField(2)
50 + @HiveField(2, defaultValue: 0)
51 int transactionsCount;
52
53 @HiveField(3)
@@ -71,8 +78,9 @@ class WalletInfo extends HiveObject {
78 this.yatEid,
79 this.yatLastUsedAddressRaw,
80 this.showIntroCakePayCard,
74 - this.derivationInfo)
75 - : _yatLastUsedAddressController = StreamController<String>.broadcast();
81 + this.derivationInfo,
82 + this.hardwareWalletType,
83 + ): _yatLastUsedAddressController = StreamController<String>.broadcast();
84
85 factory WalletInfo.external({
86 required String id,
@@ -88,6 +96,7 @@ class WalletInfo extends HiveObject {
96 String yatEid = '',
97 String yatLastUsedAddressRaw = '',
98 DerivationInfo? derivationInfo,
99 + HardwareWalletType? hardwareWalletType,
100 }) {
101 return WalletInfo(
102 id,
@@ -103,6 +112,7 @@ class WalletInfo extends HiveObject {
112 yatLastUsedAddressRaw,
113 showIntroCakePayCard,
114 derivationInfo,
115 + hardwareWalletType,
116 );
117 }
118
@@ -171,6 +181,9 @@ class WalletInfo extends HiveObject {
181 @HiveField(20)
182 DerivationInfo? derivationInfo;
183
184 + @HiveField(21)
185 + HardwareWalletType? hardwareWalletType;
186 +
187 String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
188
189 set yatLastUsedAddress(String address) {
@@ -187,6 +200,8 @@ class WalletInfo extends HiveObject {
200 return showIntroCakePayCard!;
201 }
202
203 + bool get isHardwareWallet => hardwareWalletType != null;
204 +
205 DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
206
207 Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
cw_core/lib/wallet_service.dart
+3 -1
@@ -6,11 +6,13 @@ import 'package:cw_core/wallet_credentials.dart';
6 import 'package:cw_core/wallet_type.dart';
7
8 abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
9 - RFK extends WalletCredentials> {
9 + RFK extends WalletCredentials, RFH extends WalletCredentials> {
10 WalletType getType();
11
12 Future<WalletBase> create(N credentials, {bool? isTestnet});
13
14 + Future<WalletBase> restoreFromHardwareWallet(RFH credentials);
15 +
16 Future<WalletBase> restoreFromSeed(RFS credentials, {bool? isTestnet});
17
18 Future<WalletBase> restoreFromKeys(RFK credentials, {bool? isTestnet});
cw_ethereum/lib/ethereum_wallet_service.dart
+25 -1
@@ -1,11 +1,12 @@
1 +import 'package:bip39/bip39.dart' as bip39;
2 import 'package:cw_core/wallet_base.dart';
3 +import 'package:cw_core/wallet_info.dart';
4 import 'package:cw_core/wallet_type.dart';
5 import 'package:cw_ethereum/ethereum_client.dart';
6 import 'package:cw_ethereum/ethereum_mnemonics_exception.dart';
7 import 'package:cw_ethereum/ethereum_wallet.dart';
8 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
9 import 'package:cw_evm/evm_chain_wallet_service.dart';
8 -import 'package:bip39/bip39.dart' as bip39;
10
11 class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
12 EthereumWalletService(super.walletInfoSource, {required this.client});
@@ -82,6 +83,29 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
83 await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
84 }
85
86 + @override
87 + Future<EthereumWallet> restoreFromHardwareWallet(
88 + EVMChainRestoreWalletFromHardware credentials) async {
89 + credentials.walletInfo!.derivationInfo = DerivationInfo(
90 + derivationType: DerivationType.bip39,
91 + derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"
92 + );
93 + credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
94 + credentials.walletInfo!.address = credentials.hwAccountData.address;
95 +
96 + final wallet = EthereumWallet(
97 + walletInfo: credentials.walletInfo!,
98 + password: credentials.password!,
99 + client: client,
100 + );
101 +
102 + await wallet.init();
103 + wallet.addInitialTokens();
104 + await wallet.save();
105 +
106 + return wallet;
107 + }
108 +
109 @override
110 Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
111 {bool? isTestnet}) async {
cw_ethereum/pubspec.yaml
+6
@@ -19,6 +19,12 @@ dependencies:
19 path: ../cw_evm
20 hive: ^2.2.3
21
22 +dependency_overrides:
23 + web3dart:
24 + git:
25 + url: https://github.com/cake-tech/web3dart.git
26 + ref: cake
27 +
28 dev_dependencies:
29 flutter_test:
30 sdk: flutter
cw_evm/lib/contract/erc20.dart new
+209
@@ -0,0 +1,209 @@
1 +import 'dart:typed_data';
2 +
3 +import 'package:web3dart/web3dart.dart' as web3;
4 +
5 +final _contractAbi = web3.ContractAbi.fromJson(
6 + '[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]',
7 + 'Erc20');
8 +
9 +/// Interface of the ERC20 standard as defined in the EIP.
10 +class ERC20 extends web3.GeneratedContract {
11 + /// Constructor.
12 + ERC20({
13 + required web3.EthereumAddress address,
14 + required web3.Web3Client client,
15 + int? chainId,
16 + }) : super(web3.DeployedContract(_contractAbi, address), client, chainId);
17 +
18 + /// Returns the remaining number of tokens that [spender] will be allowed to spend on behalf of [owner] through [transferFrom]. This is zero by default. This value changes when [approve] or [transferFrom] are called.
19 + ///
20 + /// The optional [atBlock] parameter can be used to view historical data. When
21 + /// set, the function will be evaluated in the specified block. By default, the
22 + /// latest on-chain block will be used.
23 + Future<BigInt> allowance(
24 + web3.EthereumAddress owner,
25 + web3.EthereumAddress spender, {
26 + web3.BlockNum? atBlock,
27 + }) async {
28 + final function = self.abi.functions[0];
29 + assert(checkSignature(function, 'dd62ed3e'));
30 + final params = [owner, spender];
31 + final response = await read(function, params, atBlock);
32 + return (response[0] as BigInt);
33 + }
34 +
35 + /// Sets [amount] as the allowance of [spender] over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an [Approval] event.
36 + ///
37 + /// The optional [transaction] parameter can be used to override parameters
38 + /// like the gas price, nonce and max gas. The `data` and `to` fields will be
39 + /// set by the contract.
40 + Future<Uint8List> approve(
41 + web3.EthereumAddress spender,
42 + BigInt amount, {
43 + required web3.Credentials credentials,
44 + web3.Transaction? transaction,
45 + }) async {
46 + final function = self.abi.functions[1];
47 + assert(checkSignature(function, '095ea7b3'));
48 + final params = [spender, amount];
49 + return writeRaw(credentials, transaction, function, params);
50 + }
51 +
52 + /// Returns the amount of tokens owned by [account].
53 + ///
54 + /// The optional [atBlock] parameter can be used to view historical data. When
55 + /// set, the function will be evaluated in the specified block. By default, the
56 + /// latest on-chain block will be used.
57 + Future<BigInt> balanceOf(
58 + web3.EthereumAddress account, {
59 + web3.BlockNum? atBlock,
60 + }) async {
61 + final function = self.abi.functions[2];
62 + assert(checkSignature(function, '70a08231'));
63 + final params = [account];
64 + final response = await read(function, params, atBlock);
65 + return (response[0] as BigInt);
66 + }
67 +
68 + /// Returns the decimal precision of the token.
69 + ///
70 + /// The optional [atBlock] parameter can be used to view historical data. When
71 + /// set, the function will be evaluated in the specified block. By default, the
72 + /// latest on-chain block will be used.
73 + Future<BigInt> decimals({web3.BlockNum? atBlock}) async {
74 + final function = self.abi.functions[3];
75 + assert(checkSignature(function, '313ce567'));
76 + final params = [];
77 + final response = await read(function, params, atBlock);
78 + return (response[0] as BigInt);
79 + }
80 +
81 + /// Returns the name of the token.
82 + ///
83 + /// The optional [atBlock] parameter can be used to view historical data. When
84 + /// set, the function will be evaluated in the specified block. By default, the
85 + /// latest on-chain block will be used.
86 + Future<String> name({web3.BlockNum? atBlock}) async {
87 + final function = self.abi.functions[4];
88 + assert(checkSignature(function, '06fdde03'));
89 + final params = [];
90 + final response = await read(function, params, atBlock);
91 + return (response[0] as String);
92 + }
93 +
94 + /// Returns the symbol of the token.
95 + ///
96 + /// The optional [atBlock] parameter can be used to view historical data. When
97 + /// set, the function will be evaluated in the specified block. By default, the
98 + /// latest on-chain block will be used.
99 + Future<String> symbol({web3.BlockNum? atBlock}) async {
100 + final function = self.abi.functions[5];
101 + assert(checkSignature(function, '95d89b41'));
102 + final params = [];
103 + final response = await read(function, params, atBlock);
104 + return (response[0] as String);
105 + }
106 +
107 + /// Returns the amount of tokens in existence.
108 + ///
109 + /// The optional [atBlock] parameter can be used to view historical data. When
110 + /// set, the function will be evaluated in the specified block. By default, the
111 + /// latest on-chain block will be used.
112 + Future<BigInt> totalSupply({web3.BlockNum? atBlock}) async {
113 + final function = self.abi.functions[6];
114 + assert(checkSignature(function, '18160ddd'));
115 + final params = [];
116 + final response = await read(function, params, atBlock);
117 + return (response[0] as BigInt);
118 + }
119 +
120 + /// Moves [amount] tokens from the caller's account to [recipient]. Returns a boolean value indicating whether the operation succeeded. Emits a [Transfer] event.
121 + ///
122 + /// The optional [transaction] parameter can be used to override parameters
123 + /// like the gas price, nonce and max gas. The `data` and `to` fields will be
124 + /// set by the contract.
125 + Future<Uint8List> transfer(
126 + web3.EthereumAddress recipient,
127 + BigInt amount, {
128 + required web3.Credentials credentials,
129 + web3.Transaction? transaction,
130 + }) async {
131 + final function = self.abi.functions[7];
132 + assert(checkSignature(function, 'a9059cbb'));
133 + final params = [recipient, amount];
134 + return writeRaw(credentials, transaction, function, params);
135 + }
136 +
137 + /// Moves [amount] tokens from [sender] to [recipient] using the allowance mechanism. [amount] is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a [Transfer] event.
138 + ///
139 + /// The optional [transaction] parameter can be used to override parameters
140 + /// like the gas price, nonce and max gas. The `data` and `to` fields will be
141 + /// set by the contract.
142 + Future<Uint8List> transferFrom(web3.EthereumAddress sender,
143 + web3.EthereumAddress recipient, BigInt amount,
144 + {required web3.Credentials credentials,
145 + web3.Transaction? transaction}) async {
146 + final function = self.abi.functions[8];
147 + assert(checkSignature(function, '23b872dd'));
148 + final params = [sender, recipient, amount];
149 + return writeRaw(credentials, transaction, function, params);
150 + }
151 +
152 + /// Returns a live stream of all Approval events emitted by this contract.
153 + Stream<Approval> approvalEvents(
154 + {web3.BlockNum? fromBlock, web3.BlockNum? toBlock}) {
155 + final event = self.event('Approval');
156 + final filter = web3.FilterOptions.events(
157 + contract: self, event: event, fromBlock: fromBlock, toBlock: toBlock);
158 + return client.events(filter).map((web3.FilterEvent result) {
159 + final decoded = event.decodeResults(result.topics!, result.data!);
160 + return Approval._(decoded);
161 + });
162 + }
163 +
164 + /// Returns a live stream of all Transfer events emitted by this contract.
165 + Stream<Transfer> transferEvents(
166 + {web3.BlockNum? fromBlock, web3.BlockNum? toBlock}) {
167 + final event = self.event('Transfer');
168 + final filter = web3.FilterOptions.events(
169 + contract: self, event: event, fromBlock: fromBlock, toBlock: toBlock);
170 + return client.events(filter).map((web3.FilterEvent result) {
171 + final decoded = event.decodeResults(result.topics!, result.data!);
172 + return Transfer._(decoded);
173 + });
174 + }
175 +}
176 +
177 +/// Emitted when the allowance of a [spender] for an [owner] is set by a call to [ERC20.approve]. [value] is the new allowance.
178 +class Approval {
179 + Approval._(List<dynamic> response)
180 + : owner = (response[0] as web3.EthereumAddress),
181 + spender = (response[1] as web3.EthereumAddress),
182 + value = (response[2] as BigInt);
183 +
184 + /// The owner address.
185 + final web3.EthereumAddress owner;
186 +
187 + /// The spender address.
188 + final web3.EthereumAddress spender;
189 +
190 + /// Value.
191 + final BigInt value;
192 +}
193 +
194 +/// Emitted when [value] tokens are moved from one account ([from]) to another ([to]). Note that [value] may be zero.
195 +class Transfer {
196 + Transfer._(List<dynamic> response)
197 + : from = (response[0] as web3.EthereumAddress),
198 + to = (response[1] as web3.EthereumAddress),
199 + value = (response[2] as BigInt);
200 +
201 + /// From address.
202 + final web3.EthereumAddress from;
203 +
204 + /// To address.
205 + final web3.EthereumAddress to;
206 +
207 + /// Value.
208 + final BigInt value;
209 +}
cw_evm/lib/evm_chain_client.dart
+25 -26
@@ -2,21 +2,20 @@ import 'dart:async';
2 import 'dart:convert';
3 import 'dart:developer';
4
5 -import 'package:cw_core/node.dart';
6 -import 'package:cw_core/erc20_token.dart';
5 import 'package:cw_core/crypto_currency.dart';
8 -
9 -import 'package:cw_evm/evm_erc20_balance.dart';
6 +import 'package:cw_core/erc20_token.dart';
7 +import 'package:cw_core/node.dart';
8 import 'package:cw_evm/evm_chain_transaction_model.dart';
11 -import 'package:cw_evm/pending_evm_chain_transaction.dart';
9 import 'package:cw_evm/evm_chain_transaction_priority.dart';
10 +import 'package:cw_evm/evm_erc20_balance.dart';
11 +import 'package:cw_evm/pending_evm_chain_transaction.dart';
12 import 'package:cw_evm/.secrets.g.dart' as secrets;
13 import 'package:flutter/services.dart';
15 -
14 +import 'package:hex/hex.dart' as hex;
15 import 'package:http/http.dart';
17 -import 'package:erc20/erc20.dart';
16 import 'package:web3dart/web3dart.dart';
19 -import 'package:hex/hex.dart' as hex;
17 +
18 +import 'contract/erc20.dart';
19
20 abstract class EVMChainClient {
21 final httpClient = Client();
@@ -82,7 +81,7 @@ abstract class EVMChainClient {
81 }
82
83 Future<PendingEVMChainTransaction> signTransaction({
85 - required EthPrivateKey privateKey,
84 + required Credentials privateKey,
85 required String toAddress,
86 required BigInt amount,
87 required int gas,
@@ -96,8 +95,7 @@ abstract class EVMChainClient {
95 currency == CryptoCurrency.maticpoly ||
96 contractAddress != null);
97
99 - bool isEVMCompatibleChain =
100 - currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
98 + bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
99
100 final price = _client!.getGasPrice();
101
@@ -105,17 +103,16 @@ abstract class EVMChainClient {
103 from: privateKey.address,
104 to: EthereumAddress.fromHex(toAddress),
105 maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
108 - amount: isEVMCompatibleChain ? EtherAmount.inWei(amount) : EtherAmount.zero(),
106 + amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
107 data: data != null ? hexToBytes(data) : null,
108 );
109
112 - final signedTransaction =
113 - await _client!.signTransaction(privateKey, transaction, chainId: chainId);
110 + Uint8List signedTransaction;
111
112 final Function _sendTransaction;
113
117 - if (isEVMCompatibleChain) {
118 - _sendTransaction = () async => await sendTransaction(signedTransaction);
114 + if (isNativeToken) {
115 + signedTransaction = await _client!.signTransaction(privateKey, transaction, chainId: chainId);
116 } else {
117 final erc20 = ERC20(
118 client: _client!,
@@ -123,16 +120,17 @@ abstract class EVMChainClient {
120 chainId: chainId,
121 );
122
126 - _sendTransaction = () async {
127 - await erc20.transfer(
128 - EthereumAddress.fromHex(toAddress),
129 - amount,
130 - credentials: privateKey,
131 - transaction: transaction,
132 - );
133 - };
123 + signedTransaction = await erc20.transfer(
124 + EthereumAddress.fromHex(toAddress),
125 + amount,
126 + credentials: privateKey,
127 + transaction: transaction,
128 + );
129 }
130
131 + _sendTransaction = () async => await sendTransaction(signedTransaction);
132 +
133 +
134 return PendingEVMChainTransaction(
135 signedTransaction: signedTransaction,
136 amount: amount.toString(),
@@ -158,8 +156,9 @@ abstract class EVMChainClient {
156 );
157 }
158
161 - Future<String> sendTransaction(Uint8List signedTransaction) async =>
162 - await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
159 + Future<String> sendTransaction(Uint8List signedTransaction) async {
160 + return await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
161 + }
162
163 Future getTransactionDetails(String transactionHash) async {
164 // Wait for the transaction receipt to become available
cw_evm/lib/evm_chain_exceptions.dart
+2
@@ -6,6 +6,8 @@ class EVMChainTransactionCreationException implements Exception {
6 EVMChainTransactionCreationException(CryptoCurrency currency)
7 : exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
8
9 + EVMChainTransactionCreationException.fromMessage(this.exceptionMessage);
10 +
11 @override
12 String toString() => exceptionMessage;
13 }
cw_evm/lib/evm_chain_hardware_wallet_service.dart new
+35
@@ -0,0 +1,35 @@
1 +import 'dart:async';
2 +
3 +import 'package:cw_core/hardware/hardware_account_data.dart';
4 +import 'package:ledger_ethereum/ledger_ethereum.dart';
5 +import 'package:ledger_flutter/ledger_flutter.dart';
6 +
7 +class EVMChainHardwareWalletService {
8 + EVMChainHardwareWalletService(this.ledger, this.device);
9 +
10 + final Ledger ledger;
11 + final LedgerDevice device;
12 +
13 + Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
14 + final ethereumLedgerApp = EthereumLedgerApp(ledger);
15 +
16 + final version = await ethereumLedgerApp.getVersion(device);
17 +
18 + final accounts = <HardwareAccountData>[];
19 + final indexRange = List.generate(limit, (i) => i + index);
20 +
21 + for (final i in indexRange) {
22 + final derivationPath = "m/44'/60'/$i'/0/0";
23 + final address =
24 + await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath);
25 +
26 + accounts.add(HardwareAccountData(
27 + address: address.first,
28 + accountIndex: i,
29 + derivationPath: derivationPath,
30 + ));
31 + }
32 +
33 + return accounts;
34 + }
35 +}
cw_evm/lib/evm_chain_transaction_credentials.dart
+1
@@ -1,6 +1,7 @@
1 import 'package:cw_core/crypto_currency.dart';
2 import 'package:cw_core/output_info.dart';
3 import 'package:cw_evm/evm_chain_transaction_priority.dart';
4 +import 'package:ledger_flutter/ledger_flutter.dart';
5
6 class EVMChainTransactionCredentials {
7 EVMChainTransactionCredentials(
cw_evm/lib/evm_chain_wallet.dart
+25 -11
@@ -25,6 +25,7 @@ import 'package:cw_evm/evm_chain_transaction_history.dart';
25 import 'package:cw_evm/evm_chain_transaction_model.dart';
26 import 'package:cw_evm/evm_chain_transaction_priority.dart';
27 import 'package:cw_evm/evm_chain_wallet_addresses.dart';
28 +import 'package:cw_evm/evm_ledger_credentials.dart';
29 import 'package:cw_evm/file.dart';
30 import 'package:hex/hex.dart';
31 import 'package:hive/hive.dart';
@@ -83,9 +84,9 @@ abstract class EVMChainWalletBase
84
85 late final Box<Erc20Token> evmChainErc20TokensBox;
86
86 - late final EthPrivateKey _evmChainPrivateKey;
87 + late final Credentials _evmChainPrivateKey;
88
88 - EthPrivateKey get evmChainPrivateKey => _evmChainPrivateKey;
89 + Credentials get evmChainPrivateKey => _evmChainPrivateKey;
90
91 late EVMChainClient _client;
92
@@ -141,12 +142,18 @@ abstract class EVMChainWalletBase
142
143 await walletAddresses.init();
144 await transactionHistory.init();
144 - _evmChainPrivateKey = await getPrivateKey(
145 - mnemonic: _mnemonic,
146 - privateKey: _hexPrivateKey,
147 - password: _password,
148 - );
149 - walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
145 +
146 + if (walletInfo.isHardwareWallet) {
147 + _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
148 + walletAddresses.address = walletInfo.address;
149 + } else {
150 + _evmChainPrivateKey = await getPrivateKey(
151 + mnemonic: _mnemonic,
152 + privateKey: _hexPrivateKey,
153 + password: _password,
154 + );
155 + walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
156 + }
157 await save();
158 }
159
@@ -283,6 +290,11 @@ abstract class EVMChainWalletBase
290 }
291 }
292
293 + if (transactionCurrency is Erc20Token && isHardwareWallet) {
294 + await (_evmChainPrivateKey as EvmLedgerCredentials)
295 + .provideERC20Info(transactionCurrency.contractAddress, _client.chainId);
296 + }
297 +
298 final pendingEVMChainTransaction = await _client.signTransaction(
299 privateKey: _evmChainPrivateKey,
300 toAddress: _credentials.outputs.first.isParsedAddress
@@ -377,7 +389,9 @@ abstract class EVMChainWalletBase
389 String? get seed => _mnemonic;
390
391 @override
380 - String get privateKey => HEX.encode(_evmChainPrivateKey.privateKey);
392 + String? get privateKey => evmChainPrivateKey is EthPrivateKey
393 + ? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
394 + : null;
395
396 Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
397
@@ -535,8 +549,8 @@ abstract class EVMChainWalletBase
549 }
550
551 @override
538 - String signMessage(String message, {String? address}) =>
539 - bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
552 + Future<String> signMessage(String message, {String? address}) async =>
553 + bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
554
555 Web3Client? getWeb3Client() => _client.getWeb3Client();
556 }
cw_evm/lib/evm_chain_wallet_creation_credentials.dart
+11
@@ -1,3 +1,4 @@
1 +import 'package:cw_core/hardware/hardware_account_data.dart';
2 import 'package:cw_core/wallet_credentials.dart';
3 import 'package:cw_core/wallet_info.dart';
4
@@ -27,3 +28,13 @@ class EVMChainRestoreWalletFromPrivateKey extends WalletCredentials {
28
29 final String privateKey;
30 }
31 +
32 +class EVMChainRestoreWalletFromHardware extends WalletCredentials {
33 + EVMChainRestoreWalletFromHardware({
34 + required String name,
35 + required this.hwAccountData,
36 + WalletInfo? walletInfo,
37 + }) : super(name: name, walletInfo: walletInfo);
38 +
39 + final HardwareAccountData hwAccountData;
40 +}
cw_evm/lib/evm_chain_wallet_service.dart
+5 -1
@@ -13,7 +13,8 @@ import 'package:hive/hive.dart';
13 abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletService<
14 EVMChainNewWalletCredentials,
15 EVMChainRestoreWalletFromSeedCredentials,
16 - EVMChainRestoreWalletFromPrivateKey> {
16 + EVMChainRestoreWalletFromPrivateKey,
17 + EVMChainRestoreWalletFromHardware> {
18 EVMChainWalletService(this.walletInfoSource);
19
20 final Box<WalletInfo> walletInfoSource;
@@ -24,6 +25,9 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
25 @override
26 Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
27
28 + @override
29 + Future<T> restoreFromHardwareWallet(EVMChainRestoreWalletFromHardware credentials);
30 +
31 @override
32 Future<T> openWallet(String name, String password);
33
cw_evm/lib/evm_ledger_credentials.dart new
+103
@@ -0,0 +1,103 @@
1 +import 'dart:async';
2 +import 'dart:typed_data';
3 +
4 +import 'package:cw_core/hardware/device_not_connected_exception.dart';
5 +import 'package:ledger_ethereum/ledger_ethereum.dart';
6 +import 'package:ledger_flutter/ledger_flutter.dart';
7 +import 'package:web3dart/crypto.dart';
8 +import 'package:web3dart/web3dart.dart';
9 +
10 +class EvmLedgerCredentials extends CredentialsWithKnownAddress {
11 + final String _address;
12 +
13 + Ledger? ledger;
14 + LedgerDevice? ledgerDevice;
15 + EthereumLedgerApp? ethereumLedgerApp;
16 +
17 + EvmLedgerCredentials(this._address);
18 +
19 + @override
20 + EthereumAddress get address => EthereumAddress.fromHex(_address);
21 +
22 + void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) {
23 + ledger = setLedger;
24 + ledgerDevice = setLedgerDevice;
25 + ethereumLedgerApp =
26 + EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
27 + }
28 +
29 + @override
30 + MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
31 + throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
32 +
33 + @override
34 + Future<MsgSignature> signToSignature(Uint8List payload,
35 + {int? chainId, bool isEIP1559 = false}) async {
36 + if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) {
37 + throw DeviceNotConnectedException();
38 + }
39 +
40 + final sig = await ethereumLedgerApp!.signTransaction(device, payload);
41 +
42 + final v = sig[0].toInt();
43 + final r = bytesToHex(sig.sublist(1, 1 + 32));
44 + final s = bytesToHex(sig.sublist(1 + 32, 1 + 32 + 32));
45 +
46 + var truncChainId = chainId ?? 1;
47 + while (truncChainId.bitLength > 32) {
48 + truncChainId >>= 8;
49 + }
50 +
51 + final truncTarget = truncChainId * 2 + 35;
52 +
53 + int parity = v;
54 + if (truncTarget & 0xff == v) {
55 + parity = 0;
56 + } else if ((truncTarget + 1) & 0xff == v) {
57 + parity = 1;
58 + }
59 +
60 + // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
61 + int chainIdV;
62 + if (isEIP1559) {
63 + chainIdV = v;
64 + } else {
65 + chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
66 + }
67 +
68 + return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
69 + }
70 +
71 + @override
72 + Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
73 + if (isNotConnected) throw DeviceNotConnectedException();
74 +
75 + final sig = await ethereumLedgerApp!.signMessage(device, payload);
76 +
77 + final r = sig.sublist(1, 1 + 32);
78 + final s = sig.sublist(1 + 32, 1 + 32 + 32);
79 + final v = [sig[0]];
80 +
81 + // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
82 + return Uint8List.fromList(r + s + v);
83 + }
84 +
85 + @override
86 + Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
87 + throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
88 +
89 + Future<void> provideERC20Info(String erc20ContractAddress, int chainId) async {
90 + if (isNotConnected) throw DeviceNotConnectedException();
91 +
92 + try {
93 + await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device,
94 + erc20ContractAddress: erc20ContractAddress, chainId: chainId);
95 + } on LedgerException catch (e) {
96 + if (e.errorCode != -28672) rethrow;
97 + }
98 + }
99 +
100 + bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null;
101 +
102 + LedgerDevice get device => ledgerDevice ?? ledger!.devices.first;
103 +}
cw_evm/pubspec.yaml
+14 -1
@@ -13,7 +13,6 @@ dependencies:
13 flutter:
14 sdk: flutter
15 web3dart: ^2.7.1
16 - erc20: ^1.0.1
16 bip39: ^1.0.6
17 bip32: ^2.0.0
18 hex: ^0.2.0
@@ -23,6 +22,20 @@ dependencies:
22 shared_preferences: ^2.0.15
23 cw_core:
24 path: ../cw_core
25 + ledger_flutter: ^1.0.1
26 + ledger_ethereum:
27 + git:
28 + url: https://github.com/cake-tech/ledger-ethereum.git
29 +
30 +dependency_overrides:
31 + web3dart:
32 + git:
33 + url: https://github.com/cake-tech/web3dart.git
34 + ref: cake
35 + ledger_flutter:
36 + git:
37 + url: https://github.com/cake-tech/ledger-flutter.git
38 + ref: cake
39
40 dev_dependencies:
41 flutter_test:
cw_haven/lib/haven_wallet_service.dart
+7 -1
@@ -56,7 +56,8 @@ class HavenRestoreWalletFromKeysCredentials extends WalletCredentials {
56 class HavenWalletService extends WalletService<
57 HavenNewWalletCredentials,
58 HavenRestoreWalletFromSeedCredentials,
59 - HavenRestoreWalletFromKeysCredentials> {
59 + HavenRestoreWalletFromKeysCredentials,
60 + HavenNewWalletCredentials> {
61 HavenWalletService(this.walletInfoSource);
62
63 final Box<WalletInfo> walletInfoSource;
@@ -172,6 +173,11 @@ class HavenWalletService extends WalletService<
173 await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
174 }
175
176 + @override
177 + Future<HavenWallet> restoreFromHardwareWallet(HavenNewWalletCredentials credentials) {
178 + throw UnimplementedError("Restoring a Haven wallet from a hardware wallet is not yet supported!");
179 + }
180 +
181 @override
182 Future<HavenWallet> restoreFromKeys(
183 HavenRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
cw_monero/lib/api/signatures.dart
+3
@@ -17,6 +17,9 @@ typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, P
17 typedef restore_wallet_from_spend_key = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
18 Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
19
20 +// typedef restore_wallet_from_device = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
21 +// Int32, Int64, Pointer<Utf8>);
22 +
23 typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
24
25 typedef load_wallet = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Int8);
cw_monero/lib/api/types.dart
+3
@@ -17,6 +17,9 @@ typedef RestoreWalletFromKeys = int Function(Pointer<Utf8>, Pointer<Utf8>,
17 typedef RestoreWalletFromSpendKey = int Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
18 Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
19
20 +typedef RestoreWalletFromDevice = int Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
21 + int, int, Pointer<Utf8>);
22 +
23 typedef IsWalletExist = int Function(Pointer<Utf8>);
24
25 typedef LoadWallet = int Function(Pointer<Utf8>, Pointer<Utf8>, int);
cw_monero/lib/api/wallet_manager.dart
+37
@@ -31,6 +31,11 @@ final restoreWalletFromSpendKeyNative = moneroApi
31 'restore_wallet_from_spend_key')
32 .asFunction<RestoreWalletFromSpendKey>();
33
34 +// final restoreWalletFromDeviceNative = moneroApi
35 +// .lookup<NativeFunction<restore_wallet_from_device>>(
36 +// 'restore_wallet_from_device')
37 +// .asFunction<RestoreWalletFromDevice>();
38 +
39 final isWalletExistNative = moneroApi
40 .lookup<NativeFunction<is_wallet_exist>>('is_wallet_exist')
41 .asFunction<IsWalletExist>();
@@ -185,6 +190,38 @@ void restoreWalletFromSpendKeySync(
190 }
191 }
192
193 +// void restoreMoneroWalletFromDevice(
194 +// {required String path,
195 +// required String password,
196 +// required String deviceName,
197 +// int nettype = 0,
198 +// int restoreHeight = 0}) {
199 +//
200 +// final pathPointer = path.toNativeUtf8();
201 +// final passwordPointer = password.toNativeUtf8();
202 +// final deviceNamePointer = deviceName.toNativeUtf8();
203 +// final errorMessagePointer = ''.toNativeUtf8();
204 +//
205 +// final isWalletRestored = restoreWalletFromDeviceNative(
206 +// pathPointer,
207 +// passwordPointer,
208 +// deviceNamePointer,
209 +// nettype,
210 +// restoreHeight,
211 +// errorMessagePointer) != 0;
212 +//
213 +// calloc.free(pathPointer);
214 +// calloc.free(passwordPointer);
215 +//
216 +// storeSync();
217 +//
218 +// if (!isWalletRestored) {
219 +// throw WalletRestoreFromKeysException(
220 +// message: convertUTF8ToString(pointer: errorMessagePointer));
221 +// }
222 +// }
223 +
224 +
225 void loadWallet({
226 required String path,
227 required String password,
cw_monero/lib/monero_wallet.dart
+1 -1
@@ -686,7 +686,7 @@ abstract class MoneroWalletBase
686 void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e;
687
688 @override
689 - String signMessage(String message, {String? address}) {
689 + Future<String> signMessage(String message, {String? address}) async {
690 final useAddress = address ?? "";
691 return monero_wallet.signMessage(message, address: useAddress);
692 }
cw_monero/lib/monero_wallet_service.dart
+6 -1
@@ -55,7 +55,7 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
55 }
56
57 class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
58 - MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials> {
58 + MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials, MoneroNewWalletCredentials> {
59 MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
60
61 final Box<WalletInfo> walletInfoSource;
@@ -227,6 +227,11 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
227 }
228 }
229
230 + @override
231 + Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
232 + throw UnimplementedError("Restoring a Monero wallet from a hardware wallet is not yet supported!");
233 + }
234 +
235 @override
236 Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
237 {bool? isTestnet}) async {
cw_nano/lib/nano_wallet_service.dart
+6 -1
@@ -14,7 +14,7 @@ import 'package:nanodart/nanodart.dart';
14 import 'package:nanoutil/nanoutil.dart';
15
16 class NanoWalletService extends WalletService<NanoNewWalletCredentials,
17 - NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
17 + NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials, NanoNewWalletCredentials> {
18 NanoWalletService(this.walletInfoSource);
19
20 final Box<WalletInfo> walletInfoSource;
@@ -109,6 +109,11 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
109 return wallet;
110 }
111
112 + @override
113 + Future<NanoWallet> restoreFromHardwareWallet(NanoNewWalletCredentials credentials) {
114 + throw UnimplementedError("Restoring a Nano wallet from a hardware wallet is not yet supported!");
115 + }
116 +
117 @override
118 Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
119 if (credentials.mnemonic.contains(' ')) {
cw_polygon/lib/polygon_wallet_service.dart
+24
@@ -1,5 +1,6 @@
1 import 'package:bip39/bip39.dart' as bip39;
2 import 'package:cw_core/wallet_base.dart';
3 +import 'package:cw_core/wallet_info.dart';
4 import 'package:cw_core/wallet_type.dart';
5 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
6 import 'package:cw_evm/evm_chain_wallet_service.dart';
@@ -86,6 +87,29 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
87 return wallet;
88 }
89
90 + @override
91 + Future<PolygonWallet> restoreFromHardwareWallet(
92 + EVMChainRestoreWalletFromHardware credentials) async {
93 + credentials.walletInfo!.derivationInfo = DerivationInfo(
94 + derivationType: DerivationType.bip39,
95 + derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"
96 + );
97 + credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
98 + credentials.walletInfo!.address = credentials.hwAccountData.address;
99 +
100 + final wallet = PolygonWallet(
101 + walletInfo: credentials.walletInfo!,
102 + password: credentials.password!,
103 + client: client,
104 + );
105 +
106 + await wallet.init();
107 + wallet.addInitialTokens();
108 + await wallet.save();
109 +
110 + return wallet;
111 + }
112 +
113 @override
114 Future<PolygonWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
115 {bool? isTestnet}) async {
cw_polygon/pubspec.yaml
+5
@@ -23,6 +23,11 @@ dependencies:
23 bip39: ^1.0.6
24 collection: ^1.17.1
25
26 +dependency_overrides:
27 + web3dart:
28 + git:
29 + url: https://github.com/cake-tech/web3dart.git
30 + ref: cake
31
32 dev_dependencies:
33 flutter_test:
cw_solana/lib/solana_wallet_service.dart
+10 -1
@@ -2,7 +2,10 @@ import 'dart:io';
2
3 import 'package:bip39/bip39.dart' as bip39;
4 import 'package:collection/collection.dart';
5 +import 'package:cw_core/balance.dart';
6 import 'package:cw_core/pathForWallet.dart';
7 +import 'package:cw_core/transaction_history.dart';
8 +import 'package:cw_core/transaction_info.dart';
9 import 'package:cw_core/wallet_base.dart';
10 import 'package:cw_core/wallet_info.dart';
11 import 'package:cw_core/wallet_service.dart';
@@ -13,7 +16,7 @@ import 'package:cw_solana/solana_wallet_creation_credentials.dart';
16 import 'package:hive/hive.dart';
17
18 class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
16 - SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey> {
19 + SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey, SolanaNewWalletCredentials> {
20 SolanaWalletService(this.walletInfoSource);
21
22 final Box<WalletInfo> walletInfoSource;
@@ -134,4 +137,10 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
137
138 await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
139 }
140 +
141 + @override
142 + Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(SolanaNewWalletCredentials credentials) {
143 + // TODO: implement restoreFromHardwareWallet
144 + throw UnimplementedError();
145 + }
146 }
cw_tron/lib/tron_wallet.dart
+1 -1
@@ -551,7 +551,7 @@ abstract class TronWalletBase
551 }
552
553 @override
554 - String signMessage(String message, {String? address}) =>
554 + Future<String> signMessage(String message, {String? address}) async =>
555 _tronPrivateKey.signPersonalMessage(ascii.encode(message));
556
557 String getTronBase58AddressFromHex(String hexAddress) {
cw_tron/lib/tron_wallet_service.dart
+14 -2
@@ -1,7 +1,10 @@
1 import 'dart:io';
2
3 import 'package:bip39/bip39.dart' as bip39;
4 +import 'package:cw_core/balance.dart';
5 import 'package:cw_core/pathForWallet.dart';
6 +import 'package:cw_core/transaction_history.dart';
7 +import 'package:cw_core/transaction_info.dart';
8 import 'package:cw_core/wallet_base.dart';
9 import 'package:cw_core/wallet_info.dart';
10 import 'package:cw_core/wallet_service.dart';
@@ -13,8 +16,11 @@ import 'package:cw_tron/tron_wallet_creation_credentials.dart';
16 import 'package:hive/hive.dart';
17 import 'package:collection/collection.dart';
18
16 -class TronWalletService extends WalletService<TronNewWalletCredentials,
17 - TronRestoreWalletFromSeedCredentials, TronRestoreWalletFromPrivateKey> {
19 +class TronWalletService extends WalletService<
20 + TronNewWalletCredentials,
21 + TronRestoreWalletFromSeedCredentials,
22 + TronRestoreWalletFromPrivateKey,
23 + TronNewWalletCredentials> {
24 TronWalletService(this.walletInfoSource, {required this.client});
25
26 late TronClient client;
@@ -145,4 +151,10 @@ class TronWalletService extends WalletService<TronNewWalletCredentials,
151 .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
152 await walletInfoSource.delete(walletInfo.key);
153 }
154 +
155 + @override
156 + Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
157 + // TODO: implement restoreFromHardwareWallet
158 + throw UnimplementedError();
159 + }
160 }
how_to_add_new_wallet_type.md
+2 -1
@@ -277,6 +277,7 @@ Now you can run the codebase and successfully create a wallet for type walletX s
277 - Modify `getCredentialsFromRestoredWallet` method
278 - Go to `lib/core/address_validator.dart`
279 - Modify the `getAddressFromStringPattern` method to add a case for `WalletType.walletx`
280 +- and if it has tokens (ex. erc20, trc20, spl tokens) then add them to the switch case as well
281 - Add the scheme for walletx for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
282
283 **Transaction History**
@@ -291,10 +292,10 @@ Now you can run the codebase and successfully create a wallet for type walletX s
292 # Points to note when adding the new wallet type
293
294 1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add that to this function `_checkIfCanSend` in `exchange_trade_view_model.dart`
295 +1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add a check for the tags as well in the
296 2. Check On/Off ramp providers that support the new wallet currency and add them accordingly in `provider_types.dart`
297 3. Add support for wallet uri scheme to restore from QR for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
298 4. Make sure no imports are using the wallet internal package files directly, instead use the proxy layers that is created in the main lib `lib/cw_ethereum.dart` for example. (i.e try building Monero.com if you get compilation errors, then you probably missed something)
297 -5.
299
300
301 Copyright (C) 2018-2023 Cake Labs LLC
ios/Podfile.lock
+11
@@ -131,7 +131,12 @@ PODS:
131 - FlutterMacOS
132 - permission_handler_apple (9.1.1):
133 - Flutter
134 + - Protobuf (3.25.3)
135 - ReachabilitySwift (5.0.0)
136 + - reactive_ble_mobile (0.0.1):
137 + - Flutter
138 + - Protobuf (~> 3.5)
139 + - SwiftProtobuf (~> 1.0)
140 - SDWebImage (5.18.11):
141 - SDWebImage/Core (= 5.18.11)
142 - SDWebImage/Core (5.18.11)
@@ -179,6 +184,7 @@ DEPENDENCIES:
184 - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
185 - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
186 - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
187 + - reactive_ble_mobile (from `.symlinks/plugins/reactive_ble_mobile/ios`)
188 - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
189 - share_plus (from `.symlinks/plugins/share_plus/ios`)
190 - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
@@ -196,6 +202,7 @@ SPEC REPOS:
202 - DKPhotoGallery
203 - MTBBarcodeScanner
204 - OrderedSet
205 + - Protobuf
206 - ReachabilitySwift
207 - SDWebImage
208 - SwiftProtobuf
@@ -244,6 +251,8 @@ EXTERNAL SOURCES:
251 :path: ".symlinks/plugins/path_provider_foundation/darwin"
252 permission_handler_apple:
253 :path: ".symlinks/plugins/permission_handler_apple/ios"
254 + reactive_ble_mobile:
255 + :path: ".symlinks/plugins/reactive_ble_mobile/ios"
256 sensitive_clipboard:
257 :path: ".symlinks/plugins/sensitive_clipboard/ios"
258 share_plus:
@@ -286,7 +295,9 @@ SPEC CHECKSUMS:
295 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
296 path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
297 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
298 + Protobuf: 8e9074797a13c484a79959fdb819ef4ae6da7dbe
299 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
300 + reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
301 SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457
302 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
303 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
ios/Runner.xcodeproj/project.pbxproj
+4
@@ -16,6 +16,7 @@
16 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
17 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
18 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
19 + 9F46EE5E2BC11178009318F5 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */; };
20 /* End PBXBuildFile section */
21
22 /* Begin PBXFileReference section */
@@ -40,6 +41,7 @@
41 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
42 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
43 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
44 + 9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
45 AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
46 /* End PBXFileReference section */
47
@@ -123,6 +125,7 @@
125 97C146FD1CF9000F007C117D /* Assets.xcassets */,
126 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
127 97C147021CF9000F007C117D /* Info.plist */,
128 + 9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */,
129 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
130 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
131 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@@ -196,6 +199,7 @@
199 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
200 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
201 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
202 + 9F46EE5E2BC11178009318F5 /* PrivacyInfo.xcprivacy in Resources */,
203 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
204 );
205 runOnlyForDeploymentPostprocessing = 0;
ios/Runner/InfoBase.plist
+4 -10
@@ -210,16 +210,6 @@
210 <string>tron-wallet</string>
211 </array>
212 </dict>
213 - <dict>
214 - <key>CFBundleTypeRole</key>
215 - <string>Viewer</string>
216 - <key>CFBundleURLName</key>
217 - <string>tron_wallet</string>
218 - <key>CFBundleURLSchemes</key>
219 - <array>
220 - <string>tron_wallet</string>
221 - </array>
222 - </dict>
213 </array>
214 <key>CFBundleVersion</key>
215 <string>$(CURRENT_PROJECT_VERSION)</string>
@@ -238,6 +228,10 @@
228 <string>Enable Face ID for fast and secure access to wallets and private keys</string>
229 <key>NSPhotoLibraryUsageDescription</key>
230 <string>We need access to documents folder for getting access to open/save backup file</string>
231 + <key>NSBluetoothPeripheralUsageDescription</key>
232 + <string>We need access to Bluetooth in order to connect to your hardware wallet when needed</string>
233 + <key>NSBluetoothAlwaysUsageDescription</key>
234 + <string>We need access to Bluetooth in order to connect to your hardware wallet when needed</string>
235 <key>UIBackgroundModes</key>
236 <array>
237 <string>fetch</string>
ios/Runner/PrivacyInfo.xcprivacy new
+24
@@ -0,0 +1,24 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 +<!--
4 + PrivacyInfo.xcprivacy
5 +
6 +
7 + Created by Omar Hatem on 06/04/2024.
8 + Copyright (c) 2024 Cake Labs. All rights reserved.
9 +-->
10 +<plist version="1.0">
11 +<dict>
12 + <key>NSPrivacyAccessedAPITypes</key>
13 + <array>
14 + <dict>
15 + <key>NSPrivacyAccessedAPIType</key>
16 + <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
17 + <key>NSPrivacyAccessedAPITypeReasons</key>
18 + <array>
19 + <string>CA92.1</string>
20 + </array>
21 + </dict>
22 + </array>
23 +</dict>
24 +</plist>
\ No newline at end of file
lib/bitcoin/cw_bitcoin.dart
+26 -4
@@ -32,6 +32,14 @@ class CWBitcoin extends Bitcoin {
32 {required String name, WalletInfo? walletInfo}) =>
33 BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
34
35 + @override
36 + WalletCredentials createBitcoinHardwareWalletCredentials(
37 + {required String name,
38 + required HardwareAccountData accountData,
39 + WalletInfo? walletInfo}) =>
40 + BitcoinRestoreWalletFromHardware(
41 + name: name, hwAccountData: accountData, walletInfo: walletInfo);
42 +
43 @override
44 TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
45
@@ -292,7 +300,7 @@ class CWBitcoin extends Bitcoin {
300 return [
301 DerivationInfo(
302 derivationType: DerivationType.electrum,
295 - derivationPath: "m/0'/0",
303 + derivationPath: "m/0'",
304 description: "Electrum",
305 scriptType: "p2wpkh",
306 )
@@ -344,9 +352,6 @@ class CWBitcoin extends Bitcoin {
352 if (derivationDepth == 3) {
353 // we add "/0/0" so that we generate account 0, index 0 and correctly get balance
354 derivationPath += "/0/0";
347 - // we don't support sub-ACCOUNTS in bitcoin like we do monero, and so the path dInfoCopy
348 - // expects should be ACCOUNT 0, index unspecified:
349 - dInfoCopy.derivationPath = dInfoCopy.derivationPath! + "/0";
355 }
356
357 // var hd = bip32.BIP32.fromSeed(seedBytes).derivePath(derivationPath);
@@ -440,4 +445,21 @@ class CWBitcoin extends Bitcoin {
445 final bitcoinWallet = wallet as ElectrumWallet;
446 return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 1.1).round();
447 }
448 +
449 + @override
450 + void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
451 + (wallet as BitcoinWallet).setLedger(ledger, device);
452 + }
453 +
454 + @override
455 + Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
456 + {int index = 0, int limit = 5}) async {
457 + final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
458 + try {
459 + return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
460 + } on LedgerException catch (err) {
461 + print(err.message);
462 + throw err;
463 + }
464 + }
465 }
lib/buy/buy_provider.dart
+3
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/buy/buy_amount.dart';
2 import 'package:cake_wallet/buy/order.dart';
3 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
4 import 'package:cw_core/wallet_base.dart';
5 import 'package:flutter/material.dart';
6
@@ -7,10 +8,12 @@ abstract class BuyProvider {
8 BuyProvider({
9 required this.wallet,
10 required this.isTestEnvironment,
11 + required this.ledgerVM,
12 });
13
14 final WalletBase wallet;
15 final bool isTestEnvironment;
16 + final LedgerViewModel? ledgerVM;
17
18 String get title;
19
lib/buy/dfx/dfx_buy_provider.dart
+20 -4
@@ -3,9 +3,11 @@ import 'dart:convert';
3 import 'package:cake_wallet/buy/buy_provider.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/routes.dart';
6 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
7 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
8 import 'package:cake_wallet/utils/device_info.dart';
9 import 'package:cake_wallet/utils/show_pop_up.dart';
10 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
11 import 'package:cw_core/wallet_base.dart';
12 import 'package:cw_core/wallet_type.dart';
13 import 'package:flutter/material.dart';
@@ -13,8 +15,8 @@ import 'package:http/http.dart' as http;
15 import 'package:url_launcher/url_launcher.dart';
16
17 class DFXBuyProvider extends BuyProvider {
16 - DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
17 - : super(wallet: wallet, isTestEnvironment: isTestEnvironment);
18 + DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
19 + : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM);
20
21 static const _baseUrl = 'api.dfx.swiss';
22 // static const _signMessagePath = '/v1/auth/signMessage';
@@ -91,7 +93,7 @@ class DFXBuyProvider extends BuyProvider {
93 // }
94
95 Future<String> auth() async {
94 - final signMessage = getSignature(await getSignMessage());
96 + final signMessage = await getSignature(await getSignMessage());
97
98 final requestBody = jsonEncode({
99 'wallet': walletName,
@@ -118,7 +120,7 @@ class DFXBuyProvider extends BuyProvider {
120 }
121 }
122
121 - String getSignature(String message) {
123 + Future<String> getSignature(String message) async {
124 switch (wallet.type) {
125 case WalletType.ethereum:
126 case WalletType.polygon:
@@ -135,6 +137,20 @@ class DFXBuyProvider extends BuyProvider {
137
138 @override
139 Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
140 + if (wallet.isHardwareWallet) {
141 + if (!ledgerVM!.isConnected) {
142 + await Navigator.of(context).pushNamed(Routes.connectDevices,
143 + arguments: ConnectDevicePageParams(
144 + walletType: wallet.walletInfo.type,
145 + onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
146 + ledgerVM.setLedger(wallet);
147 + Navigator.of(context).pop();
148 + }));
149 + } else {
150 + ledgerVM!.setLedger(wallet);
151 + }
152 + }
153 +
154 try {
155 final assetOut = this.assetOut;
156 final blockchain = this.blockchain;
lib/buy/moonpay/moonpay_provider.dart
+1 -2
@@ -14,7 +14,6 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
14 import 'package:cake_wallet/store/settings_store.dart';
15 import 'package:cake_wallet/themes/theme_base.dart';
16 import 'package:cake_wallet/utils/device_info.dart';
17 -import 'package:crypto/crypto.dart';
17 import 'package:cw_core/crypto_currency.dart';
18 import 'package:cw_core/wallet_base.dart';
19 import 'package:cw_core/wallet_type.dart';
@@ -30,7 +29,7 @@ class MoonPayProvider extends BuyProvider {
29 }) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl,
30 baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl,
31 this._settingsStore = settingsStore,
33 - super(wallet: wallet, isTestEnvironment: isTestEnvironment);
32 + super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
33
34 final SettingsStore _settingsStore;
35
lib/buy/onramper/onramper_buy_provider.dart
+1 -1
@@ -13,7 +13,7 @@ import 'package:url_launcher/url_launcher.dart';
13 class OnRamperBuyProvider extends BuyProvider {
14 OnRamperBuyProvider(this._settingsStore,
15 {required WalletBase wallet, bool isTestEnvironment = false})
16 - : super(wallet: wallet, isTestEnvironment: isTestEnvironment);
16 + : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
17
18 static const _baseUrl = 'buy.onramper.com';
19
lib/buy/robinhood/robinhood_buy_provider.dart
+21 -4
@@ -3,8 +3,11 @@ import 'dart:convert';
3 import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 import 'package:cake_wallet/buy/buy_provider.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/routes.dart';
7 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
8 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
9 import 'package:cake_wallet/utils/show_pop_up.dart';
10 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
11 import 'package:cw_core/wallet_base.dart';
12 import 'package:cw_core/wallet_type.dart';
13 import 'package:flutter/material.dart';
@@ -12,8 +15,8 @@ import 'package:http/http.dart' as http;
15 import 'package:url_launcher/url_launcher.dart';
16
17 class RobinhoodBuyProvider extends BuyProvider {
15 - RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
16 - : super(wallet: wallet, isTestEnvironment: isTestEnvironment);
18 + RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
19 + : super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM);
20
21 static const _baseUrl = 'applink.robinhood.com';
22 static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
@@ -34,7 +37,7 @@ class RobinhoodBuyProvider extends BuyProvider {
37
38 String get _apiSecret => secrets.exchangeHelperApiKey;
39
37 - String getSignature(String message) {
40 + Future<String> getSignature(String message) {
41 switch (wallet.type) {
42 case WalletType.ethereum:
43 case WalletType.polygon:
@@ -53,7 +56,7 @@ class RobinhoodBuyProvider extends BuyProvider {
56 final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10;
57 final message = "$_apiSecret:${valid_until}";
58
56 - final signature = getSignature(message);
59 + final signature = await getSignature(message);
60
61 final uri = Uri.https(_cIdBaseUrl, "/api/robinhood");
62
@@ -84,6 +87,20 @@ class RobinhoodBuyProvider extends BuyProvider {
87 }
88
89 Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
90 + if (wallet.isHardwareWallet) {
91 + if (!ledgerVM!.isConnected) {
92 + await Navigator.of(context).pushNamed(Routes.connectDevices,
93 + arguments: ConnectDevicePageParams(
94 + walletType: wallet.walletInfo.type,
95 + onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
96 + ledgerVM.setLedger(wallet);
97 + Navigator.of(context).pop();
98 + }));
99 + } else {
100 + ledgerVM!.setLedger(wallet);
101 + }
102 + }
103 +
104 try {
105 final uri = await requestProviderUrl();
106 await launchUrl(uri, mode: LaunchMode.externalApplication);
lib/buy/wyre/wyre_buy_provider.dart
+1 -1
@@ -14,7 +14,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
14 class WyreBuyProvider extends BuyProvider {
15 WyreBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
16 : baseApiUrl = isTestEnvironment ? _baseTestApiUrl : _baseProductApiUrl,
17 - super(wallet: wallet, isTestEnvironment: isTestEnvironment);
17 + super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
18
19 static const _baseTestApiUrl = 'https://api.testwyre.com';
20 static const _baseProductApiUrl = 'https://api.sendwyre.com';
lib/core/address_validator.dart
+4 -1
@@ -295,7 +295,7 @@ class AddressValidator extends TextValidator {
295 case CryptoCurrency.sol:
296 return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
297 case CryptoCurrency.trx:
298 - return '^(T|t)[1-9A-HJ-NP-Za-km-z]{33}\$';
298 + return '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
299 default:
300 if (type.tag == CryptoCurrency.eth.title) {
301 return '0x[0-9a-zA-Z]{42}';
@@ -306,6 +306,9 @@ class AddressValidator extends TextValidator {
306 if (type.tag == CryptoCurrency.sol.title) {
307 return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
308 }
309 + if (type.tag == CryptoCurrency.trx.title) {
310 + return '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
311 + }
312
313 return null;
314 }
lib/core/wallet_creation_service.dart
+15
@@ -101,4 +101,19 @@ class WalletCreationService {
101
102 return wallet;
103 }
104 +
105 + Future<WalletBase> restoreFromHardwareWallet(WalletCredentials credentials) async {
106 + checkIfExists(credentials.name);
107 + final password = generateWalletPassword();
108 + credentials.password = password;
109 + await keyService.saveWalletPassword(password: password, walletName: credentials.name);
110 + final wallet = await _service!.restoreFromHardwareWallet(credentials);
111 +
112 + if (wallet.type == WalletType.monero) {
113 + await sharedPreferences.setBool(
114 + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
115 + }
116 +
117 + return wallet;
118 + }
119 }
lib/di.dart
+167 -152
@@ -1,237 +1,240 @@
1 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
2 import 'package:cake_wallet/anonpay/anonpay_api.dart';
3 import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
4 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
5 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
6 +import 'package:cake_wallet/anypay/anypay_api.dart';
7 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
8 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
9 import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
10 import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
6 -import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
11 +import 'package:cake_wallet/buy/order.dart';
12 import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
13 +import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
14 +import 'package:cake_wallet/core/auth_service.dart';
15 +import 'package:cake_wallet/core/backup_service.dart';
16 +import 'package:cake_wallet/core/key_service.dart';
17 import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
18 import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
10 -import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
19 import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
20 +import 'package:cake_wallet/core/wallet_creation_service.dart';
21 +import 'package:cake_wallet/core/wallet_loading_service.dart';
22 import 'package:cake_wallet/core/yat_service.dart';
23 import 'package:cake_wallet/entities/background_tasks.dart';
24 +import 'package:cake_wallet/entities/biometric_auth.dart';
25 +import 'package:cake_wallet/entities/contact.dart';
26 +import 'package:cake_wallet/entities/contact_record.dart';
27 import 'package:cake_wallet/entities/exchange_api_mode.dart';
28 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
29 import 'package:cake_wallet/tron/tron.dart';
30 import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
31 import 'package:cw_core/receive_page_option.dart';
32 +import 'package:cake_wallet/entities/qr_view_data.dart';
33 +import 'package:cake_wallet/entities/template.dart';
34 +import 'package:cake_wallet/entities/transaction_description.dart';
35 import 'package:cake_wallet/ethereum/ethereum.dart';
20 -import 'package:cake_wallet/nano/nano.dart';
36 +import 'package:cake_wallet/exchange/exchange_template.dart';
37 +import 'package:cake_wallet/exchange/trade.dart';
38 +import 'package:cake_wallet/haven/haven.dart';
39 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
40 import 'package:cake_wallet/ionia/ionia_anypay.dart';
41 +import 'package:cake_wallet/ionia/ionia_api.dart';
42 import 'package:cake_wallet/ionia/ionia_gift_card.dart';
43 +import 'package:cake_wallet/ionia/ionia_merchant.dart';
44 +import 'package:cake_wallet/ionia/ionia_service.dart';
45 import 'package:cake_wallet/ionia/ionia_tip.dart';
46 +import 'package:cake_wallet/monero/monero.dart';
47 +import 'package:cake_wallet/nano/nano.dart';
48 import 'package:cake_wallet/polygon/polygon.dart';
49 +import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
50 import 'package:cake_wallet/routes.dart';
51 import 'package:cake_wallet/solana/solana.dart';
52 import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
53 +import 'package:cake_wallet/src/screens/auth/auth_page.dart';
54 +import 'package:cake_wallet/src/screens/backup/backup_page.dart';
55 +import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
56 import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
57 +import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
58 import 'package:cake_wallet/src/screens/buy/webview_page.dart';
59 +import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
60 +import 'package:cake_wallet/src/screens/contact/contact_page.dart';
61 +import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
62 import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
63 import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
64 import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
65 import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
66 import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
67 +import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
68 +import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
69 import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
70 +import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
71 +import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
72 +import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
73 +import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
74 +import 'package:cake_wallet/src/screens/faq/faq_page.dart';
75 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
76 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
77 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart';
78 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
79 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
80 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
81 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
82 +import 'package:cake_wallet/src/screens/ionia/ionia.dart';
83 +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
84 +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
85 import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
86 import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
87 import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
88 +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
89 +import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
90 import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
91 +import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
92 +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
93 import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
94 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
95 +import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
96 +import 'package:cake_wallet/src/screens/receive/receive_page.dart';
97 +import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
98 +import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
99 +import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
100 import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
101 +import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
102 +import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
103 +import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
104 +import 'package:cake_wallet/src/screens/send/send_page.dart';
105 +import 'package:cake_wallet/src/screens/send/send_template_page.dart';
106 +import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
107 import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
108 import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
109 import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
110 import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
111 import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
112 import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
49 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart';
50 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
51 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
52 -import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
53 -import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
113 import 'package:cake_wallet/src/screens/settings/tor_page.dart';
114 +import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
115 import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
56 -import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
57 -import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
116 import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
117 import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
118 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
119 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
120 +import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
121 +import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
122 +import 'package:cake_wallet/src/screens/support/support_page.dart';
123 import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
124 import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
125 +import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
126 +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
127 +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
128 +import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
129 +import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
130 import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
131 import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
132 +import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
133 +import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
134 +import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
135 +import 'package:cake_wallet/store/app_store.dart';
136 +import 'package:cake_wallet/store/authentication_store.dart';
137 +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
138 +import 'package:cake_wallet/store/dashboard/orders_store.dart';
139 +import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
140 +import 'package:cake_wallet/store/dashboard/trades_store.dart';
141 +import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
142 +import 'package:cake_wallet/store/node_list_store.dart';
143 +import 'package:cake_wallet/store/secret_store.dart';
144 +import 'package:cake_wallet/store/settings_store.dart';
145 +import 'package:cake_wallet/store/templates/exchange_template_store.dart';
146 +import 'package:cake_wallet/store/templates/send_template_store.dart';
147 +import 'package:cake_wallet/store/wallet_list_store.dart';
148 +import 'package:cake_wallet/store/yat/yat_store.dart';
149 import 'package:cake_wallet/themes/theme_list.dart';
150 import 'package:cake_wallet/utils/device_info.dart';
66 -import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
151 import 'package:cake_wallet/utils/payment_request.dart';
152 import 'package:cake_wallet/utils/responsive_layout_util.dart';
69 -import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
153 +import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
154 import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
155 import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
156 +import 'package:cake_wallet/view_model/auth_view_model.dart';
157 +import 'package:cake_wallet/view_model/backup_view_model.dart';
158 +import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
159 +import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
160 +import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
161 +import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
162 +import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
163 +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
164 +import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
165 import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
166 import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
167 import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
168 import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
169 +import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
170 +import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
171 +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
172 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
173 +import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
174 import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
175 import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
78 -import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart';
176 import 'package:cake_wallet/view_model/ionia/ionia_custom_redeem_view_model.dart';
80 -import 'package:cake_wallet/ionia/ionia_service.dart';
81 -import 'package:cake_wallet/ionia/ionia_api.dart';
82 -import 'package:cake_wallet/ionia/ionia_merchant.dart';
83 -import 'package:cake_wallet/monero/monero.dart';
84 -import 'package:cake_wallet/haven/haven.dart';
85 -import 'package:cake_wallet/bitcoin/bitcoin.dart';
86 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
87 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
88 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
89 -import 'package:cake_wallet/src/screens/ionia/ionia.dart';
90 -import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
91 -import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
177 +import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart';
178 +import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
179 import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
180 +import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
181 import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
182 +import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
183 +import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
184 +import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
185 import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
186 import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
187 +import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
188 +import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
189 import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
190 +import 'package:cake_wallet/view_model/order_details_view_model.dart';
191 +import 'package:cake_wallet/view_model/rescan_view_model.dart';
192 +import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
193 +import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
194 import 'package:cake_wallet/view_model/seed_type_view_model.dart';
195 +import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
196 +import 'package:cake_wallet/view_model/send/send_view_model.dart';
197 import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
99 -import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
198 import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
199 import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
200 import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
201 import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
104 -import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
202 import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
106 -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
107 -import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
108 -import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
109 -import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
110 -import 'package:cw_core/erc20_token.dart';
111 -import 'package:cw_core/nano_account.dart';
112 -import 'package:cw_core/unspent_coins_info.dart';
113 -import 'package:cake_wallet/core/backup_service.dart';
114 -import 'package:cw_core/wallet_service.dart';
115 -import 'package:cake_wallet/entities/biometric_auth.dart';
116 -import 'package:cake_wallet/entities/contact_record.dart';
117 -import 'package:cake_wallet/buy/order.dart';
118 -import 'package:cake_wallet/entities/transaction_description.dart';
119 -import 'package:cw_core/transaction_info.dart';
120 -import 'package:cake_wallet/entities/contact.dart';
121 -import 'package:cw_core/node.dart';
122 -import 'package:cake_wallet/exchange/trade.dart';
123 -import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
124 -import 'package:cake_wallet/src/screens/backup/backup_page.dart';
125 -import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
126 -import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
127 -import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
128 -import 'package:cake_wallet/src/screens/contact/contact_page.dart';
129 -import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
130 -import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
131 -import 'package:cake_wallet/src/screens/faq/faq_page.dart';
132 -import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
133 -import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
134 -import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
135 -import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
136 -import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
137 -import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
138 -import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
139 -import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
140 -import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
141 -import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
142 -import 'package:cake_wallet/src/screens/send/send_template_page.dart';
143 -import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
144 -import 'package:cake_wallet/src/screens/support/support_page.dart';
145 -import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
146 -import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
147 -import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
148 -import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
149 -import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
150 -import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
151 -import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
152 -import 'package:cake_wallet/store/dashboard/orders_store.dart';
153 -import 'package:cake_wallet/store/node_list_store.dart';
154 -import 'package:cake_wallet/store/secret_store.dart';
155 -import 'package:cake_wallet/store/settings_store.dart';
156 -import 'package:cake_wallet/core/auth_service.dart';
157 -import 'package:cake_wallet/core/key_service.dart';
158 -import 'package:cw_core/wallet_info.dart';
159 -import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
160 -import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
161 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
162 -import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
163 -import 'package:cake_wallet/src/screens/receive/receive_page.dart';
164 -import 'package:cake_wallet/src/screens/send/send_page.dart';
165 -import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
166 -import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
167 -import 'package:cake_wallet/store/wallet_list_store.dart';
168 -import 'package:cake_wallet/store/yat/yat_store.dart';
169 -import 'package:cake_wallet/view_model/backup_view_model.dart';
170 -import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
171 -import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
172 -import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
173 -import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
174 -import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
175 -import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
176 -import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
177 -import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
178 -import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
179 -import 'package:cake_wallet/view_model/order_details_view_model.dart';
180 -import 'package:cake_wallet/view_model/rescan_view_model.dart';
181 -import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
182 -import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
203 import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
204 import 'package:cake_wallet/view_model/support_view_model.dart';
185 -import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
205 import 'package:cake_wallet/view_model/trade_details_view_model.dart';
206 +import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
207 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
208 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
209 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
210 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
191 -import 'package:cake_wallet/view_model/auth_view_model.dart';
192 -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
193 -import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
211 +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
212 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
195 -import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
196 -import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
197 -import 'package:cake_wallet/view_model/send/send_view_model.dart';
213 +import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
214 import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
215 +import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
216 +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
217 import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
218 +import 'package:cake_wallet/view_model/wallet_new_vm.dart';
219 +import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
220 import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
221 import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
202 -import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
222 +import 'package:cw_core/crypto_currency.dart';
223 +import 'package:cw_core/nano_account.dart';
224 +import 'package:cw_core/node.dart';
225 +import 'package:cw_core/receive_page_option.dart';
226 +import 'package:cw_core/transaction_info.dart';
227 +import 'package:cw_core/unspent_coins_info.dart';
228 +import 'package:cw_core/wallet_info.dart';
229 +import 'package:cw_core/wallet_service.dart';
230 +import 'package:cw_core/wallet_type.dart';
231 import 'package:flutter/foundation.dart';
232 import 'package:flutter/widgets.dart';
233 +import 'package:flutter_secure_storage/flutter_secure_storage.dart';
234 import 'package:get_it/get_it.dart';
235 import 'package:hive/hive.dart';
236 import 'package:mobx/mobx.dart';
237 import 'package:shared_preferences/shared_preferences.dart';
209 -import 'package:flutter_secure_storage/flutter_secure_storage.dart';
210 -import 'package:cake_wallet/core/wallet_creation_service.dart';
211 -import 'package:cake_wallet/store/app_store.dart';
212 -import 'package:cw_core/wallet_type.dart';
213 -import 'package:cake_wallet/view_model/wallet_new_vm.dart';
214 -import 'package:cake_wallet/store/authentication_store.dart';
215 -import 'package:cake_wallet/store/dashboard/trades_store.dart';
216 -import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
217 -import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
218 -import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
219 -import 'package:cake_wallet/store/templates/send_template_store.dart';
220 -import 'package:cake_wallet/store/templates/exchange_template_store.dart';
221 -import 'package:cake_wallet/entities/template.dart';
222 -import 'package:cake_wallet/exchange/exchange_template.dart';
223 -import 'package:cake_wallet/.secrets.g.dart' as secrets;
224 -import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
225 -import 'package:cake_wallet/anypay/anypay_api.dart';
226 -import 'package:cake_wallet/view_model/ionia/ionia_gift_card_details_view_model.dart';
227 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
228 -import 'package:cake_wallet/view_model/ionia/ionia_payment_status_view_model.dart';
229 -import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
230 -import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
231 -import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
232 -import 'package:cake_wallet/core/wallet_loading_service.dart';
233 -import 'package:cw_core/crypto_currency.dart';
234 -import 'package:cake_wallet/entities/qr_view_data.dart';
238
239 import 'buy/dfx/dfx_buy_provider.dart';
240 import 'core/totp_request_details.dart';
@@ -308,6 +311,7 @@ Future<void> setup({
311 getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
312
313 getIt.registerSingleton(AuthenticationStore());
314 + getIt.registerSingleton(LedgerViewModel());
315 getIt.registerSingleton<WalletListStore>(WalletListStore());
316 getIt.registerSingleton(NodeListStoreBase.instance);
317 getIt.registerSingleton<SettingsStore>(settingsStore);
@@ -367,6 +371,11 @@ Future<void> setup({
371 getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type);
372 });
373
374 + getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, void>((type, _) =>
375 + WalletHardwareRestoreViewModel(getIt.get<LedgerViewModel>(), getIt.get<AppStore>(),
376 + getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
377 + type: type));
378 +
379 getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel(
380 appStore: getIt.get<AppStore>(),
381 yatStore: getIt.get<YatStore>(),
@@ -490,12 +499,8 @@ Future<void> setup({
499 getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl());
500
501 getIt.registerLazySingleton<Web3WalletService>(() {
493 - final Web3WalletService web3WalletService = Web3WalletService(
494 - getIt.get<BottomSheetService>(),
495 - getIt.get<WalletConnectKeyService>(),
496 - appStore,
497 - getIt.get<SharedPreferences>()
498 - );
502 + final Web3WalletService web3WalletService = Web3WalletService(getIt.get<BottomSheetService>(),
503 + getIt.get<WalletConnectKeyService>(), appStore, getIt.get<SharedPreferences>());
504 web3WalletService.create();
505 return web3WalletService;
506 });
@@ -599,6 +604,7 @@ Future<void> setup({
604 getIt.get<BalanceViewModel>(),
605 getIt.get<ContactListViewModel>(),
606 _transactionDescriptionBox,
607 + getIt.get<LedgerViewModel>(),
608 ),
609 );
610
@@ -804,11 +810,11 @@ Future<void> setup({
810 editingNode: editingNode,
811 isSelected: isSelected));
812
807 - getIt.registerFactory<RobinhoodBuyProvider>(
808 - () => RobinhoodBuyProvider(wallet: getIt.get<AppStore>().wallet!));
813 + getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
814 + wallet: getIt.get<AppStore>().wallet!, ledgerVM: getIt.get<LedgerViewModel>()));
815
810 - getIt
811 - .registerFactory<DFXBuyProvider>(() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
816 + getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
817 + wallet: getIt.get<AppStore>().wallet!, ledgerVM: getIt.get<LedgerViewModel>()));
818
819 getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
820 settingsStore: getIt.get<AppStore>().settingsStore,
@@ -928,8 +934,17 @@ Future<void> setup({
934 transactionDetailsViewModel:
935 getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
936
931 - getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), bool?>(
932 - (param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true));
937 + getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType),
938 + List<bool>?>((param1, additionalParams) {
939 + final isCreate = additionalParams?[0] ?? true;
940 + final isHardwareWallet = additionalParams?[1] ?? false;
941 +
942 + return NewWalletTypePage(
943 + onTypeSelected: param1,
944 + isCreate: isCreate,
945 + isHardwareWallet: isHardwareWallet,
946 + );
947 + });
948
949 getIt.registerFactoryParam<PreSeedPage, int, void>(
950 (seedPhraseLength, _) => PreSeedPage(seedPhraseLength));
@@ -1144,9 +1159,9 @@ Future<void> setup({
1159 getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
1160
1161 getIt.registerFactoryParam<RBFDetailsPage, TransactionInfo, void>(
1147 - (TransactionInfo transactionInfo, _) => RBFDetailsPage(
1162 + (TransactionInfo transactionInfo, _) => RBFDetailsPage(
1163 transactionDetailsViewModel:
1149 - getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
1164 + getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
1165
1166 getIt.registerFactory(() => AnonPayApi(
1167 useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
lib/ethereum/cw_ethereum.dart
+31 -2
@@ -30,14 +30,23 @@ class CWEthereum extends Ethereum {
30 }) =>
31 EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
32
33 + @override
34 + WalletCredentials createEthereumHardwareWalletCredentials({
35 + required String name,
36 + required HardwareAccountData hwAccountData,
37 + WalletInfo? walletInfo,
38 + }) =>
39 + EVMChainRestoreWalletFromHardware(
40 + name: name, hwAccountData: hwAccountData, walletInfo: walletInfo);
41 +
42 @override
43 String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
44
45 @override
46 String getPrivateKey(WalletBase wallet) {
47 final privateKeyHolder = (wallet as EthereumWallet).evmChainPrivateKey;
39 - String stringKey = bytesToHex(privateKeyHolder.privateKey);
40 - return stringKey;
48 + if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
49 + return "";
50 }
51
52 @override
@@ -159,4 +168,24 @@ class CWEthereum extends Ethereum {
168 }
169
170 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
171 +
172 + @override
173 + void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
174 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger(
175 + ledger,
176 + device.connectionType == ConnectionType.usb ? device : null,
177 + wallet.walletInfo.derivationInfo?.derivationPath);
178 + }
179 +
180 + @override
181 + Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
182 + {int index = 0, int limit = 5}) async {
183 + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
184 + try {
185 + return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
186 + } on LedgerException catch (err) {
187 + print(err.message);
188 + throw err;
189 + }
190 + }
191 }
lib/main.dart
+5 -1
@@ -105,7 +105,11 @@ Future<void> initializeAppConfigs() async {
105 if (!CakeHive.isAdapterRegistered(DERIVATION_INFO_TYPE_ID)) {
106 CakeHive.registerAdapter(DerivationInfoAdapter());
107 }
108 -
108 +
109 + if (!CakeHive.isAdapterRegistered(HARDWARE_WALLET_TYPE_TYPE_ID)) {
110 + CakeHive.registerAdapter(HardwareWalletTypeAdapter());
111 + }
112 +
113 if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
114 CakeHive.registerAdapter(WalletTypeAdapter());
115 }
lib/polygon/cw_polygon.dart
+45 -17
@@ -30,14 +30,23 @@ class CWPolygon extends Polygon {
30 }) =>
31 EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
32
33 + @override
34 + WalletCredentials createPolygonHardwareWalletCredentials({
35 + required String name,
36 + required HardwareAccountData hwAccountData,
37 + WalletInfo? walletInfo,
38 + }) =>
39 + EVMChainRestoreWalletFromHardware(
40 + name: name, hwAccountData: hwAccountData, walletInfo: walletInfo);
41 +
42 @override
43 String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
44
45 @override
46 String getPrivateKey(WalletBase wallet) {
47 final privateKeyHolder = (wallet as PolygonWallet).evmChainPrivateKey;
39 - String stringKey = bytesToHex(privateKeyHolder.privateKey);
40 - return stringKey;
48 + if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
49 + return "";
50 }
51
52 @override
@@ -67,21 +76,21 @@ class CWPolygon extends Polygon {
76 int? feeRate,
77 }) =>
78 EVMChainTransactionCredentials(
70 - outputs
71 - .map((out) => OutputInfo(
72 - fiatAmount: out.fiatAmount,
73 - cryptoAmount: out.cryptoAmount,
74 - address: out.address,
75 - note: out.note,
76 - sendAll: out.sendAll,
77 - extractedAddress: out.extractedAddress,
78 - isParsedAddress: out.isParsedAddress,
79 - formattedCryptoAmount: out.formattedCryptoAmount))
80 - .toList(),
81 - priority: priority as EVMChainTransactionPriority,
82 - currency: currency,
83 - feeRate: feeRate,
84 - );
79 + outputs
80 + .map((out) => OutputInfo(
81 + fiatAmount: out.fiatAmount,
82 + cryptoAmount: out.cryptoAmount,
83 + address: out.address,
84 + note: out.note,
85 + sendAll: out.sendAll,
86 + extractedAddress: out.extractedAddress,
87 + isParsedAddress: out.isParsedAddress,
88 + formattedCryptoAmount: out.formattedCryptoAmount))
89 + .toList(),
90 + priority: priority as EVMChainTransactionPriority,
91 + currency: currency,
92 + feeRate: feeRate,
93 + );
94
95 Object createPolygonTransactionCredentialsRaw(
96 List<OutputInfo> outputs, {
@@ -157,4 +166,23 @@ class CWPolygon extends Polygon {
166 }
167
168 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
169 +
170 + @override
171 + void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
172 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger(
173 + ledger,
174 + device.connectionType == ConnectionType.usb ? device : null,
175 + wallet.walletInfo.derivationInfo?.derivationPath);
176 + }
177 +
178 + @override
179 + Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
180 + {int index = 0, int limit = 5}) async {
181 + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
182 + try {
183 + return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
184 + } on LedgerException catch (err) {
185 + throw err;
186 + }
187 + }
188 }
lib/router.dart
+125 -71
@@ -1,120 +1,124 @@
1 import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 +import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
4 +import 'package:cake_wallet/buy/order.dart';
5 import 'package:cake_wallet/core/totp_request_details.dart';
6 import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
7 +import 'package:cake_wallet/di.dart';
8 import 'package:cake_wallet/entities/contact_record.dart';
6 -import 'package:cake_wallet/buy/order.dart';
9 import 'package:cake_wallet/entities/qr_view_data.dart';
10 import 'package:cake_wallet/entities/wallet_nft_response.dart';
11 +import 'package:cake_wallet/exchange/trade.dart';
12 +import 'package:cake_wallet/generated/i18n.dart';
13 +import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
14 +import 'package:cake_wallet/routes.dart';
15 import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
16 +import 'package:cake_wallet/src/screens/auth/auth_page.dart';
17 import 'package:cake_wallet/src/screens/backup/backup_page.dart';
18 import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
19 import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
20 import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
21 import 'package:cake_wallet/src/screens/buy/webview_page.dart';
22 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
23 +import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
24 +import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
25 +import 'package:cake_wallet/src/screens/contact/contact_page.dart';
26 +import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
27 +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
28 import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
29 import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
30 +import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
31 import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
18 -import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
19 -import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
20 -import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
21 -import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
22 -import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
23 -import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
24 -import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
32 import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
26 -import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
27 -import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
28 -import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
29 -import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
30 -import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
31 -import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
32 -import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
33 -import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
33 +import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
34 +import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
35 +import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
36 +import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
37 +import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
38 +import 'package:cake_wallet/src/screens/faq/faq_page.dart';
39 import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
40 import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
41 import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart';
42 import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_tip_page.dart';
43 import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
44 import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
45 +import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
46 +import 'package:cake_wallet/src/screens/ionia/ionia.dart';
47 +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
48 +import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
49 +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
50 import 'package:cake_wallet/src/screens/new_wallet/advanced_privacy_settings_page.dart';
51 +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
52 +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
53 +import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
54 +import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
55 import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
56 import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
57 +import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
58 +import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
59 +import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
60 +import 'package:cake_wallet/src/screens/receive/receive_page.dart';
61 +import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
62 import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
63 +import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
64 +import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
65 +import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
66 import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
67 import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
68 +import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
69 +import 'package:cake_wallet/src/screens/send/send_page.dart';
70 +import 'package:cake_wallet/src/screens/send/send_template_page.dart';
71 import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
47 -import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
72 +import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
73 +import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
74 +import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
75 +import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
76 +import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
77 +import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
78 +import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
79 import 'package:cake_wallet/src/screens/settings/tor_page.dart';
80 +import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
81 import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
50 -import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
51 -import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
82 import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
83 import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
84 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
85 +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
86 +import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
87 +import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
88 import 'package:cake_wallet/src/screens/support/support_page.dart';
89 import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
90 import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
91 +import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
92 +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
93 import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
94 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
95 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
96 +import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
97 import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
98 +import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
99 +import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
100 +import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
101 import 'package:cake_wallet/utils/payment_request.dart';
102 +import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
103 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
104 import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
105 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
106 import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
107 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
66 -import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
108 import 'package:cake_wallet/view_model/seed_type_view_model.dart';
109 +import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
110 +import 'package:cake_wallet/view_model/wallet_new_vm.dart';
111 import 'package:cake_wallet/wallet_type_utils.dart';
112 +import 'package:cake_wallet/wallet_types.g.dart';
113 +import 'package:cw_core/crypto_currency.dart';
114 import 'package:cw_core/nano_account.dart';
115 +import 'package:cw_core/node.dart';
116 +import 'package:cw_core/transaction_info.dart';
117 import 'package:cw_core/wallet_info.dart';
118 +import 'package:cw_core/wallet_type.dart';
119 import 'package:flutter/cupertino.dart';
120 import 'package:flutter/material.dart';
73 -import 'package:cake_wallet/routes.dart';
74 -import 'package:cake_wallet/generated/i18n.dart';
75 -import 'package:cake_wallet/di.dart';
76 -import 'package:cake_wallet/view_model/wallet_new_vm.dart';
77 -import 'package:cake_wallet/exchange/trade.dart';
78 -import 'package:cw_core/transaction_info.dart';
79 -import 'package:cw_core/wallet_type.dart';
80 -import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
81 -import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
82 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
83 -import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
84 -import 'package:cake_wallet/src/screens/receive/receive_page.dart';
85 -import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
86 -import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
87 -import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
88 -import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
89 -import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
90 -import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
91 -import 'package:cake_wallet/src/screens/send/send_page.dart';
92 -import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
93 -import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
94 -import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
95 -import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
96 -import 'package:cake_wallet/src/screens/contact/contact_page.dart';
97 -import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
98 -import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
99 -import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
100 -import 'package:cake_wallet/src/screens/faq/faq_page.dart';
101 -import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
102 -import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
103 -import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
104 -import 'package:cake_wallet/src/screens/send/send_template_page.dart';
105 -import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
106 -import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
107 -import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
121 import 'package:flutter/services.dart';
109 -import 'package:cake_wallet/wallet_types.g.dart';
110 -import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
111 -import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
112 -import 'package:cake_wallet/src/screens/ionia/ionia.dart';
113 -import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.dart';
114 -import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
115 -import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
116 -import 'package:cw_core/crypto_currency.dart';
117 -import 'package:cw_core/node.dart';
122
123 import 'src/screens/dashboard/pages/nft_import_page.dart';
124
@@ -151,7 +155,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
155 final walletNewVM = getIt.get<WalletNewVM>(param1: type);
156 final seedTypeViewModel = getIt.get<SeedTypeViewModel>();
157
154 - return CupertinoPageRoute<void>(builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel));
158 + return CupertinoPageRoute<void>(
159 + builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel));
160 +
161 + case Routes.chooseHardwareWalletAccount:
162 + final arguments = settings.arguments as List<dynamic>;
163 + final type = arguments[0] as WalletType;
164 + final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
165 +
166 + return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
167
168 case Routes.setupPin:
169 Function(PinCodeState<PinCodeWidget>, String)? callback;
@@ -168,7 +180,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
180 builder: (_) => getIt.get<NewWalletTypePage>(
181 param1: (BuildContext context, WalletType type) =>
182 Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
171 - param2: false));
183 + param2: [false, false]));
184
185 case Routes.restoreOptions:
186 final isNewInstall = settings.arguments as bool;
@@ -199,7 +211,46 @@ Route<dynamic> createRoute(RouteSettings settings) {
211 builder: (_) => getIt.get<NewWalletTypePage>(
212 param1: (BuildContext context, WalletType type) =>
213 Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
202 - param2: false));
214 + param2: [false, false]));
215 + }
216 +
217 + case Routes.restoreWalletFromHardwareWallet:
218 + final isNewInstall = settings.arguments as bool;
219 +
220 + if (isNewInstall) {
221 + return CupertinoPageRoute<void>(
222 + builder: (_) => getIt.get<SetupPinCodePage>(
223 + param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
224 + Navigator.of(context.context).pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: false),
225 + ),
226 + fullscreenDialog: true,
227 + );
228 + }
229 + if (isSingleCoin) {
230 + return MaterialPageRoute<void>(
231 + builder: (_) => ConnectDevicePage(
232 + ConnectDevicePageParams(
233 + walletType: availableWalletTypes.first,
234 + onConnectDevice: (BuildContext context, _) =>
235 + Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
236 + arguments: [availableWalletTypes.first]),
237 + ),
238 + getIt.get<LedgerViewModel>(),
239 + ));
240 + } else {
241 + return CupertinoPageRoute<void>(
242 + builder: (_) => getIt.get<NewWalletTypePage>(
243 + param1: (BuildContext context, WalletType type) {
244 + final arguments = ConnectDevicePageParams(
245 + walletType: type,
246 + onConnectDevice: (BuildContext context, _) =>
247 + Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
248 + arguments: [type]),
249 + );
250 +
251 + Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
252 + },
253 + param2: [false, true]));
254 }
255
256 case Routes.restoreWalletTypeFromQR:
@@ -400,8 +451,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
451
452 case Routes.buySellPage:
453 final args = settings.arguments as bool;
403 - return MaterialPageRoute<void>(
404 - builder: (_) => getIt.get<BuySellOptionsPage>(param1: args));
454 + return MaterialPageRoute<void>(builder: (_) => getIt.get<BuySellOptionsPage>(param1: args));
455
456 case Routes.buyWebView:
457 final args = settings.arguments as List;
@@ -424,8 +474,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
474
475 case Routes.preSeedPage:
476 return MaterialPageRoute<void>(
427 - builder: (_) => getIt.get<PreSeedPage>(
428 - param1: settings.arguments as int));
477 + builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as int));
478
479 case Routes.backup:
480 return CupertinoPageRoute<void>(
@@ -647,6 +696,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
696 case Routes.torPage:
697 return MaterialPageRoute<void>(builder: (_) => getIt.get<TorPage>());
698
699 + case Routes.connectDevices:
700 + final params = settings.arguments as ConnectDevicePageParams;
701 + return MaterialPageRoute<void>(
702 + builder: (_) => ConnectDevicePage(params, getIt.get<LedgerViewModel>()));
703 +
704 default:
705 return MaterialPageRoute<void>(
706 builder: (_) => Scaffold(
lib/routes.dart
+3
@@ -6,9 +6,11 @@ class Routes {
6 static const seed = '/seed';
7 static const restoreOptions = '/restore_options';
8 static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
9 + static const restoreWalletFromHardwareWallet = '/restore/hardware_wallet';
10 static const restoreWalletTypeFromQR = '/restore_wallet_from_qr_code';
11 static const restoreWalletChooseDerivation =
12 '/restore_wallet_choose_derivation';
13 + static const chooseHardwareWalletAccount = '/restore/hardware_wallet/accounts';
14 static const dashboard = '/dashboard';
15 static const send = '/send';
16 static const transactionDetails = '/transaction_info';
@@ -107,4 +109,5 @@ class Routes {
109 static const nftDetailsPage = '/nft_details_page';
110 static const importNFTPage = '/import_nft_page';
111 static const torPage = '/tor_page';
112 + static const connectDevices = '/device/connect';
113 }
lib/src/screens/connect_device/connect_device_page.dart new
+221
@@ -0,0 +1,221 @@
1 +import 'dart:async';
2 +import 'dart:io';
3 +
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/src/screens/base_page.dart';
6 +import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
7 +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
8 +import 'package:cake_wallet/utils/responsive_layout_util.dart';
9 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
10 +import 'package:cw_core/wallet_type.dart';
11 +import 'package:flutter/material.dart';
12 +import 'package:ledger_flutter/ledger_flutter.dart';
13 +import 'package:permission_handler/permission_handler.dart';
14 +
15 +typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
16 +
17 +class ConnectDevicePageParams {
18 + final WalletType walletType;
19 + final OnConnectDevice onConnectDevice;
20 +
21 + ConnectDevicePageParams({required this.walletType, required this.onConnectDevice});
22 +}
23 +
24 +class ConnectDevicePage extends BasePage {
25 + final WalletType walletType;
26 + final OnConnectDevice onConnectDevice;
27 + final LedgerViewModel ledgerVM;
28 +
29 + ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
30 + : walletType = params.walletType,
31 + onConnectDevice = params.onConnectDevice;
32 +
33 + @override
34 + String get title => S.current.restore_title_from_hardware_wallet;
35 +
36 + @override
37 + Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
38 +}
39 +
40 +class ConnectDevicePageBody extends StatefulWidget {
41 + final WalletType walletType;
42 + final OnConnectDevice onConnectDevice;
43 + final LedgerViewModel ledgerVM;
44 +
45 + const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM);
46 +
47 + @override
48 + ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
49 +}
50 +
51 +class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
52 + final imageLedger = 'assets/images/ledger_nano.png';
53 +
54 + final ledger = Ledger(
55 + options: LedgerOptions(
56 + scanMode: ScanMode.balanced,
57 + maxScanDuration: const Duration(minutes: 5),
58 + ),
59 + onPermissionRequest: (_) async {
60 + Map<Permission, PermissionStatus> statuses = await [
61 + Permission.bluetoothScan,
62 + Permission.bluetoothConnect,
63 + Permission.bluetoothAdvertise,
64 + ].request();
65 +
66 + return statuses.values.where((status) => status.isDenied).isEmpty;
67 + },
68 + );
69 +
70 + var bleIsEnabled = true;
71 + var bleDevices = <LedgerDevice>[];
72 + var usbDevices = <LedgerDevice>[];
73 +
74 + late Timer? _usbRefreshTimer = null;
75 + late Timer? _bleRefreshTimer = null;
76 + late StreamSubscription<LedgerDevice>? _bleRefresh = null;
77 +
78 + @override
79 + void initState() {
80 + super.initState();
81 + Future.delayed(
82 + Duration(seconds: 1),
83 + () => _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))),
84 + );
85 + // _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
86 +
87 + if (Platform.isAndroid) {
88 + _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
89 + }
90 + }
91 +
92 + @override
93 + void dispose() {
94 + _bleRefreshTimer?.cancel();
95 + _usbRefreshTimer?.cancel();
96 + _bleRefresh?.cancel();
97 + super.dispose();
98 + }
99 +
100 + Future<void> _refreshUsbDevices() async {
101 + final dev = await ledger.listUsbDevices();
102 + if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
103 + }
104 +
105 + Future<void> _refreshBleDevices() async {
106 + final isBleEnabled = await Permission.bluetooth.serviceStatus.isEnabled;
107 +
108 + setState(() => bleIsEnabled = isBleEnabled);
109 +
110 + if (isBleEnabled) {
111 + _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device)));
112 + _bleRefreshTimer?.cancel();
113 + _bleRefreshTimer = null;
114 + }
115 + }
116 +
117 + Future<void> _connectToDevice(LedgerDevice device) async {
118 + await widget.ledgerVM.connectLedger(device);
119 + widget.onConnectDevice(context, widget.ledgerVM);
120 + }
121 +
122 + @override
123 + Widget build(BuildContext context) {
124 + return Center(
125 + child: Container(
126 + width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
127 + height: double.infinity,
128 + padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
129 + child: SingleChildScrollView(
130 + child: Column(
131 + children: [
132 + Padding(
133 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
134 + child: Text(
135 + Platform.isIOS
136 + ? S.of(context).connect_your_hardware_wallet_ios
137 + : S.of(context).connect_your_hardware_wallet,
138 + style: TextStyle(
139 + fontSize: 16,
140 + fontWeight: FontWeight.w500,
141 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
142 + textAlign: TextAlign.center,
143 + ),
144 + ),
145 + if (!bleIsEnabled)
146 + Padding(
147 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
148 + child: Text(
149 + S.of(context).ledger_please_enable_bluetooth,
150 + style: TextStyle(
151 + fontSize: 16,
152 + fontWeight: FontWeight.w500,
153 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
154 + textAlign: TextAlign.center,
155 + ),
156 + ),
157 + if (bleDevices.length > 0) ...[
158 + Padding(
159 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
160 + child: Container(
161 + width: double.infinity,
162 + child: Text(
163 + S.of(context).bluetooth,
164 + style: TextStyle(
165 + fontSize: 14,
166 + fontWeight: FontWeight.w400,
167 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
168 + ),
169 + ),
170 + ),
171 + ),
172 + ...bleDevices
173 + .map(
174 + (device) => Padding(
175 + padding: EdgeInsets.only(bottom: 20),
176 + child: DeviceTile(
177 + onPressed: () => _connectToDevice(device),
178 + title: device.name,
179 + leading: imageLedger,
180 + connectionType: device.connectionType,
181 + ),
182 + ),
183 + )
184 + .toList()
185 + ],
186 + if (usbDevices.length > 0) ...[
187 + Padding(
188 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
189 + child: Container(
190 + width: double.infinity,
191 + child: Text(
192 + S.of(context).usb,
193 + style: TextStyle(
194 + fontSize: 14,
195 + fontWeight: FontWeight.w400,
196 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
197 + ),
198 + ),
199 + ),
200 + ),
201 + ...usbDevices
202 + .map(
203 + (device) => Padding(
204 + padding: EdgeInsets.only(bottom: 20),
205 + child: DeviceTile(
206 + onPressed: () => _connectToDevice(device),
207 + title: device.name,
208 + leading: imageLedger,
209 + connectionType: device.connectionType,
210 + ),
211 + ),
212 + )
213 + .toList(),
214 + ]
215 + ],
216 + ),
217 + ),
218 + ),
219 + );
220 + }
221 +}
lib/src/screens/connect_device/debug_device_page.dart new
+212
@@ -0,0 +1,212 @@
1 +// import 'dart:convert';
2 +//
3 +// import 'package:cake_wallet/src/screens/base_page.dart';
4 +// import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
5 +// import 'package:cake_wallet/src/widgets/primary_button.dart';
6 +// import 'package:cake_wallet/utils/responsive_layout_util.dart';
7 +// import 'package:convert/convert.dart';
8 +// import 'package:flutter/material.dart';
9 +// import 'package:ledger_bitcoin/ledger_bitcoin.dart';
10 +// import 'package:ledger_flutter/ledger_flutter.dart';
11 +// import 'package:permission_handler/permission_handler.dart';
12 +// import 'package:polyseed/polyseed.dart';
13 +//
14 +// class DebugDevicePage extends BasePage {
15 +// @override
16 +// String get title => "Connect Ledger";
17 +//
18 +// @override
19 +// Widget body(BuildContext context) => DebugDevicePageBody();
20 +// }
21 +//
22 +// class DebugDevicePageBody extends StatefulWidget {
23 +// @override
24 +// DebugDevicePageBodyState createState() => DebugDevicePageBodyState();
25 +// }
26 +//
27 +// class DebugDevicePageBodyState extends State<DebugDevicePageBody> {
28 +// final imageLedger = Image.asset(
29 +// 'assets/images/ledger_icon_black.png',
30 +// width: 40,
31 +// );
32 +// final ledger = Ledger(
33 +// options: LedgerOptions(
34 +// scanMode: ScanMode.balanced,
35 +// maxScanDuration: const Duration(milliseconds: 5000),
36 +// ),
37 +// onPermissionRequest: (status) async {
38 +// Map<Permission, PermissionStatus> statuses = await [
39 +// // Permission.location,
40 +// Permission.bluetoothScan,
41 +// Permission.bluetoothConnect,
42 +// Permission.bluetoothAdvertise,
43 +// ].request();
44 +//
45 +// if (status != BleStatus.ready) {
46 +// return false;
47 +// }
48 +//
49 +// return statuses.values.where((status) => status.isDenied).isEmpty;
50 +// },
51 +// );
52 +//
53 +// late BitcoinLedgerApp btc;
54 +// var devices = <LedgerDevice>[];
55 +// var status = "";
56 +// var counter = 0;
57 +// LedgerDevice? selectedDevice = null;
58 +//
59 +// @override
60 +// void initState() {
61 +// super.initState();
62 +// btc = BitcoinLedgerApp(ledger);
63 +// }
64 +//
65 +// @override
66 +// void dispose() {
67 +// super.dispose();
68 +// ledger.close(ConnectionType.ble);
69 +// ledger.close(ConnectionType.usb);
70 +// }
71 +//
72 +// Future<void> reconnectCurrentDevice() async {
73 +// // await ledger.disconnect(selectedDevice!);
74 +// // await ledger.connect(selectedDevice!);
75 +// }
76 +//
77 +// Future<void> disconnectCurrentDevice() async {
78 +// await ledger.disconnect(selectedDevice!);
79 +// setState(() => selectedDevice = null);
80 +// }
81 +//
82 +// @override
83 +// Widget build(BuildContext context) {
84 +// final imageLedger = 'assets/images/ledger_nano.png';
85 +//
86 +// return Center(
87 +// child: Container(
88 +// width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
89 +// height: double.infinity,
90 +// padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
91 +// child: SingleChildScrollView(
92 +// child: Column(
93 +// children: [
94 +// Padding(
95 +// padding: EdgeInsets.only(top: 20),
96 +// child: Text(status),
97 +// ),
98 +// if (selectedDevice != null) ...[
99 +// DebugButton(
100 +// title: "Get Version",
101 +// method: "Version",
102 +// func: () async => await btc.getVersion(selectedDevice!),
103 +// ),
104 +// DebugButton(
105 +// title: "Get Master Fingerprint",
106 +// method: "Master Fingerprint",
107 +// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)),
108 +// ),
109 +// DebugButton(
110 +// title: "Get XPub",
111 +// method: "XPub",
112 +// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"),
113 +// ),
114 +// DebugButton(
115 +// title: "Get Wallet Address",
116 +// method: "Wallet Address",
117 +// func: () async {
118 +// setState(() => counter++);
119 +// final derivationPath = "m/84'/0'/$counter'/0/0";
120 +// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
121 +// // return await ethereum!.getHardwareWalletAccounts(selectedDevice!);
122 +// },
123 +// ),
124 +// DebugButton(
125 +// title: "Send Money",
126 +// method: "Sig",
127 +// func: () async {
128 +// final psbt = PsbtV2();
129 +// final psbtBuf = base64.decode(
130 +// "cHNidP8BAgQCAAAAAQQBAQEFAQIAAQ4gTW6k/cwKKu1u7m9oKr5ob7VcAC0IPkfaDitRi/FkD7sBDwQAAAAAARAE/////wEA/ekBAQAAAAABA9AYVQLI722H0osKMa/4dvMucrnKV1Myxtlp0l0BoOBDAQAAAAD/////ku6r2ABaHt9N26f/P4eMljX8t1f4lBcFfEwuNm/uXYoBAAAAAP////+YeAl8arEGKOcyrWJAYwSboyCstkhHN8zn7/vy7pkYTAEAAAAA/////wHlHgAAAAAAABYAFKdq0umSucBGVkl2MpT6Hgo/0a/xAkcwRAIgMkiJmNFbEi2I3CQYOwyV/JepCnFQRvj4xghkySpFcJMCIGAypkkWltfj+ucvqUIu27tusDAIAAB+rBhX/GV7hPlEASEDyLmWyTLjLfC9kn8pnW42jW5N6EJo5fObjWWEyfLDu9UCSDBFAiEAg9crVtwBPF+sWk+Th6pLwzDjJGItwsUCvoBPtmMTEb4CIDGuM7WOguV0TP21oidF3bSUZlEAjUHWfWzxLKw+3LofASEDfN16xKb70UZSeQyX5Tlh8iRq7np5Nlz9GYdcSU50sKwCSDBFAiEAvotOblaEiBptRWkvb6bj2MGyRjTphKLBLiHYmrRMTCgCIEKJH+z65uPSSz1NIb0d/u3bU9l0xcWk0idEsXjB+BIiASEDrAEiEtrSNKxbh6F/KPaCTafF2LVjCzb75WB+x4xSuoQAAAAAAQEf5R4AAAAAAAAWABSnatLpkrnARlZJdjKU+h4KP9Gv8SIGA3xMuxmPsBAm9aMEUBs3N46DB+Kdts3bZR/Wxt+uM0H4GKtN6bpUAACAAAAAgAAAAIAAAAAAAAAAAAABBBTk7bEOxYcdXDi1eeWraYDufm6eJgEDCOgDAAAAAAAAAAEEFDX3g/pnDXIfsRw8shK42NZn+SdpAQMIiBMAAAAAAAAiAgN8TLsZj7AQJvWjBFAbNzeOgwfinbbN22Uf1sbfrjNB+BirTem6VAAAgAAAAIAAAACAAAAAAAAAAAAA"
131 +// );
132 +// psbt.deserialize(psbtBuf);
133 +// final result = await btc.signPsbt(selectedDevice!, psbt: psbt);
134 +// return result.toHexString();
135 +// },
136 +// ),
137 +// Padding(
138 +// padding: EdgeInsets.only(top: 20),
139 +// child: PrimaryButton(
140 +// text: "Disconnect",
141 +// onPressed: () => disconnectCurrentDevice(),
142 +// color: Theme.of(context).primaryColor,
143 +// textColor: Colors.white),
144 +// ),
145 +// ],
146 +// if (selectedDevice == null) ...[
147 +// ...devices
148 +// .map(
149 +// (device) => Padding(
150 +// padding: EdgeInsets.only(bottom: 20),
151 +// child: DeviceTile(
152 +// onPressed: () {
153 +// setState(() => selectedDevice = device);
154 +// ledger.connect(device);
155 +// },
156 +// title: device.name,
157 +// leading: imageLedger,
158 +// connectionType: device.connectionType,
159 +// ),
160 +// ),
161 +// )
162 +// .toList(),
163 +// PrimaryButton(
164 +// text: "Refresh BLE",
165 +// onPressed: () async {
166 +// setState(() => devices = []);
167 +// ledger.scan().listen((device) => setState(() {
168 +// devices.add(device);
169 +// }));
170 +// },
171 +// color: Theme.of(context).primaryColor,
172 +// textColor: Colors.white),
173 +// Padding(
174 +// padding: EdgeInsets.only(top: 20),
175 +// child: PrimaryButton(
176 +// text: "Use USB",
177 +// onPressed: () async {
178 +// final dev = await ledger.listUsbDevices();
179 +// setState(() => devices = dev);
180 +// },
181 +// color: Theme.of(context).primaryColor,
182 +// textColor: Colors.white),
183 +// ),
184 +// ],
185 +// ],
186 +// ),
187 +// )),
188 +// );
189 +// }
190 +//
191 +// Widget DebugButton(
192 +// {required String title, required String method, required Future<dynamic> Function() func}) {
193 +// return Padding(
194 +// padding: EdgeInsets.only(top: 20),
195 +// child: PrimaryButton(
196 +// text: title,
197 +// onPressed: () async {
198 +// try {
199 +// setState(() => status = "Sending...");
200 +// final acc = await func();
201 +// setState(() => status = "$method: $acc");
202 +// print("$method: $acc");
203 +// } on LedgerException catch (ex) {
204 +// setState(() => status = "${ex.errorCode.toRadixString(16)} ${ex.message}");
205 +// print("${ex.errorCode.toRadixString(16)} ${ex.message}");
206 +// }
207 +// },
208 +// color: Theme.of(context).primaryColor,
209 +// textColor: Colors.white),
210 +// );
211 +// }
212 +// }
lib/src/screens/connect_device/select_hardware_wallet_account_page.dart new
+256
@@ -0,0 +1,256 @@
1 +import 'package:cake_wallet/core/wallet_name_validator.dart';
2 +import 'package:cake_wallet/entities/generate_name.dart';
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
6 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
10 +import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
11 +import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
12 +import 'package:cake_wallet/utils/responsive_layout_util.dart';
13 +import 'package:cake_wallet/utils/show_pop_up.dart';
14 +import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
15 +import 'package:cw_core/wallet_type.dart';
16 +import 'package:flutter/material.dart';
17 +import 'package:flutter_mobx/flutter_mobx.dart';
18 +import 'package:mobx/mobx.dart';
19 +
20 +class SelectHardwareWalletAccountPage extends BasePage {
21 + SelectHardwareWalletAccountPage(this._walletHardwareRestoreVM);
22 +
23 + final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
24 +
25 + @override
26 + String get title => S.current.restore_title_from_hardware_wallet;
27 +
28 + @override
29 + Widget body(BuildContext context) => SelectHardwareWalletAccountForm(_walletHardwareRestoreVM);
30 +}
31 +
32 +class SelectHardwareWalletAccountForm extends StatefulWidget {
33 + SelectHardwareWalletAccountForm(this._walletHardwareRestoreVM);
34 +
35 + final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
36 +
37 + @override
38 + _SelectHardwareWalletAccountFormState createState() =>
39 + _SelectHardwareWalletAccountFormState(_walletHardwareRestoreVM);
40 +}
41 +
42 +class _SelectHardwareWalletAccountFormState extends State<SelectHardwareWalletAccountForm> {
43 + _SelectHardwareWalletAccountFormState(this._walletHardwareRestoreVM)
44 + : _formKey = GlobalKey<FormState>(),
45 + _controller = TextEditingController();
46 +
47 + final GlobalKey<FormState> _formKey;
48 + final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
49 + final TextEditingController _controller;
50 +
51 + @override
52 + void initState() {
53 + super.initState();
54 + _setEffects(context);
55 + if (_walletHardwareRestoreVM.availableAccounts.length == 0) _loadMoreAccounts();
56 + }
57 +
58 + @override
59 + Widget build(BuildContext context) {
60 + return Padding(
61 + padding: EdgeInsets.only(top: 24),
62 + child: ScrollableWithBottomSection(
63 + contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
64 + content: Center(
65 + child: ConstrainedBox(
66 + constraints:
67 + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
68 + child: Column(
69 + crossAxisAlignment: CrossAxisAlignment.center,
70 + children: [
71 + Padding(
72 + padding: EdgeInsets.only(top: 0),
73 + child: Form(
74 + key: _formKey,
75 + child: Stack(
76 + alignment: Alignment.centerRight,
77 + children: [
78 + TextFormField(
79 + onChanged: (value) => _walletHardwareRestoreVM.name = value,
80 + controller: _controller,
81 + style: TextStyle(
82 + fontSize: 20.0,
83 + fontWeight: FontWeight.w600,
84 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
85 + ),
86 + decoration: InputDecoration(
87 + hintStyle: TextStyle(
88 + fontSize: 18.0,
89 + fontWeight: FontWeight.w500,
90 + color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
91 + ),
92 + hintText: S.of(context).wallet_name,
93 + focusedBorder: UnderlineInputBorder(
94 + borderSide: BorderSide(
95 + color:
96 + Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
97 + width: 1.0,
98 + ),
99 + ),
100 + enabledBorder: UnderlineInputBorder(
101 + borderSide: BorderSide(
102 + color:
103 + Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
104 + width: 1.0,
105 + ),
106 + ),
107 + suffixIcon: Semantics(
108 + label: S.of(context).generate_name,
109 + child: IconButton(
110 + onPressed: () async {
111 + final rName = await generateName();
112 + FocusManager.instance.primaryFocus?.unfocus();
113 +
114 + setState(() {
115 + _controller.text = rName;
116 + _walletHardwareRestoreVM.name = rName;
117 + _controller.selection = TextSelection.fromPosition(
118 + TextPosition(offset: _controller.text.length));
119 + });
120 + },
121 + icon: Container(
122 + padding: const EdgeInsets.all(8),
123 + decoration: BoxDecoration(
124 + borderRadius: BorderRadius.circular(6.0),
125 + color: Theme.of(context).hintColor,
126 + ),
127 + width: 34,
128 + height: 34,
129 + child: Image.asset(
130 + 'assets/images/refresh_icon.png',
131 + color: Theme.of(context)
132 + .extension<SendPageTheme>()!
133 + .textFieldButtonIconColor,
134 + ),
135 + ),
136 + ),
137 + ),
138 + ),
139 + validator: WalletNameValidator(),
140 + ),
141 + ],
142 + ),
143 + ),
144 + ),
145 + Padding(
146 + padding: EdgeInsets.only(top: 20),
147 + child: Container(
148 + width: double.infinity,
149 + child: Text(
150 + "Available accounts",
151 + style: TextStyle(
152 + fontSize: 16,
153 + fontWeight: FontWeight.w500,
154 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
155 + ),
156 + ),
157 + ),
158 + ),
159 + Observer(
160 + builder: (context) => Column(
161 + children: _walletHardwareRestoreVM.availableAccounts.map((acc) {
162 + final address = acc.address;
163 + return Padding(
164 + padding: EdgeInsets.only(top: 10),
165 + child: SelectButton(
166 + image: Image.asset(
167 + walletTypeToCryptoCurrency(_walletHardwareRestoreVM.type).iconPath ??
168 + '',
169 + height: 24,
170 + width: 24,
171 + ),
172 + text:
173 + "${address.substring(0, 6)}...${address.substring(address.length - 6)}",
174 + showTrailingIcon: false,
175 + height: 54,
176 + isSelected: _walletHardwareRestoreVM.selectedAccount == acc,
177 + onTap: () =>
178 + setState(() => _walletHardwareRestoreVM.selectedAccount = acc),
179 + ),
180 + );
181 + }).toList(),
182 + ),
183 + ),
184 + Padding(
185 + padding: EdgeInsets.only(top: 10),
186 + child: Observer(builder: (context) {
187 + return LoadingPrimaryButton(
188 + onPressed: _loadMoreAccounts,
189 + text: S.of(context).load_more,
190 + color: Theme.of(context).primaryColor,
191 + textColor: Colors.white,
192 + isLoading: _walletHardwareRestoreVM.isLoadingMoreAccounts,
193 + );
194 + }),
195 + ),
196 + ],
197 + ),
198 + ),
199 + ),
200 + bottomSectionPadding: EdgeInsets.all(24),
201 + bottomSection: Observer(
202 + builder: (context) {
203 + return LoadingPrimaryButton(
204 + onPressed: _confirmForm,
205 + text: S.of(context).seed_language_next,
206 + color: Colors.green,
207 + textColor: Colors.white,
208 + isDisabled: _walletHardwareRestoreVM.name.isEmpty,
209 + );
210 + },
211 + ),
212 + ),
213 + );
214 + }
215 +
216 + Future<void> _loadMoreAccounts() async {
217 + _walletHardwareRestoreVM.isLoadingMoreAccounts = true;
218 + _walletHardwareRestoreVM.getNextAvailableAccounts(5);
219 + }
220 +
221 + Future<void> _confirmForm() async {
222 + await _walletHardwareRestoreVM.create();
223 + }
224 +
225 + bool _effectsInstalled = false;
226 +
227 + void _setEffects(BuildContext context) {
228 + if (_effectsInstalled) return;
229 +
230 + reaction((_) => _walletHardwareRestoreVM.error, (String? error) {
231 +
232 + if (error != null) {
233 +
234 + if (error == S.current.ledger_connection_error)
235 + Navigator.of(context).pop();
236 +
237 + WidgetsBinding.instance.addPostFrameCallback((_) {
238 + showPopUp<void>(
239 + context: context,
240 + builder: (BuildContext context) {
241 + return AlertWithOneAction(
242 + alertTitle: S.of(context).error,
243 + alertContent: error,
244 + buttonText: S.of(context).ok,
245 + buttonAction: () {
246 + _walletHardwareRestoreVM.error = null;
247 + Navigator.of(context).pop();
248 + });
249 + });
250 + });
251 + }
252 + });
253 +
254 + _effectsInstalled = true;
255 + }
256 +}
lib/src/screens/connect_device/widgets/device_tile.dart new
+78
@@ -0,0 +1,78 @@
1 +import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:ledger_flutter/ledger_flutter.dart';
4 +
5 +class DeviceTile extends StatelessWidget {
6 + const DeviceTile({
7 + required this.onPressed,
8 + required this.title,
9 + this.leading,
10 + this.connectionType,
11 + });
12 +
13 + final VoidCallback onPressed;
14 + final String title;
15 + final String? leading;
16 + final ConnectionType? connectionType;
17 +
18 + String? get connectionTypeIcon {
19 + switch (connectionType) {
20 + case ConnectionType.ble:
21 + return 'assets/images/bluetooth.png';
22 + case ConnectionType.usb:
23 + return 'assets/images/usb.png';
24 + case null:
25 + return null;
26 + }
27 + }
28 +
29 + @override
30 + Widget build(BuildContext context) {
31 + return GestureDetector(
32 + onTap: onPressed,
33 + child: Container(
34 + width: double.infinity,
35 + padding: EdgeInsets.all(24),
36 + alignment: Alignment.center,
37 + decoration: BoxDecoration(
38 + borderRadius: BorderRadius.all(Radius.circular(12)),
39 + color: Theme.of(context).cardColor,
40 + ),
41 + child: Row(
42 + mainAxisSize: MainAxisSize.max,
43 + mainAxisAlignment: MainAxisAlignment.center,
44 + crossAxisAlignment: CrossAxisAlignment.center,
45 + children: <Widget>[
46 + if (leading != null)
47 + Image.asset(
48 + leading!,
49 + height: 30,
50 + color: Theme.of(context).extension<OptionTileTheme>()!.titleColor,
51 + ),
52 + Expanded(
53 + child: Padding(
54 + padding: EdgeInsets.only(left: 16),
55 + child: Text(
56 + title,
57 + style: TextStyle(
58 + fontSize: 20,
59 + fontWeight: FontWeight.w500,
60 + color: Theme.of(context).extension<OptionTileTheme>()!.titleColor,
61 + ),
62 + ),
63 + ),
64 + ),
65 + if (connectionTypeIcon != null)
66 + Center(
67 + child: Image.asset(
68 + connectionTypeIcon!,
69 + height: 25,
70 + color: Theme.of(context).extension<OptionTileTheme>()!.titleColor,
71 + ),
72 + )
73 + ],
74 + ),
75 + ),
76 + );
77 + }
78 +}
lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart
+1
@@ -45,6 +45,7 @@ class SideMenuItem extends StatelessWidget {
45 ? Icon(
46 icon,
47 color: _setColor(context),
48 + size: 30,
49 )
50 : Image.asset(
51 imagePath ?? '',
lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart
+2 -1
@@ -82,7 +82,8 @@ class DesktopSidebarWrapper extends BasePage {
82 width: sideMenuWidth,
83 topItems: [
84 SideMenuItem(
85 - imagePath: 'assets/images/wallet_outline.png',
85 + // imagePath: 'assets/images/wallet_outline.png',
86 + icon: Icons.home,
87 isSelected: desktopSidebarViewModel.currentPage == SidebarItem.dashboard,
88 onTap: () {
89 desktopSidebarViewModel.onPageChange(SidebarItem.dashboard);
lib/src/screens/dashboard/pages/balance_page.dart
+11
@@ -148,6 +148,17 @@ class CryptoBalanceWidget extends StatelessWidget {
148 maxLines: 1,
149 textAlign: TextAlign.center,
150 ),
151 + if (dashboardViewModel.wallet.isHardwareWallet)
152 + Padding(
153 + padding: const EdgeInsets.all(8.0),
154 + child: Image.asset(
155 + 'assets/images/ledger_nano.png',
156 + width: 24,
157 + color: Theme.of(context)
158 + .extension<DashboardPageTheme>()!
159 + .pageTitleTextColor,
160 + ),
161 + ),
162 if (dashboardViewModel
163 .balanceViewModel.isHomeScreenSettingsEnabled)
164 InkWell(
lib/src/screens/new_wallet/new_wallet_type_page.dart
+87 -63
@@ -1,3 +1,5 @@
1 +import 'dart:io';
2 +
3 import 'package:cake_wallet/generated/i18n.dart';
4 import 'package:cake_wallet/src/screens/base_page.dart';
5 import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
@@ -10,14 +12,20 @@ import 'package:cake_wallet/themes/theme_base.dart';
12 import 'package:cake_wallet/utils/responsive_layout_util.dart';
13 import 'package:cake_wallet/utils/show_pop_up.dart';
14 import 'package:cake_wallet/wallet_types.g.dart';
15 +import 'package:cw_core/hardware/device_connection_type.dart';
16 import 'package:cw_core/wallet_type.dart';
17 import 'package:flutter/material.dart';
18
19 class NewWalletTypePage extends BasePage {
17 - NewWalletTypePage({required this.onTypeSelected, required this.isCreate});
20 + NewWalletTypePage({
21 + required this.onTypeSelected,
22 + required this.isCreate,
23 + required this.isHardwareWallet,
24 + });
25
26 final void Function(BuildContext, WalletType) onTypeSelected;
27 final bool isCreate;
28 + final bool isHardwareWallet;
29
30 final walletTypeImage = Image.asset('assets/images/wallet_type.png');
31 final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
@@ -31,15 +39,22 @@ class NewWalletTypePage extends BasePage {
39 onTypeSelected: onTypeSelected,
40 walletImage: currentTheme.type == ThemeType.dark ? walletTypeImage : walletTypeLightImage,
41 isCreate: isCreate,
42 + isHardwareWallet: isHardwareWallet,
43 );
44 }
45
46 class WalletTypeForm extends StatefulWidget {
38 - WalletTypeForm({required this.onTypeSelected, required this.walletImage, required this.isCreate});
47 + WalletTypeForm({
48 + required this.onTypeSelected,
49 + required this.walletImage,
50 + required this.isCreate,
51 + required this.isHardwareWallet,
52 + });
53
54 final void Function(BuildContext, WalletType) onTypeSelected;
55 final Image walletImage;
56 final bool isCreate;
57 + final bool isHardwareWallet;
58
59 @override
60 WalletTypeFormState createState() => WalletTypeFormState();
@@ -58,7 +73,11 @@ class WalletTypeFormState extends State<WalletTypeForm> {
73
74 @override
75 void initState() {
61 - types = filteredTypes = availableWalletTypes;
76 + types = filteredTypes = availableWalletTypes
77 + .where((element) =>
78 + !widget.isHardwareWallet ||
79 + DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty)
80 + .toList();
81 super.initState();
82
83 searchController.addListener(() {
@@ -74,76 +93,81 @@ class WalletTypeFormState extends State<WalletTypeForm> {
93 @override
94 Widget build(BuildContext context) {
95 return Center(
77 - child: ConstrainedBox(
78 - constraints:
79 - BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
80 - child: Column(
81 - children: [
82 - Padding(
83 - padding: EdgeInsets.only(top: 48),
84 - child: Text(
85 - S.of(context).choose_wallet_currency,
86 - textAlign: TextAlign.center,
87 - style: TextStyle(
88 - fontSize: 16,
89 - fontWeight: FontWeight.w500,
90 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
91 - ),
92 - ),
93 - Padding(
94 - padding: const EdgeInsets.fromLTRB(24, 24, 24, 12),
95 - child: SearchBarWidget(searchController: searchController, borderRadius: 24),
96 + child: ConstrainedBox(
97 + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
98 + child: Column(
99 + children: [
100 + Padding(
101 + padding: EdgeInsets.only(top: 48),
102 + child: Text(
103 + S.of(context).choose_wallet_currency,
104 + textAlign: TextAlign.center,
105 + style: TextStyle(
106 + fontSize: 16,
107 + fontWeight: FontWeight.w500,
108 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
109 ),
97 - Expanded(
98 - child: ScrollableWithBottomSection(
99 - contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
100 - content: Column(
101 - crossAxisAlignment: CrossAxisAlignment.center,
102 - children: <Widget>[
103 - ...filteredTypes.map((type) => Padding(
104 - padding: EdgeInsets.only(top: 12),
105 - child: SelectButton(
106 - image: Image.asset(
107 - walletTypeToCryptoCurrency(type).iconPath ?? '',
108 - height: 24,
109 - width: 24),
110 - text: walletTypeToDisplayName(type),
111 - showTrailingIcon: false,
112 - height: 54,
113 - isSelected: selected == type,
114 - onTap: () => setState(() => selected = type)),
115 - ))
116 - ],
117 - ),
118 - bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
119 - bottomSection: PrimaryButton(
120 - onPressed: () => onTypeSelected(),
121 - text: S.of(context).seed_language_next,
122 - color: Theme.of(context).primaryColor,
123 - textColor: Colors.white,
124 - isDisabled: selected == null,
110 + ),
111 + ),
112 + Padding(
113 + padding: EdgeInsets.fromLTRB(24, 24, 24, 12),
114 + child: SearchBarWidget(searchController: searchController, borderRadius: 24),
115 + ),
116 + Expanded(
117 + child: ScrollableWithBottomSection(
118 + contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
119 + content: Column(
120 + crossAxisAlignment: CrossAxisAlignment.center,
121 + children: <Widget>[
122 + ...filteredTypes.map(
123 + (type) => Padding(
124 + padding: EdgeInsets.only(top: 12),
125 + child: SelectButton(
126 + image: Image.asset(
127 + walletTypeToCryptoCurrency(type).iconPath ?? '',
128 + height: 24,
129 + width: 24,
130 + ),
131 + text: walletTypeToDisplayName(type),
132 + showTrailingIcon: false,
133 + height: 54,
134 + isSelected: selected == type,
135 + onTap: () => setState(() => selected = type),
136 + deviceConnectionTypes: widget.isHardwareWallet
137 + ? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS)
138 + : [],
139 + ),
140 + ),
141 ),
126 - ),
142 + ],
143 ),
128 - ],
129 - )));
144 + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
145 + bottomSection: PrimaryButton(
146 + onPressed: () => onTypeSelected(),
147 + text: S.of(context).seed_language_next,
148 + color: Theme.of(context).primaryColor,
149 + textColor: Colors.white,
150 + isDisabled: selected == null,
151 + ),
152 + ),
153 + ),
154 + ],
155 + ),
156 + ),
157 + );
158 }
159
160 Future<void> onTypeSelected() async {
133 - if (selected == null) {
134 - throw Exception('Wallet Type is not selected yet.');
135 - }
161 + if (selected == null) throw Exception('Wallet Type is not selected yet.');
162
163 if (selected == WalletType.haven && widget.isCreate) {
164 return await showPopUp<void>(
165 context: context,
140 - builder: (BuildContext context) {
141 - return PopUpCancellableAlertDialog(
142 - contentText: S.of(context).pause_wallet_creation,
143 - actionButtonText: S.of(context).ok,
144 - buttonAction: () => Navigator.of(context).pop(),
145 - );
146 - },
166 + builder: (BuildContext context) => PopUpCancellableAlertDialog(
167 + contentText: S.of(context).pause_wallet_creation,
168 + actionButtonText: S.of(context).ok,
169 + buttonAction: () => Navigator.of(context).pop(),
170 + ),
171 );
172 }
173
lib/src/screens/new_wallet/widgets/select_button.dart
+42 -29
@@ -1,7 +1,8 @@
1 -import 'package:flutter/material.dart';
1 import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
2 import 'package:cake_wallet/themes/extensions/filter_theme.dart';
3 import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
4 +import 'package:cw_core/hardware/device_connection_type.dart';
5 +import 'package:flutter/material.dart';
6
7 class SelectButton extends StatelessWidget {
8 SelectButton({
@@ -16,6 +17,7 @@ class SelectButton extends StatelessWidget {
17 this.textColor,
18 this.arrowColor,
19 this.borderColor,
20 + this.deviceConnectionTypes,
21 });
22
23 final Image? image;
@@ -24,6 +26,7 @@ class SelectButton extends StatelessWidget {
26 final bool isSelected;
27 final VoidCallback onTap;
28 final bool showTrailingIcon;
29 + final List<DeviceConnectionType>? deviceConnectionTypes;
30 final double height;
31 final Color? color;
32 final Color? textColor;
@@ -33,15 +36,26 @@ class SelectButton extends StatelessWidget {
36 @override
37 Widget build(BuildContext context) {
38 final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor);
36 - final effectiveTextColor = textColor ?? (isSelected
37 - ? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
38 - : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
39 - final effectiveArrowColor = arrowColor ?? (isSelected
40 - ? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
41 - : Theme.of(context).extension<FilterTheme>()!.titlesColor);
39 + final effectiveTextColor = textColor ??
40 + (isSelected
41 + ? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
42 + : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
43 + final effectiveArrowColor = arrowColor ??
44 + (isSelected
45 + ? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
46 + : Theme.of(context).extension<FilterTheme>()!.titlesColor);
47 +
48 + final trailingIcons = <Image>[];
49 + final selectArrowImage =
50 + Image.asset('assets/images/select_arrow.png', color: effectiveArrowColor);
51
43 - final selectArrowImage = Image.asset('assets/images/select_arrow.png',
44 - color: effectiveArrowColor);
52 + deviceConnectionTypes?.forEach((element) => trailingIcons.add(Image.asset(
53 + element.iconString,
54 + color: effectiveArrowColor,
55 + height: 24,
56 + )));
57 +
58 + if (showTrailingIcon) trailingIcons.add(selectArrowImage);
59
60 return GestureDetector(
61 onTap: onTap,
@@ -54,34 +68,33 @@ class SelectButton extends StatelessWidget {
68 borderRadius: BorderRadius.all(Radius.circular(30)),
69 color: backgroundColor,
70 border: borderColor != null ? Border.all(color: borderColor!) : null,
57 -
71 ),
72 child: Row(
73 mainAxisSize: MainAxisSize.max,
74 mainAxisAlignment: MainAxisAlignment.spaceBetween,
75 crossAxisAlignment: CrossAxisAlignment.center,
76 children: <Widget>[
64 - Row(
65 - mainAxisAlignment: MainAxisAlignment.start,
66 - crossAxisAlignment: CrossAxisAlignment.center,
67 - children: <Widget>[
68 - image ?? Offstage(),
69 - Padding(
70 - padding: image != null
71 - ? EdgeInsets.only(left: 15)
72 - : EdgeInsets.only(left: 0),
73 - child: Text(
74 - text,
75 - style: TextStyle(
76 - fontSize: textSize,
77 - fontWeight: FontWeight.w500,
78 - color: effectiveTextColor,
77 + Expanded(
78 + child: Row(
79 + mainAxisAlignment: MainAxisAlignment.start,
80 + crossAxisAlignment: CrossAxisAlignment.center,
81 + children: <Widget>[
82 + image ?? Offstage(),
83 + Padding(
84 + padding: image != null ? EdgeInsets.only(left: 15) : EdgeInsets.only(left: 0),
85 + child: Text(
86 + text,
87 + style: TextStyle(
88 + fontSize: textSize,
89 + fontWeight: FontWeight.w500,
90 + color: effectiveTextColor,
91 + ),
92 ),
80 - ),
81 - )
82 - ],
93 + )
94 + ],
95 + ),
96 ),
84 - if (showTrailingIcon) selectArrowImage
97 + ...trailingIcons
98 ],
99 ),
100 ),
lib/src/screens/restore/restore_options_page.dart
+66 -48
@@ -1,19 +1,19 @@
1 import 'package:cake_wallet/core/execution_state.dart';
2 import 'package:cake_wallet/di.dart';
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/routes.dart';
5 +import 'package:cake_wallet/src/screens/base_page.dart';
6 import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
7 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
8 import 'package:cake_wallet/src/widgets/option_tile.dart';
9 +import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
10 +import 'package:cake_wallet/utils/permission_handler.dart';
11 +import 'package:cake_wallet/utils/responsive_layout_util.dart';
12 import 'package:cake_wallet/utils/show_pop_up.dart';
13 import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
14 import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
9 -import 'package:cake_wallet/utils/responsive_layout_util.dart';
15 import 'package:flutter/material.dart';
11 -import 'package:cake_wallet/routes.dart';
12 -import 'package:flutter/cupertino.dart';
13 -import 'package:cake_wallet/src/screens/base_page.dart';
14 -import 'package:cake_wallet/generated/i18n.dart';
16 import 'package:permission_handler/permission_handler.dart';
16 -import 'package:cake_wallet/utils/permission_handler.dart';
17
18 class RestoreOptionsPage extends BasePage {
19 RestoreOptionsPage({required this.isNewInstall});
@@ -22,12 +22,15 @@ class RestoreOptionsPage extends BasePage {
22 String get title => S.current.restore_restore_wallet;
23
24 final bool isNewInstall;
25 - final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png');
26 - final imageBackup = Image.asset('assets/images/backup.png');
27 - final qrCode = Image.asset('assets/images/restore_qr.png');
25
26 @override
27 Widget body(BuildContext context) {
28 + final imageColor = Theme.of(context).extension<OptionTileTheme>()!.titleColor;
29 + final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
30 + final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
31 + final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
32 + final qrCode = Image.asset('assets/images/restore_qr.png', color: imageColor);
33 +
34 return Center(
35 child: Container(
36 width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
@@ -37,53 +40,37 @@ class RestoreOptionsPage extends BasePage {
40 child: Column(
41 children: <Widget>[
42 OptionTile(
40 - onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromSeedKeys,
41 - arguments: isNewInstall),
42 - image: imageSeedKeys,
43 - title: S.of(context).restore_title_from_seed_keys,
44 - description: S.of(context).restore_description_from_seed_keys),
43 + onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromSeedKeys,
44 + arguments: isNewInstall),
45 + image: imageSeedKeys,
46 + title: S.of(context).restore_title_from_seed_keys,
47 + description: S.of(context).restore_description_from_seed_keys,
48 + ),
49 if (isNewInstall)
50 Padding(
51 padding: EdgeInsets.only(top: 24),
52 child: OptionTile(
49 - onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
50 - image: imageBackup,
51 - title: S.of(context).restore_title_from_backup,
52 - description: S.of(context).restore_description_from_backup),
53 + onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
54 + image: imageBackup,
55 + title: S.of(context).restore_title_from_backup,
56 + description: S.of(context).restore_description_from_backup,
57 + ),
58 ),
59 Padding(
60 padding: EdgeInsets.only(top: 24),
61 child: OptionTile(
57 - onPressed: () async {
58 - bool isCameraPermissionGranted =
59 - await PermissionHandler.checkPermission(Permission.camera, context);
60 - if (!isCameraPermissionGranted) return;
61 - bool isPinSet = false;
62 - if (isNewInstall) {
63 - await Navigator.pushNamed(context, Routes.setupPin,
64 - arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
65 - setupPinContext.close();
66 - isPinSet = true;
67 - });
68 - }
69 - if (!isNewInstall || isPinSet) {
70 - try {
71 - final restoreWallet =
72 - await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
73 -
74 - final restoreFromQRViewModel =
75 - getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
76 -
77 - await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
78 - if (restoreFromQRViewModel.state is FailureState) {
79 - _onWalletCreateFailure(context,
80 - 'Create wallet state: ${(restoreFromQRViewModel.state as FailureState).error}');
81 - }
82 - } catch (e) {
83 - _onWalletCreateFailure(context, e.toString());
84 - }
85 - }
86 - },
62 + onPressed: () => Navigator.pushNamed(
63 + context, Routes.restoreWalletFromHardwareWallet,
64 + arguments: isNewInstall),
65 + image: imageLedger,
66 + title: S.of(context).restore_title_from_hardware_wallet,
67 + description: S.of(context).restore_description_from_hardware_wallet,
68 + ),
69 + ),
70 + Padding(
71 + padding: EdgeInsets.only(top: 24),
72 + child: OptionTile(
73 + onPressed: () => _onScanQRCode(context),
74 image: qrCode,
75 title: S.of(context).scan_qr_code,
76 description: S.of(context).cold_or_recover_wallet),
@@ -105,4 +92,35 @@ class RestoreOptionsPage extends BasePage {
92 buttonAction: () => Navigator.of(context).pop());
93 });
94 }
95 +
96 + Future<void> _onScanQRCode(BuildContext context) async {
97 + final isCameraPermissionGranted =
98 + await PermissionHandler.checkPermission(Permission.camera, context);
99 +
100 + if (!isCameraPermissionGranted) return;
101 + bool isPinSet = false;
102 + if (isNewInstall) {
103 + await Navigator.pushNamed(context, Routes.setupPin,
104 + arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
105 + setupPinContext.close();
106 + isPinSet = true;
107 + });
108 + }
109 + if (!isNewInstall || isPinSet) {
110 + try {
111 + final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
112 +
113 + final restoreFromQRViewModel =
114 + getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
115 +
116 + await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
117 + if (restoreFromQRViewModel.state is FailureState) {
118 + _onWalletCreateFailure(context,
119 + 'Create wallet state: ${(restoreFromQRViewModel.state as FailureState).error}');
120 + }
121 + } catch (e) {
122 + _onWalletCreateFailure(context, e.toString());
123 + }
124 + }
125 + }
126 }
lib/src/screens/send/send_page.dart
+54 -14
@@ -1,39 +1,40 @@
1 import 'package:cake_wallet/core/auth_service.dart';
2 import 'package:cake_wallet/entities/contact_record.dart';
3 +import 'package:cake_wallet/core/execution_state.dart';
4 import 'package:cake_wallet/entities/fiat_currency.dart';
5 import 'package:cake_wallet/entities/template.dart';
6 import 'package:cake_wallet/reactions/wallet_connect.dart';
7 +import 'package:cake_wallet/generated/i18n.dart';
8 +import 'package:cake_wallet/routes.dart';
9 +import 'package:cake_wallet/src/screens/base_page.dart';
10 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
11 import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
12 +import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
13 import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
14 import 'package:cake_wallet/src/widgets/add_template_button.dart';
15 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
16 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
17 import 'package:cake_wallet/src/widgets/picker.dart';
18 +import 'package:cake_wallet/src/widgets/primary_button.dart';
19 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
20 import 'package:cake_wallet/src/widgets/template_tile.dart';
21 +import 'package:cake_wallet/src/widgets/trail_button.dart';
22 import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
23 import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
24 import 'package:cake_wallet/themes/theme_base.dart';
25 import 'package:cake_wallet/utils/payment_request.dart';
26 import 'package:cake_wallet/utils/request_review_handler.dart';
27 import 'package:cake_wallet/utils/responsive_layout_util.dart';
28 +import 'package:cake_wallet/utils/show_pop_up.dart';
29 import 'package:cake_wallet/view_model/send/output.dart';
30 import 'package:cw_core/wallet_type.dart';
31 +import 'package:cake_wallet/view_model/send/send_view_model.dart';
32 +import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
33 +import 'package:cw_core/crypto_currency.dart';
34 import 'package:flutter/material.dart';
35 import 'package:flutter_mobx/flutter_mobx.dart';
36 import 'package:mobx/mobx.dart';
23 -import 'package:cake_wallet/routes.dart';
24 -import 'package:cake_wallet/view_model/send/send_view_model.dart';
25 -import 'package:cake_wallet/core/execution_state.dart';
26 -import 'package:cake_wallet/src/screens/base_page.dart';
27 -import 'package:cake_wallet/src/widgets/primary_button.dart';
28 -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
29 -import 'package:cake_wallet/src/widgets/trail_button.dart';
30 -import 'package:cake_wallet/utils/show_pop_up.dart';
31 -import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
32 -import 'package:cake_wallet/generated/i18n.dart';
33 -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
34 -import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
37 import 'package:smooth_page_indicator/smooth_page_indicator.dart';
36 -import 'package:cw_core/crypto_currency.dart';
38
39 class SendPage extends BasePage {
40 SendPage({
@@ -369,6 +370,21 @@ class SendPage extends BasePage {
370 return;
371 }
372
373 + if (sendViewModel.wallet.isHardwareWallet) {
374 + if (!sendViewModel.ledgerViewModel.isConnected) {
375 + await Navigator.of(context).pushNamed(Routes.connectDevices,
376 + arguments: ConnectDevicePageParams(
377 + walletType: sendViewModel.walletType,
378 + onConnectDevice: (BuildContext context, _) {
379 + sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet);
380 + Navigator.of(context).pop();
381 + },
382 + ));
383 + } else {
384 + sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet);
385 + }
386 + }
387 +
388 final check = sendViewModel.shouldDisplayTotp();
389 authService.authenticateAction(
390 context,
@@ -384,7 +400,8 @@ class SendPage extends BasePage {
400 color: Theme.of(context).primaryColor,
401 textColor: Colors.white,
402 isLoading: sendViewModel.state is IsExecutingState ||
387 - sendViewModel.state is TransactionCommitting,
403 + sendViewModel.state is TransactionCommitting ||
404 + sendViewModel.state is IsAwaitingDeviceResponseState,
405 isDisabled: !sendViewModel.isReadyForSend,
406 );
407 },
@@ -395,12 +412,20 @@ class SendPage extends BasePage {
412 );
413 }
414
415 + BuildContext? dialogContext;
416 +
417 void _setEffects(BuildContext context) {
418 if (_effectsInstalled) {
419 return;
420 }
421
422 reaction((_) => sendViewModel.state, (ExecutionState state) {
423 +
424 + if (dialogContext != null && dialogContext?.mounted == true) {
425 + Navigator.of(dialogContext!).pop();
426 + }
427 +
428 +
429 if (state is FailureState) {
430 WidgetsBinding.instance.addPostFrameCallback((_) {
431 showPopUp<void>(
@@ -510,6 +535,21 @@ class SendPage extends BasePage {
535 sendViewModel.clearOutputs();
536 });
537 }
538 +
539 + if (state is IsAwaitingDeviceResponseState) {
540 + WidgetsBinding.instance.addPostFrameCallback((_) {
541 + showPopUp<void>(
542 + context: context,
543 + builder: (BuildContext context) {
544 + dialogContext = context;
545 + return AlertWithOneAction(
546 + alertTitle: S.of(context).proceed_on_device,
547 + alertContent: S.of(context).proceed_on_device_description,
548 + buttonText: S.of(context).cancel,
549 + buttonAction: () => Navigator.of(context).pop());
550 + });
551 + });
552 + }
553 });
554
555 _effectsInstalled = true;
lib/src/screens/settings/connection_sync_page.dart
+6 -5
@@ -1,10 +1,14 @@
1 import 'dart:io';
2
3 +import 'package:cake_wallet/generated/i18n.dart';
4 import 'package:cake_wallet/reactions/wallet_connect.dart';
5 +import 'package:cake_wallet/routes.dart';
6 +import 'package:cake_wallet/src/screens/base_page.dart';
7 import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
8 import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
9 import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
10 import 'package:cake_wallet/src/screens/settings/widgets/wallet_connect_button.dart';
11 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
12 import 'package:cake_wallet/utils/device_info.dart';
13 import 'package:cake_wallet/utils/feature_flag.dart';
14 import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -12,10 +16,6 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
16 import 'package:cake_wallet/view_model/settings/sync_mode.dart';
17 import 'package:cw_core/battery_optimization_native.dart';
18 import 'package:flutter/material.dart';
15 -import 'package:cake_wallet/routes.dart';
16 -import 'package:cake_wallet/generated/i18n.dart';
17 -import 'package:cake_wallet/src/screens/base_page.dart';
18 -import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
19 import 'package:flutter_mobx/flutter_mobx.dart';
20
21 class ConnectionSyncPage extends BasePage {
@@ -106,7 +106,8 @@ class ConnectionSyncPage extends BasePage {
106 );
107 },
108 ),
109 - if (isWalletConnectCompatibleChain(dashboardViewModel.wallet.type)) ...[
109 + if (isWalletConnectCompatibleChain(dashboardViewModel.wallet.type) &&
110 + !dashboardViewModel.wallet.isHardwareWallet) ...[ // ToDo: Remove this line once WalletConnect is implemented
111 WalletConnectTile(
112 onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
113 ),
lib/view_model/dashboard/dashboard_view_model.dart
+3 -1
@@ -509,7 +509,9 @@ abstract class DashboardViewModelBase with Store {
509 final path = await pathForWallet(name: walletInfo.name, type: walletInfo.type);
510 final jsonSource = await read(path: path, password: password);
511 final data = json.decode(jsonSource) as Map;
512 - final mnemonic = data['mnemonic'] as String;
512 + final mnemonic = data['mnemonic'] as String?;
513 +
514 + if (mnemonic == null) continue;
515
516 final hash = await Cryptography.instance.sha256().hash(utf8.encode(mnemonic));
517 final seedSha = bytesToHex(hash.bytes);
lib/view_model/hardware_wallet/ledger_view_model.dart new
+66
@@ -0,0 +1,66 @@
1 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/ethereum/ethereum.dart';
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/polygon/polygon.dart';
5 +import 'package:cw_core/wallet_base.dart';
6 +import 'package:cw_core/wallet_type.dart';
7 +import 'package:ledger_flutter/ledger_flutter.dart';
8 +import 'package:permission_handler/permission_handler.dart';
9 +
10 +class LedgerViewModel {
11 + final Ledger ledger = Ledger(
12 + options: LedgerOptions(
13 + scanMode: ScanMode.balanced,
14 + maxScanDuration: const Duration(minutes: 5),
15 + ),
16 + onPermissionRequest: (_) async {
17 + Map<Permission, PermissionStatus> statuses = await [
18 + Permission.bluetoothScan,
19 + Permission.bluetoothConnect,
20 + Permission.bluetoothAdvertise,
21 + ].request();
22 +
23 + return statuses.values.where((status) => status.isDenied).isEmpty;
24 + },
25 + );
26 +
27 + Future<void> connectLedger(LedgerDevice device) async {
28 + await ledger.connect(device);
29 +
30 + if (device.connectionType == ConnectionType.usb) _device = device;
31 + }
32 +
33 + LedgerDevice? _device;
34 +
35 + bool get isConnected => ledger.devices.isNotEmpty || _device != null;
36 +
37 + LedgerDevice get device => _device ?? ledger.devices.first;
38 +
39 + void setLedger(WalletBase wallet) {
40 + switch (wallet.type) {
41 + case WalletType.bitcoin:
42 + return bitcoin!.setLedger(wallet, ledger, device);
43 + case WalletType.ethereum:
44 + return ethereum!.setLedger(wallet, ledger, device);
45 + case WalletType.polygon:
46 + return polygon!.setLedger(wallet, ledger, device);
47 + default:
48 + throw Exception('Unexpected wallet type: ${wallet.type}');
49 + }
50 + }
51 +
52 + String? interpretErrorCode(String errorCode) {
53 + switch (errorCode) {
54 + case "6985":
55 + return S.current.ledger_error_tx_rejected_by_user;
56 + case "5515":
57 + return S.current.ledger_error_device_locked;
58 + case "6d02": // UNKNOWN_APDU
59 + case "6511":
60 + case "6e00":
61 + return S.current.ledger_error_wrong_app;
62 + default:
63 + return null;
64 + }
65 + }
66 +}
lib/view_model/send/send_view_model.dart
+19 -6
@@ -15,11 +15,13 @@ import 'package:cake_wallet/store/app_store.dart';
15 import 'package:cake_wallet/tron/tron.dart';
16 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
17 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
18 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
19 import 'package:cw_core/exceptions.dart';
20 import 'package:cw_core/transaction_priority.dart';
21 import 'package:cake_wallet/view_model/send/output.dart';
22 import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
23 import 'package:hive/hive.dart';
24 +import 'package:ledger_flutter/ledger_flutter.dart';
25 import 'package:mobx/mobx.dart';
26 import 'package:cake_wallet/entities/template.dart';
27 import 'package:cake_wallet/core/address_validator.dart';
@@ -63,6 +65,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
65 this.balanceViewModel,
66 this.contactListViewModel,
67 this.transactionDescriptionBox,
68 + this.ledgerViewModel,
69 ) : state = InitialExecutionState(),
70 currencies = appStore.wallet!.balance.keys.toList(),
71 selectedCryptoCurrency = appStore.wallet!.currency,
@@ -266,6 +269,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
269 final SendTemplateViewModel sendTemplateViewModel;
270 final BalanceViewModel balanceViewModel;
271 final ContactListViewModel contactListViewModel;
272 + final LedgerViewModel ledgerViewModel;
273 final FiatConversionStore _fiatConversationStore;
274 final Box<TransactionDescription> transactionDescriptionBox;
275
@@ -340,7 +344,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
344 try {
345 state = IsExecutingState();
346
347 + if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState();
348 +
349 pendingTransaction = await wallet.createTransaction(_credentials());
350 +
351 if (provider is ThorChainExchangeProvider) {
352 final outputCount = pendingTransaction?.outputCount ?? 0;
353 if (outputCount > 10) {
@@ -354,7 +361,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
361 state = ExecutedSuccessfullyState();
362 return pendingTransaction;
363 } catch (e) {
357 - state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
364 + if (e is LedgerException) {
365 + final errorCode = e.errorCode.toRadixString(16);
366 + final fallbackMsg = e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode";
367 + final errorMsg = ledgerViewModel.interpretErrorCode(errorCode) ?? fallbackMsg;
368 +
369 + state = FailureState(errorMsg);
370 + } else {
371 + state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
372 + }
373 }
374 return null;
375 }
@@ -390,15 +405,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
405
406 String address = outputs.fold('', (acc, value) {
407 return value.isParsedAddress
393 - ? acc + value.address + '\n' + value.extractedAddress + '\n\n'
394 - : acc + value.address + '\n\n';
408 + ? '$acc${value.address}\n${value.extractedAddress}\n\n'
409 + : '$acc${value.address}\n\n';
410 });
411
412 address = address.trim();
413
399 - String note = outputs.fold('', (acc, value) {
400 - return acc + value.note + '\n';
401 - });
414 + String note = outputs.fold('', (acc, value) => '$acc${value.note}\n');
415
416 note = note.trim();
417
lib/view_model/send/send_view_model_state.dart
+1
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/core/execution_state.dart';
2
3 +class IsAwaitingDeviceResponseState extends IsExecutingState {}
4 class TransactionCommitting extends ExecutionState {}
5 class TransactionCommitted extends ExecutionState {}
lib/view_model/wallet_creation_vm.dart
+2 -1
@@ -72,6 +72,7 @@ abstract class WalletCreationVMBase with Store {
72 address: '',
73 showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
74 derivationInfo: credentials.derivationInfo ?? getDefaultDerivation(),
75 + hardwareWalletType: credentials.hardwareWalletType,
76 );
77
78 credentials.walletInfo = walletInfo;
@@ -99,7 +100,7 @@ abstract class WalletCreationVMBase with Store {
100 case WalletType.litecoin:
101 return DerivationInfo(
102 derivationType: DerivationType.electrum,
102 - derivationPath: "m/0'/0",
103 + derivationPath: "m/0'",
104 );
105 default:
106 return null;
lib/view_model/wallet_hardware_restore_view_model.dart new
+110
@@ -0,0 +1,110 @@
1 +import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/core/wallet_creation_service.dart';
3 +import 'package:cake_wallet/ethereum/ethereum.dart';
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/polygon/polygon.dart';
6 +import 'package:cake_wallet/store/app_store.dart';
7 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
8 +import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
9 +import 'package:cw_core/hardware/hardware_account_data.dart';
10 +import 'package:cw_core/wallet_base.dart';
11 +import 'package:cw_core/wallet_credentials.dart';
12 +import 'package:cw_core/wallet_info.dart';
13 +import 'package:cw_core/wallet_type.dart';
14 +import 'package:hive/hive.dart';
15 +import 'package:ledger_flutter/ledger_flutter.dart';
16 +import 'package:mobx/mobx.dart';
17 +
18 +part 'wallet_hardware_restore_view_model.g.dart';
19 +
20 +class WalletHardwareRestoreViewModel = WalletHardwareRestoreViewModelBase
21 + with _$WalletHardwareRestoreViewModel;
22 +
23 +abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with Store {
24 + final LedgerViewModel ledgerViewModel;
25 +
26 + int _nextIndex = 0;
27 +
28 + WalletHardwareRestoreViewModelBase(this.ledgerViewModel, AppStore appStore,
29 + WalletCreationService walletCreationService, Box<WalletInfo> walletInfoSource,
30 + {required WalletType type})
31 + : super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true);
32 +
33 + @observable
34 + String name = "";
35 +
36 + @observable
37 + HardwareAccountData? selectedAccount = null;
38 +
39 + @observable
40 + bool isLoadingMoreAccounts = false;
41 +
42 + @observable
43 + String? error = null;
44 +
45 + // @observable
46 + ObservableList<HardwareAccountData> availableAccounts = ObservableList();
47 +
48 + @action
49 + Future<void> getNextAvailableAccounts(int limit) async {
50 + try {
51 + List<HardwareAccountData> accounts;
52 + switch (type) {
53 + case WalletType.bitcoin:
54 + accounts = await bitcoin!
55 + .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
56 + break;
57 + case WalletType.ethereum:
58 + accounts = await ethereum!
59 + .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
60 + break;
61 + case WalletType.polygon:
62 + accounts = await polygon!
63 + .getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
64 + break;
65 + default:
66 + return;
67 + }
68 +
69 + availableAccounts.addAll(accounts);
70 + _nextIndex += limit;
71 + } on LedgerException catch (e) {
72 + error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16));
73 + } catch (e) {
74 + error = S.current.ledger_connection_error;
75 + }
76 +
77 + isLoadingMoreAccounts = false;
78 + _nextIndex += limit;
79 + }
80 +
81 + @override
82 + WalletCredentials getCredentials(dynamic _options) {
83 + WalletCredentials credentials;
84 + switch (type) {
85 + case WalletType.bitcoin:
86 + credentials =
87 + bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!);
88 + break;
89 + case WalletType.ethereum:
90 + credentials =
91 + ethereum!.createEthereumHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
92 + break;
93 + case WalletType.polygon:
94 + credentials = polygon!.createPolygonHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
95 + break;
96 + default:
97 + throw Exception('Unexpected type: ${type.toString()}');
98 + }
99 +
100 + credentials.hardwareWalletType = HardwareWalletType.ledger;
101 +
102 + return credentials;
103 + }
104 +
105 + @override
106 + Future<WalletBase> process(WalletCredentials credentials) async {
107 + walletCreationService.changeWalletType(type: type);
108 + return walletCreationService.restoreFromHardwareWallet(credentials);
109 + }
110 +}
macos/Runner/InfoBase.plist
+17
@@ -32,5 +32,22 @@
32 <string>NSApplication</string>
33 <key>LSApplicationCategoryType</key>
34 <string>public.app-category.finance</string>
35 + <key>NSCameraUsageDescription</key>
36 + <string>Used for scanning QR code and can be used to capture images for identification purposes by third-party providers.</string>
37 + <key>NSDocumentsFolderUsageDescription</key>
38 + <string>We need access to documents folder for getting access to open/save backup file</string>
39 + <key>NSFaceIDUsageDescription</key>
40 + <string>Enable Face ID for fast and secure access to wallets and private keys</string>
41 + <key>NSPhotoLibraryUsageDescription</key>
42 + <string>We need access to documents folder for getting access to open/save backup file</string>
43 + <key>NSBluetoothPeripheralUsageDescription</key>
44 + <string>We need access to Bluetooth in order to connect to your hardware wallet when needed</string>
45 + <key>NSBluetoothAlwaysUsageDescription</key>
46 + <string>We need access to Bluetooth in order to connect to your hardware wallet when needed</string>
47 + <key>UIBackgroundModes</key>
48 + <array>
49 + <string>fetch</string>
50 + <string>processing</string>
51 + </array>
52 </dict>
53 </plist>
pubspec_base.yaml
+9
@@ -111,6 +111,7 @@ dependencies:
111 git:
112 url: https://github.com/cake-tech/bitcoin_base.git
113 ref: cake-update-v2
114 + ledger_flutter: ^1.0.1
115
116 dev_dependencies:
117 flutter_test:
@@ -133,6 +134,14 @@ dependency_overrides:
134 bech32:
135 git:
136 url: https://github.com/cake-tech/bech32.git
137 + ledger_flutter:
138 + git:
139 + url: https://github.com/cake-tech/ledger-flutter.git
140 + ref: cake
141 + web3dart:
142 + git:
143 + url: https://github.com/cake-tech/web3dart.git
144 + ref: cake
145
146 flutter_icons:
147 image_path: "assets/images/app_logo.png"
res/values/strings_ar.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "موضوع البيتكوين الخفيفة",
80 "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.",
81 "Blocks_remaining": "بلوك متبقي ${status}",
82 + "bluetooth": "بلوتوث",
83 "bright_theme": "مشرق",
84 "bump_fee": "رسوم عثرة",
85 "buy": "اشتري",
@@ -144,6 +145,8 @@
145 "congratulations": "تهانينا!",
146 "connect_an_existing_yat": "توصيل Yat الحالي",
147 "connect_yats": "توصيل Yats",
148 + "connect_your_hardware_wallet": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth أو USB",
149 + "connect_your_hardware_wallet_ios": "قم بتوصيل محفظة الأجهزة الخاصة بك باستخدام Bluetooth",
150 "connection_sync": "الاتصال والمزامنة",
151 "connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ",
152 "contact": "تواصل",
@@ -328,7 +331,13 @@
331 "is_percentage": "يكون",
332 "last_30_days": "آخر 30 يومًا",
333 "learn_more": "اعرف المزيد",
334 + "ledger_connection_error": "فشل في الاتصال بك دفتر الأستاذ. حاول مرة اخرى.",
335 + "ledger_error_device_locked": "تم قفل دفتر الأستاذ",
336 + "ledger_error_tx_rejected_by_user": "تم رفض المعاملة على الجهاز",
337 + "ledger_error_wrong_app": "يرجى التأكد",
338 + "ledger_please_enable_bluetooth": "يرجى تمكين البلوتوث للكشف عن دفتر الأستاذ الخاص بك",
339 "light_theme": "فاتح",
340 + "load_more": "تحميل المزيد",
341 "loading_your_wallet": "يتم تحميل محفظتك",
342 "login": "تسجيل الدخول",
343 "logout": "تسجيل خروج",
@@ -442,6 +451,8 @@
451 "privacy_settings": "إعدادات الخصوصية",
452 "private_key": "مفتاح خاص",
453 "proceed_after_one_minute": "إذا لم تستمر الشاشة بعد دقيقة واحدة ، فتحقق من بريدك الإلكتروني.",
454 + "proceed_on_device": "تابع جهازك",
455 + "proceed_on_device_description": "يرجى اتباع الإرشادات المطلوبة على محفظة الأجهزة الخاصة بك",
456 "profile": "حساب تعريفي",
457 "provider_error": "خطأ ${provider}",
458 "public_key": "مفتاح عمومي",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "قم باستعادة محفظتك من كود مكون من 24 كلمة",
502 "restore_bitcoin_title_from_keys": "استعادة من WIF",
503 "restore_description_from_backup": "يمكنك استعادة تطبيق Cake Wallet بالكامل من ملف النسخ الاحتياطي",
504 + "restore_description_from_hardware_wallet": "استعادة من محفظة أجهزة دفتر الأستاذ",
505 "restore_description_from_keys": "قم باستعادة محفظتك من ضغطات المفاتيح المولدة المحفوظة من مفاتيحك الخاصة",
506 "restore_description_from_seed": "قم باستعادة محفظتك من الرمز المكون من 25 كلمة أو 13 كلمة",
507 "restore_description_from_seed_keys": "استرجع محفظتك من السييد / المفاتيح التي قمت بحفظها في مكان آمن",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "استعادة السييد / المفاتيح",
515 "restore_spend_key_private": "مفتاح الإنفاق (خاص)",
516 "restore_title_from_backup": "استعادة من النسخة الاحتياطية",
517 + "restore_title_from_hardware_wallet": "استعادة من محفظة الأجهزة",
518 "restore_title_from_keys": "استعادة من المفاتيح",
519 "restore_title_from_seed": "استعادة من السييد",
520 "restore_title_from_seed_keys": "استعادة من السييد / المفاتيح",
@@ -752,6 +765,7 @@
765 "unsupported_asset": ".ﻡﻮﻋﺪﻣ ﻞﺻﺃ ﻉﻮﻧ ﻦﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .ﻞﺻﻷﺍ ﺍﺬﻬﻟ ءﺍﺮﺟﻹﺍ ﺍﺬﻫ ﻢﻋﺪﻧ ﻻ ﻦﺤﻧ",
766 "uptime": "مدة التشغيل",
767 "upto": "حتى ${value}",
768 + "usb": "USB",
769 "use": "التبديل إلى",
770 "use_card_info_three": "استخدم البطاقة الرقمية عبر الإنترنت أو مع طرق الدفع غير التلامسية.",
771 "use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.",
res/values/strings_bg.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Лека биткойн тема",
80 "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.",
81 "Blocks_remaining": "${status} оставащи блока",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Ярко",
84 "bump_fee": "Такса за бум",
85 "buy": "Купуване",
@@ -144,6 +145,8 @@
145 "congratulations": "Поздравления!",
146 "connect_an_existing_yat": "Добавете съществуващ Yat",
147 "connect_yats": "Добавете Yats",
148 + "connect_your_hardware_wallet": "Свържете хардуерния си портфейл с помощта на Bluetooth или USB",
149 + "connect_your_hardware_wallet_ios": "Свържете хардуерния си портфейл с помощта на Bluetooth",
150 "connection_sync": "Свързване и синхронизиране",
151 "connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции",
152 "contact": "Контакт",
@@ -328,7 +331,13 @@
331 "is_percentage": "е",
332 "last_30_days": "Последните 30 дни",
333 "learn_more": "Научете още",
334 + "ledger_connection_error": "Не успя да се свърже с вашата книга. Моля, опитайте отново.",
335 + "ledger_error_device_locked": "Главната книга е заключена",
336 + "ledger_error_tx_rejected_by_user": "Транзакция, отхвърлена на устройство",
337 + "ledger_error_wrong_app": "Моля, уверете се, че сте отворили правилното приложение на вашата книга",
338 + "ledger_please_enable_bluetooth": "Моля, активирайте Bluetooth да открие вашата книга",
339 "light_theme": "Светло",
340 + "load_more": "Зареди още",
341 "loading_your_wallet": "Зареждане на портфейл",
342 "login": "Влизане",
343 "logout": "Logout",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Настройки за поверителност",
452 "private_key": "Таен ключ",
453 "proceed_after_one_minute": "Ако процесът продължи повече от 1 минута, проверете своя имейл.",
454 + "proceed_on_device": "Продължете на вашето устройство",
455 + "proceed_on_device_description": "Моля, следвайте инструкциите, подканени на вашия хардуер",
456 "profile": "Профил",
457 "provider_error": "Грешка на ${provider} ",
458 "public_key": "Публичен ключ",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Възстановяване на портфейл чрез код от 24 думи",
502 "restore_bitcoin_title_from_keys": "Възстановяване от WIF",
503 "restore_description_from_backup": "Можете да възстановите цялото приложение Cake Wallet от своя резервен файл",
504 + "restore_description_from_hardware_wallet": "Възстановяване от хардуерния портфейл на главната книга",
505 "restore_description_from_keys": "Възстановяване на портфейл от генерираните от Вашите тайни ключове клавиши",
506 "restore_description_from_seed": "Възстановяване на портфейл от кода от 13 или 25 думи",
507 "restore_description_from_seed_keys": "Възстановете своя портфейл от seed/keys, които сте съхранили на сигурно място",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Възстановяне от Seed/Keys",
515 "restore_spend_key_private": "Spend key (публичен)",
516 "restore_title_from_backup": "Възстановяване от резервно копие",
517 + "restore_title_from_hardware_wallet": "Възстановяване от хардуерния портфейл",
518 "restore_title_from_keys": "Възстановяване от keys",
519 "restore_title_from_seed": "Възстановяване от seed",
520 "restore_title_from_seed_keys": "Възстановяване от seed/keys",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или преминете към портфейл от поддържан тип актив.",
766 "uptime": "Време за работа",
767 "upto": "до ${value}",
768 + "usb": "USB",
769 "use": "Смяна на ",
770 "use_card_info_three": "Използвайте дигиталната карта онлайн или чрез безконтактен метод на плащане.",
771 "use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
res/values/strings_cs.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Světlé téma bitcoinů",
80 "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.",
81 "Blocks_remaining": "Zbývá ${status} bloků",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Jasný",
84 "bump_fee": "Bump Fee",
85 "buy": "Koupit",
@@ -144,6 +145,8 @@
145 "congratulations": "Gratulujeme!",
146 "connect_an_existing_yat": "Připojit existující Yat",
147 "connect_yats": "Připojit Yaty",
148 + "connect_your_hardware_wallet": "Připojte hardwarovou peněženku pomocí Bluetooth nebo USB",
149 + "connect_your_hardware_wallet_ios": "Připojte hardwarovou peněženku pomocí Bluetooth",
150 "connection_sync": "Připojení a synch.",
151 "connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce",
152 "contact": "Kontakt",
@@ -328,7 +331,13 @@
331 "is_percentage": "je",
332 "last_30_days": "Posledních 30 dnů",
333 "learn_more": "Zjistit více",
334 + "ledger_connection_error": "Nepodařilo se připojit k vaší knize. Prosím zkuste to znovu.",
335 + "ledger_error_device_locked": "Kniha je uzamčena",
336 + "ledger_error_tx_rejected_by_user": "Transakce zamítnuta na zařízení",
337 + "ledger_error_wrong_app": "Ujistěte se, že se na své knize otevřete správnou aplikaci",
338 + "ledger_please_enable_bluetooth": "Umožněte prosím Bluetooth detekovat vaši knihu",
339 "light_theme": "Světlý",
340 + "load_more": "Načíst další",
341 "loading_your_wallet": "Načítám peněženku",
342 "login": "Login",
343 "logout": "Odhlásit",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Nastavení soukromí",
452 "private_key": "Soukromý klíč",
453 "proceed_after_one_minute": "Pokud proces nepokročí během 1 minuty, zkontrolujte svůj e-mail.",
454 + "proceed_on_device": "Pokračujte ve svém zařízení",
455 + "proceed_on_device_description": "Postupujte podle pokynů na výzvu na vaší hardwarové peněžence",
456 "profile": "Profil",
457 "provider_error": "${provider} chyba",
458 "public_key": "Veřejný klíč",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Obnovte svou peněženku pomocí kombinace 24 slov",
502 "restore_bitcoin_title_from_keys": "Obnovit z WIF",
503 "restore_description_from_backup": "Můžete obnovit celou Cake Wallet aplikaci ze souboru se zálohou",
504 + "restore_description_from_hardware_wallet": "Obnovit z peněženky hardwaru knihy",
505 "restore_description_from_keys": "Obnovte svou peněženku pomocí generovaných stisků kláves uložených z vašich soukromých klíčů",
506 "restore_description_from_seed": "Obnovte svou peněženku pomocí kombinace 25, nebo 13 slov",
507 "restore_description_from_seed_keys": "Obnovte svou peněženku ze seedu/klíčů, které jste si uložili na bezpečném místě",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Obnovit ze seedu/klíčů",
515 "restore_spend_key_private": "Klíč pro platby (soukromý)",
516 "restore_title_from_backup": "Obnovit ze zálohy",
517 + "restore_title_from_hardware_wallet": "Obnovit z hardwarové peněženky",
518 "restore_title_from_keys": "Obnovit z klíčů",
519 "restore_title_from_seed": "Obnovit ze seedu",
520 "restore_title_from_seed_keys": "Obnovit ze seedu/klíčů",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Tuto akci u tohoto díla nepodporujeme. Vytvořte nebo přepněte na peněženku podporovaného typu aktiv.",
766 "uptime": "Uptime",
767 "upto": "až ${value}",
768 + "usb": "USB",
769 "use": "Přepnout na ",
770 "use_card_info_three": "Použijte tuto digitální kartu online nebo bezkontaktními platebními metodami.",
771 "use_card_info_two": "Prostředky jsou převedeny na USD, když jsou drženy na předplaceném účtu, nikoliv na digitální měnu.",
res/values/strings_de.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Light-Thema",
80 "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.",
81 "Blocks_remaining": "${status} verbleibende Blöcke",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Strahlend hell",
84 "bump_fee": "Beulengebühr",
85 "buy": "Kaufen",
@@ -144,6 +145,8 @@
145 "congratulations": "Glückwunsch!",
146 "connect_an_existing_yat": "Verbinden Sie ein vorhandenes Yat",
147 "connect_yats": "Yats verbinden",
148 + "connect_your_hardware_wallet": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth oder USB",
149 + "connect_your_hardware_wallet_ios": "Verbinden Sie Ihre Hardware-Wallet über Bluetooth",
150 "connection_sync": "Verbindung und Synchronisierung",
151 "connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen",
152 "contact": "Kontakt",
@@ -328,7 +331,13 @@
331 "is_percentage": "ist",
332 "last_30_days": "Letzte 30 Tage",
333 "learn_more": "Erfahren Sie mehr",
334 + "ledger_connection_error": "Verbindung zum Ledger gescheitert. Bitte versuche es erneut.",
335 + "ledger_error_device_locked": "Der Ledger ist gesperrt",
336 + "ledger_error_tx_rejected_by_user": "Transaktion auf dem Gerät abgelehnt",
337 + "ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben",
338 + "ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.",
339 "light_theme": "Hell",
340 + "load_more": "Mehr laden",
341 "loading_your_wallet": "Wallet wird geladen",
342 "login": "Einloggen",
343 "logout": "Abmelden",
@@ -443,6 +452,8 @@
452 "privacy_settings": "Datenschutzeinstellungen",
453 "private_key": "Privater Schlüssel",
454 "proceed_after_one_minute": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie bitte Ihre E-Mail.",
455 + "proceed_on_device": "Fahren Sie auf Ihrem Gerät fort",
456 + "proceed_on_device_description": "Bitte befolgen Sie die Anweisungen, die auf Ihrer Hardware-Wallet angezeigt werden",
457 "profile": "Profil",
458 "provider_error": "${provider}-Fehler",
459 "public_key": "Öffentlicher Schlüssel",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "Stellen Sie Ihre Wallet aus dem 24-Wort-Kombinationscode wieder her",
503 "restore_bitcoin_title_from_keys": "Aus WIF wiederherstellen",
504 "restore_description_from_backup": "Sie können die gesamte Cake Wallet-App aus Ihrer Sicherungsdatei wiederherstellen",
505 + "restore_description_from_hardware_wallet": "Stellen Sie eine Wallet von Ledger wieder her",
506 "restore_description_from_keys": "Stellen Sie Ihr Wallet aus generierten Tastenanschlägen her, die von Ihren privaten Schlüsseln gespeichert wurden",
507 "restore_description_from_seed": "Stellen Sie Ihre Wallet aus den 25 Wörtern oder dem 13-Wort-Kombinationscode wieder her",
508 "restore_description_from_seed_keys": "Stellen Sie Ihr Wallet aus Seed/Schlüsseln wieder her, die Sie sicher aufbewahrt haben",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "Seed/Schlüssel wiederherstellen",
516 "restore_spend_key_private": "Spend Key (geheim)",
517 "restore_title_from_backup": "Aus einer Sicherungsdatei wiederherstellen",
518 + "restore_title_from_hardware_wallet": "Von Hardware-Wallet wiederherstellen",
519 "restore_title_from_keys": "Aus Schlüsseln wiederherstellen",
520 "restore_title_from_seed": "Aus Seed wiederherstellen",
521 "restore_title_from_seed_keys": "Aus Seed/Schlüssel wiederherstellen",
@@ -754,6 +767,7 @@
767 "unsupported_asset": "Wir unterstützen diese Aktion für dieses Asset nicht. Bitte erstellen Sie eine Wallet eines unterstützten Asset-Typs oder wechseln Sie zu einer Wallet.",
768 "uptime": "Betriebszeit",
769 "upto": "bis zu ${value}",
770 + "usb": "USB",
771 "use": "Wechsel zu ",
772 "use_card_info_three": "Verwenden Sie die digitale Karte online oder mit kontaktlosen Zahlungsmethoden.",
773 "use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
res/values/strings_en.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Light Theme",
80 "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.",
81 "Blocks_remaining": "${status} Blocks Remaining",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Bright",
84 "bump_fee": "Bump fee",
85 "buy": "Buy",
@@ -144,6 +145,8 @@
145 "congratulations": "Congratulations!",
146 "connect_an_existing_yat": "Connect an existing Yat",
147 "connect_yats": "Connect Yats",
148 + "connect_your_hardware_wallet": "Connect your hardware wallet using Bluetooth or USB",
149 + "connect_your_hardware_wallet_ios": "Connect your hardware wallet using Bluetooth",
150 "connection_sync": "Connection and sync",
151 "connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions",
152 "contact": "Contact",
@@ -328,7 +331,13 @@
331 "is_percentage": "is",
332 "last_30_days": "Last 30 days",
333 "learn_more": "Learn More",
334 + "ledger_connection_error": "Failed to connect to you Ledger. Please try again.",
335 + "ledger_error_device_locked": "The Ledger is locked",
336 + "ledger_error_tx_rejected_by_user": "Transaction rejected on device",
337 + "ledger_error_wrong_app": "Please make sure you opend the right app on your ledger",
338 + "ledger_please_enable_bluetooth": "Please enable Bluetooth to detect your Ledger",
339 "light_theme": "Light",
340 + "load_more": "Load more",
341 "loading_your_wallet": "Loading your wallet",
342 "login": "Login",
343 "logout": "Logout",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Privacy settings",
452 "private_key": "Private key",
453 "proceed_after_one_minute": "If the screen doesn’t proceed after 1 minute, check your email.",
454 + "proceed_on_device": "Proceed on your device",
455 + "proceed_on_device_description": "Please follow the instructions prompted on your hardware wallet",
456 "profile": "Profile",
457 "provider_error": "${provider} error",
458 "public_key": "Public key",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Restore your wallet from 24 word combination code",
502 "restore_bitcoin_title_from_keys": "Restore from WIF",
503 "restore_description_from_backup": "You can restore the whole Cake Wallet app from your back-up file",
504 + "restore_description_from_hardware_wallet": "Restore from a Ledger hardware wallet",
505 "restore_description_from_keys": "Restore your wallet from generated keystrokes saved from your private keys",
506 "restore_description_from_seed": "Restore your wallet from either the 25 word or 13 word combination code",
507 "restore_description_from_seed_keys": "Get back your wallet from seed/keys that you've saved to secure place",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Seed/Keys Restore",
515 "restore_spend_key_private": "Spend key (private)",
516 "restore_title_from_backup": "Restore from backup",
517 + "restore_title_from_hardware_wallet": "Restore from hardware wallet",
518 "restore_title_from_keys": "Restore from keys",
519 "restore_title_from_seed": "Restore from seed",
520 "restore_title_from_seed_keys": "Restore from seed/keys",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "We don't support this action for this asset. Please create or switch to a wallet of a supported asset type.",
766 "uptime": "Uptime",
767 "upto": "up to ${value}",
768 + "usb": "USB",
769 "use": "Switch to ",
770 "use_card_info_three": "Use the digital card online or with contactless payment methods.",
771 "use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.",
res/values/strings_es.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Tema de la luz de Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.",
81 "Blocks_remaining": "${status} Bloques restantes",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Brillante",
84 "bump_fee": "Tarifa",
85 "buy": "Comprar",
@@ -144,6 +145,8 @@
145 "congratulations": "Felicidades!",
146 "connect_an_existing_yat": "Conectar un Yat existente",
147 "connect_yats": "Conectar Yats",
148 + "connect_your_hardware_wallet": "Conecte su billetera de hardware con Bluetooth o USB",
149 + "connect_your_hardware_wallet_ios": "Conecte su billetera de hardware con Bluetooth",
150 "connection_sync": "Conexión y sincronización",
151 "connectWalletPrompt": "Conecte su billetera con WalletConnect para realizar transacciones",
152 "contact": "Contacto",
@@ -328,7 +331,13 @@
331 "is_percentage": "es",
332 "last_30_days": "Últimos 30 días",
333 "learn_more": "Aprende más",
334 + "ledger_connection_error": "No se pudo conectar con su libro mayor. Inténtalo de nuevo.",
335 + "ledger_error_device_locked": "El libro mayor está bloqueado",
336 + "ledger_error_tx_rejected_by_user": "Transacción rechazada en el dispositivo",
337 + "ledger_error_wrong_app": "Por favor, asegúrese de abrir la aplicación correcta en su libro mayor.",
338 + "ledger_please_enable_bluetooth": "Habilite Bluetooth para detectar su libro mayor",
339 "light_theme": "Ligera",
340 + "load_more": "Carga más",
341 "loading_your_wallet": "Cargando tu billetera",
342 "login": "Iniciar sesión",
343 "logout": "Cerrar sesión",
@@ -443,6 +452,8 @@
452 "privacy_settings": "Configuración de privacidad",
453 "private_key": "Clave privada",
454 "proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.",
455 + "proceed_on_device": "Continúe con su dispositivo",
456 + "proceed_on_device_description": "Siga las instrucciones solicitadas en su billetera de hardware",
457 "profile": "Perfil",
458 "provider_error": "${provider} error",
459 "public_key": "Clave pública",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "Restaure su billetera a partir del código de combinación de 24 palabras",
503 "restore_bitcoin_title_from_keys": "Restaurar desde WIF",
504 "restore_description_from_backup": "Puede restaurar toda la aplicación Cake Wallet desde ysu archivo de respaldo",
505 + "restore_description_from_hardware_wallet": "Restaurar desde una billetera de hardware Ledger",
506 "restore_description_from_keys": "Restaure su billetera de las pulsaciones de teclas generadas guardadas de sus claves privadas",
507 "restore_description_from_seed": "Restaure su billetera desde el código de combinación de 25 palabras i de 13 palabras",
508 "restore_description_from_seed_keys": "Recupere su billetera de las semillas/claves que ha guardado en un lugar seguro",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "Restauración de semillas / llaves",
516 "restore_spend_key_private": "Spend clave (privado)",
517 "restore_title_from_backup": "Restaurar desde un archivo de respaldo",
518 + "restore_title_from_hardware_wallet": "Restaurar desde la billetera de hardware",
519 "restore_title_from_keys": "De las claves",
520 "restore_title_from_seed": "De la semilla",
521 "restore_title_from_seed_keys": "Restaurar desde semilla/claves",
@@ -753,6 +766,7 @@
766 "unsupported_asset": "No admitimos esta acción para este activo. Cree o cambie a una billetera de un tipo de activo admitido.",
767 "uptime": "Tiempo de actividad",
768 "upto": "hasta ${value}",
769 + "usb": "USB",
770 "use": "Utilizar a ",
771 "use_card_info_three": "Utilice la tarjeta digital en línea o con métodos de pago sin contacto.",
772 "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
res/values/strings_fr.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Thème léger Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.",
81 "Blocks_remaining": "Blocs Restants : ${status}",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Vif",
84 "bump_fee": "Frais de bosse",
85 "buy": "Acheter",
@@ -144,6 +145,8 @@
145 "congratulations": "Félicitations !",
146 "connect_an_existing_yat": "Connecter un Yat existant",
147 "connect_yats": "Connecter Yats",
148 + "connect_your_hardware_wallet": "Connectez votre portefeuille matériel à l'aide de Bluetooth ou USB",
149 + "connect_your_hardware_wallet_ios": "Connectez votre portefeuille matériel à l'aide de Bluetooth",
150 "connection_sync": "Connexion et synchronisation",
151 "connectWalletPrompt": "Connectez votre portefeuille (wallet) avec WalletConnect pour effectuer des transactions",
152 "contact": "Contact",
@@ -328,7 +331,13 @@
331 "is_percentage": "est",
332 "last_30_days": "30 derniers jours",
333 "learn_more": "En savoir plus",
334 + "ledger_connection_error": "Impossible de se connecter à votre grand livre. Veuillez réessayer.",
335 + "ledger_error_device_locked": "Le grand livre est verrouillé",
336 + "ledger_error_tx_rejected_by_user": "Transaction rejetée sur l'appareil",
337 + "ledger_error_wrong_app": "Veuillez vous assurer d'ouvrir la bonne application sur votre grand livre",
338 + "ledger_please_enable_bluetooth": "Veuillez activer Bluetooth pour détecter votre grand livre",
339 "light_theme": "Clair",
340 + "load_more": "Charger plus",
341 "loading_your_wallet": "Chargement de votre portefeuille (wallet)",
342 "login": "Utilisateur",
343 "logout": "Déconnexion",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Paramètres de confidentialité",
452 "private_key": "Clef privée",
453 "proceed_after_one_minute": "Si l'écran ne s'affiche pas après 1 minute, vérifiez vos e-mails.",
454 + "proceed_on_device": "Continuez sur votre appareil",
455 + "proceed_on_device_description": "Veuillez suivre les instructions invitées sur votre portefeuille matériel",
456 "profile": "Profil",
457 "provider_error": "Erreur de ${provider}",
458 "public_key": "Clef publique",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Restaurer votre portefeuille (wallet) à l'aide d'une phrase secrète (seed) de 24 mots",
502 "restore_bitcoin_title_from_keys": "Restaurer depuis la chaîne WIF",
503 "restore_description_from_backup": "Vous pouvez restaurer l'intégralité de l'application Cake Wallet depuis un fichier de sauvegarde",
504 + "restore_description_from_hardware_wallet": "Restaurer à partir d'un portefeuille matériel de grand livre",
505 "restore_description_from_keys": "Restaurer votre portefeuille (wallet) d'après les séquences de touches générées d'après vos clefs privées",
506 "restore_description_from_seed": "Restaurer votre portefeuille (wallet) depuis une phrase secrète (seed) de 25 ou 13 mots",
507 "restore_description_from_seed_keys": "Restaurez votre portefeuille (wallet) depuis une phrase secrète (seed) ou des clefs que vous avez stockées en lieu sûr",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Restaurer depuis Phrase secrète (seed)/Clefs",
515 "restore_spend_key_private": "Clef de dépense (spend key) (privée)",
516 "restore_title_from_backup": "Restaurer depuis une sauvegarde",
517 + "restore_title_from_hardware_wallet": "Restaurer à partir du portefeuille matériel",
518 "restore_title_from_keys": "Restaurer depuis des clefs",
519 "restore_title_from_seed": "Restaurer depuis une phrase secrète (seed)",
520 "restore_title_from_seed_keys": "Restaurer depuis une phrase secrète (seed) ou des clefs",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Nous ne prenons pas en charge cette action pour cet élément. Veuillez créer ou passer à un portefeuille d'un type d'actif pris en charge.",
766 "uptime": "Durée de la baisse",
767 "upto": "jusqu'à ${value}",
768 + "usb": "USB",
769 "use": "Changer vers code PIN à ",
770 "use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.",
771 "use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.",
res/values/strings_ha.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Jigon Hasken Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.",
81 "Blocks_remaining": "${status} Katanga ya rage",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Mai haske",
84 "bump_fee": "Buin",
85 "buy": "Sayi",
@@ -144,6 +145,8 @@
145 "congratulations": "Taya murna!",
146 "connect_an_existing_yat": "Haɗa Yat da ke akwai",
147 "connect_yats": "Haɗa Yats",
148 + "connect_your_hardware_wallet": "Haɗa Wallake Wallware ɗinku ta Bluetooth ko USB",
149 + "connect_your_hardware_wallet_ios": "Haɗa kayan aikinku ta Bluetooth",
150 "connection_sync": "Haɗi da daidaitawa",
151 "connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala",
152 "contact": "Tuntuɓar",
@@ -328,7 +331,13 @@
331 "is_percentage": "shine",
332 "last_30_days": "Kwanaki 30 na ƙarshe",
333 "learn_more": "Ƙara Koyi",
334 + "ledger_connection_error": "Ba a yi nasarar haɗawa da ku ba. Da fatan za a sake gwadawa.",
335 + "ledger_error_device_locked": "An kulle Ledger",
336 + "ledger_error_tx_rejected_by_user": "Ma'amala da aka ƙi akan na'urar",
337 + "ledger_error_wrong_app": "Da fatan za a tabbata kun yi amfani da app ɗin dama akan dillalarku",
338 + "ledger_please_enable_bluetooth": "Da fatan za a kunna Bluetooth don gano Ledger ɗinku",
339 "light_theme": "Haske",
340 + "load_more": "Like more",
341 "loading_your_wallet": "Ana loda walat ɗin ku",
342 "login": "Shiga",
343 "logout": "Fita",
@@ -444,6 +453,8 @@
453 "privacy_settings": "Saitunan sirri",
454 "private_key": "Keɓaɓɓen maɓalli",
455 "proceed_after_one_minute": "Idan allon bai ci gaba ba bayan minti 1, duba imel ɗin ku.",
456 + "proceed_on_device": "Ci gaba akan na'urarka",
457 + "proceed_on_device_description": "Da fatan za a bi umarnin akan walatware",
458 "profile": "Rabin fuska",
459 "provider_error": "${provider} kuskure",
460 "public_key": "Maɓallin jama'a",
@@ -492,6 +503,7 @@
503 "restore_bitcoin_description_from_seed": "Dawo da kwalinku daga 24 lambar haɗin kalma",
504 "restore_bitcoin_title_from_keys": "Dawo daga WIF",
505 "restore_description_from_backup": "Kuna iya dawo da duk aikace-aikacen Wallet ɗin Cake daga fayil ɗin ajiyar ku",
506 + "restore_description_from_hardware_wallet": "Maidowa da walatware mai wanki",
507 "restore_description_from_keys": "Maido da walat ɗin ku daga maɓallan maɓalli da aka ƙera da aka ajiye daga maɓallan ku na sirri",
508 "restore_description_from_seed": "Dawo da kwalinku daga 25 ko 13 lambar haɗin kalma",
509 "restore_description_from_seed_keys": "Maido da walat ɗin ku daga iri/maɓallan da kuka adana don amintaccen wuri",
@@ -504,6 +516,7 @@
516 "restore_seed_keys_restore": "Mayar da iri/Maɓallai",
517 "restore_spend_key_private": "Maɓallin kashewa (key kalmar sirri)",
518 "restore_title_from_backup": "Dawo daga madadin",
519 + "restore_title_from_hardware_wallet": "Dawowa daga walatware mai wuya",
520 "restore_title_from_keys": "Dawo daga maɓallai",
521 "restore_title_from_seed": "Maidowa daga iri",
522 "restore_title_from_seed_keys": "Dawo da iri/maɓallai",
@@ -754,6 +767,7 @@
767 "unsupported_asset": "Ba mu goyi bayan wannan aikin don wannan kadara. Da fatan za a ƙirƙira ko canza zuwa walat na nau'in kadara mai tallafi.",
768 "uptime": "Sama",
769 "upto": "har zuwa ${value}",
770 + "usb": "Alib",
771 "use": "Canja zuwa",
772 "use_card_info_three": "Yi amfani da katin dijital akan layi ko tare da hanyoyin biyan kuɗi mara lamba.",
773 "use_card_info_two": "Ana canza kuɗi zuwa dalar Amurka lokacin da ake riƙe su a cikin asusun da aka riga aka biya, ba cikin agogon dijital ba.",
res/values/strings_hi.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "बिटकॉइन लाइट थीम",
80 "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।",
81 "Blocks_remaining": "${status} शेष रहते हैं",
82 + "bluetooth": "ब्लूटूथ",
83 "bright_theme": "उज्ज्वल",
84 "bump_fee": "बम्प फीस",
85 "buy": "खरीदें",
@@ -144,6 +145,8 @@
145 "congratulations": "बधाई!",
146 "connect_an_existing_yat": "मौजूदा Yat कनेक्ट करें",
147 "connect_yats": "कनेक्ट Yats",
148 + "connect_your_hardware_wallet": "ब्लूटूथ या यूएसबी का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
149 + "connect_your_hardware_wallet_ios": "ब्लूटूथ का उपयोग करके अपने हार्डवेयर वॉलेट को कनेक्ट करें",
150 "connection_sync": "कनेक्शन और सिंक",
151 "connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें",
152 "contact": "संपर्क करें",
@@ -328,7 +331,13 @@
331 "is_percentage": "है",
332 "last_30_days": "पिछले 30 दिन",
333 "learn_more": "और अधिक जानें",
334 + "ledger_connection_error": "आप लेजर से जुड़ने में विफल रहे। कृपया पुन: प्रयास करें।",
335 + "ledger_error_device_locked": "खाता बंद है",
336 + "ledger_error_tx_rejected_by_user": "डिवाइस पर लेनदेन खारिज कर दिया गया",
337 + "ledger_error_wrong_app": "कृपया सुनिश्चित करें कि आप अपने लेजर पर सही ऐप को खोलते हैं",
338 + "ledger_please_enable_bluetooth": "कृपया अपने बहीखाने का पता लगाने के लिए ब्लूटूथ को सक्षम करें",
339 "light_theme": "रोशनी",
340 + "load_more": "और लोड करें",
341 "loading_your_wallet": "अपना बटुआ लोड कर रहा है",
342 "login": "लॉग इन करें",
343 "logout": "लॉगआउट",
@@ -443,6 +452,8 @@
452 "privacy_settings": "गोपनीयता सेटिंग्स",
453 "private_key": "निजी चाबी",
454 "proceed_after_one_minute": "यदि 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो अपना ईमेल देखें।",
455 + "proceed_on_device": "अपने डिवाइस पर आगे बढ़ें",
456 + "proceed_on_device_description": "कृपया अपने हार्डवेयर वॉलेट पर दिए गए निर्देशों का पालन करें",
457 "profile": "प्रोफ़ाइल",
458 "provider_error": "${provider} त्रुटि",
459 "public_key": "सार्वजनिक कुंजी",
@@ -492,6 +503,7 @@
503 "restore_bitcoin_description_from_seed": "24 शब्द संयोजन कोड से अपने वॉलेट को पुनर्स्थापित करें",
504 "restore_bitcoin_title_from_keys": "WIF से पुनर्स्थापित करें",
505 "restore_description_from_backup": "आप से पूरे केक वॉलेट एप्लिकेशन को पुनर्स्थापित कर सकते हैं आपकी बैक-अप फ़ाइल",
506 + "restore_description_from_hardware_wallet": "एक लेजर हार्डवेयर वॉलेट से पुनर्स्थापित करें",
507 "restore_description_from_keys": "अपने वॉलेट को जेनरेट से पुनर्स्थापित करें आपकी निजी कुंजी से कीस्ट्रोक्स सहेजे गए",
508 "restore_description_from_seed": "या तो 25 शब्द से अपने वॉलेट को पुनर्स्थापित करें या 13 शब्द संयोजन कोड",
509 "restore_description_from_seed_keys": "अपने बटुए को बीज से वापस लें/वे कुंजियाँ जिन्हें आपने सुरक्षित स्थान पर सहेजा है",
@@ -504,6 +516,7 @@
516 "restore_seed_keys_restore": "बीज / कुंजी पुनर्स्थापित करें",
517 "restore_spend_key_private": "कुंजी खर्च करें (निजीe)",
518 "restore_title_from_backup": "बैक-अप फ़ाइल से पुनर्स्थापित करें",
519 + "restore_title_from_hardware_wallet": "हार्डवेयर वॉलेट से पुनर्स्थापित करें",
520 "restore_title_from_keys": "कुंजी से पुनर्स्थापित करें",
521 "restore_title_from_seed": "बीज से पुनर्स्थापित करें",
522 "restore_title_from_seed_keys": "बीज / कुंजियों से पुनर्स्थापित करें",
@@ -754,6 +767,7 @@
767 "unsupported_asset": "हम इस संपत्ति के लिए इस कार्रवाई का समर्थन नहीं करते हैं. कृपया समर्थित परिसंपत्ति प्रकार का वॉलेट बनाएं या उस पर स्विच करें।",
768 "uptime": "अपटाइम",
769 "upto": "${value} तक",
770 + "usb": "USB",
771 "use": "उपयोग ",
772 "use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।",
773 "use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
res/values/strings_hr.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Light Theme",
80 "bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.",
81 "Blocks_remaining": "${status} preostalih blokova",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Jarka",
84 "bump_fee": "Naplata",
85 "buy": "Kupi",
@@ -144,6 +145,8 @@
145 "congratulations": "Čestitamo!",
146 "connect_an_existing_yat": "Povežite postojeći Yat",
147 "connect_yats": "Povežite Yats",
148 + "connect_your_hardware_wallet": "Spojite svoj hardverski novčanik pomoću Bluetooth -a ili USB -a",
149 + "connect_your_hardware_wallet_ios": "Spojite svoj hardverski novčanik pomoću Bluetooth -a",
150 "connection_sync": "Povezivanje i sinkronizacija",
151 "connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija",
152 "contact": "Kontakt",
@@ -328,7 +331,13 @@
331 "is_percentage": "je",
332 "last_30_days": "Zadnjih 30 dana",
333 "learn_more": "Saznajte više",
334 + "ledger_connection_error": "Nije uspio povezati se s knjigom. Molim te pokušaj ponovno.",
335 + "ledger_error_device_locked": "Knjiga je zaključana",
336 + "ledger_error_tx_rejected_by_user": "Transakcija odbijena na uređaju",
337 + "ledger_error_wrong_app": "Obavezno obavezno otvorite pravu aplikaciju na knjizi",
338 + "ledger_please_enable_bluetooth": "Omogućite Bluetooth da otkrije svoju knjigu",
339 "light_theme": "Svijetla",
340 + "load_more": "Učitaj više",
341 "loading_your_wallet": "Novčanik se učitava",
342 "login": "Prijava",
343 "logout": "Odjava",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Postavke privatnosti",
452 "private_key": "Privatni ključ",
453 "proceed_after_one_minute": "Ako se zaslon ne nastavi nakon 1 minute, provjerite svoju e-poštu.",
454 + "proceed_on_device": "Nastavite na svom uređaju",
455 + "proceed_on_device_description": "Slijedite upute zatražene na vašem hardverskom novčaniku",
456 "profile": "Profil",
457 "provider_error": "${provider} greška",
458 "public_key": "Javni ključ",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Oporavi novčanik pomoću koda od 12 riječi",
502 "restore_bitcoin_title_from_keys": "Oporavi pomoću formata uvoza novčanika (WIF)",
503 "restore_description_from_backup": "Možete oporaviti cijelu Cake Wallet aplikaciju pomoću vlastite datoteke sa sigurnosnom kopijom",
504 + "restore_description_from_hardware_wallet": "Vratite se iz novčanice od knjige",
505 "restore_description_from_keys": "Oporavi novčanik pomoću generiranih pritisaka na tipke spremljenih od vlastitih privatnih ključeva (keys)",
506 "restore_description_from_seed": "Oporavi novčanik pomoću koda koji sadrži kombinaciju od 25 ili 13 riječi",
507 "restore_description_from_seed_keys": "Oporavi novčanik pomoću pristupnog izraza/ključa spremljenog na sigurno mjesto",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Oporavak pomoću pristupnog izraza/ključa",
515 "restore_spend_key_private": "Spend key (privatni)",
516 "restore_title_from_backup": "Oporavak pomoću sigurnosne kopije",
517 + "restore_title_from_hardware_wallet": "Vrati iz hardverskog novčanika",
518 "restore_title_from_keys": "Oporavi pomoću ključa",
519 "restore_title_from_seed": "Oporavi pomoću pristupnog izraza",
520 "restore_title_from_seed_keys": "Oporavi pomoću pristupnog izraza/ključa",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Ne podržavamo ovu radnju za ovaj materijal. Izradite ili prijeđite na novčanik podržane vrste sredstava.",
766 "uptime": "Radno vrijeme",
767 "upto": "do ${value}",
768 + "usb": "USB",
769 "use": "Prebaci na",
770 "use_card_info_three": "Koristite digitalnu karticu online ili s beskontaktnim metodama plaćanja.",
771 "use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
res/values/strings_id.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Tema Cahaya Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.",
81 "Blocks_remaining": "${status} Blok Tersisa",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Cerah",
84 "bump_fee": "Biaya benjolan",
85 "buy": "Beli",
@@ -144,6 +145,8 @@
145 "congratulations": "Selamat!",
146 "connect_an_existing_yat": "Hubungkan Yat yang ada",
147 "connect_yats": "Hubungkan Yats",
148 + "connect_your_hardware_wallet": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth atau USB",
149 + "connect_your_hardware_wallet_ios": "Hubungkan dompet perangkat keras Anda menggunakan Bluetooth",
150 "connection_sync": "Koneksi dan sinkronisasi",
151 "connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi",
152 "contact": "Kontak",
@@ -328,7 +331,13 @@
331 "is_percentage": "adalah",
332 "last_30_days": "30 hari terakhir",
333 "learn_more": "Pelajari Lebih Lanjut",
334 + "ledger_connection_error": "Gagal terhubung ke buku besar Anda. Tolong coba lagi.",
335 + "ledger_error_device_locked": "Ledger terkunci",
336 + "ledger_error_tx_rejected_by_user": "Transaksi ditolak pada perangkat",
337 + "ledger_error_wrong_app": "Pastikan Anda membuka aplikasi yang tepat di buku besar Anda",
338 + "ledger_please_enable_bluetooth": "Harap aktifkan Bluetooth untuk mendeteksi buku besar Anda",
339 "light_theme": "Terang",
340 + "load_more": "Muat lebih banyak",
341 "loading_your_wallet": "Memuat dompet Anda",
342 "login": "Masuk",
343 "logout": "Keluar",
@@ -444,6 +453,8 @@
453 "privacy_settings": "Pengaturan privasi",
454 "private_key": "Kunci privat",
455 "proceed_after_one_minute": "Jika layar tidak bergerak setelah 1 menit, periksa email Anda.",
456 + "proceed_on_device": "Lanjutkan di perangkat Anda",
457 + "proceed_on_device_description": "Harap ikuti instruksi yang diminta di dompet perangkat keras Anda",
458 "profile": "Profil",
459 "provider_error": "${provider} error",
460 "public_key": "Kunci publik",
@@ -492,6 +503,7 @@
503 "restore_bitcoin_description_from_seed": "Pulihkan dompet Anda dari kombinasi kode 24 kata",
504 "restore_bitcoin_title_from_keys": "Pulihkan dari WIF",
505 "restore_description_from_backup": "Anda dapat memulihkan seluruh aplikasi Cake Wallet dari file cadangan Anda",
506 + "restore_description_from_hardware_wallet": "Kembalikan dari dompet perangkat keras Ledger",
507 "restore_description_from_keys": "Pulihkan dompet Anda dari tombol yang dihasilkan yang disimpan dari kunci pribadi Anda",
508 "restore_description_from_seed": "Pulihkan dompet Anda dari kombinasi kode 25 atau 13 kata",
509 "restore_description_from_seed_keys": "Dapatkan kembali dompet Anda dari seed/kunci yang Anda simpan di tempat yang aman",
@@ -505,6 +517,7 @@
517 "restore_seed_keys_restore": "Pulihkan Seed/Kunci",
518 "restore_spend_key_private": "Habiskan kunci (pribadi)",
519 "restore_title_from_backup": "Pulihkan dari cadangan",
520 + "restore_title_from_hardware_wallet": "Kembalikan dari dompet perangkat keras",
521 "restore_title_from_keys": "Pulihkan dari kunci",
522 "restore_title_from_seed": "Pulihkan dari seed",
523 "restore_title_from_seed_keys": "Pulihkan dari seed/kunci",
@@ -755,6 +768,7 @@
768 "unsupported_asset": "Kami tidak mendukung tindakan ini untuk aset ini. Harap buat atau alihkan ke dompet dari jenis aset yang didukung.",
769 "uptime": "Uptime",
770 "upto": "hingga ${value}",
771 + "usb": "USB",
772 "use": "Beralih ke ",
773 "use_card_info_three": "Gunakan kartu digital secara online atau dengan metode pembayaran tanpa kontak.",
774 "use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.",
res/values/strings_it.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Tema luce Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.",
81 "Blocks_remaining": "${status} Blocchi Rimanenti",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Colorato",
84 "bump_fee": "Commissione per bump",
85 "buy": "Comprare",
@@ -145,6 +146,8 @@
146 "congratulations": "Congratulazioni!",
147 "connect_an_existing_yat": "Collegare un Yat esistente",
148 "connect_yats": "Connetti Yats",
149 + "connect_your_hardware_wallet": "Collega il tuo portafoglio hardware con Bluetooth o USB",
150 + "connect_your_hardware_wallet_ios": "Collega il tuo portafoglio hardware con Bluetooth",
151 "connection_sync": "Connessione e sincronizzazione",
152 "connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni",
153 "contact": "Contatta",
@@ -329,7 +332,13 @@
332 "is_percentage": "è",
333 "last_30_days": "Ultimi 30 giorni",
334 "learn_more": "Impara di più",
335 + "ledger_connection_error": "Impossibile connettersi al libro mastro. Per favore riprova.",
336 + "ledger_error_device_locked": "Il libro mastro è bloccato",
337 + "ledger_error_tx_rejected_by_user": "Transazione rifiutata sul dispositivo",
338 + "ledger_error_wrong_app": "Assicurati di aprire l'app giusta sul libro mastro",
339 + "ledger_please_enable_bluetooth": "Si prega di consentire al Bluetooth di rilevare il libro mastro",
340 "light_theme": "Bianco",
341 + "load_more": "Carica di più",
342 "loading_your_wallet": "Caricamento portafoglio",
343 "login": "Accedi",
344 "logout": "Logout",
@@ -444,6 +453,8 @@
453 "privacy_settings": "Impostazioni privacy",
454 "private_key": "Chiave privata",
455 "proceed_after_one_minute": "Se lo schermo non procede dopo 1 minuto, controlla la tua email.",
456 + "proceed_on_device": "Procedi sul tuo dispositivo",
457 + "proceed_on_device_description": "Segui le istruzioni richieste sul portafoglio hardware",
458 "profile": "Profilo",
459 "provider_error": "${provider} errore",
460 "public_key": "Chiave pubblica",
@@ -492,6 +503,7 @@
503 "restore_bitcoin_description_from_seed": "Recupera il tuo portafoglio da una combinazione di 12 parole",
504 "restore_bitcoin_title_from_keys": "Recupera da WIF",
505 "restore_description_from_backup": "Puoi recuperare l'app Cake Wallet per intero dal tuo file di backup",
506 + "restore_description_from_hardware_wallet": "Ripristina da un portafoglio hardware di libro mastro",
507 "restore_description_from_keys": "Recupera il tuo portafoglio da una sequenza di caratteri generati dalle tue chiavi private",
508 "restore_description_from_seed": "Recupera il tuo portafoglio da una combinazione di 25 o 13 parole",
509 "restore_description_from_seed_keys": "Recupera il tuo portafoglio dal seme/chiavi che hai salvato in un posto sicuro",
@@ -504,6 +516,7 @@
516 "restore_seed_keys_restore": "Recupera Seme/Chiavi",
517 "restore_spend_key_private": "Chiave di Spesa (privata)",
518 "restore_title_from_backup": "Recupera da backup",
519 + "restore_title_from_hardware_wallet": "Ripristina dal portafoglio hardware",
520 "restore_title_from_keys": "Recupera dalle chiavi",
521 "restore_title_from_seed": "Recupera dal seme",
522 "restore_title_from_seed_keys": "Recupera dal seme/chiavi",
@@ -754,6 +767,7 @@
767 "unsupported_asset": "Non supportiamo questa azione per questa risorsa. Crea o passa a un portafoglio di un tipo di asset supportato.",
768 "uptime": "Uptime",
769 "upto": "fino a ${value}",
770 + "usb": "USB",
771 "use": "Passa a ",
772 "use_card_info_three": "Utilizza la carta digitale online o con metodi di pagamento contactless.",
773 "use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
res/values/strings_ja.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "ビットコインライトテーマ",
80 "bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。",
81 "Blocks_remaining": "${status} 残りのブロック",
82 + "bluetooth": "ブルートゥース",
83 "bright_theme": "明るい",
84 "bump_fee": "バンプ料金",
85 "buy": "購入",
@@ -144,6 +145,8 @@
145 "congratulations": "おめでとうございます!",
146 "connect_an_existing_yat": "既存のYatを接続します",
147 "connect_yats": "Yatsを接続します",
148 + "connect_your_hardware_wallet": "BluetoothまたはUSBを使用して、ハードウェアウォレットを接続します",
149 + "connect_your_hardware_wallet_ios": "Bluetoothを使用してハードウェアウォレットを接続します",
150 "connection_sync": "接続と同期",
151 "connectWalletPrompt": "ウォレットを WalletConnect に接続して取引を行う",
152 "contact": "接触",
@@ -329,7 +332,13 @@
332 "is_percentage": "is",
333 "last_30_days": "過去30日",
334 "learn_more": "もっと詳しく知る",
335 + "ledger_connection_error": "元帳に接続できませんでした。もう一度やり直してください。",
336 + "ledger_error_device_locked": "元帳がロックされています",
337 + "ledger_error_tx_rejected_by_user": "トランザクションはデバイスで拒否されました",
338 + "ledger_error_wrong_app": "元帳に適切なアプリを開始するようにしてください",
339 + "ledger_please_enable_bluetooth": "Bluetoothが元帳を検出できるようにしてください",
340 "light_theme": "光",
341 + "load_more": "もっと読み込む",
342 "loading_your_wallet": "ウォレットをロードしています",
343 "login": "ログイン",
344 "logout": "ログアウト",
@@ -443,6 +452,8 @@
452 "privacy_settings": "プライバシー設定",
453 "private_key": "秘密鍵",
454 "proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。",
455 + "proceed_on_device": "デバイスに進みます",
456 + "proceed_on_device_description": "ハードウェアウォレットにプロンプ​​トされた指示に従ってください",
457 "profile": "プロフィール",
458 "provider_error": "${provider} エラー",
459 "public_key": "公開鍵",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "24ワードの組み合わせコードからウォレットを復元する",
503 "restore_bitcoin_title_from_keys": "WIFから復元",
504 "restore_description_from_backup": "Cake Walletアプリ全体を復元できますバックアップファイル",
505 + "restore_description_from_hardware_wallet": "元帳ハードウェアウォレットから復元します",
506 "restore_description_from_keys": "生成されたウォレットを復元します秘密鍵から保存されたキーストローク",
507 "restore_description_from_seed": "25ワードからウォレットを復元しますまたは13ワードの組み合わせコード",
508 "restore_description_from_seed_keys": "安全な場所に保存したシード/キーから財布を取り戻す",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "シード/キーの復元",
516 "restore_spend_key_private": "キーを使う (プライベート)",
517 "restore_title_from_backup": "バックアップファイルから復元する",
518 + "restore_title_from_hardware_wallet": "ハードウェアウォレットから復元します",
519 "restore_title_from_keys": "キーから復元する",
520 "restore_title_from_seed": "シードから復元",
521 "restore_title_from_seed_keys": "シード/キーから復元",
@@ -753,6 +766,7 @@
766 "unsupported_asset": "このアセットに対するこのアクションはサポートされていません。サポートされているアセットタイプのウォレットを作成するか、ウォレットに切り替えてください。",
767 "uptime": "稼働時間",
768 "upto": "up up ${value}",
769 + "usb": "USB",
770 "use": "使用する ",
771 "use_card_info_three": "デジタルカードをオンラインまたは非接触型決済方法で使用してください。",
772 "use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
res/values/strings_ko.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "비트코인 라이트 테마",
80 "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.",
81 "Blocks_remaining": "${status} 남은 블록",
82 + "bluetooth": "블루투스",
83 "bright_theme": "선명한",
84 "bump_fee": "범프 요금",
85 "buy": "구입",
@@ -144,6 +145,8 @@
145 "congratulations": "축하합니다!",
146 "connect_an_existing_yat": "기존 Yat 연결",
147 "connect_yats": "야츠 연결",
148 + "connect_your_hardware_wallet": "Bluetooth 또는 USB를 사용하여 하드웨어 지갑을 연결하십시오",
149 + "connect_your_hardware_wallet_ios": "Bluetooth를 사용하여 하드웨어 지갑을 연결하십시오",
150 "connection_sync": "연결 및 동기화",
151 "connectWalletPrompt": "거래를 하려면 WalletConnect에 지갑을 연결하세요.",
152 "contact": "접촉",
@@ -328,7 +331,13 @@
331 "is_percentage": "이다",
332 "last_30_days": "지난 30일",
333 "learn_more": "더 알아보기",
334 + "ledger_connection_error": "원장에 연결하지 못했습니다. 다시 시도하십시오.",
335 + "ledger_error_device_locked": "원장이 잠겨 있습니다",
336 + "ledger_error_tx_rejected_by_user": "장치에서 거래가 거부되었습니다",
337 + "ledger_error_wrong_app": "원장에서 올바른 앱을 반대하는지 확인하십시오.",
338 + "ledger_please_enable_bluetooth": "Bluetooth가 원장을 감지 할 수 있도록하십시오",
339 "light_theme": "빛",
340 + "load_more": "더로드하십시오",
341 "loading_your_wallet": "지갑 넣기",
342 "login": "로그인",
343 "logout": "로그아웃",
@@ -443,6 +452,8 @@
452 "privacy_settings": "개인정보 설정",
453 "private_key": "개인 키",
454 "proceed_after_one_minute": "1분 후에도 화면이 진행되지 않으면 이메일을 확인하세요.",
455 + "proceed_on_device": "장치를 진행하십시오",
456 + "proceed_on_device_description": "하드웨어 지갑에 표시된 지침을 따르십시오",
457 "profile": "프로필",
458 "provider_error": "${provider} 오류",
459 "public_key": "공개 키",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "24 단어 조합 코드에서 지갑 복원",
503 "restore_bitcoin_title_from_keys": "WIF에서 복원",
504 "restore_description_from_backup": "백업 파일에서 전체 Cake Wallet 앱을 복원 할 수 있습니다.",
505 + "restore_description_from_hardware_wallet": "원장 하드웨어 지갑에서 복원하십시오",
506 "restore_description_from_keys": "개인 키에서 저장된 생성 된 키 스트로크에서 월렛 복원",
507 "restore_description_from_seed": "25 단어 또는 13 단어 조합 코드에서 지갑을 복원하십시오.",
508 "restore_description_from_seed_keys": "안전한 장소에 저장 한 종자 / 키로 지갑을 되 찾으십시오.",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "종자 / 키 복원",
516 "restore_spend_key_private": "지출 키 (은밀한)",
517 "restore_title_from_backup": "백업 파일에서 복원",
518 + "restore_title_from_hardware_wallet": "하드웨어 지갑에서 복원하십시오",
519 "restore_title_from_keys": "키에서 복원",
520 "restore_title_from_seed": "종자에서 복원",
521 "restore_title_from_seed_keys": "시드 / 키에서 복원",
@@ -753,6 +766,7 @@
766 "unsupported_asset": "이 저작물에 대해 이 작업을 지원하지 않습니다. 지원되는 자산 유형의 지갑을 생성하거나 전환하십시오.",
767 "uptime": "가동 시간",
768 "upto": "최대 ${value}",
769 + "usb": "USB",
770 "use": "사용하다 ",
771 "use_card_info_three": "디지털 카드를 온라인 또는 비접촉식 결제 수단으로 사용하십시오.",
772 "use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.",
res/values/strings_my.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Light အပြင်အဆင်",
80 "bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။",
81 "Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။",
82 + "bluetooth": "ဘလူးတုသ်",
83 "bright_theme": "တောက်ပ",
84 "bump_fee": "ဝင်ငွေ",
85 "buy": "ဝယ်ပါ။",
@@ -144,6 +145,8 @@
145 "congratulations": "ဂုဏ်ယူပါသည်။",
146 "connect_an_existing_yat": "ရှိပြီးသား Yat ကို ချိတ်ဆက်ပါ။",
147 "connect_yats": "Yats ကိုချိတ်ဆက်ပါ။",
148 + "connect_your_hardware_wallet": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth သို့မဟုတ် USB ကို သုံး. ချိတ်ဆက်ပါ",
149 + "connect_your_hardware_wallet_ios": "သင်၏ hardware ပိုက်ဆံအိတ်ကို Bluetooth ကို အသုံးပြု. ချိတ်ဆက်ပါ",
150 "connection_sync": "ချိတ်ဆက်မှုနှင့် ထပ်တူပြုခြင်း။",
151 "connectWalletPrompt": "အရောင်းအဝယ်ပြုလုပ်ရန် သင့်ပိုက်ဆံအိတ်ကို WalletConnect နှင့် ချိတ်ဆက်ပါ။",
152 "contact": "ဆက်သွယ်ရန်",
@@ -328,7 +331,13 @@
331 "is_percentage": "သည်",
332 "last_30_days": "လွန်ခဲ့သော ရက် 30",
333 "learn_more": "ပိုမိုသိရှိရန်",
334 + "ledger_connection_error": "သငျသညျ Ledger နှင့်ချိတ်ဆက်ရန်မအောင်မြင်ပါ။ ကျေးဇူးပြုပြီးထပ်ကြိုးစားပါ",
335 + "ledger_error_device_locked": "အဆိုပါလမ်းပြသော့ခတ်သည်",
336 + "ledger_error_tx_rejected_by_user": "ငွေပေးငွေယူ device ကိုအပေါ်ငြင်းပယ်ခဲ့သည်",
337 + "ledger_error_wrong_app": "ကျေးဇူးပြု. သင့်လက်ျာအက်ပ်ကိုသင်၏ Ledger တွင်ဖွင့်ရန်သေချာစေပါ",
338 + "ledger_please_enable_bluetooth": "သင်၏ Ledger ကိုရှာဖွေရန် Bluetooth ကိုဖွင့်ပါ",
339 "light_theme": "အလင်း",
340 + "load_more": "ပိုပြီး load",
341 "loading_your_wallet": "သင့်ပိုက်ဆံအိတ်ကို ဖွင့်နေသည်။",
342 "login": "လော့ဂ်အင်",
343 "logout": "ထွက်လိုက်ပါ။",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Privacy settings တွေကို",
452 "private_key": "သီးသန့်သော့",
453 "proceed_after_one_minute": "မျက်နှာပြင်သည် ၁ မိနစ်အကြာတွင် ဆက်လက်မလုပ်ဆောင်ပါက သင့်အီးမေးလ်ကို စစ်ဆေးပါ။",
454 + "proceed_on_device": "သင့်စက်ပေါ်တွင်ဆက်လက်ဆောင်ရွက်ပါ",
455 + "proceed_on_device_description": "သင်၏ hardware ပိုက်ဆံအိတ်ပေါ်ရှိညွှန်ကြားချက်များကိုလိုက်နာပါ",
456 "profile": "ကိုယ်ရေးအကျဉ်း",
457 "provider_error": "${provider} အမှား",
458 "public_key": "အများသူငှာသော့",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "24 စကားလုံးပေါင်းစပ်ကုဒ်မှ သင့်ပိုက်ဆံအိတ်ကို ပြန်ယူပါ။",
502 "restore_bitcoin_title_from_keys": "WIF မှ ပြန်လည်ရယူပါ။",
503 "restore_description_from_backup": "သင့်အရန်ဖိုင်မှ Cake Wallet အက်ပ်တစ်ခုလုံးကို သင်ပြန်လည်ရယူနိုင်သည်။",
504 + "restore_description_from_hardware_wallet": "Ledger ဟာ့ဒ်ဝဲပိုက်ဆံအိတ်မှ Restore",
505 "restore_description_from_keys": "သင့်ကိုယ်ပိုင်သော့များမှ သိမ်းဆည်းထားသော ထုတ်ပေးထားသော သော့ချက်များမှ သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူပါ။",
506 "restore_description_from_seed": "25 စကားလုံး သို့မဟုတ် 13 စကားလုံးပေါင်းစပ်ကုဒ်မှ သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူပါ။",
507 "restore_description_from_seed_keys": "သင့်ပိုက်ဆံအိတ်ကို လုံခြုံသောနေရာတွင် သိမ်းဆည်းထားသော မျိုးစေ့/သော့များမှ ပြန်လည်ရယူပါ။",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "မျိုးစေ့/သော့များ ပြန်လည်ရယူပါ။",
515 "restore_spend_key_private": "သော့သုံးရန် (သီးသန့်)",
516 "restore_title_from_backup": "အရန်သိမ်းခြင်းမှ ပြန်လည်ရယူပါ။",
517 + "restore_title_from_hardware_wallet": "ဟာ့ဒ်ဝဲပိုက်ဆံအိတ်မှ restore",
518 "restore_title_from_keys": "သော့များမှ ပြန်လည်ရယူပါ။",
519 "restore_title_from_seed": "မျိုးစေ့မှပြန်လည်ရယူပါ။",
520 "restore_title_from_seed_keys": "မျိုးစေ့/သော့များမှ ပြန်လည်ရယူပါ။",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "ဤပိုင်ဆိုင်မှုအတွက် ဤလုပ်ဆောင်ချက်ကို ကျွန်ုပ်တို့ မပံ့ပိုးပါ။ ကျေးဇူးပြု၍ ပံ့ပိုးပေးထားသော ပိုင်ဆိုင်မှုအမျိုးအစား၏ ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
766 "uptime": "အထက်က",
767 "upto": "${value} အထိ",
768 + "usb": "ယူအက်စ်ဘီ",
769 "use": "သို့ပြောင်းပါ။",
770 "use_card_info_three": "ဒစ်ဂျစ်တယ်ကတ်ကို အွန်လိုင်း သို့မဟုတ် ထိတွေ့မှုမဲ့ ငွေပေးချေမှုနည်းလမ်းများဖြင့် အသုံးပြုပါ။",
771 "use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
res/values/strings_nl.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Light-thema",
80 "bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.",
81 "Blocks_remaining": "${status} Resterende blokken",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Helder",
84 "bump_fee": "Bult fee",
85 "buy": "Kopen",
@@ -144,6 +145,8 @@
145 "congratulations": "gefeliciteerd!",
146 "connect_an_existing_yat": "Verbind een bestaande Yat",
147 "connect_yats": "Verbind Yats",
148 + "connect_your_hardware_wallet": "Sluit uw hardware -portemonnee aan met Bluetooth of USB",
149 + "connect_your_hardware_wallet_ios": "Sluit uw hardware -portemonnee aan met Bluetooth",
150 "connection_sync": "Verbinding en synchronisatie",
151 "connectWalletPrompt": "Verbind uw portemonnee met WalletConnect om transacties uit te voeren",
152 "contact": "Contact",
@@ -328,7 +331,13 @@
331 "is_percentage": "is",
332 "last_30_days": "Laatste 30 dagen",
333 "learn_more": "Kom meer te weten",
334 + "ledger_connection_error": "Kan geen verbinding maken met u grootboek. Probeer het opnieuw.",
335 + "ledger_error_device_locked": "Het grootboek is vergrendeld",
336 + "ledger_error_tx_rejected_by_user": "Transactie afgewezen op apparaat",
337 + "ledger_error_wrong_app": "Zorg ervoor dat u de juiste app op uw grootboek opent",
338 + "ledger_please_enable_bluetooth": "Schakel Bluetooth in staat om uw grootboek te detecteren",
339 "light_theme": "Licht",
340 + "load_more": "Meer laden",
341 "loading_your_wallet": "Uw portemonnee laden",
342 "login": "Log in",
343 "logout": "Uitloggen",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Privacy-instellingen",
452 "private_key": "Prive sleutel",
453 "proceed_after_one_minute": "Als het scherm na 1 minuut niet verder gaat, controleer dan uw e-mail.",
454 + "proceed_on_device": "Ga verder met uw apparaat",
455 + "proceed_on_device_description": "Volg de instructies die zijn aangevraagd op uw hardware -portemonnee",
456 "profile": "Profiel",
457 "provider_error": "${provider} fout",
458 "public_key": "Publieke sleutel",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Herstel uw portemonnee met een combinatiecode van 24 woorden",
502 "restore_bitcoin_title_from_keys": "Herstel van WIF",
503 "restore_description_from_backup": "Je kunt de hele Cake Wallet-app herstellen van uw back-upbestand",
504 + "restore_description_from_hardware_wallet": "Herstel van een grootboekhardware -portemonnee",
505 "restore_description_from_keys": "Herstel uw portemonnee van gegenereerd toetsaanslagen opgeslagen van uw privésleutels",
506 "restore_description_from_seed": "Herstel uw portemonnee van het 25 woord of 13 woord combinatiecode",
507 "restore_description_from_seed_keys": "Ontvang uw portemonnee terug uit seed / keys die u hebt opgeslagen op een veilige plaats",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Zaad / sleutels herstellen",
515 "restore_spend_key_private": "Sleutel uitgeven (privaat)",
516 "restore_title_from_backup": "Herstellen vanuit een back-upbestand",
517 + "restore_title_from_hardware_wallet": "Herstel van de hardware -portemonnee",
518 "restore_title_from_keys": "Herstel van sleutels",
519 "restore_title_from_seed": "Herstel van zaad",
520 "restore_title_from_seed_keys": "Herstel van zaad / sleutels",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "We ondersteunen deze actie niet voor dit item. Maak of schakel over naar een portemonnee van een ondersteund activatype.",
766 "uptime": "Uptime",
767 "upto": "tot ${value}",
768 + "usb": "USB",
769 "use": "Gebruik ",
770 "use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
771 "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
res/values/strings_pl.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Lekki motyw Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.",
81 "Blocks_remaining": "Pozostało ${status} bloków",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Biały",
84 "bump_fee": "Opłata za nierówność",
85 "buy": "Kup",
@@ -144,6 +145,8 @@
145 "congratulations": "gratulacje!",
146 "connect_an_existing_yat": "Podłącz istniejący Yat",
147 "connect_yats": "Połącz Yats",
148 + "connect_your_hardware_wallet": "Podłącz portfel sprzętowy za pomocą Bluetooth lub USB",
149 + "connect_your_hardware_wallet_ios": "Podłącz portfel sprzętowy za pomocą Bluetooth",
150 "connection_sync": "Połączenie i synchronizacja",
151 "connectWalletPrompt": "Połącz swój portfel z WalletConnect, aby dokonywać transakcji",
152 "contact": "Kontakt",
@@ -328,7 +331,13 @@
331 "is_percentage": "jest",
332 "last_30_days": "Ostatnie 30 dni",
333 "learn_more": "Dowiedz się więcej",
334 + "ledger_connection_error": "Nie udało się połączyć z twoją księgą. Proszę spróbuj ponownie.",
335 + "ledger_error_device_locked": "Księga jest zamknięta",
336 + "ledger_error_tx_rejected_by_user": "Transakcja odrzucona na urządzeniu",
337 + "ledger_error_wrong_app": "Upewnij się, że opisz odpowiednią aplikację na swojej księdze",
338 + "ledger_please_enable_bluetooth": "Włącz Bluetooth wykrywanie księgi",
339 "light_theme": "Jasny",
340 + "load_more": "Załaduj więcej",
341 "loading_your_wallet": "Ładowanie portfela",
342 "login": "Login",
343 "logout": "Wyloguj",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Ustawienia prywatności",
452 "private_key": "Klucz prywatny",
453 "proceed_after_one_minute": "Jeśli ekran nie przejdzie dalej po 1 minucie, sprawdź pocztę.",
454 + "proceed_on_device": "Kontynuuj swoje urządzenie",
455 + "proceed_on_device_description": "Postępuj zgodnie z instrukcjami wyświetlonymi w portfelu sprzętowym",
456 "profile": "Profil",
457 "provider_error": "${provider} pomyłka",
458 "public_key": "Klucz publiczny",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Przywróć swój portfel z frazy seed złożonej z 24 słów",
502 "restore_bitcoin_title_from_keys": "Przywróć z klucza prywatnego",
503 "restore_description_from_backup": "Możesz przywrócić całą aplikację Cake Wallet z pliku kopii zapasowej",
504 + "restore_description_from_hardware_wallet": "Przywróć z portfela sprzętowego księgi",
505 "restore_description_from_keys": "Przywróć swój portfel z kluczy prywatnych",
506 "restore_description_from_seed": "Przywróć swój portfel z 25 lub 13-słownej frazy seed",
507 "restore_description_from_seed_keys": "Odzyskaj swój portfel z seedów / kluczy, które zapisałeś w bezpiecznym miejscu",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Przywracanie seedów / kluczy",
515 "restore_spend_key_private": "Podaj prywatny klucz wglądu (view key)",
516 "restore_title_from_backup": "Przywróć z pliku kopii zapasowej",
517 + "restore_title_from_hardware_wallet": "Przywróć z portfela sprzętowego",
518 "restore_title_from_keys": "Przywróć z kluczy",
519 "restore_title_from_seed": "Przywróć z seedów",
520 "restore_title_from_seed_keys": "Przywróć z seedów / kluczy",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Nie obsługujemy tego działania w przypadku tego zasobu. Utwórz lub przełącz się na portfel obsługiwanego typu aktywów.",
766 "uptime": "Czas aktu",
767 "upto": "do ${value}",
768 + "usb": "USB",
769 "use": "Użyj ",
770 "use_card_info_three": "Użyj cyfrowej karty online lub za pomocą zbliżeniowych metod płatności.",
771 "use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.",
res/values/strings_pt.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Tema claro de bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.",
81 "Blocks_remaining": "${status} blocos restantes",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Brilhante",
84 "bump_fee": "Taxa de aumento",
85 "buy": "Comprar",
@@ -144,6 +145,8 @@
145 "congratulations": "Parabéns!",
146 "connect_an_existing_yat": "Conecte um Yat existente",
147 "connect_yats": "Connect Yats",
148 + "connect_your_hardware_wallet": "Conecte sua carteira de hardware usando Bluetooth ou USB",
149 + "connect_your_hardware_wallet_ios": "Conecte sua carteira de hardware usando o Bluetooth",
150 "connection_sync": "Conexão e sincronização",
151 "connectWalletPrompt": "Conecte sua carteira ao WalletConnect para fazer transações",
152 "contact": "Contato",
@@ -328,7 +331,13 @@
331 "is_percentage": "é",
332 "last_30_days": "Últimos 30 dias",
333 "learn_more": "Saber mais",
334 + "ledger_connection_error": "Falha ao se conectar ao seu livro. Por favor, tente novamente.",
335 + "ledger_error_device_locked": "O livro está trancado",
336 + "ledger_error_tx_rejected_by_user": "Transação rejeitada no dispositivo",
337 + "ledger_error_wrong_app": "Por favor, certifique -se de optar pelo aplicativo certo no seu livro",
338 + "ledger_please_enable_bluetooth": "Ative o Bluetooth para detectar seu livro",
339 "light_theme": "Luz",
340 + "load_more": "Carregue mais",
341 "loading_your_wallet": "Abrindo sua carteira",
342 "login": "Login",
343 "logout": "Logout",
@@ -444,6 +453,8 @@
453 "privacy_settings": "Configurações de privacidade",
454 "private_key": "Chave privada",
455 "proceed_after_one_minute": "Se a tela não prosseguir após 1 minuto, verifique seu e-mail.",
456 + "proceed_on_device": "Prossiga no seu dispositivo",
457 + "proceed_on_device_description": "Siga as instruções solicitadas em sua carteira de hardware",
458 "profile": "Perfil",
459 "provider_error": "${provider} erro",
460 "public_key": "Chave pública",
@@ -492,6 +503,7 @@
503 "restore_bitcoin_description_from_seed": "Restaure sua carteira a partir de um código de combinação de 24 palavras",
504 "restore_bitcoin_title_from_keys": "Restaurar de WIF",
505 "restore_description_from_backup": "Você pode restaurar todo o aplicativo Cake Wallet de seu arquivo de backup",
506 + "restore_description_from_hardware_wallet": "Restaurar de uma carteira de hardware do Ledger",
507 "restore_description_from_keys": "Restaure sua carteira a partir de suas chaves privadas",
508 "restore_description_from_seed": "Restaure sua carteira a partir de semente com 25 palavras ou 13 palavras",
509 "restore_description_from_seed_keys": "Restaure a sua carteira a partir de sementes/chaves que você salvou em um local seguro",
@@ -504,6 +516,7 @@
516 "restore_seed_keys_restore": "Restauração com sementes/chaves",
517 "restore_spend_key_private": "Chave de gastos (privada)",
518 "restore_title_from_backup": "Restaurar a partir de um arquivo de backup",
519 + "restore_title_from_hardware_wallet": "Restaurar da carteira de hardware",
520 "restore_title_from_keys": "Restaurar a partir de chaves",
521 "restore_title_from_seed": "Restaurar a partir de semente",
522 "restore_title_from_seed_keys": "Restaurar a partir de sementes/chaves",
@@ -754,6 +767,7 @@
767 "unsupported_asset": "Não oferecemos suporte a esta ação para este recurso. Crie ou mude para uma carteira de um tipo de ativo compatível.",
768 "uptime": "Tempo de atividade",
769 "upto": "até ${value}",
770 + "usb": "USB",
771 "use": "Use PIN de ",
772 "use_card_info_three": "Use o cartão digital online ou com métodos de pagamento sem contato.",
773 "use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
res/values/strings_ru.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Светлая биткойн-тема",
80 "bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.",
81 "Blocks_remaining": "${status} Осталось блоков",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Яркая",
84 "bump_fee": "Повысить комиссию",
85 "buy": "Купить",
@@ -144,6 +145,8 @@
145 "congratulations": "Поздравляем!",
146 "connect_an_existing_yat": "Подключить существующий Yat",
147 "connect_yats": "Подключить Yats",
148 + "connect_your_hardware_wallet": "Подключите свой аппаратный кошелек с помощью Bluetooth или USB",
149 + "connect_your_hardware_wallet_ios": "Подключите свой аппаратный кошелек с помощью Bluetooth",
150 "connection_sync": "Подключение и синхронизация",
151 "connectWalletPrompt": "Подключите свой кошелек к WalletConnect для совершения транзакций.",
152 "contact": "Контакт",
@@ -328,7 +331,13 @@
331 "is_percentage": "есть",
332 "last_30_days": "Последние 30 дней",
333 "learn_more": "Узнать больше",
334 + "ledger_connection_error": "Не удалось подключиться к вам, книги. Пожалуйста, попробуйте еще раз.",
335 + "ledger_error_device_locked": "Ledger заблокирован",
336 + "ledger_error_tx_rejected_by_user": "Транзакция отклоняется на устройстве",
337 + "ledger_error_wrong_app": "Пожалуйста, убедитесь, что вы предлагаете правильное приложение в своей бухгалтерской книге",
338 + "ledger_please_enable_bluetooth": "Пожалуйста, включите Bluetooth обнаружить вашу бухгалтерскую книгу",
339 "light_theme": "Светлая",
340 + "load_more": "Загрузи больше",
341 "loading_your_wallet": "Загрузка кошелька",
342 "login": "Логин",
343 "logout": "Выйти",
@@ -443,6 +452,8 @@
452 "privacy_settings": "Настройки конфиденциальности",
453 "private_key": "Приватный ключ",
454 "proceed_after_one_minute": "Если через 1 минуту экран не отображается, проверьте свою электронную почту.",
455 + "proceed_on_device": "Пройдите на свое устройство",
456 + "proceed_on_device_description": "Пожалуйста, следуйте инструкциям, представленным на вашем аппаратном кошельке",
457 "profile": "Профиль",
458 "provider_error": "${provider} ошибка",
459 "public_key": "Публичный ключ",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "Вы можете восстановить кошелёк используя 24-ти значную мнемоническую фразу",
503 "restore_bitcoin_title_from_keys": "Восстановить с помощью WIF",
504 "restore_description_from_backup": "Вы можете восстановить Cake Wallet из вашего back-up файла",
505 + "restore_description_from_hardware_wallet": "Восстановите из оборудования",
506 "restore_description_from_keys": "Вы можете восстановить кошелёк с помощью приватных ключей",
507 "restore_description_from_seed": "Вы можете восстановить кошелёк используя 25-ти значную мнемоническую фразу",
508 "restore_description_from_seed_keys": "Вы можете восстановить кошелёк из мнемонической фразы/ключей, которые вы сохранили ранее",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "Восстановить из мнемонической фразы/ключей",
516 "restore_spend_key_private": "Приватный ключ траты",
517 "restore_title_from_backup": "Восстановить из back-up файла",
518 + "restore_title_from_hardware_wallet": "Восстановить из аппаратного кошелька",
519 "restore_title_from_keys": "Восстановить с помощью ключей",
520 "restore_title_from_seed": "Восстановить из мнемонической фразы",
521 "restore_title_from_seed_keys": "Восстановить из мнемонической фразы/ключей",
@@ -753,6 +766,7 @@
766 "unsupported_asset": "Мы не поддерживаем это действие для этого объекта. Пожалуйста, создайте или переключитесь на кошелек поддерживаемого типа активов.",
767 "uptime": "Время безотказной работы",
768 "upto": "до ${value}",
769 + "usb": "USB",
770 "use": "Использовать ",
771 "use_card_info_three": "Используйте цифровую карту онлайн или с помощью бесконтактных способов оплаты.",
772 "use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
res/values/strings_th.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "ธีมแสง Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน",
81 "Blocks_remaining": "${status} บล็อกที่เหลืออยู่",
82 + "bluetooth": "บลูทู ธ",
83 "bright_theme": "สดใส",
84 "bump_fee": "ค่าธรรมเนียมชน",
85 "buy": "ซื้อ",
@@ -144,6 +145,8 @@
145 "congratulations": "ขอแสดงความยินดี!",
146 "connect_an_existing_yat": "เชื่อมต่อ Yat ที่มีอยู่",
147 "connect_yats": "เชื่อมต่อ Yats",
148 + "connect_your_hardware_wallet": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ หรือ USB",
149 + "connect_your_hardware_wallet_ios": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณโดยใช้บลูทู ธ",
150 "connection_sync": "การเชื่อมต่อและการซิงค์",
151 "connectWalletPrompt": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect เพื่อทำธุรกรรม",
152 "contact": "ผู้ติดต่อ",
@@ -328,7 +331,13 @@
331 "is_percentage": "เป็น",
332 "last_30_days": "30 วันล่าสุด",
333 "learn_more": "ศึกษาเพิ่มเติม",
334 + "ledger_connection_error": "ไม่สามารถเชื่อมต่อกับบัญชีแยกประเภทของคุณได้ กรุณาลองอีกครั้ง.",
335 + "ledger_error_device_locked": "บัญชีแยกประเภทถูกล็อค",
336 + "ledger_error_tx_rejected_by_user": "ธุรกรรมถูกปฏิเสธบนอุปกรณ์",
337 + "ledger_error_wrong_app": "โปรดตรวจสอบให้แน่ใจว่าคุณเปิดแอพที่เหมาะสมในบัญชีแยกประเภทของคุณ",
338 + "ledger_please_enable_bluetooth": "โปรดเปิดใช้งานบลูทู ธ ในการตรวจจับบัญชีแยกประเภทของคุณ",
339 "light_theme": "สว่าง",
340 + "load_more": "โหลดมากขึ้น",
341 "loading_your_wallet": "กำลังโหลดกระเป๋าของคุณ",
342 "login": "เข้าสู่ระบบ",
343 "logout": "ออกจากระบบ",
@@ -442,6 +451,8 @@
451 "privacy_settings": "การตั้งค่าความเป็นส่วนตัว",
452 "private_key": "คีย์ส่วนตัว",
453 "proceed_after_one_minute": "หากหน้าจอไม่ดำเนินการหลังจาก 1 นาทีโปรดตรวจสอบอีเมลของคุณ",
454 + "proceed_on_device": "ดำเนินการบนอุปกรณ์ของคุณ",
455 + "proceed_on_device_description": "โปรดทำตามคำแนะนำที่ได้รับแจ้งไว้ในกระเป๋าเงินฮาร์ดแวร์ของคุณ",
456 "profile": "ประวัติโดยย่อ",
457 "provider_error": "ข้อผิดพลาด ${provider}",
458 "public_key": "คีย์สาธารณะ",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "กู้กระเป๋าของคุณจากรหัสผสมของ 24 คำ",
502 "restore_bitcoin_title_from_keys": "กู้จาก WIF",
503 "restore_description_from_backup": "คุณสามารถกู้แอพ Cake Wallet ทั้งหมดจากไฟล์สำรองข้อมูลของคุณ",
504 + "restore_description_from_hardware_wallet": "กู้คืนจากกระเป๋าเงินฮาร์ดแวร์บัญชีแยกประเภท",
505 "restore_description_from_keys": "กู้กระเป๋าของคุณจากการกดปุ่มที่สร้างขึ้นจาก private keys ของคุณที่บันทึกไว้",
506 "restore_description_from_seed": "กู้กระเป๋าของคุณจากรหัสผสมของ 25 คำหรือ 13 คำ",
507 "restore_description_from_seed_keys": "เรียกกระเป๋าของคุณกลับมาจาก seed/keys ที่คุณได้บันทึกไว้ในที่ปลอดภัย",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "กู้จาก Seed/Keys",
515 "restore_spend_key_private": "คีย์สำหรับใช้ (ส่วนตัว)",
516 "restore_title_from_backup": "กู้จากการสำรองข้อมูล",
517 + "restore_title_from_hardware_wallet": "กู้คืนจากกระเป๋าเงินฮาร์ดแวร์",
518 "restore_title_from_keys": "กู้จาก keys",
519 "restore_title_from_seed": "กู้จาก seed",
520 "restore_title_from_seed_keys": "กู้จาก seed/keys",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "เราไม่สนับสนุนการกระทำนี้สำหรับเนื้อหานี้ โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงินประเภทสินทรัพย์ที่รองรับ",
766 "uptime": "เวลาทำงาน",
767 "upto": "สูงสุด ${value}",
768 + "usb": "ยูเอสบี",
769 "use": "สลับไปที่ ",
770 "use_card_info_three": "ใช้บัตรดิจิตอลออนไลน์หรือผ่านวิธีการชำระเงินแบบไม่ต้องใช้บัตรกระดาษ",
771 "use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
res/values/strings_tl.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Tema ng ilaw ng bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.",
81 "Blocks_remaining": "Ang natitirang ${status} ay natitira",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Maliwanag",
84 "bump_fee": "Bayad sa paga",
85 "buy": "Bilhin",
@@ -144,6 +145,8 @@
145 "congratulations": "Binabati kita!",
146 "connect_an_existing_yat": "Ikonekta ang isang umiiral na yat",
147 "connect_yats": "Ikonekta ang mga yats",
148 + "connect_your_hardware_wallet": "Ikonekta ang iyong wallet ng hardware gamit ang Bluetooth o USB",
149 + "connect_your_hardware_wallet_ios": "Ikonekta ang iyong wallet ng hardware gamit ang Bluetooth",
150 "connection_sync": "Koneksyon at pag -sync",
151 "connectWalletPrompt": "Ikonekta ang iyong wallet sa WalletConnect upang gumawa ng mga transaksyon",
152 "contact": "Makipag -ugnay",
@@ -328,7 +331,13 @@
331 "is_percentage": "ay",
332 "last_30_days": "Huling 30 araw",
333 "learn_more": "Matuto nang higit pa",
334 + "ledger_connection_error": "Nabigong kumonekta sa iyo ledger. Pakisubukang muli.",
335 + "ledger_error_device_locked": "Naka -lock ang ledger",
336 + "ledger_error_tx_rejected_by_user": "Ang transaksyon ay tinanggihan sa aparato",
337 + "ledger_error_wrong_app": "Mangyaring tiyaking pinipili mo ang tamang app sa iyong ledger",
338 + "ledger_please_enable_bluetooth": "Mangyaring paganahin ang Bluetooth upang makita ang iyong ledger",
339 "light_theme": "Ilaw",
340 + "load_more": "Mag -load pa",
341 "loading_your_wallet": "Naglo -load ng iyong pitaka",
342 "login": "Mag log in",
343 "logout": "Mag -logout",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Settings para sa pagsasa-pribado",
452 "private_key": "Pribadong susi",
453 "proceed_after_one_minute": "Kung ang screen ay hindi magpatuloy pagkatapos ng 1 minuto, suriin ang iyong email.",
454 + "proceed_on_device": "Magpatuloy sa iyong aparato",
455 + "proceed_on_device_description": "Mangyaring sundin ang mga tagubilin na sinenyasan sa iyong wallet ng hardware",
456 "profile": "Profile",
457 "provider_error": "${provider} error",
458 "public_key": "Pampublikong susi",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Ibalik ang iyong pitaka mula sa 24 na code ng kombinasyon ng salita",
502 "restore_bitcoin_title_from_keys": "Ibalik mula sa WIF",
503 "restore_description_from_backup": "Maaari mong ibalik ang buong cake wallet app mula sa iyong back-up file",
504 + "restore_description_from_hardware_wallet": "Ibalik mula sa isang ledger hardware wallet",
505 "restore_description_from_keys": "Ibalik ang iyong pitaka mula sa nabuong mga keystroke na na -save mula sa iyong mga pribadong susi",
506 "restore_description_from_seed": "Ibalik ang iyong pitaka mula sa alinman sa 25 salita o 13 na code ng kombinasyon ng salita",
507 "restore_description_from_seed_keys": "Ibalik ang iyong pitaka mula sa mga binhi/susi na na -save mo upang ma -secure ang lugar",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Ibinalik ang mga binhi/susi",
515 "restore_spend_key_private": "Gumastos ng susi (pribado)",
516 "restore_title_from_backup": "Ibalik mula sa backup",
517 + "restore_title_from_hardware_wallet": "Ibalik mula sa pitaka ng hardware",
518 "restore_title_from_keys": "Ibalik mula sa mga susi",
519 "restore_title_from_seed": "Ibalik mula sa binhi",
520 "restore_title_from_seed_keys": "Ibalik mula sa mga binhi/susi",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Hindi namin sinusuportahan ang pagkilos na ito para sa asset na ito. Mangyaring lumikha o lumipat sa isang pitaka ng isang suportadong uri ng pag -aari.",
766 "uptime": "Uptime",
767 "upto": "Hanggang sa ${value}",
768 + "usb": "USB",
769 "use": "Lumipat sa",
770 "use_card_info_three": "Gamitin ang digital card online o sa mga pamamaraan ng pagbabayad na walang contact.",
771 "use_card_info_two": "Ang mga pondo ay na -convert sa USD kapag gaganapin sila sa prepaid account, hindi sa mga digital na pera.",
res/values/strings_tr.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Hafif Tema",
80 "bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.",
81 "Blocks_remaining": "${status} Blok Kaldı",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Parlak",
84 "bump_fee": "Çarpma ücreti",
85 "buy": "Alış",
@@ -144,6 +145,8 @@
145 "congratulations": "Tebrikler!",
146 "connect_an_existing_yat": "Mevcut bir Yat'ı bağla",
147 "connect_yats": "Yat'lara bağlan",
148 + "connect_your_hardware_wallet": "Bluetooth veya USB kullanarak donanım cüzdanınızı bağlayın",
149 + "connect_your_hardware_wallet_ios": "Bluetooth kullanarak donanım cüzdanınızı bağlayın",
150 "connection_sync": "Bağlantı ve senkronizasyon",
151 "connectWalletPrompt": "İşlem yapmak için cüzdanınızı WalletConnect'e bağlayın",
152 "contact": "Rehber",
@@ -328,7 +331,13 @@
331 "is_percentage": "is",
332 "last_30_days": "Son 30 gün",
333 "learn_more": "Daha fazla öğren",
334 + "ledger_connection_error": "Ledger'e bağlanamadı. Lütfen tekrar deneyin.",
335 + "ledger_error_device_locked": "Defter kilitli",
336 + "ledger_error_tx_rejected_by_user": "Cihazda reddedilen işlem",
337 + "ledger_error_wrong_app": "Lütfen defterinizde doğru uygulamayı açtığınızdan emin olun",
338 + "ledger_please_enable_bluetooth": "Defterinizi algılamak için lütfen Bluetooth'u etkinleştirin",
339 "light_theme": "Aydınlık",
340 + "load_more": "Daha fazla yükle",
341 "loading_your_wallet": "Cüzdanın yükleniyor",
342 "login": "Login",
343 "logout": "Çıkış yap",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Gizlilik ayarları",
452 "private_key": "Özel anahtar",
453 "proceed_after_one_minute": "Ekran 1 dakika sonra ilerlemezse, e-postanızı kontrol edin.",
454 + "proceed_on_device": "Cihazınıza devam edin",
455 + "proceed_on_device_description": "Lütfen donanım cüzdanınızda istenen talimatları izleyin",
456 "profile": "Profil",
457 "provider_error": "${provider} hatası",
458 "public_key": "Genel Anahtar",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "Cüzdanınızı 24 kelimelik kombinasyon kodundan geri yükle",
502 "restore_bitcoin_title_from_keys": "WIF'den geri yükle",
503 "restore_description_from_backup": "Yedek dosyandan tüm Cake Wallet uygulamasını geri döndürebilirsin",
504 + "restore_description_from_hardware_wallet": "Bir defter donanım cüzdanından geri yükleyin",
505 "restore_description_from_keys": "Cüzdanınızı özel anahtarlarınızdan kaydedilen oluşturulmuş tuş vuruşlarından geri yükleyin",
506 "restore_description_from_seed": "Cüzdanınızı 25 veya 13 kelimelik kombinasyon kodundan geri döndürün",
507 "restore_description_from_seed_keys": "Güvenli bir yere kaydettiğin tohumdan/anahtarlardan cüzdanını geri döndür",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "Tohumu/Anahtarları Geri Döndür",
515 "restore_spend_key_private": "Harcama anahtarı (Özel)",
516 "restore_title_from_backup": "Yedekten geri döndür",
517 + "restore_title_from_hardware_wallet": "Donanım cüzdanından geri yükleme",
518 "restore_title_from_keys": "Anahtarlardan geri döndür",
519 "restore_title_from_seed": "Tohumdan geri döndür",
520 "restore_title_from_seed_keys": "Tohumdan/anahtarlardan geri döndür",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "Bu öğe için bu eylemi desteklemiyoruz. Lütfen desteklenen bir varlık türünde bir cüzdan oluşturun veya cüzdana geçiş yapın.",
766 "uptime": "Çalışma süresi",
767 "upto": "Şu miktara kadar: ${value}",
768 + "usb": "USB",
769 "use": "Şuna geç: ",
770 "use_card_info_three": "Dijital kartı çevrimiçi olarak veya temassız ödeme yöntemleriyle kullanın.",
771 "use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.",
res/values/strings_uk.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Світла тема Bitcoin",
80 "bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.",
81 "Blocks_remaining": "${status} Залишилось блоків",
82 + "bluetooth": "Блюдот",
83 "bright_theme": "Яскрава",
84 "bump_fee": "Підвищити комісію",
85 "buy": "Купити",
@@ -144,6 +145,8 @@
145 "congratulations": "Вітаємо!",
146 "connect_an_existing_yat": "Підключити існуючий Yat",
147 "connect_yats": "Підключіть Yats",
148 + "connect_your_hardware_wallet": "Підключіть апаратний гаманець за допомогою Bluetooth або USB",
149 + "connect_your_hardware_wallet_ios": "Підключіть апаратний гаманець за допомогою Bluetooth",
150 "connection_sync": "Підключення та синхронізація",
151 "connectWalletPrompt": "Підключіть свій гаманець до WalletConnect, щоб здійснювати транзакції",
152 "contact": "Контакт",
@@ -328,7 +331,13 @@
331 "is_percentage": "є",
332 "last_30_days": "Останні 30 днів",
333 "learn_more": "Дізнатися більше",
334 + "ledger_connection_error": "Не вдалося підключитися до вас. Будь ласка спробуйте ще раз.",
335 + "ledger_error_device_locked": "Книга заблокована",
336 + "ledger_error_tx_rejected_by_user": "Транзакція відхилена на пристрої",
337 + "ledger_error_wrong_app": "Будь ласка, переконайтеся, що ви відкриваєте потрібну програму на своїй книзі",
338 + "ledger_please_enable_bluetooth": "Будь ласка, ввімкніть Bluetooth виявити свою книгу",
339 "light_theme": "Світла",
340 + "load_more": "Завантажити ще",
341 "loading_your_wallet": "Завантаження гаманця",
342 "login": "Логін",
343 "logout": "Вийти",
@@ -442,6 +451,8 @@
451 "privacy_settings": "Налаштування конфіденційності",
452 "private_key": "Приватний ключ",
453 "proceed_after_one_minute": "Якщо екран не продовжується через 1 хвилину, перевірте свою електронну пошту.",
454 + "proceed_on_device": "Продовжуйте свій пристрій",
455 + "proceed_on_device_description": "Будь ласка, дотримуйтесь інструкцій, підказаних на вашому апаратному гаманці",
456 "profile": "Профіль",
457 "provider_error": "${provider} помилка",
458 "public_key": "Публічний ключ",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "Ви можете відновити гаманець використовуючи 24-ти слівну мнемонічну фразу",
503 "restore_bitcoin_title_from_keys": "Відновити за допомогою WIF",
504 "restore_description_from_backup": "Ви можете відновити Cake Wallet з вашого резервного файлу",
505 + "restore_description_from_hardware_wallet": "Відновлення з апаратного гаманця",
506 "restore_description_from_keys": "Ви можете відновити гаманець за допомогою приватних ключів",
507 "restore_description_from_seed": "Ви можете відновити гаманець використовуючи 25-ти слівну мнемонічну фразу",
508 "restore_description_from_seed_keys": "Ви можете відновити гаманець з мнемонічної фрази/ключів, які ви зберегли раніше",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "Відновити за допомогою мнемонічної фрази/ключів",
516 "restore_spend_key_private": "Приватний ключ витрати",
517 "restore_title_from_backup": "Відновити із резервного файлу",
518 + "restore_title_from_hardware_wallet": "Відновити з апаратного гаманця",
519 "restore_title_from_keys": "Відновити за допомогою ключів",
520 "restore_title_from_seed": "Відновити з мнемонічної фрази",
521 "restore_title_from_seed_keys": "Відновити з мнемонічної фрази/ключів",
@@ -753,6 +766,7 @@
766 "unsupported_asset": "Ми не підтримуємо цю дію для цього ресурсу. Створіть або перейдіть на гаманець підтримуваного типу активів.",
767 "uptime": "Час роботи",
768 "upto": "до ${value}",
769 + "usb": "USB",
770 "use": "Використати ",
771 "use_card_info_three": "Використовуйте цифрову картку онлайн або за допомогою безконтактних методів оплати.",
772 "use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
res/values/strings_ur.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "بٹ کوائن لائٹ تھیم",
80 "bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔",
81 "Blocks_remaining": "${status} بلاکس باقی ہیں۔",
82 + "bluetooth": "بلوٹوتھ",
83 "bright_theme": "روشن",
84 "bump_fee": "بمپ فیس",
85 "buy": "خریدنے",
@@ -144,6 +145,8 @@
145 "congratulations": "مبارک ہو!",
146 "connect_an_existing_yat": "ایک موجودہ Yat کو جوڑیں۔",
147 "connect_yats": "Yats کو جوڑیں۔",
148 + "connect_your_hardware_wallet": "بلوٹوتھ یا USB کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو مربوط کریں",
149 + "connect_your_hardware_wallet_ios": "بلوٹوتھ کا استعمال کرتے ہوئے اپنے ہارڈ ویئر پرس کو جوڑیں",
150 "connection_sync": "کنکشن اور مطابقت پذیری",
151 "connectWalletPrompt": "۔ﮟﯾﮌﻮﺟ ﮫﺗﺎﺳ ﮯﮐ WalletConnect ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻦﯾﺩ ﻦﯿﻟ",
152 "contact": "رابطہ کریں۔",
@@ -328,7 +331,13 @@
331 "is_percentage": "ہے",
332 "last_30_days": "آخری 30 دن",
333 "learn_more": "اورجانیے",
334 + "ledger_connection_error": "آپ سے لیجر سے رابطہ قائم کرنے میں ناکام۔ دوبارہ کوشش کریں.",
335 + "ledger_error_device_locked": "لیجر لاک ہے",
336 + "ledger_error_tx_rejected_by_user": "آلہ پر لین دین کو مسترد کردیا گیا",
337 + "ledger_error_wrong_app": "براہ کرم یقینی بنائیں کہ آپ اپنے لیجر پر صحیح ایپ کو کھولتے ہیں",
338 + "ledger_please_enable_bluetooth": "براہ کرم بلوٹوتھ کو اپنے لیجر کا پتہ لگانے کے لئے اہل بنائیں",
339 "light_theme": "روشنی",
340 + "load_more": "مزید لوڈ کریں",
341 "loading_your_wallet": "آپ کا بٹوہ لوڈ ہو رہا ہے۔",
342 "login": "لاگ ان کریں",
343 "logout": "لاگ آوٹ",
@@ -444,6 +453,8 @@
453 "privacy_settings": "رازداری کی ترتیبات",
454 "private_key": "نجی کلید",
455 "proceed_after_one_minute": "اگر اسکرین 1 منٹ کے بعد آگے نہیں بڑھتی ہے تو اپنا ای میل چیک کریں۔",
456 + "proceed_on_device": "اپنے آلے پر آگے بڑھیں",
457 + "proceed_on_device_description": "براہ کرم اپنے ہارڈ ویئر پرس پر آنے والی ہدایات پر عمل کریں",
458 "profile": "پروفائل",
459 "provider_error": "${provider} خرابی۔",
460 "public_key": "عوامی کلید",
@@ -492,6 +503,7 @@
503 "restore_bitcoin_description_from_seed": "24 الفاظ کے مجموعہ کوڈ سے اپنے بٹوے کو بحال کریں۔",
504 "restore_bitcoin_title_from_keys": "WIF سے بحال کریں۔",
505 "restore_description_from_backup": "آپ اپنی بیک اپ فائل سے پوری کیک والیٹ ایپ کو بحال کر سکتے ہیں۔",
506 + "restore_description_from_hardware_wallet": "لیجر ہارڈ ویئر پرس سے بحال کریں",
507 "restore_description_from_keys": "اپنے بٹوے کو اپنی نجی کلیدوں سے محفوظ کردہ کی اسٹروکس سے بحال کریں۔",
508 "restore_description_from_seed": "اپنے بٹوے کو 25 لفظ یا 13 الفاظ کے مجموعہ کوڈ سے بحال کریں۔",
509 "restore_description_from_seed_keys": "اپنے بٹوے کو بیج / چابیاں سے واپس حاصل کریں جنہیں آپ نے محفوظ جگہ پر محفوظ کیا ہے۔",
@@ -504,6 +516,7 @@
516 "restore_seed_keys_restore": "بیج/کیز کی بحالی",
517 "restore_spend_key_private": "خرچ کی کلید (نجی)",
518 "restore_title_from_backup": "بیک اپ سے بحال کریں۔",
519 + "restore_title_from_hardware_wallet": "ہارڈ ویئر پرس سے بحال کریں",
520 "restore_title_from_keys": "چابیاں سے بحال کریں۔",
521 "restore_title_from_seed": "بیج سے بحال کریں۔",
522 "restore_title_from_seed_keys": "بیج / چابیاں سے بحال کریں۔",
@@ -754,6 +767,7 @@
767 "unsupported_asset": "۔ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﺱﺍ ﺎﯾ ﮟﯿﺋﺎﻨﺑ ﺱﺮﭘ ﺎﮐ ﻢﺴﻗ ﯽﮐ ﮧﺛﺎﺛﺍ ﮧﺘﻓﺎﯾ ﻥﻭﺎﻌﺗ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮟﯿﮨ ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﺖﯾﺎﻤﺣ ﯽﮐ ﯽﺋﺍﻭﺭﺭﺎﮐ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﮧﺛﺎﺛﺍ ﺱﺍ ﻢﮨ",
768 "uptime": "اپ ٹائم",
769 "upto": "${value} تک",
770 + "usb": "یو ایس بی",
771 "use": "تبدیل کرنا",
772 "use_card_info_three": "ڈیجیٹل کارڈ آن لائن یا کنٹیکٹ لیس ادائیگی کے طریقوں کے ساتھ استعمال کریں۔",
773 "use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
res/values/strings_yo.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "Bitcoin Light Akori",
80 "bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.",
81 "Blocks_remaining": "Àkójọpọ̀ ${status} kikù",
82 + "bluetooth": "Bluetooth",
83 "bright_theme": "Funfun",
84 "bump_fee": "Ọya ija",
85 "buy": "Rà",
@@ -144,6 +145,8 @@
145 "congratulations": "Ẹ kúuṣẹ́ ooo!",
146 "connect_an_existing_yat": "So Yat wíwà",
147 "connect_yats": "So àwọn Yat",
148 + "connect_your_hardware_wallet": "So apamọwọ irinṣẹ rẹ nipa lilo Bluetooth tabi USB",
149 + "connect_your_hardware_wallet_ios": "So apamọwọ ẹrọ rẹ ni lilo Bluetooth",
150 "connection_sync": "Ìkànpọ̀ àti ìbádọ́gba",
151 "connectWalletPrompt": "So apamọwọ rẹ pọ pẹlu WalletConnect lati ṣe awọn iṣowo",
152 "contact": "Olùbásọ̀rọ̀",
@@ -329,7 +332,13 @@
332 "is_percentage": "jẹ́",
333 "last_30_days": "Ọ̀jọ̀ mọ́gbọ̀n tó kọjà",
334 "learn_more": "Túbọ̀ kọ́",
335 + "ledger_connection_error": "O kuna lati sopọ mọ ọ. Jọwọ gbiyanju lẹẹkansi.",
336 + "ledger_error_device_locked": "Ledger wa ni titiipa",
337 + "ledger_error_tx_rejected_by_user": "Idunadura kọ lori ẹrọ",
338 + "ledger_error_wrong_app": "Jọwọ rii daju pe iwọ yoo sọ app ti o tọ loju omi rẹ",
339 + "ledger_please_enable_bluetooth": "Jọwọ jẹ ki Bluetooth lati rii iṣupọ rẹ",
340 "light_theme": "Funfun bí eérú",
341 + "load_more": "Ẹru diẹ sii",
342 "loading_your_wallet": "A ń ṣí àpamọ́wọ́ yín",
343 "login": "Orúkọ",
344 "logout": "Jáde",
@@ -443,6 +452,8 @@
452 "privacy_settings": "Ààtò àdáni",
453 "private_key": "Kọ́kọ́rọ́ àdáni",
454 "proceed_after_one_minute": "Tí aṣàfihàn kò bá tẹ̀síwájú l'áàárín ìṣẹ́jú kan, ẹ tọ́ ímeèlì yín wò.",
455 + "proceed_on_device": "Tẹsiwaju lori ẹrọ rẹ",
456 + "proceed_on_device_description": "Jọwọ tẹle awọn ilana ti a ṣe lori apamọwọ ohun elo rẹ",
457 "profile": "profaili",
458 "provider_error": "Àṣìṣe ${provider}",
459 "public_key": "Kọ́kọ́rọ́ tó kò àdáni",
@@ -491,6 +502,7 @@
502 "restore_bitcoin_description_from_seed": "Mú àpamọ́wọ́ yín padà láti àkànpọ̀ ọlọ́rọ̀ ẹ̀ẹ̀mẹrinlélógun",
503 "restore_bitcoin_title_from_keys": "Mú padà láti WIF",
504 "restore_description_from_backup": "Ẹ lè fi ẹ̀dà nípamọ́ yín mú odindi Cake Wallet áàpù padà.",
505 + "restore_description_from_hardware_wallet": "Mu pada kuro ninu apamọwọ ohun elo ti o yanilenu",
506 "restore_description_from_keys": "Mú àpamọ́wọ́ yín padà láti àwọn àtẹ̀ nípamọ́ láti àwọn kọ́kọ́rọ́ àdáni yín",
507 "restore_description_from_seed": "Ẹ mú àpamọ́wọ́ yín padà láti àkànpọ̀ ọlọ́rọ̀ ẹ̀ẹ̀marùndínlọgbọ̀n tàbí ti mẹ́talá.",
508 "restore_description_from_seed_keys": "Mú àpamọ́wọ́ yín padà láti hóró/kọ́kọ́rọ́ t'ẹ́ ti pamọ́ sí ibi láìléwu",
@@ -503,6 +515,7 @@
515 "restore_seed_keys_restore": "Mú hóró/kọ́kọ́rọ́ padà",
516 "restore_spend_key_private": "kọ́kọ́rọ́ àdáni fún níná",
517 "restore_title_from_backup": "Fi ẹ̀dà nípamọ́ mú padà",
518 + "restore_title_from_hardware_wallet": "Mu pada kuro ninu apamọwọ ohun elo",
519 "restore_title_from_keys": "Fi kọ́kọ́rọ́ ṣẹ̀dá",
520 "restore_title_from_seed": "Fi hóró mú padà",
521 "restore_title_from_seed_keys": "Fi hóró/kọ́kọ́rọ́ mú padà",
@@ -753,6 +766,7 @@
766 "unsupported_asset": "A ko ṣe atilẹyin iṣẹ yii fun dukia yii. Jọwọ ṣẹda tabi yipada si apamọwọ iru dukia atilẹyin.",
767 "uptime": "Iduro",
768 "upto": "kò tóbi ju ${value}",
769 + "usb": "USB",
770 "use": "Lo",
771 "use_card_info_three": "Ẹ lo káàdí ayélujára lórí wẹ́ẹ̀bù tàbí ẹ lò ó lórí àwọn ẹ̀rọ̀ ìrajà tíwọn kò kò.",
772 "use_card_info_two": "A pààrọ̀ owó sí owó Amẹ́ríkà tó bá wà nínú àkanti t'á ti fikún tẹ́lẹ̀tẹ́lẹ̀. A kò kó owó náà nínú owó ayélujára.",
res/values/strings_zh.arb
+14
@@ -79,6 +79,7 @@
79 "bitcoin_light_theme": "比特币浅色主题",
80 "bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。",
81 "Blocks_remaining": "${status} 剩余的块",
82 + "bluetooth": "蓝牙",
83 "bright_theme": "明亮",
84 "bump_fee": "撞费",
85 "buy": "购买",
@@ -144,6 +145,8 @@
145 "congratulations": "恭喜!",
146 "connect_an_existing_yat": "連接現有 Yat",
147 "connect_yats": "连接 Yats",
148 + "connect_your_hardware_wallet": "使用蓝牙或USB连接硬件钱包",
149 + "connect_your_hardware_wallet_ios": "使用蓝牙连接硬件钱包",
150 "connection_sync": "连接和同步",
151 "connectWalletPrompt": "将您的钱包与 WalletConnect 连接以进行交易",
152 "contact": "联系",
@@ -328,7 +331,13 @@
331 "is_percentage": "是",
332 "last_30_days": "过去 30 天",
333 "learn_more": "了解更多",
334 + "ledger_connection_error": "无法连接到您的分类帐。请再试一次。",
335 + "ledger_error_device_locked": "分类帐已锁定",
336 + "ledger_error_tx_rejected_by_user": "交易在设备上拒绝",
337 + "ledger_error_wrong_app": "请确保您在分类帐中操作正确的应用程序",
338 + "ledger_please_enable_bluetooth": "请启用蓝牙来检测您的分类帐",
339 "light_theme": "艳丽",
340 + "load_more": "装载更多",
341 "loading_your_wallet": "加载您的钱包",
342 "login": "登录",
343 "logout": "注销",
@@ -442,6 +451,8 @@
451 "privacy_settings": "隐私设置",
452 "private_key": "私钥",
453 "proceed_after_one_minute": "如果屏幕在 1 分钟后没有继续,请检查您的电子邮件。",
454 + "proceed_on_device": "在设备上继续",
455 + "proceed_on_device_description": "请按照您的硬件钱包上提示的说明进行操作",
456 "profile": "轮廓",
457 "provider_error": "${provider} 错误",
458 "public_key": "公钥",
@@ -490,6 +501,7 @@
501 "restore_bitcoin_description_from_seed": "从24个文字的组成码恢复您的钱包",
502 "restore_bitcoin_title_from_keys": "从WIF还原",
503 "restore_description_from_backup": "您可以从还原整个Cake Wallet应用您的备份文件",
504 + "restore_description_from_hardware_wallet": "从分类帐硬件钱包还原",
505 "restore_description_from_keys": "使用私钥恢复钱包",
506 "restore_description_from_seed": "从25个字中恢复您的钱包或13个字的组合码",
507 "restore_description_from_seed_keys": "从保存到安全地方的种子/钥匙取回钱包",
@@ -502,6 +514,7 @@
514 "restore_seed_keys_restore": "种子/密钥还原",
515 "restore_spend_key_private": "Spend私钥",
516 "restore_title_from_backup": "从备份文件还原",
517 + "restore_title_from_hardware_wallet": "从硬件钱包还原",
518 "restore_title_from_keys": "从密钥还原",
519 "restore_title_from_seed": "从种子还原",
520 "restore_title_from_seed_keys": "从种子/密钥还原",
@@ -752,6 +765,7 @@
765 "unsupported_asset": "我们不支持针对该资产采取此操作。请创建或切换到支持的资产类型的钱包。",
766 "uptime": "正常运行时间",
767 "upto": "最高 ${value}",
768 + "usb": "USB",
769 "use": "切换使用",
770 "use_card_info_three": "在线使用电子卡或使用非接触式支付方式。",
771 "use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
tool/configure.dart
+32 -2
@@ -68,9 +68,13 @@ Future<void> generateBitcoin(bool hasImplementation) async {
68 const bitcoinCommonHeaders = """
69 import 'dart:typed_data';
70 import 'package:cw_core/node.dart';
71 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
72 +import 'package:cake_wallet/view_model/send/output.dart';
73 +import 'package:cw_core/hardware/hardware_account_data.dart';
74 import 'package:cw_core/pending_transaction.dart';
75 import 'package:cw_core/receive_page_option.dart';
76 import 'package:cw_core/unspent_transaction_output.dart';
77 +import 'package:cw_core/wallet_base.dart';
78 import 'package:cw_core/wallet_credentials.dart';
79 import 'package:cw_core/wallet_info.dart';
80 import 'package:cw_core/transaction_priority.dart';
@@ -78,8 +82,8 @@ import 'package:cw_core/output_info.dart';
82 import 'package:cw_core/unspent_coins_info.dart';
83 import 'package:cw_core/wallet_service.dart';
84 import 'package:cw_core/wallet_type.dart';
81 -import 'package:cake_wallet/view_model/send/output.dart';
85 import 'package:hive/hive.dart';
86 +import 'package:ledger_flutter/ledger_flutter.dart';
87 import 'package:bitcoin_base/bitcoin_base.dart';
88 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc;
89 import 'package:bip32/bip32.dart' as bip32;
@@ -93,6 +97,7 @@ import 'package:cw_bitcoin/electrum_derivations.dart';
97 import 'package:cw_bitcoin/electrum.dart';
98 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
99 import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
100 +import 'package:cw_bitcoin/bitcoin_wallet.dart';
101 import 'package:cw_bitcoin/electrum_wallet.dart';
102 import 'package:cw_bitcoin/bitcoin_unspent.dart';
103 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
@@ -105,6 +110,7 @@ import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
110 import 'package:cw_bitcoin/litecoin_wallet_service.dart';
111 import 'package:cw_bitcoin/script_hash.dart';
112 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
113 +import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart';
114 import 'package:mobx/mobx.dart';
115 """;
116 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
@@ -139,6 +145,7 @@ abstract class Bitcoin {
145 });
146 WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo});
147 WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo});
148 + WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo});
149 List<String> getWordList();
150 Map<String, String> getWalletKeys(Object wallet);
151 List<TransactionPriority> getTransactionPriorities();
@@ -187,6 +194,9 @@ abstract class Bitcoin {
194 int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size});
195 int getEstimatedFeeWithFeeRate(Object wallet, int feeRate, int? amount, {int? outputsCount, int? size});
196 int getMaxCustomFeeRate(Object wallet);
197 +
198 + void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device);
199 + Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
200 }
201 """;
202
@@ -568,9 +578,11 @@ abstract class HavenAccountList {
578 Future<void> generateEthereum(bool hasImplementation) async {
579 final outputFile = File(ethereumOutputPath);
580 const ethereumCommonHeaders = """
581 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
582 import 'package:cake_wallet/view_model/send/output.dart';
583 import 'package:cw_core/crypto_currency.dart';
584 import 'package:cw_core/erc20_token.dart';
585 +import 'package:cw_core/hardware/hardware_account_data.dart';
586 import 'package:cw_core/output_info.dart';
587 import 'package:cw_core/transaction_info.dart';
588 import 'package:cw_core/transaction_priority.dart';
@@ -579,6 +591,7 @@ import 'package:cw_core/wallet_credentials.dart';
591 import 'package:cw_core/wallet_info.dart';
592 import 'package:cw_core/wallet_service.dart';
593 import 'package:hive/hive.dart';
594 +import 'package:ledger_flutter/ledger_flutter.dart';
595 import 'package:web3dart/web3dart.dart';
596
597 """;
@@ -589,6 +602,9 @@ import 'package:cw_evm/evm_chain_transaction_credentials.dart';
602 import 'package:cw_evm/evm_chain_transaction_info.dart';
603 import 'package:cw_evm/evm_chain_transaction_priority.dart';
604 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
605 +import 'package:cw_evm/evm_chain_hardware_wallet_service.dart';
606 +import 'package:cw_evm/evm_ledger_credentials.dart';
607 +import 'package:cw_evm/evm_chain_wallet.dart';
608
609 import 'package:cw_ethereum/ethereum_client.dart';
610 import 'package:cw_ethereum/ethereum_wallet.dart';
@@ -605,6 +621,7 @@ abstract class Ethereum {
621 WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo});
622 WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password});
623 WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
624 + WalletCredentials createEthereumHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
625 String getAddress(WalletBase wallet);
626 String getPrivateKey(WalletBase wallet);
627 String getPublicKey(WalletBase wallet);
@@ -638,6 +655,9 @@ abstract class Ethereum {
655 void updateEtherscanUsageState(WalletBase wallet, bool isEnabled);
656 Web3Client? getWeb3Client(WalletBase wallet);
657 String getTokenAddress(CryptoCurrency asset);
658 +
659 + void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device);
660 + Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
661 }
662 """;
663
@@ -661,9 +681,11 @@ abstract class Ethereum {
681 Future<void> generatePolygon(bool hasImplementation) async {
682 final outputFile = File(polygonOutputPath);
683 const polygonCommonHeaders = """
684 +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
685 import 'package:cake_wallet/view_model/send/output.dart';
686 import 'package:cw_core/crypto_currency.dart';
687 import 'package:cw_core/erc20_token.dart';
688 +import 'package:cw_core/hardware/hardware_account_data.dart';
689 import 'package:cw_core/output_info.dart';
690 import 'package:cw_core/transaction_info.dart';
691 import 'package:cw_core/transaction_priority.dart';
@@ -672,16 +694,20 @@ import 'package:cw_core/wallet_credentials.dart';
694 import 'package:cw_core/wallet_info.dart';
695 import 'package:cw_core/wallet_service.dart';
696 import 'package:hive/hive.dart';
697 +import 'package:ledger_flutter/ledger_flutter.dart';
698 import 'package:web3dart/web3dart.dart';
699
700 """;
701 const polygonCWHeaders = """
702 import 'package:cw_evm/evm_chain_formatter.dart';
703 import 'package:cw_evm/evm_chain_mnemonics.dart';
704 +import 'package:cw_evm/evm_chain_transaction_credentials.dart';
705 import 'package:cw_evm/evm_chain_transaction_info.dart';
706 import 'package:cw_evm/evm_chain_transaction_priority.dart';
683 -import 'package:cw_evm/evm_chain_transaction_credentials.dart';
707 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
708 +import 'package:cw_evm/evm_chain_hardware_wallet_service.dart';
709 +import 'package:cw_evm/evm_ledger_credentials.dart';
710 +import 'package:cw_evm/evm_chain_wallet.dart';
711
712 import 'package:cw_polygon/polygon_client.dart';
713 import 'package:cw_polygon/polygon_wallet.dart';
@@ -698,6 +724,7 @@ abstract class Polygon {
724 WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo});
725 WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password});
726 WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
727 + WalletCredentials createPolygonHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
728 String getAddress(WalletBase wallet);
729 String getPrivateKey(WalletBase wallet);
730 String getPublicKey(WalletBase wallet);
@@ -731,6 +758,9 @@ abstract class Polygon {
758 void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled);
759 Web3Client? getWeb3Client(WalletBase wallet);
760 String getTokenAddress(CryptoCurrency asset);
761 +
762 + void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device);
763 + Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
764 }
765 """;
766