Merge branch 'testing' into conflict_resolution

This commit is contained in:
julian-CStack 2024-05-03 14:33:13 -06:00 committed by GitHub
commit 3aefb3f6e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
113 changed files with 6356 additions and 5695 deletions
analysis_options.yaml
android/app
crypto_plugins
docs
ios
lib
db
electrumx_rpc
main.dart
models
pages
add_wallet_views
add_wallet_view
restore_wallet_view
paynym/subwidgets
receive_view
settings_views/global_settings_view
wallets_view/sub_widgets
pages_desktop_specific
addresses/sub_widgets
my_stack_view
services
supported_coins.dart
themes
utilities
wallets
widgets
macos
Podfile.lock
Runner.xcodeproj
project.pbxproj
xcshareddata/xcschemes
pubspec.lockpubspec.yaml
scripts
test

View file

@ -90,6 +90,8 @@ linter:
unawaited_futures: true
avoid_double_and_int_checks: false
constant_identifier_names: false
prefer_final_locals: true
prefer_final_in_for_each: true
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

View file

@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 33
compileSdkVersion 34
// ndkVersion = "21.1.6352462"
// ndkVersion = "25.2.9519653"

@ -1 +1 @@
Subproject commit aab6a4676188901fbe158d8f1feeb1fc0ea247f8
Subproject commit 19c76409e55f1bfed58855eb767574604376edb6

@ -1 +1 @@
Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287
Subproject commit 032407f0f7734f3cec3eefba76c5dc587b9a252d

@ -1 +1 @@
Subproject commit cb876251b97d20b12ddd05268913d2cf4b78f0bf
Subproject commit 2c684cedba6c3d9353c7ea748cadb5a246008027

@ -1 +1 @@
Subproject commit 0fbc038a262e3c2d82c7c6e34e194e9a47011d91
Subproject commit d539de2348bdbb87bac341dcaa6a0755f21d48e2

View file

