mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-29 06:38:45 +01:00
Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
@@ -3,12 +3,10 @@ import 'package:hash_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:hash_wallet/entities/qr_scanner.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:hash_wallet/utils/permission_handler.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/node.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';
|
||||
|
||||
@@ -17,8 +15,8 @@ part 'node_create_or_edit_view_model.g.dart';
|
||||
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel;
|
||||
|
||||
abstract class NodeCreateOrEditViewModelBase with Store {
|
||||
NodeCreateOrEditViewModelBase(
|
||||
this._nodeSource, this.walletType, this.editingNode, this._settingsStore)
|
||||
NodeCreateOrEditViewModelBase(this.isPow, this.walletType, this._settingsStore,
|
||||
{this.editingNode})
|
||||
: state = InitialExecutionState(),
|
||||
connectionState = InitialExecutionState(),
|
||||
label = editingNode?.label ?? '',
|
||||
@@ -106,6 +104,12 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
||||
@observable
|
||||
String socksProxyAddress;
|
||||
|
||||
@observable
|
||||
Node? editingNode;
|
||||
|
||||
@observable
|
||||
bool isPow;
|
||||
|
||||
@computed
|
||||
bool get isReady => address.isNotEmpty;
|
||||
|
||||
@@ -144,8 +148,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) {
|
||||
@@ -233,30 +235,29 @@ 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();
|
||||
}
|
||||
|
||||
@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();
|
||||
@@ -286,16 +287,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,13 +1,14 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/utils/mobx.dart';
|
||||
import 'package:cw_core/node_list.dart';
|
||||
import 'package:cw_core/utils/print_verbose.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:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:hash_wallet/entities/node_list.dart';
|
||||
import 'package:hash_wallet/entities/default_settings_migration.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hash_wallet/evm/evm.dart';
|
||||
@@ -15,16 +16,38 @@ import 'package:hash_wallet/reactions/wallet_connect.dart';
|
||||
|
||||
part 'node_list_view_model.g.dart';
|
||||
|
||||
class NodeSpeed {
|
||||
final String iconPath;
|
||||
final String darkIconPath;
|
||||
|
||||
// fraction of best node ping needed to be classified as this speed.
|
||||
final double treshold;
|
||||
|
||||
const NodeSpeed._(this.iconPath, this.darkIconPath, this.treshold);
|
||||
|
||||
static const disconnected = NodeSpeed._("assets/new-ui/node_speed_badges/disconnected.svg",
|
||||
"assets/new-ui/node_speed_badges/disconnected-dark.svg", 0);
|
||||
static const slow = NodeSpeed._("assets/new-ui/node_speed_badges/slow.svg",
|
||||
"assets/new-ui/node_speed_badges/slow-dark.svg", 0);
|
||||
static const medium = NodeSpeed._("assets/new-ui/node_speed_badges/medium.svg",
|
||||
"assets/new-ui/node_speed_badges/medium.svg", 0.5);
|
||||
static const fast = NodeSpeed._(
|
||||
"assets/new-ui/node_speed_badges/fast.svg", "assets/new-ui/node_speed_badges/fast.svg", 0.8);
|
||||
|
||||
static const all = [fast, medium, slow, disconnected];
|
||||
}
|
||||
|
||||
class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel;
|
||||
|
||||
abstract class NodeListViewModelBase with Store {
|
||||
NodeListViewModelBase(this._nodeSource, this._appStore)
|
||||
NodeListViewModelBase(this._appStore, this.isPow)
|
||||
: nodes = ObservableList<Node>(),
|
||||
_nodeSpeeds = ObservableMap<String, int>(),
|
||||
settingsStore = _appStore.settingsStore {
|
||||
_bindNodes();
|
||||
bindNodes();
|
||||
|
||||
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
|
||||
_bindNodes();
|
||||
bindNodes();
|
||||
});
|
||||
|
||||
reaction((_) {
|
||||
@@ -35,7 +58,7 @@ abstract class NodeListViewModelBase with Store {
|
||||
}
|
||||
return null;
|
||||
}, (_) {
|
||||
_bindNodes();
|
||||
bindNodes();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,7 +76,7 @@ abstract class NodeListViewModelBase with Store {
|
||||
return settingsStore.getCurrentNode(walletType, chainId: chainId);
|
||||
}
|
||||
|
||||
final node = settingsStore.nodes[walletType];
|
||||
final node = isPow ? settingsStore.powNodes[walletType] : settingsStore.nodes[walletType];
|
||||
if (node == null) {
|
||||
throw Exception('No node for wallet type: $walletType');
|
||||
}
|
||||
@@ -73,24 +96,74 @@ abstract class NodeListViewModelBase with Store {
|
||||
|
||||
final ObservableList<Node> nodes;
|
||||
final SettingsStore settingsStore;
|
||||
final Box<Node> _nodeSource;
|
||||
final AppStore _appStore;
|
||||
final bool isPow;
|
||||
final ObservableMap<String, int> _nodeSpeeds;
|
||||
|
||||
@computed
|
||||
List<Node> get nonCurrentNodes => nodes.where((item) => item != currentNode).toList();
|
||||
|
||||
@observable
|
||||
bool isTestingNodeSpeed = false;
|
||||
|
||||
Future<void> speedTestNodes() async {
|
||||
try {
|
||||
isTestingNodeSpeed = true;
|
||||
_nodeSpeeds.clear();
|
||||
final nodes = this.nodes.toList();
|
||||
await Future.wait(nodes.map((node) async {
|
||||
final sw = Stopwatch()..start();
|
||||
final res =
|
||||
await node.requestNode().timeout(const Duration(seconds: 10), onTimeout: () => false);
|
||||
sw.stop();
|
||||
if (res) {
|
||||
_nodeSpeeds[node.uriRaw] = sw.elapsedMilliseconds;
|
||||
}
|
||||
}));
|
||||
} finally {
|
||||
isTestingNodeSpeed = false;
|
||||
}
|
||||
}
|
||||
|
||||
NodeSpeed? nodeSpeedFor(Node node) {
|
||||
final bestSpeed = _bestNodeSpeed;
|
||||
final currentSpeed = _nodeSpeeds[node.uriRaw];
|
||||
if (bestSpeed == null) {
|
||||
return null;
|
||||
}
|
||||
if (currentSpeed == null) {
|
||||
return NodeSpeed.disconnected;
|
||||
}
|
||||
for (final speed in NodeSpeed.all) {
|
||||
if (currentSpeed < (bestSpeed / speed.treshold)) return speed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
int? get _bestNodeSpeed {
|
||||
try {
|
||||
printV(_nodeSpeeds);
|
||||
return _nodeSpeeds.values.reduce(min);
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 +172,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);
|
||||
@@ -126,11 +199,15 @@ abstract class NodeListViewModelBase with Store {
|
||||
}
|
||||
|
||||
// For non-EVM wallets, use the wallet type directly
|
||||
settingsStore.nodes[walletType] = node;
|
||||
if (isPow) {
|
||||
settingsStore.powNodes[walletType] = node;
|
||||
} else {
|
||||
settingsStore.nodes[walletType] = node;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _bindNodes() {
|
||||
Future<void> bindNodes() async {
|
||||
nodes.clear();
|
||||
final wallet = _appStore.wallet!;
|
||||
final walletType = wallet.type;
|
||||
@@ -141,11 +218,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 +228,8 @@ 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(isPow
|
||||
? await Node.getAllForWalletTypePow(walletType)
|
||||
: await Node.getAllForWalletType(walletType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/utils/mobx.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:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:hash_wallet/entities/node_list.dart';
|
||||
import 'package:hash_wallet/entities/default_settings_migration.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
part 'pow_node_list_view_model.g.dart';
|
||||
|
||||
class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel;
|
||||
|
||||
abstract class PowNodeListViewModelBase with Store {
|
||||
PowNodeListViewModelBase(this._nodeSource, this._appStore)
|
||||
: nodes = ObservableList<Node>(),
|
||||
settingsStore = _appStore.settingsStore {
|
||||
_bindNodes();
|
||||
|
||||
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
|
||||
_bindNodes();
|
||||
});
|
||||
}
|
||||
|
||||
@computed
|
||||
Node get currentNode {
|
||||
final node = settingsStore.powNodes[_appStore.wallet!.type];
|
||||
|
||||
if (node == null) {
|
||||
throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
String getAlertContent(String uri) =>
|
||||
S.current.change_current_node(uri) +
|
||||
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance!.enabled ? '' : S.current.orbot_running_alert) : ''}';
|
||||
|
||||
@computed
|
||||
bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching;
|
||||
|
||||
@action
|
||||
void setEnableAutomaticNodeSwitching(bool value) =>
|
||||
settingsStore.enableAutomaticNodeSwitching = value;
|
||||
|
||||
final ObservableList<Node> nodes;
|
||||
final SettingsStore settingsStore;
|
||||
final Box<Node> _nodeSource;
|
||||
final AppStore _appStore;
|
||||
|
||||
Future<void> reset() async {
|
||||
await resetPowToDefault(_nodeSource);
|
||||
|
||||
Node node;
|
||||
|
||||
switch (_appStore.wallet!.type) {
|
||||
case WalletType.nano:
|
||||
node = getNanoDefaultPowNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type} for pow node list');
|
||||
}
|
||||
|
||||
await setAsCurrent(node);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> delete(Node node) async => node.delete();
|
||||
|
||||
Future<void> setAsCurrent(Node node) async =>
|
||||
settingsStore.powNodes[_appStore.wallet!.type] = node;
|
||||
|
||||
@action
|
||||
void _bindNodes() {
|
||||
nodes.clear();
|
||||
_nodeSource.bindToList(
|
||||
nodes,
|
||||
filter: (val) => val.type == _appStore.wallet!.type,
|
||||
initialFire: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user