import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/settings/tor_connection.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:socks5_proxy/socks_client.dart'; import 'package:tor/tor.dart'; part 'tor_view_model.g.dart'; class TorViewModel = TorViewModelBase with _$TorViewModel; enum TorConnectionStatus { connecting, connected, disconnected } abstract class TorViewModelBase with Store { TorViewModelBase(this._settingsStore) { reaction((_) => torConnectionMode, (TorConnectionMode mode) async { if (mode == TorConnectionMode.enabled || mode == TorConnectionMode.torOnly) { startTor(); } else { stopTor(); } }); } bool torStarted = false; final SettingsStore _settingsStore; Tor torInstance = Tor.instance; @action Future updateStartOnLaunch(bool value) async { _settingsStore.shouldStartTorOnLaunch = value; } @computed TorConnectionMode get torConnectionMode => _settingsStore.torConnectionMode; @observable TorConnectionStatus torConnectionStatus = TorConnectionStatus.disconnected; @action void setTorConnectionMode(TorConnectionMode mode) => _settingsStore.torConnectionMode = mode; Future connectOrDisconnectNodeToProxy({required bool connect}) async { final appStore = getIt.get(); if (appStore.wallet != null) { final node = _settingsStore.getCurrentNode(appStore.wallet!.type); if (connect && (node.socksProxyAddress?.isEmpty ?? true)) { node.socksProxyAddress = "${InternetAddress.loopbackIPv4.address}:${torInstance.port}"; } else if (!connect) { node.socksProxyAddress = null; } bool torOnly = _settingsStore.torConnectionMode == TorConnectionMode.torOnly; if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash] .contains(appStore.wallet!.type)) { bitcoin!.setTorOnly(appStore.wallet!, torOnly); } await appStore.wallet!.connectToNode(node: node); } } @action Future startTor() async { try { torConnectionStatus = TorConnectionStatus.connecting; // stop monero from syncing before tor is connected by connecting to a dummy node: final appStore = getIt.get(); if (appStore.wallet != null && appStore.wallet!.type == WalletType.monero) { appStore.wallet!.syncStatus = NotConnectedSyncStatus(); await appStore.wallet!.connectToNode(node: Node(uri: "http://127.0.0.1")); } if (!torStarted) { torStarted = true; torInstance = await Tor.init(); } await torInstance.enable(); _settingsStore.shouldStartTorOnLaunch = true; SocksTCPClient.setProxy(proxies: [ ProxySettings( InternetAddress.loopbackIPv4, torInstance.port, password: null, ), ]); torConnectionStatus = TorConnectionStatus.connected; // connect to node through the proxy: await connectOrDisconnectNodeToProxy(connect: true); } catch (e) { torConnectionStatus = TorConnectionStatus.disconnected; } } @action Future stopTor() async { torInstance.disable(); // setting the torConnectionMode to disabled will prevent anything from actually using the proxy _settingsStore.shouldStartTorOnLaunch = false; torConnectionStatus = TorConnectionStatus.disconnected; SocksTCPClient.removeProxy(); await connectOrDisconnectNodeToProxy(connect: false); } }