Ledger monero fix (#1834)

* Fix sending for monero ledger * Ignore no tx keys found error * re-add Monero to Ledger enabled wallets * Fix No Element Exception on requireHardwareWalletConnection check * Fix Showing connection screen again * Maybe fix Race condition * fix namespace * Maybe fix Race condition and add missing pop * Minor fixes * Minor fixes * Fix minor localization * Fix minor localization * Add Text prompt if device is not showing after 10 seconds. --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>

Konstantin Ullrich committed Dec 14, 2024 at 00:32 UTC ae758756d8e2f870dd43f2740234c432770f2de5
41 files changed +288 -91
android/app/build.gradle
+1 -1
@@ -99,4 +99,4 @@ configurations {
99 implementation.exclude module:'proto-google-common-protos'
100 implementation.exclude module:'protolite-well-known-types'
101 implementation.exclude module:'protobuf-javalite'
102 -}
\ No newline at end of file
102 +}
cw_core/lib/hardware/device_connection_type.dart
+1 -1
@@ -7,7 +7,7 @@ enum DeviceConnectionType {
7 static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
8 [bool isIOS = false]) {
9 switch (walletType) {
10 - // case WalletType.monero:
10 + case WalletType.monero:
11 case WalletType.bitcoin:
12 case WalletType.litecoin:
13 case WalletType.ethereum:
cw_monero/lib/api/transaction_history.dart
+10 -3
@@ -200,9 +200,16 @@ String? commitTransactionFromPointerAddress({required int address, required bool
200 commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
201
202 String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
203 + final transactionPointerAddress = transactionPointer.address;
204 final txCommit = useUR
204 - ? monero.PendingTransaction_commitUR(transactionPointer, 120)
205 - : monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
205 + ? monero.PendingTransaction_commitUR(transactionPointer, 120)
206 + : Isolate.run(() {
207 + monero.PendingTransaction_commit(
208 + Pointer.fromAddress(transactionPointerAddress),
209 + filename: '',
210 + overwrite: false,
211 + );
212 + });
213
214 String? error = (() {
215 final status = monero.PendingTransaction_status(transactionPointer.cast());
@@ -221,7 +228,7 @@ String? commitTransaction({required monero.PendingTransaction transactionPointer
228 })();
229
230 }
224 - if (error != null) {
231 + if (error != null && error != "no tx keys found for this txid") {
232 throw CreationTransactionException(message: error);
233 }
234 if (useUR) {
cw_monero/lib/ledger.dart
+67 -3
@@ -2,11 +2,12 @@ import 'dart:async';
2 import 'dart:ffi';
3 import 'dart:typed_data';
4
5 +import 'package:collection/collection.dart';
6 +import 'package:cw_core/utils/print_verbose.dart';
7 import 'package:ffi/ffi.dart';
8 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
9 import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
10 import 'package:monero/monero.dart' as monero;
9 -// import 'package:polyseed/polyseed.dart';
11
12 LedgerConnection? gLedger;
13
@@ -28,9 +29,16 @@ void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
29 ptr, emptyPointer.cast<UnsignedChar>(), 0);
30 malloc.free(emptyPointer);
31
31 - // printV("> ${ledgerRequest.toHexString()}");
32 + _logLedgerCommand(ledgerRequest, false);
33 final response = await exchange(connection, ledgerRequest);
33 - // printV("< ${response.toHexString()}");
34 + _logLedgerCommand(response, true);
35 +
36 + if (ListEquality().equals(response, [0x55, 0x15])) {
37 + await connection.disconnect();
38 + // // TODO: Show POPUP pls unlock your device
39 + // await Future.delayed(Duration(seconds: 15));
40 + // response = await exchange(connection, ledgerRequest);
41 + }
42
43 final Pointer<Uint8> result = malloc<Uint8>(response.length);
44 for (var i = 0; i < response.length; i++) {
@@ -82,3 +90,59 @@ class ExchangeOperation extends LedgerRawOperation<Uint8List> {
90 @override
91 Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData];
92 }
93 +
94 +const _ledgerMoneroCommands = {
95 + 0x00: "INS_NONE",
96 + 0x02: "INS_RESET",
97 + 0x20: "INS_GET_KEY",
98 + 0x21: "INS_DISPLAY_ADDRESS",
99 + 0x22: "INS_PUT_KEY",
100 + 0x24: "INS_GET_CHACHA8_PREKEY",
101 + 0x26: "INS_VERIFY_KEY",
102 + 0x28: "INS_MANAGE_SEEDWORDS",
103 + 0x30: "INS_SECRET_KEY_TO_PUBLIC_KEY",
104 + 0x32: "INS_GEN_KEY_DERIVATION",
105 + 0x34: "INS_DERIVATION_TO_SCALAR",
106 + 0x36: "INS_DERIVE_PUBLIC_KEY",
107 + 0x38: "INS_DERIVE_SECRET_KEY",
108 + 0x3A: "INS_GEN_KEY_IMAGE",
109 + 0x3B: "INS_DERIVE_VIEW_TAG",
110 + 0x3C: "INS_SECRET_KEY_ADD",
111 + 0x3E: "INS_SECRET_KEY_SUB",
112 + 0x40: "INS_GENERATE_KEYPAIR",
113 + 0x42: "INS_SECRET_SCAL_MUL_KEY",
114 + 0x44: "INS_SECRET_SCAL_MUL_BASE",
115 + 0x46: "INS_DERIVE_SUBADDRESS_PUBLIC_KEY",
116 + 0x48: "INS_GET_SUBADDRESS",
117 + 0x4A: "INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY",
118 + 0x4C: "INS_GET_SUBADDRESS_SECRET_KEY",
119 + 0x70: "INS_OPEN_TX",
120 + 0x72: "INS_SET_SIGNATURE_MODE",
121 + 0x74: "INS_GET_ADDITIONAL_KEY",
122 + 0x76: "INS_STEALTH",
123 + 0x77: "INS_GEN_COMMITMENT_MASK",
124 + 0x78: "INS_BLIND",
125 + 0x7A: "INS_UNBLIND",
126 + 0x7B: "INS_GEN_TXOUT_KEYS",
127 + 0x7D: "INS_PREFIX_HASH",
128 + 0x7C: "INS_VALIDATE",
129 + 0x7E: "INS_MLSAG",
130 + 0x7F: "INS_CLSAG",
131 + 0x80: "INS_CLOSE_TX",
132 + 0xA0: "INS_GET_TX_PROOF",
133 + 0xC0: "INS_GET_RESPONSE"
134 +};
135 +
136 +void _logLedgerCommand(Uint8List command, [bool isResponse = true]) {
137 + String toHexString(Uint8List data) =>
138 + data.map((e) => e.toRadixString(16).padLeft(2, '0')).join();
139 +
140 +
141 +
142 + if (isResponse) {
143 + printV("< ${toHexString(command)}");
144 + } else {
145 + printV(
146 + "> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}");
147 + }
148 +}
cw_monero/lib/monero_wallet_service.dart
+26 -30
@@ -1,5 +1,7 @@
1 import 'dart:ffi';
2 import 'dart:io';
3 +
4 +import 'package:cw_core/get_height_by_date.dart';
5 import 'package:cw_core/monero_wallet_utils.dart';
6 import 'package:cw_core/pathForWallet.dart';
7 import 'package:cw_core/unspent_coins_info.dart';
@@ -9,16 +11,16 @@ import 'package:cw_core/wallet_credentials.dart';
11 import 'package:cw_core/wallet_info.dart';
12 import 'package:cw_core/wallet_service.dart';
13 import 'package:cw_core/wallet_type.dart';
12 -import 'package:cw_core/get_height_by_date.dart';
14 import 'package:cw_monero/api/account_list.dart';
15 import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
16 import 'package:cw_monero/api/wallet_manager.dart';
17 import 'package:cw_monero/ledger.dart';
18 import 'package:cw_monero/monero_wallet.dart';
19 +import 'package:collection/collection.dart';
20 import 'package:hive/hive.dart';
21 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
20 -import 'package:polyseed/polyseed.dart';
22 import 'package:monero/monero.dart' as monero;
23 +import 'package:polyseed/polyseed.dart';
24
25 class MoneroNewWalletCredentials extends WalletCredentials {
26 MoneroNewWalletCredentials(
@@ -133,14 +135,12 @@ class MoneroWalletService extends WalletService<
135 try {
136 final path = await pathForWallet(name: name, type: getType());
137
136 - if (walletFilesExist(path)) {
137 - await repairOldAndroidWallet(name);
138 - }
138 + if (walletFilesExist(path)) await repairOldAndroidWallet(name);
139
140 await monero_wallet_manager
141 .openWalletAsync({'path': path, 'password': password});
142 - final walletInfo = walletInfoSource.values.firstWhere(
143 - (info) => info.id == WalletBase.idFor(name, getType()));
142 + final walletInfo = walletInfoSource.values
143 + .firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
144 final wallet = MoneroWallet(
145 walletInfo: walletInfo,
146 unspentCoinsInfo: unspentCoinsInfoSource,
@@ -204,7 +204,7 @@ class MoneroWalletService extends WalletService<
204 @override
205 Future<void> rename(String currentName, String password, String newName) async {
206 final currentWalletInfo = walletInfoSource.values.firstWhere(
207 - (info) => info.id == WalletBase.idFor(currentName, getType()));
207 + (info) => info.id == WalletBase.idFor(currentName, getType()));
208 final currentWallet = MoneroWallet(
209 walletInfo: currentWalletInfo,
210 unspentCoinsInfo: unspentCoinsInfoSource,
@@ -255,14 +255,14 @@ class MoneroWalletService extends WalletService<
255 final password = credentials.password;
256 final height = credentials.height;
257
258 - if (wptr == null ) monero_wallet_manager.createWalletPointer();
258 + if (wptr == null) monero_wallet_manager.createWalletPointer();
259
260 enableLedgerExchange(wptr!, credentials.ledgerConnection);
261 await monero_wallet_manager.restoreWalletFromHardwareWallet(
262 - path: path,
263 - password: password!,
264 - restoreHeight: height!,
265 - deviceName: 'Ledger');
262 + path: path,
263 + password: password!,
264 + restoreHeight: height!,
265 + deviceName: 'Ledger');
266
267 final wallet = MoneroWallet(
268 walletInfo: credentials.walletInfo!,
@@ -279,7 +279,8 @@ class MoneroWalletService extends WalletService<
279 }
280
281 @override
282 - Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
282 + Future<MoneroWallet> restoreFromSeed(
283 + MoneroRestoreWalletFromSeedCredentials credentials,
284 {bool? isTestnet}) async {
285 // Restore from Polyseed
286 if (Polyseed.isValidSeed(credentials.mnemonic)) {
@@ -313,7 +314,8 @@ class MoneroWalletService extends WalletService<
314 final path = await pathForWallet(name: credentials.name, type: getType());
315 final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
316 final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
316 - final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
317 + final polyseed =
318 + Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
319
320 return _restoreFromPolyseed(
321 path, credentials.password!, polyseed, credentials.walletInfo!, lang);
@@ -355,24 +357,18 @@ class MoneroWalletService extends WalletService<
357
358 Future<void> repairOldAndroidWallet(String name) async {
359 try {
358 - if (!Platform.isAndroid) {
359 - return;
360 - }
360 + if (!Platform.isAndroid) return;
361
362 final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
363 final dir = Directory(oldAndroidWalletDirPath);
364
365 - if (!dir.existsSync()) {
366 - return;
367 - }
365 + if (!dir.existsSync()) return;
366
367 final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
368
369 dir.listSync().forEach((f) {
370 final file = File(f.path);
373 - final name = f.path
374 - .split('/')
375 - .last;
371 + final name = f.path.split('/').last;
372 final newPath = newWalletDirPath + '/$name';
373 final newFile = File(newPath);
374
@@ -391,9 +387,7 @@ class MoneroWalletService extends WalletService<
387 try {
388 final path = await pathForWallet(name: name, type: getType());
389
394 - if (walletFilesExist(path)) {
395 - await repairOldAndroidWallet(name);
396 - }
390 + if (walletFilesExist(path)) await repairOldAndroidWallet(name);
391
392 await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
393 final walletInfo = walletInfoSource.values
@@ -412,8 +406,10 @@ class MoneroWalletService extends WalletService<
406
407 @override
408 bool requireHardwareWalletConnection(String name) {
415 - final walletInfo = walletInfoSource.values
416 - .firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
417 - return walletInfo.isHardwareWallet;
409 + return walletInfoSource.values
410 + .firstWhereOrNull(
411 + (info) => info.id == WalletBase.idFor(name, getType()))
412 + ?.isHardwareWallet ??
413 + false;
414 }
415 }
lib/reactions/on_authentication_state_change.dart
+14 -11
@@ -44,13 +44,16 @@ void startAuthenticationStateChange(
44 } catch (error, stack) {
45 loginError = error;
46 await ExceptionHandler.resetLastPopupDate();
47 - await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
47 + await ExceptionHandler.onError(
48 + FlutterErrorDetails(exception: error, stack: stack));
49 }
50 return;
51 }
52
52 - if (state == AuthenticationState.allowed) {
53 - if (requireHardwareWalletConnection()) {
53 + if ([AuthenticationState.allowed, AuthenticationState.allowedCreate]
54 + .contains(state)) {
55 + if (state == AuthenticationState.allowed &&
56 + requireHardwareWalletConnection()) {
57 await navigatorKey.currentState!.pushNamedAndRemoveUntil(
58 Routes.connectDevices,
59 (route) => false,
@@ -58,14 +61,14 @@ void startAuthenticationStateChange(
61 walletType: WalletType.monero,
62 onConnectDevice: (context, ledgerVM) async {
63 monero!.setGlobalLedgerConnection(ledgerVM.connection);
61 - showPopUp<void>(
62 - context: context,
63 - builder: (BuildContext context) => AlertWithOneAction(
64 - alertTitle: S.of(context).proceed_on_device,
65 - alertContent: S.of(context).proceed_on_device_description,
66 - buttonText: S.of(context).cancel,
67 - buttonAction: () => Navigator.of(context).pop()),
68 - );
64 + showPopUp<void>(
65 + context: context,
66 + builder: (BuildContext context) => AlertWithOneAction(
67 + alertTitle: S.of(context).proceed_on_device,
68 + alertContent: S.of(context).proceed_on_device_description,
69 + buttonText: S.of(context).cancel,
70 + buttonAction: () => Navigator.of(context).pop()),
71 + );
72 await loadCurrentWallet();
73 getIt.get<BottomSheetService>().resetCurrentSheet();
74 await navigatorKey.currentState!
lib/src/screens/connect_device/connect_device_page.dart
+52 -19
@@ -22,11 +22,13 @@ class ConnectDevicePageParams {
22 final WalletType walletType;
23 final OnConnectDevice onConnectDevice;
24 final bool allowChangeWallet;
25 + final bool isReconnect;
26
27 ConnectDevicePageParams({
28 required this.walletType,
29 required this.onConnectDevice,
30 this.allowChangeWallet = false,
31 + this.isReconnect = false,
32 });
33 }
34
@@ -34,19 +36,33 @@ class ConnectDevicePage extends BasePage {
36 final WalletType walletType;
37 final OnConnectDevice onConnectDevice;
38 final bool allowChangeWallet;
39 + final bool isReconnect;
40 final LedgerViewModel ledgerVM;
41
42 ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
43 : walletType = params.walletType,
44 onConnectDevice = params.onConnectDevice,
42 - allowChangeWallet = params.allowChangeWallet;
45 + allowChangeWallet = params.allowChangeWallet,
46 + isReconnect = params.isReconnect;
47
48 @override
45 - String get title => S.current.restore_title_from_hardware_wallet;
49 + String get title => isReconnect
50 + ? S.current.reconnect_your_hardware_wallet
51 + : S.current.restore_title_from_hardware_wallet;
52
53 @override
48 - Widget body(BuildContext context) => ConnectDevicePageBody(
49 - walletType, onConnectDevice, allowChangeWallet, ledgerVM);
54 + Widget? leading(BuildContext context) =>
55 + !isReconnect ? super.leading(context) : null;
56 +
57 + @override
58 + Widget body(BuildContext context) => PopScope(
59 + canPop: !isReconnect,
60 + child: ConnectDevicePageBody(
61 + walletType,
62 + onConnectDevice,
63 + allowChangeWallet,
64 + ledgerVM,
65 + ));
66 }
67
68 class ConnectDevicePageBody extends StatefulWidget {
@@ -75,6 +91,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
91 late Timer? _bleStateTimer = null;
92 late StreamSubscription<LedgerDevice>? _bleRefresh = null;
93
94 + bool longWait = false;
95 +
96 @override
97 void initState() {
98 super.initState();
@@ -89,6 +107,11 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
107 _usbRefreshTimer =
108 Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
109 }
110 +
111 + Future.delayed(Duration(seconds: 10), () {
112 + if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
113 + setState(() => longWait = true);
114 + });
115 });
116 }
117
@@ -98,6 +121,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
121 _bleStateTimer?.cancel();
122 _usbRefreshTimer?.cancel();
123 _bleRefresh?.cancel();
124 +
125 + widget.ledgerVM.stopScanning();
126 super.dispose();
127 }
128
@@ -118,12 +143,14 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
143 Future<void> _refreshBleDevices() async {
144 try {
145 if (widget.ledgerVM.bleIsEnabled) {
121 - _bleRefresh = widget.ledgerVM
122 - .scanForBleDevices()
123 - .listen((device) => setState(() => bleDevices.add(device)))
124 - ..onError((e) {
125 - throw e.toString();
126 - });
146 + _bleRefresh =
147 + widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
148 + bleDevices.add(device);
149 + if (longWait) longWait = false;
150 + }))
151 + ..onError((e) {
152 + throw e.toString();
153 + });
154 _bleRefreshTimer?.cancel();
155 _bleRefreshTimer = null;
156 }
@@ -175,15 +202,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
202 textAlign: TextAlign.center,
203 ),
204 ),
178 - // DeviceTile(
179 - // onPressed: () => Navigator.of(context).push(
180 - // MaterialPageRoute<void>(
181 - // builder: (BuildContext context) => DebugDevicePage(),
182 - // ),
183 - // ),
184 - // title: "Debug Ledger",
185 - // leading: imageLedger,
186 - // ),
205 + Offstage(
206 + offstage: !longWait,
207 + child: Padding(
208 + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
209 + child: Text(S.of(context).if_you_dont_see_your_device,
210 + style: TextStyle(
211 + fontSize: 16,
212 + fontWeight: FontWeight.w500,
213 + color: Theme.of(context)
214 + .extension<CakeTextTheme>()!
215 + .titleColor),
216 + textAlign: TextAlign.center,
217 + ),
218 + ),
219 + ),
220 Observer(
221 builder: (_) => Offstage(
222 offstage: widget.ledgerVM.bleIsEnabled,
lib/src/screens/restore/restore_options_page.dart
+2 -3
@@ -56,9 +56,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
56 }
57
58 if (isMoneroOnly) {
59 - // return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
60 - // .isNotEmpty;
61 - return false;
59 + return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
60 + .isNotEmpty;
61 }
62
63 return true;
lib/src/screens/send/send_page.dart
+4 -1
@@ -580,7 +580,10 @@ class SendPage extends BasePage {
580 alertTitle: S.of(context).proceed_on_device,
581 alertContent: S.of(context).proceed_on_device_description,
582 buttonText: S.of(context).cancel,
583 - buttonAction: () => Navigator.of(context).pop());
583 + buttonAction: () {
584 + sendViewModel.state = InitialExecutionState();
585 + Navigator.of(context).pop();
586 + });
587 });
588 });
589 }
lib/src/screens/wallet_list/wallet_list_page.dart
+6 -4
@@ -422,8 +422,9 @@ class WalletListBodyState extends State<WalletListBody> {
422 if (!isAuthenticatedSuccessfully) return;
423
424 try {
425 - if (widget.walletListViewModel
426 - .requireHardwareWalletConnection(wallet)) {
425 + final requireHardwareWalletConnection = widget.walletListViewModel
426 + .requireHardwareWalletConnection(wallet);
427 + if (requireHardwareWalletConnection) {
428 await Navigator.of(context).pushNamed(
429 Routes.connectDevices,
430 arguments: ConnectDevicePageParams(
@@ -445,8 +446,6 @@ class WalletListBodyState extends State<WalletListBody> {
446 );
447 }
448
448 -
449 -
449 changeProcessText(
450 S.of(context).wallet_list_loading_wallet(wallet.name));
451 await widget.walletListViewModel.loadWallet(wallet);
@@ -456,6 +455,9 @@ class WalletListBodyState extends State<WalletListBody> {
455 if (responsiveLayoutUtil.shouldRenderMobileUI) {
456 WidgetsBinding.instance.addPostFrameCallback((_) {
457 if (this.mounted) {
458 + if (requireHardwareWalletConnection) {
459 + Navigator.of(context).pop();
460 + }
461 widget.onWalletLoaded.call(context);
462 }
463 });
lib/store/authentication_store.dart
+7 -1
@@ -4,7 +4,7 @@ part 'authentication_store.g.dart';
4
5 class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore;
6
7 -enum AuthenticationState { uninitialized, installed, allowed, _reset }
7 +enum AuthenticationState { uninitialized, installed, allowed, allowedCreate, _reset }
8
9 abstract class AuthenticationStoreBase with Store {
10 AuthenticationStoreBase() : state = AuthenticationState.uninitialized;
@@ -23,4 +23,10 @@ abstract class AuthenticationStoreBase with Store {
23 state = AuthenticationState._reset;
24 state = AuthenticationState.allowed;
25 }
26 +
27 + @action
28 + void allowedCreate() {
29 + state = AuthenticationState._reset;
30 + state = AuthenticationState.allowedCreate;
31 + }
32 }
lib/view_model/hardware_wallet/ledger_view_model.dart
+39 -10
@@ -4,14 +4,18 @@ import 'dart:io';
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/generated/i18n.dart';
7 +import 'package:cake_wallet/main.dart';
8 import 'package:cake_wallet/monero/monero.dart';
9 import 'package:cake_wallet/polygon/polygon.dart';
10 +import 'package:cake_wallet/routes.dart';
11 +import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
12 import 'package:cake_wallet/utils/device_info.dart';
13 import 'package:cake_wallet/wallet_type_utils.dart';
14 import 'package:cw_core/hardware/device_connection_type.dart';
15 import 'package:cw_core/utils/print_verbose.dart';
16 import 'package:cw_core/wallet_base.dart';
17 import 'package:cw_core/wallet_type.dart';
18 +import 'package:flutter/widgets.dart';
19
20 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
21 import 'package:mobx/mobx.dart';
@@ -59,15 +63,18 @@ abstract class LedgerViewModelBase with Store {
63 bool _bleIsInitialized = false;
64 Future<void> _initBLE() async {
65 if (bleIsEnabled && !_bleIsInitialized) {
62 - ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async {
63 - Map<Permission, PermissionStatus> statuses = await [
64 - Permission.bluetoothScan,
65 - Permission.bluetoothConnect,
66 - Permission.bluetoothAdvertise,
67 - ].request();
68 -
69 - return statuses.values.where((status) => status.isDenied).isEmpty;
70 - });
66 + ledgerPlusBLE = sdk.LedgerInterface.ble(
67 + onPermissionRequest: (_) async {
68 + Map<Permission, PermissionStatus> statuses = await [
69 + Permission.bluetoothScan,
70 + Permission.bluetoothConnect,
71 + Permission.bluetoothAdvertise,
72 + ].request();
73 +
74 + return statuses.values.where((status) => status.isDenied).isEmpty;
75 + },
76 + bleOptions:
77 + sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)));
78 _bleIsInitialized = true;
79 }
80 }
@@ -84,16 +91,26 @@ abstract class LedgerViewModelBase with Store {
91
92 Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
93
94 + Future<void> stopScanning() async {
95 + await ledgerPlusBLE.stopScanning();
96 + if (!Platform.isIOS) {
97 + await ledgerPlusUSB.stopScanning();
98 + }
99 + }
100 +
101 Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
102 if (isConnected) {
103 try {
90 - await _connection!.disconnect();
104 + await _connectionChangeListener?.cancel();
105 + _connectionChangeListener = null;
106 + await _connection!.disconnect().catchError((_) {});
107 } catch (_) {}
108 }
109 final ledger = device.connectionType == sdk.ConnectionType.ble
110 ? ledgerPlusBLE
111 : ledgerPlusUSB;
112
113 +
114 if (_connectionChangeListener == null) {
115 _connectionChangeListener = ledger.deviceStateChanges.listen((event) {
116 printV('Ledger Device State Changed: $event');
@@ -101,6 +118,18 @@ abstract class LedgerViewModelBase with Store {
118 _connection = null;
119 if (type == WalletType.monero) {
120 monero!.resetLedgerConnection();
121 +
122 + Navigator.of( navigatorKey.currentContext!).pushNamed(
123 + Routes.connectDevices,
124 + arguments: ConnectDevicePageParams(
125 + walletType: WalletType.monero,
126 + allowChangeWallet: true,
127 + isReconnect: true,
128 + onConnectDevice: (context, ledgerVM) async {
129 + Navigator.of(context).pop();
130 + },
131 + ),
132 + );
133 }
134 }
135 });
lib/view_model/wallet_creation_vm.dart
+1 -2
@@ -8,7 +8,6 @@ import 'package:cake_wallet/generated/i18n.dart';
8 import 'package:cake_wallet/nano/nano.dart';
9 import 'package:cake_wallet/store/app_store.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
11 -import 'package:cake_wallet/view_model/restore/restore_mode.dart';
11 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
12 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
13 import 'package:cw_core/pathForWallet.dart';
@@ -114,7 +113,7 @@ abstract class WalletCreationVMBase with Store {
113 await _walletInfoSource.add(walletInfo);
114 await _appStore.changeCurrentWallet(wallet);
115 getIt.get<BackgroundTasks>().registerSyncTask();
117 - _appStore.authenticationStore.allowed();
116 + _appStore.authenticationStore.allowedCreate();
117 state = ExecutedSuccessfullyState();
118 } catch (e, s) {
119 printV("error: $e");
res/values/strings_ar.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": " ﻞﻤﻌﺘﺴﺗ ﻒﻴﻛ",
356 "how_to_use_card": "كيفية استخدام هذه البطاقة",
357 "id": "رقم المعرف:",
358 + "if_you_dont_see_your_device": "إذا كنت لا ترى جهازك أعلاه ، فيرجى التأكد من أن دفتر الأستاذ الخاص بك مستيقظًا ومؤمنًا!",
359 "ignor": "تجاهل",
360 "import": "ﺩﺭﻮﺘﺴﻳ",
361 "importNFTs": "NFTs ﺩﺍﺮﻴﺘﺳﺍ",
@@ -538,6 +539,7 @@
539 "recipient_address": "عنوان المستلم",
540 "reconnect": "أعد الاتصال",
541 "reconnect_alert_text": "هل أنت متأكد من رغبتك في إعادة الاتصال؟",
542 + "reconnect_your_hardware_wallet": "أعد توصيل محفظة الأجهزة الخاصة بك",
543 "reconnection": "إعادة الاتصال",
544 "red_dark_theme": "موضوع الظلام الأحمر",
545 "red_light_theme": "موضوع الضوء الأحمر",
res/values/strings_bg.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Как да използвам",
356 "how_to_use_card": "Как се ползва тази карта",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Ако не виждате устройството си по -горе, моля, уверете се, че вашата книга е будна и отключена!",
359 "ignor": "Игнориране",
360 "import": "Импортиране",
361 "importNFTs": "Импортирайте NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "Адрес на получател",
540 "reconnect": "Reconnect",
541 "reconnect_alert_text": "Сигурни ли сте, че искате да се свържете отново?",
542 + "reconnect_your_hardware_wallet": "Свържете отново хардуерния си портфейл",
543 "reconnection": "Свързване отново",
544 "red_dark_theme": "Червена тъмна тема",
545 "red_light_theme": "Тема на червената светлина",
res/values/strings_cs.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Jak používat",
356 "how_to_use_card": "Jak použít tuto kartu",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Pokud vaše zařízení nevidíte výše, ujistěte se, že vaše kniha je vzhůru a odemknutá!",
359 "ignor": "Ignorovat",
360 "import": "Import",
361 "importNFTs": "Importujte NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "Adresa příjemce",
540 "reconnect": "Znovu připojit",
541 "reconnect_alert_text": "Opravdu se chcete znovu připojit?",
542 + "reconnect_your_hardware_wallet": "Znovu připojte svou hardwarovou peněženku",
543 "reconnection": "Znovu připojit",
544 "red_dark_theme": "Červené temné téma",
545 "red_light_theme": "Téma červeného světla",
res/values/strings_de.arb
+3 -1
@@ -355,6 +355,7 @@
355 "how_to_use": "Wie benutzt man",
356 "how_to_use_card": "Wie man diese Karte benutzt",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Wenn Sie Ihr Gerät nicht sehen, stellen Sie bitte sicher, dass Ihr Ledger an und entsperrt ist!",
359 "ignor": "Ignorieren",
360 "import": "Importieren",
361 "importNFTs": "NFTs importieren",
@@ -539,6 +540,7 @@
540 "recipient_address": "Empfängeradresse",
541 "reconnect": "Erneut verbinden",
542 "reconnect_alert_text": "Sind Sie sicher, dass Sie sich neu verbinden möchten?",
543 + "reconnect_your_hardware_wallet": "Hardware-Wallet neu verbinden",
544 "reconnection": "Neu verbinden",
545 "red_dark_theme": "Red Dark Thema",
546 "red_light_theme": "Red Light Thema",
@@ -973,4 +975,4 @@
975 "you_will_get": "Konvertieren zu",
976 "you_will_send": "Konvertieren von",
977 "yy": "YY"
976 -}
\ No newline at end of file
978 +}
res/values/strings_en.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "How to use",
356 "how_to_use_card": "How to use this card",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "If you don't see your device above, please be sure your Ledger is awake and unlocked!",
359 "ignor": "Ignore",
360 "import": "Import",
361 "importNFTs": "Import NFTs",
@@ -538,6 +539,7 @@
539 "recipient_address": "Recipient address",
540 "reconnect": "Reconnect",
541 "reconnect_alert_text": "Are you sure you want to reconnect?",
542 + "reconnect_your_hardware_wallet": "Reconnect your Hardware Wallet",
543 "reconnection": "Reconnection",
544 "red_dark_theme": "Red Dark Theme",
545 "red_light_theme": "Red Light Theme",
res/values/strings_es.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Cómo utilizar",
356 "how_to_use_card": "Cómo usar esta tarjeta",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Si no ve su dispositivo arriba, ¡asegúrese de que su libro mayor esté despierto y desbloqueado!",
359 "ignor": "Pasar por alto",
360 "import": "Importar",
361 "importNFTs": "Importar NFT",
@@ -539,6 +540,7 @@
540 "recipient_address": "Dirección del receptor",
541 "reconnect": "Volver a conectar",
542 "reconnect_alert_text": "¿Estás seguro de reconectar?",
543 + "reconnect_your_hardware_wallet": "Vuelva a conectar su billetera de hardware",
544 "reconnection": "Reconexión",
545 "red_dark_theme": "Tema rojo oscuro",
546 "red_light_theme": "Tema de la luz roja",
res/values/strings_fr.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Comment utiliser",
356 "how_to_use_card": "Comment utiliser cette carte",
357 "id": "ID : ",
358 + "if_you_dont_see_your_device": "Si vous ne voyez pas votre appareil ci-dessus, assurez-vous que votre grand livre est éveillé et déverrouillé!",
359 "ignor": "Ignorer",
360 "import": "Importer",
361 "importNFTs": "Importer des NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "Adresse bénéficiaire",
540 "reconnect": "Reconnecter",
541 "reconnect_alert_text": "Êtes vous certain de vouloir vous reconnecter ?",
542 + "reconnect_your_hardware_wallet": "Reconnectez votre portefeuille matériel",
543 "reconnection": "Reconnexion",
544 "red_dark_theme": "Thème rouge sombre",
545 "red_light_theme": "Thème rouge clair",
res/values/strings_ha.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Yadda ake amfani da shi",
356 "how_to_use_card": "Yadda ake amfani da wannan kati",
357 "id": "ID:",
358 + "if_you_dont_see_your_device": "Idan baku ga na'urarka da ke sama ba, da fatan za a tabbata Ledger dinku yana farkawa kuma a buɗe!",
359 "ignor": "Yi watsi da shi",
360 "import": "Shigo da",
361 "importNFTs": "Shigo da NFTs",
@@ -540,6 +541,7 @@
541 "recipient_address": "Adireshin mai karɓa",
542 "reconnect": "Sake haɗawa",
543 "reconnect_alert_text": "Shin kun tabbata kuna son sake haɗawa?",
544 + "reconnect_your_hardware_wallet": "Sake kunnawa kayan aiki",
545 "reconnection": "Sake haɗawa",
546 "red_dark_theme": "Ja duhu taken",
547 "red_light_theme": "Ja mai haske",
res/values/strings_hi.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "का उपयोग कैसे करें",
356 "how_to_use_card": "इस कार्ड का उपयोग कैसे करें",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "यदि आप अपने डिवाइस को ऊपर नहीं देखते हैं, तो कृपया सुनिश्चित करें कि आपका लेजर जागृत और अनलॉक हो गया है!",
359 "ignor": "नज़रअंदाज़ करना",
360 "import": "आयात",
361 "importNFTs": "एनएफटी आयात करें",
@@ -540,6 +541,7 @@
541 "recipient_address": "प्राप्तकर्ता का पता",
542 "reconnect": "रिकनेक्ट",
543 "reconnect_alert_text": "क्या आप पुन: कनेक्ट होना सुनिश्चित करते हैं?",
544 + "reconnect_your_hardware_wallet": "अपने हार्डवेयर वॉलेट को फिर से कनेक्ट करें",
545 "reconnection": "पुनर्संयोजन",
546 "red_dark_theme": "लाल डार्क थीम",
547 "red_light_theme": "लाल प्रकाश थीम",
res/values/strings_hr.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Kako koristiti",
356 "how_to_use_card": "Kako koristiti ovu karticu",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Ako svoj uređaj ne vidite gore, budite sigurni da je vaša knjiga budna i otključana!",
359 "ignor": "Zanemariti",
360 "import": "Uvoz",
361 "importNFTs": "Uvoz NFT-ova",
@@ -538,6 +539,7 @@
539 "recipient_address": "Primateljeva adresa",
540 "reconnect": "Ponovno povezivanje",
541 "reconnect_alert_text": "Jeste li sigurni da se želite ponovno povezati?",
542 + "reconnect_your_hardware_wallet": "Ponovno spojite svoj hardverski novčanik",
543 "reconnection": "Ponovno povezivanje",
544 "red_dark_theme": "Crvena tamna tema",
545 "red_light_theme": "Tema crvenog svjetla",
res/values/strings_hy.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Ինչպես օգտագործել",
356 "how_to_use_card": "Ինչպես օգտագործել այս քարտը",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Եթե ​​ձեր սարքը վերեւում չեք տեսնում, համոզվեք, որ ձեր Ledger- ը արթուն է եւ բացված:",
359 "ignor": "Անտեսել",
360 "import": "Ներմուծել",
361 "importNFTs": "Ներմուծել NFT-ներ",
@@ -538,6 +539,7 @@
539 "recipient_address": "Ստացողի հասցե",
540 "reconnect": "Վերակապվել",
541 "reconnect_alert_text": "Դուք վստահ եք, որ ուզում եք վերակապվել?",
542 + "reconnect_your_hardware_wallet": "Միացրեք ձեր ապարատային դրամապանակը",
543 "reconnection": "Վերակապում",
544 "red_dark_theme": "Կարմիր մութ տեսք",
545 "red_light_theme": "Կարմիր պայծառ տեսք",
res/values/strings_id.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Cara Penggunaan",
356 "how_to_use_card": "Bagaimana menggunakan kartu ini",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Jika Anda tidak melihat perangkat Anda di atas, pastikan buku besar Anda terjaga dan tidak terkunci!",
359 "ignor": "Abaikan",
360 "import": "Impor",
361 "importNFTs": "Impor NFT",
@@ -540,6 +541,7 @@
541 "recipient_address": "Alamat penerima",
542 "reconnect": "Sambungkan kembali",
543 "reconnect_alert_text": "Apakah Anda yakin ingin menyambungkan kembali?",
544 + "reconnect_your_hardware_wallet": "Hubungkan kembali dompet perangkat keras Anda",
545 "reconnection": "Koneksi kembali",
546 "red_dark_theme": "Tema gelap merah",
547 "red_light_theme": "Tema lampu merah",
res/values/strings_it.arb
+2
@@ -356,6 +356,7 @@
356 "how_to_use": "Come usare",
357 "how_to_use_card": "Come usare questa carta",
358 "id": "ID: ",
359 + "if_you_dont_see_your_device": "Se non vedi il tuo dispositivo sopra, assicurati che il tuo libro mastro sia sveglio e sbloccato!",
360 "ignor": "Ignorare",
361 "import": "Importare",
362 "importNFTs": "Importa NFT",
@@ -540,6 +541,7 @@
541 "recipient_address": "Indirizzo di destinazione",
542 "reconnect": "Riconnetti",
543 "reconnect_alert_text": "Sei sicuro di volerti riconnettere?",
544 + "reconnect_your_hardware_wallet": "Ricollega il tuo portafoglio hardware",
545 "reconnection": "Riconnessione",
546 "red_dark_theme": "Red Dark Theme",
547 "red_light_theme": "Tema della luce rossa",
res/values/strings_ja.arb
+2
@@ -356,6 +356,7 @@
356 "how_to_use": "使い方",
357 "how_to_use_card": "このカードの使用方法",
358 "id": "ID: ",
359 + "if_you_dont_see_your_device": "上記のデバイスが表示されない場合は、元帳が目を覚ましてロック解除されていることを確認してください!",
360 "ignor": "無視",
361 "import": "輸入",
362 "importNFTs": "NFTのインポート",
@@ -539,6 +540,7 @@
540 "recipient_address": "受信者のアドレス",
541 "reconnect": "再接続",
542 "reconnect_alert_text": "再接続しますか?",
543 + "reconnect_your_hardware_wallet": "ハードウェアウォレットを再接続します",
544 "reconnection": "再接続",
545 "red_dark_theme": "赤い暗いテーマ",
546 "red_light_theme": "赤色光のテーマ",
res/values/strings_ko.arb
+3 -1
@@ -355,6 +355,7 @@
355 "how_to_use": "사용하는 방법",
356 "how_to_use_card": "이 카드를 사용하는 방법",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "위의 장치가 표시되지 않으면 원장이 깨어 있고 잠금 해제되었는지 확인하십시오!",
359 "ignor": "무시하다",
360 "import": "수입",
361 "importNFTs": "NFT 가져오기",
@@ -502,8 +503,8 @@
503 "placeholder_transactions": "거래가 여기에 표시됩니다",
504 "please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.",
505 "please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.",
505 - "please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
506 "Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
507 + "please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
508 "please_select": "선택 해주세요:",
509 "please_select_backup_file": "백업 파일을 선택하고 백업 암호를 입력하십시오.",
510 "please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오",
@@ -539,6 +540,7 @@
540 "recipient_address": "받는 사람 주소",
541 "reconnect": "다시 연결",
542 "reconnect_alert_text": "다시 연결 하시겠습니까?",
543 + "reconnect_your_hardware_wallet": "하드웨어 지갑을 다시 연결하십시오",
544 "reconnection": "재 연결",
545 "red_dark_theme": "빨간 어두운 테마",
546 "red_light_theme": "빨간불 테마",
res/values/strings_my.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "အသုံးပြုနည်း",
356 "how_to_use_card": "ဒီကတ်ကို ဘယ်လိုသုံးမလဲ။",
357 "id": "ID:",
358 + "if_you_dont_see_your_device": "သင်၏စက်ကိုအထက်တွင်မတွေ့ပါကသင်၏ Ledger သည်နိုးလာပြီးသော့ဖွင့်နေသည်ကိုသေချာပါစေ။",
359 "ignor": "လျစ်လျူရှုပါ။",
360 "import": "သွင်းကုန်",
361 "importNFTs": "NFTs များကို တင်သွင်းပါ။",
@@ -538,6 +539,7 @@
539 "recipient_address": "လက်ခံသူလိပ်စာ",
540 "reconnect": "ပြန်လည်ချိတ်ဆက်ပါ။",
541 "reconnect_alert_text": "ပြန်လည်ချိတ်ဆက်လိုသည်မှာ သေချာပါသလား။ ?",
542 + "reconnect_your_hardware_wallet": "သင့်ရဲ့ hardware ပိုက်ဆံအိတ်ကိုပြန်လည်ချိတ်ဆက်ပါ",
543 "reconnection": "ပြန်လည်ချိတ်ဆက်မှု",
544 "red_dark_theme": "အနီရောင်မှောင်မိုက်ဆောင်ပုဒ်",
545 "red_light_theme": "အနီရောင်အလင်းအကြောင်းအရာ",
res/values/strings_nl.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Hoe te gebruiken",
356 "how_to_use_card": "Hoe deze kaart te gebruiken",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Als u uw apparaat hierboven niet ziet, zorg er dan voor dat uw grootboek wakker is en ontgrendeld is!",
359 "ignor": "Negeren",
360 "import": "Importeren",
361 "importNFTs": "NFT's importeren",
@@ -538,6 +539,7 @@
539 "recipient_address": "Adres ontvanger",
540 "reconnect": "Sluit",
541 "reconnect_alert_text": "Weet u zeker dat u opnieuw verbinding wilt maken?",
542 + "reconnect_your_hardware_wallet": "Sluit uw hardware -portemonnee opnieuw aan",
543 "reconnection": "Reconnection",
544 "red_dark_theme": "Rood donker thema",
545 "red_light_theme": "Rood licht thema",
res/values/strings_pl.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Jak używać",
356 "how_to_use_card": "Jak korzystać z tej karty?",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Jeśli nie widzisz swojego urządzenia powyżej, upewnij się, że Twoja księga nie śpi i odblokowana!",
359 "ignor": "Ignorować",
360 "import": "Import",
361 "importNFTs": "Importuj NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "Adres odbiorcy",
540 "reconnect": "Połącz ponownie",
541 "reconnect_alert_text": "Czy na pewno ponownie się ponownie połączysz?",
542 + "reconnect_your_hardware_wallet": "Ponownie podłącz portfel sprzętowy",
543 "reconnection": "Ponowne łączenie",
544 "red_dark_theme": "Czerwony Mroczny motyw",
545 "red_light_theme": "Motyw czerwony światło",
res/values/strings_pt.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Como usar",
356 "how_to_use_card": "Como usar este cartão",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Se você não vê seu dispositivo acima, certifique -se de que seu livro esteja acordado e desbloqueado!",
359 "ignor": "Ignorar",
360 "import": "Importar",
361 "importNFTs": "Importar NFTs",
@@ -540,6 +541,7 @@
541 "recipient_address": "Endereço do destinatário",
542 "reconnect": "Reconectar",
543 "reconnect_alert_text": "Você tem certeza de que deseja reconectar?",
544 + "reconnect_your_hardware_wallet": "Reconecte sua carteira de hardware",
545 "reconnection": "Reconectar",
546 "red_dark_theme": "Tema escuro vermelho",
547 "red_light_theme": "Tema da luz vermelha",
res/values/strings_ru.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Как использовать",
356 "how_to_use_card": "Как использовать эту карту",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Если вы не видите свое устройство выше, пожалуйста, убедитесь, что ваша бухгалтерская книга бодрствует и разблокирована!",
359 "ignor": "Игнорировать",
360 "import": "Импортировать",
361 "importNFTs": "Импортировать NFT",
@@ -539,6 +540,7 @@
540 "recipient_address": "Адрес получателя",
541 "reconnect": "Переподключиться",
542 "reconnect_alert_text": "Вы хотите переподключиться?",
543 + "reconnect_your_hardware_wallet": "Воссоедините свой аппаратный кошелек",
544 "reconnection": "Переподключение",
545 "red_dark_theme": "Красная темная тема",
546 "red_light_theme": "Тема красного света",
res/values/strings_th.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "วิธีใช้",
356 "how_to_use_card": "วิธีใช้บัตรนี้",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "หากคุณไม่เห็นอุปกรณ์ของคุณด้านบนโปรดตรวจสอบให้แน่ใจว่าบัญชีแยกประเภทของคุณตื่นและปลดล็อค!",
359 "ignor": "ละเว้น",
360 "import": "นำเข้า",
361 "importNFTs": "นำเข้า NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "ที่อยู่ผู้รับ",
540 "reconnect": "เชื่อมต่อใหม่",
541 "reconnect_alert_text": "คุณแน่ใจหรือไม่ว่าต้องการเชื่อมต่อใหม่?",
542 + "reconnect_your_hardware_wallet": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณอีกครั้ง",
543 "reconnection": "เชื่อมต่อใหม่",
544 "red_dark_theme": "ธีมสีแดงเข้ม",
545 "red_light_theme": "ธีมแสงสีแดง",
res/values/strings_tl.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Paano gamitin",
356 "how_to_use_card": "Paano gamitin ang card na ito",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Kung hindi mo nakikita ang iyong aparato sa itaas, siguraduhin na ang iyong ledger ay gising at naka -lock!",
359 "ignor": "Huwag pansinin",
360 "import": "Mag-import",
361 "importNFTs": "Mag-import ng mga NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "Address ng tatanggap",
540 "reconnect": "Kumonekta muli",
541 "reconnect_alert_text": "Sigurado ka bang gusto mong kumonekta uli?",
542 + "reconnect_your_hardware_wallet": "Ikonekta muli ang iyong wallet ng hardware",
543 "reconnection": "Muling pagkakakonekta",
544 "red_dark_theme": "Red Dark Theme",
545 "red_light_theme": "Red Light Theme",
res/values/strings_tr.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Nasıl kullanılır",
356 "how_to_use_card": "Bu kart nasıl kullanılır",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Cihazınızı yukarıda görmüyorsanız, lütfen defterinizin uyanık olduğundan ve kilidinin açıldığından emin olun!",
359 "ignor": "Yoksay",
360 "import": "İçe aktarmak",
361 "importNFTs": "NFT'leri içe aktar",
@@ -538,6 +539,7 @@
539 "recipient_address": "Alıcı adresi",
540 "reconnect": "Yeniden Bağlan",
541 "reconnect_alert_text": "Yeniden bağlanmak istediğinden emin misin?",
542 + "reconnect_your_hardware_wallet": "Donanım cüzdanınızı yeniden bağlayın",
543 "reconnection": "Yeniden bağlantı",
544 "red_dark_theme": "Kırmızı Karanlık Tema",
545 "red_light_theme": "Kırmızı Işık Teması",
res/values/strings_uk.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "Як використовувати",
356 "how_to_use_card": "Як використовувати цю картку",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "Якщо ви не бачите свого пристрою вище, будь ласка, переконайтеся, що ваша книга прокинеться і розблокована!",
359 "ignor": "Ігнорувати",
360 "import": "Імпорт",
361 "importNFTs": "Імпорт NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "Адреса одержувача",
540 "reconnect": "Перепідключитися",
541 "reconnect_alert_text": "Ви хочете перепідключитися?",
542 + "reconnect_your_hardware_wallet": "Повторно підключіть свій апаратний гаманець",
543 "reconnection": "Перепідключення",
544 "red_dark_theme": "Червона темна тема",
545 "red_light_theme": "Тема червоного світла",
res/values/strings_ur.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": " ﮧﻘﯾﺮﻃ ﺎﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ",
356 "how_to_use_card": "اس کارڈ کو استعمال کرنے کا طریقہ",
357 "id": "ID:",
358 + "if_you_dont_see_your_device": "اگر آپ اوپر اپنا آلہ نہیں دیکھتے ہیں تو ، براہ کرم یقینی بنائیں کہ آپ کا لیجر بیدار اور غیر مقفل ہے!",
359 "ignor": "نظر انداز کرنا",
360 "import": " ۔ﮟﯾﺮﮐ ﺪﻣﺁﺭﺩ",
361 "importNFTs": "NFTs ۔ﮟﯾﺮﮐ ﺪﻣﺁﺭﺩ",
@@ -540,6 +541,7 @@
541 "recipient_address": "وصول کنندہ کا پتہ",
542 "reconnect": "دوبارہ جڑیں۔",
543 "reconnect_alert_text": "کیا آپ واقعی دوبارہ جڑنا چاہتے ہیں؟",
544 + "reconnect_your_hardware_wallet": "اپنے ہارڈ ویئر پرس کو دوبارہ مربوط کریں",
545 "reconnection": "دوبارہ رابطہ",
546 "red_dark_theme": "ریڈ ڈارک تھیم",
547 "red_light_theme": "ریڈ لائٹ تھیم",
res/values/strings_vi.arb
+2
@@ -354,6 +354,7 @@
354 "how_to_use": "Cách sử dụng",
355 "how_to_use_card": "Cách sử dụng thẻ này",
356 "id": "ID: ",
357 + "if_you_dont_see_your_device": "Nếu bạn không thấy thiết bị của mình ở trên, xin hãy chắc chắn rằng sổ cái của bạn đã tỉnh táo và mở khóa!",
358 "ignor": "Bỏ qua",
359 "import": "Nhập",
360 "importNFTs": "Nhập NFT",
@@ -537,6 +538,7 @@
538 "recipient_address": "Địa chỉ người nhận",
539 "reconnect": "Kết nối lại",
540 "reconnect_alert_text": "Bạn có chắc chắn muốn kết nối lại không?",
541 + "reconnect_your_hardware_wallet": "Kết nối lại ví phần cứng của bạn",
542 "reconnection": "Kết nối lại",
543 "red_dark_theme": "Chủ đề tối đỏ",
544 "red_light_theme": "Chủ đề sáng đỏ",
res/values/strings_yo.arb
+2
@@ -356,6 +356,7 @@
356 "how_to_use": "Bawo ni lati lo",
357 "how_to_use_card": "Báyìí ni wọ́n ṣe ń lo káàdì yìí.",
358 "id": "Àmì Ìdánimọ̀: ",
359 + "if_you_dont_see_your_device": "Ti o ko ba ri ẹrọ rẹ loke, jọwọ rii daju pe a le jiji rẹ ati ṣiṣi!",
360 "ignor": "Ṣàìfiyèsí",
361 "import": "gbe wọle",
362 "importNFTs": "Gbe awọn NFT wọle",
@@ -539,6 +540,7 @@
540 "recipient_address": "Àdírẹ́sì olùgbà",
541 "reconnect": "Ṣe àtúnse",
542 "reconnect_alert_text": "Ṣó dá ẹ lójú pé ẹ fẹ́ ṣe àtúnse?",
543 + "reconnect_your_hardware_wallet": "Ṣe atunṣe apamọwọ ohun elo rẹ",
544 "reconnection": "Àtúnṣe",
545 "red_dark_theme": "Akọle dudu pupa",
546 "red_light_theme": "Akori ina pupa",
res/values/strings_zh.arb
+2
@@ -355,6 +355,7 @@
355 "how_to_use": "如何使用",
356 "how_to_use_card": "如何使用这张卡",
357 "id": "ID: ",
358 + "if_you_dont_see_your_device": "如果您在上面看不到设备,请确保您的分类帐已经清醒并解锁!",
359 "ignor": "忽视",
360 "import": "进口",
361 "importNFTs": "导入 NFT",
@@ -538,6 +539,7 @@
539 "recipient_address": "收件人地址",
540 "reconnect": "重新连接",
541 "reconnect_alert_text": "您确定要重新连接吗?",
542 + "reconnect_your_hardware_wallet": "重新连接您的硬件钱包",
543 "reconnection": "重新连接",
544 "red_dark_theme": "红色的黑暗主题",
545 "red_light_theme": "红灯主题",