Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d03e27b
fix(mwc): serialize openWallet FFI calls with mutex
sneurlax Mar 1, 2026
f9c8375
fix(mwc): write .api_secret for default node authentication
sneurlax Mar 3, 2026
fb6542a
refactor(mwc): merge flutter_libmwc#fix/global-chain-type-race
sneurlax Mar 3, 2026
f8cc879
feat(model): add nodeApiSecret field to NodeModel
sneurlax Mar 6, 2026
b10ef79
feat(mwc): set nodeApiSecret on default MWC node
sneurlax Mar 6, 2026
6170e8c
refactor(mwc): read API secret from NodeModel instead of hardcoding
sneurlax Mar 6, 2026
9dd3d96
chore(db): bump data version 15->16 for nodeApiSecret field
sneurlax Mar 6, 2026
22a609f
refactor(mwc): consolidate openWallet calls through _ensureWalletOpen
sneurlax Apr 27, 2026
4f93355
chore: dart format fix/mwc
sneurlax Apr 27, 2026
2dece0a
Merge remote-tracking branch 'origin/staging' into fix/mwc
sneurlax May 13, 2026
72530c8
feat(ui): persist nodeApiSecret to NodeModel on node save
sneurlax May 13, 2026
47bac5f
feat(ui): expose configurable MWC API secret in add/edit node form
sneurlax May 13, 2026
c522944
fix(mwc): bump flutter_libmwc for get_chain_height chain type fix
sneurlax May 13, 2026
972a5b3
diag(mwc): initialize MWC Rust trace logger on first wallet open
sneurlax May 13, 2026
febb52f
fix(wl_gen): expose initLogs on LibMwcInterface
sneurlax May 13, 2026
47ad670
fix(mwc): discard stale wallet handle from previous process
sneurlax May 13, 2026
3cfddb3
Merge branch 'staging' into fix/mwc
julian-CStack May 14, 2026
e167e82
refactor(mwc): keep wallet handle in instance var, not secure storage
sneurlax May 14, 2026
962755f
chore(mwc): bump flutter_libmwc
sneurlax May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crypto_plugins/flutter_libmwc
Submodule flutter_libmwc updated 1 files
+110 −28 rust/src/lib.rs
120 changes: 62 additions & 58 deletions lib/db/db_version_migration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<isar_models.AddressLabel> labels = [];
for (final address in addresses) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -376,6 +373,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<dynamic>(
boxName: DB.boxNameDBInfo,
key: "hive_data_version",
value: 16,
);

// try to continue migrating
return await migrate(16, secureStore: secureStore);

default:
// finally return
return;
Expand Down Expand Up @@ -421,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,
Expand All @@ -453,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));
}
Expand Down Expand Up @@ -518,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);
Expand Down
6 changes: 6 additions & 0 deletions lib/models/node_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class NodeModel {
final bool forceNoTor;
// @HiveField(14)
final bool isPrimary;
// @HiveField(15)
final String? nodeApiSecret;

NodeModel({
required this.host,
Expand All @@ -64,6 +66,7 @@ class NodeModel {
this.forceNoTor = false,
this.loginName,
this.trusted,
this.nodeApiSecret,
});

NodeModel copyWith({
Expand All @@ -81,6 +84,7 @@ class NodeModel {
bool? forceNoTor,
bool? clearnetEnabled,
bool? isPrimary,
String? nodeApiSecret,
}) {
return NodeModel(
host: host ?? this.host,
Expand All @@ -98,6 +102,7 @@ class NodeModel {
clearnetEnabled: clearnetEnabled ?? this.clearnetEnabled,
forceNoTor: forceNoTor ?? this.forceNoTor,
isPrimary: isPrimary ?? this.isPrimary,
nodeApiSecret: nodeApiSecret ?? this.nodeApiSecret,
);
}

Expand All @@ -123,6 +128,7 @@ class NodeModel {
map['clearEnabled'] = clearnetEnabled;
map['forceNoTor'] = forceNoTor;
map['isPrimary'] = isPrimary;
map['nodeApiSecret'] = nodeApiSecret;
return map;
}

Expand Down
7 changes: 5 additions & 2 deletions lib/models/type_adaptors/node_model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
clearnetEnabled: plainEnabled,
forceNoTor: forceNoTor,
isPrimary: false,
nodeApiSecret: formData.apiSecret,
);

await ref
Expand Down Expand Up @@ -288,6 +289,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
clearnetEnabled: plainEnabled,
forceNoTor: forceNoTor,
isPrimary: formData.isPrimary ?? false,
nodeApiSecret: formData.apiSecret,
);

await ref
Expand Down Expand Up @@ -750,7 +752,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
}

class NodeFormData {
String? name, host, login, password;
String? name, host, login, password, apiSecret;
int? port;
bool? useSSL, isFailover, trusted, forceNoTor, isPrimary;
TorPlainNetworkOption? netOption;
Expand Down Expand Up @@ -791,12 +793,14 @@ class _NodeFormState extends ConsumerState<NodeForm> {
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;
Expand Down Expand Up @@ -858,6 +862,9 @@ class _NodeFormState extends ConsumerState<NodeForm> {
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;
Expand All @@ -876,6 +883,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
_portController = TextEditingController();
_passwordController = TextEditingController();
_usernameController = TextEditingController();
_apiSecretController = TextEditingController();

enableAuthFields = _checkShouldEnableAuthFields(widget.coin);

Expand All @@ -897,6 +905,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
_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;
Expand Down Expand Up @@ -940,12 +949,14 @@ class _NodeFormState extends ConsumerState<NodeForm> {
_portController.dispose();
_passwordController.dispose();
_usernameController.dispose();
_apiSecretController.dispose();

_nameFocusNode.dispose();
_passwordFocusNode.dispose();
_usernameFocusNode.dispose();
_hostFocusNode.dispose();
_portFocusNode.dispose();
_apiSecretFocusNode.dispose();
super.dispose();
}

Expand Down Expand Up @@ -1244,6 +1255,54 @@ class _NodeFormState extends ConsumerState<NodeForm> {
),
),
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: [
Expand Down
2 changes: 1 addition & 1 deletion lib/utilities/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading
Loading