CW-723-Add-Monero-support-to-the-Shared-Seed-feature-in-Cake (#2131)

* feat: add exodus style bip39 to monero legacy seed

* feat: restore monero wallet from bip39 and add test

* bug: fix wrong naming in CI

* feat: add monero bip39 UI flow

* fix: monero.dart generation

* fix: skip monero_wallet_service tests till CI is fixed

* ci: copy monero_libwallet2_api_c.so to /usr/lib for testing
ci: reduce timeout for cw_monero tests

* fix: monero wallet creation credentials default to bip39 if mnemonic are set

* fix: do not skip monero wallets services test

* fix: Include non bip39 monero wallets on Wallet Group

* fix: null pointer stemming from missing language selector if seed is selected

* fix: Fixes to Bip39 Creation and restore

- Do not restore from 0 for fresh bip39 wallet
- disallow restoring bip39 wallet without date or height

* fix: Fixes to Bip39 restore

- Refresh height is now getting set correctly
- Add new create monero wallet tests
- Add seed-language English for Bip39 Monero wallets
- Fix seed-type naming

* feat (cw_monero): Store monero wallet after bip39 creation

* feat (cw_monero): remove prints from monero_wallet_service_test.dart

* fix: exception during seed language autodetect

* feat (cw_monero): Add support for passphrases on bip39 seeds

* feat (cw_monero): Add support for passphrases on bip39 seeds

* fix: seed language selection for recovering bip39 wallets

* style: improve readability of isLegacySeedOnly in wallet_keys_view_model.dart

* feat: hide monero seed type selector from advanced settings when creating a child wallet

* fix(cw_monero): use named arguments for bip39_seed tests

---------

Co-authored-by: cyan <cyjan@mrcyjanek.net>
This commit is contained in:
Konstantin Ullrich 2025-04-10 03:31:26 +02:00 committed by GitHub
parent 494207290e
commit f58a5fb8fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 702 additions and 283 deletions

View file

@ -283,6 +283,9 @@ jobs:
xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" &
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart
- name: Test [cw_monero]
timeout-minutes: 2
run: cd cw_monero && flutter test
- name: Stop screen recording, encrypt and upload
if: always()
run: |

View file

@ -12,3 +12,4 @@ android/.cxx/
macos/cw_monero.podspec
macos/External/
*monero_libwallet2_api_c.*

View file

@ -62,6 +62,11 @@ String getSeed() {
}
return cakepolyseed;
}
final bip39 = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed.bip39");
if(bip39.isNotEmpty) return bip39;
final legacy = getSeedLegacy(null);
return legacy;
}

View file

@ -0,0 +1,59 @@
import 'dart:typed_data';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:polyseed/polyseed.dart';
bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic);
String getBip39Seed() => bip39.generateMnemonic();
String getLegacySeedFromBip39(String mnemonic,
{int accountIndex = 0, String passphrase = ""}) {
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase);
final bip32KeyPair =
bip32.BIP32.fromSeed(seed).derivePath("m/44'/128'/$accountIndex'/0/0");
final spendKey = _reduceECKey(bip32KeyPair.privateKey!);
return LegacySeedLang.getByEnglishName("English")
.encodePhrase(spendKey.toHexString());
}
const _ed25519CurveOrder =
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED";
Uint8List _reduceECKey(Uint8List buffer) {
final curveOrder = BigInt.parse(_ed25519CurveOrder, radix: 16);
final bigNumber = _readBytes(buffer);
var result = bigNumber % curveOrder;
final resultBuffer = Uint8List(32);
for (var i = 0; i < 32; i++) {
resultBuffer[i] = (result & BigInt.from(0xff)).toInt();
result = result >> 8;
}
return resultBuffer;
}
/// Read BigInt from a little-endian Uint8List
/// From https://github.com/dart-lang/sdk/issues/32803#issuecomment-387405784
BigInt _readBytes(Uint8List bytes) {
BigInt read(int start, int end) {
if (end - start <= 4) {
var result = 0;
for (int i = end - 1; i >= start; i--) {
result = result * 256 + bytes[i];
}
return BigInt.from(result);
}
final mid = start + ((end - start) >> 1);
return read(start, mid) +
read(mid, end) * (BigInt.one << ((mid - start) * 8));
}
return read(0, bytes.length);
}

View file

