From d03e27bc9c54d0b7bf399f3f433dd4a4ac43590f Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 1 Mar 2026 14:46:25 -0600 Subject: [PATCH 01/17] fix(mwc): serialize openWallet FFI calls with mutex --- crypto_plugins/flutter_libmwc | 2 +- .../wallet/impl/mimblewimblecoin_wallet.dart | 95 +++++++++++-------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 5b43e0e91..cf9406505 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 5b43e0e91f3d04bddfe88bba1d2f6178a18aadf9 +Subproject commit cf94065052b0ff2fd92c6c5b0fc6baed8569ef48 diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 3850cb750..dad7b3632 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -41,6 +41,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { : super(Mimblewimblecoin(network)); final syncMutex = Mutex(); + final _walletOpenMutex = Mutex(); NodeModel? _mimblewimblecoinNode; Timer? timer; @@ -95,24 +96,29 @@ class MimblewimblecoinWallet extends Bip39Wallet { } Future _ensureWalletOpen() async { - final existing = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - if (existing != null && existing.isNotEmpty) return existing; + return await _walletOpenMutex.protect(() async { + final existing = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); + if (existing != null && existing.isNotEmpty) return existing; - final config = await _getRealConfig(); - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } - final opened = await libMwc.openWallet(config: config, password: password); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: opened, - ); - return opened; + final config = await _getRealConfig(); + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + final opened = await libMwc.openWallet( + config: config, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: opened, + ); + return opened; + }); } /// Returns an empty String on success, error message on failure. @@ -894,14 +900,17 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open wallet - encodedWallet = await libMwc.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: encodedWallet, - ); + encodedWallet = await _walletOpenMutex.protect(() async { + final opened = await libMwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: opened, + ); + return opened; + }); //Store MwcMqs address info await _generateAndStoreReceivingAddressForIndex(0); @@ -935,14 +944,16 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_password', ); - final walletOpen = await libMwc.openWallet( - config: config, - password: password!, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); + await _walletOpenMutex.protect(() async { + final walletOpen = await libMwc.openWallet( + config: config, + password: password!, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + }); await updateNode(); } catch (e, s) { @@ -1144,14 +1155,16 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open Wallet - final walletOpen = await libMwc.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); + await _walletOpenMutex.protect(() async { + final walletOpen = await libMwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + }); await _generateAndStoreReceivingAddressForIndex( mimblewimblecoinData.receivingIndex, From f9c83756f9039c6c5511cc230beaabae5c234a68 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 17:40:23 -0600 Subject: [PATCH 02/17] fix(mwc): write .api_secret for default node authentication --- crypto_plugins/flutter_libmwc | 2 +- .../wallet/impl/mimblewimblecoin_wallet.dart | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index cf9406505..dc62d292f 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit cf94065052b0ff2fd92c6c5b0fc6baed8569ef48 +Subproject commit dc62d292f4ff077ca057585edec4d45dd474aba7 diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index dad7b3632..a577b03ff 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -582,6 +582,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String nodeApiAddress = uri.toString(); final walletDir = await _currentWalletDirPath(); + await _ensureApiSecret(walletDir, nodeApiAddress); + final Map config = {}; config["wallet_dir"] = walletDir; config["check_node_api_http_addr"] = nodeApiAddress; @@ -591,6 +593,21 @@ class MimblewimblecoinWallet extends Bip39Wallet { return stringConfig; } + /// Write the node API secret to .api_secret in the wallet directory so that + /// the Rust HTTPNodeClient can authenticate to the MWC node. + Future _ensureApiSecret(String walletDir, String nodeUrl) async { + const defaultNodeHost = 'mwc713.mwc.mw'; + const defaultNodeSecret = '11ne3EAUtOXVKwhxm84U'; + + final file = File('$walletDir/.api_secret'); + if (nodeUrl.contains(defaultNodeHost)) { + await Directory(walletDir).create(recursive: true); + await file.writeAsString(defaultNodeSecret); + } else if (await file.exists()) { + await file.delete(); + } + } + Future _currentWalletDirPath() async { final Directory appDir = await StackFileSystem.applicationRootDirectory(); From fb6542a5fb1b773a0783b8aa2fb6dea7f9233890 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 3 Mar 2026 17:47:58 -0600 Subject: [PATCH 03/17] refactor(mwc): merge flutter_libmwc#fix/global-chain-type-race --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index dc62d292f..f5ad0a99a 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit dc62d292f4ff077ca057585edec4d45dd474aba7 +Subproject commit f5ad0a99a1781f600742095fee0e47057eafd9c0 From f8cc879d58a6171b95a0321d5235cb10f7202a38 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 6 Mar 2026 14:40:30 -0600 Subject: [PATCH 04/17] feat(model): add nodeApiSecret field to NodeModel --- lib/models/node_model.dart | 6 ++++++ lib/models/type_adaptors/node_model.g.dart | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/models/node_model.dart b/lib/models/node_model.dart index 5d43d84db..5386cae0f 100644 --- a/lib/models/node_model.dart +++ b/lib/models/node_model.dart @@ -47,6 +47,8 @@ class NodeModel { final bool forceNoTor; // @HiveField(14) final bool isPrimary; + // @HiveField(15) + final String? nodeApiSecret; NodeModel({ required this.host, @@ -64,6 +66,7 @@ class NodeModel { this.forceNoTor = false, this.loginName, this.trusted, + this.nodeApiSecret, }); NodeModel copyWith({ @@ -81,6 +84,7 @@ class NodeModel { bool? forceNoTor, bool? clearnetEnabled, bool? isPrimary, + String? nodeApiSecret, }) { return NodeModel( host: host ?? this.host, @@ -98,6 +102,7 @@ class NodeModel { clearnetEnabled: clearnetEnabled ?? this.clearnetEnabled, forceNoTor: forceNoTor ?? this.forceNoTor, isPrimary: isPrimary ?? this.isPrimary, + nodeApiSecret: nodeApiSecret ?? this.nodeApiSecret, ); } @@ -123,6 +128,7 @@ class NodeModel { map['clearEnabled'] = clearnetEnabled; map['forceNoTor'] = forceNoTor; map['isPrimary'] = isPrimary; + map['nodeApiSecret'] = nodeApiSecret; return map; } diff --git a/lib/models/type_adaptors/node_model.g.dart b/lib/models/type_adaptors/node_model.g.dart index 218731a69..1ab826d55 100644 --- a/lib/models/type_adaptors/node_model.g.dart +++ b/lib/models/type_adaptors/node_model.g.dart @@ -32,13 +32,14 @@ class NodeModelAdapter extends TypeAdapter { clearnetEnabled: fields[12] as bool? ?? true, forceNoTor: fields[13] as bool? ?? false, isPrimary: fields[14] as bool? ?? false, + nodeApiSecret: fields[15] as String?, ); } @override void write(BinaryWriter writer, NodeModel obj) { writer - ..writeByte(15) + ..writeByte(16) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -68,7 +69,9 @@ class NodeModelAdapter extends TypeAdapter { ..writeByte(13) ..write(obj.forceNoTor) ..writeByte(14) - ..write(obj.isPrimary); + ..write(obj.isPrimary) + ..writeByte(15) + ..write(obj.nodeApiSecret); } @override From b10ef7982dda26ce4f1ed6e3b03c978a44973fe4 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 6 Mar 2026 14:40:41 -0600 Subject: [PATCH 05/17] feat(mwc): set nodeApiSecret on default MWC node --- lib/wallets/crypto_currency/coins/mimblewimblecoin.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index c9d57878a..2ed274b35 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -101,6 +101,7 @@ class Mimblewimblecoin extends Bip39Currency { torEnabled: true, clearnetEnabled: true, isPrimary: true, + nodeApiSecret: '11ne3EAUtOXVKwhxm84U', ); default: From 6170e8cd57aaac054975ea6adbe6f238441e1166 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 6 Mar 2026 14:40:57 -0600 Subject: [PATCH 06/17] refactor(mwc): read API secret from NodeModel instead of hardcoding --- .../add_edit_node_view.dart | 2 +- lib/utilities/test_mwcmqs_connection.dart | 10 +++---- .../wallet/impl/mimblewimblecoin_wallet.dart | 30 +++++++++---------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 75411e9cb..b05815e0f 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -751,7 +751,7 @@ class _AddEditNodeViewState extends ConsumerState { } class NodeFormData { - String? name, host, login, password; + String? name, host, login, password, apiSecret; int? port; bool? useSSL, isFailover, trusted, forceNoTor, isPrimary; TorPlainNetworkOption? netOption; diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart index c199afcbf..48860df3c 100644 --- a/lib/utilities/test_mwcmqs_connection.dart +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -17,15 +17,13 @@ import '../services/tor_service.dart'; import 'logger.dart'; import 'prefs.dart'; -Future _testMwcMqsNodeConnection(Uri uri) async { +Future _testMwcMqsNodeConnection(Uri uri, {String? apiSecret}) async { final HTTP client = HTTP(); try { final headers = {'Content-Type': 'application/json'}; - if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') { - const username = 'mwcmain'; - const password = '11ne3EAUtOXVKwhxm84U'; - final credentials = base64Encode(utf8.encode('$username:$password')); + if (apiSecret != null) { + final credentials = base64Encode(utf8.encode('mwcmain:$apiSecret')); headers['Authorization'] = 'Basic $credentials'; } final response = await client @@ -80,7 +78,7 @@ Future testMwcNodeConnection(NodeFormData data) async { uri = uri.replace(port: data.port); try { - if (await _testMwcMqsNodeConnection(uri)) { + if (await _testMwcMqsNodeConnection(uri, apiSecret: data.apiSecret)) { return data; } else { return null; diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index a577b03ff..d70346306 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -569,6 +569,17 @@ class MimblewimblecoinWallet extends Bip39Wallet { // ================= Private ================================================= + Future _ensureApiSecret(String walletDir) async { + final file = File('$walletDir/.api_secret'); + final secret = _mimblewimblecoinNode?.nodeApiSecret; + if (secret != null) { + await Directory(walletDir).create(recursive: true); + await file.writeAsString(secret); + } else if (await file.exists()) { + await file.delete(); + } + } + Future _getConfig() async { if (_mimblewimblecoinNode == null) { await updateNode(); @@ -582,7 +593,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String nodeApiAddress = uri.toString(); final walletDir = await _currentWalletDirPath(); - await _ensureApiSecret(walletDir, nodeApiAddress); + await _ensureApiSecret(walletDir); final Map config = {}; config["wallet_dir"] = walletDir; @@ -593,20 +604,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { return stringConfig; } - /// Write the node API secret to .api_secret in the wallet directory so that - /// the Rust HTTPNodeClient can authenticate to the MWC node. - Future _ensureApiSecret(String walletDir, String nodeUrl) async { - const defaultNodeHost = 'mwc713.mwc.mw'; - const defaultNodeSecret = '11ne3EAUtOXVKwhxm84U'; - - final file = File('$walletDir/.api_secret'); - if (nodeUrl.contains(defaultNodeHost)) { - await Directory(walletDir).create(recursive: true); - await file.writeAsString(defaultNodeSecret); - } else if (await file.exists()) { - await file.delete(); - } - } Future _currentWalletDirPath() async { final Directory appDir = await StackFileSystem.applicationRootDirectory(); @@ -1535,7 +1532,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { NodeFormData() ..host = node!.host ..useSSL = node.useSSL - ..port = node.port, + ..port = node.port + ..apiSecret = node.nodeApiSecret, ) != null; } catch (e, s) { From 9dd3d96d8df437df33f448661c3e0b5c4f559291 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 6 Mar 2026 14:41:04 -0600 Subject: [PATCH 07/17] chore(db): bump data version 15->16 for nodeApiSecret field --- lib/db/db_version_migration.dart | 14 ++++++++++++++ lib/utilities/constants.dart | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index ab2b8bcde..c0deb8eb8 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -376,6 +376,20 @@ class DbVersionMigrator with WalletDB { // try to continue migrating return await migrate(15, secureStore: secureStore); + case 15: + // No-op: nodeApiSecret field added to NodeModel (Hive field 15). + // Existing nodes read null; updateDefaults() backfills from defaultNode. + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, + key: "hive_data_version", + value: 16, + ); + + // try to continue migrating + return await migrate(16, secureStore: secureStore); + default: // finally return return; diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 69205f1f0..f7b495ee0 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -40,7 +40,7 @@ abstract class Constants { // Enable Logger.print statements static const bool disableLogger = false; - static const int currentDataVersion = 15; + static const int currentDataVersion = 16; static const int rescanV1 = 1; From 22a609f8853fe8b97880ec0ed09e1c47fbe47b98 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 26 Apr 2026 19:45:21 -0500 Subject: [PATCH 08/17] refactor(mwc): consolidate openWallet calls through _ensureWalletOpen --- .../wallet/impl/mimblewimblecoin_wallet.dart | 53 ++++--------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index d70346306..69f925c64 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -109,10 +109,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (password == null) { throw Exception('Wallet password not found'); } - final opened = await libMwc.openWallet( - config: config, - password: password, - ); + final opened = await libMwc + .openWallet(config: config, password: password) + .timeout( + const Duration(seconds: 60), + onTimeout: () => throw TimeoutException('openWallet timed out'), + ); await secureStorageInterface.write( key: '${walletId}_wallet', value: opened, @@ -914,17 +916,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open wallet - encodedWallet = await _walletOpenMutex.protect(() async { - final opened = await libMwc.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: opened, - ); - return opened; - }); + encodedWallet = await _ensureWalletOpen(); //Store MwcMqs address info await _generateAndStoreReceivingAddressForIndex(0); @@ -949,25 +941,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } else { try { - final config = await _getRealConfig(); - //if (!_logsInitialized) { - // await libMwc.initLogs(config: config); - // _logsInitialized = true; // Set flag to true after initializing - //} - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - - await _walletOpenMutex.protect(() async { - final walletOpen = await libMwc.openWallet( - config: config, - password: password!, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); - }); + await _ensureWalletOpen(); await updateNode(); } catch (e, s) { @@ -1169,16 +1143,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open Wallet - await _walletOpenMutex.protect(() async { - final walletOpen = await libMwc.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); - }); + await _ensureWalletOpen(); await _generateAndStoreReceivingAddressForIndex( mimblewimblecoinData.receivingIndex, From 4f93355e50f1160f117bd691e09d7e329b380a86 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 26 Apr 2026 21:22:51 -0500 Subject: [PATCH 09/17] chore: dart format fix/mwc `dart format $(git diff --name-only origin/staging...HEAD | grep '\.dart$')` --- lib/db/db_version_migration.dart | 106 ++++++++---------- .../wallet/impl/mimblewimblecoin_wallet.dart | 1 - 2 files changed, 48 insertions(+), 59 deletions(-) diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index c0deb8eb8..8b56d4e0b 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -170,12 +170,11 @@ class DbVersionMigrator with WalletDB { final count = await MainDB.instance.isar.addresses.count(); // add change/receiving tags to address labels for (var i = 0; i < count; i += 50) { - final addresses = - await MainDB.instance.isar.addresses - .where() - .offset(i) - .limit(50) - .findAll(); + final addresses = await MainDB.instance.isar.addresses + .where() + .offset(i) + .limit(50) + .findAll(); final List labels = []; for (final address in addresses) { @@ -203,14 +202,13 @@ class DbVersionMigrator with WalletDB { // update/create label if tags is not empty if (tags != null) { - isar_models.AddressLabel? label = - await MainDB.instance.isar.addressLabels - .where() - .addressStringWalletIdEqualTo( - address.value, - address.walletId, - ) - .findFirst(); + isar_models.AddressLabel? label = await MainDB + .instance + .isar + .addressLabels + .where() + .addressStringWalletIdEqualTo(address.value, address.walletId) + .findFirst(); if (label == null) { label = isar_models.AddressLabel( walletId: address.walletId, @@ -268,13 +266,12 @@ class DbVersionMigrator with WalletDB { Bitcoincash(CryptoCurrencyNetwork.main).identifier || info.coinIdentifier == Bitcoincash(CryptoCurrencyNetwork.test).identifier) { - final ids = - await MainDB.instance - .getAddresses(walletId) - .filter() - .typeEqualTo(isar_models.AddressType.p2sh) - .idProperty() - .findAll(); + final ids = await MainDB.instance + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2sh) + .idProperty() + .findAll(); await MainDB.instance.isar.writeTxn(() async { await MainDB.instance.isar.addresses.deleteAll(ids); @@ -435,17 +432,15 @@ class DbVersionMigrator with WalletDB { walletId: walletId, txid: tx.txid, timestamp: tx.timestamp, - type: - isIncoming - ? isar_models.TransactionType.incoming - : isar_models.TransactionType.outgoing, + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, subType: isar_models.TransactionSubType.none, amount: tx.amount, - amountString: - Amount( - rawValue: BigInt.from(tx.amount), - fractionDigits: epic.fractionDigits, - ).toJsonString(), + amountString: Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: epic.fractionDigits, + ).toJsonString(), fee: tx.fees, height: tx.height, isCancelled: tx.isCancelled, @@ -467,14 +462,12 @@ class DbVersionMigrator with WalletDB { publicKey: [], derivationIndex: isIncoming ? rcvIndex : -1, derivationPath: null, - type: - isIncoming - ? isar_models.AddressType.mimbleWimble - : isar_models.AddressType.unknown, - subType: - isIncoming - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.unknown, + type: isIncoming + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.unknown, + subType: isIncoming + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.unknown, ); transactionsData.add(Tuple2(iTx, address)); } @@ -532,28 +525,25 @@ class DbVersionMigrator with WalletDB { final crypto = AppConfig.getCryptoCurrencyFor(info.coinIdentifier)!; for (var i = 0; i < count; i += 50) { - final txns = - await MainDB.instance - .getTransactions(walletId) - .offset(i) - .limit(50) - .findAll(); + final txns = await MainDB.instance + .getTransactions(walletId) + .offset(i) + .limit(50) + .findAll(); // migrate amount to serialized amount string - final txnsData = - txns - .map( - (tx) => Tuple2( - tx - ..amountString = - Amount( - rawValue: BigInt.from(tx.amount), - fractionDigits: crypto.fractionDigits, - ).toJsonString(), - tx.address.value, - ), - ) - .toList(); + final txnsData = txns + .map( + (tx) => Tuple2( + tx + ..amountString = Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: crypto.fractionDigits, + ).toJsonString(), + tx.address.value, + ), + ) + .toList(); // update db records await MainDB.instance.addNewTransactionData(txnsData, walletId); diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 69f925c64..9a7dbc53f 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -606,7 +606,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { return stringConfig; } - Future _currentWalletDirPath() async { final Directory appDir = await StackFileSystem.applicationRootDirectory(); From 72530c8ea8796bba4d81aaf49b223d5033c47bac Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 13 May 2026 17:03:58 -0500 Subject: [PATCH 10/17] feat(ui): persist nodeApiSecret to NodeModel on node save --- .../manage_nodes_views/add_edit_node_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index fae7fa5d4..62c1723b1 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -259,6 +259,7 @@ class _AddEditNodeViewState extends ConsumerState { clearnetEnabled: plainEnabled, forceNoTor: forceNoTor, isPrimary: false, + nodeApiSecret: formData.apiSecret, ); await ref @@ -288,6 +289,7 @@ class _AddEditNodeViewState extends ConsumerState { clearnetEnabled: plainEnabled, forceNoTor: forceNoTor, isPrimary: formData.isPrimary ?? false, + nodeApiSecret: formData.apiSecret, ); await ref From 47bac5f36a5e89258c24d74dd957644f31fede15 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 13 May 2026 17:04:59 -0500 Subject: [PATCH 11/17] feat(ui): expose configurable MWC API secret in add/edit node form chore: dart format fix/mwc --- .../add_edit_node_view.dart | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 62c1723b1..bd009f171 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -793,12 +793,14 @@ class _NodeFormState extends ConsumerState { late final TextEditingController _portController; late final TextEditingController _passwordController; late final TextEditingController _usernameController; + late final TextEditingController _apiSecretController; final _nameFocusNode = FocusNode(); final _passwordFocusNode = FocusNode(); final _portFocusNode = FocusNode(); final _hostFocusNode = FocusNode(); final _usernameFocusNode = FocusNode(); + final _apiSecretFocusNode = FocusNode(); bool _useSSL = false; bool _isFailover = false; @@ -860,6 +862,9 @@ class _NodeFormState extends ConsumerState { ref.read(nodeFormDataProvider).password = _passwordController.text.isEmpty ? null : _passwordController.text; + ref.read(nodeFormDataProvider).apiSecret = _apiSecretController.text.isEmpty + ? null + : _apiSecretController.text; ref.read(nodeFormDataProvider).port = port; ref.read(nodeFormDataProvider).useSSL = _useSSL; ref.read(nodeFormDataProvider).isFailover = _isFailover; @@ -878,6 +883,7 @@ class _NodeFormState extends ConsumerState { _portController = TextEditingController(); _passwordController = TextEditingController(); _usernameController = TextEditingController(); + _apiSecretController = TextEditingController(); enableAuthFields = _checkShouldEnableAuthFields(widget.coin); @@ -899,6 +905,7 @@ class _NodeFormState extends ConsumerState { _hostController.text = node.host; _portController.text = node.port.toString(); _usernameController.text = node.loginName ?? ""; + _apiSecretController.text = node.nodeApiSecret ?? ""; _useSSL = node.useSSL; _isFailover = node.isFailover; _trusted = node.trusted ?? false; @@ -942,12 +949,14 @@ class _NodeFormState extends ConsumerState { _portController.dispose(); _passwordController.dispose(); _usernameController.dispose(); + _apiSecretController.dispose(); _nameFocusNode.dispose(); _passwordFocusNode.dispose(); _usernameFocusNode.dispose(); _hostFocusNode.dispose(); _portFocusNode.dispose(); + _apiSecretFocusNode.dispose(); super.dispose(); } @@ -1246,6 +1255,54 @@ class _NodeFormState extends ConsumerState { ), ), if (enableAuthFields) const SizedBox(height: 8), + if (widget.coin is Mimblewimblecoin) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _apiSecretController, + readOnly: shouldBeReadOnly, + enabled: enableField(_apiSecretController), + obscureText: true, + focusNode: _apiSecretFocusNode, + style: STextStyles.field(context), + decoration: + standardInputDecoration( + "API secret (optional)", + _apiSecretFocusNode, + context, + ).copyWith( + suffixIcon: + !shouldBeReadOnly && + _apiSecretController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _apiSecretController.text = ""; + _updateState(); + }, + ), + ], + ), + ), + ) + : null, + ), + onChanged: (newValue) { + _updateState(); + setState(() {}); + }, + ), + ), + if (widget.coin is Mimblewimblecoin) const SizedBox(height: 8), if (widget.coin is! CryptonoteCurrency) Row( children: [ From c52294448090cdf066b09217aa6c4dc5b27c5ce8 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 13 May 2026 17:24:16 -0500 Subject: [PATCH 12/17] fix(mwc): bump flutter_libmwc for get_chain_height chain type fix diag(mwc): panic hook, listener fallback, and scanOutputs catch_unwind --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 931062f80..adfd177ab 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 931062f80d5da745ff1535a1ad03ecaae3f87c15 +Subproject commit adfd177ab6180648b7af127bccd3c24e47a03ea8 From 972a5b3a8163f400c0a25b601470e8aca67efe88 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 13 May 2026 18:01:05 -0500 Subject: [PATCH 13/17] diag(mwc): initialize MWC Rust trace logger on first wallet open --- .../wallet/impl/mimblewimblecoin_wallet.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 9a7dbc53f..58aedff6c 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -45,6 +45,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { NodeModel? _mimblewimblecoinNode; Timer? timer; + static bool _mwcLogsInitialized = false; + double highestPercent = 0; Future get getSyncPercent async { final int lastScannedBlock = @@ -103,6 +105,18 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (existing != null && existing.isNotEmpty) return existing; final config = await _getRealConfig(); + // Initialize MWC's own Rust logger once per process so trace-level + // output from scan()/listener lands in /mwc-wallet.log. + // This is invaluable when the native side crashes silently (SIGSEGV + // / abort) without leaving a Rust panic. + if (!_mwcLogsInitialized) { + try { + await libMwc.initLogs(config: config); + _mwcLogsInitialized = true; + } catch (e, s) { + Logging.instance.w("libMwc.initLogs failed: $e\n$s"); + } + } final password = await secureStorageInterface.read( key: '${walletId}_password', ); From febb52fec8d6f301fbd9f8008d42909ff77b4f3a Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 13 May 2026 18:05:24 -0500 Subject: [PATCH 14/17] fix(wl_gen): expose initLogs on LibMwcInterface --- lib/wl_gen/interfaces/libmwc_interface.dart | 2 ++ tool/wl_templates/MWC_libmwc_interface_impl.template.dart | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/lib/wl_gen/interfaces/libmwc_interface.dart b/lib/wl_gen/interfaces/libmwc_interface.dart index acd5b2d79..a44b3b4d2 100644 --- a/lib/wl_gen/interfaces/libmwc_interface.dart +++ b/lib/wl_gen/interfaces/libmwc_interface.dart @@ -140,6 +140,8 @@ abstract class LibMwcInterface { Future deleteWallet({required String wallet, required String config}); + Future initLogs({required String config}); + String getPluginVersion(); } diff --git a/tool/wl_templates/MWC_libmwc_interface_impl.template.dart b/tool/wl_templates/MWC_libmwc_interface_impl.template.dart index 0262cc5e3..c88c1662e 100644 --- a/tool/wl_templates/MWC_libmwc_interface_impl.template.dart +++ b/tool/wl_templates/MWC_libmwc_interface_impl.template.dart @@ -117,6 +117,11 @@ final class _LibMwcInterfaceImpl extends LibMwcInterface { return mimblewimblecoin.Libmwc.getChainHeight(config: config); } + @override + Future initLogs({required String config}) { + return mimblewimblecoin.Libmwc.initLogs(config: config); + } + @override Future<({int fee, bool strategyUseAll, int total})> getTransactionFees({ required String wallet, From 47ad670bc67a136d844174da1a32130edcca555a Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 13 May 2026 18:11:22 -0500 Subject: [PATCH 15/17] fix(mwc): discard stale wallet handle from previous process --- .../wallet/impl/mimblewimblecoin_wallet.dart | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 58aedff6c..a3f0c7e22 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -47,6 +47,14 @@ class MimblewimblecoinWallet extends Bip39Wallet { static bool _mwcLogsInitialized = false; + /// Tracks wallets that have been openWallet'd in *this* process. The + /// `${walletId}_wallet` value in secure storage is a serialized Rust + /// pointer (u64): it persists across launches via libsecret but only + /// dereferences safely inside the process that wrote it. Anything in + /// secure storage from a previous process is a dangling pointer that + /// will SIGSEGV the host on FFI use. + static final Set _openedInProcess = {}; + double highestPercent = 0; Future get getSyncPercent async { final int lastScannedBlock = @@ -99,10 +107,21 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future _ensureWalletOpen() async { return await _walletOpenMutex.protect(() async { - final existing = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - if (existing != null && existing.isNotEmpty) return existing; + if (_openedInProcess.contains(walletId)) { + // We opened this wallet earlier in *this* process; secure storage + // holds the live handle. + final existing = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); + if (existing != null && existing.isNotEmpty) return existing; + } else { + // Whatever is in secure storage is a serialized Rust pointer from a + // previous process. Dereferencing it in this process crashes the + // host on the next FFI call (e.g. scanOutputs). Drop it so callers + // that read `${walletId}_wallet` directly pick up the fresh handle + // we're about to write. + await secureStorageInterface.delete(key: '${walletId}_wallet'); + } final config = await _getRealConfig(); // Initialize MWC's own Rust logger once per process so trace-level @@ -133,6 +152,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', value: opened, ); + _openedInProcess.add(walletId); return opened; }); } From e167e82adb246f8111d9c04897dedcc5c98e16ee Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 14 May 2026 10:32:28 -0500 Subject: [PATCH 16/17] refactor(mwc): keep wallet handle in instance var, not secure storage chore(mwc): trim comments around wallet-handle refactor --- .../wallet/impl/mimblewimblecoin_wallet.dart | 143 ++++++------------ 1 file changed, 49 insertions(+), 94 deletions(-) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index a3f0c7e22..a5f74581e 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -47,13 +47,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { static bool _mwcLogsInitialized = false; - /// Tracks wallets that have been openWallet'd in *this* process. The - /// `${walletId}_wallet` value in secure storage is a serialized Rust - /// pointer (u64): it persists across launches via libsecret but only - /// dereferences safely inside the process that wrote it. Anything in - /// secure storage from a previous process is a dangling pointer that - /// will SIGSEGV the host on FFI use. - static final Set _openedInProcess = {}; + // Process-scoped Rust pointer; do not persist. + String? _walletHandle; double highestPercent = 0; Future get getSyncPercent async { @@ -86,12 +81,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { value: stringConfig, ); - // Restart MWCMQS listener with new configuration if wallet has a handle. try { - final handle = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - if (handle != null && handle.isNotEmpty) { + if (_walletHandle != null) { await stopSlatepackListener(); await startSlatepackListener(); Logging.instance.i( @@ -107,27 +98,13 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future _ensureWalletOpen() async { return await _walletOpenMutex.protect(() async { - if (_openedInProcess.contains(walletId)) { - // We opened this wallet earlier in *this* process; secure storage - // holds the live handle. - final existing = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - if (existing != null && existing.isNotEmpty) return existing; - } else { - // Whatever is in secure storage is a serialized Rust pointer from a - // previous process. Dereferencing it in this process crashes the - // host on the next FFI call (e.g. scanOutputs). Drop it so callers - // that read `${walletId}_wallet` directly pick up the fresh handle - // we're about to write. - await secureStorageInterface.delete(key: '${walletId}_wallet'); - } + final cached = _walletHandle; + if (cached != null && cached.isNotEmpty) return cached; + + // Drop stale pointer left by pre-instance-var builds. + await secureStorageInterface.delete(key: '${walletId}_wallet'); final config = await _getRealConfig(); - // Initialize MWC's own Rust logger once per process so trace-level - // output from scan()/listener lands in /mwc-wallet.log. - // This is invaluable when the native side crashes silently (SIGSEGV - // / abort) without leaving a Rust panic. if (!_mwcLogsInitialized) { try { await libMwc.initLogs(config: config); @@ -148,11 +125,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { const Duration(seconds: 60), onTimeout: () => throw TimeoutException('openWallet timed out'), ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: opened, - ); - _openedInProcess.add(walletId); + _walletHandle = opened; return opened; }); } @@ -160,9 +133,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Returns an empty String on success, error message on failure. Future cancelPendingTransactionAndPost(String txSlateId) async { try { - final String wallet = (await secureStorageInterface.read( - key: '${walletId}_wallet', - ))!; + final String wallet = await _ensureWalletOpen(); final result = await libMwc.cancelTransaction( wallet: wallet, @@ -295,9 +266,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Decode a slatepack. Future decodeSlatepack(String slatepack) async { try { - final handle = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); + final handle = _walletHandle; final result = handle != null ? await libMwc.decodeSlatepackWithWallet( wallet: handle, @@ -388,13 +357,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Start MWCMQS listener for automatic transaction processing. Future startSlatepackListener() async { try { - await _ensureWalletOpen(); + final wallet = await _ensureWalletOpen(); final mwcmqsConfig = await getMwcMqsConfig(); - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); libMwc.startMwcMqsListener( - wallet: wallet!, + wallet: wallet, mwcmqsConfig: mwcmqsConfig.toString(), ); } catch (e, s) { @@ -451,10 +417,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { > analyzeSlatepack(String slatepack) async { try { - // Get wallet handle if available - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); + final wallet = _walletHandle; // Decode the slatepack final decoded = wallet != null @@ -652,11 +615,11 @@ class MimblewimblecoinWallet extends Bip39Wallet { int satoshiAmount, { bool ifErrorEstimateFee = false, }) async { - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await _ensureWalletOpen(); try { final available = info.cachedBalance.spendable.raw.toInt(); final transactionFees = await libMwc.getTransactionFees( - wallet: wallet!, + wallet: wallet, amount: satoshiAmount, minimumConfirmations: cryptoCurrency.minConfirms, available: available, @@ -680,13 +643,13 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future _startSync() async { Logging.instance.i("request start sync"); - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await _ensureWalletOpen(); const int refreshFromNode = 1; if (!syncMutex.isLocked) { await syncMutex.protect(() async { // How does getWalletBalances start syncing???? await libMwc.getWalletBalances( - wallet: wallet!, + wallet: wallet, refreshFromNode: refreshFromNode, minimumConfirmations: 10, ); @@ -705,10 +668,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { }) > _allWalletBalances() async { - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await _ensureWalletOpen(); const refreshFromNode = 0; return await libMwc.getWalletBalances( - wallet: wallet!, + wallet: wallet, refreshFromNode: refreshFromNode, minimumConfirmations: cryptoCurrency.minConfirms, ); @@ -773,10 +736,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { int index, MwcMqsConfigModel mwcmqsConfig, ) async { - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await _ensureWalletOpen(); final walletAddress = await libMwc.getAddressInfo( - wallet: wallet!, + wallet: wallet, index: index, ); @@ -799,9 +762,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { //First stop the current listener libMwc.stopMwcMqsListener(); - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); + final wallet = await _ensureWalletOpen(); // max number of blocks to scan per loop iteration const scanChunkSize = 10000; @@ -821,7 +782,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); final int nextScannedBlock = await libMwc.scanOutputs( - wallet: wallet!, + wallet: wallet, startHeight: lastScannedBlock, numberOfBlocks: scanChunkSize, ); @@ -853,10 +814,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future _listenToMwcmqs() async { Logging.instance.i("STARTING WALLET LISTENER ...."); - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await _ensureWalletOpen(); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); libMwc.startMwcMqsListener( - wallet: wallet!, + wallet: wallet, mwcmqsConfig: mwcmqsConfig.toString(), ); } @@ -910,22 +871,19 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { if (isRestore != true) { - String? encodedWallet = await secureStorageInterface.read( - key: "${walletId}_wallet", + // Password presence is the durable "wallet provisioned" marker; the + // old wallet-handle marker was process-scoped. + final existingPassword = await secureStorageInterface.read( + key: '${walletId}_password', ); - // check if should create a new wallet - if (encodedWallet == null) { + if (existingPassword == null) { await updateNode(); final mnemonicString = await getMnemonic(); final String password = generatePassword(); final String stringConfig = await _getConfig(); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); - //if (!_logsInitialized) { - // await libMwc.initLogs(config: stringConfig); - // _logsInitialized = true; // Set flag to true after initializing - // } await secureStorageInterface.write( key: '${walletId}_config', value: stringConfig, @@ -949,7 +907,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open wallet - encodedWallet = await _ensureWalletOpen(); + await _ensureWalletOpen(); //Store MwcMqs address info await _generateAndStoreReceivingAddressForIndex(0); @@ -990,9 +948,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future confirmSend({required TxData txData}) async { try { - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); + final wallet = await _ensureWalletOpen(); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); // TODO determine whether it is worth sending change to a change address. @@ -1015,7 +971,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { transaction = await libMwc.txHttpSend( - wallet: wallet!, + wallet: wallet, selectionStrategyIsAll: 0, minimumConfirmations: cryptoCurrency.minConfirms, message: txData.noteOnChain ?? "", @@ -1024,7 +980,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } else if (receiverAddress.startsWith("mwcmqs://")) { transaction = await libMwc.createTransaction( - wallet: wallet!, + wallet: wallet, amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, secretKeyIndex: 0, @@ -1342,9 +1298,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future updateTransactions() async { try { - final wallet = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); + final wallet = await _ensureWalletOpen(); const refreshFromNode = 1; final myAddresses = await mainDB @@ -1360,7 +1314,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final myAddressesSet = myAddresses.toSet(); final transactions = await libMwc.getTransactions( - wallet: wallet!, + wallet: wallet, refreshFromNode: refreshFromNode, ); @@ -1596,8 +1550,12 @@ Future deleteMimblewimblecoinWallet({ required String walletId, required SecureStorageInterface secureStore, }) async { - final wallet = await secureStore.read(key: '${walletId}_wallet'); + await secureStore.delete(key: '${walletId}_wallet'); + String? config = await secureStore.read(key: '${walletId}_config'); + if (config == null) { + return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; + } if (Platform.isIOS) { final Directory appDir = await StackFileSystem.applicationRootDirectory(); @@ -1605,20 +1563,17 @@ Future deleteMimblewimblecoinWallet({ final String name = walletId.trim(); final walletDir = '$path/$name'; - final editConfig = jsonDecode(config as String); + final editConfig = jsonDecode(config); editConfig["wallet_dir"] = walletDir; config = jsonEncode(editConfig); } - if (wallet == null) { - return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; - } else { - try { - return libMwc.deleteWallet(wallet: wallet, config: config!); - } catch (e, s) { - Logging.instance.e("$e\n$s"); - return "deleteMimblewimblecoinWallet($walletId) failed..."; - } + try { + // Rust deleteWallet ignores the handle param. + return libMwc.deleteWallet(wallet: "", config: config); + } catch (e, s) { + Logging.instance.e("$e\n$s"); + return "deleteMimblewimblecoinWallet($walletId) failed..."; } } From 962755f26cd4009c6788361bf5e09a6582c02a5e Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 14 May 2026 10:48:07 -0500 Subject: [PATCH 17/17] chore(mwc): bump flutter_libmwc --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index adfd177ab..d74b89b75 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit adfd177ab6180648b7af127bccd3c24e47a03ea8 +Subproject commit d74b89b75863ce2d20e54c9c28c4c18111184e28