Rebase node-sqlite-migration onto dev

Squashed migration of node Hive→SQLite, default-node handling,
isPow/proxy support, arbitrum node updates, and related fixes.
Replays node-sqlite-migration on top of current dev without the
amount-refactor merge that the original branch carried.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Omar
2026-05-26 17:04:36 +02:00
co-authored by Claude Opus 4.7
parent f3de4b2588
commit ac05166e95
72 changed files with 1205 additions and 1056 deletions
@@ -5,10 +5,11 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
import 'package:collection/collection.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -19,7 +20,7 @@ class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
abstract class NodeCreateOrEditViewModelBase with Store {
NodeCreateOrEditViewModelBase(
this._nodeSource, this.walletType, this.editingNode, this._settingsStore)
this.isPow, this.nodeListViewModel,this.powNodeListViewModel, this.walletType, this._settingsStore, {this.editingNode})
: state = InitialExecutionState(),
connectionState = InitialExecutionState(),
label = editingNode?.label ?? '',
@@ -36,8 +37,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
editingNode?.isEnabledForAutoSwitching ?? false,
useSocksProxy = editingNode?.socksProxyAddress != null &&
editingNode!.socksProxyAddress!.isNotEmpty,
useSSL = editingNode?.useSSL ?? false {
}
useSSL = editingNode?.useSSL ?? false {}
final nodeLabelUIKey = 'node_label_row_key';
final nodeAddressUIKey = 'node_address_row_key';
@@ -110,6 +110,15 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@observable
String socksProxyAddress;
@observable
Node? editingNode;
@observable
bool isPow;
final NodeListViewModel? nodeListViewModel;
final PowNodeListViewModel? powNodeListViewModel;
@computed
bool get isReady =>
(address.isNotEmpty) ||
@@ -158,8 +167,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
final WalletType walletType;
final Node? editingNode;
final Box<Node> _nodeSource;
final SettingsStore _settingsStore;
void updateViewModelFromText(String key, String value) {
@@ -248,33 +255,45 @@ abstract class NodeCreateOrEditViewModelBase with Store {
void setSocksProxyAddress(String val) => socksProxyAddress = val;
@action
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
final node = Node(
label: label,
uri: uri,
path: path,
type: walletType,
login: login,
password: password,
useSSL: useSSL,
trusted: trusted,
isEnabledForAutoSwitching: isEnabledForAutoSwitching,
socksProxyAddress: socksProxyAddress);
Future<void> delete({required Node editingNode}) async {
await editingNode.delete();
if(nodeListViewModel != null) {
nodeListViewModel!.bindNodes();
}
if(powNodeListViewModel != null) {
powNodeListViewModel!.bindNodes();
}
}
@action
Future<void> save({bool saveAsCurrent = false}) async {
editingNode ??= Node();
editingNode!.type = walletType;
editingNode!.label = label;
editingNode!.uriRaw = uri;
editingNode!.path = path;
editingNode!.login = login;
editingNode!.password = password;
editingNode!.isPow = isPow;
editingNode!.useSSL = useSSL;
editingNode!.trusted = trusted;
editingNode!.socksProxyAddress = socksProxyAddress;
try {
state = IsExecutingState();
if (editingNode != null) {
await _nodeSource.put(editingNode.key, node);
} else if (_existingNode(node) != null) {
setAsCurrent(_existingNode(node)!);
} else {
await _nodeSource.add(node);
setAsCurrent(_nodeSource.values.last);
}
await editingNode!.save();
if (saveAsCurrent) {
setAsCurrent(node);
setAsCurrent(editingNode!);
}
state = ExecutedSuccessfullyState();
if(nodeListViewModel != null) {
nodeListViewModel!.bindNodes();
}
if(powNodeListViewModel != null) {
powNodeListViewModel!.bindNodes();
}
} catch (e) {
state = FailureState(e.toString());
}
@@ -301,15 +320,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
}
Node? _existingNode(Node node) {
final nodes = _nodeSource.values.toList();
nodes.forEach((item) {
item.login ??= '';
item.password ??= '';
item.useSSL ??= false;
});
return nodes.firstWhereOrNull((item) => item == node);
}
@action
void setAsCurrent(Node node) => _settingsStore.nodes[walletType] = node;
@@ -1,8 +1,7 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cw_core/node_list.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -18,13 +17,13 @@ part 'node_list_view_model.g.dart';
class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel;
abstract class NodeListViewModelBase with Store {
NodeListViewModelBase(this._nodeSource, this._appStore)
NodeListViewModelBase(this._appStore)
: nodes = ObservableList<Node>(),
settingsStore = _appStore.settingsStore {
_bindNodes();
bindNodes();
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
_bindNodes();
bindNodes();
});
reaction((_) {
@@ -35,7 +34,7 @@ abstract class NodeListViewModelBase with Store {
}
return null;
}, (_) {
_bindNodes();
bindNodes();
});
}
@@ -73,24 +72,24 @@ abstract class NodeListViewModelBase with Store {
final ObservableList<Node> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
final AppStore _appStore;
Future<void> reset() async {
await resetToDefault(_nodeSource);
await resetToDefault();
final wallet = _appStore.wallet!;
final walletType = wallet.type;
Node node;
if (walletType == WalletType.bitcoin && wallet.isTestnet) {
node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
node = (await getBitcoinTestnetDefaultElectrumServer())!;
} else if (isEVMCompatibleChain(walletType)) {
final chainId = evm!.getSelectedChainId(wallet);
if (chainId != null) {
final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
if (nodeWalletType != null) {
node = getDefaultNode(nodes: _nodeSource, type: nodeWalletType)!;
node = (await Node.getDefaultForWalletType(nodeWalletType))!;
} else {
throw Exception(
'Cannot reset node for EVM wallet: wallet type not found for chainId: $chainId');
@@ -99,7 +98,7 @@ abstract class NodeListViewModelBase with Store {
throw Exception('Cannot reset node for EVM wallet: chainId is null');
}
} else {
node = getDefaultNode(nodes: _nodeSource, type: walletType)!;
node = (await Node.getDefaultForWalletType(_appStore.wallet!.type))!;
}
await setAsCurrent(node);
@@ -130,7 +129,7 @@ abstract class NodeListViewModelBase with Store {
}
@action
void _bindNodes() {
Future<void> bindNodes() async {
nodes.clear();
final wallet = _appStore.wallet!;
final walletType = wallet.type;
@@ -141,11 +140,8 @@ abstract class NodeListViewModelBase with Store {
if (chainId != null) {
final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
if (nodeWalletType != null) {
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == nodeWalletType,
initialFire: true,
);
nodes.addAll(await Node.getAllForWalletType(nodeWalletType));
return;
}
}
@@ -154,10 +150,7 @@ abstract class NodeListViewModelBase with Store {
}
// For non-EVM wallets, use the wallet type directly
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == walletType,
initialFire: true,
);
nodes.addAll(await Node.getAllForWalletType(walletType));
}
}
@@ -1,6 +1,7 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cw_core/node_list.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@@ -16,13 +17,13 @@ part 'pow_node_list_view_model.g.dart';
class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel;
abstract class PowNodeListViewModelBase with Store {
PowNodeListViewModelBase(this._nodeSource, this._appStore)
PowNodeListViewModelBase(this._appStore)
: nodes = ObservableList<Node>(),
settingsStore = _appStore.settingsStore {
_bindNodes();
bindNodes();
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
_bindNodes();
bindNodes();
});
}
@@ -50,17 +51,16 @@ abstract class PowNodeListViewModelBase with Store {
final ObservableList<Node> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
final AppStore _appStore;
Future<void> reset() async {
await resetPowToDefault(_nodeSource);
await resetPowToDefault();
Node node;
switch (_appStore.wallet!.type) {
case WalletType.nano:
node = getNanoDefaultPowNode(nodes: _nodeSource)!;
node = (await getNanoDefaultPowNode())!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type} for pow node list');
@@ -76,12 +76,8 @@ abstract class PowNodeListViewModelBase with Store {
settingsStore.powNodes[_appStore.wallet!.type] = node;
@action
void _bindNodes() {
Future<void> bindNodes() async {
nodes.clear();
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == _appStore.wallet!.type,
initialFire: true,
);
nodes.addAll(await Node.getAllPow());
}
}