@ -1,6 +1,7 @@
import 'dart:ffi';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/pathForWallet.dart';
@ -14,26 +15,35 @@ import 'package:cw_core/wallet_type.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/bip39_seed.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:monero/monero.dart' as monero;
import 'package:polyseed/polyseed.dart';
enum MoneroSeedType { polyseed, legacy, bip39 }
class MoneroNewWalletCredentials extends WalletCredentials {
MoneroNewWalletCredentials(
{required String name, required this.language, required this.isPolyseed, String? password, this.passphrase})
{required String name,
required this.language,
required this.seedType,
String? password,
this.passphrase,
this.mnemonic})
: super(name: name, password: password);
final String language;
final bool isPolyseed;
final MoneroSeedType seedType;
final String? passphrase;
final String? mnemonic;
}
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
MoneroRestoreWalletFromHardwareCredentials({required String name,
MoneroRestoreWalletFromHardwareCredentials(
{required String name,
required this.ledgerConnection,
int height = 0,
String? password})
@ -60,7 +70,8 @@ class MoneroWalletLoadingException implements Exception {
}
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
MoneroRestoreWalletFromKeysCredentials({required String name,
MoneroRestoreWalletFromKeysCredentials(
{required String name,
required String password,
required this.language,
required this.address,
@ -98,26 +109,41 @@ class MoneroWalletService extends WalletService<
WalletType getType() => WalletType.monero;
@override
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials,
{bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
if (credentials.isPolyseed) {
if (credentials.seedType == MoneroSeedType.bip39) {
return _restoreFromBip39(
path: path,
password: credentials.password!,
mnemonic: credentials.mnemonic ?? getBip39Seed(),
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,
);
}
if (credentials.seedType == MoneroSeedType.polyseed) {
final polyseed = Polyseed.create();
final lang = PolyseedLang.getByEnglishName(credentials.language);
if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
if (credentials.passphrase != null)
polyseed.crypt(credentials.passphrase!);
final heightOverride =
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
final heightOverride = getMoneroHeigthByDate(
date: DateTime.now().subtract(Duration(days: 2)));
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
return _restoreFromPolyseed(path, credentials.password!, polyseed,
credentials.walletInfo!, lang,
overrideHeight: heightOverride, passphrase: credentials.passphrase);
}
await monero_wallet_manager.createWallet(
path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??"");
path: path,
password: credentials.password!,
language: credentials.language,
passphrase: credentials.passphrase ?? "");
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
@ -145,7 +171,8 @@ class MoneroWalletService extends WalletService<
}
@override
Future<MoneroWallet> openWallet(String name, String password, {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async {
Future<MoneroWallet> openWallet(String name, String password,
{OpenWalletTry openWalletTry = OpenWalletTry.initial}) async {
try {
final path = await pathForWallet(name: name, type: getType());
@ -303,8 +330,28 @@ class MoneroWalletService extends WalletService<
rethrow;
}
try {
if (isBip39Seed(credentials.mnemonic)) {
final path =
await pathForWallet(name: credentials.name, type: getType());
return _restoreFromBip39(
path: path,
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
overrideHeight: credentials.height!,
passphrase: credentials.passphrase,
);
}
} catch (e) {
printV("Bip39 restore failed: $e");
rethrow;
}
try {
final path = await pathForWallet(name: credentials.name, type: getType());
monero_wallet_manager.restoreFromSeed(
path: path,
password: credentials.password!,
@ -325,6 +372,50 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> _restoreFromBip39({
required String path,
required String password,
required String mnemonic,
required WalletInfo walletInfo,
String? passphrase,
int? overrideHeight,
}) async {
walletInfo.derivationInfo = DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/128'/0'/0/0",
);
final legacyMnemonic =
getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? "");
final height =
overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now());
walletInfo.isRecovery = true;
walletInfo.restoreHeight = height;
monero_wallet_manager.restoreFromSeed(
path: path,
password: password,
passphrase: '',
seed: legacyMnemonic,
restoreHeight: height,
);
monero.Wallet_setCacheAttribute(wptr!,
key: "cakewallet.seed.bip39", value: mnemonic);
monero.Wallet_store(wptr!);
final wallet = MoneroWallet(
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
password: password,
);
await wallet.init();
return wallet;
}
Future<MoneroWallet> restoreFromPolyseed(
MoneroRestoreWalletFromSeedCredentials credentials) async {
try {
@ -344,21 +435,19 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> _restoreFromPolyseed(
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
Future<MoneroWallet> _restoreFromPolyseed(String path, String password,
Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO,
int? overrideHeight,
String? passphrase}) async {
if (polyseed.isEncrypted == false &&
(passphrase??'') != "") {
if (polyseed.isEncrypted == false && (passphrase ?? '') != "") {
// Fallback to the different passphrase offset method, when a passphrase
// was provided but the polyseed is not encrypted.
monero_wallet_manager.restoreWalletFromPolyseedWithOffset(
path: path,
password: password,
seed: polyseed.encode(lang, coin),
seedOffset: passphrase??'',
seedOffset: passphrase ?? '',
language: "English");
final wallet = MoneroWallet(
@ -437,7 +526,8 @@ class MoneroWalletService extends WalletService<
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = MoneroWallet(

View file

@ -5,18 +5,23 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev"
source: hosted
version: "47.0.0"
version: "76.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev"
source: hosted
version: "4.7.0"
version: "6.11.0"
args:
dependency: transitive
description:
@ -41,6 +46,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bip32:
dependency: "direct main"
description:
name: bip32
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
bip39:
dependency: "direct main"
description:
name: bip39
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
url: "https://pub.dev"
source: hosted
version: "1.0.6"
blockchain_utils:
dependency: transitive
description:
@ -66,6 +87,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bs58check:
dependency: transitive
description:
name: bs58check
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
url: "https://pub.dev"
source: hosted
version: "1.0.2"
build:
dependency: transitive
description:
@ -94,10 +123,10 @@ packages:
dependency: "direct dev"
description:
name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev"
source: hosted
version: "2.0.10"
version: "2.4.4"
build_runner:
dependency: "direct dev"
description:
@ -222,10 +251,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
version: "2.3.8"
dbus:
dependency: transitive
description:
@ -348,6 +377,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.0"
hex:
dependency: transitive
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
hive:
dependency: transitive
description:
@ -360,10 +397,10 @@ packages:
dependency: "direct dev"
description:
name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
version: "2.0.1"
http:
dependency: "direct main"
description:
@ -468,6 +505,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher:
dependency: transitive
description:
@ -512,10 +557,18 @@ packages:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.7.0"
mockito:
dependency: "direct dev"
description:
name: mockito
sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6
url: "https://pub.dev"
source: hosted
version: "5.4.5"
monero:
dependency: "direct main"
description:
@ -735,18 +788,18 @@ packages:
dependency: transitive
description:
name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev"
source: hosted
version: "1.2.6"
version: "1.5.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
version: "1.3.5"
source_span:
dependency: transitive
description:
@ -924,5 +977,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.5.0 <4.0.0"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0"

View file

@ -12,6 +12,8 @@ environment:
dependencies:
flutter:
sdk: flutter
bip39: ^1.0.6
bip32: ^2.0.0
ffi: ^2.0.1
http: ^1.1.0
path_provider: ^2.0.11
@ -36,7 +38,8 @@ dev_dependencies:
build_runner: ^2.4.7
build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
mockito: ^5.4.5
hive_generator: ^2.0.1
dependency_overrides:
watcher: ^1.1.0

View file

@ -0,0 +1,39 @@
import 'package:cw_monero/bip39_seed.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group("Exodus Style bip39", () {
group("Test Wallet 1", () {
final bip39Seed = 'meadow tip best belt boss eyebrow control affair eternal piece very shiver';
final expectedLegacySeed0 = "tasked eight afraid laboratory tail feline rift reinvest vane cafe bailed foggy dormant paper jigsaw king hazard suture king dapper dummy jolted dating dwindling king";
final expectedLegacySeed1 = "palace pairing axes mohawk rekindle excess awful juvenile shipped talent nibs efficient dapper biggest swung fight pact innocent emerge issued titans affair nearby noises emerge";
test("Get legacy Seed from bip39", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed);
expect(legacySeed, expectedLegacySeed0);
});
test("Get legacy Seed from bip39 with account index", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1);
expect(legacySeed, expectedLegacySeed1);
});
});
group("Test Wallet 2", () {
final bip39Seed = "color ranch color remove subway public water embrace before begin liberty fault";
final expectedLegacySeed0 = "somewhere problems gauze gigantic intended foxes upcoming saved waffle pipeline lurk bogeys empty wipeout abbey italics novelty tucks rafts elite lunar obnoxious awful bugs elite";
final expectedLegacySeed1 = "playful toxic wildly eluded mesh fainted february mugged maps repent vigilant hitched seventh threaten clue fetches sample diet number alkaline future cottage tuition vegan alkaline";
test("Get legacy Seed from bip39", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed);
expect(legacySeed, expectedLegacySeed0);
});
test("Get legacy Seed from bip39 with account index", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1);
expect(legacySeed, expectedLegacySeed1);
});
});
});
}

View file

@ -0,0 +1,29 @@
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockPathProviderPlatform extends Mock
with MockPlatformInterfaceMixin
implements PathProviderPlatform {
Future<String> getTemporaryPath() => throw UnimplementedError();
Future<String> getApplicationSupportPath() => throw UnimplementedError();
Future<String> getLibraryPath() => throw UnimplementedError();
Future<String> getApplicationDocumentsPath() async => "./test/data";
Future<String> getExternalStoragePath() => throw UnimplementedError();
Future<List<String>> getExternalCachePaths() => throw UnimplementedError();
Future<String> getDownloadsPath() => throw UnimplementedError();
@override
Future<String?> getApplicationCachePath() => throw UnimplementedError();
@override
Future<List<String>?> getExternalStoragePaths({StorageDirectory? type}) =>
throw UnimplementedError();
}

View file

@ -0,0 +1,148 @@
import 'dart:io';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/monero_wallet_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hive/hive.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'mock/path_provider.dart';
import 'utils/setup_monero_c.dart';
Future<void> main() async {
group("MoneroWalletService Tests", () {
Hive.init('./test/data/db');
late MoneroWalletService walletService;
late File moneroCBinary;
setUpAll(() async {
PathProviderPlatform.instance = MockPathProviderPlatform();
final Box<WalletInfo> walletInfoSource =
await Hive.openBox('testWalletInfo');
final Box<UnspentCoinsInfo> unspentCoinsInfoSource =
await Hive.openBox('testUnspentCoinsInfo');
walletService = MoneroWalletService(walletInfoSource, unspentCoinsInfoSource);
moneroCBinary = getMoneroCBinary().copySync(moneroCBinaryName);
});
tearDownAll(() {
Directory('./test/data').deleteSync(recursive: true);
moneroCBinary.deleteSync();
});
group("Create wallet", () {
test("Create Legacy Wallet", () async {
final credentials = _getTestCreateCredentials(
name: 'Create Wallet LS',
language: 'English',
seedType: MoneroSeedType.legacy);
final wallet = await walletService.create(credentials);
expect(wallet.seed.split(" ").length, 25);
expect(wallet.restoreHeight, greaterThan(3000000));
});
test("Create Polyseed Wallet", () async {
final credentials = _getTestCreateCredentials(
name: 'Create Wallet PS',
language: 'English',
seedType: MoneroSeedType.polyseed);
final wallet = await walletService.create(credentials);
expect(wallet.seed.split(" ").length, 16);
expect(wallet.restoreHeight, greaterThan(3000000));
});
test("Create Bip39 Wallet", () async {
final credentials = _getTestCreateCredentials(
name: 'Create Wallet BS',
language: 'English',
seedType: MoneroSeedType.bip39);
final wallet = await walletService.create(credentials);
expect(wallet.seed.split(" ").length, 12);
expect(wallet.restoreHeight, greaterThan(3000000));
});
});
group("Restore wallet", () {
test('Legacy Seed', () async {
final credentials = _getTestRestoreCredentials(
name: 'Test Wallet LS',
mnemonic:
'ability pockets lordship tomorrow gypsy match neutral uncle avatar betting bicycle junk unzip pyramid lynx mammal edgy empty uneven knowledge juvenile wiring paradise psychic betting',
);
final wallet = await walletService.restoreFromSeed(credentials);
expect(wallet.walletAddresses.primaryAddress,
'48tLyQXpcwt8w6uKHyb5Zs3vdnoDWAEKFQr1c198o7aX9dBzXP3BTSMVsDiuH3ozDCNqwojb4vNeQZf7xg6URimDLaNtGSN');
});
test('Bip39 Seed', () async {
final credentials = _getTestRestoreCredentials(
name: 'Test Wallet BS',
mnemonic:
'color ranch color remove subway public water embrace before begin liberty fault');
final wallet = await walletService.restoreFromSeed(credentials);
expect(wallet.walletAddresses.primaryAddress,
'49MggvPosJugF8Zq7WAKbsSchz6vbyL6YiUxM4ryfGQDXphs6wiWiXLFWCSshnLPcceGTWUaKfWWMHQAAKESV3TQJVQsL9a');
});
});
});
}
MoneroRestoreWalletFromSeedCredentials _getTestRestoreCredentials({
required String name,
required String mnemonic,
}) {
final credentials = MoneroRestoreWalletFromSeedCredentials(
name: name, mnemonic: mnemonic, passphrase: '', password: "test");
credentials.walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, WalletType.monero),
name: name,
type: WalletType.monero,
isRecovery: true,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: '',
dirPath: '',
address: '',
);
return credentials;
}
MoneroNewWalletCredentials _getTestCreateCredentials({
required String name,
required String language,
required MoneroSeedType seedType,
String? mnemonic,
}) {
final credentials = MoneroNewWalletCredentials(
name: name,
language: language,
seedType: seedType,
password: "test",
mnemonic: mnemonic,
passphrase: '',
);
credentials.walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, WalletType.monero),
name: name,
type: WalletType.monero,
isRecovery: false,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: '',
dirPath: '',
address: '',
);
return credentials;
}

View file

@ -0,0 +1,16 @@
import 'dart:io';
File getMoneroCBinary() {
if (Platform.isWindows)
return File(
'../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll');
if (Platform.isMacOS) return File('../macos/monero_libwallet2_api_c.dylib');
return File('../scripts/monero_c/release/monero/x86_64-linux-gnu_libwallet2_api_c.so');
}
String get moneroCBinaryName {
if (Platform.isWindows)
return "monero_libwallet2_api_c.dll";
if (Platform.isMacOS) return "monero_libwallet2_api_c.dylib";
return "/usr/lib/monero_libwallet2_api_c.so";
}

View file

@ -1,17 +1,17 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/enumerable_item.dart';
import 'package:cw_core/wallet_info.dart';
class MoneroSeedType extends EnumerableItem<int> with Serializable<int> {
const MoneroSeedType({required String title, required int raw}) : super(title: title, raw: raw);
static const all = [MoneroSeedType.legacy, MoneroSeedType.polyseed];
static const all = [legacy, polyseed, bip39];
static const defaultSeedType = polyseed;
static const legacy = MoneroSeedType(raw: 0, title: 'Legacy (25 words)');
static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed (16 words)');
static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero (14 words)');
static const legacy = MoneroSeedType(raw: 0, title: 'Legacy');
static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed');
static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero');
static const bip39 = MoneroSeedType(raw: 3, title: 'BIP39');
static MoneroSeedType deserialize({required int raw}) {
switch (raw) {
@ -21,24 +21,15 @@ class MoneroSeedType extends EnumerableItem<int> with Serializable<int> {
return polyseed;
case 2:
return wowneroSeed;
case 3:
return bip39;
default:
throw Exception('Unexpected token: $raw for SeedType deserialize');
}
}
@override
String toString() {
switch (this) {
case MoneroSeedType.legacy:
return S.current.seedtype_legacy;
case MoneroSeedType.polyseed:
return S.current.seedtype_polyseed;
case MoneroSeedType.wowneroSeed:
return S.current.seedtype_wownero;
default:
return '';
}
}
String toString() => title;
}
class BitcoinSeedType extends EnumerableItem<int> with Serializable<int> {

View file

@ -252,11 +252,21 @@ class CWMonero extends Monero {
WalletCredentials createMoneroNewWalletCredentials({
required String name,
required String language,
required bool isPolyseed,
required int seedType,
required String? passphrase,
String? password}) =>
String? password,
String? mnemonic,
}) =>
MoneroNewWalletCredentials(
name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase);
name: name,
password: password,
language: language,
seedType: seedType == 1
? MoneroSeedType.polyseed
: (seedType == 3 ? MoneroSeedType.bip39 : MoneroSeedType.legacy),
passphrase: passphrase,
mnemonic: mnemonic,
);
@override
Map<String, String> getKeys(Object wallet) {

View file

@ -11,8 +11,8 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.bitcoinCash:
case WalletType.nano:
case WalletType.banano:
return true;
case WalletType.monero:
return true;
case WalletType.wownero:
case WalletType.haven:
case WalletType.zano:

View file

@ -144,7 +144,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
),
);
}),
if (widget.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled)
if (widget
.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled &&
!widget.isChildWallet)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<MoneroSeedType>(

View file

@ -305,7 +305,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
),
),
if (_walletNewVM.hasLanguageSelector) ...[
if (_walletNewVM.showLanguageSelector) ...[
if (_walletNewVM.hasSeedType) ...[
Observer(
builder: (BuildContext build) => Padding(
@ -401,7 +401,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
} else {
await _walletNewVM.create(
options: _walletNewVM.hasLanguageSelector
? [_languageSelectorKey.currentState!.selected, isPolyseed]
? [
_languageSelectorKey.currentState?.selected ??
defaultSeedLanguage,
widget._seedSettingsViewModel.moneroSeedType
]
: null);
}
} catch (e) {

View file

@ -11,13 +11,15 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/show_pop_up.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/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
class WalletRestoreFromSeedForm extends StatefulWidget {
WalletRestoreFromSeedForm({Key? key,
WalletRestoreFromSeedForm({
Key? key,
required this.displayLanguageSelector,
required this.displayBlockHeightSelector,
required this.type,
@ -47,20 +49,22 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
@override
WalletRestoreFromSeedFormState createState() =>
WalletRestoreFromSeedFormState('English', displayWalletPassword: displayWalletPassword);
WalletRestoreFromSeedFormState('English',
displayWalletPassword: displayWalletPassword);
}
class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
WalletRestoreFromSeedFormState(this.language, {required bool displayWalletPassword})
WalletRestoreFromSeedFormState(this.language,
{required bool displayWalletPassword})
: seedWidgetStateKey = GlobalKey<SeedWidgetState>(),
blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
formKey = GlobalKey<FormState>(),
languageController = TextEditingController(),
nameTextEditingController = TextEditingController(),
passwordTextEditingController = displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController = displayWalletPassword
? TextEditingController()
: null,
passwordTextEditingController =
displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController =
displayWalletPassword ? TextEditingController() : null,
seedTypeController = TextEditingController();
final GlobalKey<SeedWidgetState> seedWidgetStateKey;
@ -83,18 +87,21 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
_setLanguageLabel(language);
if (passwordTextEditingController != null) {
passwordListener = () => widget.onPasswordChange?.call(passwordTextEditingController!.text);
passwordListener = () =>
widget.onPasswordChange?.call(passwordTextEditingController!.text);
passwordTextEditingController?.addListener(passwordListener!);
}
if (repeatedPasswordTextEditingController != null) {
repeatedPasswordListener =
() => widget.onRepeatedPasswordChange?.call(repeatedPasswordTextEditingController!.text);
repeatedPasswordTextEditingController?.addListener(repeatedPasswordListener!);
repeatedPasswordListener = () => widget.onRepeatedPasswordChange
?.call(repeatedPasswordTextEditingController!.text);
repeatedPasswordTextEditingController
?.addListener(repeatedPasswordListener!);
}
moneroSeedTypeReaction =
reaction((_) => widget.seedSettingsViewModel.moneroSeedType, (MoneroSeedType item) {
reaction((_) => widget.seedSettingsViewModel.moneroSeedType,
(MoneroSeedType item) {
_setSeedType(item);
_changeLanguage('English');
});
@ -118,16 +125,22 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
}
void onSeedChange(String seed) {
if ((widget.type == WalletType.monero || widget.type == WalletType.wownero) &&
Polyseed.isValidSeed(seed)) {
if ([WalletType.monero, WalletType.wownero].contains(widget.type) &&
(seed.split(" ").length == 12 || Polyseed.isValidSeed(seed))) {
try {
final lang = PolyseedLang.getByPhrase(seed);
if (widget.type == WalletType.monero && seed.split(" ").length == 12) {
_changeSeedType(MoneroSeedType.bip39);
} else {
_changeSeedType(MoneroSeedType.polyseed);
_changeLanguage(lang.nameEnglish);
}
if (widget.type == WalletType.wownero && seed
.split(" ")
.length == 14) {
_changeLanguage(lang.nameEnglish, true);
} catch (e) {
printV(e);
}
}
if (widget.type == WalletType.wownero && seed.split(" ").length == 14) {
_changeSeedType(MoneroSeedType.wowneroSeed);
_changeLanguage("English");
}
@ -147,9 +160,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
BaseTextFormField(
key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'),
controller: nameTextEditingController,
hintText: S
.of(context)
.wallet_name,
hintText: S.of(context).wallet_name,
suffixIcon: IconButton(
key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'),
onPressed: () async {
@ -158,17 +169,17 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
setState(() {
nameTextEditingController.text = rName;
nameTextEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: nameTextEditingController.text.length));
nameTextEditingController.selection =
TextSelection.fromPosition(TextPosition(
offset:
nameTextEditingController.text.length));
});
},
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme
.of(context)
.hintColor,
color: Theme.of(context).hintColor,
),
width: 34,
height: 34,
@ -194,14 +205,13 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
seedTextFieldKey: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'),
pasteButtonKey: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'),
),
if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
if ([WalletType.monero, WalletType.wownero].contains(widget.type))
GestureDetector(
key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'),
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) =>
Picker(
builder: (_) => Picker(
items: _getItems(),
selectedAtIndex: isPolyseed
? 1
@ -226,20 +236,16 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
),
),
),
if (widget.displayWalletPassword)
...[BaseTextFormField(
if (widget.displayWalletPassword) ...[
BaseTextFormField(
key: ValueKey('password'),
controller: passwordTextEditingController,
hintText: S
.of(context)
.password,
hintText: S.of(context).password,
obscureText: true),
BaseTextFormField(
key: ValueKey('repeat_wallet_password'),
controller: repeatedPasswordTextEditingController,
hintText: S
.of(context)
.repeat_wallet_password,
hintText: S.of(context).repeat_wallet_password,
obscureText: true)
],
if (widget.displayLanguageSelector)
@ -248,11 +254,12 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) =>
SeedLanguagePicker(
builder: (_) => SeedLanguagePicker(
selected: language,
onItemSelected: _changeLanguage,
seedType: isPolyseed ? MoneroSeedType.polyseed : MoneroSeedType.legacy,
onItemSelected: (lang) =>
_changeLanguage(lang, isPolyseed || isBip39),
seedType:
widget.seedSettingsViewModel.moneroSeedType,
));
},
child: Container(
@ -274,7 +281,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
key: blockchainHeightKey,
blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'),
onHeightOrDateEntered: widget.onHeightOrDateEntered,
hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero,
hasDatePicker:
[WalletType.monero, WalletType.wownero].contains(widget.type),
walletType: widget.type,
),
]));
@ -282,24 +290,25 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
bool get isPolyseed =>
widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed &&
(widget.type == WalletType.monero || widget.type == WalletType.wownero);
[WalletType.monero, WalletType.wownero].contains(widget.type);
Widget get expandIcon =>
Container(
bool get isBip39 =>
widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.bip39 &&
WalletType.monero == widget.type;
Widget get expandIcon => Container(
padding: EdgeInsets.all(18),
width: 24,
height: 24,
child: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
height: 8,
color: Theme
.of(context)
.hintColor,
color: Theme.of(context).hintColor,
),
);
void _changeLanguage(String language) {
final setLang = isPolyseed
void _changeLanguage(String language, [bool useBip39Wordlist = false]) {
final setLang = useBip39Wordlist
? "POLYSEED_$language"
: seedTypeController.value.text.contains("14")
? "WOWSEED_" + language
@ -312,8 +321,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
});
}
void _setLanguageLabel(String language) =>
languageController.text = '${language.replaceAll("POLYSEED_", "")} (Seed language)';
void _setLanguageLabel(String language) => languageController.text =
'${language.replaceAll("POLYSEED_", "")} (Seed language)';
void _changeSeedType(MoneroSeedType item) {
_setSeedType(item);
@ -328,9 +337,17 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
List<MoneroSeedType> _getItems() {
switch (widget.type) {
case WalletType.monero:
return [MoneroSeedType.legacy, MoneroSeedType.polyseed];
return [
MoneroSeedType.legacy,
MoneroSeedType.polyseed,
MoneroSeedType.bip39
];
case WalletType.wownero:
return [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.wowneroSeed];
return [
MoneroSeedType.legacy,
MoneroSeedType.polyseed,
MoneroSeedType.wowneroSeed
];
default:
return [MoneroSeedType.legacy];
}

View file

@ -527,25 +527,42 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
}
bool _isValidSeed() {
final seedPhrase =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text;
final seedPhrase = walletRestoreFromSeedFormKey
.currentState!.seedWidgetStateKey.currentState!.text;
if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true;
final seedWords = seedPhrase.split(' ');
if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true;
if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true;
if (seedWords.length == 14 &&
walletRestoreViewModel.type == WalletType.wownero) return true;
if (seedWords.length == 26 &&
walletRestoreViewModel.type == WalletType.zano) return true;
if ((walletRestoreViewModel.type == WalletType.monero ||
walletRestoreViewModel.type == WalletType.wownero ||
walletRestoreViewModel.type == WalletType.haven) &&
seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) {
return false;
if (seedWords.length == 12 &&
walletRestoreViewModel.type == WalletType.monero) {
return walletRestoreFromSeedFormKey
.currentState
?.blockchainHeightKey
.currentState
?.restoreHeightController
.text
.isNotEmpty == true;
}
if ([WalletType.monero, WalletType.wownero, WalletType.haven]
.contains(walletRestoreViewModel.type) &&
seedWords.length ==
WalletRestoreViewModelBase.moneroSeedMnemonicLength) {
return true;
}
// bip39:
final validBip39SeedLengths = [12, 18, 24];
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred];
final nonBip39WalletTypes = [
WalletType.wownero,
WalletType.haven,
WalletType.decred
];
// if it's a bip39 wallet and the length is not valid return false
if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) &&
!(validBip39SeedLengths.contains(seedWords.length))) {
@ -558,8 +575,9 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
return false;
}
final words =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
final words = walletRestoreFromSeedFormKey
.currentState!.seedWidgetStateKey.currentState!.words
.toSet();
return seedWords.toSet().difference(words).toSet().isEmpty;
}

View file

@ -16,7 +16,7 @@ class SeedLanguagePickerOption {
final List<SeedLanguagePickerOption> seedLanguages = [
SeedLanguagePickerOption('English', S.current.seed_language_english,
Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.bip39]),
SeedLanguagePickerOption('Chinese (Simplified)', S.current.seed_language_chinese,
Image.asset('assets/images/flags/chn.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Chinese (Traditional)', S.current.seed_language_chinese_traditional,

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@ -127,12 +128,16 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
bool isNonSeedWallet = wallet.isNonSeedWallet;
bool isNotMoneroBip39Wallet = wallet.type == WalletType.monero &&
wallet.derivationInfo?.derivationType != DerivationType.bip39;
// Exclude if any of these conditions are true
return isNonBIP39Wallet ||
isNanoDerivationType ||
isElectrumDerivationType ||
isSameTypeAsSelectedWallet ||
isNonSeedWallet;
isNonSeedWallet ||
isNotMoneroBip39Wallet;
});
if (shouldExcludeGroup) continue;

View file

@ -63,14 +63,15 @@ abstract class WalletKeysViewModelBase with Store {
String get seed => _wallet.seed != null ? _wallet.seed! : '';
bool get isLegacySeedOnly =>
(_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) &&
[WalletType.monero, WalletType.wownero].contains(_wallet.type) &&
_wallet.seed != null &&
!Polyseed.isValidSeed(_wallet.seed!);
!(Polyseed.isValidSeed(_wallet.seed!) ||
_wallet.seed!.split(' ').length == 12);
String get legacySeed {
if ((_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) &&
_wallet.seed != null &&
Polyseed.isValidSeed(_wallet.seed!)) {
(Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12)) {
final langName = PolyseedLang.getByPhrase(_wallet.seed!).nameEnglish;
if (_wallet.type == WalletType.monero) {

View file

@ -49,6 +49,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
bool get hasLanguageSelector =>
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
bool get showLanguageSelector =>
newWalletArguments?.mnemonic == null && hasLanguageSelector;
int get seedPhraseWordsLength {
switch (type) {
case WalletType.monero:
@ -81,7 +84,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
}
}
bool get hasSeedType => [WalletType.monero, WalletType.wownero].contains(type);
bool get hasSeedType =>
newWalletArguments?.mnemonic == null &&
[WalletType.monero, WalletType.wownero].contains(type);
@override
WalletCredentials getCredentials(dynamic _options) {
@ -96,7 +101,11 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
language: options!.first as String,
password: walletPassword,
passphrase: passphrase,
isPolyseed: options.last as bool);
seedType: newWalletArguments!.mnemonic != null
? MoneroSeedType.bip39.raw
: (options.last as MoneroSeedType).raw,
mnemonic: newWalletArguments!.mnemonic,
);
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.createBitcoinNewWalletCredentials(

View file

@ -683,9 +683,6 @@
"seedtype": "البذور",
"seedtype_alert_content": "مشاركة البذور مع محافظ أخرى ممكن فقط مع BIP39 Seedtype.",
"seedtype_alert_title": "تنبيه البذور",
"seedtype_legacy": "إرث (25 كلمة)",
"seedtype_polyseed": "بوليسيد (16 كلمة)",
"seedtype_wownero": "Wownero (14 كلمة)",
"select_backup_file": "حدد ملف النسخ الاحتياطي",
"select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.",
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",

View file

@ -683,9 +683,6 @@
"seedtype": "Семенна тип",
"seedtype_alert_content": "Споделянето на семена с други портфейли е възможно само с BIP39 Seedtype.",
"seedtype_alert_title": "Сигнал за семена",
"seedtype_legacy": "Наследство (25 думи)",
"seedtype_polyseed": "Поли семе (16 думи)",
"seedtype_wownero": "Wownero (14 думи)",
"select_backup_file": "Избор на резервно копие",
"select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.",
"select_destination": "Моля, изберете дестинация за архивния файл.",

View file

@ -683,9 +683,6 @@
"seedtype": "SeedType",
"seedtype_alert_content": "Sdílení semen s jinými peněženkami je možné pouze u BIP39 SeedType.",
"seedtype_alert_title": "Upozornění seedtype",
"seedtype_legacy": "Legacy (25 slov)",
"seedtype_polyseed": "Polyseed (16 slov)",
"seedtype_wownero": "Wownero (14 slov)",
"select_backup_file": "Vybrat soubor se zálohou",
"select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.",
"select_destination": "Vyberte cíl pro záložní soubor.",

View file

@ -684,9 +684,6 @@
"seedtype": "Seedtyp",
"seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.",
"seedtype_alert_title": "Seedype-Alarm",
"seedtype_legacy": "Veraltet (25 Wörter)",
"seedtype_polyseed": "Polyseed (16 Wörter)",
"seedtype_wownero": "WOWNO (14 Wörter)",
"select_backup_file": "Sicherungsdatei auswählen",
"select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.",
"select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.",

View file

@ -684,9 +684,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "Sharing seeds with other wallets is only possible with BIP39 SeedType.",
"seedtype_alert_title": "SeedType Alert",
"seedtype_legacy": "Legacy (25 words)",
"seedtype_polyseed": "Polyseed (16 words)",
"seedtype_wownero": "Wownero (14 words)",
"select_backup_file": "Select backup file",
"select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.",
"select_destination": "Please select destination for the backup file.",

View file

@ -684,9 +684,6 @@
"seedtype": "Tipos de semillas",
"seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con semillas bip39 - un tipo específico de semilla.",
"seedtype_alert_title": "Alerta de tipo de semillas",
"seedtype_legacy": "Semilla clásica-legacy (25 palabras)",
"seedtype_polyseed": "Poli-semilla (16 palabras)",
"seedtype_wownero": "Wownero (14 palabras)",
"select_backup_file": "Seleccionar archivo de respaldo",
"select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
"select_destination": "Selecciona el destino del archivo de copia de seguridad.",

View file

@ -683,9 +683,6 @@
"seedtype": "Type de graine",
"seedtype_alert_content": "Le partage de graines avec d'autres portefeuilles n'est possible qu'avec le type de graine BIP39.",
"seedtype_alert_title": "Alerte Type de Graine",
"seedtype_legacy": "Legacy (25 words)",
"seedtype_polyseed": "Polyseed (16 mots)",
"seedtype_wownero": "WOWNERO (14 mots)",
"select_backup_file": "Sélectionnez le fichier de sauvegarde",
"select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.",
"select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.",

View file

@ -685,9 +685,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "Raba tsaba tare da sauran wallets yana yiwuwa ne kawai tare da Bip39 seedtype.",
"seedtype_alert_title": "Seedtype farke",
"seedtype_legacy": "Legacy (25 kalmomi)",
"seedtype_polyseed": "Polyseed (16 kalmomi)",
"seedtype_wownero": "WowRero (kalmomi 14)",
"select_backup_file": "Zaɓi fayil ɗin madadin",
"select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.",
"select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.",

View file

@ -685,9 +685,6 @@
"seedtype": "बीज",
"seedtype_alert_content": "अन्य पर्स के साथ बीज साझा करना केवल BIP39 सीडटाइप के साथ संभव है।",
"seedtype_alert_title": "बीजगणित अलर्ट",
"seedtype_legacy": "विरासत (25 शब्द)",
"seedtype_polyseed": "पॉलीसीड (16 शब्द)",
"seedtype_wownero": "Wownero (14 शब्द)",
"select_backup_file": "बैकअप फ़ाइल का चयन करें",
"select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।",
"select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।",

View file

@ -683,9 +683,6 @@
"seedtype": "Sjemenska vrsta",
"seedtype_alert_content": "Dijeljenje sjemena s drugim novčanicima moguće je samo s BIP39 sjemenom.",
"seedtype_alert_title": "Upozorenje o sjemenu",
"seedtype_legacy": "Nasljeđe (25 riječi)",
"seedtype_polyseed": "Poliseed (16 riječi)",
"seedtype_wownero": "WANERO (14 riječi)",
"select_backup_file": "Odaberite datoteku sigurnosne kopije",
"select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.",
"select_destination": "Odaberite odredište za datoteku sigurnosne kopije.",

View file

@ -682,9 +682,6 @@
"seedtype": "Սերմի տեսակ",
"seedtype_alert_content": "Այլ դրամապանակներով սերմերի փոխանակումը հնարավոր է միայն BIP39 SEEDTYPE- ով:",
"seedtype_alert_title": "SEEDTYPE ALERT",
"seedtype_legacy": "Legacy (25 բառ)",
"seedtype_polyseed": "Polyseed (16 բառ)",
"seedtype_wownero": "Wownero (14 բառ)",
"select_backup_file": "Ընտրել կրկնօրինակ ֆայլ",
"select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով",
"select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը",

View file

@ -686,9 +686,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "Berbagi biji dengan dompet lain hanya dimungkinkan dengan BIP39 seedtype.",
"seedtype_alert_title": "Peringatan seedtype",
"seedtype_legacy": "Legacy (25 kata)",
"seedtype_polyseed": "Polyseed (16 kata)",
"seedtype_wownero": "Wownero (14 kata)",
"select_backup_file": "Pilih file cadangan",
"select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.",
"select_destination": "Silakan pilih tujuan untuk file cadangan.",

View file

@ -684,9 +684,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "La condivisione di semi con altri portafogli è possibile solo con Bip39 SeedType.",
"seedtype_alert_title": "Avviso seedType",
"seedtype_legacy": "Legacy (25 parole)",
"seedtype_polyseed": "Polyseed (16 parole)",
"seedtype_wownero": "Wownero (14 parole)",
"select_backup_file": "Seleziona file di backup",
"select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.",
"select_destination": "Seleziona la destinazione per il file di backup.",

View file

@ -684,9 +684,6 @@
"seedtype": "SeedType",
"seedtype_alert_content": "他の財布と種子を共有することは、BIP39 SeedTypeでのみ可能です。",
"seedtype_alert_title": "SeedTypeアラート",
"seedtype_legacy": "レガシー25語",
"seedtype_polyseed": "ポリシード16語",
"seedtype_wownero": "wownero14ワード",
"select_backup_file": "バックアップファイルを選択",
"select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。",
"select_destination": "バックアップファイルの保存先を選択してください。",

View file

@ -683,9 +683,6 @@
"seedtype": "시드 타입",
"seedtype_alert_content": "다른 지갑과 씨앗을 공유하는 것은 BIP39 SeedType에서만 가능합니다.",
"seedtype_alert_title": "종자 경보",
"seedtype_legacy": "레거시 (25 단어)",
"seedtype_polyseed": "다문 (16 단어)",
"seedtype_wownero": "Wownero (14 단어)",
"select_backup_file": "백업 파일 선택",
"select_buy_provider_notice": "위의 구매 제공자를 선택하십시오. 앱 설정에서 기본 구매 제공자를 설정 하여이 화면을 건너 뛸 수 있습니다.",
"select_destination": "백업 파일의 대상을 선택하십시오.",

View file

@ -683,9 +683,6 @@
"seedtype": "မျိုးပွားခြင်း",
"seedtype_alert_content": "အခြားပိုက်ဆံအိတ်များနှင့်မျိုးစေ့များကိုမျှဝေခြင်းသည် BIP39 sebyspe ဖြင့်သာဖြစ်သည်။",
"seedtype_alert_title": "ပျိုးပင်သတိပေးချက်",
"seedtype_legacy": "အမွေအနှစ် (စကားလုံး 25 လုံး)",
"seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)",
"seedtype_wownero": "Wownero (စကားလုံး 14 လုံး)",
"select_backup_file": "အရန်ဖိုင်ကို ရွေးပါ။",
"select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။",
"select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",

View file

@ -683,9 +683,6 @@
"seedtype": "Zaadtype",
"seedtype_alert_content": "Het delen van zaden met andere portefeuilles is alleen mogelijk met BIP39 SeedType.",
"seedtype_alert_title": "Zaadtype alert",
"seedtype_legacy": "Legacy (25 woorden)",
"seedtype_polyseed": "Polyseed (16 woorden)",
"seedtype_wownero": "WOWNERO (14 woorden)",
"select_backup_file": "Selecteer een back-upbestand",
"select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.",
"select_destination": "Selecteer de bestemming voor het back-upbestand.",

View file

@ -683,9 +683,6 @@
"seedtype": "Sedtype",
"seedtype_alert_content": "Dzielenie się nasionami z innymi portfelami jest możliwe tylko z BIP39 sededType.",
"seedtype_alert_title": "Ustanowienie typu sedype",
"seedtype_legacy": "Legacy (25 słów)",
"seedtype_polyseed": "Polyseed (16 słów)",
"seedtype_wownero": "Wowero (14 słów)",
"select_backup_file": "Wybierz plik kopii zapasowej",
"select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.",
"select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.",

View file

@ -685,9 +685,6 @@
"seedtype": "SeedType",
"seedtype_alert_content": "Compartilhar sementes com outras carteiras só é possível com o BIP39 SeedType.",
"seedtype_alert_title": "Alerta de SeedType",
"seedtype_legacy": "Legado (25 palavras)",
"seedtype_polyseed": "Polyseed (16 palavras)",
"seedtype_wownero": "Wowrone (14 palavras)",
"select_backup_file": "Selecione o arquivo de backup",
"select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.",
"select_destination": "Selecione o destino para o arquivo de backup.",

View file

@ -684,9 +684,6 @@
"seedtype": "SEEDTYPE",
"seedtype_alert_content": "Обмен семенами с другими кошельками возможно только с BIP39 SeedType.",
"seedtype_alert_title": "SEEDTYPE ALERT",
"seedtype_legacy": "Наследие (25 слов)",
"seedtype_polyseed": "Полиса (16 слов)",
"seedtype_wownero": "Wownero (14 слов)",
"select_backup_file": "Выберите файл резервной копии",
"select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.",
"select_destination": "Пожалуйста, выберите место для файла резервной копии.",

View file

@ -683,9 +683,6 @@
"seedtype": "เมล็ดพันธุ์",
"seedtype_alert_content": "การแบ่งปันเมล็ดกับกระเป๋าเงินอื่น ๆ เป็นไปได้เฉพาะกับ bip39 seedtype",
"seedtype_alert_title": "การแจ้งเตือน seedtype",
"seedtype_legacy": "มรดก (25 คำ)",
"seedtype_polyseed": "โพลีส (16 คำ)",
"seedtype_wownero": "wownero (14 คำ)",
"select_backup_file": "เลือกไฟล์สำรอง",
"select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป",
"select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",

View file

@ -683,9 +683,6 @@
"seedtype": "Seed type",
"seedtype_alert_content": "Ang pagbabahagi ng mga buto sa iba pang mga pitaka ay posible lamang sa bip39 seedtype.",
"seedtype_alert_title": "Alerto ng Seedtype",
"seedtype_legacy": "Legacy (25 na salita)",
"seedtype_polyseed": "Polyseed (16 na salita)",
"seedtype_wownero": "Wownero (14 na salita)",
"select_backup_file": "Piliin ang backup na file",
"select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.",
"select_destination": "Mangyaring piliin ang patutunguhan para sa backup na file.",

View file

@ -683,9 +683,6 @@
"seedtype": "Tohum",
"seedtype_alert_content": "Tohumları diğer cüzdanlarla paylaşmak sadece BIP39 tohumu ile mümkündür.",
"seedtype_alert_title": "SeedType uyarısı",
"seedtype_legacy": "Miras (25 kelime)",
"seedtype_polyseed": "Polyseed (16 kelime)",
"seedtype_wownero": "Wownero (14 kelime)",
"select_backup_file": "Yedek dosyası seç",
"select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.",
"select_destination": "Lütfen yedekleme dosyası için hedef seçin.",

View file

@ -684,9 +684,6 @@
"seedtype": "Насіннєвий тип",
"seedtype_alert_content": "Спільний доступ до інших гаманців можливе лише за допомогою BIP39 Seedtype.",
"seedtype_alert_title": "Попередження насінника",
"seedtype_legacy": "Спадщина (25 слів)",
"seedtype_polyseed": "Полісей (16 слів)",
"seedtype_wownero": "Влонеро (14 слів)",
"select_backup_file": "Виберіть файл резервної копії",
"select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.",
"select_destination": "Виберіть місце призначення для файлу резервної копії.",

View file

@ -685,9 +685,6 @@
"seedtype": "سیڈ ٹائپ",
"seedtype_alert_content": "دوسرے بٹوے کے ساتھ بیجوں کا اشتراک صرف BIP39 بیج ٹائپ کے ساتھ ہی ممکن ہے۔",
"seedtype_alert_title": "سیڈ ٹائپ الرٹ",
"seedtype_legacy": "میراث (25 الفاظ)",
"seedtype_polyseed": "پالیسیڈ (16 الفاظ)",
"seedtype_wownero": "واونرو (14 الفاظ)",
"select_backup_file": "بیک اپ فائل کو منتخب کریں۔",
"select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔",
"select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",

View file

@ -681,9 +681,6 @@
"seedtype": "Loại hạt giống",
"seedtype_alert_content": "Chia sẻ hạt giống với ví khác chỉ có thể với BIP39 SeedType.",
"seedtype_alert_title": "Cảnh báo hạt giống",
"seedtype_legacy": "Di sản (25 từ)",
"seedtype_polyseed": "Polyseed (16 từ)",
"seedtype_wownero": "Wownero (14 từ)",
"select_backup_file": "Chọn tệp sao lưu",
"select_buy_provider_notice": "Chọn nhà cung cấp mua ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp mua mặc định trong cài đặt ứng dụng.",
"select_destination": "Vui lòng chọn đích cho tệp sao lưu.",

View file

@ -684,9 +684,6 @@
"seedtype": "Irugbin-seetypu",
"seedtype_alert_content": "Pinpin awọn irugbin pẹlu awọn gedo miiran ṣee ṣe pẹlu Bip39 irugbin.",
"seedtype_alert_title": "Ṣajọpọ Seeytype",
"seedtype_legacy": "Legacy (awọn ọrọ 25)",
"seedtype_polyseed": "Polyseed (awọn ọrọ 16)",
"seedtype_wownero": "Wowero (awọn ọrọ 14)",
"select_backup_file": "Select backup file",
"select_buy_provider_notice": "Yan olupese Ra loke. O le skii iboju yii nipa ṣiṣeto olupese rẹ ni awọn eto App.",
"select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.",

View file

@ -683,9 +683,6 @@
"seedtype": "籽粒",
"seedtype_alert_content": "只有BIP39籽粒可以与其他钱包共享种子。",
"seedtype_alert_title": "籽粒警报",
"seedtype_legacy": "遗产25个单词",
"seedtype_polyseed": "多种物品16个单词",
"seedtype_wownero": "沃恩罗14个单词",
"select_backup_file": "选择备份文件",
"select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。",
"select_destination": "请选择备份文件的目的地。",

View file

@ -406,7 +406,7 @@ abstract class Monero {
required int height});
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, required String? passphrase, String? password});
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic});
Map<String, String> getKeys(Object wallet);
int? getRestoreHeight(Object wallet);
Object createMoneroTransactionCreationCredentials({required List<Output> outputs, required TransactionPriority priority});