@ -11,6 +11,9 @@ Here you will find instructions on how to install the necessary tools for buildi
## Linux host
The following instructions are for building and running on a Linux host. Alternatively, see the [Mac](#mac-host) and/or [Windows](#windows-host) section. This entire section (except for the Android Studio section) needs to be completed in WSL if building on a Windows host.
### Flutter
Install Flutter 3.19 beta (3.19.0-0.1.pre) by following these instructions: https://docs.flutter.dev/get-started/install/linux/desktop?tab=download#install-the-flutter-sdk. You can also clone https://github.com/flutter/flutter, check out the `3.19.0-0.1.pre` tag, and add its `flutter/bin` folder to your PATH. Run `flutter doctor` in a terminal to confirm its installation.
### Android Studio
Install Android Studio. Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap:
```
@ -277,12 +280,8 @@ Copy the resulting `dll`s to their respective positions on the Windows host:
-->
<!-- TODO: script the copying or installation of libraries from WSL2 to the parent Windows host -->
### Flutter
Install Flutter 3.16.0 on the Windows host (not in WSL2) by following [Flutter's Windows install guide](https://docs.flutter.dev/get-started/install/windows), by running `scripts/windows/deps.ps1`, or by
- `git clone https://github.com/flutter/flutter` somewhere it can live (`C:`, **avoid** anywhere in `C:/Users/`),
- `git checkout 3.16.9` (after navigating into the `flutter` folder),
- adding `flutter\bin`'s full absolute path to your PATH environment variable (search "environment variables" in the Start menu. If you ran `deps.ps1`, use `C:\development\flutter\bin`. You may also need to open a new terminal),
- and running `flutter doctor` in PowerShell to confirm its installation. You may need to resolve any issues which `flutter doctor` might raise.
### Install Flutter on Windows host
Install Flutter 3.19.5 on your Windows host (not in WSL2) by following these instructions: https://docs.flutter.dev/get-started/install/windows/desktop?tab=download#install-the-flutter-sdk. You can also clone https://github.com/flutter/flutter, check out the `3.19.5` tag, and add its `flutter/bin` folder to your PATH. Run `flutter doctor` in PowerShell to confirm its installation.
### Rust
Install [Rust](https://www.rust-lang.org/tools/install) on the Windows host (not in WSL2). Download the installer from [rustup.rs](https://rustup.rs), make sure it works on the commandline (you may need to open a new terminal), and install the following versions:
@ -333,3 +332,11 @@ Run the following commands:
flutter pub get
flutter run -d windows
```
# Troubleshooting
Run with `-v` or `--verbose` to see a more detailed error. Certain exceptions (like missing a plugin library) may not report quality errors without `verbose`, especially on Windows.
## Tor
To test Tor usage, run Stack Wallet from Android Studio. Click the Flutter DevTools icon in the Run tab (next to the Hot Reload and Hot Restart buttons) and navigate to the Network tab. Connections using Tor will show as `GET InternetAddress('127.0.0.1', IPv4) 101 ws`. Connections outside of Tor will show the destination address directly (although some Tor requests may also show the destination address directly, check the Headers take for *eg.* `{localPort: 59940, remoteAddress: 127.0.0.1, remotePort: 6725}`. `localPort` should match your Tor port.

View file

@ -3,6 +3,9 @@ PODS:
- Flutter
- MTBBarcodeScanner
- SwiftProtobuf
- coinlib_flutter (0.3.2):
- Flutter
- FlutterMacOS
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
@ -106,12 +109,16 @@ PODS:
- Flutter
- flutter_libmonero (0.0.1):
- Flutter
- flutter_libsparkmobile (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- frostdart (0.0.1):
- Flutter
- integration_test (0.0.1):
- Flutter
- isar_flutter_libs (1.0.0):
@ -126,7 +133,7 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.0.4):
- permission_handler_apple (9.1.1):
- Flutter
- ReachabilitySwift (5.0.0)
- SDWebImage (5.13.2):
@ -134,13 +141,12 @@ PODS:
- SDWebImage/Core (5.13.2)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- stack_wallet_backup (0.0.1):
- Flutter
- SwiftProtobuf (1.19.0)
- SwiftyGif (5.4.3)
- tor_ffi_plugin (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter
- wakelock (0.0.1):
@ -148,6 +154,7 @@ PODS:
DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
- coinlib_flutter (from `.symlinks/plugins/coinlib_flutter/darwin`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
@ -158,9 +165,11 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_libepiccash (from `.symlinks/plugins/flutter_libepiccash/ios`)
- flutter_libmonero (from `.symlinks/plugins/flutter_libmonero/ios`)
- flutter_libsparkmobile (from `.symlinks/plugins/flutter_libsparkmobile/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- frostdart (from `.symlinks/plugins/frostdart/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
- lelantus (from `.symlinks/plugins/lelantus/ios`)
@ -169,8 +178,8 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- stack_wallet_backup (from `.symlinks/plugins/stack_wallet_backup/ios`)
- tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
@ -187,6 +196,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
barcode_scan2:
:path: ".symlinks/plugins/barcode_scan2/ios"
coinlib_flutter:
:path: ".symlinks/plugins/coinlib_flutter/darwin"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
cw_monero:
@ -207,12 +218,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_libepiccash/ios"
flutter_libmonero:
:path: ".symlinks/plugins/flutter_libmonero/ios"
flutter_libsparkmobile:
:path: ".symlinks/plugins/flutter_libsparkmobile/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
frostdart:
:path: ".symlinks/plugins/frostdart/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
isar_flutter_libs:
@ -229,10 +244,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
stack_wallet_backup:
:path: ".symlinks/plugins/stack_wallet_backup/ios"
tor_ffi_plugin:
:path: ".symlinks/plugins/tor_ffi_plugin/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock:
@ -240,6 +255,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
coinlib_flutter: 6abec900d67762a6e7ccfd567a3cd3ae00bbee35
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
cw_monero: 9816991daff0e3ad0a8be140e31933b5526babd4
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
@ -249,30 +265,32 @@ SPEC CHECKSUMS:
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_libepiccash: 36241aa7d3126f6521529985ccb3dc5eaf7bb317
flutter_libmonero: da68a616b73dd0374a8419c684fa6b6df2c44ffe
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
flutter_libsparkmobile: 6373955cc3327a926d17059e7405dde2fb12f99f
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
frostdart: ed3dc4e5dce431a1a8791dd7ddba472a05ea626d
integration_test: 13825b8a9334a850581300559b8839134b124670
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
lelantus: 417f0221260013dfc052cae9cf4b741b6479edba
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03
SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
tor_ffi_plugin: d80e291b649379c8176e1be739e49be007d4ef93
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb
COCOAPODS: 1.11.3
COCOAPODS: 1.14.3

View file

@ -305,6 +305,7 @@
"${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/barcode_scan2/barcode_scan2.framework",
"${BUILT_PRODUCTS_DIR}/coinlib_flutter/secp256k1.framework",
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/cw_monero/cw_monero.framework",
"${BUILT_PRODUCTS_DIR}/cw_shared_external/cw_shared_external.framework",
@ -313,9 +314,11 @@
"${BUILT_PRODUCTS_DIR}/devicelocale/devicelocale.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/flutter_libmonero/flutter_libmonero.framework",
"${PODS_ROOT}/../.symlinks/plugins/flutter_libsparkmobile/ios/flutter_libsparkmobile.framework",
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
"${BUILT_PRODUCTS_DIR}/flutter_native_splash/flutter_native_splash.framework",
"${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework",
"${BUILT_PRODUCTS_DIR}/frostdart/frostdart.framework",
"${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework",
"${BUILT_PRODUCTS_DIR}/isar_flutter_libs/isar_flutter_libs.framework",
"${BUILT_PRODUCTS_DIR}/lelantus/lelantus.framework",
@ -338,6 +341,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan2.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/secp256k1.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cw_monero.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cw_shared_external.framework",
@ -346,9 +350,11 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/devicelocale.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_libmonero.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_libsparkmobile.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_native_splash.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/frostdart.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/isar_flutter_libs.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/lelantus.framework",

View file

@ -33,6 +33,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:tuple/tuple.dart';
class DbVersionMigrator with WalletDB {
@ -85,7 +87,7 @@ class DbVersionMigrator with WalletDB {
useSSL: node.useSSL),
prefs: prefs,
failovers: failovers,
coin: Coin.firo,
cryptoCurrency: Firo(CryptoCurrencyNetwork.main),
);
try {

View file

@ -11,9 +11,6 @@
import 'dart:convert';
import 'dart:math';
import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
import 'package:electrum_adapter/electrum_adapter.dart';
import 'package:electrum_adapter/methods/specific/firo.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -22,41 +19,18 @@ import 'package:string_validator/string_validator.dart';
class CachedElectrumXClient {
final ElectrumXClient electrumXClient;
ElectrumClient electrumAdapterClient;
final Future<ElectrumClient> Function() electrumAdapterUpdateCallback;
static const minCacheConfirms = 30;
CachedElectrumXClient({
required this.electrumXClient,
required this.electrumAdapterClient,
required this.electrumAdapterUpdateCallback,
});
CachedElectrumXClient({required this.electrumXClient});
factory CachedElectrumXClient.from({
required ElectrumXClient electrumXClient,
required ElectrumClient electrumAdapterClient,
required Future<ElectrumClient> Function() electrumAdapterUpdateCallback,
}) =>
CachedElectrumXClient(
electrumXClient: electrumXClient,
electrumAdapterClient: electrumAdapterClient,
electrumAdapterUpdateCallback: electrumAdapterUpdateCallback,
);
/// If the client is closed, use the callback to update it.
_checkElectrumAdapterClient() async {
if (electrumAdapterClient.peer.isClosed) {
Logging.instance.log(
"ElectrumAdapterClient is closed, reopening it...",
level: LogLevel.Info,
);
ElectrumClient? _electrumAdapterClient =
await electrumAdapterUpdateCallback.call();
electrumAdapterClient = _electrumAdapterClient;
}
}
Future<Map<String, dynamic>> getAnonymitySet({
required String groupId,
String blockhash = "",
@ -80,12 +54,9 @@ class CachedElectrumXClient {
set = Map<String, dynamic>.from(cachedSet);
}
await _checkElectrumAdapterClient();
final newSet = await (electrumAdapterClient as FiroElectrumClient)
.getLelantusAnonymitySet(
final newSet = await electrumXClient.getLelantusAnonymitySet(
groupId: groupId,
blockHash: set["blockHash"] as String,
blockhash: set["blockHash"] as String,
);
// update set with new data
@ -157,10 +128,7 @@ class CachedElectrumXClient {
set = Map<String, dynamic>.from(cachedSet);
}
await _checkElectrumAdapterClient();
final newSet = await (electrumAdapterClient as FiroElectrumClient)
.getSparkAnonymitySet(
final newSet = await electrumXClient.getSparkAnonymitySet(
coinGroupId: groupId,
startBlockHash: set["blockHash"] as String,
);
@ -218,10 +186,11 @@ class CachedElectrumXClient {
final cachedTx = box.get(txHash) as Map?;
if (cachedTx == null) {
await _checkElectrumAdapterClient();
final Map<String, dynamic> result =
await electrumAdapterClient.getTransaction(txHash);
await electrumXClient.getTransaction(
txHash: txHash,
verbose: verbose,
);
result.remove("hex");
result.remove("lelantusData");
@ -263,10 +232,7 @@ class CachedElectrumXClient {
cachedSerials.length - 100, // 100 being some arbitrary buffer
);
await _checkElectrumAdapterClient();
final serials = await (electrumAdapterClient as FiroElectrumClient)
.getLelantusUsedCoinSerials(
final serials = await electrumXClient.getLelantusUsedCoinSerials(
startNumber: startNumber,
);
@ -314,22 +280,12 @@ class CachedElectrumXClient {
cachedTags.length - 100, // 100 being some arbitrary buffer
);
await _checkElectrumAdapterClient();
final tags =
await (electrumAdapterClient as FiroElectrumClient).getUsedCoinsTags(
final newTags = await electrumXClient.getSparkUsedCoinsTags(
startNumber: startNumber,
);
// final newSerials = List<String>.from(serials["serials"] as List)
// .map((e) => !isHexadecimal(e) ? base64ToHex(e) : e)
// .toSet();
// Convert the Map<String, dynamic> tags to a Set<Object?>.
final newTags = (tags["tags"] as List).toSet();
// ensure we are getting some overlap so we know we are not missing any
if (cachedTags.isNotEmpty && tags.isNotEmpty) {
if (cachedTags.isNotEmpty && newTags.isNotEmpty) {
assert(cachedTags.intersection(newTags).isNotEmpty);
}

View file

@ -0,0 +1,96 @@
import 'dart:async';
import 'package:electrum_adapter/electrum_adapter.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
class ClientManager {
ClientManager._();
static final ClientManager sharedInstance = ClientManager._();
final Map<String, ElectrumClient> _map = {};
final Map<String, int> _heights = {};
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
final Map<String, Completer<int>> _heightCompleters = {};
String _keyHelper(CryptoCurrency cryptoCurrency) {
return "${cryptoCurrency.runtimeType}_${cryptoCurrency.network.name}";
}
final Finalizer<ClientManager> _finalizer = Finalizer((manager) async {
await manager._kill();
});
ElectrumClient? getClient({
required CryptoCurrency cryptoCurrency,
}) =>
_map[_keyHelper(cryptoCurrency)];
void addClient(
ElectrumClient client, {
required CryptoCurrency cryptoCurrency,
}) {
final key = _keyHelper(cryptoCurrency);
if (_map[key] != null) {
throw Exception("ElectrumX Client for $key already exists.");
} else {
_map[key] = client;
}
_heightCompleters[key] = Completer<int>();
_subscriptions[key] = client.subscribeHeaders().listen((event) {
_heights[key] = event.height;
if (!_heightCompleters[key]!.isCompleted) {
_heightCompleters[key]!.complete(event.height);
}
});
}
Future<int> getChainHeightFor(CryptoCurrency cryptoCurrency) async {
final key = _keyHelper(cryptoCurrency);
if (_map[key] == null) {
throw Exception(
"No managed ElectrumClient for $cryptoCurrency found.",
);
}
if (_heightCompleters[key] == null) {
throw Exception(
"No managed _heightCompleters for $cryptoCurrency found.",
);
}
return _heights[key] ?? await _heightCompleters[key]!.future;
}
Future<ElectrumClient?> remove({
required CryptoCurrency cryptoCurrency,
}) async {
final key = _keyHelper(cryptoCurrency);
await _subscriptions[key]?.cancel();
_subscriptions.remove(key);
_heights.remove(key);
_heightCompleters.remove(key);
return _map.remove(key);
}
Future<void> closeAll() async {
await _kill();
_finalizer.detach(this);
}
Future<void> _kill() async {
for (final sub in _subscriptions.values) {
await sub.cancel();
}
for (final client in _map.values) {
await client.close();
}
_heightCompleters.clear();
_heights.clear();
_subscriptions.clear();
_map.clear();
}
}

View file

@ -1,149 +0,0 @@
import 'dart:async';
import 'package:electrum_adapter/electrum_adapter.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
/// Manage chain height subscriptions for each coin.
abstract class ChainHeightServiceManager {
// A map of chain height services for each coin.
static final Map<Coin, ChainHeightService> _services = {};
// Map<Coin, ChainHeightService> get services => _services;
// Get the chain height service for a specific coin.
static ChainHeightService? getService(Coin coin) {
return _services[coin];
}
// Add a chain height service for a specific coin.
static void add(ChainHeightService service, Coin coin) {
// Don't add a new service if one already exists.
if (_services[coin] == null) {
_services[coin] = service;
} else {
throw Exception("Chain height service for $coin already managed");
}
}
// Remove a chain height service for a specific coin.
static void remove(Coin coin) {
_services.remove(coin);
}
// Close all subscriptions and clean up resources.
static Future<void> dispose() async {
// Close each subscription.
//
// Create a list of keys to avoid concurrent modification during iteration
var keys = List<Coin>.from(_services.keys);
// Iterate over the copy of the keys
for (final coin in keys) {
final ChainHeightService? service = getService(coin);
await service?.cancelListen();
remove(coin);
}
}
}
/// A service to fetch and listen for chain height updates.
///
/// TODO: Add error handling and branching to handle various other scenarios.
class ChainHeightService {
// The electrum_adapter client to use for fetching chain height updates.
ElectrumClient client;
// The subscription to listen for chain height updates.
StreamSubscription<dynamic>? _subscription;
// Whether the service has started listening for updates.
bool get started => _subscription != null;
// The current chain height.
int? _height;
int? get height => _height;
// Whether the service is currently reconnecting.
bool _isReconnecting = false;
// The reconnect timer.
Timer? _reconnectTimer;
// The reconnection timeout duration.
static const Duration _connectionTimeout = Duration(seconds: 10);
ChainHeightService({required this.client});
/// Fetch the current chain height and start listening for updates.
Future<int> fetchHeightAndStartListenForUpdates() async {
// Don't start a new subscription if one already exists.
if (_subscription != null) {
throw Exception(
"Attempted to start a chain height service where an existing"
" subscription already exists!",
);
}
// A completer to wait for the current chain height to be fetched.
final completer = Completer<int>();
// Fetch the current chain height.
_subscription = client.subscribeHeaders().listen((BlockHeader event) {
_height = event.height;
if (!completer.isCompleted) {
completer.complete(_height);
}
});
_subscription?.onError((dynamic error) {
_handleError(error);
});
// Wait for the current chain height to be fetched.
return completer.future;
}
/// Handle an error from the subscription.
void _handleError(dynamic error) {
Logging.instance.log(
"Error reconnecting for chain height: ${error.toString()}",
level: LogLevel.Error,
);
_subscription?.cancel();
_subscription = null;
_attemptReconnect();
}
/// Attempt to reconnect to the electrum server.
void _attemptReconnect() {
// Avoid multiple reconnection attempts.
if (_isReconnecting) return;
_isReconnecting = true;
// Attempt to reconnect.
unawaited(fetchHeightAndStartListenForUpdates().then((_) {
_isReconnecting = false;
}));
// Set a timer to on the reconnection attempt and clean up if it fails.
_reconnectTimer?.cancel();
_reconnectTimer = Timer(_connectionTimeout, () async {
if (_subscription == null) {
await _subscription?.cancel();
_subscription = null; // Will also occur on an error via handleError.
_reconnectTimer?.cancel();
_reconnectTimer = null;
_isReconnecting = false;
}
});
}
/// Stop listening for chain height updates.
Future<void> cancelListen() async {
await _subscription?.cancel();
_subscription = null;
_reconnectTimer?.cancel();
}
}

View file

@ -20,16 +20,17 @@ import 'package:event_bus/event_bus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:mutex/mutex.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_chain_height_service.dart';
import 'package:stackwallet/electrumx_rpc/rpc.dart';
import 'package:stackwallet/electrumx_rpc/client_manager.dart';
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/dogecoin.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stream_channel/stream_channel.dart';
class WifiOnlyException implements Exception {}
@ -65,6 +66,8 @@ class ElectrumXNode {
}
class ElectrumXClient {
final CryptoCurrency cryptoCurrency;
String get host => _host;
late String _host;
@ -74,14 +77,13 @@ class ElectrumXClient {
bool get useSSL => _useSSL;
late bool _useSSL;
JsonRPC? get rpcClient => _rpcClient;
JsonRPC? _rpcClient;
StreamChannel<dynamic>? get electrumAdapterChannel => _electrumAdapterChannel;
// StreamChannel<dynamic>? get electrumAdapterChannel => _electrumAdapterChannel;
StreamChannel<dynamic>? _electrumAdapterChannel;
ElectrumClient? get electrumAdapterClient => _electrumAdapterClient;
ElectrumClient? _electrumAdapterClient;
ElectrumClient? getElectrumAdapter() =>
ClientManager.sharedInstance.getClient(
cryptoCurrency: cryptoCurrency,
);
late Prefs _prefs;
late TorService _torService;
@ -91,9 +93,6 @@ class ElectrumXClient {
final Duration connectionTimeoutForSpecialCaseJsonRPCClients;
Coin? get coin => _coin;
late Coin? _coin;
// add finalizer to cancel stream subscription when all references to an
// instance of ElectrumX becomes inaccessible
static final Finalizer<ElectrumXClient> _finalizer = Finalizer(
@ -114,7 +113,7 @@ class ElectrumXClient {
required bool useSSL,
required Prefs prefs,
required List<ElectrumXNode> failovers,
Coin? coin,
required this.cryptoCurrency,
this.connectionTimeoutForSpecialCaseJsonRPCClients =
const Duration(seconds: 60),
TorService? torService,
@ -125,7 +124,6 @@ class ElectrumXClient {
_host = host;
_port = port;
_useSSL = useSSL;
_coin = coin;
final bus = globalEventBusForTesting ?? GlobalEventBus.instance;
@ -161,10 +159,12 @@ class ElectrumXClient {
// setting to null should force the creation of a new json rpc client
// on the next request sent through this electrumx instance
_electrumAdapterChannel = null;
_electrumAdapterClient = null;
await (await ClientManager.sharedInstance
.remove(cryptoCurrency: cryptoCurrency))
?.close();
// Also close any chain height services that are currently open.
await ChainHeightServiceManager.dispose();
// await ChainHeightServiceManager.dispose();
},
);
}
@ -173,7 +173,7 @@ class ElectrumXClient {
required ElectrumXNode node,
required Prefs prefs,
required List<ElectrumXNode> failovers,
required Coin coin,
required CryptoCurrency cryptoCurrency,
TorService? torService,
EventBus? globalEventBusForTesting,
}) {
@ -185,7 +185,7 @@ class ElectrumXClient {
torService: torService,
failovers: failovers,
globalEventBusForTesting: globalEventBusForTesting,
coin: coin,
cryptoCurrency: cryptoCurrency,
);
}
@ -197,7 +197,11 @@ class ElectrumXClient {
return true;
}
Future<void> checkElectrumAdapter() async {
Future<void> closeAdapter() async {
await getElectrumAdapter()?.close();
}
Future<void> _checkElectrumAdapter() async {
({InternetAddress host, int port})? proxyInfo;
// If we're supposed to use Tor...
@ -223,75 +227,60 @@ class ElectrumXClient {
}
}
// TODO [prio=med]: Add proxyInfo to StreamChannel (or add to wrapper).
// if (_electrumAdapter!.proxyInfo != proxyInfo) {
// _electrumAdapter!.proxyInfo = proxyInfo;
// _electrumAdapter!.disconnect(
// reason: "Tor proxyInfo does not match current info",
// );
// }
// If the current ElectrumAdapterClient is closed, create a new one.
if (_electrumAdapterClient != null &&
_electrumAdapterClient!.peer.isClosed) {
if (getElectrumAdapter() != null && getElectrumAdapter()!.peer.isClosed) {
_electrumAdapterChannel = null;
_electrumAdapterClient = null;
await ClientManager.sharedInstance.remove(cryptoCurrency: cryptoCurrency);
}
final String useHost;
final int usePort;
final bool useUseSSL;
if (currentFailoverIndex == -1) {
_electrumAdapterChannel ??= await electrum_adapter.connect(
host,
port: port,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
aliveTimerDuration: connectionTimeoutForSpecialCaseJsonRPCClients,
acceptUnverified: true,
useSSL: useSSL,
proxyInfo: proxyInfo,
);
if (_coin == Coin.firo || _coin == Coin.firoTestNet) {
_electrumAdapterClient ??= FiroElectrumClient(
_electrumAdapterChannel!,
host,
port,
useSSL,
proxyInfo,
);
} else {
_electrumAdapterClient ??= ElectrumClient(
_electrumAdapterChannel!,
host,
port,
useSSL,
proxyInfo,
);
}
useHost = host;
usePort = port;
useUseSSL = useSSL;
} else {
_electrumAdapterChannel ??= await electrum_adapter.connect(
failovers![currentFailoverIndex].address,
port: failovers![currentFailoverIndex].port,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
aliveTimerDuration: connectionTimeoutForSpecialCaseJsonRPCClients,
acceptUnverified: true,
useSSL: failovers![currentFailoverIndex].useSSL,
proxyInfo: proxyInfo,
);
if (_coin == Coin.firo || _coin == Coin.firoTestNet) {
_electrumAdapterClient ??= FiroElectrumClient(
useHost = failovers![currentFailoverIndex].address;
usePort = failovers![currentFailoverIndex].port;
useUseSSL = failovers![currentFailoverIndex].useSSL;
}
_electrumAdapterChannel ??= await electrum_adapter.connect(
useHost,
port: usePort,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
aliveTimerDuration: connectionTimeoutForSpecialCaseJsonRPCClients,
acceptUnverified: true,
useSSL: useUseSSL,
proxyInfo: proxyInfo,
);
if (getElectrumAdapter() == null) {
final ElectrumClient newClient;
if (cryptoCurrency is Firo) {
newClient = FiroElectrumClient(
_electrumAdapterChannel!,
failovers![currentFailoverIndex].address,
failovers![currentFailoverIndex].port,
failovers![currentFailoverIndex].useSSL,
useHost,
usePort,
useUseSSL,
proxyInfo,
);
} else {
_electrumAdapterClient ??= ElectrumClient(
newClient = ElectrumClient(
_electrumAdapterChannel!,
failovers![currentFailoverIndex].address,
failovers![currentFailoverIndex].port,
failovers![currentFailoverIndex].useSSL,
useHost,
usePort,
useUseSSL,
proxyInfo,
);
}
ClientManager.sharedInstance.addClient(
newClient,
cryptoCurrency: cryptoCurrency,
);
}
return;
@ -311,13 +300,13 @@ class ElectrumXClient {
if (_requireMutex) {
await _torConnectingLock
.protect(() async => await checkElectrumAdapter());
.protect(() async => await _checkElectrumAdapter());
} else {
await checkElectrumAdapter();
await _checkElectrumAdapter();
}
try {
final response = await _electrumAdapterClient!.request(
final response = await getElectrumAdapter()!.request(
command,
args,
);
@ -397,16 +386,16 @@ class ElectrumXClient {
if (_requireMutex) {
await _torConnectingLock
.protect(() async => await checkElectrumAdapter());
.protect(() async => await _checkElectrumAdapter());
} else {
await checkElectrumAdapter();
await _checkElectrumAdapter();
}
try {
var futures = <Future<dynamic>>[];
_electrumAdapterClient!.peer.withBatch(() {
getElectrumAdapter()!.peer.withBatch(() {
for (final arg in args) {
futures.add(_electrumAdapterClient!.request(command, arg));
futures.add(getElectrumAdapter()!.request(command, arg));
}
});
final response = await Future.wait(futures);
@ -778,8 +767,8 @@ class ElectrumXClient {
}) async {
Logging.instance.log("attempting to fetch blockchain.transaction.get...",
level: LogLevel.Info);
await checkElectrumAdapter();
dynamic response = await _electrumAdapterClient!.getTransaction(txHash);
await _checkElectrumAdapter();
dynamic response = await getElectrumAdapter()!.getTransaction(txHash);
Logging.instance.log("Fetching blockchain.transaction.get finished",
level: LogLevel.Info);
@ -811,9 +800,9 @@ class ElectrumXClient {
}) async {
Logging.instance.log("attempting to fetch lelantus.getanonymityset...",
level: LogLevel.Info);
await checkElectrumAdapter();
await _checkElectrumAdapter();
Map<String, dynamic> response =
await (_electrumAdapterClient as FiroElectrumClient)!
await (getElectrumAdapter() as FiroElectrumClient)
.getLelantusAnonymitySet(groupId: groupId, blockHash: blockhash);
Logging.instance.log("Fetching lelantus.getanonymityset finished",
level: LogLevel.Info);
@ -830,8 +819,8 @@ class ElectrumXClient {
}) async {
Logging.instance.log("attempting to fetch lelantus.getmintmetadata...",
level: LogLevel.Info);
await checkElectrumAdapter();
dynamic response = await (_electrumAdapterClient as FiroElectrumClient)!
await _checkElectrumAdapter();
dynamic response = await (getElectrumAdapter() as FiroElectrumClient)
.getLelantusMintData(mints: mints);
Logging.instance.log("Fetching lelantus.getmintmetadata finished",
level: LogLevel.Info);
@ -846,13 +835,13 @@ class ElectrumXClient {
}) async {
Logging.instance.log("attempting to fetch lelantus.getusedcoinserials...",
level: LogLevel.Info);
await checkElectrumAdapter();
await _checkElectrumAdapter();
int retryCount = 3;
dynamic response;
while (retryCount > 0 && response is! List) {
response = await (_electrumAdapterClient as FiroElectrumClient)!
response = await (getElectrumAdapter() as FiroElectrumClient)
.getLelantusUsedCoinSerials(startNumber: startNumber);
// TODO add 2 minute timeout.
Logging.instance.log("Fetching lelantus.getusedcoinserials finished",
@ -870,9 +859,9 @@ class ElectrumXClient {
Future<int> getLelantusLatestCoinId({String? requestID}) async {
Logging.instance.log("attempting to fetch lelantus.getlatestcoinid...",
level: LogLevel.Info);
await checkElectrumAdapter();
await _checkElectrumAdapter();
int response =
await (_electrumAdapterClient as FiroElectrumClient).getLatestCoinId();
await (getElectrumAdapter() as FiroElectrumClient).getLatestCoinId();
Logging.instance.log("Fetching lelantus.getlatestcoinid finished",
level: LogLevel.Info);
return response;
@ -901,9 +890,9 @@ class ElectrumXClient {
try {
Logging.instance.log("attempting to fetch spark.getsparkanonymityset...",
level: LogLevel.Info);
await checkElectrumAdapter();
await _checkElectrumAdapter();
Map<String, dynamic> response =
await (_electrumAdapterClient as FiroElectrumClient)
await (getElectrumAdapter() as FiroElectrumClient)
.getSparkAnonymitySet(
coinGroupId: coinGroupId, startBlockHash: startBlockHash);
Logging.instance.log("Fetching spark.getsparkanonymityset finished",
@ -924,11 +913,12 @@ class ElectrumXClient {
// Use electrum_adapter package's getSparkUsedCoinsTags method.
Logging.instance.log("attempting to fetch spark.getusedcoinstags...",
level: LogLevel.Info);
await checkElectrumAdapter();
await _checkElectrumAdapter();
Map<String, dynamic> response =
await (_electrumAdapterClient as FiroElectrumClient)
await (getElectrumAdapter() as FiroElectrumClient)
.getUsedCoinsTags(startNumber: startNumber);
// TODO: Add 2 minute timeout.
// Why 2 minutes?
Logging.instance.log("Fetching spark.getusedcoinstags finished",
level: LogLevel.Info);
final map = Map<String, dynamic>.from(response);
@ -957,9 +947,9 @@ class ElectrumXClient {
try {
Logging.instance.log("attempting to fetch spark.getsparkmintmetadata...",
level: LogLevel.Info);
await checkElectrumAdapter();
await _checkElectrumAdapter();
List<dynamic> response =
await (_electrumAdapterClient as FiroElectrumClient)
await (getElectrumAdapter() as FiroElectrumClient)
.getSparkMintMetaData(sparkCoinHashes: sparkCoinHashes);
Logging.instance.log("Fetching spark.getsparkmintmetadata finished",
level: LogLevel.Info);
@ -979,8 +969,8 @@ class ElectrumXClient {
try {
Logging.instance.log("attempting to fetch spark.getsparklatestcoinid...",
level: LogLevel.Info);
await checkElectrumAdapter();
int response = await (_electrumAdapterClient as FiroElectrumClient)
await _checkElectrumAdapter();
int response = await (getElectrumAdapter() as FiroElectrumClient)
.getSparkLatestCoinId();
Logging.instance.log("Fetching spark.getsparklatestcoinid finished",
level: LogLevel.Info);
@ -1001,8 +991,8 @@ class ElectrumXClient {
/// "rate": 1000,
/// }
Future<Map<String, dynamic>> getFeeRate({String? requestID}) async {
await checkElectrumAdapter();
return await _electrumAdapterClient!.getFeeRate();
await _checkElectrumAdapter();
return await getElectrumAdapter()!.getFeeRate();
}
/// Return the estimated transaction fee per kilobyte for a transaction to be confirmed within a certain number of [blocks].
@ -1022,7 +1012,7 @@ class ElectrumXClient {
try {
// If the response is -1 or null, return a temporary hardcoded value for
// Dogecoin. This is a temporary fix until the fee estimation is fixed.
if (coin == Coin.dogecoin &&
if (cryptoCurrency is Dogecoin &&
(response == null ||
response == -1 ||
Decimal.parse(response.toString()) == Decimal.parse("-1"))) {
@ -1035,7 +1025,7 @@ class ElectrumXClient {
return Decimal.parse(response.toString());
} catch (e, s) {
final String msg = "Error parsing fee rate. Response: $response"
"\nResult: ${response}\nError: $e\nStack trace: $s";
"\nResult: $response\nError: $e\nStack trace: $s";
Logging.instance.log(msg, level: LogLevel.Fatal);
throw Exception(msg);
}

View file

@ -1,413 +0,0 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:mutex/mutex.dart';
import 'package:stackwallet/exceptions/json_rpc/json_rpc_exception.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:tor_ffi_plugin/socks_socket.dart';
// Json RPC class to handle connecting to electrumx servers
class JsonRPC {
JsonRPC({
required this.host,
required this.port,
this.useSSL = false,
this.connectionTimeout = const Duration(seconds: 60),
required ({InternetAddress host, int port})? proxyInfo,
});
final bool useSSL;
final String host;
final int port;
final Duration connectionTimeout;
({InternetAddress host, int port})? proxyInfo;
final _requestMutex = Mutex();
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
Socket? _socket;
SOCKSSocket? _socksSocket;
StreamSubscription<List<int>>? _subscription;
void _dataHandler(List<int> data) {
_requestQueue.nextIncompleteReq.then((req) {
if (req != null) {
req.appendDataAndCheckIfComplete(data);
if (req.isComplete) {
_onReqCompleted(req);
}
} else {
Logging.instance.log(
"_dataHandler found a null req!",
level: LogLevel.Warning,
);
}
});
}
void _errorHandler(Object error, StackTrace trace) {
_requestQueue.nextIncompleteReq.then((req) {
if (req != null) {
req.completer.completeError(error, trace);
_onReqCompleted(req);
}
});
}
void _doneHandler() {
disconnect(reason: "JsonRPC _doneHandler() called");
}
void _onReqCompleted(_JsonRPCRequest req) {
_requestQueue.remove(req).then((_) {
// attempt to send next request
_sendNextAvailableRequest();
});
}
void _sendNextAvailableRequest() {
_requestQueue.nextIncompleteReq.then((req) {
if (req != null) {
if (!Prefs.instance.useTor) {
if (_socket == null) {
Logging.instance.log(
"JsonRPC _sendNextAvailableRequest attempted with"
" _socket=null on $host:$port",
level: LogLevel.Error,
);
}
// \r\n required by electrumx server
_socket!.write('${req.jsonRequest}\r\n');
} else {
if (_socksSocket == null) {
Logging.instance.log(
"JsonRPC _sendNextAvailableRequest attempted with"
" _socksSocket=null on $host:$port",
level: LogLevel.Error,
);
}
// \r\n required by electrumx server
_socksSocket?.write('${req.jsonRequest}\r\n');
}
// TODO different timeout length?
req.initiateTimeout(
onTimedOut: () {
_onReqCompleted(req);
},
);
}
});
}
Future<JsonRPCResponse> request(
String jsonRpcRequest,
Duration requestTimeout,
) async {
await _requestMutex.protect(() async {
if (!Prefs.instance.useTor) {
if (_socket == null) {
Logging.instance.log(
"JsonRPC request: opening socket $host:$port",
level: LogLevel.Info,
);
await _connect().timeout(requestTimeout, onTimeout: () {
throw Exception("Request timeout: $jsonRpcRequest");
});
}
} else {
if (_socksSocket == null) {
Logging.instance.log(
"JsonRPC request: opening SOCKS socket to $host:$port",
level: LogLevel.Info,
);
await _connect().timeout(requestTimeout, onTimeout: () {
throw Exception("Request timeout: $jsonRpcRequest");
});
}
}
});
final req = _JsonRPCRequest(
jsonRequest: jsonRpcRequest,
requestTimeout: requestTimeout,
completer: Completer<JsonRPCResponse>(),
);
final future = req.completer.future.onError(
(error, stackTrace) async {
await disconnect(
reason: "return req.completer.future.onError: $error\n$stackTrace",
);
return JsonRPCResponse(
exception: error is JsonRpcException
? error
: JsonRpcException(
"req.completer.future.onError: $error\n$stackTrace",
),
);
},
);
// if this is the only/first request then send it right away
await _requestQueue.add(
req,
onInitialRequestAdded: _sendNextAvailableRequest,
);
return future;
}
/// DO NOT set [ignoreMutex] to true unless fully aware of the consequences
Future<void> disconnect({
required String reason,
bool ignoreMutex = false,
}) async {
if (ignoreMutex) {
await _disconnectHelper(reason: reason);
} else {
await _requestMutex.protect(() async {
await _disconnectHelper(reason: reason);
});
}
}
Future<void> _disconnectHelper({required String reason}) async {
await _subscription?.cancel();
_subscription = null;
_socket?.destroy();
_socket = null;
await _socksSocket?.close();
_socksSocket = null;
// clean up remaining queue
await _requestQueue.completeRemainingWithError(
"JsonRPC disconnect() called with reason: \"$reason\"",
);
}
Future<void> _connect() async {
// ignore mutex is set to true here as _connect is already called within
// the mutex.protect block. Setting to false here leads to a deadlock
await disconnect(
reason: "New connection requested",
ignoreMutex: true,
);
if (!Prefs.instance.useTor) {
if (useSSL) {
_socket = await SecureSocket.connect(
host,
port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
); // TODO do not automatically trust bad certificates.
} else {
_socket = await Socket.connect(
host,
port,
timeout: connectionTimeout,
);
}
_subscription = _socket!.listen(
_dataHandler,
onError: _errorHandler,
onDone: _doneHandler,
cancelOnError: true,
);
} else {
if (proxyInfo == null) {
throw JsonRpcException(
"JsonRPC.connect failed with useTor=${Prefs.instance.useTor} and proxyInfo is null");
}
// instantiate a socks socket at localhost and on the port selected by the tor service
_socksSocket = await SOCKSSocket.create(
proxyHost: proxyInfo!.host.address,
proxyPort: proxyInfo!.port,
sslEnabled: useSSL,
);
try {
Logging.instance.log(
"JsonRPC.connect(): connecting to SOCKS socket at $proxyInfo (SSL $useSSL)...",
level: LogLevel.Info);
await _socksSocket?.connect();
Logging.instance.log(
"JsonRPC.connect(): connected to SOCKS socket at $proxyInfo...",
level: LogLevel.Info);
} catch (e) {
Logging.instance.log(
"JsonRPC.connect(): failed to connect to SOCKS socket at $proxyInfo, $e",
level: LogLevel.Error);
throw JsonRpcException(
"JsonRPC.connect(): failed to connect to SOCKS socket at $proxyInfo, $e");
}
try {
Logging.instance.log(
"JsonRPC.connect(): connecting to $host:$port over SOCKS socket at $proxyInfo...",
level: LogLevel.Info);
await _socksSocket?.connectTo(host, port);
Logging.instance.log(
"JsonRPC.connect(): connected to $host:$port over SOCKS socket at $proxyInfo",
level: LogLevel.Info);
} catch (e) {
Logging.instance.log(
"JsonRPC.connect(): failed to connect to $host over tor proxy at $proxyInfo, $e",
level: LogLevel.Error);
throw JsonRpcException(
"JsonRPC.connect(): failed to connect to tor proxy, $e");
}
_subscription = _socksSocket!.listen(
_dataHandler,
onError: _errorHandler,
onDone: _doneHandler,
cancelOnError: true,
);
}
return;
}
}
class _JsonRPCRequestQueue {
final _lock = Mutex();
final List<_JsonRPCRequest> _rq = [];
Future<void> add(
_JsonRPCRequest req, {
VoidCallback? onInitialRequestAdded,
}) async {
return await _lock.protect(() async {
_rq.add(req);
if (_rq.length == 1) {
onInitialRequestAdded?.call();
}
});
}
Future<bool> remove(_JsonRPCRequest req) async {
return await _lock.protect(() async {
final result = _rq.remove(req);
return result;
});
}
Future<_JsonRPCRequest?> get nextIncompleteReq async {
return await _lock.protect(() async {
int removeCount = 0;
_JsonRPCRequest? returnValue;
for (final req in _rq) {
if (req.isComplete) {
removeCount++;
} else {
returnValue = req;
break;
}
}
_rq.removeRange(0, removeCount);
return returnValue;
});
}
Future<void> completeRemainingWithError(
String error, {
StackTrace? stackTrace,
}) async {
await _lock.protect(() async {
for (final req in _rq) {
if (!req.isComplete) {
req.completer.completeError(Exception(error), stackTrace);
}
}
_rq.clear();
});
}
Future<bool> get isEmpty async {
return await _lock.protect(() async {
return _rq.isEmpty;
});
}
}
class _JsonRPCRequest {
// 0x0A is newline
// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
static const int separatorByte = 0x0A;
final String jsonRequest;
final Completer<JsonRPCResponse> completer;
final Duration requestTimeout;
final List<int> _responseData = [];
_JsonRPCRequest({
required this.jsonRequest,
required this.completer,
required this.requestTimeout,
});
void appendDataAndCheckIfComplete(List<int> data) {
_responseData.addAll(data);
if (data.last == separatorByte) {
try {
final response = json.decode(String.fromCharCodes(_responseData));
completer.complete(JsonRPCResponse(data: response));
} catch (e, s) {
Logging.instance.log(
"JsonRPC json.decode: $e\n$s",
level: LogLevel.Error,
);
completer.completeError(e, s);
}
}
}
void initiateTimeout({
required VoidCallback onTimedOut,
}) {
Future<void>.delayed(requestTimeout).then((_) {
if (!isComplete) {
completer.complete(
JsonRPCResponse(
data: null,
exception: JsonRpcException(
"_JsonRPCRequest timed out: $jsonRequest",
),
),
);
}
onTimedOut.call();
});
}
bool get isComplete => completer.isCompleted;
}
class JsonRPCResponse {
final dynamic data;
final JsonRpcException? exception;
JsonRPCResponse({this.data, this.exception});
}

View file

@ -687,6 +687,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
appBarTheme: AppBarTheme(
centerTitle: false,
color: colorScheme.background,
surfaceTintColor: colorScheme.background,
elevation: 0,
),
inputDecorationTheme: InputDecorationTheme(

View file

@ -164,7 +164,8 @@ enum AddressType {
stellar,
tezos,
frostMS,
;
p2tr,
solana;
String get readableName {
switch (this) {
@ -196,6 +197,10 @@ enum AddressType {
return "Tezos";
case AddressType.frostMS:
return "FrostMS";
case AddressType.solana:
return "Solana";
case AddressType.p2tr:
return "Taproot"; // Why not use P2TR, P2PKH, etc.?
}
}
}

View file

@ -267,6 +267,8 @@ const _AddresstypeEnumValueMap = {
'stellar': 11,
'tezos': 12,
'frostMS': 13,
'p2tr': 14,
'solana': 15,
};
const _AddresstypeValueEnumMap = {
0: AddressType.p2pkh,
@ -283,6 +285,8 @@ const _AddresstypeValueEnumMap = {
11: AddressType.stellar,
12: AddressType.tezos,
13: AddressType.frostMS,
14: AddressType.p2tr,
15: AddressType.solana,
};
Id _addressGetId(Address object) {

View file

@ -8,9 +8,7 @@
*
*/
import 'dart:typed_data';
import 'package:bitcoindart/bitcoindart.dart';
import 'package:coinlib_flutter/coinlib_flutter.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
@ -18,25 +16,19 @@ class SigningData {
SigningData({
required this.derivePathType,
required this.utxo,
this.output,
this.keyPair,
this.redeemScript,
});
final DerivePathType derivePathType;
final UTXO utxo;
Uint8List? output;
ECPair? keyPair;
Uint8List? redeemScript;
HDPrivateKey? keyPair;
@override
String toString() {
return "SigningData{\n"
" derivePathType: $derivePathType,\n"
" utxo: $utxo,\n"
" output: $output,\n"
" keyPair: $keyPair,\n"
" redeemScript: $redeemScript,\n"
"}";
}
}

View file

@ -139,6 +139,11 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
_coins.remove(Coin.bitcoinFrost);
}
// Remove Solana from the list of coins based on our frostEnabled preference.
if (!ref.read(prefsChangeNotifierProvider).solanaEnabled) {
_coins.remove(Coin.solana);
}
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) {

View file

@ -56,7 +56,6 @@ import 'package:stackwallet/wallets/wallet/impl/monero_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/wownero_wallet.dart';
import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
@ -724,480 +723,459 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
],
),
body: Container(
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SingleChildScrollView(
controller: controller,
child: Column(
children: [
/*if (isDesktop)
color: Theme.of(context).extension<StackColors>()!.background,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SingleChildScrollView(
controller: controller,
child: Column(
children: [
/*if (isDesktop)
const Spacer(
flex: 10,
),*/
if (!isDesktop)
Text(
widget.walletName,
style: STextStyles.itemSubtitle(context),
),
SizedBox(
height: isDesktop ? 0 : 4,
),
if (!isDesktop)
Text(
"Recovery phrase",
style: isDesktop
? STextStyles.desktopH2(context)
: STextStyles.pageTitleH1(context),
widget.walletName,
style: STextStyles.itemSubtitle(context),
),
SizedBox(
height: isDesktop ? 16 : 8,
),
Text(
"Enter your $_seedWordCount-word recovery phrase.",
style: isDesktop
? STextStyles.desktopSubtitleH2(context)
: STextStyles.subtitle(context),
),
SizedBox(
height: isDesktop ? 16 : 10,
),
if (isDesktop)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: pasteMnemonic,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 12,
),
child: Row(
children: [
SvgPicture.asset(
Assets.svg.clipboard,
width: 22,
height: 22,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
const SizedBox(
width: 8,
),
Text(
"Paste",
style: STextStyles
.desktopButtonSmallSecondaryEnabled(
context),
)
],
),
SizedBox(
height: isDesktop ? 0 : 4,
),
Text(
"Recovery phrase",
style: isDesktop
? STextStyles.desktopH2(context)
: STextStyles.pageTitleH1(context),
),
SizedBox(
height: isDesktop ? 16 : 8,
),
Text(
"Enter your $_seedWordCount-word recovery phrase.",
style: isDesktop
? STextStyles.desktopSubtitleH2(context)
: STextStyles.subtitle(context),
),
SizedBox(
height: isDesktop ? 16 : 10,
),
if (isDesktop)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: pasteMnemonic,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 12,
),
),
],
),
if (isDesktop)
const SizedBox(
height: 20,
),
if (isDesktop)
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 1008,
),
child: Builder(
builder: (BuildContext context) {
const cols = 4;
final int rows = _seedWordCount ~/ cols;
final int remainder = _seedWordCount % cols;
return Column(
child: Row(
children: [
Form(
key: _formKey,
child: TableView(
shrinkWrap: true,
rowSpacing: 20,
rows: [
for (int i = 0; i < rows; i++)
TableViewRow(
crossAxisAlignment:
CrossAxisAlignment.start,
spacing: 16,
cells: [
for (int j = 1; j <= cols; j++)
TableViewCell(
flex: 1,
child: Column(
children: [
TextFormField(
autocorrect: !isDesktop,
enableSuggestions:
!isDesktop,
textCapitalization:
TextCapitalization.none,
key: Key(
"restoreMnemonicFormField_$i"),
decoration:
_getInputDecorationFor(
_inputStatuses[
i * 4 + j - 1],
"${i * 4 + j}"),
autovalidateMode:
AutovalidateMode
.onUserInteraction,
selectionControls: i * 4 +
j -
1 ==
1
? textSelectionControls
: null,
// focusNode:
// _focusNodes[i * 4 + j - 1],
onChanged: (value) {
final FormInputStatus
formInputStatus;
if (value.isEmpty) {
formInputStatus =
FormInputStatus
.empty;
} else if (_isValidMnemonicWord(
value
.trim()
.toLowerCase())) {
formInputStatus =
FormInputStatus
.valid;
} else {
formInputStatus =
FormInputStatus
.invalid;
}
// if (formInputStatus ==
// FormInputStatus.valid) {
// if (i * 4 + j <
// _focusNodes.length) {
// _focusNodes[i * 4 + j]
// .requestFocus();
// } else if (i * 4 + j ==
// _focusNodes.length) {
// _focusNodes[i * 4 + j - 1]
// .unfocus();
// }
// }
setState(() {
_inputStatuses[
i * 4 + j - 1] =
formInputStatus;
});
},
controller: _controllers[
i * 4 + j - 1],
style: STextStyles.field(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textRestore,
fontSize:
isDesktop ? 16 : 14,
),
),
if (_inputStatuses[
i * 4 + j - 1] ==
FormInputStatus.invalid)
Align(
alignment:
Alignment.topLeft,
child: Padding(
padding:
const EdgeInsets
.only(
left: 12.0,
bottom: 4.0,
),
child: Text(
"Please check spelling",
textAlign:
TextAlign.left,
style:
STextStyles.label(
context)
.copyWith(
color: Theme.of(
context)
.extension<
StackColors>()!
.textError,
),
),
),
)
],
),
),
],
expandingChild: null,
),
if (remainder > 0)
TableViewRow(
spacing: 16,
cells: [
for (int i = rows * cols;
i < _seedWordCount;
i++) ...[
TableViewCell(
flex: 1,
child: Column(
children: [
TextFormField(
autocorrect: !isDesktop,
enableSuggestions:
!isDesktop,
textCapitalization:
TextCapitalization.none,
key: Key(
"restoreMnemonicFormField_$i"),
decoration:
_getInputDecorationFor(
_inputStatuses[i],
"${i + 1}"),
autovalidateMode:
AutovalidateMode
.onUserInteraction,
selectionControls: i == 1
? textSelectionControls
: null,
// focusNode: _focusNodes[i],
onChanged: (value) {
final FormInputStatus
formInputStatus;
if (value.isEmpty) {
formInputStatus =
FormInputStatus
.empty;
} else if (_isValidMnemonicWord(
value
.trim()
.toLowerCase())) {
formInputStatus =
FormInputStatus
.valid;
} else {
formInputStatus =
FormInputStatus
.invalid;
}
// if (formInputStatus ==
// FormInputStatus
// .valid &&
// (i - 1) <
// _focusNodes.length) {
// Focus.of(context)
// .requestFocus(
// _focusNodes[i]);
// }
// if (formInputStatus ==
// FormInputStatus.valid) {
// if (i + 1 <
// _focusNodes.length) {
// _focusNodes[i + 1]
// .requestFocus();
// } else if (i + 1 ==
// _focusNodes.length) {
// _focusNodes[i].unfocus();
// }
// }
},
controller: _controllers[i],
style: STextStyles.field(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.overlay,
fontSize:
isDesktop ? 16 : 14,
),
),
if (_inputStatuses[i] ==
FormInputStatus.invalid)
Align(
alignment:
Alignment.topLeft,
child: Padding(
padding:
const EdgeInsets
.only(
left: 12.0,
bottom: 4.0,
),
child: Text(
"Please check spelling",
textAlign:
TextAlign.left,
style:
STextStyles.label(
context)
.copyWith(
color: Theme.of(
context)
.extension<
StackColors>()!
.textError,
),
),
),
)
],
),
),
],
for (int i = remainder;
i < cols;
i++) ...[
TableViewCell(
flex: 1,
child: Container(),
),
],
],
expandingChild: null,
),
],
),
SvgPicture.asset(
Assets.svg.clipboard,
width: 22,
height: 22,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
const SizedBox(
height: 32,
),
PrimaryButton(
label: "Restore wallet",
width: 480,
onPressed: requestRestore,
width: 8,
),
Text(
"Paste",
style: STextStyles
.desktopButtonSmallSecondaryEnabled(
context),
)
],
);
},
),
),
),
],
),
if (isDesktop)
const SizedBox(
height: 20,
),
if (isDesktop)
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 1008,
),
/*if (isDesktop)
child: Builder(
builder: (BuildContext context) {
const cols = 4;
final int rows = _seedWordCount ~/ cols;
final int remainder = _seedWordCount % cols;
return Column(
children: [
Form(
key: _formKey,
child: TableView(
shrinkWrap: true,
rowSpacing: 20,
rows: [
for (int i = 0; i < rows; i++)
TableViewRow(
crossAxisAlignment:
CrossAxisAlignment.start,
spacing: 16,
cells: [
for (int j = 1; j <= cols; j++)
TableViewCell(
flex: 1,
child: Column(
children: [
TextFormField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
textCapitalization:
TextCapitalization.none,
key: Key(
"restoreMnemonicFormField_$i"),
decoration:
_getInputDecorationFor(
_inputStatuses[
i * 4 + j - 1],
"${i * 4 + j}"),
autovalidateMode:
AutovalidateMode
.onUserInteraction,
selectionControls: i * 4 +
j -
1 ==
1
? textSelectionControls
: null,
// focusNode:
// _focusNodes[i * 4 + j - 1],
onChanged: (value) {
final FormInputStatus
formInputStatus;
if (value.isEmpty) {
formInputStatus =
FormInputStatus.empty;
} else if (_isValidMnemonicWord(
value
.trim()
.toLowerCase())) {
formInputStatus =
FormInputStatus.valid;
} else {
formInputStatus =
FormInputStatus
.invalid;
}
// if (formInputStatus ==
// FormInputStatus.valid) {
// if (i * 4 + j <
// _focusNodes.length) {
// _focusNodes[i * 4 + j]
// .requestFocus();
// } else if (i * 4 + j ==
// _focusNodes.length) {
// _focusNodes[i * 4 + j - 1]
// .unfocus();
// }
// }
setState(() {
_inputStatuses[i * 4 +
j -
1] = formInputStatus;
});
},
controller: _controllers[
i * 4 + j - 1],
style:
STextStyles.field(context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textRestore,
fontSize:
isDesktop ? 16 : 14,
),
),
if (_inputStatuses[
i * 4 + j - 1] ==
FormInputStatus.invalid)
Align(
alignment:
Alignment.topLeft,
child: Padding(
padding:
const EdgeInsets.only(
left: 12.0,
bottom: 4.0,
),
child: Text(
"Please check spelling",
textAlign:
TextAlign.left,
style:
STextStyles.label(
context)
.copyWith(
color: Theme.of(
context)
.extension<
StackColors>()!
.textError,
),
),
),
)
],
),
),
],
expandingChild: null,
),
if (remainder > 0)
TableViewRow(
spacing: 16,
cells: [
for (int i = rows * cols;
i < _seedWordCount - remainder;
i++) ...[
TableViewCell(
flex: 1,
child: Column(
// ... (existing code for input field)
),
),
],
for (int i = _seedWordCount - remainder;
i < _seedWordCount;
i++) ...[
TableViewCell(
flex: 1,
child: Column(
children: [
TextFormField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
textCapitalization:
TextCapitalization.none,
key: Key(
"restoreMnemonicFormField_$i"),
decoration:
_getInputDecorationFor(
_inputStatuses[i],
"${i + 1}"),
autovalidateMode:
AutovalidateMode
.onUserInteraction,
selectionControls: i == 1
? textSelectionControls
: null,
onChanged: (value) {
final FormInputStatus
formInputStatus;
if (value.isEmpty) {
formInputStatus =
FormInputStatus.empty;
} else if (_isValidMnemonicWord(
value
.trim()
.toLowerCase())) {
formInputStatus =
FormInputStatus.valid;
} else {
formInputStatus =
FormInputStatus
.invalid;
}
setState(() {
_inputStatuses[i] =
formInputStatus;
});
},
controller: _controllers[i],
style:
STextStyles.field(context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.overlay,
fontSize:
isDesktop ? 16 : 14,
),
),
if (_inputStatuses[i] ==
FormInputStatus.invalid)
Align(
alignment:
Alignment.topLeft,
child: Padding(
padding:
const EdgeInsets.only(
left: 12.0,
bottom: 4.0,
),
child: Text(
"Please check spelling",
textAlign:
TextAlign.left,
style:
STextStyles.label(
context)
.copyWith(
color: Theme.of(
context)
.extension<
StackColors>()!
.textError,
),
),
),
)
],
),
),
],
for (int i = 0;
i < cols - remainder;
i++) ...[
TableViewCell(
flex: 1,
child: Container(),
),
],
],
expandingChild: null,
),
],
),
),
const SizedBox(
height: 32,
),
PrimaryButton(
label: "Restore wallet",
width: 480,
onPressed: requestRestore,
),
],
);
},
),
),
/*if (isDesktop)
const Spacer(
flex: 15,
),*/
if (!isDesktop)
Padding(
padding: const EdgeInsets.all(4.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 1; i <= _seedWordCount; i++)
Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: TextFormField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
textCapitalization:
TextCapitalization.none,
key: Key("restoreMnemonicFormField_$i"),
decoration: _getInputDecorationFor(
_inputStatuses[i - 1], "$i"),
autovalidateMode:
AutovalidateMode.onUserInteraction,
selectionControls:
i == 1 ? textSelectionControls : null,
// focusNode: _focusNodes[i - 1],
onChanged: (value) {
final FormInputStatus formInputStatus;
if (!isDesktop)
Padding(
padding: const EdgeInsets.all(4.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 1; i <= _seedWordCount; i++)
Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: TextFormField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
textCapitalization: TextCapitalization.none,
key: Key("restoreMnemonicFormField_$i"),
decoration: _getInputDecorationFor(
_inputStatuses[i - 1], "$i"),
autovalidateMode:
AutovalidateMode.onUserInteraction,
selectionControls:
i == 1 ? textSelectionControls : null,
// focusNode: _focusNodes[i - 1],
onChanged: (value) {
final FormInputStatus formInputStatus;
if (value.isEmpty) {
formInputStatus =
FormInputStatus.empty;
} else if (_isValidMnemonicWord(
value.trim().toLowerCase())) {
formInputStatus =
FormInputStatus.valid;
} else {
formInputStatus =
FormInputStatus.invalid;
}
if (value.isEmpty) {
formInputStatus = FormInputStatus.empty;
} else if (_isValidMnemonicWord(
value.trim().toLowerCase())) {
formInputStatus = FormInputStatus.valid;
} else {
formInputStatus =
FormInputStatus.invalid;
}
// if (formInputStatus ==
// FormInputStatus.valid) {
// if (i < _focusNodes.length) {
// _focusNodes[i].requestFocus();
// } else if (i == _focusNodes.length) {
// _focusNodes[i - 1].unfocus();
// }
// }
setState(() {
_inputStatuses[i - 1] =
formInputStatus;
});
},
controller: _controllers[i - 1],
style:
STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textRestore,
fontSize: isDesktop ? 16 : 14,
),
// if (formInputStatus ==
// FormInputStatus.valid) {
// if (i < _focusNodes.length) {
// _focusNodes[i].requestFocus();
// } else if (i == _focusNodes.length) {
// _focusNodes[i - 1].unfocus();
// }
// }
setState(() {
_inputStatuses[i - 1] = formInputStatus;
});
},
controller: _controllers[i - 1],
style: STextStyles.field(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textRestore,
fontSize: isDesktop ? 16 : 14,
),
),
if (_inputStatuses[i - 1] ==
FormInputStatus.invalid)
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(
left: 12.0,
bottom: 4.0,
),
child: Text(
"Please check spelling",
textAlign: TextAlign.left,
style: STextStyles.label(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textError,
),
),
if (_inputStatuses[i - 1] ==
FormInputStatus.invalid)
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(
left: 12.0,
bottom: 4.0,
),
child: Text(
"Please check spelling",
textAlign: TextAlign.left,
style:
STextStyles.label(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textError,
),
),
)
],
),
Padding(
padding: const EdgeInsets.only(
top: 8.0,
),
child: PrimaryButton(
onPressed: requestRestore,
label: "Restore",
),
),
)
],
),
],
),
Padding(
padding: const EdgeInsets.only(
top: 8.0,
),
child: PrimaryButton(
onPressed: requestRestore,
label: "Restore",
),
),
],
),
),
],
),
),
],
),
),
),
);
),
);
}
}

View file

@ -8,8 +8,12 @@
*
*/
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/networking/http.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/prefs.dart';
class PayNymBot extends StatelessWidget {
const PayNymBot({
@ -28,16 +32,37 @@ class PayNymBot extends StatelessWidget {
child: SizedBox(
width: size,
height: size,
child: Image.network(
"https://paynym.is/$paymentCodeString/avatar",
loadingBuilder: (context, child, loadingProgress) =>
loadingProgress == null
? child
: const Center(
child: LoadingIndicator(),
),
child: FutureBuilder<Uint8List>(
future: _fetchImage(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.memory(snapshot.data!);
} else if (snapshot.hasError) {
return const Center(child: Icon(Icons.error));
} else {
return const Center(); // TODO [prio=low]: Make better loading indicator.
}
},
),
),
);
}
Future<Uint8List> _fetchImage() async {
final HTTP client = HTTP();
final Uri uri = Uri.parse("https://paynym.is/$paymentCodeString/avatar");
final response = await client.get(
url: uri,
proxyInfo: Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null,
);
if (response.code == 200) {
return Uint8List.fromList(response.bodyBytes);
} else {
throw Exception('Failed to load image');
}
}
}

View file

@ -10,31 +10,46 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_buttons/simple_edit_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class AddressCard extends ConsumerStatefulWidget {
const AddressCard({
Key? key,
super.key,
required this.addressId,
required this.walletId,
required this.coin,
this.onPressed,
this.clipboard = const ClipboardWrapper(),
}) : super(key: key);
});
final int addressId;
final String walletId;
@ -47,6 +62,7 @@ class AddressCard extends ConsumerStatefulWidget {
}
class _AddressCardState extends ConsumerState<AddressCard> {
final _qrKey = GlobalKey();
final isDesktop = Util.isDesktop;
late Stream<AddressLabel?> stream;
@ -54,6 +70,72 @@ class _AddressCardState extends ConsumerState<AddressCard> {
AddressLabel? label;
Future<void> _capturePng(bool shouldSaveInsteadOfShare) async {
try {
final RenderRepaintBoundary boundary =
_qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage();
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
if (shouldSaveInsteadOfShare) {
if (Util.isDesktop) {
final dir = Directory("${Platform.environment['HOME']}");
if (!dir.existsSync()) {
throw Exception(
"Home dir not found while trying to open filepicker on QR image save");
}
final path = await FilePicker.platform.saveFile(
fileName: "qrcode.png",
initialDirectory: dir.path,
);
if (path != null) {
final file = File(path);
if (file.existsSync()) {
if (mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.warning,
message: "$path already exists!",
context: context,
),
);
}
} else {
await file.writeAsBytes(pngBytes);
if (mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.success,
message: "$path saved!",
context: context,
),
);
}
}
}
} else {
// await DocumentFileSavePlus.saveFile(
// pngBytes,
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png",
// "image/png");
}
} else {
final tempDir = await getTemporaryDirectory();
final file = await File("${tempDir.path}/qrcode.png").create();
await file.writeAsBytes(pngBytes);
await Share.shareFiles(["${tempDir.path}/qrcode.png"],
text: "Receive URI QR Code");
}
} catch (e) {
//todo: comeback to this
debugPrint(e.toString());
}
}
@override
void initState() {
address = MainDB.instance.isar.addresses
@ -117,16 +199,32 @@ class _AddressCardState extends ConsumerState<AddressCard> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (label!.value.isNotEmpty)
Text(
label!.value,
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
),
if (label!.value.isNotEmpty)
SizedBox(
height: isDesktop ? 2 : 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label!.value.isNotEmpty ? label!.value : "No label",
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
),
SimpleEditButton(
editValue: label!.value,
editLabel: 'label',
overrideTitle: 'Edit label',
disableIcon: true,
onValueChanged: (value) {
MainDB.instance.putAddressLabel(
label!.copyWith(
label: value,
),
);
},
),
],
),
SizedBox(
height: isDesktop ? 2 : 8,
),
Row(
children: [
Expanded(
@ -140,18 +238,152 @@ class _AddressCardState extends ConsumerState<AddressCard> {
const SizedBox(
height: 10,
),
if (label!.tags != null && label!.tags!.isNotEmpty)
Wrap(
spacing: 10,
runSpacing: 10,
children: label!.tags!
.map(
(e) => AddressTag(
tag: e,
Row(
children: [
CustomTextButton(
text: "Copy address",
onTap: () {
widget.clipboard
.setData(
ClipboardData(
text: address.value,
),
)
.toList(),
),
.then((value) {
if (context.mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
context: context,
),
);
}
});
},
),
const SizedBox(
width: 16,
),
CustomTextButton(
text: "Show QR code",
onTap: () async {
await showDialog<void>(
context: context,
builder: (_) {
return StackDialogBase(
child: Column(
children: [
if (label!.value.isNotEmpty)
Text(
label!.value,
style: STextStyles.w600_18(context),
),
if (label!.value.isNotEmpty)
const SizedBox(
height: 8,
),
Text(
address.value,
style:
STextStyles.w500_16(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
const SizedBox(
height: 16,
),
Center(
child: RepaintBoundary(
key: _qrKey,
child: QrImageView(
data: AddressUtils.buildUriString(
widget.coin,
address.value,
{},
),
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
const SizedBox(
height: 16,
),
Row(
children: [
if (!isDesktop)
Expanded(
child: SecondaryButton(
label: "Share",
buttonHeight: isDesktop
? ButtonHeight.l
: null,
icon: SvgPicture.asset(
Assets.svg.share,
width: 14,
height: 14,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
onPressed: () async {
await _capturePng(false);
},
),
),
if (isDesktop)
Expanded(
child: PrimaryButton(
buttonHeight: isDesktop
? ButtonHeight.l
: null,
onPressed: () async {
// TODO: add save functionality instead of share
// save works on linux at the moment
await _capturePng(true);
},
label: "Save",
icon: SvgPicture.asset(
Assets.svg.arrowDown,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary,
),
),
),
],
)
],
),
);
},
);
},
),
],
),
// if (label!.tags != null && label!.tags!.isNotEmpty)
// Wrap(
// spacing: 10,
// runSpacing: 10,
// children: label!.tags!
// .map(
// (e) => AddressTag(
// tag: e,
// ),
// )
// .toList(),
// ),
],
),
);

View file

@ -10,14 +10,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_card.dart';
import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
@ -25,13 +23,8 @@ import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:tuple/tuple.dart';
import '../../../utilities/assets.dart';
import '../../../widgets/icon_widgets/x_icon.dart';
import '../../../widgets/textfield_icon_button.dart';
class WalletAddressesView extends ConsumerStatefulWidget {
const WalletAddressesView({
Key? key,
@ -50,10 +43,10 @@ class WalletAddressesView extends ConsumerStatefulWidget {
class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
final bool isDesktop = Util.isDesktop;
String _searchString = "";
final String _searchString = "";
late final TextEditingController _searchController;
final searchFieldFocusNode = FocusNode();
// late final TextEditingController _searchController;
// final searchFieldFocusNode = FocusNode();
Future<List<int>> _search(String term) async {
if (term.isEmpty) {
@ -119,19 +112,19 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
.findAll();
}
@override
void initState() {
_searchController = TextEditingController();
super.initState();
}
@override
void dispose() {
_searchController.dispose();
searchFieldFocusNode.dispose();
super.dispose();
}
// @override
// void initState() {
// _searchController = TextEditingController();
//
// super.initState();
// }
//
// @override
// void dispose() {
// _searchController.dispose();
// searchFieldFocusNode.dispose();
// super.dispose();
// }
@override
Widget build(BuildContext context) {
@ -165,74 +158,74 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
),
child: Column(
children: [
SizedBox(
width: isDesktop ? 490 : null,
child: ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: searchFieldFocusNode,
onChanged: (value) {
setState(() {
_searchString = value;
});
},
style: isDesktop
? STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
height: 1.8,
)
: STextStyles.field(context),
decoration: standardInputDecoration(
"Search...",
searchFieldFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: EdgeInsets.symmetric(
horizontal: isDesktop ? 12 : 10,
vertical: isDesktop ? 18 : 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: isDesktop ? 20 : 16,
height: isDesktop ? 20 : 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
_searchString = "";
});
},
),
],
),
),
)
: null,
),
),
),
),
SizedBox(
height: isDesktop ? 20 : 16,
),
// SizedBox(
// width: isDesktop ? 490 : null,
// child: ClipRRect(
// borderRadius: BorderRadius.circular(
// Constants.size.circularBorderRadius,
// ),
// child: TextField(
// autocorrect: !isDesktop,
// enableSuggestions: !isDesktop,
// controller: _searchController,
// focusNode: searchFieldFocusNode,
// onChanged: (value) {
// setState(() {
// _searchString = value;
// });
// },
// style: isDesktop
// ? STextStyles.desktopTextExtraSmall(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .textFieldActiveText,
// height: 1.8,
// )
// : STextStyles.field(context),
// decoration: standardInputDecoration(
// "Search...",
// searchFieldFocusNode,
// context,
// desktopMed: isDesktop,
// ).copyWith(
// prefixIcon: Padding(
// padding: EdgeInsets.symmetric(
// horizontal: isDesktop ? 12 : 10,
// vertical: isDesktop ? 18 : 16,
// ),
// child: SvgPicture.asset(
// Assets.svg.search,
// width: isDesktop ? 20 : 16,
// height: isDesktop ? 20 : 16,
// ),
// ),
// suffixIcon: _searchController.text.isNotEmpty
// ? Padding(
// padding: const EdgeInsets.only(right: 0),
// child: UnconstrainedBox(
// child: Row(
// children: [
// TextFieldIconButton(
// child: const XIcon(),
// onTap: () async {
// setState(() {
// _searchController.text = "";
// _searchString = "";
// });
// },
// ),
// ],
// ),
// ),
// )
// : null,
// ),
// ),
// ),
// ),
// SizedBox(
// height: isDesktop ? 20 : 16,
// ),
Expanded(
child: FutureBuilder(
future: _search(_searchString),
@ -249,15 +242,17 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
walletId: widget.walletId,
addressId: snapshot.data![index],
coin: coin,
onPressed: () {
Navigator.of(context).pushNamed(
AddressDetailsView.routeName,
arguments: Tuple2(
snapshot.data![index],
widget.walletId,
),
);
},
onPressed: !isDesktop
? null
: () {
Navigator.of(context).pushNamed(
AddressDetailsView.routeName,
arguments: Tuple2(
snapshot.data![index],
widget.walletId,
),
);
},
),
);
} else {

View file

@ -30,8 +30,11 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'package:stackwallet/widgets/background.dart';
@ -39,15 +42,17 @@ import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class ReceiveView extends ConsumerStatefulWidget {
const ReceiveView({
Key? key,
super.key,
required this.walletId,
this.tokenContract,
this.clipboard = const ClipboardWrapper(),
}) : super(key: key);
});
static const String routeName = "/receiveView";
@ -63,11 +68,14 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
late final Coin coin;
late final String walletId;
late final ClipboardInterface clipboard;
late final bool supportsSpark;
late final bool _supportsSpark;
late final bool _showMultiType;
String? _sparkAddress;
String? _qrcodeContent;
bool _showSparkAddress = true;
int _currentIndex = 0;
final List<AddressType> _walletAddressTypes = [];
final Map<AddressType, String> _addressMap = {};
final Map<AddressType, StreamSubscription<Address?>> _addressSubMap = {};
Future<void> generateNewAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId);
@ -95,13 +103,32 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
),
);
await wallet.generateNewReceivingAddress();
final Address? address;
if (wallet is Bip39HDWallet && wallet is! BCashInterface) {
final type = DerivePathType.values.firstWhere(
(e) => e.getAddressType() == _walletAddressTypes[_currentIndex],
);
address = await wallet.generateNextReceivingAddress(
derivePathType: type,
);
await ref.read(mainDBProvider).isar.writeTxn(() async {
await ref.read(mainDBProvider).isar.addresses.put(address!);
});
} else {
await wallet.generateNewReceivingAddress();
address = null;
}
shouldPop = true;
if (mounted) {
Navigator.of(context)
.popUntil(ModalRoute.withName(ReceiveView.routeName));
setState(() {
_addressMap[_walletAddressTypes[_currentIndex]] =
address?.value ?? ref.read(pWalletReceivingAddress(walletId));
});
}
}
}
@ -140,45 +167,64 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
if (mounted) {
Navigator.of(context, rootNavigator: true).pop();
if (_sparkAddress != address.value) {
setState(() {
_sparkAddress = address.value;
});
}
setState(() {
_addressMap[AddressType.spark] = address.value;
});
}
}
}
StreamSubscription<Address?>? _streamSub;
@override
void initState() {
walletId = widget.walletId;
coin = ref.read(pWalletCoin(walletId));
clipboard = widget.clipboard;
supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
final wallet = ref.read(pWallets).getWallet(walletId);
_supportsSpark = wallet is SparkInterface;
_showMultiType = _supportsSpark ||
(wallet is! BCashInterface &&
wallet is Bip39HDWallet &&
wallet.supportedAddressTypes.length > 1);
if (supportsSpark) {
_streamSub = ref
.read(mainDBProvider)
.isar
.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.typeEqualTo(AddressType.spark)
.sortByDerivationIndexDesc()
.findFirst()
.asStream()
.listen((event) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_sparkAddress = event?.value;
});
}
_walletAddressTypes.add(coin.primaryAddressType);
if (_showMultiType) {
if (_supportsSpark) {
_walletAddressTypes.insert(0, AddressType.spark);
} else {
_walletAddressTypes.addAll((wallet as Bip39HDWallet)
.supportedAddressTypes
.where((e) => e != coin.primaryAddressType));
}
}
_addressMap[_walletAddressTypes[_currentIndex]] =
ref.read(pWalletReceivingAddress(walletId));
if (_showMultiType) {
for (final type in _walletAddressTypes) {
_addressSubMap[type] = ref
.read(mainDBProvider)
.isar
.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.typeEqualTo(type)
.sortByDerivationIndexDesc()
.findFirst()
.asStream()
.listen((event) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_addressMap[type] =
event?.value ?? _addressMap[type] ?? "[No address yet]";
});
}
});
});
});
}
}
super.initState();
@ -186,7 +232,9 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
@override
void dispose() {
_streamSub?.cancel();
for (final subscription in _addressSubMap.values) {
subscription.cancel();
}
super.dispose();
}
@ -196,14 +244,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
final ticker = widget.tokenContract?.symbol ?? coin.ticker;
if (supportsSpark) {
if (_showSparkAddress) {
_qrcodeContent = _sparkAddress;
} else {
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
}
final String address;
if (_showMultiType) {
address = _addressMap[_walletAddressTypes[_currentIndex]]!;
} else {
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
address = ref.watch(pWalletReceivingAddress(walletId));
}
return Background(
@ -319,33 +364,40 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ConditionalParent(
condition: supportsSpark,
condition: _showMultiType,
builder: (child) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Address type",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.infoItemLabel,
),
),
const SizedBox(
height: 10,
),
DropdownButtonHideUnderline(
child: DropdownButton2<bool>(
value: _showSparkAddress,
child: DropdownButton2<int>(
value: _currentIndex,
items: [
DropdownMenuItem(
value: true,
child: Text(
"Spark address",
style: STextStyles.desktopTextMedium(context),
for (int i = 0;
i < _walletAddressTypes.length;
i++)
DropdownMenuItem(
value: i,
child: Text(
"${_walletAddressTypes[i].readableName} address",
style: STextStyles.w500_14(context),
),
),
),
DropdownMenuItem(
value: false,
child: Text(
"Transparent address",
style: STextStyles.desktopTextMedium(context),
),
),
],
onChanged: (value) {
if (value is bool && value != _showSparkAddress) {
if (value != null && value != _currentIndex) {
setState(() {
_showSparkAddress = value;
_currentIndex = value;
});
}
},
@ -363,6 +415,16 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
),
),
),
buttonStyleData: ButtonStyleData(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
dropdownStyleData: DropdownStyleData(
offset: const Offset(0, -10),
elevation: 0,
@ -386,89 +448,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
const SizedBox(
height: 12,
),
if (_showSparkAddress)
GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: _sparkAddress ?? "Error"),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
width: 1,
),
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
child: RoundedWhiteContainer(
child: Column(
children: [
Row(
children: [
Text(
"Your ${coin.ticker} SPARK address",
style:
STextStyles.itemSubtitle(context),
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 15,
height: 15,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
],
),
const SizedBox(
height: 8,
),
Row(
children: [
Expanded(
child: Text(
_sparkAddress ?? "Error",
style: STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
],
),
],
),
),
),
),
if (!_showSparkAddress) child,
child,
],
),
child: GestureDetector(
@ -476,8 +456,8 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
HapticFeedback.lightImpact();
clipboard.setData(
ClipboardData(
text:
ref.watch(pWalletReceivingAddress(walletId))),
text: address,
),
);
showFloatingFlushBar(
type: FlushBarType.info,
@ -524,8 +504,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
children: [
Expanded(
child: Text(
ref.watch(
pWalletReceivingAddress(walletId)),
address,
style: STextStyles.itemSubtitle12(context),
),
),
@ -536,31 +515,44 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
),
),
),
const SizedBox(
height: 12,
),
PrimaryButton(
label: "Copy address",
onPressed: () {
HapticFeedback.lightImpact();
clipboard.setData(
ClipboardData(
text: address,
),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
),
if (ref.watch(pWallets
.select((value) => value.getWallet(walletId)))
is MultiAddressInterface ||
supportsSpark)
_supportsSpark)
const SizedBox(
height: 12,
),
if (ref.watch(pWallets
.select((value) => value.getWallet(walletId)))
is MultiAddressInterface ||
supportsSpark)
TextButton(
onPressed: supportsSpark && _showSparkAddress
_supportsSpark)
SecondaryButton(
label: "Generate new address",
onPressed: _supportsSpark &&
_walletAddressTypes[_currentIndex] ==
AddressType.spark
? generateNewSparkAddress
: generateNewAddress,
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
child: Text(
"Generate new address",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
const SizedBox(
height: 30,
@ -574,7 +566,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
QrImageView(
data: AddressUtils.buildUriString(
coin,
_qrcodeContent ?? "",
address,
{},
),
size: MediaQuery.of(context).size.width / 2,
@ -585,7 +577,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
height: 20,
),
CustomTextButton(
text: "Create new QR code",
text: "Advanced options",
onTap: () async {
unawaited(Navigator.of(context).push(
RouteGenerator.getRoute(
@ -593,7 +585,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView(
coin: coin,
receivingAddress: _qrcodeContent ?? "",
receivingAddress: address,
),
settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName,

View file

@ -20,13 +20,15 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/dialogs/tor_warning_dialog.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class HiddenSettings extends StatelessWidget {
const HiddenSettings({Key? key}) : super(key: key);
const HiddenSettings({super.key});
static const String routeName = "/hiddenSettings";
@ -246,24 +248,53 @@ class HiddenSettings extends StatelessWidget {
}
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
await showDialog<bool>(
context: context,
builder: (_) => TorWarningDialog(
coin: Coin.stellar,
),
);
},
child: RoundedWhiteContainer(
child: Text(
"Show Tor warning popup",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
ref
.read(prefsChangeNotifierProvider)
.frostEnabled =
.solanaEnabled =
!(ref
.read(prefsChangeNotifierProvider)
.frostEnabled);
.solanaEnabled);
if (kDebugMode) {
print(
"FROST enabled: ${ref.read(prefsChangeNotifierProvider).frostEnabled}");
"Solana enabled: ${ref.read(prefsChangeNotifierProvider).solanaEnabled}");
}
},
child: RoundedWhiteContainer(
child: Text(
"Toggle FROST multisig",
"Toggle Solana",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!

View file

@ -14,18 +14,21 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:solana/solana.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/test_epic_box_connection.dart';
import 'package:stackwallet/utilities/test_eth_node_connection.dart';
import 'package:stackwallet/utilities/test_monero_node_connection.dart';
import 'package:stackwallet/utilities/test_stellar_node_connection.dart';
import 'package:stackwallet/utilities/text_styles.dart';
@ -173,17 +176,14 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
case Coin.bitcoincashTestnet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
final client = ElectrumXClient(
host: formData.host!,
port: formData.port!,
useSSL: formData.useSSL!,
failovers: [],
prefs: ref.read(prefsChangeNotifierProvider),
coin: coin,
);
try {
testPassed = await client.ping();
testPassed = await checkElectrumServer(
host: formData.host!,
port: formData.port!,
useSSL: formData.useSSL!,
overridePrefs: ref.read(prefsChangeNotifierProvider),
overrideTorService: ref.read(pTorService),
);
} catch (_) {
testPassed = false;
}
@ -191,14 +191,13 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
break;
case Coin.ethereum:
// TODO fix this
// final client = Web3Client(
// "https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba",
// Client());
try {
// await client.getSyncStatus();
} catch (_) {}
testPassed = await testEthNodeConnection(formData.host!);
} catch (_) {
testPassed = false;
}
break;
case Coin.stellar:
case Coin.stellarTestnet:
try {
@ -218,6 +217,20 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
);
} catch (_) {}
break;
case Coin.solana:
try {
RpcClient rpcClient;
if (formData.host!.startsWith("http") || formData.host!.startsWith("https")) {
rpcClient = RpcClient("${formData.host}:${formData.port}");
} else {
rpcClient = RpcClient("http://${formData.host}:${formData.port}");
}
await rpcClient.getEpochInfo().then((value) => testPassed = true);
} catch (_) {
testPassed = false;
}
break;
}
if (showFlushBar && mounted) {
@ -758,6 +771,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
case Coin.nano:
case Coin.banano:
case Coin.eCash:
case Coin.solana:
case Coin.stellar:
case Coin.stellarTestnet:
case Coin.bitcoinFrost:

View file

@ -13,13 +13,15 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:solana/solana.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -150,17 +152,14 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node!.host,
port: node.port,
useSSL: node.useSSL,
failovers: [],
prefs: ref.read(prefsChangeNotifierProvider),
coin: coin,
);
try {
testPassed = await client.ping();
testPassed = await checkElectrumServer(
host: node!.host,
port: node.port,
useSSL: node.useSSL,
overridePrefs: ref.read(prefsChangeNotifierProvider),
overrideTorService: ref.read(pTorService),
);
} catch (_) {
testPassed = false;
}
@ -195,6 +194,20 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
testPassed = false;
}
break;
case Coin.solana:
try {
RpcClient rpcClient;
if (node!.host.startsWith("http") || node.host.startsWith("https")) {
rpcClient = RpcClient("${node.host}:${node.port}");
} else {
rpcClient = RpcClient("http://${node.host}:${node.port}");
}
await rpcClient.getEpochInfo().then((value) => testPassed = true);
} catch (_) {
testPassed = false;
}
break;
}
if (testPassed) {

View file

@ -17,6 +17,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages/wallets_view/wallets_overview.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/supported_coins.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -26,6 +27,7 @@ import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
import 'package:stackwallet/widgets/dialogs/tor_warning_dialog.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class WalletListItem extends ConsumerWidget {
@ -58,6 +60,25 @@ class WalletListItem extends ConsumerWidget {
BorderRadius.circular(Constants.size.circularBorderRadius),
),
onPressed: () async {
// Check if Tor is enabled...
if (ref.read(prefsChangeNotifierProvider).useTor) {
// ... and if the coin supports Tor.
final cryptocurrency = SupportedCoins.coins[coin];
if (cryptocurrency != null && !cryptocurrency!.torSupport) {
// If not, show a Tor warning dialog.
final shouldContinue = await showDialog<bool>(
context: context,
builder: (_) => TorWarningDialog(
coin: coin,
),
) ??
false;
if (!shouldContinue) {
return;
}
}
}
if (walletCount == 1 && coin != Coin.ethereum) {
final wallet = ref
.read(pWallets)

View file

@ -29,10 +29,10 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart';
class DesktopAddressList extends ConsumerStatefulWidget {
const DesktopAddressList({
Key? key,
super.key,
required this.walletId,
this.searchHeight,
}) : super(key: key);
});
final String walletId;
final double? searchHeight;

View file

@ -15,6 +15,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/wallets_view/wallets_overview.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/supported_coins.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -24,6 +25,7 @@ import 'package:stackwallet/wallets/isar/providers/all_wallets_info_provider.dar
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/dialogs/tor_warning_dialog.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class WalletSummaryTable extends ConsumerStatefulWidget {
@ -85,7 +87,26 @@ class _DesktopWalletSummaryRowState
extends ConsumerState<DesktopWalletSummaryRow> {
bool _hovering = false;
void _onPressed() {
void _onPressed() async {
// Check if Tor is enabled...
if (ref.read(prefsChangeNotifierProvider).useTor) {
// ... and if the coin supports Tor.
final cryptocurrency = SupportedCoins.coins[widget.coin];
if (cryptocurrency != null && !cryptocurrency!.torSupport) {
// If not, show a Tor warning dialog.
final shouldContinue = await showDialog<bool>(
context: context,
builder: (_) => TorWarningDialog(
coin: widget.coin,
),
) ??
false;
if (!shouldContinue) {
return;
}
}
}
showDialog<void>(
context: context,
builder: (_) => DesktopDialog(

View file

@ -1,4 +1,4 @@
/*
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
@ -9,14 +9,13 @@
*/
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stackwallet/models/notification_model.dart';
import 'package:stackwallet/services/notifications_service.dart';
import 'package:stackwallet/utilities/prefs.dart';
class NotificationApi {
static final _notifications = FlutterLocalNotificationsPlugin();
static final onNotifications = BehaviorSubject<String?>();
// static final onNotifications = BehaviorSubject<String?>();
static Future<NotificationDetails> _notificationDetails() async {
return const NotificationDetails(
@ -25,17 +24,17 @@ class NotificationApi {
// importance: Importance.max,
priority: Priority.high,
ticker: 'ticker'),
iOS: IOSNotificationDetails(),
macOS: MacOSNotificationDetails(),
iOS: DarwinNotificationDetails(),
macOS: DarwinNotificationDetails(),
);
}
static Future<void> init({bool initScheduled = false}) async {
const android = AndroidInitializationSettings('app_icon_alpha');
const iOS = IOSInitializationSettings();
const iOS = DarwinInitializationSettings();
const linux = LinuxInitializationSettings(
defaultActionName: "temporary_stack_wallet");
const macOS = MacOSInitializationSettings();
const macOS = DarwinInitializationSettings();
const settings = InitializationSettings(
android: android,
iOS: iOS,
@ -44,9 +43,12 @@ class NotificationApi {
);
await _notifications.initialize(
settings,
onSelectNotification: (payload) async {
onNotifications.add(payload);
},
// onDidReceiveNotificationResponse: (payload) async {
// onNotifications.add(payload.payload);
// },
// onDidReceiveBackgroundNotificationResponse: (payload) async {
// onNotifications.add(payload.payload);
// },
);
}

View file

@ -147,7 +147,7 @@ class NotificationsService extends ChangeNotifier {
node: eNode,
failovers: failovers,
prefs: prefs,
coin: coin,
cryptoCurrency: wallet.cryptoCurrency,
);
final tx = await client.getTransaction(txHash: txid);

View file

@ -101,7 +101,7 @@ class PriceAPI {
"https://api.coingecko.com/api/v3/coins/markets?vs_currency"
"=${baseCurrency.toLowerCase()}"
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos"
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos,solana"
"&order=market_cap_desc&per_page=50&page=1&sparkline=false");
final coinGeckoResponse = await client.get(
@ -187,34 +187,39 @@ class PriceAPI {
}
try {
final contractAddressesString =
contractAddresses.reduce((value, element) => "$value,$element");
final uri = Uri.parse(
"https://api.coingecko.com/api/v3/simple/token_price/ethereum"
"?vs_currencies=${baseCurrency.toLowerCase()}&contract_addresses"
"=$contractAddressesString&include_24hr_change=true");
final coinGeckoResponse = await client.get(
url: uri,
headers: {'Content-Type': 'application/json'},
proxyInfo: Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null,
);
final coinGeckoData = jsonDecode(coinGeckoResponse.body) as Map;
for (final key in coinGeckoData.keys) {
final contractAddress = key as String;
final map = coinGeckoData[contractAddress] as Map;
final price = Decimal.parse(map[baseCurrency.toLowerCase()].toString());
final change24h = double.parse(
map["${baseCurrency.toLowerCase()}_24h_change"].toString());
tokenPrices[contractAddress] = Tuple2(price, change24h);
}
// for (final contractAddress in contractAddresses) {
// final uri = Uri.parse(
// "https://api.coingecko.com/api/v3/simple/token_price/ethereum"
// "?vs_currencies=${baseCurrency.toLowerCase()}&contract_addresses"
// "=$contractAddress&include_24hr_change=true");
//
// final coinGeckoResponse = await client.get(
// url: uri,
// headers: {'Content-Type': 'application/json'},
// proxyInfo: Prefs.instance.useTor
// ? TorService.sharedInstance.getProxyInfo()
// : null,
// );
//
// try {
// final coinGeckoData = jsonDecode(coinGeckoResponse.body) as Map;
//
// final map = coinGeckoData[contractAddress] as Map;
//
// final price =
// Decimal.parse(map[baseCurrency.toLowerCase()].toString());
// final change24h = double.parse(
// map["${baseCurrency.toLowerCase()}_24h_change"].toString());
//
// tokenPrices[contractAddress] = Tuple2(price, change24h);
// } catch (e, s) {
// // only log the error as we don't want to interrupt the rest of the loop
// Logging.instance.log(
// "getPricesAnd24hChangeForEthTokens($baseCurrency,$contractAddress): $e\n$s\nRESPONSE: ${coinGeckoResponse.body}",
// level: LogLevel.Warning,
// );
// }
// }
return tokenPrices;
} catch (e, s) {

78
lib/supported_coins.dart Normal file
View file

@ -0,0 +1,78 @@
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/banano.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoincash.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/dogecoin.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/ecash.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/epiccash.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/ethereum.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/litecoin.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/monero.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/namecoin.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/nano.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/particl.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/stellar.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/tezos.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/wownero.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
/// The supported coins.
class SupportedCoins {
/// A List of our supported coins.
static final List<CryptoCurrency> cryptocurrencies = [
// Mainnet coins.
Bitcoin(CryptoCurrencyNetwork.main),
Monero(CryptoCurrencyNetwork.main),
Banano(CryptoCurrencyNetwork.main),
Bitcoincash(CryptoCurrencyNetwork.main),
BitcoinFrost(CryptoCurrencyNetwork.main),
Dogecoin(CryptoCurrencyNetwork.main),
Ecash(CryptoCurrencyNetwork.main),
Epiccash(CryptoCurrencyNetwork.main),
Ethereum(CryptoCurrencyNetwork.main),
Firo(CryptoCurrencyNetwork.main),
Litecoin(CryptoCurrencyNetwork.main),
Namecoin(CryptoCurrencyNetwork.main),
Nano(CryptoCurrencyNetwork.main),
Particl(CryptoCurrencyNetwork.main),
Stellar(CryptoCurrencyNetwork.main),
Tezos(CryptoCurrencyNetwork.main),
Wownero(CryptoCurrencyNetwork.main),
/// Testnet coins.
Bitcoin(CryptoCurrencyNetwork.test),
Banano(CryptoCurrencyNetwork.test),
Bitcoincash(CryptoCurrencyNetwork.test),
BitcoinFrost(CryptoCurrencyNetwork.test),
Dogecoin(CryptoCurrencyNetwork.test),
Stellar(CryptoCurrencyNetwork.test),
Firo(CryptoCurrencyNetwork.test),
Litecoin(CryptoCurrencyNetwork.test),
Stellar(CryptoCurrencyNetwork.test),
];
/// A Map linking a CryptoCurrency with its associated Coin.
///
/// Temporary: Remove when the Coin enum is removed.
static final Map<Coin, CryptoCurrency> coins = {
Coin.bitcoin: Bitcoin(CryptoCurrencyNetwork.main),
Coin.monero: Monero(CryptoCurrencyNetwork.main),
Coin.banano: Banano(CryptoCurrencyNetwork.main),
Coin.bitcoincash: Bitcoincash(CryptoCurrencyNetwork.main),
Coin.bitcoinFrost: BitcoinFrost(CryptoCurrencyNetwork.main),
Coin.dogecoin: Dogecoin(CryptoCurrencyNetwork.main),
Coin.eCash: Ecash(CryptoCurrencyNetwork.main),
Coin.epicCash: Epiccash(CryptoCurrencyNetwork.main),
Coin.ethereum: Ethereum(CryptoCurrencyNetwork.main),
Coin.firo: Firo(CryptoCurrencyNetwork.main),
Coin.litecoin: Litecoin(CryptoCurrencyNetwork.main),
Coin.namecoin: Namecoin(CryptoCurrencyNetwork.main),
Coin.nano: Nano(CryptoCurrencyNetwork.main),
Coin.particl: Particl(CryptoCurrencyNetwork.main),
Coin.stellar: Stellar(CryptoCurrencyNetwork.main),
Coin.tezos: Tezos(CryptoCurrencyNetwork.main),
Coin.wownero: Wownero(CryptoCurrencyNetwork.main),
};
}

View file

@ -28,6 +28,7 @@ class CoinThemeColorDefault {
Color get namecoin => const Color(0xFF91B1E1);
Color get wownero => const Color(0xFFED80C1);
Color get particl => const Color(0xFF8175BD);
Color get solana => const Color(0xFFC696FF);
Color get stellar => const Color(0xFF6600FF);
Color get nano => const Color(0xFF209CE9);
Color get banano => const Color(0xFFFBDD11);
@ -66,6 +67,8 @@ class CoinThemeColorDefault {
return wownero;
case Coin.particl:
return particl;
case Coin.solana:
return solana;
case Coin.stellar:
case Coin.stellarTestnet:
return stellar;

View file

@ -1709,6 +1709,8 @@ class StackColors extends ThemeExtension<StackColors> {
return _coin.wownero;
case Coin.particl:
return _coin.particl;
case Coin.solana:
return _coin.solana;
case Coin.stellar:
case Coin.stellarTestnet:
return _coin.stellar;

View file

@ -10,8 +10,6 @@
import 'dart:convert';
import 'package:bitcoindart/bitcoindart.dart';
import 'package:crypto/crypto.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/banano.dart';
@ -28,6 +26,7 @@ import 'package:stackwallet/wallets/crypto_currency/coins/monero.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/namecoin.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/nano.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/particl.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/solana.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/stellar.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/tezos.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/wownero.dart';
@ -38,34 +37,6 @@ class AddressUtils {
return '${address.substring(0, 5)}...${address.substring(address.length - 5)}';
}
/// attempts to convert a string to a valid scripthash
///
/// Returns the scripthash or throws an exception on invalid firo address
static String convertToScriptHash(
String address,
NetworkType network, [
String overridePrefix = "",
]) {
try {
final output =
Address.addressToOutputScript(address, network, overridePrefix);
final hash = sha256.convert(output.toList(growable: false)).toString();
final chars = hash.split("");
final reversedPairs = <String>[];
// TODO find a better/faster way to do this?
var i = chars.length - 1;
while (i > 0) {
reversedPairs.add(chars[i - 1]);
reversedPairs.add(chars[i]);
i -= 2;
}
return reversedPairs.join("");
} catch (e) {
rethrow;
}
}
static bool validateAddress(String address, Coin coin) {
//This calls the validate address for each crypto coin, validateAddress is
//only used in 2 places, so I just replaced the old functionality here
@ -97,6 +68,8 @@ class AddressUtils {
return Namecoin(CryptoCurrencyNetwork.main).validateAddress(address);
case Coin.particl:
return Particl(CryptoCurrencyNetwork.main).validateAddress(address);
case Coin.solana:
return Solana(CryptoCurrencyNetwork.main).validateAddress(address);
case Coin.stellar:
return Stellar(CryptoCurrencyNetwork.main).validateAddress(address);
case Coin.nano:

View file

@ -55,6 +55,7 @@ enum AmountUnit {
case Coin.stellar: // TODO: check if this is correct
case Coin.stellarTestnet:
case Coin.tezos:
case Coin.solana:
return AmountUnit.values.sublist(0, 4);
case Coin.monero:

View file

@ -10,7 +10,6 @@
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoindart/bitcoindart.dart';
import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
@ -19,25 +18,17 @@ abstract class Bip32Utils {
static bip32.BIP32 getBip32RootSync(
String mnemonic,
String mnemonicPassphrase,
NetworkType networkType,
bip32.NetworkType networkType,
) {
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: mnemonicPassphrase);
final _networkType = bip32.NetworkType(
wif: networkType.wif,
bip32: bip32.Bip32Type(
public: networkType.bip32.public,
private: networkType.bip32.private,
),
);
final root = bip32.BIP32.fromSeed(seed, _networkType);
final root = bip32.BIP32.fromSeed(seed, networkType);
return root;
}
static Future<bip32.BIP32> getBip32Root(
String mnemonic,
String mnemonicPassphrase,
NetworkType networkType,
bip32.NetworkType networkType,
) async {
final root = await compute(
_getBip32RootWrapper,
@ -52,7 +43,7 @@ abstract class Bip32Utils {
/// wrapper for compute()
static bip32.BIP32 _getBip32RootWrapper(
Tuple3<String, String, NetworkType> args,
Tuple3<String, String, bip32.NetworkType> args,
) {
return getBip32RootSync(
args.item1,
@ -97,7 +88,7 @@ abstract class Bip32Utils {
static bip32.BIP32 getBip32NodeSync(
String mnemonic,
String mnemonicPassphrase,
NetworkType network,
bip32.NetworkType network,
String derivePath,
) {
final root = getBip32RootSync(mnemonic, mnemonicPassphrase, network);
@ -109,7 +100,7 @@ abstract class Bip32Utils {
static Future<bip32.BIP32> getBip32Node(
String mnemonic,
String mnemonicPassphrase,
NetworkType networkType,
bip32.NetworkType networkType,
String derivePath,
) async {
final node = await compute(
@ -126,7 +117,7 @@ abstract class Bip32Utils {
/// wrapper for compute()
static bip32.BIP32 _getBip32NodeWrapper(
Tuple4<String, String, NetworkType, String> args,
Tuple4<String, String, bip32.NetworkType, String> args,
) {
return getBip32NodeSync(
args.item1,

View file

@ -66,6 +66,8 @@ Uri getDefaultBlockExplorerUrlFor({
return Uri.parse("https://testnet.stellarchain.io/transactions/$txid");
case Coin.tezos:
return Uri.parse("https://tzstats.com/$txid");
case Coin.solana:
return Uri.parse("https://explorer.solana.com/tx/$txid");
}
}

View file

@ -0,0 +1,63 @@
import 'dart:io';
import 'package:electrum_adapter/electrum_adapter.dart';
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
Future<bool> checkElectrumServer({
required String host,
required int port,
required bool useSSL,
Prefs? overridePrefs,
TorService? overrideTorService,
}) async {
final _prefs = overridePrefs ?? Prefs.instance;
final _torService = overrideTorService ?? TorService.sharedInstance;
({InternetAddress host, int port})? proxyInfo;
try {
// If we're supposed to use Tor...
if (_prefs.useTor) {
// But Tor isn't running...
if (_torService.status != TorConnectionStatus.connected) {
// And the killswitch isn't set...
if (!_prefs.torKillSwitch) {
// Then we'll just proceed and connect to ElectrumX through clearnet at the bottom of this function.
Logging.instance.log(
"Tor preference set but Tor is not enabled, killswitch not set, connecting to Electrum adapter through clearnet",
level: LogLevel.Warning,
);
} else {
// ... But if the killswitch is set, then we throw an exception.
throw Exception(
"Tor preference and killswitch set but Tor is not enabled, not connecting to Electrum adapter");
// TODO [prio=low]: Try to start Tor.
}
} else {
// Get the proxy info from the TorService.
proxyInfo = _torService.getProxyInfo();
}
}
final client = await ElectrumClient.connect(
host: host,
port: port,
useSSL: useSSL,
proxyInfo: proxyInfo,
).timeout(
const Duration(seconds: 5),
onTimeout: () => throw Exception(
"The checkElectrumServer connect() call timed out.",
),
);
await client.ping().timeout(const Duration(seconds: 5));
return true;
} catch (_) {
return false;
}
}

View file

@ -46,6 +46,7 @@ abstract class Constants {
10000000); // https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/assets#amount-precision
static final BigInt _satsPerCoin = BigInt.from(100000000);
static final BigInt _satsPerCoinTezos = BigInt.from(1000000);
static final BigInt _satsPerCoinSolana = BigInt.from(1000000000);
static const int _decimalPlaces = 8;
static const int _decimalPlacesNano = 30;
static const int _decimalPlacesBanano = 29;
@ -55,6 +56,7 @@ abstract class Constants {
static const int _decimalPlacesECash = 2;
static const int _decimalPlacesStellar = 7;
static const int _decimalPlacesTezos = 6;
static const int _decimalPlacesSolana = 9;
static const int notificationsMax = 0xFFFFFFFF;
static const Duration networkAliveTimerDuration = Duration(seconds: 10);
@ -109,6 +111,9 @@ abstract class Constants {
case Coin.tezos:
return _satsPerCoinTezos;
case Coin.solana:
return _satsPerCoinSolana;
}
}
@ -155,6 +160,9 @@ abstract class Constants {
case Coin.tezos:
return _decimalPlacesTezos;
case Coin.solana:
return _decimalPlacesSolana;
}
}
@ -176,6 +184,7 @@ abstract class Constants {
case Coin.ethereum:
case Coin.namecoin:
case Coin.particl:
case Coin.solana:
case Coin.nano:
case Coin.stellar:
case Coin.stellarTestnet:
@ -245,6 +254,7 @@ abstract class Constants {
case Coin.nano: // TODO: Verify this
case Coin.banano: // TODO: Verify this
case Coin.solana:
return 1;
case Coin.stellar:
@ -272,6 +282,7 @@ abstract class Constants {
case Coin.namecoin:
case Coin.particl:
case Coin.ethereum:
case Coin.solana:
return 12;
case Coin.wownero:

View file

@ -188,6 +188,18 @@ abstract class DefaultNodes {
isDown: false,
);
static NodeModel get solana => NodeModel(
host: "https://api.mainnet-beta.solana.com", // TODO: Change this to stack wallet one
port: 443,
name: DefaultNodes.defaultName,
id: DefaultNodes.buildId(Coin.solana),
useSSL: true,
enabled: true,
coinName: Coin.solana.name,
isFailover: true,
isDown: false,
);
static NodeModel get stellar => NodeModel(
host: "https://horizon.stellar.org",
port: 443,
@ -348,6 +360,9 @@ abstract class DefaultNodes {
case Coin.particl:
return particl;
case Coin.solana:
return solana;
case Coin.stellar:
return stellar;

View file

@ -26,6 +26,7 @@ enum Coin {
namecoin,
nano,
particl,
solana,
stellar,
tezos,
wownero,
@ -69,6 +70,8 @@ extension CoinExt on Coin {
return "Monero";
case Coin.particl:
return "Particl";
case Coin.solana:
return "Solana";
case Coin.stellar:
return "Stellar";
case Coin.tezos:
@ -121,6 +124,8 @@ extension CoinExt on Coin {
return "XMR";
case Coin.particl:
return "PART";
case Coin.solana:
return "SOL";
case Coin.stellar:
return "XLM";
case Coin.tezos:
@ -173,6 +178,8 @@ extension CoinExt on Coin {
return "monero";
case Coin.particl:
return "particl";
case Coin.solana:
return "solana";
case Coin.stellar:
return "stellar";
case Coin.tezos:
@ -229,6 +236,7 @@ extension CoinExt on Coin {
case Coin.nano:
case Coin.banano:
case Coin.tezos:
case Coin.solana:
return false;
}
}
@ -259,6 +267,7 @@ extension CoinExt on Coin {
case Coin.firoTestNet:
case Coin.nano:
case Coin.banano:
case Coin.solana:
case Coin.stellar:
case Coin.stellarTestnet:
return false;
@ -284,6 +293,7 @@ extension CoinExt on Coin {
case Coin.banano:
case Coin.eCash:
case Coin.stellar:
case Coin.solana:
return false;
case Coin.dogecoinTestNet:
@ -327,6 +337,7 @@ extension CoinExt on Coin {
case Coin.banano:
case Coin.eCash:
case Coin.stellar:
case Coin.solana:
return this;
case Coin.dogecoinTestNet:
@ -400,6 +411,9 @@ extension CoinExt on Coin {
case Coin.stellar:
case Coin.stellarTestnet:
return AddressType.stellar;
case Coin.solana:
return AddressType.solana;
}
}
}
@ -448,6 +462,10 @@ Coin coinFromPrettyName(String name) {
case "particl":
return Coin.particl;
case "Solana":
case "solana":
return Coin.solana;
case "Stellar":
case "stellar":
return Coin.stellar;
@ -548,6 +566,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
return Coin.namecoin;
case "part":
return Coin.particl;
case "sol":
return Coin.solana;
case "xlm":
return Coin.stellar;
case "xtz":

View file

@ -8,6 +8,7 @@
*
*/
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
enum DerivePathType {
@ -17,6 +18,32 @@ enum DerivePathType {
bip84,
eth,
eCash44,
solana,
bip86;
AddressType getAddressType() {
switch (this) {
case DerivePathType.bip44:
case DerivePathType.bch44:
case DerivePathType.eCash44:
return AddressType.p2pkh;
case DerivePathType.bip49:
return AddressType.p2sh;
case DerivePathType.bip84:
return AddressType.p2wpkh;
case DerivePathType.eth:
return AddressType.ethereum;
case DerivePathType.solana:
return AddressType.solana;
case DerivePathType.bip86:
return AddressType.p2tr;
}
}
}
extension DerivePathTypeExt on DerivePathType {
@ -44,6 +71,9 @@ extension DerivePathTypeExt on DerivePathType {
case Coin.ethereum: // TODO: do we need something here?
return DerivePathType.eth;
case Coin.solana:
return DerivePathType.solana;
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
case Coin.epicCash:

View file

@ -68,6 +68,7 @@ class Prefs extends ChangeNotifier {
await _setMaxDecimals();
_useTor = await _getUseTor();
_fusionServerInfo = await _getFusionServerInfo();
_solanaEnabled = await _getSolanaEnabled();
_frostEnabled = await _getFrostEnabled();
_initialized = true;
@ -1010,6 +1011,27 @@ class Prefs extends ChangeNotifier {
return actualMap;
}
// Solana
bool _solanaEnabled = false;
bool get solanaEnabled => _solanaEnabled;
set solanaEnabled(bool solanaEnabled) {
if (_solanaEnabled != solanaEnabled) {
DB.instance.put<dynamic>(
boxName: DB.boxNamePrefs, key: "solanaEnabled", value: solanaEnabled);
_solanaEnabled = solanaEnabled;
notifyListeners();
}
}
Future<bool> _getSolanaEnabled() async {
return await DB.instance.get<dynamic>(
boxName: DB.boxNamePrefs, key: "solanaEnabled") as bool? ??
false;
}
// FROST multisig
bool _frostEnabled = false;

View file

@ -317,6 +317,28 @@ class STextStyles {
}
}
static TextStyle w600_18(BuildContext context) {
switch (_theme(context).themeId) {
default:
return GoogleFonts.inter(
color: _theme(context).textDark,
fontWeight: FontWeight.w600,
fontSize: 18,
);
}
}
static TextStyle w500_16(BuildContext context) {
switch (_theme(context).themeId) {
default:
return GoogleFonts.inter(
color: _theme(context).textDark,
fontWeight: FontWeight.w500,
fontSize: 16,
);
}
}
static TextStyle w500_14(BuildContext context) {
switch (_theme(context).themeId) {
default:

View file

@ -45,4 +45,12 @@ class Banano extends NanoCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Banano && other.network == network;
}
@override
int get hashCode => Object.hash(Banano, network);
}

View file

@ -25,11 +25,15 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
// change this to change the number of confirms a tx needs in order to show as confirmed
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
List<DerivePathType> get supportedDerivationPathTypes => [
DerivePathType.bip44,
DerivePathType.bip49,
DerivePathType.bip84,
DerivePathType.bip86, // P2TR.
];
@override
@ -51,10 +55,10 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
);
@override
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0x80,
p2pkhPrefix: 0x00,
p2shPrefix: 0x05,
@ -62,9 +66,12 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
pubHDPrefix: 0x0488b21e,
bech32Hrp: "bc",
messagePrefix: '\x18Bitcoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
case CryptoCurrencyNetwork.test:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xef,
p2pkhPrefix: 0x6f,
p2shPrefix: 0xc4,
@ -72,6 +79,9 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
pubHDPrefix: 0x043587cf,
bech32Hrp: "tb",
messagePrefix: "\x18Bitcoin Signed Message:\n",
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
default:
throw Exception("Unsupported network: $network");
@ -109,6 +119,9 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
case DerivePathType.bip84:
purpose = 84;
break;
case DerivePathType.bip86:
purpose = 86;
break;
default:
throw Exception("DerivePathType $derivePathType not supported");
}
@ -130,13 +143,14 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
return (address: addr, addressType: AddressType.p2pkh);
// TODO: [prio=high] verify this works similarly to bitcoindart's p2sh or something(!!)
case DerivePathType.bip49:
final p2wpkhScript = coinlib.P2WPKHAddress.fromPublicKey(
publicKey,
hrp: networkParams.bech32Hrp,
).program.script;
final addr = coinlib.P2SHAddress.fromScript(
final addr = coinlib.P2SHAddress.fromRedeemScript(
p2wpkhScript,
version: networkParams.p2shPrefix,
);
@ -151,6 +165,16 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
return (address: addr, addressType: AddressType.p2wpkh);
case DerivePathType.bip86:
final taproot = coinlib.Taproot(internalKey: publicKey);
final addr = coinlib.P2TRAddress.fromTaproot(
taproot,
hrp: networkParams.bech32Hrp,
);
return (address: addr, addressType: AddressType.p2tr);
default:
throw Exception("DerivePathType $derivePathType not supported");
}
@ -179,4 +203,12 @@ class Bitcoin extends Bip39HDCurrency with PaynymCurrencyInterface {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Bitcoin && other.network == network;
}
@override
int get hashCode => Object.hash(Bitcoin, network);
}

View file

@ -23,6 +23,9 @@ class BitcoinFrost extends FrostCurrency {
@override
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
NodeModel get defaultNode {
switch (network) {
@ -69,4 +72,12 @@ class BitcoinFrost extends FrostCurrency {
// TODO: implement validateAddress for frost addresses
return true;
}
@override
bool operator ==(Object other) {
return other is BitcoinFrost && other.network == network;
}
@override
int get hashCode => Object.hash(BitcoinFrost, network);
}

View file

@ -34,6 +34,9 @@ class Bitcoincash extends Bip39HDCurrency {
// change this to change the number of confirms a tx needs in order to show as confirmed
int get minConfirms => 0; // bch zeroconf
@override
bool get torSupport => true;
@override
List<DerivePathType> get supportedDerivationPathTypes => [
DerivePathType.bip44,
@ -59,10 +62,10 @@ class Bitcoincash extends Bip39HDCurrency {
);
@override
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0x80,
p2pkhPrefix: 0x00,
p2shPrefix: 0x05,
@ -70,9 +73,12 @@ class Bitcoincash extends Bip39HDCurrency {
pubHDPrefix: 0x0488b21e,
bech32Hrp: "bc",
messagePrefix: '\x18Bitcoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
case CryptoCurrencyNetwork.test:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xef,
p2pkhPrefix: 0x6f,
p2shPrefix: 0xc4,
@ -80,6 +86,9 @@ class Bitcoincash extends Bip39HDCurrency {
pubHDPrefix: 0x043587cf,
bech32Hrp: "tb",
messagePrefix: "\x18Bitcoin Signed Message:\n",
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
default:
throw Exception("Unsupported network: $network");
@ -280,4 +289,12 @@ class Bitcoincash extends Bip39HDCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Bitcoincash && other.network == network;
}
@override
int get hashCode => Object.hash(Bitcoincash, network);
}

View file

@ -20,6 +20,9 @@ class Dogecoin extends Bip39HDCurrency {
}
}
@override
bool get torSupport => true;
@override
List<DerivePathType> get supportedDerivationPathTypes => [
DerivePathType.bip44,
@ -102,10 +105,10 @@ class Dogecoin extends Bip39HDCurrency {
int get minConfirms => 1;
@override
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0x9e,
p2pkhPrefix: 0x1e,
p2shPrefix: 0x16,
@ -113,9 +116,12 @@ class Dogecoin extends Bip39HDCurrency {
pubHDPrefix: 0x02facafd,
bech32Hrp: "doge",
messagePrefix: '\x18Dogecoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
case CryptoCurrencyNetwork.test:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xf1,
p2pkhPrefix: 0x71,
p2shPrefix: 0xc4,
@ -123,6 +129,9 @@ class Dogecoin extends Bip39HDCurrency {
pubHDPrefix: 0x043587cf,
bech32Hrp: "tdge",
messagePrefix: "\x18Dogecoin Signed Message:\n",
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
default:
throw Exception("Unsupported network: $network");
@ -172,4 +181,12 @@ class Dogecoin extends Bip39HDCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Dogecoin && other.network == network;
}
@override
int get hashCode => Object.hash(Dogecoin, network);
}

View file

@ -32,6 +32,9 @@ class Ecash extends Bip39HDCurrency {
// change this to change the number of confirms a tx needs in order to show as confirmed
int get minConfirms => 0; // bch zeroconf
@override
bool get torSupport => true;
@override
List<DerivePathType> get supportedDerivationPathTypes => [
DerivePathType.eCash44,
@ -57,10 +60,10 @@ class Ecash extends Bip39HDCurrency {
);
@override
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0x80,
p2pkhPrefix: 0x00,
p2shPrefix: 0x05,
@ -68,9 +71,12 @@ class Ecash extends Bip39HDCurrency {
pubHDPrefix: 0x0488b21e,
bech32Hrp: "bc",
messagePrefix: '\x18Bitcoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
case CryptoCurrencyNetwork.test:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xef,
p2pkhPrefix: 0x6f,
p2shPrefix: 0xc4,
@ -78,6 +84,9 @@ class Ecash extends Bip39HDCurrency {
pubHDPrefix: 0x043587cf,
bech32Hrp: "tb",
messagePrefix: "\x18Bitcoin Signed Message:\n",
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
default:
throw Exception("Unsupported network: $network");
@ -260,4 +269,12 @@ class Ecash extends Bip39HDCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Ecash && other.network == network;
}
@override
int get hashCode => Object.hash(Ecash, network);
}

View file

@ -61,4 +61,12 @@ class Epiccash extends Bip39Currency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Epiccash && other.network == network;
}
@override
int get hashCode => Object.hash(Epiccash, network);
}

View file

@ -34,4 +34,12 @@ class Ethereum extends Bip39Currency {
bool validateAddress(String address) {
return isValidEthereumAddress(address);
}
@override
bool operator ==(Object other) {
return other is Ethereum && other.network == network;
}
@override
int get hashCode => Object.hash(Ethereum, network);
}

View file

@ -24,6 +24,9 @@ class Firo extends Bip39HDCurrency {
@override
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
List<DerivePathType> get supportedDerivationPathTypes => [
DerivePathType.bip44,
@ -48,10 +51,10 @@ class Firo extends Bip39HDCurrency {
);
@override
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xd2,
p2pkhPrefix: 0x52,
p2shPrefix: 0x07,
@ -59,9 +62,12 @@ class Firo extends Bip39HDCurrency {
pubHDPrefix: 0x0488b21e,
bech32Hrp: "bc",
messagePrefix: '\x18Zcoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
case CryptoCurrencyNetwork.test:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xb9,
p2pkhPrefix: 0x41,
p2shPrefix: 0xb2,
@ -69,6 +75,9 @@ class Firo extends Bip39HDCurrency {
pubHDPrefix: 0x043587cf,
bech32Hrp: "tb",
messagePrefix: "\x18Zcoin Signed Message:\n",
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
default:
throw Exception("Unsupported network: $network");
@ -190,4 +199,12 @@ class Firo extends Bip39HDCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Firo && other.network == network;
}
@override
int get hashCode => Object.hash(Firo, network);
}

View file

@ -24,6 +24,9 @@ class Litecoin extends Bip39HDCurrency {
// change this to change the number of confirms a tx needs in order to show as confirmed
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
List<DerivePathType> get supportedDerivationPathTypes => [
DerivePathType.bip44,
@ -55,10 +58,10 @@ class Litecoin extends Bip39HDCurrency {
);
@override
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xb0,
p2pkhPrefix: 0x30,
p2shPrefix: 0x32,
@ -66,9 +69,12 @@ class Litecoin extends Bip39HDCurrency {
pubHDPrefix: 0x0488b21e,
bech32Hrp: "ltc",
messagePrefix: '\x19Litecoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
case CryptoCurrencyNetwork.test:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xef,
p2pkhPrefix: 0x6f,
p2shPrefix: 0x3a,
@ -76,6 +82,9 @@ class Litecoin extends Bip39HDCurrency {
pubHDPrefix: 0x043587cf,
bech32Hrp: "tltc",
messagePrefix: "\x19Litecoin Signed Message:\n",
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
default:
throw Exception("Unsupported network: $network");
@ -140,7 +149,7 @@ class Litecoin extends Bip39HDCurrency {
hrp: networkParams.bech32Hrp,
).program.script;
final addr = coinlib.P2SHAddress.fromScript(
final addr = coinlib.P2SHAddress.fromRedeemScript(
p2wpkhScript,
version: networkParams.p2shPrefix,
);
@ -203,4 +212,12 @@ class Litecoin extends Bip39HDCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Litecoin && other.network == network;
}
@override
int get hashCode => Object.hash(Litecoin, network);
}

View file

@ -44,4 +44,12 @@ class Monero extends CryptonoteCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Monero && other.network == network;
}
@override
int get hashCode => Object.hash(Monero, network);
}

View file

@ -22,6 +22,9 @@ class Namecoin extends Bip39HDCurrency {
// See https://github.com/cypherstack/stack_wallet/blob/621aff47969761014e0a6c4e699cb637d5687ab3/lib/services/coins/namecoin/namecoin_wallet.dart#L58
int get minConfirms => 2;
@override
bool get torSupport => true;
@override
// See https://github.com/cypherstack/stack_wallet/blob/621aff47969761014e0a6c4e699cb637d5687ab3/lib/services/coins/namecoin/namecoin_wallet.dart#L80
String constructDerivePath({
@ -121,7 +124,7 @@ class Namecoin extends Bip39HDCurrency {
hrp: networkParams.bech32Hrp,
).program.script;
final addr = coinlib.P2SHAddress.fromScript(
final addr = coinlib.P2SHAddress.fromRedeemScript(
p2wpkhScript,
version: networkParams.p2shPrefix,
);
@ -143,10 +146,10 @@ class Namecoin extends Bip39HDCurrency {
@override
// See https://github.com/cypherstack/stack_wallet/blob/621aff47969761014e0a6c4e699cb637d5687ab3/lib/services/coins/namecoin/namecoin_wallet.dart#L3474
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0xb4, // From 180.
p2pkhPrefix: 0x34, // From 52.
p2shPrefix: 0x0d, // From 13.
@ -154,6 +157,9 @@ class Namecoin extends Bip39HDCurrency {
pubHDPrefix: 0x0488b21e,
bech32Hrp: "nc",
messagePrefix: '\x18Namecoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
// case CryptoCurrencyNetwork.test:
// TODO: [prio=low] Add testnet support.
@ -179,4 +185,12 @@ class Namecoin extends Bip39HDCurrency {
return false;
}
}
@override
bool operator ==(Object other) {
return other is Namecoin && other.network == network;
}
@override
int get hashCode => Object.hash(Namecoin, network);
}

View file

@ -45,4 +45,12 @@ class Nano extends NanoCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Nano && other.network == network;
}
@override
int get hashCode => Object.hash(Nano, network);
}

View file

@ -22,6 +22,9 @@ class Particl extends Bip39HDCurrency {
// See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L57
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
// See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L68
String constructDerivePath(
@ -125,10 +128,10 @@ class Particl extends Bip39HDCurrency {
@override
// See https://github.com/cypherstack/stack_wallet/blob/d08b5c9b22b58db800ad07b2ceeb44c6d05f9cf3/lib/services/coins/particl/particl_wallet.dart#L3532
coinlib.NetworkParams get networkParams {
coinlib.Network get networkParams {
switch (network) {
case CryptoCurrencyNetwork.main:
return const coinlib.NetworkParams(
return coinlib.Network(
wifPrefix: 0x6c,
p2pkhPrefix: 0x38,
p2shPrefix: 0x3c,
@ -136,6 +139,9 @@ class Particl extends Bip39HDCurrency {
pubHDPrefix: 0x696e82d1,
bech32Hrp: "pw",
messagePrefix: '\x18Bitcoin Signed Message:\n',
minFee: BigInt.from(1), // TODO [prio=high].
minOutput: dustLimit.raw, // TODO.
feePerKb: BigInt.from(1), // TODO.
);
// case CryptoCurrencyNetwork.test:
// TODO: [prio=low] Add testnet.
@ -159,4 +165,12 @@ class Particl extends Bip39HDCurrency {
return false;
}
}
@override
bool operator ==(Object other) {
return other is Particl && other.network == network;
}
@override
int get hashCode => Object.hash(Particl, network);
}

View file

@ -0,0 +1,61 @@
import 'package:solana/solana.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_currency.dart';
class Solana extends Bip39Currency {
Solana(super.network) {
switch (network) {
case CryptoCurrencyNetwork.main:
coin = Coin.solana;
default:
throw Exception("Unsupported network: $network");
}
}
@override
NodeModel get defaultNode {
switch (network) {
case CryptoCurrencyNetwork.main:
return NodeModel(
host:
"https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one
port: 443,
name: DefaultNodes.defaultName,
id: DefaultNodes.buildId(Coin.solana),
useSSL: true,
enabled: true,
coinName: Coin.solana.name,
isFailover: true,
isDown: false,
);
default:
throw Exception("Unsupported network: $network");
}
}
@override
int get minConfirms => 21;
@override
bool get torSupport => true;
@override
bool validateAddress(String address) {
return isPointOnEd25519Curve(
Ed25519HDPublicKey.fromBase58(address).toByteArray());
}
@override
String get genesisHash => throw UnimplementedError();
@override
bool operator ==(Object other) {
return other is Solana && other.network == network;
}
@override
int get hashCode => Object.hash(Solana, network);
}

View file

@ -19,6 +19,9 @@ class Stellar extends Bip39Currency {
@override
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
String get genesisHash => throw UnimplementedError(
"Not used for stellar",
@ -39,4 +42,12 @@ class Stellar extends Bip39Currency {
@override
bool validateAddress(String address) =>
RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address);
@override
bool operator ==(Object other) {
return other is Stellar && other.network == network;
}
@override
int get hashCode => Object.hash(Stellar, network);
}

View file

@ -70,6 +70,9 @@ class Tezos extends Bip39Currency {
@override
int get minConfirms => 1;
@override
bool get torSupport => true;
@override
bool validateAddress(String address) {
return RegExp(r"^tz[1-9A-HJ-NP-Za-km-z]{34}$").hasMatch(address);
@ -146,4 +149,12 @@ class Tezos extends Bip39Currency {
}
// ===========================================================================
@override
bool operator ==(Object other) {
return other is Tezos && other.network == network;
}
@override
int get hashCode => Object.hash(Tezos, network);
}

View file

@ -44,4 +44,12 @@ class Wownero extends CryptonoteCurrency {
throw UnimplementedError();
}
}
@override
bool operator ==(Object other) {
return other is Wownero && other.network == network;
}
@override
int get hashCode => Object.hash(Wownero, network);
}

View file

@ -20,6 +20,9 @@ abstract class CryptoCurrency {
// (used for eth currently)
bool get hasTokenSupport => false;
// Override in subclass if the currency has Tor support:
bool get torSupport => false;
// TODO: [prio=low] require these be overridden in concrete implementations to remove reliance on [coin]
int get fractionDigits => coin.decimals;
BigInt get satsPerCoin => Constants.satsPerCoin(coin);

View file

@ -1,5 +1,3 @@
import 'package:bech32/bech32.dart';
import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
@ -11,7 +9,7 @@ import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_currency.
abstract class Bip39HDCurrency extends Bip39Currency {
Bip39HDCurrency(super.network);
coinlib.NetworkParams get networkParams;
coinlib.Network get networkParams;
Amount get dustLimit;
@ -57,37 +55,19 @@ abstract class Bip39HDCurrency extends Bip39Currency {
}
DerivePathType addressType({required String address}) {
Uint8List? decodeBase58;
Segwit? decodeBech32;
try {
decodeBase58 = bs58check.decode(address);
} catch (err) {
// Base58check decode fail
}
if (decodeBase58 != null) {
if (decodeBase58[0] == networkParams.p2pkhPrefix) {
// P2PKH
return DerivePathType.bip44;
}
if (decodeBase58[0] == networkParams.p2shPrefix) {
// P2SH
return DerivePathType.bip49;
}
throw ArgumentError('Invalid version or Network mismatch');
} else {
try {
decodeBech32 = segwit.decode(address, networkParams.bech32Hrp);
} catch (err) {
// Bech32 decode fail
}
if (networkParams.bech32Hrp != decodeBech32!.hrp) {
throw ArgumentError('Invalid prefix or Network mismatch');
}
if (decodeBech32.version != 0) {
throw ArgumentError('Invalid address version');
}
// P2WPKH
final address2 = coinlib.Address.fromString(address, networkParams);
if (address2 is coinlib.P2PKHAddress) {
return DerivePathType.bip44;
} else if (address2 is coinlib.P2SHAddress) {
return DerivePathType.bip49;
} else if (address2 is coinlib.P2WPKHAddress) {
return DerivePathType.bip84;
} else if (address2 is coinlib.P2TRAddress) {
return DerivePathType.bip86;
} else {
// TODO: [prio=med] better error handling
throw ArgumentError('Invalid address');
}
}
}

View file

@ -266,6 +266,8 @@ const _WalletInfomainAddressTypeEnumValueMap = {
'stellar': 11,
'tezos': 12,
'frostMS': 13,
'p2tr': 14,
'solana': 15,
};
const _WalletInfomainAddressTypeValueEnumMap = {
0: AddressType.p2pkh,
@ -282,6 +284,8 @@ const _WalletInfomainAddressTypeValueEnumMap = {
11: AddressType.stellar,
12: AddressType.tezos,
13: AddressType.frostMS,
14: AddressType.p2tr,
15: AddressType.solana,
};
Id _walletInfoGetId(WalletInfo object) {

View file

@ -1,8 +1,6 @@
import 'dart:async';
import 'dart:ffi';
import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
import 'package:electrum_adapter/electrum_adapter.dart';
import 'package:flutter/foundation.dart';
import 'package:frostdart/frostdart.dart' as frost;
import 'package:frostdart/frostdart_bindings_generated.dart';
@ -20,19 +18,15 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/frost.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/private_key_currency.dart';
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stream_channel/stream_channel.dart';
class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
BitcoinFrostWallet(CryptoCurrencyNetwork network)
@ -44,8 +38,6 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
.findFirstSync()!;
late ElectrumXClient electrumXClient;
late StreamChannel electrumAdapterChannel;
late ElectrumClient electrumAdapterClient;
late CachedElectrumXClient electrumXCachedClient;
Future<void> initializeNewFrost({
@ -1102,66 +1094,29 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
final newNode = await _getCurrentElectrumXNode();
try {
await electrumXClient.electrumAdapterClient?.close();
} catch (e, s) {
await electrumXClient.closeAdapter();
} catch (e) {
if (e.toString().contains("initialized")) {
// Ignore. This should happen every first time the wallet is opened.
} else {
Logging.instance
.log("Error closing electrumXClient: $e", level: LogLevel.Error);
Logging.instance.log(
"Error closing electrumXClient: $e",
level: LogLevel.Error,
);
}
}
electrumXClient = ElectrumXClient.from(
node: newNode,
prefs: prefs,
failovers: failovers,
coin: cryptoCurrency.coin,
cryptoCurrency: cryptoCurrency,
);
electrumAdapterChannel = await electrum_adapter.connect(
newNode.address,
port: newNode.port,
acceptUnverified: true,
useSSL: newNode.useSSL,
proxyInfo: Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null,
);
if (electrumXClient.coin == Coin.firo ||
electrumXClient.coin == Coin.firoTestNet) {
electrumAdapterClient = FiroElectrumClient(
electrumAdapterChannel,
newNode.address,
newNode.port,
newNode.useSSL,
Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null);
} else {
electrumAdapterClient = ElectrumClient(
electrumAdapterChannel,
newNode.address,
newNode.port,
newNode.useSSL,
Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null);
}
electrumXCachedClient = CachedElectrumXClient.from(
electrumXClient: electrumXClient,
electrumAdapterClient: electrumAdapterClient,
electrumAdapterUpdateCallback: updateClient,
);
}
// TODO [prio=low]: Use ElectrumXInterface method.
Future<ElectrumClient> updateClient() async {
Logging.instance.log(
"Updating electrum node and ElectrumAdapterClient from Frost wallet.",
level: LogLevel.Info);
await updateNode();
return electrumAdapterClient;
}
bool _duplicateTxCheck(
List<Map<String, dynamic>> allTransactions, String txid) {
for (int i = 0; i < allTransactions.length; i++) {

View file

@ -36,7 +36,7 @@ import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart';
import 'package:websocket_universal/websocket_universal.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
//
// refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart
@ -103,15 +103,16 @@ class EpiccashWallet extends Bip39Wallet {
}
Future<EpicBoxConfigModel> getEpicBoxConfig() async {
EpicBoxConfigModel? _epicBoxConfig =
EpicBoxConfigModel.fromServer(DefaultEpicBoxes.defaultEpicBoxServer);
EpicBoxConfigModel? _epicBoxConfig = EpicBoxConfigModel.fromServer(
DefaultEpicBoxes.defaultEpicBoxServer,
);
//Get the default Epicbox server and check if it's conected
// bool isEpicboxConnected = await _testEpicboxServer(
// DefaultEpicBoxes.defaultEpicBoxServer.host, DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443);
// if (isEpicboxConnected) {
//Use default server for as Epicbox config
//Use default server for as Epicbox config
// }
// else {
@ -231,48 +232,33 @@ class EpiccashWallet extends Bip39Wallet {
);
}
Future<bool> _testEpicboxServer(String host, int port) async {
// TODO use an EpicBoxServerModel as the only param
final websocketConnectionUri = 'wss://$host:$port';
const connectionOptions = SocketConnectionOptions(
pingIntervalMs: 3000,
timeoutConnectionMs: 4000,
Future<bool> _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async {
final host = epicboxConfig.host;
final port = epicboxConfig.port ?? 443;
WebSocketChannel? channel;
try {
final uri = Uri.parse('wss://$host:$port');
/// see ping/pong messages in [logEventStream] stream
skipPingMessages: true,
channel = WebSocketChannel.connect(
uri,
);
/// Set this attribute to `true` if do not need any ping/pong
/// messages and ping measurement. Default is `false`
pingRestrictionForce: true,
);
await channel.ready;
final IMessageProcessor<String, String> textSocketProcessor =
SocketSimpleTextProcessor();
final textSocketHandler = IWebSocketHandler<String, String>.createClient(
websocketConnectionUri,
textSocketProcessor,
connectionOptions: connectionOptions,
);
final response = await channel.stream.first.timeout(
const Duration(seconds: 2),
);
// Listening to server responses:
bool isConnected = true;
textSocketHandler.incomingMessagesStream.listen((inMsg) {
return response is String && response.contains("Challenge");
} catch (_) {
Logging.instance.log(
'> webSocket got text message from server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]',
level: LogLevel.Info);
});
// Connecting to server:
final isTextSocketConnected = await textSocketHandler.connect();
if (!isTextSocketConnected) {
// ignore: avoid_print
Logging.instance.log(
'Connection to [$websocketConnectionUri] failed for some reason!',
level: LogLevel.Error);
isConnected = false;
"_testEpicBoxConnection failed on \"$host:$port\"",
level: LogLevel.Info,
);
return false;
} finally {
await channel?.sink.close();
}
return isConnected;
}
Future<bool> _putSendToAddresses(
@ -344,9 +330,9 @@ class EpiccashWallet extends Bip39Wallet {
return address;
}
Future<Address> thisWalletAddress(int index, EpicBoxConfigModel epicboxConfig) async {
final wallet =
await secureStorageInterface.read(key: '${walletId}_wallet');
Future<Address> thisWalletAddress(
int index, EpicBoxConfigModel epicboxConfig) async {
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
// EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
final walletAddress = await epiccash.LibEpiccash.getAddressInfo(
@ -602,7 +588,8 @@ class EpiccashWallet extends Bip39Wallet {
if (!receiverAddress.startsWith("http://") ||
!receiverAddress.startsWith("https://")) {
bool isEpicboxConnected = await _testEpicboxServer(
epicboxConfig.host, epicboxConfig.port ?? 443);
epicboxConfig,
);
if (!isEpicboxConnected) {
throw Exception("Failed to send TX : Unable to reach epicbox server");
}
@ -942,7 +929,6 @@ class EpiccashWallet extends Bip39Wallet {
.findAll();
final myAddressesSet = myAddresses.toSet();
final transactions = await epiccash.LibEpiccash.getTransactions(
wallet: wallet!,
refreshFromNode: refreshFromNode,

View file

@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:bitcoindart/bitcoindart.dart' as bitcoindart;
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
@ -7,6 +9,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/signing_data.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/extensions/impl/uint8_list.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/particl.dart';
@ -340,20 +343,92 @@ class ParticlWallet extends Bip39HDWallet
Logging.instance.log("Starting Particl buildTransaction ----------",
level: LogLevel.Info);
// TODO: use coinlib
// TODO: use coinlib (For this we need coinlib to support particl)
final convertedNetwork = bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
);
final List<({Uint8List? output, Uint8List? redeem})> extraData = [];
for (int i = 0; i < utxoSigningData.length; i++) {
final sd = utxoSigningData[i];
final pubKey = sd.keyPair!.publicKey.data;
final bitcoindart.PaymentData? data;
Uint8List? redeem, output;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = bitcoindart
.P2PKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip49:
final p2wpkh = bitcoindart
.P2WPKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
redeem = p2wpkh.output;
data = bitcoindart
.P2SH(
data: bitcoindart.PaymentData(redeem: p2wpkh),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip84:
// input = coinlib.P2WPKHInput(
// prevOut: coinlib.OutPoint.fromHex(sd.utxo.txid, sd.utxo.vout),
// publicKey: keys.publicKey,
// );
data = bitcoindart
.P2WPKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip86:
data = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
// sd.output = input.script!.compiled;
if (sd.derivePathType != DerivePathType.bip86) {
output = data!.output!;
}
extraData.add((output: output, redeem: redeem));
}
final txb = bitcoindart.TransactionBuilder(
network: bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
),
network: convertedNetwork,
);
const version = 160; // buildTransaction overridden for Particl to set this.
// TODO: [prio=low] refactor overridden buildTransaction to use eg. cryptocurrency.networkParams.txVersion.
@ -370,7 +445,7 @@ class ParticlWallet extends Bip39HDWallet
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
extraData[i].output!,
cryptoCurrency.networkParams.bech32Hrp,
);
@ -427,9 +502,13 @@ class ParticlWallet extends Bip39HDWallet
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[i].keyPair!,
keyPair: bitcoindart.ECPair.fromPrivateKey(
utxoSigningData[i].keyPair!.privateKey.data,
network: convertedNetwork,
compressed: utxoSigningData[i].keyPair!.privateKey.compressed,
),
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
redeemScript: extraData[i].redeem,
overridePrefix: cryptoCurrency.networkParams.bech32Hrp,
);
}

View file

@ -0,0 +1,478 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:decimal/decimal.dart';
import 'package:isar/isar.dart';
import 'package:socks5_proxy/socks_client.dart';
import 'package:solana/dto.dart';
import 'package:solana/solana.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
as isar;
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/solana.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
import 'package:tuple/tuple.dart';
class SolanaWallet extends Bip39Wallet<Solana> {
SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network));
static const String _addressDerivationPath = "m/44'/501'/0'/0'";
NodeModel? _solNode;
RpcClient? _rpcClient; // The Solana RpcClient.
Future<Ed25519HDKeyPair> _getKeyPair() async {
return Ed25519HDKeyPair.fromMnemonic(
await getMnemonic(),
account: 0,
change: 0,
);
}
Future<Address> _generateAddress() async {
final addressStruct = Address(
walletId: walletId,
value: (await _getKeyPair()).address,
publicKey: List<int>.empty(),
derivationIndex: 0,
derivationPath: DerivationPath()..value = _addressDerivationPath,
type: cryptoCurrency.coin.primaryAddressType,
subType: AddressSubType.receiving,
);
return addressStruct;
}
Future<int> _getCurrentBalanceInLamports() async {
_checkClient();
final balance = await _rpcClient?.getBalance((await _getKeyPair()).address);
return balance!.value;
}
Future<int?> _getEstimatedNetworkFee(Amount transferAmount) async {
final latestBlockhash = await _rpcClient?.getLatestBlockhash();
final pubKey = (await _getKeyPair()).publicKey;
final compiledMessage = Message(instructions: [
SystemInstruction.transfer(
fundingAccount: pubKey,
recipientAccount: pubKey,
lamports: transferAmount.raw.toInt(),
)
]).compile(
recentBlockhash: latestBlockhash!.value.blockhash,
feePayer: pubKey,
);
return await _rpcClient?.getFeeForMessage(
base64Encode(compiledMessage.toByteArray().toList()),
);
}
@override
FilterOperation? get changeAddressFilterOperation =>
throw UnimplementedError();
@override
Future<void> checkSaveInitialReceivingAddress() async {
try {
Address? address = await getCurrentReceivingAddress();
if (address == null) {
address = await _generateAddress();
await mainDB.updateOrPutAddresses([address]);
}
} catch (e, s) {
Logging.instance.log(
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<TxData> prepareSend({required TxData txData}) async {
try {
_checkClient();
if (txData.recipients == null || txData.recipients!.length != 1) {
throw Exception("$runtimeType prepareSend requires 1 recipient");
}
final Amount sendAmount = txData.amount!;
if (sendAmount > info.cachedBalance.spendable) {
throw Exception("Insufficient available balance");
}
final feeAmount = await _getEstimatedNetworkFee(sendAmount);
if (feeAmount == null) {
throw Exception(
"Failed to get fees, please check your node connection.");
}
final address = await getCurrentReceivingAddress();
// Rent exemption of Solana
final accInfo = await _rpcClient?.getAccountInfo(address!.value);
final int minimumRent =
await _rpcClient?.getMinimumBalanceForRentExemption(
accInfo!.value!.data.toString().length) ??
0; // TODO revisit null condition.
if (minimumRent >
((await _getCurrentBalanceInLamports()) -
txData.amount!.raw.toInt() -
feeAmount)) {
throw Exception(
"Insufficient remaining balance for rent exemption, minimum rent: "
"${minimumRent / pow(10, cryptoCurrency.fractionDigits)}",
);
}
return txData.copyWith(
fee: Amount(
rawValue: BigInt.from(feeAmount),
fractionDigits: cryptoCurrency.fractionDigits,
),
);
} catch (e, s) {
Logging.instance.log(
"$runtimeType Solana prepareSend failed: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
@override
Future<TxData> confirmSend({required TxData txData}) async {
try {
_checkClient();
final keyPair = await _getKeyPair();
final recipientAccount = txData.recipients!.first;
final recipientPubKey =
Ed25519HDPublicKey.fromBase58(recipientAccount.address);
final message = Message(
instructions: [
SystemInstruction.transfer(
fundingAccount: keyPair.publicKey,
recipientAccount: recipientPubKey,
lamports: txData.amount!.raw.toInt()),
ComputeBudgetInstruction.setComputeUnitPrice(
microLamports: txData.fee!.raw.toInt() - 5000),
// 5000 lamports is the base fee for a transaction. This instruction adds the necessary fee on top of base fee if it is needed.
ComputeBudgetInstruction.setComputeUnitLimit(units: 1000000),
// 1000000 is the multiplication number to turn the compute unit price of microLamports to lamports.
// These instructions also help the user to not pay more than the shown fee.
// See: https://solanacookbook.com/references/basic-transactions.html#how-to-change-compute-budget-fee-priority-for-a-transaction
],
);
final txid = await _rpcClient?.signAndSendTransaction(message, [keyPair]);
return txData.copyWith(
txid: txid,
);
} catch (e, s) {
Logging.instance.log(
"$runtimeType Solana confirmSend failed: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
_checkClient();
if (info.cachedBalance.spendable.raw == BigInt.zero) {
return Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
}
final fee = await _getEstimatedNetworkFee(amount);
if (fee == null) {
throw Exception("Failed to get fees, please check your node connection.");
}
return Amount(
rawValue: BigInt.from(fee),
fractionDigits: cryptoCurrency.fractionDigits,
);
}
@override
Future<FeeObject> get fees async {
_checkClient();
final fee = await _getEstimatedNetworkFee(
Amount.fromDecimal(
Decimal.one, // 1 SOL
fractionDigits: cryptoCurrency.fractionDigits,
),
);
if (fee == null) {
throw Exception("Failed to get fees, please check your node connection.");
}
return FeeObject(
numberOfBlocksFast: 1,
numberOfBlocksAverage: 1,
numberOfBlocksSlow: 1,
fast: fee,
medium: fee,
slow: fee);
}
@override
Future<bool> pingCheck() {
try {
_checkClient();
_rpcClient?.getHealth();
return Future.value(true);
} catch (e, s) {
Logging.instance.log(
"$runtimeType Solana pingCheck failed: $e\n$s",
level: LogLevel.Error,
);
return Future.value(false);
}
}
@override
FilterOperation? get receivingAddressFilterOperation =>
FilterGroup.and(standardReceivingAddressFilters);
@override
Future<void> recover({required bool isRescan}) async {
await refreshMutex.protect(() async {
final addressStruct = await _generateAddress();
await mainDB.updateOrPutAddresses([addressStruct]);
if (info.cachedReceivingAddress != addressStruct.value) {
await info.updateReceivingAddress(
newAddress: addressStruct.value,
isar: mainDB.isar,
);
}
await Future.wait([
updateBalance(),
updateChainHeight(),
updateTransactions(),
]);
});
}
@override
Future<void> updateBalance() async {
try {
final address = await getCurrentReceivingAddress();
_checkClient();
final balance = await _rpcClient?.getBalance(address!.value);
// Rent exemption of Solana
final accInfo = await _rpcClient?.getAccountInfo(address!.value);
// TODO [prio=low]: handle null account info.
final int minimumRent =
await _rpcClient?.getMinimumBalanceForRentExemption(
accInfo!.value!.data.toString().length) ??
0;
// TODO [prio=low]: revisit null condition.
final spendableBalance = balance!.value - minimumRent;
final newBalance = Balance(
total: Amount(
rawValue: BigInt.from(balance.value),
fractionDigits: Coin.solana.decimals,
),
spendable: Amount(
rawValue: BigInt.from(spendableBalance),
fractionDigits: Coin.solana.decimals,
),
blockedTotal: Amount(
rawValue: BigInt.from(minimumRent),
fractionDigits: Coin.solana.decimals,
),
pendingSpendable: Amount(
rawValue: BigInt.zero,
fractionDigits: Coin.solana.decimals,
),
);
await info.updateBalance(newBalance: newBalance, isar: mainDB.isar);
} catch (e, s) {
Logging.instance.log(
"Error getting balance in solana_wallet.dart: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<void> updateChainHeight() async {
try {
_checkClient();
final int blockHeight = await _rpcClient?.getSlot() ?? 0;
// TODO [prio=low]: Revisit null condition.
await info.updateCachedChainHeight(
newHeight: blockHeight,
isar: mainDB.isar,
);
} catch (e, s) {
Logging.instance.log(
"Error occurred in solana_wallet.dart while getting"
" chain height for solana: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<void> updateNode() async {
_solNode = getCurrentNode();
await refresh();
}
@override
NodeModel getCurrentNode() {
return _solNode ??
NodeService(secureStorageInterface: secureStorageInterface)
.getPrimaryNodeFor(coin: info.coin) ??
DefaultNodes.getNodeFor(info.coin);
}
@override
Future<void> updateTransactions() async {
try {
_checkClient();
final transactionsList = await _rpcClient?.getTransactionsList(
(await _getKeyPair()).publicKey,
encoding: Encoding.jsonParsed);
final txsList =
List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
final myAddress = (await getCurrentReceivingAddress())!;
// TODO [prio=low]: Revisit null assertion below.
for (final tx in transactionsList!) {
final senderAddress =
(tx.transaction as ParsedTransaction).message.accountKeys[0].pubkey;
var receiverAddress =
(tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey;
var txType = isar.TransactionType.unknown;
final txAmount = Amount(
rawValue:
BigInt.from(tx.meta!.postBalances[1] - tx.meta!.preBalances[1]),
fractionDigits: cryptoCurrency.fractionDigits,
);
if ((senderAddress == myAddress.value) &&
(receiverAddress == "11111111111111111111111111111111")) {
// The account that is only 1's are System Program accounts which
// means there is no receiver except the sender,
// see: https://explorer.solana.com/address/11111111111111111111111111111111
txType = isar.TransactionType.sentToSelf;
receiverAddress = senderAddress;
} else if (senderAddress == myAddress.value) {
txType = isar.TransactionType.outgoing;
} else if (receiverAddress == myAddress.value) {
txType = isar.TransactionType.incoming;
}
final transaction = isar.Transaction(
walletId: walletId,
txid: (tx.transaction as ParsedTransaction).signatures[0],
timestamp: tx.blockTime!,
type: txType,
subType: isar.TransactionSubType.none,
amount: tx.meta!.postBalances[1] - tx.meta!.preBalances[1],
amountString: txAmount.toJsonString(),
fee: tx.meta!.fee,
height: tx.slot,
isCancelled: false,
isLelantus: false,
slateId: null,
otherData: null,
inputs: [],
outputs: [],
nonce: null,
numberOfMessages: 0,
);
final txAddress = Address(
walletId: walletId,
value: receiverAddress,
publicKey: List<int>.empty(),
derivationIndex: 0,
derivationPath: DerivationPath()..value = _addressDerivationPath,
type: AddressType.solana,
subType: txType == isar.TransactionType.outgoing
? AddressSubType.unknown
: AddressSubType.receiving,
);
txsList.add(Tuple2(transaction, txAddress));
}
await mainDB.addNewTransactionData(txsList, walletId);
} catch (e, s) {
Logging.instance.log(
"Error occurred in solana_wallet.dart while getting"
" transactions for solana: $e\n$s",
level: LogLevel.Error,
);
}
}
@override
Future<bool> updateUTXOs() {
// No UTXOs in Solana
return Future.value(false);
}
/// Make sure the Solana RpcClient uses Tor if it's enabled.
///
void _checkClient() async {
HttpClient? httpClient;
if (prefs.useTor) {
// Make proxied HttpClient.
final ({InternetAddress host, int port}) proxyInfo =
TorService.sharedInstance.getProxyInfo();
final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port);
httpClient = HttpClient();
SocksTCPClient.assignToHttpClient(httpClient, [proxySettings]);
}
_rpcClient = RpcClient(
"${getCurrentNode().host}:${getCurrentNode().port}",
timeout: const Duration(seconds: 30),
customHeaders: {},
httpClient: httpClient,
);
return;
}
}

View file

@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:isar/isar.dart';
import 'package:socks5_proxy/socks.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
@ -9,6 +11,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart'
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -43,6 +46,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
// ============== Private ====================================================
stellar.StellarSDK? _stellarSdk;
HttpClient? _httpClient;
Future<int> _getBaseFee() async {
final fees = await stellarSdk.feeStats.execute();
@ -51,7 +55,21 @@ class StellarWallet extends Bip39Wallet<Stellar> {
void _updateSdk() {
final currentNode = getCurrentNode();
_stellarSdk = stellar.StellarSDK("${currentNode.host}:${currentNode.port}");
// TODO [prio=med]: refactor out and call before requests in case Tor is enabled/disabled, listen to prefs change, or similar.
if (prefs.useTor) {
final ({InternetAddress host, int port}) proxyInfo =
TorService.sharedInstance.getProxyInfo();
_httpClient = HttpClient();
SocksTCPClient.assignToHttpClient(
_httpClient!, [ProxySettings(proxyInfo.host, proxyInfo.port)]);
} else {
_httpClient = null;
}
_stellarSdk = stellar.StellarSDK("${currentNode.host}:${currentNode.port}",
httpClient: _httpClient);
}
Future<bool> _accountExists(String accountId) async {

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
@ -5,6 +7,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -105,9 +108,12 @@ class TezosWallet extends Bip39Wallet<Tezos> {
// print("COUNTER: $counter");
// print("customFee: $customFee");
// }
final tezartClient = tezart.TezartClient(
server,
);
({InternetAddress host, int port})? proxyInfo =
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null;
final tezartClient = tezart.TezartClient(server,
proxy: proxyInfo != null
? "socks5://${proxyInfo.host}:${proxyInfo.port};"
: null);
final opList = await tezartClient.transferOperation(
source: sourceKeyStore,

View file

@ -11,7 +11,13 @@ import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address
abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
with MultiAddressInterface<T> {
Bip39HDWallet(T cryptoCurrency) : super(cryptoCurrency);
Bip39HDWallet(super.cryptoCurrency);
Set<AddressType> get supportedAddressTypes =>
cryptoCurrency.supportedDerivationPathTypes
.where((e) => e != DerivePathType.bip49)
.map((e) => e.getAddressType())
.toSet();
Future<coinlib.HDPrivateKey> getRootHDNode() async {
final seed = bip39.mnemonicToSeed(
@ -21,6 +27,33 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
return coinlib.HDPrivateKey.fromSeed(seed);
}
Future<Address> generateNextReceivingAddress({
required DerivePathType derivePathType,
}) async {
if (!cryptoCurrency.supportedDerivationPathTypes.contains(derivePathType)) {
throw Exception(
"Unsupported DerivePathType passed to generateNextReceivingAddress().",
);
}
final current = await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.typeEqualTo(derivePathType.getAddressType())
.sortByDerivationIndexDesc()
.findFirst();
final index = current == null ? 0 : current.derivationIndex + 1;
const chain = 0; // receiving address
final address = await _generateAddress(
chain: chain,
index: index,
derivePathType: derivePathType,
);
return address;
}
/// Generates a receiving address. If none
/// are in the current wallet db it will generate at index 0, otherwise the
/// highest index found in the current wallet db.

View file

@ -39,6 +39,7 @@ import 'package:stackwallet/wallets/wallet/impl/monero_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/namecoin_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/nano_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/particl_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/solana_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/stellar_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
import 'package:stackwallet/wallets/wallet/impl/tezos_wallet.dart';
@ -362,6 +363,9 @@ abstract class Wallet<T extends CryptoCurrency> {
case Coin.particl:
return ParticlWallet(CryptoCurrencyNetwork.main);
case Coin.solana:
return SolanaWallet(CryptoCurrencyNetwork.main);
case Coin.stellar:
return StellarWallet(CryptoCurrencyNetwork.main);
case Coin.stellarTestnet:
@ -393,10 +397,10 @@ abstract class Wallet<T extends CryptoCurrency> {
}
void _periodicPingCheck() async {
bool hasNetwork = await pingCheck();
final bool hasNetwork = await pingCheck();
if (_isConnected != hasNetwork) {
NodeConnectionStatus status = hasNetwork
final NodeConnectionStatus status = hasNetwork
? NodeConnectionStatus.connected
: NodeConnectionStatus.disconnected;
GlobalEventBus.instance.fire(
@ -633,7 +637,7 @@ abstract class Wallet<T extends CryptoCurrency> {
// Close the subscription if this wallet is not in the list to be synced.
if (!prefs.walletIdsSyncOnStartup.contains(walletId)) {
// Check if there's another wallet of this coin on the sync list.
List<String> walletIds = [];
final List<String> walletIds = [];
for (final id in prefs.walletIdsSyncOnStartup) {
final wallet = mainDB.isar.walletInfo
.where()

View file

@ -1,4 +1,5 @@
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitbox/src/utils/network.dart' as bitbox_utils;
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
@ -89,8 +90,17 @@ mixin BCashInterface on Bip39HDWallet, ElectrumXInterface {
try {
// Sign the transaction accordingly
for (int i = 0; i < utxoSigningData.length; i++) {
final bitboxEC = bitbox.ECPair.fromWIF(
utxoSigningData[i].keyPair!.toWIF(),
final bitboxEC = bitbox.ECPair.fromPrivateKey(
utxoSigningData[i].keyPair!.privateKey.data,
network: bitbox_utils.Network(
cryptoCurrency.networkParams.privHDPrefix,
cryptoCurrency.networkParams.pubHDPrefix,
cryptoCurrency.network == CryptoCurrencyNetwork.test,
cryptoCurrency.networkParams.p2pkhPrefix,
cryptoCurrency.networkParams.wifPrefix,
cryptoCurrency.networkParams.p2pkhPrefix,
),
compressed: utxoSigningData[i].keyPair!.privateKey.compressed,
);
builder.sign(

View file

@ -1,14 +1,11 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'package:bip47/src/util.dart';
import 'package:bitcoindart/bitcoindart.dart' as bitcoindart;
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
import 'package:electrum_adapter/electrum_adapter.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_chain_height_service.dart';
import 'package:stackwallet/electrumx_rpc/client_manager.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
@ -16,29 +13,23 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/paynym_is_api.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/bitcoin_wallet.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
import 'package:stream_channel/stream_channel.dart';
mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
late ElectrumXClient electrumXClient;
late StreamChannel electrumAdapterChannel;
late ElectrumClient electrumAdapterClient;
late CachedElectrumXClient electrumXCachedClient;
// late SubscribableElectrumXClient subscribableElectrumXClient;
late ChainHeightServiceManager chainHeightServiceManager;
int? get maximumFeerate => null;
@ -246,6 +237,13 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
}
final int amount = satoshiAmountToSend - feeForOneOutput;
if (amount < 0) {
throw Exception(
"Estimated fee ($feeForOneOutput sats) is greater than balance!",
);
}
final data = await buildTransaction(
txData: txData.copyWith(
recipients: await _helperRecipientsConvert(
@ -544,18 +542,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
);
}
final convertedNetwork = bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
);
final root = await getRootHDNode();
for (final sd in signingData) {
@ -596,72 +582,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
"Failed to fetch signing data. Local db corrupt. Rescan wallet.");
}
// final coinlib.Input input;
final pubKey = keys.publicKey.data;
final bitcoindart.PaymentData data;
switch (sd.derivePathType) {
case DerivePathType.bip44:
// input = coinlib.P2PKHInput(
// prevOut: coinlib.OutPoint.fromHex(sd.utxo.txid, sd.utxo.vout),
// publicKey: keys.publicKey,
// );
data = bitcoindart
.P2PKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip49:
final p2wpkh = bitcoindart
.P2WPKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
sd.redeemScript = p2wpkh.output;
data = bitcoindart
.P2SH(
data: bitcoindart.PaymentData(redeem: p2wpkh),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip84:
// input = coinlib.P2WPKHInput(
// prevOut: coinlib.OutPoint.fromHex(sd.utxo.txid, sd.utxo.vout),
// publicKey: keys.publicKey,
// );
data = bitcoindart
.P2WPKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
break;
default:
throw Exception("DerivePathType unsupported");
}
// sd.output = input.script!.compiled;
sd.output = data.output!;
sd.keyPair = bitcoindart.ECPair.fromPrivateKey(
keys.privateKey.data,
compressed: keys.privateKey.compressed,
network: convertedNetwork,
);
sd.keyPair = keys;
}
return signingData;
@ -680,43 +601,84 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
Logging.instance
.log("Starting buildTransaction ----------", level: LogLevel.Info);
// TODO: use coinlib
final txb = bitcoindart.TransactionBuilder(
network: bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
),
maximumFeeRate: maximumFeerate,
);
const version = 1; // TODO possibly override this for certain coins?
txb.setVersion(version);
// temp tx data to show in gui while waiting for real data from server
final List<InputV2> tempInputs = [];
final List<OutputV2> tempOutputs = [];
final List<coinlib.Output> prevOuts = [];
coinlib.Transaction clTx = coinlib.Transaction(
version: 1, // TODO: check if we can use 3 (as is default in coinlib)
inputs: [],
outputs: [],
);
// Add transaction inputs
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
cryptoCurrency.networkParams.bech32Hrp,
final hash = Uint8List.fromList(
txid.toUint8ListFromHex.reversed.toList(),
);
final prevOutpoint = coinlib.OutPoint(
hash,
utxoSigningData[i].utxo.vout,
);
final prevOutput = coinlib.Output.fromAddress(
BigInt.from(utxoSigningData[i].utxo.value),
coinlib.Address.fromString(
utxoSigningData[i].utxo.address!,
cryptoCurrency.networkParams,
),
);
prevOuts.add(prevOutput);
final coinlib.Input input;
switch (utxoSigningData[i].derivePathType) {
case DerivePathType.bip44:
case DerivePathType.bch44:
input = coinlib.P2PKHInput(
prevOut: prevOutpoint,
publicKey: utxoSigningData[i].keyPair!.publicKey,
sequence: 0xffffffff - 1,
);
// TODO: fix this as it is (probably) wrong!
case DerivePathType.bip49:
throw Exception("TODO p2sh");
// input = coinlib.P2SHMultisigInput(
// prevOut: prevOutpoint,
// program: coinlib.MultisigProgram.decompile(
// utxoSigningData[i].redeemScript!,
// ),
// sequence: 0xffffffff - 1,
// );
case DerivePathType.bip84:
input = coinlib.P2WPKHInput(
prevOut: prevOutpoint,
publicKey: utxoSigningData[i].keyPair!.publicKey,
sequence: 0xffffffff - 1,
);
case DerivePathType.bip86:
input = coinlib.TaprootKeyInput(prevOut: prevOutpoint);
default:
throw UnsupportedError(
"Unknown derivation path type found: ${utxoSigningData[i].derivePathType}",
);
}
clTx = clTx.addInput(input);
tempInputs.add(
InputV2.isarCantDoRequiredInDefaultConstructor(
scriptSigHex: txb.inputs.first.script?.toHex,
scriptSigHex: input.scriptSig.toHex,
scriptSigAsm: null,
sequence: 0xffffffff - 1,
outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor(
@ -737,12 +699,18 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
// Add transaction output
for (var i = 0; i < txData.recipients!.length; i++) {
txb.addOutput(
final address = coinlib.Address.fromString(
normalizeAddress(txData.recipients![i].address),
txData.recipients![i].amount.raw.toInt(),
cryptoCurrency.networkParams.bech32Hrp,
cryptoCurrency.networkParams,
);
final output = coinlib.Output.fromAddress(
txData.recipients![i].amount.raw,
address,
);
clTx = clTx.addOutput(output);
tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: "000000",
@ -765,12 +733,22 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
try {
// Sign the transaction accordingly
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
overridePrefix: cryptoCurrency.networkParams.bech32Hrp,
final value = BigInt.from(utxoSigningData[i].utxo.value);
coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey;
if (clTx.inputs[i] is coinlib.TaprootKeyInput) {
final taproot = coinlib.Taproot(
internalKey: utxoSigningData[i].keyPair!.publicKey,
);
key = taproot.tweakPrivateKey(key);
}
clTx = clTx.sign(
inputN: i,
value: value,
key: key,
prevOuts: prevOuts,
);
}
} catch (e, s) {
@ -779,22 +757,19 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
rethrow;
}
final builtTx = txb.build(cryptoCurrency.networkParams.bech32Hrp);
final vSize = builtTx.virtualSize();
return txData.copyWith(
raw: builtTx.toHex(),
vSize: vSize,
raw: clTx.toHex(),
vSize: clTx.size,
tempTx: TransactionV2(
walletId: walletId,
blockHash: null,
hash: builtTx.getId(),
txid: builtTx.getId(),
hash: clTx.hashHex,
txid: clTx.txid,
height: null,
timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(tempInputs),
outputs: List.unmodifiable(tempOutputs),
version: version,
version: clTx.version,
type:
tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) &&
txData.paynymAccountLite == null
@ -808,24 +783,9 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
Future<int> fetchChainHeight() async {
try {
// Get the chain height service for the current coin.
ChainHeightService? service = ChainHeightServiceManager.getService(
cryptoCurrency.coin,
return await ClientManager.sharedInstance.getChainHeightFor(
cryptoCurrency,
);
// ... or create a new one if it doesn't exist.
if (service == null) {
service = ChainHeightService(client: electrumAdapterClient);
ChainHeightServiceManager.add(service, cryptoCurrency.coin);
}
// If the service hasn't been started, start it and fetch the chain height.
if (!service.started) {
return await service.fetchHeightAndStartListenForUpdates();
}
// Return the height as per the service if available or the cached height.
return service.height ?? info.cachedChainHeight;
} catch (e, s) {
Logging.instance.log(
"Exception rethrown in fetchChainHeight\nError: $e\nStack trace: $s",
@ -891,8 +851,8 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
final newNode = await _getCurrentElectrumXNode();
try {
await electrumXClient.electrumAdapterClient?.close();
} catch (e, s) {
await electrumXClient.closeAdapter();
} catch (e) {
if (e.toString().contains("initialized")) {
// Ignore. This should happen every first time the wallet is opened.
} else {
@ -904,50 +864,11 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
node: newNode,
prefs: prefs,
failovers: failovers,
coin: cryptoCurrency.coin,
cryptoCurrency: cryptoCurrency,
);
electrumAdapterChannel = await electrum_adapter.connect(
newNode.address,
port: newNode.port,
acceptUnverified: true,
useSSL: newNode.useSSL,
proxyInfo: Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null,
);
if (electrumXClient.coin == Coin.firo ||
electrumXClient.coin == Coin.firoTestNet) {
electrumAdapterClient = FiroElectrumClient(
electrumAdapterChannel,
newNode.address,
newNode.port,
newNode.useSSL,
Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null);
} else {
electrumAdapterClient = ElectrumClient(
electrumAdapterChannel,
newNode.address,
newNode.port,
newNode.useSSL,
Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo()
: null);
}
electrumXCachedClient = CachedElectrumXClient.from(
electrumXClient: electrumXClient,
electrumAdapterClient: electrumAdapterClient,
electrumAdapterUpdateCallback: updateClient,
);
// Replaced using electrum_adapters' SubscribableClient in fetchChainHeight.
// subscribableElectrumXClient = SubscribableElectrumXClient.from(
// node: newNode,
// prefs: prefs,
// failovers: failovers,
// );
// await subscribableElectrumXClient.connect(
// host: newNode.address, port: newNode.port);
}
//============================================================================
@ -1175,8 +1096,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
coin: cryptoCurrency.coin,
);
print("txn: $txn");
final vout = jsonUTXO["tx_pos"] as int;
final outputs = txn["vout"] as List;
@ -1244,13 +1163,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
await updateElectrumX();
}
Future<ElectrumClient> updateClient() async {
Logging.instance.log("Updating electrum node and ElectrumAdapterClient.",
level: LogLevel.Info);
await updateNode();
return electrumAdapterClient;
}
FeeObject? _cachedFees;
@override
@ -1778,9 +1690,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
}
}
@override
Future<void> checkSaveInitialReceivingAddress() async {}
@override
Future<void> init() async {
try {

View file

@ -742,18 +742,20 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
Future<TxData> buildMintTransaction({required TxData txData}) async {
final signingData = await fetchBuildTxData(txData.utxos!.toList());
final txb = bitcoindart.TransactionBuilder(
network: bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
final convertedNetwork = bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
);
final txb = bitcoindart.TransactionBuilder(
network: convertedNetwork,
);
txb.setVersion(2);
@ -763,11 +765,62 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
int amount = 0;
// Add transaction inputs
for (var i = 0; i < signingData.length; i++) {
final pubKey = signingData[i].keyPair!.publicKey.data;
final bitcoindart.PaymentData? data;
switch (signingData[i].derivePathType) {
case DerivePathType.bip44:
data = bitcoindart
.P2PKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip49:
final p2wpkh = bitcoindart
.P2WPKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
data = bitcoindart
.P2SH(
data: bitcoindart.PaymentData(redeem: p2wpkh),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip84:
data = bitcoindart
.P2WPKH(
data: bitcoindart.PaymentData(
pubkey: pubKey,
),
network: convertedNetwork,
)
.data;
break;
case DerivePathType.bip86:
data = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
txb.addInput(
signingData[i].utxo.txid,
signingData[i].utxo.vout,
null,
signingData[i].output,
data!.output!,
);
amount += signingData[i].utxo.value;
}
@ -782,7 +835,11 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
for (var i = 0; i < signingData.length; i++) {
txb.sign(
vin: i,
keyPair: signingData[i].keyPair!,
keyPair: bitcoindart.ECPair.fromPrivateKey(
signingData[i].keyPair!.privateKey.data,
network: convertedNetwork,
compressed: signingData[i].keyPair!.privateKey.compressed,
),
witnessValue: signingData[i].utxo.value,
);
}

View file

@ -7,6 +7,7 @@ import 'package:bip47/bip47.dart';
import 'package:bitcoindart/bitcoindart.dart' as btc_dart;
import 'package:bitcoindart/src/utils/constants/op.dart' as op;
import 'package:bitcoindart/src/utils/script.dart' as bscript;
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:isar/isar.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
@ -20,6 +21,7 @@ import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/bip32_utils.dart';
import 'package:stackwallet/utilities/bip47_utils.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -290,7 +292,13 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
return _cachedRootNode ??= await Bip32Utils.getBip32Root(
(await getMnemonic()),
(await getMnemonicPassphrase()),
networkType,
bip32.NetworkType(
wif: networkType.wif,
bip32: bip32.Bip32Type(
public: networkType.bip32.public,
private: networkType.bip32.private,
),
),
);
}
@ -701,7 +709,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
final myKeyPair = utxoSigningData.first.keyPair!;
final S = SecretPoint(
myKeyPair.privateKey!,
myKeyPair.privateKey.data,
targetPaymentCode.notificationPublicKey(),
);
@ -719,63 +727,146 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
]);
// build a notification tx
final txb = btc_dart.TransactionBuilder(network: networkType);
txb.setVersion(1);
txb.addInput(
utxo.txid,
txPointIndex,
null,
utxoSigningData.first.output!,
final List<coinlib.Output> prevOuts = [];
coinlib.Transaction clTx = coinlib.Transaction(
version: 1,
inputs: [],
outputs: [],
);
// add rest of possible inputs
for (var i = 1; i < utxoSigningData.length; i++) {
final utxo = utxoSigningData[i].utxo;
txb.addInput(
utxo.txid,
utxo.vout,
null,
utxoSigningData[i].output!,
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
final hash = Uint8List.fromList(
txid.toUint8ListFromHex.reversed.toList(),
);
final prevOutpoint = coinlib.OutPoint(
hash,
utxoSigningData[i].utxo.vout,
);
final prevOutput = coinlib.Output.fromAddress(
BigInt.from(utxoSigningData[i].utxo.value),
coinlib.Address.fromString(
utxoSigningData[i].utxo.address!,
cryptoCurrency.networkParams,
),
);
prevOuts.add(prevOutput);
final coinlib.Input input;
switch (utxoSigningData[i].derivePathType) {
case DerivePathType.bip44:
case DerivePathType.bch44:
input = coinlib.P2PKHInput(
prevOut: prevOutpoint,
publicKey: utxoSigningData[i].keyPair!.publicKey,
sequence: 0xffffffff - 1,
);
// TODO: fix this as it is (probably) wrong! (unlikely used in paynyms)
case DerivePathType.bip49:
throw Exception("TODO p2sh");
// input = coinlib.P2SHMultisigInput(
// prevOut: prevOutpoint,
// program: coinlib.MultisigProgram.decompile(
// utxoSigningData[i].redeemScript!,
// ),
// sequence: 0xffffffff - 1,
// );
case DerivePathType.bip84:
input = coinlib.P2WPKHInput(
prevOut: prevOutpoint,
publicKey: utxoSigningData[i].keyPair!.publicKey,
sequence: 0xffffffff - 1,
);
case DerivePathType.bip86:
input = coinlib.TaprootKeyInput(prevOut: prevOutpoint);
default:
throw UnsupportedError(
"Unknown derivation path type found: ${utxoSigningData[i].derivePathType}",
);
}
clTx = clTx.addInput(input);
}
final String notificationAddress =
targetPaymentCode.notificationAddressP2PKH();
txb.addOutput(
notificationAddress,
(overrideAmountForTesting ?? cryptoCurrency.dustLimitP2PKH.raw).toInt(),
final address = coinlib.Address.fromString(
normalizeAddress(notificationAddress),
cryptoCurrency.networkParams,
);
final output = coinlib.Output.fromAddress(
overrideAmountForTesting ?? cryptoCurrency.dustLimitP2PKH.raw,
address,
);
clTx = clTx.addOutput(output);
clTx = clTx.addOutput(
coinlib.Output.fromScriptBytes(
BigInt.zero,
opReturnScript,
),
);
txb.addOutput(opReturnScript, 0);
// TODO: add possible change output and mark output as dangerous
if (change > BigInt.zero) {
// generate new change address if current change address has been used
await checkChangeAddressForTransactions();
final String changeAddress = (await getCurrentChangeAddress())!.value;
txb.addOutput(changeAddress, change.toInt());
final output = coinlib.Output.fromAddress(
change,
coinlib.Address.fromString(
normalizeAddress(changeAddress),
cryptoCurrency.networkParams,
),
);
clTx = clTx.addOutput(output);
}
txb.sign(
vin: 0,
keyPair: myKeyPair,
witnessValue: utxo.value,
witnessScript: utxoSigningData.first.redeemScript,
clTx = clTx.sign(
inputN: 0,
value: BigInt.from(utxo.value),
key: myKeyPair.privateKey,
prevOuts: prevOuts,
);
// sign rest of possible inputs
for (var i = 1; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
witnessScript: utxoSigningData[i].redeemScript,
for (int i = 1; i < utxoSigningData.length; i++) {
final value = BigInt.from(utxoSigningData[i].utxo.value);
coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey;
if (clTx.inputs[i] is coinlib.TaprootKeyInput) {
final taproot = coinlib.Taproot(
internalKey: utxoSigningData[i].keyPair!.publicKey,
);
key = taproot.tweakPrivateKey(key);
}
clTx = clTx.sign(
inputN: i,
value: value,
key: key,
prevOuts: prevOuts,
);
}
final builtTx = txb.build();
return Tuple2(builtTx.toHex(), builtTx.virtualSize());
return Tuple2(clTx.toHex(), clTx.size);
} catch (e, s) {
Logging.instance.log(
"_createNotificationTx(): $e\n$s",

View file

@ -13,6 +13,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/signing_data.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
@ -1001,13 +1002,64 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
for (final sd in setCoins) {
vin.add(sd);
final pubKey = sd.keyPair!.publicKey.data;
final btc.PaymentData? data;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = btc
.P2PKH(
data: btc.PaymentData(
pubkey: pubKey,
),
network: _bitcoinDartNetwork,
)
.data;
break;
case DerivePathType.bip49:
final p2wpkh = btc
.P2WPKH(
data: btc.PaymentData(
pubkey: pubKey,
),
network: _bitcoinDartNetwork,
)
.data;
data = btc
.P2SH(
data: btc.PaymentData(redeem: p2wpkh),
network: _bitcoinDartNetwork,
)
.data;
break;
case DerivePathType.bip84:
data = btc
.P2WPKH(
data: btc.PaymentData(
pubkey: pubKey,
),
network: _bitcoinDartNetwork,
)
.data;
break;
case DerivePathType.bip86:
data = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
// add to dummy tx
dummyTxb.addInput(
sd.utxo.txid,
sd.utxo.vout,
0xffffffff -
1, // minus 1 is important. 0xffffffff on its own will burn funds
sd.output,
data!.output!,
);
}
@ -1015,9 +1067,15 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
for (var i = 0; i < setCoins.length; i++) {
dummyTxb.sign(
vin: i,
keyPair: setCoins[i].keyPair!,
keyPair: btc.ECPair.fromPrivateKey(
setCoins[i].keyPair!.privateKey.data,
network: _bitcoinDartNetwork,
compressed: setCoins[i].keyPair!.privateKey.compressed,
),
witnessValue: setCoins[i].utxo.value,
redeemScript: setCoins[i].redeemScript,
// maybe not needed here as this was originally copied from btc? We'll find out...
// redeemScript: setCoins[i].redeemScript,
);
}
@ -1114,12 +1172,63 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
txb.setVersion(txVersion);
txb.setLockTime(lockTime);
for (final input in vin) {
final pubKey = input.keyPair!.publicKey.data;
final btc.PaymentData? data;
switch (input.derivePathType) {
case DerivePathType.bip44:
data = btc
.P2PKH(
data: btc.PaymentData(
pubkey: pubKey,
),
network: _bitcoinDartNetwork,
)
.data;
break;
case DerivePathType.bip49:
final p2wpkh = btc
.P2WPKH(
data: btc.PaymentData(
pubkey: pubKey,
),
network: _bitcoinDartNetwork,
)
.data;
data = btc
.P2SH(
data: btc.PaymentData(redeem: p2wpkh),
network: _bitcoinDartNetwork,
)
.data;
break;
case DerivePathType.bip84:
data = btc
.P2WPKH(
data: btc.PaymentData(
pubkey: pubKey,
),
network: _bitcoinDartNetwork,
)
.data;
break;
case DerivePathType.bip86:
data = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
txb.addInput(
input.utxo.txid,
input.utxo.vout,
0xffffffff -
1, // minus 1 is important. 0xffffffff on its own will burn funds
input.output,
data!.output!,
);
tempInputs.add(
@ -1172,9 +1281,15 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
for (var i = 0; i < vin.length; i++) {
txb.sign(
vin: i,
keyPair: vin[i].keyPair!,
keyPair: btc.ECPair.fromPrivateKey(
vin[i].keyPair!.privateKey.data,
network: _bitcoinDartNetwork,
compressed: vin[i].keyPair!.privateKey.compressed,
),
witnessValue: vin[i].utxo.value,
redeemScript: vin[i].redeemScript,
// maybe not needed here as this was originally copied from btc? We'll find out...
// redeemScript: setCoins[i].redeemScript,
);
}
} catch (e, s) {

View file

@ -15,29 +15,31 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/icon_widgets/pencil_icon.dart';
import 'package:tuple/tuple.dart';
import '../desktop/desktop_dialog.dart';
import '../icon_widgets/pencil_icon.dart';
class SimpleEditButton extends StatelessWidget {
const SimpleEditButton({
Key? key,
super.key,
this.editValue,
this.editLabel,
this.overrideTitle,
this.disableIcon = false,
this.onValueChanged,
this.onPressedOverride,
}) : assert(
}) : assert(
(editLabel != null && editValue != null && onValueChanged != null) ||
(editLabel == null &&
editValue == null &&
onValueChanged == null &&
onPressedOverride != null),
),
super(key: key);
);
final String? editValue;
final String? editLabel;
final String? overrideTitle;
final bool disableIcon;
final void Function(String)? onValueChanged;
final VoidCallback? onPressedOverride;
@ -101,17 +103,20 @@ class SimpleEditButton extends StatelessWidget {
},
child: Row(
children: [
SvgPicture.asset(
Assets.svg.pencil,
width: 10,
height: 10,
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
),
const SizedBox(
width: 4,
),
if (!disableIcon)
SvgPicture.asset(
Assets.svg.pencil,
width: 10,
height: 10,
color:
Theme.of(context).extension<StackColors>()!.infoItemIcons,
),
if (!disableIcon)
const SizedBox(
width: 4,
),
Text(
"Edit",
overrideTitle ?? "Edit",
style: STextStyles.link2(context),
),
],

View file

@ -26,6 +26,7 @@ class BasicDialog extends StatelessWidget {
this.desktopHeight = 474,
this.desktopWidth = 641,
this.canPopWithBackButton = false,
this.flex = false,
}) : super(key: key);
final Widget? leftButton;
@ -41,6 +42,8 @@ class BasicDialog extends StatelessWidget {
final bool canPopWithBackButton;
final bool flex;
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
@ -64,6 +67,10 @@ class BasicDialog extends StatelessWidget {
],
),
),
if (flex)
const Spacer(
flex: 2,
),
if (message != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
@ -72,6 +79,10 @@ class BasicDialog extends StatelessWidget {
style: STextStyles.desktopTextSmall(context),
),
),
if (flex)
const Spacer(
flex: 3,
),
if (leftButton != null || rightButton != null)
const SizedBox(
height: 32,

View file

@ -0,0 +1,44 @@
import 'package:flutter/cupertino.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/dialogs/basic_dialog.dart';
class TorWarningDialog extends StatelessWidget {
final Coin coin;
final VoidCallback? onContinue;
final VoidCallback? onCancel;
TorWarningDialog({
Key? key,
required this.coin,
this.onContinue,
this.onCancel,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BasicDialog(
title: "Warning! Tor not supported.",
message: "${coin.prettyName} is not compatible with Tor. "
"Continuing will leak your IP address."
"\n\nAre you sure you want to continue?",
// A PrimaryButton widget:
leftButton: PrimaryButton(
label: "Cancel",
onPressed: () {
onCancel?.call();
Navigator.of(context).pop(false);
},
),
rightButton: SecondaryButton(
label: "Continue",
onPressed: () {
onContinue?.call();
Navigator.of(context).pop(true);
},
),
flex: true,
);
}
}

View file

@ -13,15 +13,17 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:solana/solana.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
import 'package:stackwallet/providers/global/active_wallet_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -171,17 +173,14 @@ class _NodeCardState extends ConsumerState<NodeCard> {
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node.host,
port: node.port,
useSSL: node.useSSL,
failovers: [],
prefs: ref.read(prefsChangeNotifierProvider),
coin: widget.coin,
);
try {
testPassed = await client.ping();
testPassed = await checkElectrumServer(
host: node.host,
port: node.port,
useSSL: node.useSSL,
overridePrefs: ref.read(prefsChangeNotifierProvider),
overrideTorService: ref.read(pTorService),
);
} catch (_) {
testPassed = false;
}
@ -215,6 +214,20 @@ class _NodeCardState extends ConsumerState<NodeCard> {
testPassed = false;
}
break;
case Coin.solana:
try {
RpcClient rpcClient;
if (node.host.startsWith("http") || node.host.startsWith("https")) {
rpcClient = RpcClient("${node.host}:${node.port}");
} else {
rpcClient = RpcClient("http://${node.host}:${node.port}");
}
await rpcClient.getEpochInfo().then((value) => testPassed = true);
} catch (_) {
testPassed = false;
}
break;
}
if (testPassed) {

View file

@ -13,7 +13,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:solana/solana.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
@ -23,6 +23,7 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -153,18 +154,14 @@ class NodeOptionsSheet extends ConsumerWidget {
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node.host,
port: node.port,
useSSL: node.useSSL,
failovers: [],
prefs: ref.read(prefsChangeNotifierProvider),
torService: ref.read(pTorService),
coin: coin,
);
try {
testPassed = await client.ping();
testPassed = await checkElectrumServer(
host: node.host,
port: node.port,
useSSL: node.useSSL,
overridePrefs: ref.read(prefsChangeNotifierProvider),
overrideTorService: ref.read(pTorService),
);
} catch (_) {
testPassed = false;
}
@ -186,6 +183,20 @@ class NodeOptionsSheet extends ConsumerWidget {
case Coin.stellarTestnet:
throw UnimplementedError();
//TODO: check network/node
case Coin.solana:
try {
RpcClient rpcClient;
if (node.host.startsWith("http") || node.host.startsWith("https")) {
rpcClient = RpcClient("${node.host}:${node.port}");
} else {
rpcClient = RpcClient("http://${node.host}:${node.port}");
}
await rpcClient.getEpochInfo().then((value) => testPassed = true);
} catch (_) {
testPassed = false;
}
break;
}
if (testPassed) {

View file

@ -195,4 +195,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.11.3
COCOAPODS: 1.14.3

View file

@ -277,7 +277,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -158,6 +158,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
borsh_annotation:
dependency: transitive
description:
name: borsh_annotation
sha256: "4a226cf8b7a165ecf8020c0c8d366b2728167fd102ef9b9e89d94d86f89ac57b"
url: "https://pub.dev"
source: hosted
version: "0.3.1+5"
bs58check:
dependency: "direct main"
description:
@ -271,23 +279,21 @@ packages:
source: hosted
version: "4.6.0"
coinlib:
dependency: "direct overridden"
dependency: transitive
description:
path: coinlib
ref: "376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7"
resolved-ref: "376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7"
url: "https://github.com/cypherstack/coinlib.git"
source: git
version: "1.1.0"
name: coinlib
sha256: "44aa3f7b07d3b03d58353e7657f43cdaf76a70ad2cce5bdac9306208099d8df5"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
coinlib_flutter:
dependency: "direct main"
description:
path: coinlib_flutter
ref: "376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7"
resolved-ref: "376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7"
url: "https://github.com/cypherstack/coinlib.git"
source: git
version: "1.1.0"
name: coinlib_flutter
sha256: b352378773158dbaec37bd542c297682f3812f9881acb676971f0f4c5893631f
url: "https://pub.dev"
source: hosted
version: "2.0.0"
collection:
dependency: transitive
description:
@ -324,10 +330,10 @@ packages:
dependency: transitive
description:
name: coverage
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
url: "https://pub.dev"
source: hosted
version: "1.6.3"
version: "1.7.2"
cross_file:
dependency: transitive
description:
@ -508,6 +514,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.9"
ed25519_hd_key:
dependency: transitive
description:
name: ed25519_hd_key
sha256: c5c9f11a03f5789bf9dcd9ae88d641571c802640851f1cacdb13123f171b3a26
url: "https://pub.dev"
source: hosted
version: "2.2.1"
eip1559:
dependency: transitive
description:
@ -593,10 +607,10 @@ packages:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
@ -674,8 +688,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "3f986ca1a94bdac5d31373454c989cc2f5842de8"
resolved-ref: "3f986ca1a94bdac5d31373454c989cc2f5842de8"
ref: "65a6676e37f1bcaf2e293afbd50e50c81394276c"
resolved-ref: "65a6676e37f1bcaf2e293afbd50e50c81394276c"
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
source: git
version: "0.0.1"
@ -691,26 +705,26 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64"
sha256: f9a05409385b77b06c18f200a41c7c2711ebf7415669350bb0f8474c07bd40d1
url: "https://pub.dev"
source: hosted
version: "9.9.1"
version: "17.0.0"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "4.0.0+1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a"
sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "7.0.0+1"
flutter_mobx:
dependency: transitive
description:
@ -817,6 +831,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d
url: "https://pub.dev"
source: hosted
version: "2.4.1"
frontend_server_client:
dependency: transitive
description:
@ -841,8 +863,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
resolved-ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
ref: "7dd8ff0dc9cb0caaac795fa44841a26437edfec3"
resolved-ref: "7dd8ff0dc9cb0caaac795fa44841a26437edfec3"
url: "https://github.com/cypherstack/fusiondart.git"
source: git
version: "1.0.0"
@ -1043,6 +1065,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lelantus:
dependency: "direct main"
description:
@ -1086,18 +1132,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.8.0"
memoize:
dependency: transitive
description:
@ -1110,10 +1156,10 @@ packages:
dependency: "direct main"
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -1214,10 +1260,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_parsing:
dependency: transitive
description:
@ -1334,10 +1380,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
@ -1374,10 +1420,10 @@ packages:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
version: "5.0.2"
protobuf:
dependency: transitive
description:
@ -1523,12 +1569,12 @@ packages:
dependency: "direct main"
description:
name: socks5_proxy
sha256: e0cba6917cd374de6f6cb0ce081e50e6efc24c61644b8e9f20c8bf8b91bb0b75
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
url: "https://pub.dev"
source: hosted
version: "1.0.3+dev.3"
version: "1.0.4"
socks_socket:
dependency: "direct main"
dependency: transitive
description:
path: "."
ref: master
@ -1536,6 +1582,15 @@ packages:
url: "https://github.com/cypherstack/socks_socket.git"
source: git
version: "0.1.0"
solana:
dependency: "direct main"
description:
path: "packages/solana"
ref: a83e375678eb22fe544dc125d29bbec0fb833882
resolved-ref: a83e375678eb22fe544dc125d29bbec0fb833882
url: "https://github.com/cypherstack/espresso-cash-public.git"
source: git
version: "0.30.4"
source_gen:
dependency: transitive
description:
@ -1604,10 +1659,11 @@ packages:
stellar_flutter_sdk:
dependency: "direct main"
description:
name: stellar_flutter_sdk
sha256: "4c55b1b6dfbde7f89bba59a422754280715fa3b5726cff5e7eeaed454d2c4b89"
url: "https://pub.dev"
source: hosted
path: "."
ref: eca1d730e952cf6a6d64502f977cfc03876b75d4
resolved-ref: eca1d730e952cf6a6d64502f977cfc03876b75d4
url: "https://github.com/cypherstack/stellar_flutter_sdk.git"
source: git
version: "1.5.3"
stream_channel:
dependency: "direct main"
@ -1685,8 +1741,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "8a7070f533e63dd150edae99476f6853bfb25913"
resolved-ref: "8a7070f533e63dd150edae99476f6853bfb25913"
ref: "1fb2669e2b530367a449217e952f220d5e667043"
resolved-ref: "1fb2669e2b530367a449217e952f220d5e667043"
url: "https://github.com/cypherstack/tezart.git"
source: git
version: "2.0.5"
@ -1702,10 +1758,10 @@ packages:
dependency: transitive
description:
name: timezone
sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e"
sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.9.2"
timing:
dependency: transitive
description:
@ -1879,10 +1935,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "11.10.0"
version: "13.0.0"
wakelock:
dependency: "direct main"
description:
@ -1952,10 +2008,10 @@ packages:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.5.1"
web3dart:
dependency: "direct main"
description:
@ -1965,21 +2021,21 @@ packages:
source: hosted
version: "2.6.1"
web_socket_channel:
dependency: transitive
dependency: "direct main"
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.5"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
webkit_inspection_protocol:
dependency: transitive
description:
@ -1988,14 +2044,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.1"
websocket_universal:
dependency: "direct main"
description:
name: websocket_universal
sha256: "681e3050bd70b9c94617394f87a021746d24d4b3c8302be8c6b0b2a3e4209d7f"
url: "https://pub.dev"
source: hosted
version: "0.5.2"
win32:
dependency: transitive
description:
@ -2025,10 +2073,10 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "0.2.0+3"
version: "1.0.4"
xml:
dependency: transitive
description:
@ -2062,5 +2110,5 @@ packages:
source: hosted
version: "1.0.0"
sdks:
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.16.0"
dart: ">=3.3.3 <4.0.0"
flutter: ">=3.19.5"

View file

@ -11,18 +11,18 @@ description: Stack Wallet
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.10.4+217
version: 2.0.0+219
environment:
sdk: ">=3.0.2 <4.0.0"
flutter: ^3.16.0
sdk: ">=3.3.3 <4.0.0"
flutter: ^3.19.5
dependencies:
flutter:
sdk: flutter
ffi: ^2.0.1
mutex: ^3.0.0
websocket_universal: ^0.5.1
web_socket_channel: ^2.4.5
lelantus:
path: ./crypto_plugins/flutter_liblelantus
@ -33,7 +33,7 @@ dependencies:
flutter_libsparkmobile:
git:
url: https://github.com/cypherstack/flutter_libsparkmobile.git
ref: 3f986ca1a94bdac5d31373454c989cc2f5842de8
ref: 65a6676e37f1bcaf2e293afbd50e50c81394276c
flutter_libmonero:
path: ./crypto_plugins/flutter_libmonero
@ -73,13 +73,13 @@ dependencies:
fusiondart:
git:
url: https://github.com/cypherstack/fusiondart.git
ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
ref: 7dd8ff0dc9cb0caaac795fa44841a26437edfec3
# Utility plugins
http: ^0.13.0
local_auth: ^1.1.10
permission_handler: ^11.0.0
flutter_local_notifications: ^9.4.0
flutter_local_notifications: ^17.0.0
rxdart: ^0.27.3
zxcvbn: ^1.0.0
dart_numerics: ^0.0.6
@ -156,31 +156,31 @@ dependencies:
desktop_drop: ^0.4.1
nanodart: ^2.0.0
basic_utils: ^5.5.4
stellar_flutter_sdk: ^1.5.3
socks_socket:
git:
url: https://github.com/cypherstack/socks_socket.git
ref: master
stellar_flutter_sdk: # ^1.5.3
git: # TODO [prio=low]: Revert to official package once Tor support is merged upstream.
url: https://github.com/cypherstack/stellar_flutter_sdk.git
ref: eca1d730e952cf6a6d64502f977cfc03876b75d4 # tor-backport branch (based on 1.5.3).
bip340: ^0.2.0
# tezart: ^2.0.5
tezart:
git:
url: https://github.com/cypherstack/tezart.git
ref: 8a7070f533e63dd150edae99476f6853bfb25913
ref: 1fb2669e2b530367a449217e952f220d5e667043
socks5_proxy: ^1.0.3+dev.3
convert: ^3.1.1
flutter_hooks: ^0.20.3
meta: ^1.9.1
coinlib_flutter:
git:
url: https://github.com/cypherstack/coinlib.git
path: coinlib_flutter
ref: 376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7
coinlib_flutter: ^2.0.0
electrum_adapter:
git:
url: https://github.com/cypherstack/electrum_adapter.git
ref: 9e9441fc1e9ace8907256fff05fe2c607b0933b6
stream_channel: ^2.1.0
solana:
git: # TODO [prio=low]: Revert to official package once Tor support is merged upstream.
url: https://github.com/cypherstack/espresso-cash-public.git
ref: a83e375678eb22fe544dc125d29bbec0fb833882
path: packages/solana
dev_dependencies:
flutter_test:
@ -227,17 +227,6 @@ dependency_overrides:
url: https://github.com/cypherstack/bip47.git
ref: a6e7941b98a43a613708b1a12564bc17e712cfc7
coinlib_flutter:
git:
url: https://github.com/cypherstack/coinlib.git
path: coinlib_flutter
ref: 376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7
coinlib:
git:
url: https://github.com/cypherstack/coinlib.git
path: coinlib
ref: 376d520b4516d4eb7c3f0bd4b1522f7769f3f2a7
# required for dart 3, at least until a fix is merged upstream
wakelock_windows:
git:
@ -266,6 +255,8 @@ dependency_overrides:
crypto: 3.0.2
analyzer: ^5.2.0
pinenacl: ^0.3.3
http: ^0.13.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -10,11 +10,13 @@ mkdir -p build
. ./config.sh
./install_ndk.sh
(cd ../../crypto_plugins/flutter_liblelantus/scripts/android && ./build_all.sh ) &
(cd ../../crypto_plugins/flutter_libepiccash/scripts/android && ./install_ndk.sh && ./build_opensll.sh && ./build_all.sh ) &
(cd ../../crypto_plugins/flutter_libmonero/scripts/android/ && ./build_all.sh ) &&
set_rust_to_1720 &
(cd ../../crypto_plugins/frostdart/scripts/android && ./build_all.sh ) &
PLUGINS_DIR=../../crypto_plugins
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh ) &
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) &
(cd "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/ && ./build_all.sh ) &&
set_rust_to_1720 &&
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh ) &
wait
echo "Done building"

View file

@ -2,18 +2,20 @@
mkdir -p build
. ./config.sh
TOOLCHAIN_DIR=${WORKDIR}/toolchain
ANDROID_NDK_SHA256="8381c440fe61fcbb01e209211ac01b519cd6adf51ab1c2281d5daad6ca4c8c8c"
if [ ! -e "$ANDROID_NDK_ZIP" ]; then
curl https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip -o ${ANDROID_NDK_ZIP}
curl https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip -o "${ANDROID_NDK_ZIP}"
fi
echo $ANDROID_NDK_SHA256 $ANDROID_NDK_ZIP | sha256sum -c || exit 1
echo "${ANDROID_NDK_SHA256}" "${ANDROID_NDK_ZIP}" | sha256sum -c || exit 1
mkdir ../../crypto_plugins/flutter_libmonero/scripts/android/build
mkdir ../../crypto_plugins/flutter_liblelantus/scripts/android/build
mkdir ../../crypto_plugins/flutter_libepiccash/scripts/android/build
cp ${ANDROID_NDK_ZIP} ../../crypto_plugins/flutter_libmonero/scripts/android/build/
cp ${ANDROID_NDK_ZIP} ../../crypto_plugins/flutter_liblelantus/scripts/android/build/
cp ${ANDROID_NDK_ZIP} ../../crypto_plugins/flutter_libepiccash/scripts/android/build/
PLUGINS_DIR=../../crypto_plugins
mkdir -p "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/build
mkdir -p "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android/build
mkdir -p "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android/build
cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/build/
cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android/build/
cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android/build/

View file

@ -16,8 +16,8 @@ rustup target add x86_64-apple-ios
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) &
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) &
(cd ../../crypto_plugins/flutter_libmonero/scripts/ios/ && ./build_all.sh ) &
set_rust_to_1720 &
(cd ../../crypto_plugins/flutter_libmonero/scripts/ios/ && ./build_all.sh ) &&
set_rust_to_1720 &&
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) &
wait

View file

@ -8,8 +8,8 @@ set_rust_to_1671
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) &
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) &
(cd ../../crypto_plugins/flutter_libmonero/scripts/macos/ && ./build_all.sh ) &
set_rust_to_1720 &
(cd ../../crypto_plugins/flutter_libmonero/scripts/macos/ && ./build_all.sh ) &&
set_rust_to_1720 &&
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) &
wait

View file

@ -1,75 +0,0 @@
# Create C:\development
New-Item -Path 'C:\development' -ItemType Directory -ErrorAction Ignore
# $wc = [System.Net.WebClient]::new()
# $publishedHash = '8E28E54D601F0751922DE24632C1E716B4684876255CF82304A9B19E89A9CCAC'
# $FileHash = Get-FileHash -InputStream ($wc.OpenRead("C:\development\flutter_windows_3.7.12-stable.zip"))
# if (-Not [System.IO.File]::Exists("C:\development\flutter_windows_3.7.12-stable.zip") or -Not ($FileHash.Hash -eq $publishedHash)) {
# } else {
# Download flutter_windows_3.7.12-stable.zip
# Write-Output "Downloading flutter_windows_3.7.12-stable.zip"
# $ProgressPreference = 'SilentlyContinue' # Speed up download process, see https://stackoverflow.com/questions/28682642/powershell-why-is-using-invoke-webrequest-much-slower-than-a-browser-download
# Invoke-WebRequest "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.7.12-stable.zip" -OutFile "C:\development\flutter_windows_3.7.12-stable.zip"
# }
# Extract Flutter SDK
Write-Output "Extracting flutter_windows_3.7.12-stable.zip"
$progressPreference = 'SilentlyContinue' # Speed up extraction process, see https://github.com/PowerShell/Microsoft.PowerShell.Archive/issues/32#issuecomment-642582179
# Add-MpPreference -ExclusionPath C:\development
# Expand-Archive "C:\development\flutter_windows_3.7.12-stable.zip" -DestinationPath "C:\development"
Add-Type -Assembly "System.IO.Compression.Filesystem"
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\development\flutter_windows_3.7.12-stable.zip", "C:\development")
# See https://stackoverflow.com/a/69239861
function Add-Path {
param(
[Parameter(Mandatory, Position=0)]
[string] $LiteralPath,
[ValidateSet('User', 'CurrentUser', 'Machine', 'LocalMachine')]
[string] $Scope
)
Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'
$isMachineLevel = $Scope -in 'Machine', 'LocalMachine'
if ($isMachineLevel -and -not $($ErrorActionPreference = 'Continue'; net session 2>$null)) { throw "You must run AS ADMIN to update the machine-level Path environment variable." }
$regPath = 'registry::' + ('HKEY_CURRENT_USER\Environment', 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment')[$isMachineLevel]
# Note the use of the .GetValue() method to ensure that the *unexpanded* value is returned.
$currDirs = (Get-Item -LiteralPath $regPath).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne ''
if ($LiteralPath -in $currDirs) {
Write-Verbose "Already present in the persistent $(('user', 'machine')[$isMachineLevel])-level Path: $LiteralPath"
return
}
$newValue = ($currDirs + $LiteralPath) -join ';'
# Update the registry.
Set-ItemProperty -Type ExpandString -LiteralPath $regPath Path $newValue
# Broadcast WM_SETTINGCHANGE to get the Windows shell to reload the
# updated environment, via a dummy [Environment]::SetEnvironmentVariable() operation.
$dummyName = [guid]::NewGuid().ToString()
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
# Finally, also update the current session's `$env:Path` definition.
# Note: For simplicity, we always append to the in-process *composite* value,
# even though for a -Scope Machine update this isn't strictly the same.
$env:Path = ($env:Path -replace ';$') + ';' + $LiteralPath
Write-Verbose "`"$LiteralPath`" successfully appended to the persistent $(('user', 'machine')[$isMachineLevel])-level Path and also the current-process value."
}
# Add Flutter SDK to PATH if it's not there already
if ($Env:Path -split ";" -contains 'C:\development\flutter\bin') {
Write-Output "Flutter SDK in PATH, done"
} else {
Write-Output "Attempting to add Flutter SDK to PATH"
Add-Path("C:\development\flutter\bin")
}

View file

@ -3,19 +3,21 @@
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'dart:ui' as _i11;
import 'dart:async' as _i6;
import 'dart:ui' as _i12;
import 'package:decimal/decimal.dart' as _i2;
import 'package:decimal/decimal.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i4;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i7;
import 'package:stackwallet/utilities/prefs.dart' as _i6;
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i5;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i10;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i9;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i11;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i8;
import 'package:stackwallet/utilities/prefs.dart' as _i7;
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'
as _i2;
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'
as _i3;
as _i4;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@ -28,8 +30,9 @@ import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_i
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeDuration_0 extends _i1.SmartFake implements Duration {
_FakeDuration_0(
class _FakeCryptoCurrency_0 extends _i1.SmartFake
implements _i2.CryptoCurrency {
_FakeCryptoCurrency_0(
Object parent,
Invocation parentInvocation,
) : super(
@ -38,8 +41,8 @@ class _FakeDuration_0 extends _i1.SmartFake implements Duration {
);
}
class _FakeDecimal_1 extends _i1.SmartFake implements _i2.Decimal {
_FakeDecimal_1(
class _FakeDuration_1 extends _i1.SmartFake implements Duration {
_FakeDuration_1(
Object parent,
Invocation parentInvocation,
) : super(
@ -48,8 +51,18 @@ class _FakeDecimal_1 extends _i1.SmartFake implements _i2.Decimal {
);
}
class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
_FakeFusionInfo_2(
class _FakeDecimal_2 extends _i1.SmartFake implements _i3.Decimal {
_FakeDecimal_2(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeFusionInfo_3 extends _i1.SmartFake implements _i4.FusionInfo {
_FakeFusionInfo_3(
Object parent,
Invocation parentInvocation,
) : super(
@ -61,13 +74,21 @@ class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
/// A class which mocks [ElectrumXClient].
///
/// See the documentation for Mockito's code generation for more information.
class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient {
MockElectrumXClient() {
_i1.throwOnMissingStub(this);
}
@override
set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod(
_i2.CryptoCurrency get cryptoCurrency => (super.noSuchMethod(
Invocation.getter(#cryptoCurrency),
returnValue: _FakeCryptoCurrency_0(
this,
Invocation.getter(#cryptoCurrency),
),
) as _i2.CryptoCurrency);
@override
set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod(
Invocation.setter(
#failovers,
_failovers,
@ -91,7 +112,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
Duration get connectionTimeoutForSpecialCaseJsonRPCClients =>
(super.noSuchMethod(
Invocation.getter(#connectionTimeoutForSpecialCaseJsonRPCClients),
returnValue: _FakeDuration_0(
returnValue: _FakeDuration_1(
this,
Invocation.getter(#connectionTimeoutForSpecialCaseJsonRPCClients),
),
@ -112,7 +133,16 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
returnValue: false,
) as bool);
@override
_i5.Future<dynamic> request({
_i6.Future<void> closeAdapter() => (super.noSuchMethod(
Invocation.method(
#closeAdapter,
[],
),
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
@override
_i6.Future<dynamic> request({
required String? command,
List<dynamic>? args = const [],
String? requestID,
@ -131,12 +161,12 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#requestTimeout: requestTimeout,
},
),
returnValue: _i5.Future<dynamic>.value(),
) as _i5.Future<dynamic>);
returnValue: _i6.Future<dynamic>.value(),
) as _i6.Future<dynamic>);
@override
_i5.Future<List<Map<String, dynamic>>> batchRequest({
_i6.Future<List<dynamic>> batchRequest({
required String? command,
required Map<String, List<dynamic>>? args,
required List<dynamic>? args,
Duration? requestTimeout = const Duration(seconds: 60),
int? retries = 2,
}) =>
@ -151,11 +181,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#retries: retries,
},
),
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i5.Future<List<Map<String, dynamic>>>);
returnValue: _i6.Future<List<dynamic>>.value(<dynamic>[]),
) as _i6.Future<List<dynamic>>);
@override
_i5.Future<bool> ping({
_i6.Future<bool> ping({
String? requestID,
int? retryCount = 1,
}) =>
@ -168,10 +197,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#retryCount: retryCount,
},
),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
returnValue: _i6.Future<bool>.value(false),
) as _i6.Future<bool>);
@override
_i5.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
_i6.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getBlockHeadTip,
@ -179,10 +208,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
{#requestID: requestID},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
_i6.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getServerFeatures,
@ -190,10 +219,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
{#requestID: requestID},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<String> broadcastTransaction({
_i6.Future<String> broadcastTransaction({
required String? rawTx,
String? requestID,
}) =>
@ -206,10 +235,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#requestID: requestID,
},
),
returnValue: _i5.Future<String>.value(''),
) as _i5.Future<String>);
returnValue: _i6.Future<String>.value(''),
) as _i6.Future<String>);
@override
_i5.Future<Map<String, dynamic>> getBalance({
_i6.Future<Map<String, dynamic>> getBalance({
required String? scripthash,
String? requestID,
}) =>
@ -223,10 +252,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<List<Map<String, dynamic>>> getHistory({
_i6.Future<List<Map<String, dynamic>>> getHistory({
required String? scripthash,
String? requestID,
}) =>
@ -239,23 +268,23 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#requestID: requestID,
},
),
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
returnValue: _i6.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i5.Future<List<Map<String, dynamic>>>);
) as _i6.Future<List<Map<String, dynamic>>>);
@override
_i5.Future<Map<String, List<Map<String, dynamic>>>> getBatchHistory(
{required Map<String, List<dynamic>>? args}) =>
_i6.Future<List<List<Map<String, dynamic>>>> getBatchHistory(
{required List<dynamic>? args}) =>
(super.noSuchMethod(
Invocation.method(
#getBatchHistory,
[],
{#args: args},
),
returnValue: _i5.Future<Map<String, List<Map<String, dynamic>>>>.value(
<String, List<Map<String, dynamic>>>{}),
) as _i5.Future<Map<String, List<Map<String, dynamic>>>>);
returnValue: _i6.Future<List<List<Map<String, dynamic>>>>.value(
<List<Map<String, dynamic>>>[]),
) as _i6.Future<List<List<Map<String, dynamic>>>>);
@override
_i5.Future<List<Map<String, dynamic>>> getUTXOs({
_i6.Future<List<Map<String, dynamic>>> getUTXOs({
required String? scripthash,
String? requestID,
}) =>
@ -268,23 +297,23 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#requestID: requestID,
},
),
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
returnValue: _i6.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i5.Future<List<Map<String, dynamic>>>);
) as _i6.Future<List<Map<String, dynamic>>>);
@override
_i5.Future<Map<String, List<Map<String, dynamic>>>> getBatchUTXOs(
{required Map<String, List<dynamic>>? args}) =>
_i6.Future<List<List<Map<String, dynamic>>>> getBatchUTXOs(
{required List<dynamic>? args}) =>
(super.noSuchMethod(
Invocation.method(
#getBatchUTXOs,
[],
{#args: args},
),
returnValue: _i5.Future<Map<String, List<Map<String, dynamic>>>>.value(
<String, List<Map<String, dynamic>>>{}),
) as _i5.Future<Map<String, List<Map<String, dynamic>>>>);
returnValue: _i6.Future<List<List<Map<String, dynamic>>>>.value(
<List<Map<String, dynamic>>>[]),
) as _i6.Future<List<List<Map<String, dynamic>>>>);
@override
_i5.Future<Map<String, dynamic>> getTransaction({
_i6.Future<Map<String, dynamic>> getTransaction({
required String? txHash,
bool? verbose = true,
String? requestID,
@ -300,10 +329,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<Map<String, dynamic>> getLelantusAnonymitySet({
_i6.Future<Map<String, dynamic>> getLelantusAnonymitySet({
String? groupId = r'1',
String? blockhash = r'',
String? requestID,
@ -319,10 +348,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<dynamic> getLelantusMintData({
_i6.Future<dynamic> getLelantusMintData({
dynamic mints,
String? requestID,
}) =>
@ -335,10 +364,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#requestID: requestID,
},
),
returnValue: _i5.Future<dynamic>.value(),
) as _i5.Future<dynamic>);
returnValue: _i6.Future<dynamic>.value(),
) as _i6.Future<dynamic>);
@override
_i5.Future<Map<String, dynamic>> getLelantusUsedCoinSerials({
_i6.Future<Map<String, dynamic>> getLelantusUsedCoinSerials({
String? requestID,
required int? startNumber,
}) =>
@ -352,20 +381,20 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<int> getLelantusLatestCoinId({String? requestID}) =>
_i6.Future<int> getLelantusLatestCoinId({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getLelantusLatestCoinId,
[],
{#requestID: requestID},
),
returnValue: _i5.Future<int>.value(0),
) as _i5.Future<int>);
returnValue: _i6.Future<int>.value(0),
) as _i6.Future<int>);
@override
_i5.Future<Map<String, dynamic>> getSparkAnonymitySet({
_i6.Future<Map<String, dynamic>> getSparkAnonymitySet({
String? coinGroupId = r'1',
String? startBlockHash = r'',
String? requestID,
@ -381,10 +410,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<Set<String>> getSparkUsedCoinsTags({
_i6.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID,
required int? startNumber,
}) =>
@ -397,10 +426,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#startNumber: startNumber,
},
),
returnValue: _i5.Future<Set<String>>.value(<String>{}),
) as _i5.Future<Set<String>>);
returnValue: _i6.Future<Set<String>>.value(<String>{}),
) as _i6.Future<Set<String>>);
@override
_i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID,
required List<String>? sparkCoinHashes,
}) =>
@ -413,21 +442,21 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#sparkCoinHashes: sparkCoinHashes,
},
),
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
returnValue: _i6.Future<List<Map<String, dynamic>>>.value(
<Map<String, dynamic>>[]),
) as _i5.Future<List<Map<String, dynamic>>>);
) as _i6.Future<List<Map<String, dynamic>>>);
@override
_i5.Future<int> getSparkLatestCoinId({String? requestID}) =>
_i6.Future<int> getSparkLatestCoinId({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getSparkLatestCoinId,
[],
{#requestID: requestID},
),
returnValue: _i5.Future<int>.value(0),
) as _i5.Future<int>);
returnValue: _i6.Future<int>.value(0),
) as _i6.Future<int>);
@override
_i5.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
_i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
(super.noSuchMethod(
Invocation.method(
#getFeeRate,
@ -435,10 +464,10 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
{#requestID: requestID},
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>);
@override
_i5.Future<_i2.Decimal> estimateFee({
_i6.Future<_i3.Decimal> estimateFee({
String? requestID,
required int? blocks,
}) =>
@ -451,7 +480,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#blocks: blocks,
},
),
returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1(
returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2(
this,
Invocation.method(
#estimateFee,
@ -462,15 +491,15 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
},
),
)),
) as _i5.Future<_i2.Decimal>);
) as _i6.Future<_i3.Decimal>);
@override
_i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
_i6.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
Invocation.method(
#relayFee,
[],
{#requestID: requestID},
),
returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1(
returnValue: _i6.Future<_i3.Decimal>.value(_FakeDecimal_2(
this,
Invocation.method(
#relayFee,
@ -478,13 +507,13 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
{#requestID: requestID},
),
)),
) as _i5.Future<_i2.Decimal>);
) as _i6.Future<_i3.Decimal>);
}
/// A class which mocks [Prefs].
///
/// See the documentation for Mockito's code generation for more information.
class MockPrefs extends _i1.Mock implements _i6.Prefs {
class MockPrefs extends _i1.Mock implements _i7.Prefs {
MockPrefs() {
_i1.throwOnMissingStub(this);
}
@ -540,12 +569,12 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
_i7.SyncingType get syncType => (super.noSuchMethod(
_i8.SyncingType get syncType => (super.noSuchMethod(
Invocation.getter(#syncType),
returnValue: _i7.SyncingType.currentWalletOnly,
) as _i7.SyncingType);
returnValue: _i8.SyncingType.currentWalletOnly,
) as _i8.SyncingType);
@override
set syncType(_i7.SyncingType? syncType) => super.noSuchMethod(
set syncType(_i8.SyncingType? syncType) => super.noSuchMethod(
Invocation.setter(
#syncType,
syncType,
@ -704,12 +733,12 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
_i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
_i9.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
Invocation.getter(#backupFrequencyType),
returnValue: _i8.BackupFrequencyType.everyTenMinutes,
) as _i8.BackupFrequencyType);
returnValue: _i9.BackupFrequencyType.everyTenMinutes,
) as _i9.BackupFrequencyType);
@override
set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) =>
set backupFrequencyType(_i9.BackupFrequencyType? backupFrequencyType) =>
super.noSuchMethod(
Invocation.setter(
#backupFrequencyType,
@ -855,66 +884,92 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
bool get solanaEnabled => (super.noSuchMethod(
Invocation.getter(#solanaEnabled),
returnValue: false,
) as bool);
@override
set solanaEnabled(bool? solanaEnabled) => super.noSuchMethod(
Invocation.setter(
#solanaEnabled,
solanaEnabled,
),
returnValueForMissingStub: null,
);
@override
bool get frostEnabled => (super.noSuchMethod(
Invocation.getter(#frostEnabled),
returnValue: false,
) as bool);
@override
set frostEnabled(bool? frostEnabled) => super.noSuchMethod(
Invocation.setter(
#frostEnabled,
frostEnabled,
),
returnValueForMissingStub: null,
);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
_i5.Future<void> init() => (super.noSuchMethod(
_i6.Future<void> init() => (super.noSuchMethod(
Invocation.method(
#init,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
@override
_i5.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
_i6.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
Invocation.method(
#incrementCurrentNotificationIndex,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
@override
_i5.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
_i6.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
Invocation.method(
#isExternalCallsSet,
[],
),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
returnValue: _i6.Future<bool>.value(false),
) as _i6.Future<bool>);
@override
_i5.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
_i6.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
Invocation.method(
#saveUserID,
[userId],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
@override
_i5.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
_i6.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
Invocation.method(
#saveSignupEpoch,
[signupEpoch],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
@override
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
_i10.AmountUnit amountUnit(_i11.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#amountUnit,
[coin],
),
returnValue: _i9.AmountUnit.normal,
) as _i9.AmountUnit);
returnValue: _i10.AmountUnit.normal,
) as _i10.AmountUnit);
@override
void updateAmountUnit({
required _i10.Coin? coin,
required _i9.AmountUnit? amountUnit,
required _i11.Coin? coin,
required _i10.AmountUnit? amountUnit,
}) =>
super.noSuchMethod(
Invocation.method(
@ -928,7 +983,7 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
int maxDecimals(_i11.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#maxDecimals,
[coin],
@ -937,7 +992,7 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
) as int);
@override
void updateMaxDecimals({
required _i10.Coin? coin,
required _i11.Coin? coin,
required int? maxDecimals,
}) =>
super.noSuchMethod(
@ -952,23 +1007,23 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
_i3.FusionInfo getFusionServerInfo(_i10.Coin? coin) => (super.noSuchMethod(
_i4.FusionInfo getFusionServerInfo(_i11.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#getFusionServerInfo,
[coin],
),
returnValue: _FakeFusionInfo_2(
returnValue: _FakeFusionInfo_3(
this,
Invocation.method(
#getFusionServerInfo,
[coin],
),
),
) as _i3.FusionInfo);
) as _i4.FusionInfo);
@override
void setFusionServerInfo(
_i10.Coin? coin,
_i3.FusionInfo? fusionServerInfo,
_i11.Coin? coin,
_i4.FusionInfo? fusionServerInfo,
) =>
super.noSuchMethod(
Invocation.method(
@ -981,7 +1036,7 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
void addListener(_i12.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
@ -989,7 +1044,7 @@ class MockPrefs extends _i1.Mock implements _i6.Prefs {
returnValueForMissingStub: null,
);
@override
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],

File diff suppressed because it is too large Load diff

View file

@ -1,758 +0,0 @@
// Mocks generated by Mockito 5.4.2 from annotations
// in stackwallet/test/electrumx_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'dart:io' as _i4;
import 'dart:ui' as _i11;
import 'package:mockito/mockito.dart' as _i1;
import 'package:stackwallet/electrumx_rpc/rpc.dart' as _i2;
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'
as _i13;
import 'package:stackwallet/services/tor_service.dart' as _i12;
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8;
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i7;
import 'package:stackwallet/utilities/prefs.dart' as _i6;
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'
as _i3;
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i14;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeDuration_0 extends _i1.SmartFake implements Duration {
_FakeDuration_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeJsonRPCResponse_1 extends _i1.SmartFake
implements _i2.JsonRPCResponse {
_FakeJsonRPCResponse_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
_FakeFusionInfo_2(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeInternetAddress_3 extends _i1.SmartFake
implements _i4.InternetAddress {
_FakeInternetAddress_3(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [JsonRPC].
///
/// See the documentation for Mockito's code generation for more information.
class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
MockJsonRPC() {
_i1.throwOnMissingStub(this);
}
@override
bool get useSSL => (super.noSuchMethod(
Invocation.getter(#useSSL),
returnValue: false,
) as bool);
@override
String get host => (super.noSuchMethod(
Invocation.getter(#host),
returnValue: '',
) as String);
@override
int get port => (super.noSuchMethod(
Invocation.getter(#port),
returnValue: 0,
) as int);
@override
Duration get connectionTimeout => (super.noSuchMethod(
Invocation.getter(#connectionTimeout),
returnValue: _FakeDuration_0(
this,
Invocation.getter(#connectionTimeout),
),
) as Duration);
@override
set proxyInfo(({_i4.InternetAddress host, int port})? _proxyInfo) =>
super.noSuchMethod(
Invocation.setter(
#proxyInfo,
_proxyInfo,
),
returnValueForMissingStub: null,
);
@override
_i5.Future<_i2.JsonRPCResponse> request(
String? jsonRpcRequest,
Duration? requestTimeout,
) =>
(super.noSuchMethod(
Invocation.method(
#request,
[
jsonRpcRequest,
requestTimeout,
],
),
returnValue:
_i5.Future<_i2.JsonRPCResponse>.value(_FakeJsonRPCResponse_1(
this,
Invocation.method(
#request,
[
jsonRpcRequest,
requestTimeout,
],
),
)),
) as _i5.Future<_i2.JsonRPCResponse>);
@override
_i5.Future<void> disconnect({
required String? reason,
bool? ignoreMutex = false,
}) =>
(super.noSuchMethod(
Invocation.method(
#disconnect,
[],
{
#reason: reason,
#ignoreMutex: ignoreMutex,
},
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
}
/// A class which mocks [Prefs].
///
/// See the documentation for Mockito's code generation for more information.
class MockPrefs extends _i1.Mock implements _i6.Prefs {
MockPrefs() {
_i1.throwOnMissingStub(this);
}
@override
bool get isInitialized => (super.noSuchMethod(
Invocation.getter(#isInitialized),
returnValue: false,
) as bool);
@override
int get lastUnlockedTimeout => (super.noSuchMethod(
Invocation.getter(#lastUnlockedTimeout),
returnValue: 0,
) as int);
@override
set lastUnlockedTimeout(int? lastUnlockedTimeout) => super.noSuchMethod(
Invocation.setter(
#lastUnlockedTimeout,
lastUnlockedTimeout,
),
returnValueForMissingStub: null,
);
@override
int get lastUnlocked => (super.noSuchMethod(
Invocation.getter(#lastUnlocked),
returnValue: 0,
) as int);
@override
set lastUnlocked(int? lastUnlocked) => super.noSuchMethod(
Invocation.setter(
#lastUnlocked,
lastUnlocked,
),
returnValueForMissingStub: null,
);
@override
int get currentNotificationId => (super.noSuchMethod(
Invocation.getter(#currentNotificationId),
returnValue: 0,
) as int);
@override
List<String> get walletIdsSyncOnStartup => (super.noSuchMethod(
Invocation.getter(#walletIdsSyncOnStartup),
returnValue: <String>[],
) as List<String>);
@override
set walletIdsSyncOnStartup(List<String>? walletIdsSyncOnStartup) =>
super.noSuchMethod(
Invocation.setter(
#walletIdsSyncOnStartup,
walletIdsSyncOnStartup,
),
returnValueForMissingStub: null,
);
@override
_i7.SyncingType get syncType => (super.noSuchMethod(
Invocation.getter(#syncType),
returnValue: _i7.SyncingType.currentWalletOnly,
) as _i7.SyncingType);
@override
set syncType(_i7.SyncingType? syncType) => super.noSuchMethod(
Invocation.setter(
#syncType,
syncType,
),
returnValueForMissingStub: null,
);
@override
bool get wifiOnly => (super.noSuchMethod(
Invocation.getter(#wifiOnly),
returnValue: false,
) as bool);
@override
set wifiOnly(bool? wifiOnly) => super.noSuchMethod(
Invocation.setter(
#wifiOnly,
wifiOnly,
),
returnValueForMissingStub: null,
);
@override
bool get showFavoriteWallets => (super.noSuchMethod(
Invocation.getter(#showFavoriteWallets),
returnValue: false,
) as bool);
@override
set showFavoriteWallets(bool? showFavoriteWallets) => super.noSuchMethod(
Invocation.setter(
#showFavoriteWallets,
showFavoriteWallets,
),
returnValueForMissingStub: null,
);
@override
String get language => (super.noSuchMethod(
Invocation.getter(#language),
returnValue: '',
) as String);
@override
set language(String? newLanguage) => super.noSuchMethod(
Invocation.setter(
#language,
newLanguage,
),
returnValueForMissingStub: null,
);
@override
String get currency => (super.noSuchMethod(
Invocation.getter(#currency),
returnValue: '',
) as String);
@override
set currency(String? newCurrency) => super.noSuchMethod(
Invocation.setter(
#currency,
newCurrency,
),
returnValueForMissingStub: null,
);
@override
bool get randomizePIN => (super.noSuchMethod(
Invocation.getter(#randomizePIN),
returnValue: false,
) as bool);
@override
set randomizePIN(bool? randomizePIN) => super.noSuchMethod(
Invocation.setter(
#randomizePIN,
randomizePIN,
),
returnValueForMissingStub: null,
);
@override
bool get useBiometrics => (super.noSuchMethod(
Invocation.getter(#useBiometrics),
returnValue: false,
) as bool);
@override
set useBiometrics(bool? useBiometrics) => super.noSuchMethod(
Invocation.setter(
#useBiometrics,
useBiometrics,
),
returnValueForMissingStub: null,
);
@override
bool get hasPin => (super.noSuchMethod(
Invocation.getter(#hasPin),
returnValue: false,
) as bool);
@override
set hasPin(bool? hasPin) => super.noSuchMethod(
Invocation.setter(
#hasPin,
hasPin,
),
returnValueForMissingStub: null,
);
@override
int get familiarity => (super.noSuchMethod(
Invocation.getter(#familiarity),
returnValue: 0,
) as int);
@override
set familiarity(int? familiarity) => super.noSuchMethod(
Invocation.setter(
#familiarity,
familiarity,
),
returnValueForMissingStub: null,
);
@override
bool get torKillSwitch => (super.noSuchMethod(
Invocation.getter(#torKillSwitch),
returnValue: false,
) as bool);
@override
set torKillSwitch(bool? torKillswitch) => super.noSuchMethod(
Invocation.setter(
#torKillSwitch,
torKillswitch,
),
returnValueForMissingStub: null,
);
@override
bool get showTestNetCoins => (super.noSuchMethod(
Invocation.getter(#showTestNetCoins),
returnValue: false,
) as bool);
@override
set showTestNetCoins(bool? showTestNetCoins) => super.noSuchMethod(
Invocation.setter(
#showTestNetCoins,
showTestNetCoins,
),
returnValueForMissingStub: null,
);
@override
bool get isAutoBackupEnabled => (super.noSuchMethod(
Invocation.getter(#isAutoBackupEnabled),
returnValue: false,
) as bool);
@override
set isAutoBackupEnabled(bool? isAutoBackupEnabled) => super.noSuchMethod(
Invocation.setter(
#isAutoBackupEnabled,
isAutoBackupEnabled,
),
returnValueForMissingStub: null,
);
@override
set autoBackupLocation(String? autoBackupLocation) => super.noSuchMethod(
Invocation.setter(
#autoBackupLocation,
autoBackupLocation,
),
returnValueForMissingStub: null,
);
@override
_i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
Invocation.getter(#backupFrequencyType),
returnValue: _i8.BackupFrequencyType.everyTenMinutes,
) as _i8.BackupFrequencyType);
@override
set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) =>
super.noSuchMethod(
Invocation.setter(
#backupFrequencyType,
backupFrequencyType,
),
returnValueForMissingStub: null,
);
@override
set lastAutoBackup(DateTime? lastAutoBackup) => super.noSuchMethod(
Invocation.setter(
#lastAutoBackup,
lastAutoBackup,
),
returnValueForMissingStub: null,
);
@override
bool get hideBlockExplorerWarning => (super.noSuchMethod(
Invocation.getter(#hideBlockExplorerWarning),
returnValue: false,
) as bool);
@override
set hideBlockExplorerWarning(bool? hideBlockExplorerWarning) =>
super.noSuchMethod(
Invocation.setter(
#hideBlockExplorerWarning,
hideBlockExplorerWarning,
),
returnValueForMissingStub: null,
);
@override
bool get gotoWalletOnStartup => (super.noSuchMethod(
Invocation.getter(#gotoWalletOnStartup),
returnValue: false,
) as bool);
@override
set gotoWalletOnStartup(bool? gotoWalletOnStartup) => super.noSuchMethod(
Invocation.setter(
#gotoWalletOnStartup,
gotoWalletOnStartup,
),
returnValueForMissingStub: null,
);
@override
set startupWalletId(String? startupWalletId) => super.noSuchMethod(
Invocation.setter(
#startupWalletId,
startupWalletId,
),
returnValueForMissingStub: null,
);
@override
bool get externalCalls => (super.noSuchMethod(
Invocation.getter(#externalCalls),
returnValue: false,
) as bool);
@override
set externalCalls(bool? externalCalls) => super.noSuchMethod(
Invocation.setter(
#externalCalls,
externalCalls,
),
returnValueForMissingStub: null,
);
@override
bool get enableCoinControl => (super.noSuchMethod(
Invocation.getter(#enableCoinControl),
returnValue: false,
) as bool);
@override
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
Invocation.setter(
#enableCoinControl,
enableCoinControl,
),
returnValueForMissingStub: null,
);
@override
bool get enableSystemBrightness => (super.noSuchMethod(
Invocation.getter(#enableSystemBrightness),
returnValue: false,
) as bool);
@override
set enableSystemBrightness(bool? enableSystemBrightness) =>
super.noSuchMethod(
Invocation.setter(
#enableSystemBrightness,
enableSystemBrightness,
),
returnValueForMissingStub: null,
);
@override
String get themeId => (super.noSuchMethod(
Invocation.getter(#themeId),
returnValue: '',
) as String);
@override
set themeId(String? themeId) => super.noSuchMethod(
Invocation.setter(
#themeId,
themeId,
),
returnValueForMissingStub: null,
);
@override
String get systemBrightnessLightThemeId => (super.noSuchMethod(
Invocation.getter(#systemBrightnessLightThemeId),
returnValue: '',
) as String);
@override
set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) =>
super.noSuchMethod(
Invocation.setter(
#systemBrightnessLightThemeId,
systemBrightnessLightThemeId,
),
returnValueForMissingStub: null,
);
@override
String get systemBrightnessDarkThemeId => (super.noSuchMethod(
Invocation.getter(#systemBrightnessDarkThemeId),
returnValue: '',
) as String);
@override
set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) =>
super.noSuchMethod(
Invocation.setter(
#systemBrightnessDarkThemeId,
systemBrightnessDarkThemeId,
),
returnValueForMissingStub: null,
);
@override
bool get useTor => (super.noSuchMethod(
Invocation.getter(#useTor),
returnValue: false,
) as bool);
@override
set useTor(bool? useTor) => super.noSuchMethod(
Invocation.setter(
#useTor,
useTor,
),
returnValueForMissingStub: null,
);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
) as bool);
@override
_i5.Future<void> init() => (super.noSuchMethod(
Invocation.method(
#init,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
Invocation.method(
#incrementCurrentNotificationIndex,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
Invocation.method(
#isExternalCallsSet,
[],
),
returnValue: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
_i5.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
Invocation.method(
#saveUserID,
[userId],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
Invocation.method(
#saveSignupEpoch,
[signupEpoch],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#amountUnit,
[coin],
),
returnValue: _i9.AmountUnit.normal,
) as _i9.AmountUnit);
@override
void updateAmountUnit({
required _i10.Coin? coin,
required _i9.AmountUnit? amountUnit,
}) =>
super.noSuchMethod(
Invocation.method(
#updateAmountUnit,
[],
{
#coin: coin,
#amountUnit: amountUnit,
},
),
returnValueForMissingStub: null,
);
@override
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#maxDecimals,
[coin],
),
returnValue: 0,
) as int);
@override
void updateMaxDecimals({
required _i10.Coin? coin,
required int? maxDecimals,
}) =>
super.noSuchMethod(
Invocation.method(
#updateMaxDecimals,
[],
{
#coin: coin,
#maxDecimals: maxDecimals,
},
),
returnValueForMissingStub: null,
);
@override
_i3.FusionInfo getFusionServerInfo(_i10.Coin? coin) => (super.noSuchMethod(
Invocation.method(
#getFusionServerInfo,
[coin],
),
returnValue: _FakeFusionInfo_2(
this,
Invocation.method(
#getFusionServerInfo,
[coin],
),
),
) as _i3.FusionInfo);
@override
void setFusionServerInfo(
_i10.Coin? coin,
_i3.FusionInfo? fusionServerInfo,
) =>
super.noSuchMethod(
Invocation.method(
#setFusionServerInfo,
[
coin,
fusionServerInfo,
],
),
returnValueForMissingStub: null,
);
@override
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
),
returnValueForMissingStub: null,
);
@override
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
#removeListener,
[listener],
),
returnValueForMissingStub: null,
);
@override
void dispose() => super.noSuchMethod(
Invocation.method(
#dispose,
[],
),
returnValueForMissingStub: null,
);
@override
void notifyListeners() => super.noSuchMethod(
Invocation.method(
#notifyListeners,
[],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [TorService].
///
/// See the documentation for Mockito's code generation for more information.
class MockTorService extends _i1.Mock implements _i12.TorService {
MockTorService() {
_i1.throwOnMissingStub(this);
}
@override
_i13.TorConnectionStatus get status => (super.noSuchMethod(
Invocation.getter(#status),
returnValue: _i13.TorConnectionStatus.disconnected,
) as _i13.TorConnectionStatus);
@override
({_i4.InternetAddress host, int port}) getProxyInfo() => (super.noSuchMethod(
Invocation.method(
#getProxyInfo,
[],
),
returnValue: (
host: _FakeInternetAddress_3(
this,
Invocation.method(
#getProxyInfo,
[],
),
),
port: 0
),
) as ({_i4.InternetAddress host, int port}));
@override
void init({
required String? torDataDirPath,
_i14.Tor? mockableOverride,
}) =>
super.noSuchMethod(
Invocation.method(
#init,
[],
{
#torDataDirPath: torDataDirPath,
#mockableOverride: mockableOverride,
},
),
returnValueForMissingStub: null,
);
@override
_i5.Future<void> start() => (super.noSuchMethod(
Invocation.method(
#start,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> disable() => (super.noSuchMethod(
Invocation.method(
#disable,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
}

Some files were not shown because too many files have changed in this diff Show more