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>
This commit is contained in:
Konstantin Ullrich 2024-12-14 00:32:36 +01:00 committed by GitHub
parent c620d7f486
commit ae758756d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 287 additions and 90 deletions

View file

@ -7,7 +7,7 @@ enum DeviceConnectionType {
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
[bool isIOS = false]) {
switch (walletType) {
// case WalletType.monero:
case WalletType.monero:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:

View file

@ -200,9 +200,16 @@ String? commitTransactionFromPointerAddress({required int address, required bool
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
final transactionPointerAddress = transactionPointer.address;
final txCommit = useUR
? monero.PendingTransaction_commitUR(transactionPointer, 120)
: monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
: Isolate.run(() {
monero.PendingTransaction_commit(
Pointer.fromAddress(transactionPointerAddress),
filename: '',
overwrite: false,
);
});
String? error = (() {
final status = monero.PendingTransaction_status(transactionPointer.cast());
@ -221,7 +228,7 @@ String? commitTransaction({required monero.PendingTransaction transactionPointer
})();
}
if (error != null) {
if (error != null && error != "no tx keys found for this txid") {
throw CreationTransactionException(message: error);
}
if (useUR) {

View file

@ -2,11 +2,12 @@ import 'dart:async';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:ffi/ffi.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
import 'package:monero/monero.dart' as monero;
// import 'package:polyseed/polyseed.dart';
LedgerConnection? gLedger;
@ -28,9 +29,16 @@ void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
ptr, emptyPointer.cast<UnsignedChar>(), 0);
malloc.free(emptyPointer);
// printV("> ${ledgerRequest.toHexString()}");
_logLedgerCommand(ledgerRequest, false);
final response = await exchange(connection, ledgerRequest);
// printV("< ${response.toHexString()}");
_logLedgerCommand(response, true);
if (ListEquality().equals(response, [0x55, 0x15])) {
await connection.disconnect();
// // TODO: Show POPUP pls unlock your device
// await Future.delayed(Duration(seconds: 15));
// response = await exchange(connection, ledgerRequest);
}
final Pointer<Uint8> result = malloc<Uint8>(response.length);
for (var i = 0; i < response.length; i++) {
@ -82,3 +90,59 @@ class ExchangeOperation extends LedgerRawOperation<Uint8List> {
@override
Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData];
}
const _ledgerMoneroCommands = {
0x00: "INS_NONE",
0x02: "INS_RESET",
0x20: "INS_GET_KEY",
0x21: "INS_DISPLAY_ADDRESS",
0x22: "INS_PUT_KEY",
0x24: "INS_GET_CHACHA8_PREKEY",
0x26: "INS_VERIFY_KEY",
0x28: "INS_MANAGE_SEEDWORDS",
0x30: "INS_SECRET_KEY_TO_PUBLIC_KEY",
0x32: "INS_GEN_KEY_DERIVATION",
0x34: "INS_DERIVATION_TO_SCALAR",
0x36: "INS_DERIVE_PUBLIC_KEY",
0x38: "INS_DERIVE_SECRET_KEY",
0x3A: "INS_GEN_KEY_IMAGE",
0x3B: "INS_DERIVE_VIEW_TAG",
0x3C: "INS_SECRET_KEY_ADD",
0x3E: "INS_SECRET_KEY_SUB",
0x40: "INS_GENERATE_KEYPAIR",
0x42: "INS_SECRET_SCAL_MUL_KEY",
0x44: "INS_SECRET_SCAL_MUL_BASE",
0x46: "INS_DERIVE_SUBADDRESS_PUBLIC_KEY",
0x48: "INS_GET_SUBADDRESS",
0x4A: "INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY",
0x4C: "INS_GET_SUBADDRESS_SECRET_KEY",
0x70: "INS_OPEN_TX",
0x72: "INS_SET_SIGNATURE_MODE",
0x74: "INS_GET_ADDITIONAL_KEY",
0x76: "INS_STEALTH",
0x77: "INS_GEN_COMMITMENT_MASK",
0x78: "INS_BLIND",
0x7A: "INS_UNBLIND",
0x7B: "INS_GEN_TXOUT_KEYS",
0x7D: "INS_PREFIX_HASH",
0x7C: "INS_VALIDATE",
0x7E: "INS_MLSAG",
0x7F: "INS_CLSAG",
0x80: "INS_CLOSE_TX",
0xA0: "INS_GET_TX_PROOF",
0xC0: "INS_GET_RESPONSE"
};
void _logLedgerCommand(Uint8List command, [bool isResponse = true]) {
String toHexString(Uint8List data) =>
data.map((e) => e.toRadixString(16).padLeft(2, '0')).join();
if (isResponse) {
printV("< ${toHexString(command)}");
} else {
printV(
"> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}");
}
}

View file

@ -1,5 +1,7 @@
import 'dart:ffi';
import 'dart:io';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/unspent_coins_info.dart';
@ -9,16 +11,16 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:polyseed/polyseed.dart';
import 'package:monero/monero.dart' as monero;
import 'package:polyseed/polyseed.dart';
class MoneroNewWalletCredentials extends WalletCredentials {
MoneroNewWalletCredentials(
@ -133,14 +135,12 @@ class MoneroWalletService extends WalletService<
try {
final path = await pathForWallet(name: name, type: getType());
if (walletFilesExist(path)) {
await repairOldAndroidWallet(name);
}
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(name, getType()));
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = MoneroWallet(
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
@ -279,7 +279,8 @@ class MoneroWalletService extends WalletService<
}
@override
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
Future<MoneroWallet> restoreFromSeed(
MoneroRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
// Restore from Polyseed
if (Polyseed.isValidSeed(credentials.mnemonic)) {
@ -313,7 +314,8 @@ class MoneroWalletService extends WalletService<
final path = await pathForWallet(name: credentials.name, type: getType());
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
final polyseed =
Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
@ -355,24 +357,18 @@ class MoneroWalletService extends WalletService<
Future<void> repairOldAndroidWallet(String name) async {
try {
if (!Platform.isAndroid) {
return;
}
if (!Platform.isAndroid) return;
final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
final dir = Directory(oldAndroidWalletDirPath);
if (!dir.existsSync()) {
return;
}
if (!dir.existsSync()) return;
final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
dir.listSync().forEach((f) {
final file = File(f.path);
final name = f.path
.split('/')
.last;
final name = f.path.split('/').last;
final newPath = newWalletDirPath + '/$name';
final newFile = File(newPath);
@ -391,9 +387,7 @@ class MoneroWalletService extends WalletService<
try {
final path = await pathForWallet(name: name, type: getType());
if (walletFilesExist(path)) {
await repairOldAndroidWallet(name);
}
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values
@ -412,8 +406,10 @@ class MoneroWalletService extends WalletService<
@override
bool requireHardwareWalletConnection(String name) {
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
return walletInfo.isHardwareWallet;
return walletInfoSource.values
.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))
?.isHardwareWallet ??
false;
}
}

View file

@ -44,13 +44,16 @@ void startAuthenticationStateChange(
} catch (error, stack) {
loginError = error;
await ExceptionHandler.resetLastPopupDate();
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
await ExceptionHandler.onError(
FlutterErrorDetails(exception: error, stack: stack));
}
return;
}
if (state == AuthenticationState.allowed) {
if (requireHardwareWalletConnection()) {
if ([AuthenticationState.allowed, AuthenticationState.allowedCreate]
.contains(state)) {
if (state == AuthenticationState.allowed &&
requireHardwareWalletConnection()) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil(
Routes.connectDevices,
(route) => false,

View file

@ -22,11 +22,13 @@ class ConnectDevicePageParams {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
ConnectDevicePageParams({
required this.walletType,
required this.onConnectDevice,
this.allowChangeWallet = false,
this.isReconnect = false,
});
}
@ -34,19 +36,33 @@ class ConnectDevicePage extends BasePage {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
final LedgerViewModel ledgerVM;
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
: walletType = params.walletType,
onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet;
allowChangeWallet = params.allowChangeWallet,
isReconnect = params.isReconnect;
@override
String get title => S.current.restore_title_from_hardware_wallet;
String get title => isReconnect
? S.current.reconnect_your_hardware_wallet
: S.current.restore_title_from_hardware_wallet;
@override
Widget body(BuildContext context) => ConnectDevicePageBody(
walletType, onConnectDevice, allowChangeWallet, ledgerVM);
Widget? leading(BuildContext context) =>
!isReconnect ? super.leading(context) : null;
@override
Widget body(BuildContext context) => PopScope(
canPop: !isReconnect,
child: ConnectDevicePageBody(
walletType,
onConnectDevice,
allowChangeWallet,
ledgerVM,
));
}
class ConnectDevicePageBody extends StatefulWidget {
@ -75,6 +91,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
late Timer? _bleStateTimer = null;
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
bool longWait = false;
@override
void initState() {
super.initState();
@ -89,6 +107,11 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_usbRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
}
Future.delayed(Duration(seconds: 10), () {
if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
setState(() => longWait = true);
});
});
}
@ -98,6 +121,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_bleStateTimer?.cancel();
_usbRefreshTimer?.cancel();
_bleRefresh?.cancel();
widget.ledgerVM.stopScanning();
super.dispose();
}
@ -118,9 +143,11 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Future<void> _refreshBleDevices() async {
try {
if (widget.ledgerVM.bleIsEnabled) {
_bleRefresh = widget.ledgerVM
.scanForBleDevices()
.listen((device) => setState(() => bleDevices.add(device)))
_bleRefresh =
widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
bleDevices.add(device);
if (longWait) longWait = false;
}))
..onError((e) {
throw e.toString();
});
@ -175,15 +202,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
textAlign: TextAlign.center,
),
),
// DeviceTile(
// onPressed: () => Navigator.of(context).push(
// MaterialPageRoute<void>(
// builder: (BuildContext context) => DebugDevicePage(),
// ),
// ),
// title: "Debug Ledger",
// leading: imageLedger,
// ),
Offstage(
offstage: !longWait,
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(S.of(context).if_you_dont_see_your_device,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
textAlign: TextAlign.center,
),
),
),
Observer(
builder: (_) => Offstage(
offstage: widget.ledgerVM.bleIsEnabled,

View file

@ -56,9 +56,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
}
if (isMoneroOnly) {
// return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
// .isNotEmpty;
return false;
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
.isNotEmpty;
}
return true;

View file

@ -580,7 +580,10 @@ class SendPage extends BasePage {
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
buttonAction: () => Navigator.of(context).pop());
buttonAction: () {
sendViewModel.state = InitialExecutionState();
Navigator.of(context).pop();
});
});
});
}

View file

@ -422,8 +422,9 @@ class WalletListBodyState extends State<WalletListBody> {
if (!isAuthenticatedSuccessfully) return;
try {
if (widget.walletListViewModel
.requireHardwareWalletConnection(wallet)) {
final requireHardwareWalletConnection = widget.walletListViewModel
.requireHardwareWalletConnection(wallet);
if (requireHardwareWalletConnection) {
await Navigator.of(context).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
@ -445,8 +446,6 @@ class WalletListBodyState extends State<WalletListBody> {
);
}
changeProcessText(
S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
@ -456,6 +455,9 @@ class WalletListBodyState extends State<WalletListBody> {
if (responsiveLayoutUtil.shouldRenderMobileUI) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (this.mounted) {
if (requireHardwareWalletConnection) {
Navigator.of(context).pop();
}
widget.onWalletLoaded.call(context);
}
});

View file

@ -4,7 +4,7 @@ part 'authentication_store.g.dart';
class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore;
enum AuthenticationState { uninitialized, installed, allowed, _reset }
enum AuthenticationState { uninitialized, installed, allowed, allowedCreate, _reset }
abstract class AuthenticationStoreBase with Store {
AuthenticationStoreBase() : state = AuthenticationState.uninitialized;
@ -23,4 +23,10 @@ abstract class AuthenticationStoreBase with Store {
state = AuthenticationState._reset;
state = AuthenticationState.allowed;
}
@action
void allowedCreate() {
state = AuthenticationState._reset;
state = AuthenticationState.allowedCreate;
}
}

View file

@ -4,14 +4,18 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
import 'package:mobx/mobx.dart';
@ -59,7 +63,8 @@ abstract class LedgerViewModelBase with Store {
bool _bleIsInitialized = false;
Future<void> _initBLE() async {
if (bleIsEnabled && !_bleIsInitialized) {
ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async {
ledgerPlusBLE = sdk.LedgerInterface.ble(
onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
@ -67,7 +72,9 @@ abstract class LedgerViewModelBase with Store {
].request();
return statuses.values.where((status) => status.isDenied).isEmpty;
});
},
bleOptions:
sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)));
_bleIsInitialized = true;
}
}
@ -84,16 +91,26 @@ abstract class LedgerViewModelBase with Store {
Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
Future<void> stopScanning() async {
await ledgerPlusBLE.stopScanning();
if (!Platform.isIOS) {
await ledgerPlusUSB.stopScanning();
}
}
Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
if (isConnected) {
try {
await _connection!.disconnect();
await _connectionChangeListener?.cancel();
_connectionChangeListener = null;
await _connection!.disconnect().catchError((_) {});
} catch (_) {}
}
final ledger = device.connectionType == sdk.ConnectionType.ble
? ledgerPlusBLE
: ledgerPlusUSB;
if (_connectionChangeListener == null) {
_connectionChangeListener = ledger.deviceStateChanges.listen((event) {
printV('Ledger Device State Changed: $event');
@ -101,6 +118,18 @@ abstract class LedgerViewModelBase with Store {
_connection = null;
if (type == WalletType.monero) {
monero!.resetLedgerConnection();
Navigator.of( navigatorKey.currentContext!).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
allowChangeWallet: true,
isReconnect: true,
onConnectDevice: (context, ledgerVM) async {
Navigator.of(context).pop();
},
),
);
}
}
});

View file

@ -8,7 +8,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cw_core/pathForWallet.dart';
@ -114,7 +113,7 @@ abstract class WalletCreationVMBase with Store {
await _walletInfoSource.add(walletInfo);
await _appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed();
_appStore.authenticationStore.allowedCreate();
state = ExecutedSuccessfullyState();
} catch (e, s) {
printV("error: $e");

View file

@ -355,6 +355,7 @@
"how_to_use": " ﻞﻤﻌﺘﺴﺗ ﻒﻴﻛ",
"how_to_use_card": "كيفية استخدام هذه البطاقة",
"id": "رقم المعرف:",
"if_you_dont_see_your_device": "إذا كنت لا ترى جهازك أعلاه ، فيرجى التأكد من أن دفتر الأستاذ الخاص بك مستيقظًا ومؤمنًا!",
"ignor": "تجاهل",
"import": "ﺩﺭﻮﺘﺴﻳ",
"importNFTs": "NFTs ﺩﺍﺮﻴﺘﺳﺍ",
@ -538,6 +539,7 @@
"recipient_address": "عنوان المستلم",
"reconnect": "أعد الاتصال",
"reconnect_alert_text": "هل أنت متأكد من رغبتك في إعادة الاتصال؟",
"reconnect_your_hardware_wallet": "أعد توصيل محفظة الأجهزة الخاصة بك",
"reconnection": "إعادة الاتصال",
"red_dark_theme": "موضوع الظلام الأحمر",
"red_light_theme": "موضوع الضوء الأحمر",

View file

@ -355,6 +355,7 @@
"how_to_use": "Как да използвам",
"how_to_use_card": "Как се ползва тази карта",
"id": "ID: ",
"if_you_dont_see_your_device": "Ако не виждате устройството си по -горе, моля, уверете се, че вашата книга е будна и отключена!",
"ignor": "Игнориране",
"import": "Импортиране",
"importNFTs": "Импортирайте NFT",
@ -538,6 +539,7 @@
"recipient_address": "Адрес на получател",
"reconnect": "Reconnect",
"reconnect_alert_text": "Сигурни ли сте, че искате да се свържете отново?",
"reconnect_your_hardware_wallet": "Свържете отново хардуерния си портфейл",
"reconnection": "Свързване отново",
"red_dark_theme": "Червена тъмна тема",
"red_light_theme": "Тема на червената светлина",

View file

@ -355,6 +355,7 @@
"how_to_use": "Jak používat",
"how_to_use_card": "Jak použít tuto kartu",
"id": "ID: ",
"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á!",
"ignor": "Ignorovat",
"import": "Import",
"importNFTs": "Importujte NFT",
@ -538,6 +539,7 @@
"recipient_address": "Adresa příjemce",
"reconnect": "Znovu připojit",
"reconnect_alert_text": "Opravdu se chcete znovu připojit?",
"reconnect_your_hardware_wallet": "Znovu připojte svou hardwarovou peněženku",
"reconnection": "Znovu připojit",
"red_dark_theme": "Červené temné téma",
"red_light_theme": "Téma červeného světla",

View file

@ -355,6 +355,7 @@
"how_to_use": "Wie benutzt man",
"how_to_use_card": "Wie man diese Karte benutzt",
"id": "ID: ",
"if_you_dont_see_your_device": "Wenn Sie Ihr Gerät nicht sehen, stellen Sie bitte sicher, dass Ihr Ledger an und entsperrt ist!",
"ignor": "Ignorieren",
"import": "Importieren",
"importNFTs": "NFTs importieren",
@ -539,6 +540,7 @@
"recipient_address": "Empfängeradresse",
"reconnect": "Erneut verbinden",
"reconnect_alert_text": "Sind Sie sicher, dass Sie sich neu verbinden möchten?",
"reconnect_your_hardware_wallet": "Hardware-Wallet neu verbinden",
"reconnection": "Neu verbinden",
"red_dark_theme": "Red Dark Thema",
"red_light_theme": "Red Light Thema",

View file

@ -355,6 +355,7 @@
"how_to_use": "How to use",
"how_to_use_card": "How to use this card",
"id": "ID: ",
"if_you_dont_see_your_device": "If you don't see your device above, please be sure your Ledger is awake and unlocked!",
"ignor": "Ignore",
"import": "Import",
"importNFTs": "Import NFTs",
@ -538,6 +539,7 @@
"recipient_address": "Recipient address",
"reconnect": "Reconnect",
"reconnect_alert_text": "Are you sure you want to reconnect?",
"reconnect_your_hardware_wallet": "Reconnect your Hardware Wallet",
"reconnection": "Reconnection",
"red_dark_theme": "Red Dark Theme",
"red_light_theme": "Red Light Theme",

View file

@ -355,6 +355,7 @@
"how_to_use": "Cómo utilizar",
"how_to_use_card": "Cómo usar esta tarjeta",
"id": "ID: ",
"if_you_dont_see_your_device": "Si no ve su dispositivo arriba, ¡asegúrese de que su libro mayor esté despierto y desbloqueado!",
"ignor": "Pasar por alto",
"import": "Importar",
"importNFTs": "Importar NFT",
@ -539,6 +540,7 @@
"recipient_address": "Dirección del receptor",
"reconnect": "Volver a conectar",
"reconnect_alert_text": "¿Estás seguro de reconectar?",
"reconnect_your_hardware_wallet": "Vuelva a conectar su billetera de hardware",
"reconnection": "Reconexión",
"red_dark_theme": "Tema rojo oscuro",
"red_light_theme": "Tema de la luz roja",

View file

@ -355,6 +355,7 @@
"how_to_use": "Comment utiliser",
"how_to_use_card": "Comment utiliser cette carte",
"id": "ID : ",
"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é!",
"ignor": "Ignorer",
"import": "Importer",
"importNFTs": "Importer des NFT",
@ -538,6 +539,7 @@
"recipient_address": "Adresse bénéficiaire",
"reconnect": "Reconnecter",
"reconnect_alert_text": "Êtes vous certain de vouloir vous reconnecter ?",
"reconnect_your_hardware_wallet": "Reconnectez votre portefeuille matériel",
"reconnection": "Reconnexion",
"red_dark_theme": "Thème rouge sombre",
"red_light_theme": "Thème rouge clair",

View file

@ -355,6 +355,7 @@
"how_to_use": "Yadda ake amfani da shi",
"how_to_use_card": "Yadda ake amfani da wannan kati",
"id": "ID:",
"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!",
"ignor": "Yi watsi da shi",
"import": "Shigo da",
"importNFTs": "Shigo da NFTs",
@ -540,6 +541,7 @@
"recipient_address": "Adireshin mai karɓa",
"reconnect": "Sake haɗawa",
"reconnect_alert_text": "Shin kun tabbata kuna son sake haɗawa?",
"reconnect_your_hardware_wallet": "Sake kunnawa kayan aiki",
"reconnection": "Sake haɗawa",
"red_dark_theme": "Ja duhu taken",
"red_light_theme": "Ja mai haske",

View file

@ -355,6 +355,7 @@
"how_to_use": "का उपयोग कैसे करें",
"how_to_use_card": "इस कार्ड का उपयोग कैसे करें",
"id": "ID: ",
"if_you_dont_see_your_device": "यदि आप अपने डिवाइस को ऊपर नहीं देखते हैं, तो कृपया सुनिश्चित करें कि आपका लेजर जागृत और अनलॉक हो गया है!",
"ignor": "नज़रअंदाज़ करना",
"import": "आयात",
"importNFTs": "एनएफटी आयात करें",
@ -540,6 +541,7 @@
"recipient_address": "प्राप्तकर्ता का पता",
"reconnect": "रिकनेक्ट",
"reconnect_alert_text": "क्या आप पुन: कनेक्ट होना सुनिश्चित करते हैं?",
"reconnect_your_hardware_wallet": "अपने हार्डवेयर वॉलेट को फिर से कनेक्ट करें",
"reconnection": "पुनर्संयोजन",
"red_dark_theme": "लाल डार्क थीम",
"red_light_theme": "लाल प्रकाश थीम",

View file

@ -355,6 +355,7 @@
"how_to_use": "Kako koristiti",
"how_to_use_card": "Kako koristiti ovu karticu",
"id": "ID: ",
"if_you_dont_see_your_device": "Ako svoj uređaj ne vidite gore, budite sigurni da je vaša knjiga budna i otključana!",
"ignor": "Zanemariti",
"import": "Uvoz",
"importNFTs": "Uvoz NFT-ova",
@ -538,6 +539,7 @@
"recipient_address": "Primateljeva adresa",
"reconnect": "Ponovno povezivanje",
"reconnect_alert_text": "Jeste li sigurni da se želite ponovno povezati?",
"reconnect_your_hardware_wallet": "Ponovno spojite svoj hardverski novčanik",
"reconnection": "Ponovno povezivanje",
"red_dark_theme": "Crvena tamna tema",
"red_light_theme": "Tema crvenog svjetla",

View file

@ -355,6 +355,7 @@
"how_to_use": "Ինչպես օգտագործել",
"how_to_use_card": "Ինչպես օգտագործել այս քարտը",
"id": "ID: ",
"if_you_dont_see_your_device": "Եթե ​​ձեր սարքը վերեւում չեք տեսնում, համոզվեք, որ ձեր Ledger- ը արթուն է եւ բացված:",
"ignor": "Անտեսել",
"import": "Ներմուծել",
"importNFTs": "Ներմուծել NFT-ներ",
@ -538,6 +539,7 @@
"recipient_address": "Ստացողի հասցե",
"reconnect": "Վերակապվել",
"reconnect_alert_text": "Դուք վստահ եք, որ ուզում եք վերակապվել?",
"reconnect_your_hardware_wallet": "Միացրեք ձեր ապարատային դրամապանակը",
"reconnection": "Վերակապում",
"red_dark_theme": "Կարմիր մութ տեսք",
"red_light_theme": "Կարմիր պայծառ տեսք",

View file

@ -355,6 +355,7 @@
"how_to_use": "Cara Penggunaan",
"how_to_use_card": "Bagaimana menggunakan kartu ini",
"id": "ID: ",
"if_you_dont_see_your_device": "Jika Anda tidak melihat perangkat Anda di atas, pastikan buku besar Anda terjaga dan tidak terkunci!",
"ignor": "Abaikan",
"import": "Impor",
"importNFTs": "Impor NFT",
@ -540,6 +541,7 @@
"recipient_address": "Alamat penerima",
"reconnect": "Sambungkan kembali",
"reconnect_alert_text": "Apakah Anda yakin ingin menyambungkan kembali?",
"reconnect_your_hardware_wallet": "Hubungkan kembali dompet perangkat keras Anda",
"reconnection": "Koneksi kembali",
"red_dark_theme": "Tema gelap merah",
"red_light_theme": "Tema lampu merah",

View file

@ -356,6 +356,7 @@
"how_to_use": "Come usare",
"how_to_use_card": "Come usare questa carta",
"id": "ID: ",
"if_you_dont_see_your_device": "Se non vedi il tuo dispositivo sopra, assicurati che il tuo libro mastro sia sveglio e sbloccato!",
"ignor": "Ignorare",
"import": "Importare",
"importNFTs": "Importa NFT",
@ -540,6 +541,7 @@
"recipient_address": "Indirizzo di destinazione",
"reconnect": "Riconnetti",
"reconnect_alert_text": "Sei sicuro di volerti riconnettere?",
"reconnect_your_hardware_wallet": "Ricollega il tuo portafoglio hardware",
"reconnection": "Riconnessione",
"red_dark_theme": "Red Dark Theme",
"red_light_theme": "Tema della luce rossa",

View file

@ -356,6 +356,7 @@
"how_to_use": "使い方",
"how_to_use_card": "このカードの使用方法",
"id": "ID: ",
"if_you_dont_see_your_device": "上記のデバイスが表示されない場合は、元帳が目を覚ましてロック解除されていることを確認してください!",
"ignor": "無視",
"import": "輸入",
"importNFTs": "NFTのインポート",
@ -539,6 +540,7 @@
"recipient_address": "受信者のアドレス",
"reconnect": "再接続",
"reconnect_alert_text": "再接続しますか?",
"reconnect_your_hardware_wallet": "ハードウェアウォレットを再接続します",
"reconnection": "再接続",
"red_dark_theme": "赤い暗いテーマ",
"red_light_theme": "赤色光のテーマ",

View file

@ -355,6 +355,7 @@
"how_to_use": "사용하는 방법",
"how_to_use_card": "이 카드를 사용하는 방법",
"id": "ID: ",
"if_you_dont_see_your_device": "위의 장치가 표시되지 않으면 원장이 깨어 있고 잠금 해제되었는지 확인하십시오!",
"ignor": "무시하다",
"import": "수입",
"importNFTs": "NFT 가져오기",
@ -502,8 +503,8 @@
"placeholder_transactions": "거래가 여기에 표시됩니다",
"please_fill_totp": "다른 기기에 있는 8자리 코드를 입력하세요.",
"please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.",
"please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
"Please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
"please_reference_document": "자세한 내용은 아래 문서를 참조하십시오.",
"please_select": "선택 해주세요:",
"please_select_backup_file": "백업 파일을 선택하고 백업 암호를 입력하십시오.",
"please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오",
@ -539,6 +540,7 @@
"recipient_address": "받는 사람 주소",
"reconnect": "다시 연결",
"reconnect_alert_text": "다시 연결 하시겠습니까?",
"reconnect_your_hardware_wallet": "하드웨어 지갑을 다시 연결하십시오",
"reconnection": "재 연결",
"red_dark_theme": "빨간 어두운 테마",
"red_light_theme": "빨간불 테마",

View file

@ -355,6 +355,7 @@
"how_to_use": "အသုံးပြုနည်း",
"how_to_use_card": "ဒီကတ်ကို ဘယ်လိုသုံးမလဲ။",
"id": "ID:",
"if_you_dont_see_your_device": "သင်၏စက်ကိုအထက်တွင်မတွေ့ပါကသင်၏ Ledger သည်နိုးလာပြီးသော့ဖွင့်နေသည်ကိုသေချာပါစေ။",
"ignor": "လျစ်လျူရှုပါ။",
"import": "သွင်းကုန်",
"importNFTs": "NFTs များကို တင်သွင်းပါ။",
@ -538,6 +539,7 @@
"recipient_address": "လက်ခံသူလိပ်စာ",
"reconnect": "ပြန်လည်ချိတ်ဆက်ပါ။",
"reconnect_alert_text": "ပြန်လည်ချိတ်ဆက်လိုသည်မှာ သေချာပါသလား။ ?",
"reconnect_your_hardware_wallet": "သင့်ရဲ့ hardware ပိုက်ဆံအိတ်ကိုပြန်လည်ချိတ်ဆက်ပါ",
"reconnection": "ပြန်လည်ချိတ်ဆက်မှု",
"red_dark_theme": "အနီရောင်မှောင်မိုက်ဆောင်ပုဒ်",
"red_light_theme": "အနီရောင်အလင်းအကြောင်းအရာ",

View file

@ -355,6 +355,7 @@
"how_to_use": "Hoe te gebruiken",
"how_to_use_card": "Hoe deze kaart te gebruiken",
"id": "ID: ",
"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!",
"ignor": "Negeren",
"import": "Importeren",
"importNFTs": "NFT's importeren",
@ -538,6 +539,7 @@
"recipient_address": "Adres ontvanger",
"reconnect": "Sluit",
"reconnect_alert_text": "Weet u zeker dat u opnieuw verbinding wilt maken?",
"reconnect_your_hardware_wallet": "Sluit uw hardware -portemonnee opnieuw aan",
"reconnection": "Reconnection",
"red_dark_theme": "Rood donker thema",
"red_light_theme": "Rood licht thema",

View file

@ -355,6 +355,7 @@
"how_to_use": "Jak używać",
"how_to_use_card": "Jak korzystać z tej karty?",
"id": "ID: ",
"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!",
"ignor": "Ignorować",
"import": "Import",
"importNFTs": "Importuj NFT",
@ -538,6 +539,7 @@
"recipient_address": "Adres odbiorcy",
"reconnect": "Połącz ponownie",
"reconnect_alert_text": "Czy na pewno ponownie się ponownie połączysz?",
"reconnect_your_hardware_wallet": "Ponownie podłącz portfel sprzętowy",
"reconnection": "Ponowne łączenie",
"red_dark_theme": "Czerwony Mroczny motyw",
"red_light_theme": "Motyw czerwony światło",

View file

@ -355,6 +355,7 @@
"how_to_use": "Como usar",
"how_to_use_card": "Como usar este cartão",
"id": "ID: ",
"if_you_dont_see_your_device": "Se você não vê seu dispositivo acima, certifique -se de que seu livro esteja acordado e desbloqueado!",
"ignor": "Ignorar",
"import": "Importar",
"importNFTs": "Importar NFTs",
@ -540,6 +541,7 @@
"recipient_address": "Endereço do destinatário",
"reconnect": "Reconectar",
"reconnect_alert_text": "Você tem certeza de que deseja reconectar?",
"reconnect_your_hardware_wallet": "Reconecte sua carteira de hardware",
"reconnection": "Reconectar",
"red_dark_theme": "Tema escuro vermelho",
"red_light_theme": "Tema da luz vermelha",

View file

@ -355,6 +355,7 @@
"how_to_use": "Как использовать",
"how_to_use_card": "Как использовать эту карту",
"id": "ID: ",
"if_you_dont_see_your_device": "Если вы не видите свое устройство выше, пожалуйста, убедитесь, что ваша бухгалтерская книга бодрствует и разблокирована!",
"ignor": "Игнорировать",
"import": "Импортировать",
"importNFTs": "Импортировать NFT",
@ -539,6 +540,7 @@
"recipient_address": "Адрес получателя",
"reconnect": "Переподключиться",
"reconnect_alert_text": "Вы хотите переподключиться?",
"reconnect_your_hardware_wallet": "Воссоедините свой аппаратный кошелек",
"reconnection": "Переподключение",
"red_dark_theme": "Красная темная тема",
"red_light_theme": "Тема красного света",

View file

@ -355,6 +355,7 @@
"how_to_use": "วิธีใช้",
"how_to_use_card": "วิธีใช้บัตรนี้",
"id": "ID: ",
"if_you_dont_see_your_device": "หากคุณไม่เห็นอุปกรณ์ของคุณด้านบนโปรดตรวจสอบให้แน่ใจว่าบัญชีแยกประเภทของคุณตื่นและปลดล็อค!",
"ignor": "ละเว้น",
"import": "นำเข้า",
"importNFTs": "นำเข้า NFT",
@ -538,6 +539,7 @@
"recipient_address": "ที่อยู่ผู้รับ",
"reconnect": "เชื่อมต่อใหม่",
"reconnect_alert_text": "คุณแน่ใจหรือไม่ว่าต้องการเชื่อมต่อใหม่?",
"reconnect_your_hardware_wallet": "เชื่อมต่อกระเป๋าเงินฮาร์ดแวร์ของคุณอีกครั้ง",
"reconnection": "เชื่อมต่อใหม่",
"red_dark_theme": "ธีมสีแดงเข้ม",
"red_light_theme": "ธีมแสงสีแดง",

View file

@ -355,6 +355,7 @@
"how_to_use": "Paano gamitin",
"how_to_use_card": "Paano gamitin ang card na ito",
"id": "ID: ",
"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!",
"ignor": "Huwag pansinin",
"import": "Mag-import",
"importNFTs": "Mag-import ng mga NFT",
@ -538,6 +539,7 @@
"recipient_address": "Address ng tatanggap",
"reconnect": "Kumonekta muli",
"reconnect_alert_text": "Sigurado ka bang gusto mong kumonekta uli?",
"reconnect_your_hardware_wallet": "Ikonekta muli ang iyong wallet ng hardware",
"reconnection": "Muling pagkakakonekta",
"red_dark_theme": "Red Dark Theme",
"red_light_theme": "Red Light Theme",

View file

@ -355,6 +355,7 @@
"how_to_use": "Nasıl kullanılır",
"how_to_use_card": "Bu kart nasıl kullanılır",
"id": "ID: ",
"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!",
"ignor": "Yoksay",
"import": "İçe aktarmak",
"importNFTs": "NFT'leri içe aktar",
@ -538,6 +539,7 @@
"recipient_address": "Alıcı adresi",
"reconnect": "Yeniden Bağlan",
"reconnect_alert_text": "Yeniden bağlanmak istediğinden emin misin?",
"reconnect_your_hardware_wallet": "Donanım cüzdanınızı yeniden bağlayın",
"reconnection": "Yeniden bağlantı",
"red_dark_theme": "Kırmızı Karanlık Tema",
"red_light_theme": "Kırmızıık Teması",

View file

@ -355,6 +355,7 @@
"how_to_use": "Як використовувати",
"how_to_use_card": "Як використовувати цю картку",
"id": "ID: ",
"if_you_dont_see_your_device": "Якщо ви не бачите свого пристрою вище, будь ласка, переконайтеся, що ваша книга прокинеться і розблокована!",
"ignor": "Ігнорувати",
"import": "Імпорт",
"importNFTs": "Імпорт NFT",
@ -538,6 +539,7 @@
"recipient_address": "Адреса одержувача",
"reconnect": "Перепідключитися",
"reconnect_alert_text": "Ви хочете перепідключитися?",
"reconnect_your_hardware_wallet": "Повторно підключіть свій апаратний гаманець",
"reconnection": "Перепідключення",
"red_dark_theme": "Червона темна тема",
"red_light_theme": "Тема червоного світла",

View file

@ -355,6 +355,7 @@
"how_to_use": " ﮧﻘﯾﺮﻃ ﺎﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ",
"how_to_use_card": "اس کارڈ کو استعمال کرنے کا طریقہ",
"id": "ID:",
"if_you_dont_see_your_device": "اگر آپ اوپر اپنا آلہ نہیں دیکھتے ہیں تو ، براہ کرم یقینی بنائیں کہ آپ کا لیجر بیدار اور غیر مقفل ہے!",
"ignor": "نظر انداز کرنا",
"import": " ۔ﮟﯾﺮﮐ ﺪﻣﺁﺭﺩ",
"importNFTs": "NFTs ۔ﮟﯾﺮﮐ ﺪﻣﺁﺭﺩ",
@ -540,6 +541,7 @@
"recipient_address": "وصول کنندہ کا پتہ",
"reconnect": "دوبارہ جڑیں۔",
"reconnect_alert_text": "کیا آپ واقعی دوبارہ جڑنا چاہتے ہیں؟",
"reconnect_your_hardware_wallet": "اپنے ہارڈ ویئر پرس کو دوبارہ مربوط کریں",
"reconnection": "دوبارہ رابطہ",
"red_dark_theme": "ریڈ ڈارک تھیم",
"red_light_theme": "ریڈ لائٹ تھیم",

View file

@ -354,6 +354,7 @@
"how_to_use": "Cách sử dụng",
"how_to_use_card": "Cách sử dụng thẻ này",
"id": "ID: ",
"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!",
"ignor": "Bỏ qua",
"import": "Nhập",
"importNFTs": "Nhập NFT",
@ -537,6 +538,7 @@
"recipient_address": "Địa chỉ người nhận",
"reconnect": "Kết nối lại",
"reconnect_alert_text": "Bạn có chắc chắn muốn kết nối lại không?",
"reconnect_your_hardware_wallet": "Kết nối lại ví phần cứng của bạn",
"reconnection": "Kết nối lại",
"red_dark_theme": "Chủ đề tối đỏ",
"red_light_theme": "Chủ đề sáng đỏ",

View file

@ -356,6 +356,7 @@
"how_to_use": "Bawo ni lati lo",
"how_to_use_card": "Báyìí ni wọ́n ṣe ń lo káàdì yìí.",
"id": "Àmì Ìdánimọ̀: ",
"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!",
"ignor": "Ṣàìfiyèsí",
"import": "gbe wọle",
"importNFTs": "Gbe awọn NFT wọle",
@ -539,6 +540,7 @@
"recipient_address": "Àdírẹ́sì olùgbà",
"reconnect": "Ṣe àtúnse",
"reconnect_alert_text": "Ṣó dá ẹ lójú pé ẹ fẹ́ ṣe àtúnse?",
"reconnect_your_hardware_wallet": "Ṣe atunṣe apamọwọ ohun elo rẹ",
"reconnection": "Àtúnṣe",
"red_dark_theme": "Akọle dudu pupa",
"red_light_theme": "Akori ina pupa",

View file

@ -355,6 +355,7 @@
"how_to_use": "如何使用",
"how_to_use_card": "如何使用这张卡",
"id": "ID: ",
"if_you_dont_see_your_device": "如果您在上面看不到设备,请确保您的分类帐已经清醒并解锁!",
"ignor": "忽视",
"import": "进口",
"importNFTs": "导入 NFT",
@ -538,6 +539,7 @@
"recipient_address": "收件人地址",
"reconnect": "重新连接",
"reconnect_alert_text": "您确定要重新连接吗?",
"reconnect_your_hardware_wallet": "重新连接您的硬件钱包",
"reconnection": "重新连接",
"red_dark_theme": "红色的黑暗主题",
"red_light_theme": "红灯主题",